mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 13:42:59 +00:00 
			
		
		
		
	Merge branch 'mc-1.15.x' into mc-1.16.x
This commit is contained in:
		
							
								
								
									
										1
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							| @@ -12,4 +12,5 @@ labels: bug | ||||
| ## Useful information to include: | ||||
|  - Minecraft version | ||||
|  - CC: Tweaked version | ||||
|  - Logs: These will be located in the `logs/` directory of your Minecraft instance. Please upload them as a gist or directly into this editor. | ||||
|  - Detailed reproduction steps: sometimes I can spot a bug pretty easily, but often it's much more obscure. The more information I have to help reproduce it, the quicker it'll get fixed. | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| #  | ||||
| #  | ||||
| [](https://github.com/SquidDev-CC/CC-Tweaked/actions "Current build status") [](https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked on CurseForge") | ||||
|  | ||||
| CC: Tweaked is a fork of [ComputerCraft](https://github.com/dan200/ComputerCraft), adding programmable computers, | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| # Mod properties | ||||
| mod_version=1.90.3 | ||||
| mod_version=1.91.0 | ||||
|  | ||||
| # Minecraft properties (update mods.toml when changing) | ||||
| mc_version=1.16.2 | ||||
| forge_version=33.0.3 | ||||
| forge_version=33.0.20 | ||||
| mappings_version=20200723-1.16.1 | ||||
|   | ||||
| @@ -101,6 +101,9 @@ | ||||
|   (linters -doc:unresolved-reference)) | ||||
|  | ||||
| (at /src/test/resources/test-rom | ||||
|   ; We should still be able to test deprecated members. | ||||
|   (linters -var:deprecated) | ||||
|  | ||||
|   (lint | ||||
|     (globals | ||||
|       :max sleep write | ||||
|   | ||||
| @@ -479,6 +479,7 @@ public class FSAPI implements ILuaAPI | ||||
|             BasicFileAttributes attributes = fileSystem.getAttributes( path ); | ||||
|             Map<String, Object> result = new HashMap<>(); | ||||
|             result.put( "modification", getFileTime( attributes.lastModifiedTime() ) ); | ||||
|             result.put( "modified", getFileTime( attributes.lastModifiedTime() ) ); | ||||
|             result.put( "created", getFileTime( attributes.creationTime() ) ); | ||||
|             result.put( "size", attributes.isDirectory() ? 0 : attributes.size() ); | ||||
|             result.put( "isDir", attributes.isDirectory() ); | ||||
|   | ||||
| @@ -395,14 +395,7 @@ public final class ComputerThread | ||||
|                             executor.timeout.hardAbort(); | ||||
|                             executor.abort(); | ||||
|  | ||||
|                             if( afterHardAbort >= ABORT_TIMEOUT ) | ||||
|                             { | ||||
|                                 // If we've hard aborted but we're still not dead, dump the stack trace and interrupt | ||||
|                                 // the task. | ||||
|                                 timeoutTask( executor, runner.owner, afterStart ); | ||||
|                                 runner.owner.interrupt(); | ||||
|                             } | ||||
|                             else if( afterHardAbort >= ABORT_TIMEOUT * 2 ) | ||||
|                             if( afterHardAbort >= ABORT_TIMEOUT * 2 ) | ||||
|                             { | ||||
|                                 // If we've hard aborted and interrupted, and we're still not dead, then mark the runner | ||||
|                                 // as dead, finish off the task, and spawn a new runner. | ||||
| @@ -421,6 +414,13 @@ public final class ComputerThread | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                             else if( afterHardAbort >= ABORT_TIMEOUT ) | ||||
|                             { | ||||
|                                 // If we've hard aborted but we're still not dead, dump the stack trace and interrupt | ||||
|                                 // the task. | ||||
|                                 timeoutTask( executor, runner.owner, afterStart ); | ||||
|                                 runner.owner.interrupt(); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|   | ||||
| @@ -83,9 +83,9 @@ class VarargArguments implements IArguments | ||||
|     public ByteBuffer getBytes( int index ) throws LuaException | ||||
|     { | ||||
|         LuaValue value = varargs.arg( index + 1 ); | ||||
|         if( !(value instanceof LuaString) ) throw LuaValues.badArgument( index, "string", value.typeName() ); | ||||
|         if( !(value instanceof LuaBaseString) ) throw LuaValues.badArgument( index, "string", value.typeName() ); | ||||
|  | ||||
|         LuaString str = (LuaString) value; | ||||
|         LuaString str = ((LuaBaseString) value).strvalue(); | ||||
|         return ByteBuffer.wrap( str.bytes, str.offset, str.length ).asReadOnlyBuffer(); | ||||
|     } | ||||
|  | ||||
| @@ -94,9 +94,9 @@ class VarargArguments implements IArguments | ||||
|     { | ||||
|         LuaValue value = varargs.arg( index + 1 ); | ||||
|         if( value.isNil() ) return Optional.empty(); | ||||
|         if( !(value instanceof LuaString) ) throw LuaValues.badArgument( index, "string", value.typeName() ); | ||||
|         if( !(value instanceof LuaBaseString) ) throw LuaValues.badArgument( index, "string", value.typeName() ); | ||||
|  | ||||
|         LuaString str = (LuaString) value; | ||||
|         LuaString str = ((LuaBaseString) value).strvalue(); | ||||
|         return Optional.of( ByteBuffer.wrap( str.bytes, str.offset, str.length ).asReadOnlyBuffer() ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -14,10 +14,8 @@ import net.minecraft.util.text.StringTextComponent; | ||||
| import net.minecraftforge.api.distmarker.Dist; | ||||
| import net.minecraftforge.api.distmarker.OnlyIn; | ||||
| import net.minecraftforge.fml.network.NetworkEvent; | ||||
| import net.minecraftforge.registries.ForgeRegistries; | ||||
|  | ||||
| import javax.annotation.Nonnull; | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * Starts or stops a record on the client, depending on if {@link #soundEvent} is {@code null}. | ||||
| @@ -52,7 +50,7 @@ public class PlayRecordClientMessage implements NetworkMessage | ||||
|         if( buf.readBoolean() ) | ||||
|         { | ||||
|             name = buf.readString( Short.MAX_VALUE ); | ||||
|             soundEvent = ForgeRegistries.SOUND_EVENTS.getValue( buf.readResourceLocation() ); | ||||
|             soundEvent = buf.readRegistryIdSafe( SoundEvent.class ); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
| @@ -73,7 +71,7 @@ public class PlayRecordClientMessage implements NetworkMessage | ||||
|         { | ||||
|             buf.writeBoolean( true ); | ||||
|             buf.writeString( name ); | ||||
|             buf.writeResourceLocation( Objects.requireNonNull( soundEvent.getRegistryName(), "Sound is not registered" ) ); | ||||
|             buf.writeRegistryId( soundEvent ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -7,6 +7,8 @@ | ||||
| package dan200.computercraft.shared.peripheral.generic.data; | ||||
|  | ||||
| import com.google.gson.JsonParseException; | ||||
| import dan200.computercraft.ComputerCraft; | ||||
| import dan200.computercraft.shared.util.NBTUtil; | ||||
| import net.minecraft.enchantment.Enchantment; | ||||
| import net.minecraft.enchantment.EnchantmentHelper; | ||||
| import net.minecraft.item.EnchantedBookItem; | ||||
| @@ -22,13 +24,29 @@ import javax.annotation.Nullable; | ||||
| import java.util.*; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| /** | ||||
|  * Data providers for items. | ||||
|  * | ||||
|  * We guard using {@link ComputerCraft#genericPeripheral} in several places, as advanced functionality should not be | ||||
|  * exposed for {@code turtle.getItemDetail} when generic peripehrals are disabled. | ||||
|  */ | ||||
| public class ItemData | ||||
| { | ||||
|     @Nonnull | ||||
|     public static <T extends Map<? super String, Object>> T fillBasic( @Nonnull T data, @Nonnull ItemStack stack ) | ||||
|     public static <T extends Map<? super String, Object>> T fillBasicSafe( @Nonnull T data, @Nonnull ItemStack stack ) | ||||
|     { | ||||
|         data.put( "name", DataHelpers.getId( stack.getItem() ) ); | ||||
|         data.put( "count", stack.getCount() ); | ||||
|  | ||||
|         return data; | ||||
|     } | ||||
|  | ||||
|     @Nonnull | ||||
|     public static <T extends Map<? super String, Object>> T fillBasic( @Nonnull T data, @Nonnull ItemStack stack ) | ||||
|     { | ||||
|         fillBasicSafe( data, stack ); | ||||
|         String hash = NBTUtil.getNBTHash( stack.getTag() ); | ||||
|         if( hash != null ) data.put( "nbt", hash ); | ||||
|         return data; | ||||
|     } | ||||
|  | ||||
| @@ -55,6 +73,8 @@ public class ItemData | ||||
|  | ||||
|         data.put( "tags", DataHelpers.getTags( stack.getItem().getTags() ) ); | ||||
|  | ||||
|         if( !ComputerCraft.genericPeripheral ) return data; | ||||
|  | ||||
|         CompoundNBT tag = stack.getTag(); | ||||
|         if( tag != null && tag.contains( "display", Constants.NBT.TAG_COMPOUND ) ) | ||||
|         { | ||||
|   | ||||
| @@ -605,7 +605,7 @@ public class TurtleAPI implements ILuaAPI | ||||
|  | ||||
|         Map<String, Object> table = detailed | ||||
|             ? ItemData.fill( new HashMap<>(), stack ) | ||||
|             : ItemData.fillBasic( new HashMap<>(), stack ); | ||||
|             : ItemData.fillBasicSafe( new HashMap<>(), stack ); | ||||
|  | ||||
|         TurtleActionEvent event = new TurtleInspectItemEvent( turtle, stack, table, detailed ); | ||||
|         if( MinecraftForge.EVENT_BUS.post( event ) ) return new Object[] { false, event.getFailureMessage() }; | ||||
|   | ||||
| @@ -5,9 +5,19 @@ | ||||
|  */ | ||||
| package dan200.computercraft.shared.util; | ||||
|  | ||||
| import dan200.computercraft.ComputerCraft; | ||||
| import net.minecraft.nbt.*; | ||||
| import net.minecraftforge.common.util.Constants; | ||||
| import org.apache.commons.codec.binary.Hex; | ||||
|  | ||||
| import javax.annotation.Nonnull; | ||||
| import javax.annotation.Nullable; | ||||
| import java.io.DataOutput; | ||||
| import java.io.DataOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.OutputStream; | ||||
| import java.security.MessageDigest; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| @@ -159,4 +169,46 @@ public final class NBTUtil | ||||
|         } | ||||
|         return objects; | ||||
|     } | ||||
|  | ||||
|     @Nullable | ||||
|     public static String getNBTHash( @Nullable CompoundNBT tag ) | ||||
|     { | ||||
|         if( tag == null ) return null; | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             MessageDigest digest = MessageDigest.getInstance( "MD5" ); | ||||
|             DataOutput output = new DataOutputStream( new DigestOutputStream( digest ) ); | ||||
|             CompressedStreamTools.write( tag, output ); | ||||
|             byte[] hash = digest.digest(); | ||||
|             return new String( Hex.encodeHex( hash ) ); | ||||
|         } | ||||
|         catch( NoSuchAlgorithmException | IOException e ) | ||||
|         { | ||||
|             ComputerCraft.log.error( "Cannot hash NBT", e ); | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static final class DigestOutputStream extends OutputStream | ||||
|     { | ||||
|         private final MessageDigest digest; | ||||
|  | ||||
|         DigestOutputStream( MessageDigest digest ) | ||||
|         { | ||||
|             this.digest = digest; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void write( @Nonnull byte[] b, int off, int len ) | ||||
|         { | ||||
|             digest.update( b, off, len ); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void write( int b ) | ||||
|         { | ||||
|             digest.update( (byte) b ); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,6 @@ CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles a | ||||
| [[dependencies.computercraft]] | ||||
|     modId="forge" | ||||
|     mandatory=true | ||||
|     versionRange="[33.0.3,34)" | ||||
|     versionRange="[33.0.20,34)" | ||||
|     ordering="NONE" | ||||
|     side="BOTH" | ||||
|   | ||||
| @@ -181,9 +181,6 @@ end | ||||
| --- Either calls @{colors.packRGB} or @{colors.unpackRGB}, depending on how many | ||||
| -- arguments it receives. | ||||
| -- | ||||
| -- **Note:** This function is deprecated, and it is recommended you use the | ||||
| -- specific pack/unpack function directly. | ||||
| -- | ||||
| -- @tparam[1] number r The red channel, as an argument to @{colors.packRGB}. | ||||
| -- @tparam[1] number g The green channel, as an argument to @{colors.packRGB}. | ||||
| -- @tparam[1] number b The blue channel, as an argument to @{colors.packRGB}. | ||||
| @@ -192,6 +189,7 @@ end | ||||
| -- @treturn[2] number The red channel, as returned by @{colors.unpackRGB} | ||||
| -- @treturn[2] number The green channel, as returned by @{colors.unpackRGB} | ||||
| -- @treturn[2] number The blue channel, as returned by @{colors.unpackRGB} | ||||
| -- @deprecated Use @{packRGB} or @{unpackRGB} directly. | ||||
| -- @usage | ||||
| -- ```lua | ||||
| -- colors.rgb(0xb23399) | ||||
|   | ||||
| @@ -77,7 +77,7 @@ function formatTime(nTime, bTwentyFourHour) | ||||
|     local nHour = math.floor(nTime) | ||||
|     local nMinute = math.floor((nTime - nHour) * 60) | ||||
|     if sTOD then | ||||
|         return string.format("%d:%02d %s", nHour, nMinute, sTOD) | ||||
|         return string.format("%d:%02d %s", nHour == 0 and 12 or nHour, nMinute, sTOD) | ||||
|     else | ||||
|         return string.format("%d:%02d", nHour, nMinute) | ||||
|     end | ||||
| @@ -335,6 +335,31 @@ empty_json_array = mk_tbl("[]", "empty_json_array") | ||||
| -- @see textutils.unserialiseJSON | ||||
| json_null = mk_tbl("null", "json_null") | ||||
|  | ||||
| local serializeJSONString | ||||
| do | ||||
|     local function hexify(c) | ||||
|         return ("\\u00%02X"):format(c:byte()) | ||||
|     end | ||||
|  | ||||
|     local map = { | ||||
|         ["\""] = "\\\"", | ||||
|         ["\\"] = "\\\\", | ||||
|         ["\b"] = "\\b", | ||||
|         ["\f"] = "\\f", | ||||
|         ["\n"] = "\\n", | ||||
|         ["\r"] = "\\r", | ||||
|         ["\t"] = "\\t", | ||||
|     } | ||||
|     for i = 0, 0x1f do | ||||
|         local c = string.char(i) | ||||
|         if map[c] == nil then map[c] = hexify(c) end | ||||
|     end | ||||
|  | ||||
|     serializeJSONString = function(s) | ||||
|         return ('"%s"'):format(s:gsub("[\0-\x1f\"\\]", map):gsub("[\x7f-\xff]", hexify)) | ||||
|     end | ||||
| end | ||||
|  | ||||
| local function serializeJSONImpl(t, tTracking, bNBTStyle) | ||||
|     local sType = type(t) | ||||
|     if t == empty_json_array then return "[]" | ||||
| @@ -361,7 +386,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle) | ||||
|                     if bNBTStyle then | ||||
|                         sEntry = tostring(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle) | ||||
|                     else | ||||
|                         sEntry = string.format("%q", k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle) | ||||
|                         sEntry = serializeJSONString(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle) | ||||
|                     end | ||||
|                     if nObjectSize == 0 then | ||||
|                         sObjectResult = sObjectResult .. sEntry | ||||
| @@ -390,7 +415,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle) | ||||
|         end | ||||
|  | ||||
|     elseif sType == "string" then | ||||
|         return string.format("%q", t) | ||||
|         return serializeJSONString(t) | ||||
|  | ||||
|     elseif sType == "number" or sType == "boolean" then | ||||
|         return tostring(t) | ||||
|   | ||||
| @@ -1,3 +1,17 @@ | ||||
| # New features in CC: Tweaked 1.91.0 | ||||
|  | ||||
| * [Generic peripherals] Expose NBT hashes of items to inventory methods. | ||||
| * Bump Cobalt version | ||||
|   * Optimise handling of string concatenation. | ||||
|   * Add string.{pack,unpack,packsize} (MCJack123) | ||||
| * Update to 1.16.2 | ||||
|  | ||||
| And several bug fixes: | ||||
| * Escape non-ASCII characters in JSON strings (neumond) | ||||
| * Make field names in fs.attributes more consistent (abby) | ||||
| * Fix textutils.formatTime correctly handle 12 AM (R93950X) | ||||
| * Fix turtles placing buckets multiple times. | ||||
|  | ||||
| # New features in CC: Tweaked 1.90.3 | ||||
|  | ||||
| * Fix the selected slot indicator missing from the turtle GUI. | ||||
|   | ||||
| @@ -1,6 +1,15 @@ | ||||
| New features in CC: Tweaked 1.90.3 | ||||
| New features in CC: Tweaked 1.91.0 | ||||
|  | ||||
| * Fix the selected slot indicator missing from the turtle GUI. | ||||
| * Ensure we load/save computer data from the world directory, rather than a global one. | ||||
| * [Generic peripherals] Expose NBT hashes of items to inventory methods. | ||||
| * Bump Cobalt version | ||||
|   * Optimise handling of string concatenation. | ||||
|   * Add string.{pack,unpack,packsize} (MCJack123) | ||||
| * Update to 1.16.2 | ||||
|  | ||||
| And several bug fixes: | ||||
| * Escape non-ASCII characters in JSON strings (neumond) | ||||
| * Make field names in fs.attributes more consistent (abby) | ||||
| * Fix textutils.formatTime correctly handle 12 AM (R93950X) | ||||
| * Fix turtles placing buckets multiple times. | ||||
|  | ||||
| Type "help changelog" to see the full version history. | ||||
|   | ||||
| @@ -208,9 +208,11 @@ describe("The fs library", function() | ||||
|                 fail(("Expected created time (%d) to be within 1000ms of now (%d"):format(attributes.created, now)) | ||||
|             end | ||||
|  | ||||
|             if attributes.modification - now >= 1000 then | ||||
|                 fail(("Expected modification time (%d) to be within 1000ms of now (%d"):format(attributes.modification, now)) | ||||
|             if attributes.modified - now >= 1000 then | ||||
|                 fail(("Expected modified time (%d) to be within 1000ms of now (%d"):format(attributes.modified, now)) | ||||
|             end | ||||
|  | ||||
|             expect(attributes.modification):eq(attributes.modified) | ||||
|         end) | ||||
|     end) | ||||
| end) | ||||
|   | ||||
| @@ -12,6 +12,14 @@ describe("The textutils library", function() | ||||
|             expect.error(textutils.formatTime, nil):eq("bad argument #1 (expected number, got nil)") | ||||
|             expect.error(textutils.formatTime, 1, 1):eq("bad argument #2 (expected boolean, got number)") | ||||
|         end) | ||||
|  | ||||
|         it("correctly formats 12 o'clock", function() | ||||
|             expect(textutils.formatTime(0, false)):eq("12:00 AM") | ||||
|             expect(textutils.formatTime(0.1, false)):eq("12:06 AM") | ||||
|  | ||||
|             expect(textutils.formatTime(0, true)):eq("0:00") | ||||
|             expect(textutils.formatTime(0.1, true)):eq("0:06") | ||||
|         end) | ||||
|     end) | ||||
|  | ||||
|     describe("textutils.pagedPrint", function() | ||||
| @@ -49,7 +57,7 @@ describe("The textutils library", function() | ||||
|     describe("textutils.empty_json_array", function() | ||||
|         it("is immutable", function() | ||||
|             expect.error(function() textutils.empty_json_array[1] = true end) | ||||
|                 :str_match("^[^:]+:51: attempt to mutate textutils.empty_json_array$") | ||||
|                 :str_match("^[^:]+:%d+: attempt to mutate textutils.empty_json_array$") | ||||
|         end) | ||||
|     end) | ||||
|  | ||||
| @@ -78,6 +86,20 @@ describe("The textutils library", function() | ||||
|         it("serializes null", function() | ||||
|             expect(textutils.serializeJSON(textutils.json_null)):eq("null") | ||||
|         end) | ||||
|  | ||||
|         it("serializes strings", function() | ||||
|             expect(textutils.serializeJSON('a')):eq('"a"') | ||||
|             expect(textutils.serializeJSON('"')):eq('"\\""') | ||||
|             expect(textutils.serializeJSON('\\')):eq('"\\\\"') | ||||
|             expect(textutils.serializeJSON('/')):eq('"/"') | ||||
|             expect(textutils.serializeJSON('\b')):eq('"\\b"') | ||||
|             expect(textutils.serializeJSON('\n')):eq('"\\n"') | ||||
|             expect(textutils.serializeJSON(string.char(0))):eq('"\\u0000"') | ||||
|             expect(textutils.serializeJSON(string.char(0x0A))):eq('"\\n"') | ||||
|             expect(textutils.serializeJSON(string.char(0x1D))):eq('"\\u001D"') | ||||
|             expect(textutils.serializeJSON(string.char(0x81))):eq('"\\u0081"') | ||||
|             expect(textutils.serializeJSON(string.char(0xFF))):eq('"\\u00FF"') | ||||
|         end) | ||||
|     end) | ||||
|  | ||||
|     describe("textutils.unserializeJSON", function() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 SquidDev
					SquidDev