From 8dfcb0f914552c5d9a3a88f86d5209e33e48253f Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Fri, 28 Jul 2017 15:12:41 +1000 Subject: [PATCH] Various Start work on interactive commands (//help, //biomelist, //pos1/2) Added clipboard and world remapping (between mcpe/pe) //schematic remap //anvil remapall Added anvil -> leveldb converter (run Nukkit jar or /anvil2leveldb) Added safe zstd decompressor Angle mask fixes Fix paste air issues with fawe format (which no-one uses anyway) Fix cfi file:// with an absolute path FIx schematic format and addBlocks Update forge to 1.12 Fixes #663 --- README.md | 15 +- blocks.json | 2240 ++++++++++++++++- .../com/boydti/fawe/bukkit/FaweBukkit.java | 8 + .../boydti/fawe/bukkit/chat/ArrayWrapper.java | 110 + .../fawe/bukkit/chat/BukkitChatManager.java | 64 + .../boydti/fawe/bukkit/chat/FancyMessage.java | 955 +++++++ .../bukkit/chat/JsonRepresentedObject.java | 18 + .../boydti/fawe/bukkit/chat/JsonString.java | 46 + .../boydti/fawe/bukkit/chat/MessagePart.java | 156 ++ .../boydti/fawe/bukkit/chat/Reflection.java | 205 ++ .../fawe/bukkit/chat/TextualComponent.java | 316 +++ .../boydti/fawe/bukkit/v0/BukkitQueue_0.java | 10 - .../bukkit/BukkitPlayerBlockBag.java | 204 ++ core/build.gradle | 2 +- core/src/main/java/com/boydti/fawe/Fawe.java | 24 +- .../boydti/fawe/command/AnvilCommands.java | 36 +- .../main/java/com/boydti/fawe/config/BBC.java | 146 +- .../java/com/boydti/fawe/config/Commands.java | 8 +- .../com/boydti/fawe/database/DBHandler.java | 4 + .../boydti/fawe/installer/BrowseButton.java | 32 +- .../boydti/fawe/installer/CloseButton.java | 2 + .../com/boydti/fawe/installer/ImagePanel.java | 30 + .../boydti/fawe/installer/InstallerFrame.java | 5 +- .../fawe/installer/InteractiveButton.java | 12 +- .../fawe/installer/JSystemFileChooser.java | 60 + .../boydti/fawe/installer/MovablePanel.java | 6 +- .../fawe/installer/TextAreaOutputStream.java | 47 +- .../fawe/jnbt/CorruptSchematicStreamer.java | 276 +- .../boydti/fawe/jnbt/SchematicStreamer.java | 39 +- .../com/boydti/fawe/jnbt/anvil/MCAChunk.java | 3 +- .../com/boydti/fawe/jnbt/anvil/MCAFile.java | 5 +- .../com/boydti/fawe/jnbt/anvil/MCAFilter.java | 10 + .../com/boydti/fawe/jnbt/anvil/MCAQueue.java | 59 +- .../com/boydti/fawe/jnbt/anvil/MCAWriter.java | 2 +- .../jnbt/anvil/MutableMCABackedBaseBlock.java | 4 + .../jnbt/anvil/filters/DelegateMCAFilter.java | 93 + .../fawe/jnbt/anvil/filters/RemapFilter.java | 123 + .../rollback/RollbackOptimizedHistory.java | 1 + .../com/boydti/fawe/object/FawePlayer.java | 2 +- .../object/changeset/DiskStorageHistory.java | 9 +- .../changeset/MemoryOptimizedHistory.java | 9 +- .../AbstractDelegateFaweClipboard.java | 5 + .../clipboard/CPUOptimizedClipboard.java | 1 + .../object/clipboard/ClipboardRemapper.java | 230 ++ .../clipboard/DiskOptimizedClipboard.java | 43 +- .../fawe/object/clipboard/FaweClipboard.java | 2 + .../clipboard/MemoryOptimizedClipboard.java | 1 + .../object/clipboard/ReadOnlyClipboard.java | 5 + .../object/clipboard/RemappedClipboard.java | 62 + .../object/clipboard/WorldCopyClipboard.java | 2 +- .../fawe/object/collection/ByteStore.java | 14 + .../object/io/LittleEndianOutputStream.java | 280 +++ .../fawe/object/io/PGZIPOutputStream.java | 4 +- .../boydti/fawe/object/io/zstd/BitStream.java | 206 ++ .../object/io/zstd/FiniteStateEntropy.java | 176 ++ .../fawe/object/io/zstd/FrameHeader.java | 32 + .../fawe/object/io/zstd/FseTableReader.java | 182 ++ .../boydti/fawe/object/io/zstd/Huffman.java | 317 +++ .../io/zstd/MalformedInputException.java | 23 + .../fawe/object/io/zstd/UnsafeUtil.java | 71 + .../com/boydti/fawe/object/io/zstd/Util.java | 48 + .../fawe/object/io/zstd/ZstdDecompressor.java | 108 + .../object/io/zstd/ZstdFrameDecompressor.java | 958 +++++++ .../fawe/object/io/zstd/ZstdInputStream.java | 179 ++ .../boydti/fawe/object/mask/AngleMask.java | 6 +- .../fawe/object/schematic/FaweFormat.java | 5 +- .../fawe/object/schematic/PNGWriter.java | 2 +- .../fawe/object/schematic/Schematic.java | 4 +- .../com/boydti/fawe/regions/FaweMask.java | 2 - .../regions/general/plot/CreateFromImage.java | 2 +- .../fawe/util/DocumentationPrinter.java | 14 +- .../java/com/boydti/fawe/util/MainUtil.java | 24 +- .../java/com/boydti/fawe/util/MemUtil.java | 9 + .../java/com/boydti/fawe/util/SetQueue.java | 11 + .../java/com/boydti/fawe/util/StringMan.java | 33 + .../com/boydti/fawe/util/TextureUtil.java | 1 + .../boydti/fawe/util/chat/ChatManager.java | 21 + .../com/boydti/fawe/util/chat/Message.java | 130 + .../fawe/util/chat/PlainChatManager.java | 47 + .../java/com/sk89q/jnbt/NBTOutputStream.java | 21 +- .../java/com/sk89q/worldedit/EditSession.java | 108 +- .../com/sk89q/worldedit/blocks/BaseBlock.java | 5 +- .../worldedit/blocks/ImmutableBlock.java | 8 + .../worldedit/command/BiomeCommands.java | 27 +- .../worldedit/command/BrushCommands.java | 4 +- .../command/BrushOptionsCommands.java | 15 +- .../sk89q/worldedit/command/MaskCommands.java | 8 +- .../worldedit/command/SchematicCommands.java | 19 + .../worldedit/command/SelectionCommands.java | 2 +- .../worldedit/command/TransformCommands.java | 7 +- .../worldedit/command/UtilityCommands.java | 88 +- .../extent/clipboard/io/ClipboardFormat.java | 10 +- .../extent/clipboard/io/SchematicWriter.java | 31 +- .../selector/CuboidRegionSelector.java | 17 +- .../util/command/SimpleCommandMapping.java | 102 + core/src/main/resources/de/message.yml | 2 +- core/src/main/resources/ru/message.yml | 2 +- forge112/build.gradle | 93 + .../java/com/boydti/fawe/forge/FaweForge.java | 179 ++ .../com/boydti/fawe/forge/ForgeCommand.java | 42 + .../java/com/boydti/fawe/forge/ForgeMain.java | 77 + .../com/boydti/fawe/forge/ForgeMetrics.java | 476 ++++ .../com/boydti/fawe/forge/ForgePlayer.java | 77 + .../fawe/forge/ForgePlayerBlockBag.java | 199 ++ .../com/boydti/fawe/forge/ForgeTaskMan.java | 168 ++ .../boydti/fawe/forge/MutableGenLayer.java | 26 + .../fawe/forge/v112/ForgeChunk_All.java | 388 +++ .../fawe/forge/v112/ForgeQueue_All.java | 663 +++++ .../sk89q/worldedit/forge/ForgePlayer.java | 231 ++ nukkit/build.gradle | 7 +- .../fawe/nukkit/core/NukkitWorldEdit.java | 6 + .../core/converter/ConvertCommands.java | 65 + .../nukkit/core/converter/ConverterFrame.java | 395 +++ .../core/converter/LevelDBToMCAFile.java | 73 + .../core/converter/MCAFile2LevelDB.java | 462 ++++ .../optimization/queue/NukkitQueue.java | 4 + nukkit/src/main/resources/axe-logo.png | Bin 0 -> 2107 bytes settings.gradle | 2 +- .../com/boydti/fawe/sponge/FaweSponge.java | 2 + .../sponge/chat/SpongeChatManager.java | 167 ++ 120 files changed, 12385 insertions(+), 549 deletions(-) create mode 100644 bukkit/src/main/java/com/boydti/fawe/bukkit/chat/ArrayWrapper.java create mode 100644 bukkit/src/main/java/com/boydti/fawe/bukkit/chat/BukkitChatManager.java create mode 100644 bukkit/src/main/java/com/boydti/fawe/bukkit/chat/FancyMessage.java create mode 100644 bukkit/src/main/java/com/boydti/fawe/bukkit/chat/JsonRepresentedObject.java create mode 100644 bukkit/src/main/java/com/boydti/fawe/bukkit/chat/JsonString.java create mode 100644 bukkit/src/main/java/com/boydti/fawe/bukkit/chat/MessagePart.java create mode 100644 bukkit/src/main/java/com/boydti/fawe/bukkit/chat/Reflection.java create mode 100644 bukkit/src/main/java/com/boydti/fawe/bukkit/chat/TextualComponent.java create mode 100644 bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayerBlockBag.java create mode 100644 core/src/main/java/com/boydti/fawe/installer/ImagePanel.java create mode 100644 core/src/main/java/com/boydti/fawe/installer/JSystemFileChooser.java create mode 100644 core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/DelegateMCAFilter.java create mode 100644 core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/RemapFilter.java create mode 100644 core/src/main/java/com/boydti/fawe/object/clipboard/ClipboardRemapper.java create mode 100644 core/src/main/java/com/boydti/fawe/object/clipboard/RemappedClipboard.java create mode 100644 core/src/main/java/com/boydti/fawe/object/collection/ByteStore.java create mode 100644 core/src/main/java/com/boydti/fawe/object/io/LittleEndianOutputStream.java create mode 100644 core/src/main/java/com/boydti/fawe/object/io/zstd/BitStream.java create mode 100644 core/src/main/java/com/boydti/fawe/object/io/zstd/FiniteStateEntropy.java create mode 100644 core/src/main/java/com/boydti/fawe/object/io/zstd/FrameHeader.java create mode 100644 core/src/main/java/com/boydti/fawe/object/io/zstd/FseTableReader.java create mode 100644 core/src/main/java/com/boydti/fawe/object/io/zstd/Huffman.java create mode 100644 core/src/main/java/com/boydti/fawe/object/io/zstd/MalformedInputException.java create mode 100644 core/src/main/java/com/boydti/fawe/object/io/zstd/UnsafeUtil.java create mode 100644 core/src/main/java/com/boydti/fawe/object/io/zstd/Util.java create mode 100644 core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdDecompressor.java create mode 100644 core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdFrameDecompressor.java create mode 100644 core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdInputStream.java create mode 100644 core/src/main/java/com/boydti/fawe/util/chat/ChatManager.java create mode 100644 core/src/main/java/com/boydti/fawe/util/chat/Message.java create mode 100644 core/src/main/java/com/boydti/fawe/util/chat/PlainChatManager.java create mode 100644 core/src/main/java/com/sk89q/worldedit/util/command/SimpleCommandMapping.java create mode 100644 forge112/build.gradle create mode 100644 forge112/src/main/java/com/boydti/fawe/forge/FaweForge.java create mode 100644 forge112/src/main/java/com/boydti/fawe/forge/ForgeCommand.java create mode 100644 forge112/src/main/java/com/boydti/fawe/forge/ForgeMain.java create mode 100644 forge112/src/main/java/com/boydti/fawe/forge/ForgeMetrics.java create mode 100644 forge112/src/main/java/com/boydti/fawe/forge/ForgePlayer.java create mode 100644 forge112/src/main/java/com/boydti/fawe/forge/ForgePlayerBlockBag.java create mode 100644 forge112/src/main/java/com/boydti/fawe/forge/ForgeTaskMan.java create mode 100644 forge112/src/main/java/com/boydti/fawe/forge/MutableGenLayer.java create mode 100644 forge112/src/main/java/com/boydti/fawe/forge/v112/ForgeChunk_All.java create mode 100644 forge112/src/main/java/com/boydti/fawe/forge/v112/ForgeQueue_All.java create mode 100644 forge112/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java create mode 100644 nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConvertCommands.java create mode 100644 nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java create mode 100644 nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/LevelDBToMCAFile.java create mode 100644 nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java create mode 100644 nukkit/src/main/resources/axe-logo.png create mode 100644 sponge/src/main/java/com/sk89q/worldedit/sponge/chat/SpongeChatManager.java diff --git a/README.md b/README.md index 3722ff93..27026f3b 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,14 @@ # FastAsyncWorldEdit -FAWE is an addon for WorldEdit that has huge speed and memory improvements as well as a few extra features. +FAWE is a fork of WorldEdit that has huge speed and memory improvements and considerably more features -## Spigot page -https://www.spigotmc.org/resources/13932/ +It is available for Bukkit, Forge, Sponge and Nukkit. -## IRC -Meet us on `irc.esper.net` in the `#IntellectualCrafters` channel or use the [webchat to join through browser](http://webchat.esper.net/?nick=&channels=IntellectualCrafters). +## Chat +Meet us on [`Discord`](https://discord.gg/ngZCzbU) or [`irc.esper.net #IntellectualCrafters`](http://webchat.esper.net/?nick=&channels=IntellectualCrafters). ## Releases -You can find the most recent stable releases on GitHub -https://github.com/boy0001/FastAsyncWorldedit/releases + +https://github.com/boy0001/FastAsyncWorldedit/releases/latest ## Building FAWE uses gradle to build @@ -23,4 +22,4 @@ $ gradlew build Have an idea for an optimization, or a cool feature? - I'll accept most PR's - Let me know what you've tested / what may need further testing - - If you need any help, create a ticket or discuss on IRC \ No newline at end of file + - If you need any help, create a ticket or discuss on Discord \ No newline at end of file diff --git a/blocks.json b/blocks.json index ac8fefd4..63a4bbdf 100644 --- a/blocks.json +++ b/blocks.json @@ -95,6 +95,7 @@ "localizedName": "Grass Block", "states": { "snowy": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -137,6 +138,7 @@ "localizedName": "Dirt", "states": { "snowy": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -886,13 +888,52 @@ "dataMask": 12, "values": { "x": { - "data": 4 + "data": 4, + "direction": [ + 1, + 0, + 0 + ] + }, + "-x": { + "data": 4, + "direction": [ + -1, + 0, + 0 + ] }, "y": { - "data": 0 + "data": 0, + "direction": [ + 0, + 1, + 0 + ] + }, + "-y": { + "data": 0, + "direction": [ + 0, + -1, + 0 + ] }, "z": { - "data": 8 + "data": 8, + "direction": [ + 0, + 0, + 1 + ] + }, + "-z": { + "data": 8, + "direction": [ + 0, + 0, + -1 + ] }, "none": { "data": 12 @@ -1009,7 +1050,7 @@ "fragileWhenPushed": true, "unpushable": false, "adventureModeExempt": false, - "ambientOcclusionLightValue": 0.2, + "ambientOcclusionLightValue": 1.0, "grassBlocking": false } }, @@ -1326,7 +1367,7 @@ "legacyId": 26, "id": "minecraft:bed", "unlocalizedName": "tile.bed", - "localizedName": "Bed", + "localizedName": "tile.bed.name", "states": { "facing": { "dataMask": 3, @@ -1366,6 +1407,7 @@ } }, "occupied": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -1904,6 +1946,7 @@ } }, "short": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -2138,6 +2181,7 @@ "localizedName": "Flower", "states": { "type": { + "dataMask": 0, "values": { "dandelion": { "data": 0 @@ -2177,7 +2221,7 @@ "localizedName": "Flower", "states": { "type": { - "dataMask": 8, + "dataMask": 15, "values": { "poppy": { "data": 0 @@ -2440,10 +2484,20 @@ "dataMask": 8, "values": { "top": { - "data": 8 + "data": 8, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, @@ -2572,7 +2626,7 @@ "fragileWhenPushed": false, "unpushable": false, "adventureModeExempt": false, - "ambientOcclusionLightValue": 0.2, + "ambientOcclusionLightValue": 1.0, "grassBlocking": false } }, @@ -2806,6 +2860,7 @@ } }, "east": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -2816,6 +2871,7 @@ } }, "north": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -2826,6 +2882,7 @@ } }, "south": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -2836,6 +2893,7 @@ } }, "up": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -2846,6 +2904,7 @@ } }, "west": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -2959,15 +3018,25 @@ "dataMask": 4, "values": { "top": { - "data": 4 + "data": 4, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "shape": { - "dataMask": 7, + "dataMask": 0, "values": { "straight": { "data": 0 @@ -3088,6 +3157,7 @@ "localizedName": "Redstone Dust", "states": { "east": { + "dataMask": 0, "values": { "up": { "data": 0 @@ -3101,6 +3171,7 @@ } }, "north": { + "dataMask": 0, "values": { "up": { "data": 0 @@ -3167,6 +3238,7 @@ } }, "south": { + "dataMask": 0, "values": { "up": { "data": 0 @@ -3180,6 +3252,7 @@ } }, "west": { + "dataMask": 0, "values": { "up": { "data": 0 @@ -3581,52 +3654,132 @@ "dataMask": 15, "values": { "0": { - "data": 0 + "data": 0, + "direction": [ + 0.0, + 0.0, + -1.0 + ] }, "1": { - "data": 1 + "data": 1, + "direction": [ + 0.5, + 0.0, + -1.0 + ] }, "2": { - "data": 2 + "data": 2, + "direction": [ + 1.0, + 0.0, + -1.0 + ] }, "3": { - "data": 3 + "data": 3, + "direction": [ + 1.0, + 0.0, + -0.5 + ] }, "4": { - "data": 4 + "data": 4, + "direction": [ + 1.0, + 0.0, + 0.0 + ] }, "5": { - "data": 5 + "data": 5, + "direction": [ + 1.0, + 0.0, + 0.5 + ] }, "6": { - "data": 6 + "data": 6, + "direction": [ + 1.0, + 0.0, + 1.0 + ] }, "7": { - "data": 7 + "data": 7, + "direction": [ + 0.5, + 0.0, + 1.0 + ] }, "8": { - "data": 8 + "data": 8, + "direction": [ + 0.0, + 0.0, + 1.0 + ] }, "9": { - "data": 9 + "data": 9, + "direction": [ + -0.5, + 0.0, + 1.0 + ] }, "10": { - "data": 10 + "data": 10, + "direction": [ + -1.0, + 0.0, + 1.0 + ] }, "11": { - "data": 11 + "data": 11, + "direction": [ + -1.0, + 0.0, + 0.5 + ] }, "12": { - "data": 12 + "data": 12, + "direction": [ + -1.0, + 0.0, + 0.0 + ] }, "13": { - "data": 13 + "data": 13, + "direction": [ + -1.0, + 0.0, + -0.5 + ] }, "14": { - "data": 14 + "data": 14, + "direction": [ + -1.0, + 0.0, + -1.0 + ] }, "15": { - "data": 15 + "data": 15, + "direction": [ + -0.5, + 0.0, + -1.0 + ] } } } @@ -3711,6 +3864,7 @@ } }, "hinge": { + "dataMask": 0, "values": { "left": { "data": 0 @@ -3732,6 +3886,7 @@ } }, "powered": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -3843,7 +3998,7 @@ "localizedName": "Rail", "states": { "shape": { - "dataMask": 12, + "dataMask": 15, "values": { "north_south": { "data": 0 @@ -3950,15 +4105,25 @@ "dataMask": 4, "values": { "top": { - "data": 4 + "data": 4, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "shape": { - "dataMask": 7, + "dataMask": 0, "values": { "straight": { "data": 0 @@ -4085,16 +4250,36 @@ "data": 0 }, "east": { - "data": 1 + "data": 1, + "direction": [ + 1, + 0, + 0 + ] }, "west": { - "data": 2 + "data": 2, + "direction": [ + -1, + 0, + 0 + ] }, "south": { - "data": 3 + "data": 3, + "direction": [ + 0, + 0, + 1 + ] }, "north": { - "data": 4 + "data": 4, + "direction": [ + 0, + 0, + -1 + ] }, "up_z": { "data": 5 @@ -4242,6 +4427,7 @@ } }, "hinge": { + "dataMask": 0, "values": { "left": { "data": 0 @@ -4263,6 +4449,7 @@ } }, "powered": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -4741,7 +4928,7 @@ "fragileWhenPushed": false, "unpushable": false, "adventureModeExempt": true, - "ambientOcclusionLightValue": 0.2, + "ambientOcclusionLightValue": 1.0, "grassBlocking": false } }, @@ -4758,7 +4945,7 @@ "usingNeighborLight": false, "hardness": 0.2, "resistance": 1.0, - "ticksRandomly": true, + "ticksRandomly": false, "fullCube": true, "slipperiness": 0.6, "renderedAsNormalBlock": true, @@ -5027,6 +5214,7 @@ "localizedName": "Oak Fence", "states": { "east": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -5037,6 +5225,7 @@ } }, "north": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -5047,6 +5236,7 @@ } }, "south": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -5057,6 +5247,7 @@ } }, "west": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -5250,7 +5441,7 @@ "fragileWhenPushed": false, "unpushable": false, "adventureModeExempt": true, - "ambientOcclusionLightValue": 0.2, + "ambientOcclusionLightValue": 1.0, "grassBlocking": false } }, @@ -5261,13 +5452,39 @@ "localizedName": "Portal", "states": { "axis": { - "dataMask": 1, + "dataMask": 3, "values": { "x": { - "data": 1 + "data": 1, + "direction": [ + 1, + 0, + 0 + ] + }, + "-x": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] }, "z": { - "data": 2 + "data": 2, + "direction": [ + 0, + 0, + 1 + ] + }, + "-z": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] } } } @@ -5485,6 +5702,7 @@ } }, "locked": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -5581,6 +5799,7 @@ } }, "locked": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -5748,10 +5967,20 @@ "dataMask": 8, "values": { "top": { - "data": 8 + "data": 8, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, @@ -6055,6 +6284,7 @@ "localizedName": "Iron Bars", "states": { "east": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -6065,6 +6295,7 @@ } }, "north": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -6075,6 +6306,7 @@ } }, "south": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -6085,6 +6317,7 @@ } }, "west": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -6127,6 +6360,7 @@ "localizedName": "Glass Pane", "states": { "east": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -6137,6 +6371,7 @@ } }, "north": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -6147,6 +6382,7 @@ } }, "south": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -6157,6 +6393,7 @@ } }, "west": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -6259,7 +6496,7 @@ } }, "facing": { - "dataMask": 7, + "dataMask": 0, "values": { "up": { "data": 0, @@ -6365,7 +6602,7 @@ } }, "facing": { - "dataMask": 7, + "dataMask": 0, "values": { "up": { "data": 0, @@ -6475,6 +6712,7 @@ } }, "up": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -6485,6 +6723,7 @@ } }, "west": { + "dataMask": 2, "values": { "true": { "data": 2 @@ -6564,6 +6803,7 @@ } }, "in_wall": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -6668,15 +6908,25 @@ "dataMask": 4, "values": { "top": { - "data": 4 + "data": 4, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "shape": { - "dataMask": 7, + "dataMask": 0, "values": { "straight": { "data": 0 @@ -6768,15 +7018,25 @@ "dataMask": 4, "values": { "top": { - "data": 4 + "data": 4, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "shape": { - "dataMask": 7, + "dataMask": 0, "values": { "straight": { "data": 0 @@ -6828,6 +7088,7 @@ "localizedName": "Mycelium", "states": { "snowy": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -6932,6 +7193,7 @@ "localizedName": "Nether Brick Fence", "states": { "east": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -6942,6 +7204,7 @@ } }, "north": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -6952,6 +7215,7 @@ } }, "south": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -6962,6 +7226,7 @@ } }, "west": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -7044,15 +7309,25 @@ "dataMask": 4, "values": { "top": { - "data": 4 + "data": 4, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "shape": { - "dataMask": 7, + "dataMask": 0, "values": { "straight": { "data": 0 @@ -7195,6 +7470,7 @@ } }, "has_bottle_1": { + "dataMask": 2, "values": { "true": { "data": 2 @@ -7590,10 +7866,20 @@ "dataMask": 8, "values": { "top": { - "data": 8 + "data": 8, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, @@ -7653,7 +7939,7 @@ "localizedName": "Cocoa", "states": { "age": { - "dataMask": 8, + "dataMask": 12, "values": { "0": { "data": 0 @@ -7776,15 +8062,25 @@ "dataMask": 4, "values": { "top": { - "data": 4 + "data": 4, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "shape": { - "dataMask": 7, + "dataMask": 0, "values": { "straight": { "data": 0 @@ -8049,6 +8345,7 @@ } }, "east": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -8059,6 +8356,7 @@ } }, "north": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -8080,6 +8378,7 @@ } }, "south": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -8090,6 +8389,7 @@ } }, "west": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -8203,15 +8503,25 @@ "dataMask": 4, "values": { "top": { - "data": 4 + "data": 4, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "shape": { - "dataMask": 7, + "dataMask": 0, "values": { "straight": { "data": 0 @@ -8303,15 +8613,25 @@ "dataMask": 4, "values": { "top": { - "data": 4 + "data": 4, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "shape": { - "dataMask": 7, + "dataMask": 0, "values": { "straight": { "data": 0 @@ -8403,15 +8723,25 @@ "dataMask": 4, "values": { "top": { - "data": 4 + "data": 4, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "shape": { - "dataMask": 7, + "dataMask": 0, "values": { "straight": { "data": 0 @@ -8590,6 +8920,7 @@ "localizedName": "Cobblestone Wall", "states": { "east": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -8600,6 +8931,7 @@ } }, "north": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -8610,6 +8942,7 @@ } }, "south": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -8620,6 +8953,7 @@ } }, "up": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -8641,6 +8975,7 @@ } }, "west": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -8683,6 +9018,7 @@ "localizedName": "Flower Pot", "states": { "contents": { + "dataMask": 0, "values": { "empty": { "data": 0 @@ -9152,7 +9488,7 @@ "localizedName": "Anvil", "states": { "damage": { - "dataMask": 8, + "dataMask": 12, "values": { "0": { "data": 0 @@ -9761,7 +10097,7 @@ "fragileWhenPushed": false, "unpushable": false, "adventureModeExempt": false, - "ambientOcclusionLightValue": 0.2, + "ambientOcclusionLightValue": 1.0, "grassBlocking": false } }, @@ -9891,7 +10227,7 @@ "localizedName": "Block of Quartz", "states": { "variant": { - "dataMask": 4, + "dataMask": 7, "values": { "default": { "data": 0 @@ -9983,15 +10319,25 @@ "dataMask": 4, "values": { "top": { - "data": 4 + "data": 4, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "shape": { - "dataMask": 7, + "dataMask": 0, "values": { "straight": { "data": 0 @@ -10202,7 +10548,7 @@ "legacyId": 159, "id": "minecraft:stained_hardened_clay", "unlocalizedName": "tile.clayHardenedStained", - "localizedName": "Stained Hardened Clay", + "localizedName": "Stained Terracotta", "states": { "color": { "dataMask": 15, @@ -10343,6 +10689,7 @@ } }, "east": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -10353,6 +10700,7 @@ } }, "north": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -10363,6 +10711,7 @@ } }, "south": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -10373,6 +10722,7 @@ } }, "west": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -10469,7 +10819,7 @@ "fragileWhenPushed": true, "unpushable": false, "adventureModeExempt": false, - "ambientOcclusionLightValue": 0.2, + "ambientOcclusionLightValue": 1.0, "grassBlocking": false } }, @@ -10483,13 +10833,52 @@ "dataMask": 12, "values": { "x": { - "data": 4 + "data": 4, + "direction": [ + 1, + 0, + 0 + ] + }, + "-x": { + "data": 4, + "direction": [ + -1, + 0, + 0 + ] }, "y": { - "data": 0 + "data": 0, + "direction": [ + 0, + 1, + 0 + ] + }, + "-y": { + "data": 0, + "direction": [ + 0, + -1, + 0 + ] }, "z": { - "data": 8 + "data": 8, + "direction": [ + 0, + 0, + 1 + ] + }, + "-z": { + "data": 8, + "direction": [ + 0, + 0, + -1 + ] }, "none": { "data": 12 @@ -10580,15 +10969,25 @@ "dataMask": 4, "values": { "top": { - "data": 4 + "data": 4, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "shape": { - "dataMask": 7, + "dataMask": 0, "values": { "straight": { "data": 0 @@ -10680,15 +11079,25 @@ "dataMask": 4, "values": { "top": { - "data": 4 + "data": 4, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "shape": { - "dataMask": 7, + "dataMask": 0, "values": { "straight": { "data": 0 @@ -10842,10 +11251,20 @@ "dataMask": 8, "values": { "top": { - "data": 8 + "data": 8, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, @@ -10959,7 +11378,7 @@ "fragileWhenPushed": false, "unpushable": false, "adventureModeExempt": true, - "ambientOcclusionLightValue": 0.2, + "ambientOcclusionLightValue": 1.0, "grassBlocking": false } }, @@ -10970,16 +11389,55 @@ "localizedName": "Hay Bale", "states": { "axis": { - "dataMask": 8, + "dataMask": 12, "values": { "x": { - "data": 4 + "data": 4, + "direction": [ + 1, + 0, + 0 + ] + }, + "-x": { + "data": 4, + "direction": [ + -1, + 0, + 0 + ] }, "y": { - "data": 0 + "data": 0, + "direction": [ + 0, + 1, + 0 + ] + }, + "-y": { + "data": 0, + "direction": [ + 0, + -1, + 0 + ] }, "z": { - "data": 8 + "data": 8, + "direction": [ + 0, + 0, + 1 + ] + }, + "-z": { + "data": 8, + "direction": [ + 0, + 0, + -1 + ] } } } @@ -11098,7 +11556,7 @@ "legacyId": 172, "id": "minecraft:hardened_clay", "unlocalizedName": "tile.clayHardened", - "localizedName": "Hardened Clay", + "localizedName": "Terracotta", "states": {}, "material": { "powerSource": false, @@ -11194,7 +11652,7 @@ "localizedName": "Plant", "states": { "facing": { - "dataMask": 3, + "dataMask": 0, "values": { "north": { "data": 0, @@ -11231,6 +11689,7 @@ } }, "half": { + "dataMask": 10, "values": { "upper": { "data": 10 @@ -11299,52 +11758,132 @@ "dataMask": 15, "values": { "0": { - "data": 0 + "data": 0, + "direction": [ + 0.0, + 0.0, + -1.0 + ] }, "1": { - "data": 1 + "data": 1, + "direction": [ + 0.5, + 0.0, + -1.0 + ] }, "2": { - "data": 2 + "data": 2, + "direction": [ + 1.0, + 0.0, + -1.0 + ] }, "3": { - "data": 3 + "data": 3, + "direction": [ + 1.0, + 0.0, + -0.5 + ] }, "4": { - "data": 4 + "data": 4, + "direction": [ + 1.0, + 0.0, + 0.0 + ] }, "5": { - "data": 5 + "data": 5, + "direction": [ + 1.0, + 0.0, + 0.5 + ] }, "6": { - "data": 6 + "data": 6, + "direction": [ + 1.0, + 0.0, + 1.0 + ] }, "7": { - "data": 7 + "data": 7, + "direction": [ + 0.5, + 0.0, + 1.0 + ] }, "8": { - "data": 8 + "data": 8, + "direction": [ + 0.0, + 0.0, + 1.0 + ] }, "9": { - "data": 9 + "data": 9, + "direction": [ + -0.5, + 0.0, + 1.0 + ] }, "10": { - "data": 10 + "data": 10, + "direction": [ + -1.0, + 0.0, + 1.0 + ] }, "11": { - "data": 11 + "data": 11, + "direction": [ + -1.0, + 0.0, + 0.5 + ] }, "12": { - "data": 12 + "data": 12, + "direction": [ + -1.0, + 0.0, + 0.0 + ] }, "13": { - "data": 13 + "data": 13, + "direction": [ + -1.0, + 0.0, + -0.5 + ] }, "14": { - "data": 14 + "data": 14, + "direction": [ + -1.0, + 0.0, + -1.0 + ] }, "15": { - "data": 15 + "data": 15, + "direction": [ + -0.5, + 0.0, + -1.0 + ] } } } @@ -11621,15 +12160,25 @@ "dataMask": 4, "values": { "top": { - "data": 4 + "data": 4, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "shape": { - "dataMask": 7, + "dataMask": 0, "values": { "straight": { "data": 0 @@ -11692,6 +12241,7 @@ } }, "variant": { + "dataMask": 0, "values": { "red_sandstone": { "data": 0 @@ -11734,14 +12284,25 @@ "dataMask": 8, "values": { "top": { - "data": 8 + "data": 8, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "variant": { + "dataMask": 0, "values": { "red_sandstone": { "data": 0 @@ -11818,6 +12379,7 @@ } }, "in_wall": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -11919,6 +12481,7 @@ } }, "in_wall": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12020,6 +12583,7 @@ } }, "in_wall": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12121,6 +12685,7 @@ } }, "in_wall": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12222,6 +12787,7 @@ } }, "in_wall": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12286,6 +12852,7 @@ "localizedName": "Spruce Fence", "states": { "east": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12296,6 +12863,7 @@ } }, "north": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12306,6 +12874,7 @@ } }, "south": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12316,6 +12885,7 @@ } }, "west": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12358,6 +12928,7 @@ "localizedName": "Birch Fence", "states": { "east": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12368,6 +12939,7 @@ } }, "north": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12378,6 +12950,7 @@ } }, "south": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12388,6 +12961,7 @@ } }, "west": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12430,6 +13004,7 @@ "localizedName": "Jungle Fence", "states": { "east": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12440,6 +13015,7 @@ } }, "north": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12450,6 +13026,7 @@ } }, "south": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12460,6 +13037,7 @@ } }, "west": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12502,6 +13080,7 @@ "localizedName": "Dark Oak Fence", "states": { "east": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12512,6 +13091,7 @@ } }, "north": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12522,6 +13102,7 @@ } }, "south": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12532,6 +13113,7 @@ } }, "west": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12574,6 +13156,7 @@ "localizedName": "Acacia Fence", "states": { "east": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12584,6 +13167,7 @@ } }, "north": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12594,6 +13178,7 @@ } }, "south": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12604,6 +13189,7 @@ } }, "west": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12694,6 +13280,7 @@ } }, "hinge": { + "dataMask": 0, "values": { "left": { "data": 0 @@ -12715,6 +13302,7 @@ } }, "powered": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12805,6 +13393,7 @@ } }, "hinge": { + "dataMask": 0, "values": { "left": { "data": 0 @@ -12826,6 +13415,7 @@ } }, "powered": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -12916,6 +13506,7 @@ } }, "hinge": { + "dataMask": 0, "values": { "left": { "data": 0 @@ -12937,6 +13528,7 @@ } }, "powered": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -13027,6 +13619,7 @@ } }, "hinge": { + "dataMask": 0, "values": { "left": { "data": 0 @@ -13048,6 +13641,7 @@ } }, "powered": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -13138,6 +13732,7 @@ } }, "hinge": { + "dataMask": 0, "values": { "left": { "data": 0 @@ -13159,6 +13754,7 @@ } }, "powered": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -13286,6 +13882,7 @@ "localizedName": "Chorus Plant", "states": { "down": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -13296,6 +13893,7 @@ } }, "east": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -13306,6 +13904,7 @@ } }, "north": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -13316,6 +13915,7 @@ } }, "south": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -13326,6 +13926,7 @@ } }, "up": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -13336,6 +13937,7 @@ } }, "west": { + "dataMask": 0, "values": { "true": { "data": 0 @@ -13464,16 +14066,55 @@ "localizedName": "Purpur Pillar", "states": { "axis": { - "dataMask": 8, + "dataMask": 12, "values": { "x": { - "data": 4 + "data": 4, + "direction": [ + 1, + 0, + 0 + ] + }, + "-x": { + "data": 4, + "direction": [ + -1, + 0, + 0 + ] }, "y": { - "data": 0 + "data": 0, + "direction": [ + 0, + 1, + 0 + ] + }, + "-y": { + "data": 0, + "direction": [ + 0, + -1, + 0 + ] }, "z": { - "data": 8 + "data": 8, + "direction": [ + 0, + 0, + 1 + ] + }, + "-z": { + "data": 8, + "direction": [ + 0, + 0, + -1 + ] } } } @@ -13550,15 +14191,25 @@ "dataMask": 4, "values": { "top": { - "data": 4 + "data": 4, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "shape": { - "dataMask": 7, + "dataMask": 0, "values": { "straight": { "data": 0 @@ -13610,6 +14261,7 @@ "localizedName": "Purpur Slab", "states": { "variant": { + "dataMask": 0, "values": { "default": { "data": 0 @@ -13652,14 +14304,25 @@ "dataMask": 8, "values": { "top": { - "data": 8 + "data": 8, + "direction": [ + 0, + 1, + 0 + ] }, "bottom": { - "data": 0 + "data": 0, + "direction": [ + 0, + -1, + 0 + ] } } }, "variant": { + "dataMask": 0, "values": { "default": { "data": 0 @@ -14071,7 +14734,7 @@ "fragileWhenPushed": false, "unpushable": false, "adventureModeExempt": true, - "ambientOcclusionLightValue": 0.2, + "ambientOcclusionLightValue": 1.0, "grassBlocking": false } }, @@ -14175,16 +14838,55 @@ "localizedName": "Bone Block", "states": { "axis": { - "dataMask": 8, + "dataMask": 12, "values": { "x": { - "data": 4 + "data": 4, + "direction": [ + 1, + 0, + 0 + ] + }, + "-x": { + "data": 4, + "direction": [ + -1, + 0, + 0 + ] }, "y": { - "data": 0 + "data": 0, + "direction": [ + 0, + 1, + 0 + ] + }, + "-y": { + "data": 0, + "direction": [ + 0, + -1, + 0 + ] }, "z": { - "data": 8 + "data": 8, + "direction": [ + 0, + 0, + 1 + ] + }, + "-z": { + "data": 8, + "direction": [ + 0, + 0, + -1 + ] } } } @@ -14337,7 +15039,7 @@ "fragileWhenPushed": false, "unpushable": false, "adventureModeExempt": false, - "ambientOcclusionLightValue": 0.2, + "ambientOcclusionLightValue": 1.0, "grassBlocking": false } }, @@ -15701,6 +16403,1280 @@ "grassBlocking": false } }, + { + "legacyId": 235, + "id": "minecraft:white_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaWhite", + "localizedName": "White Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 236, + "id": "minecraft:orange_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaOrange", + "localizedName": "Orange Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 237, + "id": "minecraft:magenta_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaMagenta", + "localizedName": "Magenta Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 238, + "id": "minecraft:light_blue_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaLightBlue", + "localizedName": "Light Blue Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 239, + "id": "minecraft:yellow_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaYellow", + "localizedName": "Yellow Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 240, + "id": "minecraft:lime_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaLime", + "localizedName": "Lime Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 241, + "id": "minecraft:pink_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaPink", + "localizedName": "Pink Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 242, + "id": "minecraft:gray_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaGray", + "localizedName": "Gray Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 243, + "id": "minecraft:silver_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaSilver", + "localizedName": "Light Gray Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 244, + "id": "minecraft:cyan_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaCyan", + "localizedName": "Cyan Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 245, + "id": "minecraft:purple_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaPurple", + "localizedName": "Purple Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 246, + "id": "minecraft:blue_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaBlue", + "localizedName": "Blue Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 247, + "id": "minecraft:brown_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaBrown", + "localizedName": "Brown Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 248, + "id": "minecraft:green_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaGreen", + "localizedName": "Green Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 249, + "id": "minecraft:red_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaRed", + "localizedName": "Red Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 250, + "id": "minecraft:black_glazed_terracotta", + "unlocalizedName": "tile.glazedTerracottaBlack", + "localizedName": "Black Glazed Terracotta", + "states": { + "facing": { + "dataMask": 3, + "values": { + "north": { + "data": 2, + "direction": [ + 0, + 0, + -1 + ] + }, + "south": { + "data": 0, + "direction": [ + 0, + 0, + 1 + ] + }, + "west": { + "data": 1, + "direction": [ + -1, + 0, + 0 + ] + }, + "east": { + "data": 3, + "direction": [ + 1, + 0, + 0 + ] + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.4, + "resistance": 7.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 251, + "id": "minecraft:concrete", + "unlocalizedName": "tile.concrete", + "localizedName": "tile.concrete.name", + "states": { + "color": { + "dataMask": 15, + "values": { + "white": { + "data": 0 + }, + "orange": { + "data": 1 + }, + "magenta": { + "data": 2 + }, + "light_blue": { + "data": 3 + }, + "yellow": { + "data": 4 + }, + "lime": { + "data": 5 + }, + "pink": { + "data": 6 + }, + "gray": { + "data": 7 + }, + "silver": { + "data": 8 + }, + "cyan": { + "data": 9 + }, + "purple": { + "data": 10 + }, + "blue": { + "data": 11 + }, + "brown": { + "data": 12 + }, + "green": { + "data": 13 + }, + "red": { + "data": 14 + }, + "black": { + "data": 15 + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 1.8, + "resistance": 9.0, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": true, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, + { + "legacyId": 252, + "id": "minecraft:concrete_powder", + "unlocalizedName": "tile.concretePowder", + "localizedName": "tile.concretePowder.name", + "states": { + "color": { + "dataMask": 15, + "values": { + "white": { + "data": 0 + }, + "orange": { + "data": 1 + }, + "magenta": { + "data": 2 + }, + "light_blue": { + "data": 3 + }, + "yellow": { + "data": 4 + }, + "lime": { + "data": 5 + }, + "pink": { + "data": 6 + }, + "gray": { + "data": 7 + }, + "silver": { + "data": 8 + }, + "cyan": { + "data": 9 + }, + "purple": { + "data": 10 + }, + "blue": { + "data": 11 + }, + "brown": { + "data": 12 + }, + "green": { + "data": 13 + }, + "red": { + "data": 14 + }, + "black": { + "data": 15 + } + } + } + }, + "material": { + "powerSource": false, + "lightOpacity": 255, + "lightValue": 0, + "usingNeighborLight": false, + "hardness": 0.5, + "resistance": 2.5, + "ticksRandomly": false, + "fullCube": true, + "slipperiness": 0.6, + "renderedAsNormalBlock": true, + "liquid": false, + "solid": true, + "movementBlocker": true, + "burnable": false, + "opaque": true, + "replacedDuringPlacement": false, + "toolRequired": false, + "fragileWhenPushed": false, + "unpushable": false, + "adventureModeExempt": false, + "ambientOcclusionLightValue": 0.2, + "grassBlocking": false + } + }, { "legacyId": 255, "id": "minecraft:structure_block", diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java index 3f995e02..aaf73006 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java @@ -2,6 +2,7 @@ package com.boydti.fawe.bukkit; import com.boydti.fawe.Fawe; import com.boydti.fawe.IFawe; +import com.boydti.fawe.bukkit.chat.BukkitChatManager; import com.boydti.fawe.bukkit.regions.FactionsFeature; import com.boydti.fawe.bukkit.regions.FactionsOneFeature; import com.boydti.fawe.bukkit.regions.FactionsUUIDFeature; @@ -31,6 +32,7 @@ import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.ReflectionUtils; import com.boydti.fawe.util.TaskManager; import com.sk89q.bukkit.util.FallbackRegistrationListener; +import com.sk89q.worldedit.bukkit.BukkitPlayerBlockBag; import com.sk89q.worldedit.bukkit.BukkitWorld; import com.sk89q.worldedit.bukkit.EditSessionBlockChangeDelegate; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -78,6 +80,7 @@ public class FaweBukkit implements IFawe, Listener { com.sk89q.worldedit.bukkit.BukkitPlayer.inject(); // Fixes BukkitWorld.inject(); // Fixes FallbackRegistrationListener.inject(); // Fixes + BukkitPlayerBlockBag.inject(); // features try { new BrushListener(plugin); } catch (Throwable e) { @@ -95,6 +98,11 @@ public class FaweBukkit implements IFawe, Listener { if (Bukkit.getVersion().contains("git-Paper") && Settings.IMP.EXPERIMENTAL.DYNAMIC_CHUNK_RENDERING) { new RenderListener(plugin); } + try { + Fawe.get().setChatManager(new BukkitChatManager()); + } catch (Throwable ignore) { + ignore.printStackTrace(); + } } catch (final Throwable e) { MainUtil.handleError(e); Bukkit.getServer().shutdown(); diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/ArrayWrapper.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/ArrayWrapper.java new file mode 100644 index 00000000..5ce87056 --- /dev/null +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/ArrayWrapper.java @@ -0,0 +1,110 @@ +package com.boydti.fawe.bukkit.chat; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import org.apache.commons.lang.Validate; + +/** + * Represents a wrapper around an array class of an arbitrary reference type, + * which properly implements "value" hash code and equality functions. + *

+ * This class is intended for use as a key to a map. + *

+ * + * @param The type of elements in the array. + * @author Glen Husman + * @see java.util.Arrays + */ +public final class ArrayWrapper { + + /** + * Creates an array wrapper with some elements. + * + * @param elements The elements of the array. + */ + public ArrayWrapper(E... elements) { + setArray(elements); + } + + private E[] _array; + + /** + * Retrieves a reference to the wrapped array instance. + * + * @return The array wrapped by this instance. + */ + public E[] getArray() { + return _array; + } + + /** + * Set this wrapper to wrap a new array instance. + * + * @param array The new wrapped array. + */ + public void setArray(E[] array) { + Validate.notNull(array, "The array must not be null."); + _array = array; + } + + /** + * Determines if this object has a value equivalent to another object. + * + * @see java.util.Arrays#equals(Object[], Object[]) + */ + @SuppressWarnings("rawtypes") + @Override + public boolean equals(Object other) { + if (!(other instanceof ArrayWrapper)) { + return false; + } + return Arrays.equals(_array, ((ArrayWrapper) other)._array); + } + + /** + * Gets the hash code represented by this objects value. + * + * @return This object's hash code. + * @see java.util.Arrays#hashCode(Object[]) + */ + @Override + public int hashCode() { + return Arrays.hashCode(_array); + } + + /** + * Converts an iterable element collection to an array of elements. + * The iteration order of the specified object will be used as the array element order. + * + * @param list The iterable of objects which will be converted to an array. + * @param c The type of the elements of the array. + * @return An array of elements in the specified iterable. + */ + @SuppressWarnings("unchecked") + public static T[] toArray(Iterable list, Class c) { + int size = -1; + if (list instanceof Collection) { + @SuppressWarnings("rawtypes") + Collection coll = (Collection) list; + size = coll.size(); + } + + + if (size < 0) { + size = 0; + // Ugly hack: Count it ourselves + for (@SuppressWarnings("unused") T element : list) { + size++; + } + } + + T[] result = (T[]) Array.newInstance(c, size); + int i = 0; + for (T element : list) { // Assumes iteration order is consistent + result[i++] = element; // Assign array element at index THEN increment counter + } + return result; + } + +} diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/BukkitChatManager.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/BukkitChatManager.java new file mode 100644 index 00000000..92ab9d18 --- /dev/null +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/BukkitChatManager.java @@ -0,0 +1,64 @@ +package com.boydti.fawe.bukkit.chat; + +import com.boydti.fawe.bukkit.BukkitPlayer; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.util.chat.ChatManager; +import com.boydti.fawe.util.chat.Message; +import com.boydti.fawe.wrappers.FakePlayer; +import java.util.ArrayList; +import java.util.List; +import org.bukkit.ChatColor; + +public class BukkitChatManager implements ChatManager { + + @Override + public FancyMessage builder() { + return new FancyMessage(""); + } + + @Override + public void color(Message message, String color) { + message.$(this).color(ChatColor.getByChar(BBC.color(color).substring(1))); + } + + @Override + public void tooltip(Message message, Message... tooltips) { + List lines = new ArrayList<>(); + for (Message tooltip : tooltips) { + lines.add(tooltip.$(this)); + } + message.$(this).formattedTooltip(lines); + } + + @Override + public void command(Message message, String command) { + message.$(this).command(command); + } + + @Override + public void text(Message message, String text) { + message.$(this).color(BBC.color(text)); + } + + @Override + public void send(Message Message, FawePlayer player) { + if (player == FakePlayer.getConsole().toFawePlayer()) { + player.sendMessage(Message.$(this).toOldMessageFormat()); + } else { + Message.$(this).send(((BukkitPlayer) player).parent); + } + } + + @Override + public void suggest(Message Message, String command) { + Message.$(this).suggest(command); + } + + @Override + public void link(Message Message, String url) { + Message.$(this).link(url); + } + + +} diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/FancyMessage.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/FancyMessage.java new file mode 100644 index 00000000..29d3c507 --- /dev/null +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/FancyMessage.java @@ -0,0 +1,955 @@ +package com.boydti.fawe.bukkit.chat; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.logging.Level; +import org.bukkit.Achievement; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.Statistic; +import org.bukkit.Statistic.Type; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + + +import static com.boydti.fawe.bukkit.chat.TextualComponent.rawText; + +/** + * Represents a formattable message. Such messages can use elements such as colors, formatting codes, hover and click data, and other features provided by the vanilla Minecraft JSON message formatter. + * This class allows plugins to emulate the functionality of the vanilla Minecraft tellraw command. + *

+ * This class follows the builder pattern, allowing for method chaining. + * It is set up such that invocations of property-setting methods will affect the current editing component, + * and a call to {@link #then()} or {@link #then(String)} will append a new editing component to the end of the message, + * optionally initializing it with text. Further property-setting method calls will affect that editing component. + *

+ */ +public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable, ConfigurationSerializable { + + static { + ConfigurationSerialization.registerClass(FancyMessage.class); + } + + private List messageParts; + private int index = 0; + private String jsonString; + private boolean dirty; + + private static Constructor nmsPacketPlayOutChatConstructor; + + @Override + public FancyMessage clone() throws CloneNotSupportedException { + FancyMessage instance = (FancyMessage) super.clone(); + instance.messageParts = new ArrayList<>(messageParts.size()); + for (int i = 0; i < messageParts.size(); i++) { + instance.messageParts.add(i, messageParts.get(i).clone()); + } + instance.index = index; + instance.dirty = false; + instance.jsonString = null; + return instance; + } + + /** + * Creates a JSON message with text. + * + * @param firstPartText The existing text in the message. + */ + public FancyMessage(final String firstPartText) { + this(rawText(firstPartText)); + } + + private FancyMessage(final TextualComponent firstPartText) { + messageParts = new ArrayList<>(); + messageParts.add(new MessagePart(firstPartText)); + index = messageParts.size(); + jsonString = null; + dirty = false; + if (nmsPacketPlayOutChatConstructor == null) { + try { + nmsPacketPlayOutChatConstructor = Reflection.getNMSClass("PacketPlayOutChat").getDeclaredConstructor(Reflection.getNMSClass("IChatBaseComponent")); + nmsPacketPlayOutChatConstructor.setAccessible(true); + } catch (NoSuchMethodException e) { + Bukkit.getLogger().log(Level.SEVERE, "Could not find Minecraft method or constructor.", e); + } catch (SecurityException e) { + Bukkit.getLogger().log(Level.WARNING, "Could not access constructor.", e); + } + } + } + + /** + * Creates a JSON message without text. + */ + public FancyMessage() { + this((TextualComponent) null); + } + + /** + * Sets the text of the current editing component to a value. + * + * @param text The new text of the current editing component. + * @return This builder instance. + */ + public FancyMessage text(String text) { + MessagePart latest = latest(); + latest.text = rawText(text); + dirty = true; + return this; + } + + /** + * Sets the text of the current editing component to a value. + * + * @param text The new text of the current editing component. + * @return This builder instance. + */ + public FancyMessage text(TextualComponent text) { + MessagePart latest = latest(); + latest.text = text; + dirty = true; + return this; + } + + /** + * + * @param text Text with coloring + * @return This builder instance. + * @throws IllegalArgumentException If the specified {@code ChatColor} enumeration value is not a color (but a format value). + */ + public FancyMessage color(String text) { + index = messageParts.size(); + boolean color = false; + ArrayDeque colors = new ArrayDeque<>(); + int last = 0; + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (color != (color = false)) { + ChatColor chatColor = ChatColor.getByChar(c); + if (chatColor != null) { + if (i - last > 1) { + append(text.substring(last, i - 1)); + colors.forEach(this::color); + colors.clear(); + } + colors.add(chatColor); + last = i + 1; + } + } + if (c == '\u00A7') { + color = true; + } + } + if (text.length() - last > 0) { + append(text.substring(last, text.length())); + colors.forEach(this::color); + } + index++; + return this; + } + + /** + * Sets the color of the current editing component to a value.
+ * Setting the color will clear current styles + * + * @param color The new color of the current editing component. + * @return This builder instance. + * @throws IllegalArgumentException If the specified {@code ChatColor} enumeration value is not a color (but a format value). + */ + public FancyMessage color(ChatColor color) { + if (!color.isColor()) { + if (color.isFormat()) { + return style(color); + } + if (color == ChatColor.RESET) { + color = ChatColor.WHITE; + } + } else { + latest().styles.clear(); + } + latest().color = color; + dirty = true; + return this; + } + + /** + * Sets the stylization of the current editing component. + * + * @param styles The array of styles to apply to the editing component. + * @return This builder instance. + * @throws IllegalArgumentException If any of the enumeration values in the array do not represent formatters. + */ + public FancyMessage style(ChatColor... styles) { + for (final ChatColor style : styles) { + if (!style.isFormat()) { + color(style); + } + } + latest().styles.addAll(Arrays.asList(styles)); + dirty = true; + return this; + } + + /** + * Set the behavior of the current editing component to instruct the client to open a file on the client side filesystem when the currently edited part of the {@code FancyMessage} is clicked. + * + * @param path The path of the file on the client filesystem. + * @return This builder instance. + */ + public FancyMessage file(final String path) { + onClick("open_file", path); + return this; + } + + /** + * Set the behavior of the current editing component to instruct the client to open a webpage in the client's web browser when the currently edited part of the {@code FancyMessage} is clicked. + * + * @param url The URL of the page to open when the link is clicked. + * @return This builder instance. + */ + public FancyMessage link(final String url) { + onClick("open_url", url); + return this; + } + + /** + * Set the behavior of the current editing component to instruct the client to replace the chat input box content with the specified string when the currently edited part of the {@code FancyMessage} is clicked. + * The client will not immediately send the command to the server to be executed unless the client player submits the command/chat message, usually with the enter key. + * + * @param command The text to display in the chat bar of the client. + * @return This builder instance. + */ + public FancyMessage suggest(final String command) { + onClick("suggest_command", command); + return this; + } + + /** + * Set the behavior of the current editing component to instruct the client to append the chat input box content with the specified string when the currently edited part of the {@code FancyMessage} is SHIFT-CLICKED. + * The client will not immediately send the command to the server to be executed unless the client player submits the command/chat message, usually with the enter key. + * + * @param command The text to append to the chat bar of the client. + * @return This builder instance. + */ + public FancyMessage insert(final String command) { + onCurrent(m -> m.insertionData = command); + dirty = true; + return this; + } + + /** + * Set the behavior of the current editing component to instruct the client to send the specified string to the server as a chat message when the currently edited part of the {@code FancyMessage} is clicked. + * The client will immediately send the command to the server to be executed when the editing component is clicked. + * + * @param command The text to display in the chat bar of the client. + * @return This builder instance. + */ + public FancyMessage command(final String command) { + onClick("run_command", command); + return this; + } + + /** + * Set the behavior of the current editing component to display information about an achievement when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param name The name of the achievement to display, excluding the "achievement." prefix. + * @return This builder instance. + */ + public FancyMessage achievementTooltip(final String name) { + onHover("show_achievement", new JsonString("achievement." + name)); + return this; + } + + /** + * Set the behavior of the current editing component to display information about an achievement when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param which The achievement to display. + * @return This builder instance. + */ + public FancyMessage achievementTooltip(final Achievement which) { + try { + Object achievement = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getNMSAchievement", Achievement.class).invoke(null, which); + return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Achievement"), "name").get(achievement)); + } catch (IllegalAccessException e) { + Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e); + return this; + } catch (IllegalArgumentException e) { + Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e); + return this; + } catch (InvocationTargetException e) { + Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e); + return this; + } + } + + /** + * Set the behavior of the current editing component to display information about a parameterless statistic when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param which The statistic to display. + * @return This builder instance. + * @throws IllegalArgumentException If the statistic requires a parameter which was not supplied. + */ + public FancyMessage statisticTooltip(final Statistic which) { + Type type = which.getType(); + if (type != Type.UNTYPED) { + throw new IllegalArgumentException("That statistic requires an additional " + type + " parameter!"); + } + try { + Object statistic = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getNMSStatistic", Statistic.class).invoke(null, which); + return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Statistic"), "name").get(statistic)); + } catch (IllegalAccessException e) { + Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e); + return this; + } catch (IllegalArgumentException e) { + Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e); + return this; + } catch (InvocationTargetException e) { + Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e); + return this; + } + } + + /** + * Set the behavior of the current editing component to display information about a statistic parameter with a material when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param which The statistic to display. + * @param item The sole material parameter to the statistic. + * @return This builder instance. + * @throws IllegalArgumentException If the statistic requires a parameter which was not supplied, or was supplied a parameter that was not required. + */ + public FancyMessage statisticTooltip(final Statistic which, Material item) { + Type type = which.getType(); + if (type == Type.UNTYPED) { + throw new IllegalArgumentException("That statistic needs no additional parameter!"); + } + if ((type == Type.BLOCK && item.isBlock()) || type == Type.ENTITY) { + throw new IllegalArgumentException("Wrong parameter type for that statistic - needs " + type + "!"); + } + try { + Object statistic = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getMaterialStatistic", Statistic.class, Material.class).invoke(null, which, item); + return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Statistic"), "name").get(statistic)); + } catch (IllegalAccessException e) { + Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e); + return this; + } catch (IllegalArgumentException e) { + Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e); + return this; + } catch (InvocationTargetException e) { + Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e); + return this; + } + } + + /** + * Set the behavior of the current editing component to display information about a statistic parameter with an entity type when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param which The statistic to display. + * @param entity The sole entity type parameter to the statistic. + * @return This builder instance. + * @throws IllegalArgumentException If the statistic requires a parameter which was not supplied, or was supplied a parameter that was not required. + */ + public FancyMessage statisticTooltip(final Statistic which, EntityType entity) { + Type type = which.getType(); + if (type == Type.UNTYPED) { + throw new IllegalArgumentException("That statistic needs no additional parameter!"); + } + if (type != Type.ENTITY) { + throw new IllegalArgumentException("Wrong parameter type for that statistic - needs " + type + "!"); + } + try { + Object statistic = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getEntityStatistic", Statistic.class, EntityType.class).invoke(null, which, entity); + return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Statistic"), "name").get(statistic)); + } catch (IllegalAccessException e) { + Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e); + return this; + } catch (IllegalArgumentException e) { + Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e); + return this; + } catch (InvocationTargetException e) { + Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e); + return this; + } + } + + /** + * Set the behavior of the current editing component to display information about an item when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param itemJSON A string representing the JSON-serialized NBT data tag of an {@link org.bukkit.inventory.ItemStack}. + * @return This builder instance. + */ + public FancyMessage itemTooltip(final String itemJSON) { + onHover("show_item", new JsonString(itemJSON)); // Seems a bit hacky, considering we have a JSON object as a parameter + return this; + } + + /** + * Set the behavior of the current editing component to display information about an item when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param itemStack The stack for which to display information. + * @return This builder instance. + */ + public FancyMessage itemTooltip(final ItemStack itemStack) { + try { + Object nmsItem = Reflection.getMethod(Reflection.getOBCClass("inventory.CraftItemStack"), "asNMSCopy", ItemStack.class).invoke(null, itemStack); + return itemTooltip(Reflection.getMethod(Reflection.getNMSClass("ItemStack"), "save", Reflection.getNMSClass("NBTTagCompound")).invoke(nmsItem, Reflection.getNMSClass("NBTTagCompound").newInstance()).toString()); + } catch (Exception e) { + e.printStackTrace(); + return this; + } + } + + /** + * Set the behavior of the current editing component to display raw text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param text The text, which supports newlines, which will be displayed to the client upon hovering. + * @return This builder instance. + */ + public FancyMessage tooltip(final String text) { + onHover("show_text", new JsonString(text)); + return this; + } + + /** + * Set the behavior of the current editing component to display raw text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param lines The lines of text which will be displayed to the client upon hovering. The iteration order of this object will be the order in which the lines of the tooltip are created. + * @return This builder instance. + */ + public FancyMessage tooltip(final Iterable lines) { + tooltip(com.boydti.fawe.bukkit.chat.ArrayWrapper.toArray(lines, String.class)); + return this; + } + + /** + * Set the behavior of the current editing component to display raw text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param lines The lines of text which will be displayed to the client upon hovering. + * @return This builder instance. + */ + public FancyMessage tooltip(final String... lines) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < lines.length; i++) { + builder.append(lines[i]); + if (i != lines.length - 1) { + builder.append('\n'); + } + } + tooltip(builder.toString()); + return this; + } + + /** + * Set the behavior of the current editing component to display formatted text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param text The formatted text which will be displayed to the client upon hovering. + * @return This builder instance. + */ + public FancyMessage formattedTooltip(FancyMessage text) { + for (MessagePart component : text.messageParts) { + if (component.clickActionData != null && component.clickActionName != null) { + throw new IllegalArgumentException("The tooltip text cannot have click data."); + } else if (component.hoverActionData != null && component.hoverActionName != null) { + throw new IllegalArgumentException("The tooltip text cannot have a tooltip."); + } + } + onHover("show_text", text); + return this; + } + + /** + * Set the behavior of the current editing component to display the specified lines of formatted text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param lines The lines of formatted text which will be displayed to the client upon hovering. + * @return This builder instance. + */ + public FancyMessage formattedTooltip(FancyMessage... lines) { + if (lines.length < 1) { + onHover(null, null); // Clear tooltip + return this; + } + + FancyMessage result = new FancyMessage(); + result.messageParts.clear(); // Remove the one existing text component that exists by default, which destabilizes the object + + for (int i = 0; i < lines.length; i++) { + try { + for (MessagePart component : lines[i]) { + if (component.clickActionData != null && component.clickActionName != null) { + throw new IllegalArgumentException("The tooltip text cannot have click data."); + } else if (component.hoverActionData != null && component.hoverActionName != null) { + throw new IllegalArgumentException("The tooltip text cannot have a tooltip."); + } + if (component.hasText()) { + result.messageParts.add(component.clone()); + result.index = result.messageParts.size(); + } + } + if (i != lines.length - 1) { + result.messageParts.add(new MessagePart(rawText("\n"))); + result.index = result.messageParts.size(); + } + } catch (CloneNotSupportedException e) { + Bukkit.getLogger().log(Level.WARNING, "Failed to clone object", e); + return this; + } + } + return formattedTooltip(result.messageParts.isEmpty() ? null : result); // Throws NPE if size is 0, intended + } + + /** + * Set the behavior of the current editing component to display the specified lines of formatted text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param lines The lines of text which will be displayed to the client upon hovering. The iteration order of this object will be the order in which the lines of the tooltip are created. + * @return This builder instance. + */ + public FancyMessage formattedTooltip(final Iterable lines) { + return formattedTooltip(com.boydti.fawe.bukkit.chat.ArrayWrapper.toArray(lines, FancyMessage.class)); + } + + /** + * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message. + * + * @param replacements The replacements, in order, that will be used in the language-specific message. + * @return This builder instance. + */ + public FancyMessage translationReplacements(final String... replacements) { + for (String str : replacements) { + latest().translationReplacements.add(new JsonString(str)); + } + dirty = true; + + return this; + } + /* + + /** + * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message. + * @param replacements The replacements, in order, that will be used in the language-specific message. + * @return This builder instance. + */ /* ------------ + public FancyMessage translationReplacements(final Iterable replacements){ + for(CharSequence str : replacements){ + latest().translationReplacements.add(new JsonString(str)); + } + + return this; + } + + */ + + /** + * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message. + * + * @param replacements The replacements, in order, that will be used in the language-specific message. + * @return This builder instance. + */ + public FancyMessage translationReplacements(final FancyMessage... replacements) { + for (FancyMessage str : replacements) { + latest().translationReplacements.add(str); + } + + dirty = true; + + return this; + } + + /** + * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message. + * + * @param replacements The replacements, in order, that will be used in the language-specific message. + * @return This builder instance. + */ + public FancyMessage translationReplacements(final Iterable replacements) { + return translationReplacements(com.boydti.fawe.bukkit.chat.ArrayWrapper.toArray(replacements, FancyMessage.class)); + } + + /** + * Terminate construction of the current editing component, and begin construction of a new message component. + * After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method. + * + * @param text The text which will populate the new message component. + * @return This builder instance. + */ + public FancyMessage then(final String text) { + return then(rawText(text)); + } + + private FancyMessage append(final String text) { + if (!latest().hasText()) { + throw new IllegalStateException("previous message part has no text"); + } + MessagePart latest = latest(); + messageParts.add(new MessagePart(rawText(text))); + latest().color = latest.color; + latest().styles.addAll(latest.styles); + dirty = true; + return this; + } + + /** + * Terminate construction of the current editing component, and begin construction of a new message component. + * After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method. + * + * @param text The text which will populate the new message component. + * @return This builder instance. + */ + public FancyMessage then(final TextualComponent text) { + if (!latest().hasText()) { + throw new IllegalStateException("previous message part has no text"); + } + messageParts.add(new MessagePart(text)); + index = messageParts.size(); + dirty = true; + return this; + } + + /** + * Terminate construction of the current editing component, and begin construction of a new message component. + * After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method. + * + * @return This builder instance. + */ + public FancyMessage then() { + if (!latest().hasText()) { + throw new IllegalStateException("previous message part has no text"); + } + messageParts.add(new MessagePart()); + index = messageParts.size(); + dirty = true; + return this; + } + + @Override + public void writeJson(JsonWriter writer) throws IOException { + if (messageParts.size() == 1) { + latest().writeJson(writer); + } else { + writer.beginObject().name("text").value("").name("extra").beginArray(); + for (final MessagePart part : this) { + part.writeJson(writer); + } + writer.endArray().endObject(); + } + } + + /** + * Serialize this fancy message, converting it into syntactically-valid JSON using a {@link com.google.gson.stream.JsonWriter}. + * This JSON should be compatible with vanilla formatter commands such as {@code /tellraw}. + * + * @return The JSON string representing this object. + */ + public String toJSONString() { + if (!dirty && jsonString != null) { + return jsonString; + } + StringWriter string = new StringWriter(); + JsonWriter json = new JsonWriter(string); + try { + writeJson(json); + json.close(); + } catch (IOException e) { + throw new RuntimeException("invalid message"); + } + jsonString = string.toString(); + dirty = false; + return jsonString; + } + + /** + * Sends this message to a player. The player will receive the fully-fledged formatted display of this message. + * + * @param player The player who will receive the message. + */ + public void send(Player player) { + send(player, toJSONString()); + } + + private void send(CommandSender sender, String jsonString) { + if (!(sender instanceof Player)) { + sender.sendMessage(toOldMessageFormat()); + return; + } + Player player = (Player) sender; + try { + Object handle = Reflection.getHandle(player); + Object connection = Reflection.getField(handle.getClass(), "playerConnection").get(handle); + Reflection.getMethod(connection.getClass(), "sendPacket", Reflection.getNMSClass("Packet")).invoke(connection, createChatPacket(jsonString)); + } catch (IllegalArgumentException e) { + Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e); + } catch (IllegalAccessException e) { + Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e); + } catch (InstantiationException e) { + Bukkit.getLogger().log(Level.WARNING, "Underlying class is abstract.", e); + } catch (InvocationTargetException e) { + Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e); + } catch (NoSuchMethodException e) { + Bukkit.getLogger().log(Level.WARNING, "Could not find method.", e); + } catch (ClassNotFoundException e) { + Bukkit.getLogger().log(Level.WARNING, "Could not find class.", e); + } + } + + // The ChatSerializer's instance of Gson + private static Object nmsChatSerializerGsonInstance; + private static Method fromJsonMethod; + + private Object createChatPacket(String json) throws IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { + if (nmsChatSerializerGsonInstance == null) { + // Find the field and its value, completely bypassing obfuscation + Class chatSerializerClazz; + + // Get the three parts of the version string (major version is currently unused) + // vX_Y_RZ + // X = major + // Y = minor + // Z = revision + final String version = Reflection.getVersion(); + String[] split = version.substring(1, version.length() - 1).split("_"); // Remove trailing dot + //int majorVersion = Integer.parseInt(split[0]); + int minorVersion = Integer.parseInt(split[1]); + int revisionVersion = Integer.parseInt(split[2].substring(1)); // Substring to ignore R + + if (minorVersion < 8 || (minorVersion == 8 && revisionVersion == 1)) { + chatSerializerClazz = Reflection.getNMSClass("ChatSerializer"); + } else { + chatSerializerClazz = Reflection.getNMSClass("IChatBaseComponent$ChatSerializer"); + } + + if (chatSerializerClazz == null) { + throw new ClassNotFoundException("Can't find the ChatSerializer class"); + } + + for (Field declaredField : chatSerializerClazz.getDeclaredFields()) { + if (Modifier.isFinal(declaredField.getModifiers()) && Modifier.isStatic(declaredField.getModifiers()) && declaredField.getType().getName().endsWith("Gson")) { + // We've found our field + declaredField.setAccessible(true); + nmsChatSerializerGsonInstance = declaredField.get(null); + fromJsonMethod = nmsChatSerializerGsonInstance.getClass().getMethod("fromJson", String.class, Class.class); + break; + } + } + } + + // Since the method is so simple, and all the obfuscated methods have the same name, it's easier to reimplement 'IChatBaseComponent a(String)' than to reflectively call it + // Of course, the implementation may change, but fuzzy matches might break with signature changes + Object serializedChatComponent = fromJsonMethod.invoke(nmsChatSerializerGsonInstance, json, Reflection.getNMSClass("IChatBaseComponent")); + + return nmsPacketPlayOutChatConstructor.newInstance(serializedChatComponent); + } + + /** + * Sends this message to a command sender. + * If the sender is a player, they will receive the fully-fledged formatted display of this message. + * Otherwise, they will receive a version of this message with less formatting. + * + * @param sender The command sender who will receive the message. + * @see #toOldMessageFormat() + */ + public void send(CommandSender sender) { + send(sender, toJSONString()); + } + + /** + * Sends this message to multiple command senders. + * + * @param senders The command senders who will receive the message. + * @see #send(org.bukkit.command.CommandSender) + */ + public void send(final Iterable senders) { + String string = toJSONString(); + for (final CommandSender sender : senders) { + send(sender, string); + } + } + + /** + * Convert this message to a human-readable string with limited formatting. + * This method is used to send this message to clients without JSON formatting support. + *

+ * Serialization of this message by using this message will include (in this order for each message part): + *

    + *
  1. The color of each message part.
  2. + *
  3. The applicable stylizations for each message part.
  4. + *
  5. The core text of the message part.
  6. + *
+ * The primary omissions are tooltips and clickable actions. Consequently, this method should be used only as a last resort. + *

+ *

+ * Color and formatting can be removed from the returned string by using {@link org.bukkit.ChatColor#stripColor(String)}.

+ * + * @return A human-readable string representing limited formatting in addition to the core text of this message. + */ + public String toOldMessageFormat() { + StringBuilder result = new StringBuilder(); + for (MessagePart part : this) { + result.append(part.color == null ? "" : part.color); + for (ChatColor formatSpecifier : part.styles) { + result.append(formatSpecifier); + } + result.append(part.text); + } + return result.toString(); + } + + private void onCurrent(Consumer call) { + for (int i = index - 1; i < messageParts.size(); i++) { + call.accept(messageParts.get(i)); + } + } + + private MessagePart latest() { + return messageParts.get(messageParts.size() - 1); + } + + private void onClick(final String name, final String data) { + onCurrent(m -> { m.clickActionName = name; m.clickActionData = data; }); + dirty = true; + } + + private void onHover(final String name, final JsonRepresentedObject data) { + onCurrent(m -> { m.hoverActionName = name; m.hoverActionData = data; }); + dirty = true; + } + + // Doc copied from interface + public Map serialize() { + HashMap map = new HashMap<>(); + map.put("messageParts", messageParts); +// map.put("JSON", toJSONString()); + return map; + } + + /** + * Deserializes a JSON-represented message from a mapping of key-value pairs. + * This is called by the Bukkit serialization API. + * It is not intended for direct public API consumption. + * + * @param serialized The key-value mapping which represents a fancy message. + */ + @SuppressWarnings("unchecked") + public static FancyMessage deserialize(Map serialized) { + FancyMessage msg = new FancyMessage(); + msg.messageParts = (List) serialized.get("messageParts"); + msg.jsonString = serialized.containsKey("JSON") ? serialized.get("JSON").toString() : null; + msg.dirty = !serialized.containsKey("JSON"); + return msg; + } + + /** + * Internally called method. Not for API consumption. + */ + public Iterator iterator() { + return messageParts.iterator(); + } + + private static JsonParser _stringParser = new JsonParser(); + + /** + * Deserializes a fancy message from its JSON representation. This JSON representation is of the format of + * that returned by {@link #toJSONString()}, and is compatible with vanilla inputs. + * + * @param json The JSON string which represents a fancy message. + * @return A {@code FancyMessage} representing the parameterized JSON message. + */ + public static FancyMessage deserialize(String json) { + JsonObject serialized = _stringParser.parse(json).getAsJsonObject(); + JsonArray extra = serialized.getAsJsonArray("extra"); // Get the extra component + FancyMessage returnVal = new FancyMessage(); + returnVal.messageParts.clear(); + for (JsonElement mPrt : extra) { + MessagePart component = new MessagePart(); + JsonObject messagePart = mPrt.getAsJsonObject(); + for (Map.Entry entry : messagePart.entrySet()) { + // Deserialize text + if (TextualComponent.isTextKey(entry.getKey())) { + // The map mimics the YAML serialization, which has a "key" field and one or more "value" fields + Map serializedMapForm = new HashMap<>(); // Must be object due to Bukkit serializer API compliance + serializedMapForm.put("key", entry.getKey()); + if (entry.getValue().isJsonPrimitive()) { + // Assume string + serializedMapForm.put("value", entry.getValue().getAsString()); + } else { + // Composite object, but we assume each element is a string + for (Map.Entry compositeNestedElement : entry.getValue().getAsJsonObject().entrySet()) { + serializedMapForm.put("value." + compositeNestedElement.getKey(), compositeNestedElement.getValue().getAsString()); + } + } + component.text = TextualComponent.deserialize(serializedMapForm); + } else if (MessagePart.stylesToNames.inverse().containsKey(entry.getKey())) { + if (entry.getValue().getAsBoolean()) { + component.styles.add(MessagePart.stylesToNames.inverse().get(entry.getKey())); + } + } else if (entry.getKey().equals("color")) { + component.color = ChatColor.valueOf(entry.getValue().getAsString().toUpperCase()); + } else if (entry.getKey().equals("clickEvent")) { + JsonObject object = entry.getValue().getAsJsonObject(); + component.clickActionName = object.get("action").getAsString(); + component.clickActionData = object.get("value").getAsString(); + } else if (entry.getKey().equals("hoverEvent")) { + JsonObject object = entry.getValue().getAsJsonObject(); + component.hoverActionName = object.get("action").getAsString(); + if (object.get("value").isJsonPrimitive()) { + // Assume string + component.hoverActionData = new JsonString(object.get("value").getAsString()); + } else { + // Assume composite type + // The only composite type we currently store is another FancyMessage + // Therefore, recursion time! + component.hoverActionData = deserialize(object.get("value").toString() /* This should properly serialize the JSON object as a JSON string */); + } + } else if (entry.getKey().equals("insertion")) { + component.insertionData = entry.getValue().getAsString(); + } else if (entry.getKey().equals("with")) { + for (JsonElement object : entry.getValue().getAsJsonArray()) { + if (object.isJsonPrimitive()) { + component.translationReplacements.add(new JsonString(object.getAsString())); + } else { + // Only composite type stored in this array is - again - FancyMessages + // Recurse within this function to parse this as a translation replacement + component.translationReplacements.add(deserialize(object.toString())); + } + } + } + } + returnVal.messageParts.add(component); + returnVal.index = returnVal.messageParts.size(); + } + return returnVal; + } + +} diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/JsonRepresentedObject.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/JsonRepresentedObject.java new file mode 100644 index 00000000..f5c2837f --- /dev/null +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/JsonRepresentedObject.java @@ -0,0 +1,18 @@ +package com.boydti.fawe.bukkit.chat; + +import com.google.gson.stream.JsonWriter; +import java.io.IOException; + +/** + * Represents an object that can be serialized to a JSON writer instance. + */ +interface JsonRepresentedObject { + + /** + * Writes the JSON representation of this object to the specified writer. + * @param writer The JSON writer which will receive the object. + * @throws java.io.IOException If an error occurs writing to the stream. + */ + public void writeJson(JsonWriter writer) throws IOException; + +} diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/JsonString.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/JsonString.java new file mode 100644 index 00000000..e59fb2e7 --- /dev/null +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/JsonString.java @@ -0,0 +1,46 @@ +package com.boydti.fawe.bukkit.chat; + +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import org.bukkit.configuration.serialization.ConfigurationSerializable; + +/** + * Represents a JSON string value. + * Writes by this object will not write name values nor begin/end objects in the JSON stream. + * All writes merely write the represented string value. + */ +final class JsonString implements JsonRepresentedObject, ConfigurationSerializable { + + private String _value; + + public JsonString(CharSequence value) { + _value = value == null ? null : value.toString(); + } + + @Override + public void writeJson(JsonWriter writer) throws IOException { + writer.value(getValue()); + } + + public String getValue() { + return _value; + } + + public Map serialize() { + HashMap theSingleValue = new HashMap(); + theSingleValue.put("stringValue", _value); + return theSingleValue; + } + + public static JsonString deserialize(Map map) { + return new JsonString(map.get("stringValue").toString()); + } + + @Override + public String toString() { + return _value; + } + +} diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/MessagePart.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/MessagePart.java new file mode 100644 index 00000000..805ad0b9 --- /dev/null +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/MessagePart.java @@ -0,0 +1,156 @@ +package com.boydti.fawe.bukkit.chat; + +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; + +/** + * Internal class: Represents a component of a JSON-serializable {@link FancyMessage}. + */ +final class MessagePart implements JsonRepresentedObject, ConfigurationSerializable, Cloneable { + + ChatColor color = ChatColor.WHITE; + ArrayList styles = new ArrayList<>(); + String clickActionName = null; + String clickActionData = null; + String hoverActionName = null; + JsonRepresentedObject hoverActionData = null; + TextualComponent text = null; + String insertionData = null; + ArrayList translationReplacements = new ArrayList<>(); + + MessagePart(final TextualComponent text) { + this.text = text; + } + + MessagePart() { + this.text = null; + } + + boolean hasText() { + return text != null; + } + + @Override + @SuppressWarnings("unchecked") + public MessagePart clone() throws CloneNotSupportedException { + MessagePart obj = (MessagePart) super.clone(); + obj.styles = (ArrayList) styles.clone(); + if (hoverActionData instanceof JsonString) { + obj.hoverActionData = new JsonString(((JsonString) hoverActionData).getValue()); + } else if (hoverActionData instanceof FancyMessage) { + obj.hoverActionData = ((FancyMessage) hoverActionData).clone(); + } + obj.translationReplacements = (ArrayList) translationReplacements.clone(); + return obj; + + } + + static final BiMap stylesToNames; + + static { + ImmutableBiMap.Builder builder = ImmutableBiMap.builder(); + for (final ChatColor style : ChatColor.values()) { + if (!style.isFormat()) { + continue; + } + + String styleName; + switch (style) { + case MAGIC: + styleName = "obfuscated"; + break; + case UNDERLINE: + styleName = "underlined"; + break; + default: + styleName = style.name().toLowerCase(); + break; + } + + builder.put(style, styleName); + } + stylesToNames = builder.build(); + } + + public void writeJson(JsonWriter json) { + try { + json.beginObject(); + text.writeJson(json); + json.name("color").value(color.name().toLowerCase()); + for (final ChatColor style : styles) { + json.name(stylesToNames.get(style)).value(true); + } + if (clickActionName != null && clickActionData != null) { + json.name("clickEvent") + .beginObject() + .name("action").value(clickActionName) + .name("value").value(clickActionData) + .endObject(); + } + if (hoverActionName != null && hoverActionData != null) { + json.name("hoverEvent") + .beginObject() + .name("action").value(hoverActionName) + .name("value"); + hoverActionData.writeJson(json); + json.endObject(); + } + if (insertionData != null) { + json.name("insertion").value(insertionData); + } + if (translationReplacements.size() > 0 && text != null && TextualComponent.isTranslatableText(text)) { + json.name("with").beginArray(); + for (JsonRepresentedObject obj : translationReplacements) { + obj.writeJson(json); + } + json.endArray(); + } + json.endObject(); + } catch (IOException e) { + Bukkit.getLogger().log(Level.WARNING, "A problem occured during writing of JSON string", e); + } + } + + public Map serialize() { + HashMap map = new HashMap<>(); + map.put("text", text); + map.put("styles", styles); + map.put("color", color.getChar()); + map.put("hoverActionName", hoverActionName); + map.put("hoverActionData", hoverActionData); + map.put("clickActionName", clickActionName); + map.put("clickActionData", clickActionData); + map.put("insertion", insertionData); + map.put("translationReplacements", translationReplacements); + return map; + } + + @SuppressWarnings("unchecked") + public static MessagePart deserialize(Map serialized) { + MessagePart part = new MessagePart((TextualComponent) serialized.get("text")); + part.styles = (ArrayList) serialized.get("styles"); + part.color = ChatColor.getByChar(serialized.get("color").toString()); + part.hoverActionName = (String) serialized.get("hoverActionName"); + part.hoverActionData = (JsonRepresentedObject) serialized.get("hoverActionData"); + part.clickActionName = (String) serialized.get("clickActionName"); + part.clickActionData = (String) serialized.get("clickActionData"); + part.insertionData = (String) serialized.get("insertion"); + part.translationReplacements = (ArrayList) serialized.get("translationReplacements"); + return part; + } + + static { + ConfigurationSerialization.registerClass(MessagePart.class); + } + +} diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/Reflection.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/Reflection.java new file mode 100644 index 00000000..b90aef81 --- /dev/null +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/Reflection.java @@ -0,0 +1,205 @@ +package com.boydti.fawe.bukkit.chat; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import org.bukkit.Bukkit; + +/** + * A class containing static utility methods and caches which are intended as reflective conveniences. + * Unless otherwise noted, upon failure methods will return {@code null}. + */ +public final class Reflection { + + /** + * Stores loaded classes from the {@code net.minecraft.server} package. + */ + private static final Map> _loadedNMSClasses = new HashMap<>(); + /** + * Stores loaded classes from the {@code org.bukkit.craftbukkit} package (and subpackages). + */ + private static final Map> _loadedOBCClasses = new HashMap<>(); + private static final Map, Map> _loadedFields = new HashMap<>(); + /** + * Contains loaded methods in a cache. + * The map maps [types to maps of [method names to maps of [parameter types to method instances]]]. + */ + private static final Map, Map>, Method>>> _loadedMethods = new HashMap<>(); + private static String _versionString; + + private Reflection() { } + + /** + * Gets the version string from the package name of the CraftBukkit server implementation. + * This is needed to bypass the JAR package name changing on each update. + * + * @return The version string of the OBC and NMS packages, including the trailing dot. + */ + public synchronized static String getVersion() { + if (_versionString == null) { + if (Bukkit.getServer() == null) { + // The server hasn't started, static initializer call? + return null; + } + String name = Bukkit.getServer().getClass().getPackage().getName(); + _versionString = name.substring(name.lastIndexOf('.') + 1) + "."; + } + + return _versionString; + } + + /** + * Gets a {@link Class} object representing a type contained within the {@code net.minecraft.server} versioned package. + * The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this method simultaneously). + * + * @param className The name of the class, excluding the package, within NMS. + * @return The class instance representing the specified NMS class, or {@code null} if it could not be loaded. + */ + public synchronized static Class getNMSClass(String className) { + if (_loadedNMSClasses.containsKey(className)) { + return _loadedNMSClasses.get(className); + } + + String fullName = "net.minecraft.server." + getVersion() + className; + Class clazz; + try { + clazz = Class.forName(fullName); + } catch (ClassNotFoundException e) { + _loadedNMSClasses.put(className, null); + throw new RuntimeException(e); + } + _loadedNMSClasses.put(className, clazz); + return clazz; + } + + /** + * Gets a {@link Class} object representing a type contained within the {@code org.bukkit.craftbukkit} versioned package. + * The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this method simultaneously). + * + * @param className The name of the class, excluding the package, within OBC. This name may contain a subpackage name, such as {@code inventory.CraftItemStack}. + * @return The class instance representing the specified OBC class, or {@code null} if it could not be loaded. + */ + public synchronized static Class getOBCClass(String className) { + if (_loadedOBCClasses.containsKey(className)) { + return _loadedOBCClasses.get(className); + } + + String fullName = "org.bukkit.craftbukkit." + getVersion() + className; + Class clazz; + try { + clazz = Class.forName(fullName); + } catch (ClassNotFoundException e) { + _loadedOBCClasses.put(className, null); + throw new RuntimeException(e); + } + _loadedOBCClasses.put(className, clazz); + return clazz; + } + + /** + * Attempts to get the NMS handle of a CraftBukkit object. + *

+ * The only match currently attempted by this method is a retrieval by using a parameterless {@code getHandle()} method implemented by the runtime type of the specified object. + *

+ * + * @param obj The object for which to retrieve an NMS handle. + * @return The NMS handle of the specified object, or {@code null} if it could not be retrieved using {@code getHandle()}. + */ + public synchronized static Object getHandle(Object obj) throws InvocationTargetException, IllegalAccessException, IllegalArgumentException { + return getMethod(obj.getClass(), "getHandle").invoke(obj); + } + + /** + * Retrieves a {@link java.lang.reflect.Field} instance declared by the specified class with the specified name. + * Java access modifiers are ignored during this retrieval. No guarantee is made as to whether the field + * returned will be an instance or static field. + *

+ * A global caching mechanism within this class is used to store fields. Combined with synchronization, this guarantees that + * no field will be reflectively looked up twice. + *

+ *

+ * If a field is deemed suitable for return, {@link java.lang.reflect.Field#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code true} before it is returned. + * This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance. + *

+ * + * @param clazz The class which contains the field to retrieve. + * @param name The declared name of the field in the class. + * @return A field object with the specified name declared by the specified class. + * @see Class#getDeclaredField(String) + */ + public synchronized static Field getField(Class clazz, String name) { + Map loaded; + if (!_loadedFields.containsKey(clazz)) { + loaded = new HashMap<>(); + _loadedFields.put(clazz, loaded); + } else { + loaded = _loadedFields.get(clazz); + } + if (loaded.containsKey(name)) { + // If the field is loaded (or cached as not existing), return the relevant value, which might be null + return loaded.get(name); + } + try { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + loaded.put(name, field); + return field; + } catch (NoSuchFieldException | SecurityException e) { + // Error loading + e.printStackTrace(); + // Cache field as not existing + loaded.put(name, null); + return null; + } + } + + /** + * Retrieves a {@link java.lang.reflect.Method} instance declared by the specified class with the specified name and argument types. + * Java access modifiers are ignored during this retrieval. No guarantee is made as to whether the field + * returned will be an instance or static field. + *

+ * A global caching mechanism within this class is used to store method. Combined with synchronization, this guarantees that + * no method will be reflectively looked up twice. + *

+ * If a method is deemed suitable for return, {@link java.lang.reflect.Method#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code true} before it is returned. + * This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance. + *

+ * This method does not search superclasses of the specified type for methods with the specified signature. + * Callers wishing this behavior should use {@link Class#getDeclaredMethod(String, Class...)}. + * + * @param clazz The class which contains the method to retrieve. + * @param name The declared name of the method in the class. + * @param args The formal argument types of the method. + * @return A method object with the specified name declared by the specified class. + */ + public synchronized static Method getMethod(Class clazz, String name, Class... args) { + if (!_loadedMethods.containsKey(clazz)) { + _loadedMethods.put(clazz, new HashMap>, Method>>()); + } + + Map>, Method>> loadedMethodNames = _loadedMethods.get(clazz); + if (!loadedMethodNames.containsKey(name)) { + loadedMethodNames.put(name, new HashMap>, Method>()); + } + + Map>, Method> loadedSignatures = loadedMethodNames.get(name); + ArrayWrapper> wrappedArg = new ArrayWrapper<>(args); + if (loadedSignatures.containsKey(wrappedArg)) { + return loadedSignatures.get(wrappedArg); + } + + for (Method m : clazz.getMethods()) { + if (m.getName().equals(name) && Arrays.equals(args, m.getParameterTypes())) { + m.setAccessible(true); + loadedSignatures.put(wrappedArg, m); + return m; + } + } + loadedSignatures.put(wrappedArg, null); + return null; + } + +} diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/TextualComponent.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/TextualComponent.java new file mode 100644 index 00000000..68a9ed0a --- /dev/null +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/TextualComponent.java @@ -0,0 +1,316 @@ +package com.boydti.fawe.bukkit.chat; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; + +/** + * Represents a textual component of a message part. + * This can be used to not only represent string literals in a JSON message, + * but also to represent localized strings and other text values. + *

Different instances of this class can be created with static constructor methods.

+ */ +public abstract class TextualComponent implements Cloneable { + + static { + ConfigurationSerialization.registerClass(TextualComponent.ArbitraryTextTypeComponent.class); + ConfigurationSerialization.registerClass(TextualComponent.ComplexTextTypeComponent.class); + } + + static TextualComponent deserialize(Map map) { + if (map.containsKey("key") && map.size() == 2 && map.containsKey("value")) { + // Arbitrary text component + return ArbitraryTextTypeComponent.deserialize(map); + } else if (map.size() >= 2 && map.containsKey("key") && !map.containsKey("value") /* It contains keys that START WITH value */) { + // Complex JSON object + return ComplexTextTypeComponent.deserialize(map); + } + + return null; + } + + static boolean isTextKey(String key) { + return key.equals("translate") || key.equals("text") || key.equals("score") || key.equals("selector"); + } + + static boolean isTranslatableText(TextualComponent component) { + return component instanceof ComplexTextTypeComponent && component.getKey().equals("translate"); + } + + /** + * Create a textual component representing a string literal. + * + *

This is the default type of textual component when a single string + * literal is given to a method. + * + * @param textValue The text which will be represented. + * @return The text component representing the specified literal text. + */ + public static TextualComponent rawText(String textValue) { + return new ArbitraryTextTypeComponent("text", textValue); + } + + /** + * Create a textual component representing a localized string. + * The client will see this text component as their localized version of the specified string key, which can be overridden by a + * resource pack. + *

+ * If the specified translation key is not present on the client resource pack, the translation key will be displayed as a string literal to + * the client. + *

+ * + * @param translateKey The string key which maps to localized text. + * @return The text component representing the specified localized text. + */ + public static TextualComponent localizedText(String translateKey) { + return new ArbitraryTextTypeComponent("translate", translateKey); + } + + private static void throwUnsupportedSnapshot() { + throw new UnsupportedOperationException("This feature is only supported in snapshot releases."); + } + + /** + * Create a textual component representing a scoreboard value. + * The client will see their own score for the specified objective as the text represented by this component. + *

+ * This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients. + *

+ * + * @param scoreboardObjective The name of the objective for which to display the score. + * @return The text component representing the specified scoreboard score (for the viewing player), or {@code null} if an error occurs during + * JSON serialization. + */ + public static TextualComponent objectiveScore(String scoreboardObjective) { + return objectiveScore("*", scoreboardObjective); + } + + /** + * Create a textual component representing a scoreboard value. + * The client will see the score of the specified player for the specified objective as the text represented by this component. + * + *

This method is currently guaranteed to throw an {@code UnsupportedOperationException} + * as it is only supported on snapshot clients. + * + * @param playerName The name of the player whos score will be shown. If + * this string represents the single-character sequence + * "*", the viewing player's score will be displayed. + * Standard minecraft selectors (@a, @p, etc) + * are not supported. + * @param scoreboardObjective The name of the objective for + * which to display the score. + * @return The text component representing the specified scoreboard score + * for the specified player, or {@code null} if an error occurs during JSON serialization. + */ + public static TextualComponent objectiveScore(String playerName, String scoreboardObjective) { + throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE + // OVERLOADS documentation accordingly + + return new ComplexTextTypeComponent("score", ImmutableMap.builder() + .put("name", playerName) + .put("objective", scoreboardObjective) + .build()); + } + + /** + * Create a textual component representing a player name, retrievable by using a standard minecraft selector. + * The client will see the players or entities captured by the specified selector as the text represented by this component. + *

+ * This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients. + *

+ * + * @param selector The minecraft player or entity selector which will capture the entities whose string representations will be displayed in + * the place of this text component. + * @return The text component representing the name of the entities captured by the selector. + */ + public static TextualComponent selector(String selector) { + throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE + // OVERLOADS documentation accordingly + + return new ArbitraryTextTypeComponent("selector", selector); + } + + @Override + public String toString() { + return getReadableString(); + } + + /** + * @return The JSON key used to represent text components of this type. + */ + public abstract String getKey(); + + /** + * @return A readable String + */ + public abstract String getReadableString(); + + /** + * Clones a textual component instance. + * The returned object should not reference this textual component instance, but should maintain the same key and value. + */ + @Override + public abstract TextualComponent clone() throws CloneNotSupportedException; + + /** + * Writes the text data represented by this textual component to the specified JSON writer object. + * A new object within the writer is not started. + * + * @param writer The object to which to write the JSON data. + * @throws java.io.IOException If an error occurs while writing to the stream. + */ + public abstract void writeJson(JsonWriter writer) throws IOException; + + /** + * Internal class used to represent all types of text components. + * Exception validating done is on keys and values. + */ + private static final class ArbitraryTextTypeComponent extends TextualComponent implements ConfigurationSerializable { + + private String key; + private String value; + + public ArbitraryTextTypeComponent(String key, String value) { + setKey(key); + setValue(value); + } + + public static ArbitraryTextTypeComponent deserialize(Map map) { + return new ArbitraryTextTypeComponent(map.get("key").toString(), map.get("value").toString()); + } + + @Override + public String getKey() { + return key; + } + + public void setKey(String key) { + Preconditions.checkArgument(key != null && !key.isEmpty(), "The key must be specified."); + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + Preconditions.checkArgument(value != null, "The value must be specified."); + this.value = value; + } + + @Override + public TextualComponent clone() throws CloneNotSupportedException { + // Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone + return new ArbitraryTextTypeComponent(getKey(), getValue()); + } + + @Override + public void writeJson(JsonWriter writer) throws IOException { + writer.name(getKey()).value(getValue()); + } + + @Override + @SuppressWarnings("serial") + public Map serialize() { + return new HashMap() { + { + put("key", getKey()); + put("value", getValue()); + } + }; + } + + @Override + public String getReadableString() { + return getValue(); + } + } + + /** + * Internal class used to represent a text component with a nested JSON + * value. + * + *

Exception validating done is on keys and values. + */ + private static final class ComplexTextTypeComponent extends TextualComponent implements ConfigurationSerializable { + + private String key; + private Map value; + + public ComplexTextTypeComponent(String key, Map values) { + setKey(key); + setValue(values); + } + + public static ComplexTextTypeComponent deserialize(Map map) { + String key = null; + Map value = new HashMap<>(); + for (Map.Entry valEntry : map.entrySet()) { + if (valEntry.getKey().equals("key")) { + key = (String) valEntry.getValue(); + } else if (valEntry.getKey().startsWith("value.")) { + value.put(valEntry.getKey().substring(6) /* Strips out the value prefix */, valEntry.getValue().toString()); + } + } + return new ComplexTextTypeComponent(key, value); + } + + @Override + public String getKey() { + return key; + } + + public void setKey(String key) { + Preconditions.checkArgument(key != null && !key.isEmpty(), "The key must be specified."); + this.key = key; + } + + public Map getValue() { + return value; + } + + public void setValue(Map value) { + Preconditions.checkArgument(value != null, "The value must be specified."); + this.value = value; + } + + @Override + public TextualComponent clone() { + // Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone + return new ComplexTextTypeComponent(getKey(), getValue()); + } + + @Override + public void writeJson(JsonWriter writer) throws IOException { + writer.name(getKey()); + writer.beginObject(); + for (Map.Entry jsonPair : value.entrySet()) { + writer.name(jsonPair.getKey()).value(jsonPair.getValue()); + } + writer.endObject(); + } + + @Override + @SuppressWarnings("serial") + public Map serialize() { + return new HashMap() { + { + put("key", getKey()); + for (Entry valEntry : getValue().entrySet()) { + put("value." + valEntry.getKey(), valEntry.getValue()); + } + } + }; + } + + @Override + public String getReadableString() { + return getKey(); + } + } +} diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java index d146ad5a..8ae633d4 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java @@ -4,7 +4,6 @@ import com.boydti.fawe.Fawe; import com.boydti.fawe.FaweCache; import com.boydti.fawe.bukkit.BukkitPlayer; import com.boydti.fawe.bukkit.FaweBukkit; -import com.boydti.fawe.config.Settings; import com.boydti.fawe.example.CharFaweChunk; import com.boydti.fawe.example.NMSMappedFaweQueue; import com.boydti.fawe.object.FaweChunk; @@ -217,7 +216,6 @@ public abstract class BukkitQueue_0 extends NMSMa private volatile boolean timingsEnabled; private static boolean alertTimingsChange = true; - private volatile int parallelThreads; private static Field fieldTimingsEnabled; private static Field fieldAsyncCatcherEnabled; @@ -239,10 +237,6 @@ public abstract class BukkitQueue_0 extends NMSMa public void startSet(boolean parallel) { ChunkListener.physicsFreeze = true; if (parallel) { - if (Fawe.get().isMainThread()) { - parallelThreads = Settings.IMP.QUEUE.PARALLEL_THREADS; - Settings.IMP.QUEUE.PARALLEL_THREADS = 1; - } try { if (fieldAsyncCatcherEnabled != null) { fieldAsyncCatcherEnabled.set(null, false); @@ -268,9 +262,6 @@ public abstract class BukkitQueue_0 extends NMSMa public void endSet(boolean parallel) { ChunkListener.physicsFreeze = false; if (parallel) { - if (Fawe.get().isMainThread() && parallelThreads != 0) { - Settings.IMP.QUEUE.PARALLEL_THREADS = parallelThreads; - } try { if (fieldAsyncCatcherEnabled != null) { fieldAsyncCatcherEnabled.set(null, true); @@ -283,7 +274,6 @@ public abstract class BukkitQueue_0 extends NMSMa e.printStackTrace(); } } - parallelThreads = 0; } @Override diff --git a/bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayerBlockBag.java b/bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayerBlockBag.java new file mode 100644 index 00000000..aa8d64b4 --- /dev/null +++ b/bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayerBlockBag.java @@ -0,0 +1,204 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit; + +import com.sk89q.worldedit.WorldVector; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import com.sk89q.worldedit.extent.inventory.*; +import com.sk89q.worldedit.blocks.BaseItem; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.blocks.BlockID; +import com.sk89q.worldedit.blocks.ItemType; + +public class BukkitPlayerBlockBag extends BlockBag { + + private Player player; + private ItemStack[] items; + + /** + * Construct the object. + * + * @param player the player + */ + public BukkitPlayerBlockBag(Player player) { + this.player = player; + } + + /** + * Loads inventory on first use. + */ + private void loadInventory() { + if (items == null) { + items = player.getInventory().getContents(); + } + } + + /** + * Get the player. + * + * @return the player + */ + public Player getPlayer() { + return player; + } + + @Override + public void fetchItem(BaseItem item) throws BlockBagException { + final int id = item.getType(); + final int damage = item.getData(); + int amount = (item instanceof BaseItemStack) ? ((BaseItemStack) item).getAmount() : 1; + assert(amount == 1); + boolean usesDamageValue = ItemType.usesDamageValue(id); + + if (id == BlockID.AIR) { + throw new IllegalArgumentException("Can't fetch air block"); + } + + loadInventory(); + + boolean found = false; + + for (int slot = 0; slot < items.length; ++slot) { + ItemStack bukkitItem = items[slot]; + + if (bukkitItem == null) { + continue; + } + + if (bukkitItem.getTypeId() != id) { + // Type id doesn't fit + continue; + } + + if (usesDamageValue && bukkitItem.getDurability() != damage) { + // Damage value doesn't fit. + continue; + } + + int currentAmount = bukkitItem.getAmount(); + if (currentAmount < 0) { + // Unlimited + return; + } + + if (currentAmount > 1) { + bukkitItem.setAmount(currentAmount - 1); + found = true; + } else { + items[slot] = null; + found = true; + } + + break; + } + + if (!found) { + throw new OutOfBlocksException(); + } + } + + @Override + public void storeItem(BaseItem item) throws BlockBagException { + final int id = item.getType(); + final int damage = item.getData(); + int amount = (item instanceof BaseItemStack) ? ((BaseItemStack) item).getAmount() : 1; + assert(amount <= 64); + boolean usesDamageValue = ItemType.usesDamageValue(id); + + if (id == BlockID.AIR) { + throw new IllegalArgumentException("Can't store air block"); + } + + loadInventory(); + + int freeSlot = -1; + + for (int slot = 0; slot < items.length; ++slot) { + ItemStack bukkitItem = items[slot]; + + if (bukkitItem == null) { + // Delay using up a free slot until we know there are no stacks + // of this item to merge into + + if (freeSlot == -1) { + freeSlot = slot; + } + continue; + } + + if (bukkitItem.getTypeId() != id) { + // Type id doesn't fit + continue; + } + + if (usesDamageValue && bukkitItem.getDurability() != damage) { + // Damage value doesn't fit. + continue; + } + + int currentAmount = bukkitItem.getAmount(); + if (currentAmount < 0) { + // Unlimited + return; + } + if (currentAmount >= 64) { + // Full stack + continue; + } + + int spaceLeft = 64 - currentAmount; + if (spaceLeft >= amount) { + bukkitItem.setAmount(currentAmount + amount); + return; + } + + bukkitItem.setAmount(64); + amount -= spaceLeft; + } + + if (freeSlot > -1) { + items[freeSlot] = new ItemStack(id, amount); + return; + } + + throw new OutOfSpaceException(id); + } + + @Override + public void flushChanges() { + if (items != null) { + player.getInventory().setContents(items); + items = null; + } + } + + @Override + public void addSourcePosition(WorldVector pos) { + } + + @Override + public void addSingleSourcePosition(WorldVector pos) { + } + + public static Class inject() { + return BukkitPlayerBlockBag.class; + } +} \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle index 51ead323..50f6e5f2 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -7,7 +7,7 @@ dependencies { compile 'com.google.code.gson:gson:2.2.4' compile 'net.fabiozumbi12:redprotect:1.9.6' compile 'com.sk89q:worldguard:6.0.0-SNAPSHOT' - compile group: "com.plotsquared", name: "plotsquared-api", version: "latest", changing: true + compile group: "com.plotsquared", name: "plotsquared-api", version: "latest" compile 'org.primesoft:BlocksHub:2.0' compile 'com.github.luben:zstd-jni:1.1.1' // compile 'org.javassist:javassist:3.22.0-CR1' diff --git a/core/src/main/java/com/boydti/fawe/Fawe.java b/core/src/main/java/com/boydti/fawe/Fawe.java index e0c894ab..1e053545 100644 --- a/core/src/main/java/com/boydti/fawe/Fawe.java +++ b/core/src/main/java/com/boydti/fawe/Fawe.java @@ -13,11 +13,12 @@ import com.boydti.fawe.util.FaweTimer; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.MemUtil; import com.boydti.fawe.util.RandomTextureUtil; -import com.boydti.fawe.util.StringMan; import com.boydti.fawe.util.TaskManager; import com.boydti.fawe.util.TextureUtil; import com.boydti.fawe.util.Updater; import com.boydti.fawe.util.WEManager; +import com.boydti.fawe.util.chat.ChatManager; +import com.boydti.fawe.util.chat.PlainChatManager; import com.sk89q.jnbt.NBTInputStream; import com.sk89q.jnbt.NBTOutputStream; import com.sk89q.worldedit.BlockVector; @@ -122,6 +123,7 @@ import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.session.PasteBuilder; import com.sk89q.worldedit.session.SessionManager; import com.sk89q.worldedit.session.request.Request; +import com.sk89q.worldedit.util.command.SimpleCommandMapping; import com.sk89q.worldedit.util.command.SimpleDispatcher; import com.sk89q.worldedit.util.command.fluent.DispatcherNode; import com.sk89q.worldedit.util.command.parametric.ParameterData; @@ -153,6 +155,9 @@ import javax.management.Notification; import javax.management.NotificationEmitter; import javax.management.NotificationListener; + +import static com.google.common.base.Preconditions.checkNotNull; + /** * [ WorldEdit action] * | @@ -205,6 +210,7 @@ public class Fawe { private Updater updater; private TextureUtil textures; private DefaultTransformParser transformParser; + private ChatManager chatManager = new PlainChatManager(); // @Deprecated // private boolean isJava8 = MainUtil.getJavaVersion() >= 1.8; @@ -246,7 +252,7 @@ public class Fawe { public static void debugPlain(String s) { if (INSTANCE != null) { - INSTANCE.IMP.debug(StringMan.getString(s)); + INSTANCE.IMP.debug(s); } else { System.out.println(s); } @@ -333,7 +339,16 @@ public class Fawe { return false; } -// @Deprecated + public ChatManager getChatManager() { + return chatManager; + } + + public void setChatManager(ChatManager chatManager) { + checkNotNull(chatManager); + this.chatManager = chatManager; + } + + // @Deprecated // public boolean isJava8() { // return isJava8; // } @@ -601,6 +616,7 @@ public class Fawe { CommandManager.inject(); // Async commands PlatformManager.inject(); // Async brushes / tools SimpleDispatcher.inject(); // Optimize perm checks + SimpleCommandMapping.inject(); // Hashcode + equals } catch (Throwable e) { debug("====== UPDATE WORLDEDIT TO 6.1.1 ======"); MainUtil.handleError(e, false); @@ -618,7 +634,7 @@ public class Fawe { debug(" - AsyncWorldEdit/WorldEditRegions isn't installed"); debug(" - Any other errors in the startup log"); debug("Contact Empire92 if you need assistance:"); - debug(" - Send me a PM or ask on IRC"); + debug(" - Send me a PM or ask on IRC/Discord"); debug(" - http://webchat.esper.net/?nick=&channels=IntellectualCrafters"); debug("======================================="); } diff --git a/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java b/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java index 1d562dac..7449a326 100644 --- a/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java +++ b/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java @@ -15,6 +15,7 @@ import com.boydti.fawe.jnbt.anvil.filters.DeleteOldFilter; import com.boydti.fawe.jnbt.anvil.filters.DeleteUninhabitedFilter; import com.boydti.fawe.jnbt.anvil.filters.MappedReplacePatternFilter; import com.boydti.fawe.jnbt.anvil.filters.PlotTrimFilter; +import com.boydti.fawe.jnbt.anvil.filters.RemapFilter; import com.boydti.fawe.jnbt.anvil.filters.RemoveLayerFilter; import com.boydti.fawe.jnbt.anvil.filters.ReplacePatternFilter; import com.boydti.fawe.jnbt.anvil.filters.ReplaceSimpleFilter; @@ -22,6 +23,7 @@ import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.FaweQueue; import com.boydti.fawe.object.RegionWrapper; import com.boydti.fawe.object.RunnableVal4; +import com.boydti.fawe.object.clipboard.ClipboardRemapper; import com.boydti.fawe.object.mask.FaweBlockMatcher; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.SetQueue; @@ -80,6 +82,7 @@ public class AnvilCommands { * @param * @return */ + @Deprecated public static > T runWithWorld(Player player, String folder, T filter, boolean force) { boolean copy = false; if (FaweAPI.getWorld(folder) != null) { @@ -109,6 +112,7 @@ public class AnvilCommands { * @param * @return */ + @Deprecated public static > T runWithSelection(Player player, EditSession editSession, Region selection, T filter) { if (!(selection instanceof CuboidRegion)) { BBC.NO_REGION.send(player); @@ -159,10 +163,37 @@ public class AnvilCommands { if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); } + @Command( + aliases = {"remapall"}, + usage = "", + help = "Remap the world between MCPE/PC values", + desc = "Remap the world between MCPE/PC values", + min = 1, + max = 1 + ) + @CommandPermissions("worldedit.anvil.remapall") + public void remapall(Player player, String folder, @Switch('f') boolean force) throws WorldEditException { + ClipboardRemapper mapper; + ClipboardRemapper.RemapPlatform from; + ClipboardRemapper.RemapPlatform to; + if (Fawe.imp().getPlatform().equalsIgnoreCase("nukkit")) { + from = ClipboardRemapper.RemapPlatform.PC; + to = ClipboardRemapper.RemapPlatform.PE; + } else { + from = ClipboardRemapper.RemapPlatform.PE; + to = ClipboardRemapper.RemapPlatform.PC; + } + RemapFilter filter = new RemapFilter(from, to); + RemapFilter result = runWithWorld(player, folder, filter, force); + if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); + } + + @Command( aliases = {"deleteallunvisited", "delunvisited" }, usage = " [file-age=60000]", - desc = "Delete all chunks which haven't been occupied for `age-ticks` (20t = 1s) and \n" + + desc = "Delete all chunks which haven't been occupied", + help = "Delete all chunks which haven't been occupied for `age-ticks` (20t = 1s) and \n" + "Have not been accessed since `file-duration` (ms) after creation and\n" + "Have not been used in the past `chunk-inactivity` (ms)" + "The auto-save interval is the recommended value for `file-duration` and `chunk-inactivity`", @@ -180,7 +211,8 @@ public class AnvilCommands { @Command( aliases = {"deletealloldregions", "deloldreg" }, usage = "

+ *

+ * ... [16 17 18 19 20 21 22 23] [8 9 10 11 12 13 14 15] [0 1 2 3 4 5 6 7] + */ +class BitStream +{ + private BitStream() + { + } + + public static boolean isEndOfStream(long startAddress, long currentAddress, int bitsConsumed) + { + return startAddress == currentAddress && bitsConsumed == Long.SIZE; + } + + static long readTail(Object inputBase, long inputAddress, int inputSize) + { + long bits = UNSAFE.getByte(inputBase, inputAddress) & 0xFF; + + switch (inputSize) { + case 7: + bits |= (UNSAFE.getByte(inputBase, inputAddress + 6) & 0xFFL) << 48; + case 6: + bits |= (UNSAFE.getByte(inputBase, inputAddress + 5) & 0xFFL) << 40; + case 5: + bits |= (UNSAFE.getByte(inputBase, inputAddress + 4) & 0xFFL) << 32; + case 4: + bits |= (UNSAFE.getByte(inputBase, inputAddress + 3) & 0xFFL) << 24; + case 3: + bits |= (UNSAFE.getByte(inputBase, inputAddress + 2) & 0xFFL) << 16; + case 2: + bits |= (UNSAFE.getByte(inputBase, inputAddress + 1) & 0xFFL) << 8; + } + + return bits; + } + + /** + * @return numberOfBits in the low order bits of a long + */ + public static long peekBits(int bitsConsumed, long bitContainer, int numberOfBits) + { + return (((bitContainer << bitsConsumed) >>> 1) >>> (63 - numberOfBits)); + } + + /** + * numberOfBits must be > 0 + * + * @return numberOfBits in the low order bits of a long + */ + public static long peekBitsFast(int bitsConsumed, long bitContainer, int numberOfBits) + { + return ((bitContainer << bitsConsumed) >>> (64 - numberOfBits)); + } + + static class Initializer + { + private final Object inputBase; + private final long startAddress; + private final long endAddress; + private long bits; + private long currentAddress; + private int bitsConsumed; + + public Initializer(Object inputBase, long startAddress, long endAddress) + { + this.inputBase = inputBase; + this.startAddress = startAddress; + this.endAddress = endAddress; + } + + public long getBits() + { + return bits; + } + + public long getCurrentAddress() + { + return currentAddress; + } + + public int getBitsConsumed() + { + return bitsConsumed; + } + + public void initialize() + { + verify(endAddress - startAddress >= 1, startAddress, "Bitstream is empty"); + + int lastByte = UNSAFE.getByte(inputBase, endAddress - 1) & 0xFF; + verify(lastByte != 0, endAddress, "Bitstream end mark not present"); + + bitsConsumed = SIZE_OF_LONG - highestBit(lastByte); + + int inputSize = (int) (endAddress - startAddress); + if (inputSize >= SIZE_OF_LONG) { /* normal case */ + currentAddress = endAddress - SIZE_OF_LONG; + bits = UNSAFE.getLong(inputBase, currentAddress); + } + else { + currentAddress = startAddress; + bits = readTail(inputBase, startAddress, inputSize); + + bitsConsumed += (SIZE_OF_LONG - inputSize) * 8; + } + } + } + + static final class Loader + { + private final Object inputBase; + private final long startAddress; + private long bits; + private long currentAddress; + private int bitsConsumed; + private boolean overflow; + + public Loader(Object inputBase, long startAddress, long currentAddress, long bits, int bitsConsumed) + { + this.inputBase = inputBase; + this.startAddress = startAddress; + this.bits = bits; + this.currentAddress = currentAddress; + this.bitsConsumed = bitsConsumed; + } + + public long getBits() + { + return bits; + } + + public long getCurrentAddress() + { + return currentAddress; + } + + public int getBitsConsumed() + { + return bitsConsumed; + } + + public boolean isOverflow() + { + return overflow; + } + + public boolean load() + { + if (bitsConsumed > 64) { + overflow = true; + return true; + } + + else if (currentAddress == startAddress) { + return true; + } + + int bytes = bitsConsumed >>> 3; // divide by 8 + if (currentAddress >= startAddress + SIZE_OF_LONG) { + if (bytes > 0) { + currentAddress -= bytes; + bits = UNSAFE.getLong(inputBase, currentAddress); + } + bitsConsumed &= 0b111; + } + else if (currentAddress - bytes < startAddress) { + bytes = (int) (currentAddress - startAddress); + currentAddress = startAddress; + bitsConsumed -= bytes * SIZE_OF_LONG; + bits = UNSAFE.getLong(inputBase, startAddress); + return true; + } + else { + currentAddress -= bytes; + bitsConsumed -= bytes * SIZE_OF_LONG; + bits = UNSAFE.getLong(inputBase, currentAddress); + } + + return false; + } + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/FiniteStateEntropy.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/FiniteStateEntropy.java new file mode 100644 index 00000000..9569b3db --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/FiniteStateEntropy.java @@ -0,0 +1,176 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.boydti.fawe.object.io.zstd; + +import static com.boydti.fawe.object.io.zstd.BitStream.peekBits; +import static com.boydti.fawe.object.io.zstd.FseTableReader.FSE_MAX_SYMBOL_VALUE; +import static com.boydti.fawe.object.io.zstd.UnsafeUtil.UNSAFE; +import static sun.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET; + +class FiniteStateEntropy +{ + private static final int MAX_TABLE_LOG = 12; + + private final FiniteStateEntropy.Table table; + private final FseTableReader reader = new FseTableReader(); + + public FiniteStateEntropy(int maxLog) + { + table = new FiniteStateEntropy.Table(maxLog); + } + + public int decompress(final Object inputBase, final long inputAddress, final long inputLimit, byte[] weights) + { + long input = inputAddress; + input += reader.readFseTable(table, inputBase, input, inputLimit, FSE_MAX_SYMBOL_VALUE, MAX_TABLE_LOG); + + final Object outputBase = weights; + final long outputAddress = ARRAY_BYTE_BASE_OFFSET; + final long outputLimit = outputAddress + weights.length; + + long output = outputAddress; + + // initialize bit stream + BitStream.Initializer initializer = new BitStream.Initializer(inputBase, input, inputLimit); + initializer.initialize(); + int bitsConsumed = initializer.getBitsConsumed(); + long currentAddress = initializer.getCurrentAddress(); + long bits = initializer.getBits(); + + // initialize first FSE stream + int state1 = (int) peekBits(bitsConsumed, bits, table.log2Size); + bitsConsumed += table.log2Size; + + BitStream.Loader loader = new BitStream.Loader(inputBase, input, currentAddress, bits, bitsConsumed); + loader.load(); + bits = loader.getBits(); + bitsConsumed = loader.getBitsConsumed(); + currentAddress = loader.getCurrentAddress(); + + // initialize second FSE stream + int state2 = (int) peekBits(bitsConsumed, bits, table.log2Size); + bitsConsumed += table.log2Size; + + loader = new BitStream.Loader(inputBase, input, currentAddress, bits, bitsConsumed); + loader.load(); + bits = loader.getBits(); + bitsConsumed = loader.getBitsConsumed(); + currentAddress = loader.getCurrentAddress(); + + byte[] symbols = table.symbol; + byte[] numbersOfBits = table.numberOfBits; + int[] newStates = table.newState; + + // decode 4 symbols per loop + while (output < outputLimit) { + int numberOfBits; + + UNSAFE.putByte(outputBase, output, symbols[state1]); + numberOfBits = numbersOfBits[state1]; + state1 = (int) (newStates[state1] + peekBits(bitsConsumed, bits, numberOfBits)); + bitsConsumed += numberOfBits; + + UNSAFE.putByte(outputBase, output + 1, symbols[state2]); + numberOfBits = numbersOfBits[state2]; + state2 = (int) (newStates[state2] + peekBits(bitsConsumed, bits, numberOfBits)); + bitsConsumed += numberOfBits; + + UNSAFE.putByte(outputBase, output + 2, symbols[state1]); + numberOfBits = numbersOfBits[state1]; + state1 = (int) (newStates[state1] + peekBits(bitsConsumed, bits, numberOfBits)); + bitsConsumed += numberOfBits; + + UNSAFE.putByte(outputBase, output + 3, symbols[state2]); + numberOfBits = numbersOfBits[state2]; + state2 = (int) (newStates[state2] + peekBits(bitsConsumed, bits, numberOfBits)); + bitsConsumed += numberOfBits; + + output += ZstdFrameDecompressor.SIZE_OF_INT; + + loader = new BitStream.Loader(inputBase, input, currentAddress, bits, bitsConsumed); + boolean done = loader.load(); + bitsConsumed = loader.getBitsConsumed(); + bits = loader.getBits(); + currentAddress = loader.getCurrentAddress(); + if (done) { + break; + } + } + + while (true) { + UNSAFE.putByte(outputBase, output++, symbols[state1]); + int numberOfBits = numbersOfBits[state1]; + state1 = (int) (newStates[state1] + peekBits(bitsConsumed, bits, numberOfBits)); + bitsConsumed += numberOfBits; + + loader = new BitStream.Loader(inputBase, input, currentAddress, bits, bitsConsumed); + loader.load(); + bitsConsumed = loader.getBitsConsumed(); + bits = loader.getBits(); + currentAddress = loader.getCurrentAddress(); + + if (loader.isOverflow()) { + UNSAFE.putByte(outputBase, output++, symbols[state2]); + break; + } + + UNSAFE.putByte(outputBase, output++, symbols[state2]); + int numberOfBits1 = numbersOfBits[state2]; + state2 = (int) (newStates[state2] + peekBits(bitsConsumed, bits, numberOfBits1)); + bitsConsumed += numberOfBits1; + + loader = new BitStream.Loader(inputBase, input, currentAddress, bits, bitsConsumed); + loader.load(); + bitsConsumed = loader.getBitsConsumed(); + bits = loader.getBits(); + currentAddress = loader.getCurrentAddress(); + + if (loader.isOverflow()) { + UNSAFE.putByte(outputBase, output++, symbols[state1]); + break; + } + } + + return (int) (output - outputAddress); + } + + public static final class Table + { + int log2Size; + final int[] newState; + final byte[] symbol; + final byte[] numberOfBits; + + public Table(int log2Size) + { + int size = 1 << log2Size; + newState = new int[size]; + symbol = new byte[size]; + numberOfBits = new byte[size]; + } + + public Table(int log2Size, int[] newState, byte[] symbol, byte[] numberOfBits) + { + int size = 1 << log2Size; + if (newState.length != size || symbol.length != size || numberOfBits.length != size) { + throw new IllegalArgumentException("Expected arrays to match provided size"); + } + + this.log2Size = log2Size; + this.newState = newState; + this.symbol = symbol; + this.numberOfBits = numberOfBits; + } + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/FrameHeader.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/FrameHeader.java new file mode 100644 index 00000000..f310ea51 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/FrameHeader.java @@ -0,0 +1,32 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.boydti.fawe.object.io.zstd; + +class FrameHeader +{ + final long headerSize; + final int windowSize; + final long contentSize; + final long dictionaryId; + final boolean hasChecksum; + + public FrameHeader(long headerSize, int windowSize, long contentSize, long dictionaryId, boolean hasChecksum) + { + this.headerSize = headerSize; + this.windowSize = windowSize; + this.contentSize = contentSize; + this.dictionaryId = dictionaryId; + this.hasChecksum = hasChecksum; + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/FseTableReader.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/FseTableReader.java new file mode 100644 index 00000000..bce65eac --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/FseTableReader.java @@ -0,0 +1,182 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.boydti.fawe.object.io.zstd; + +import static com.boydti.fawe.object.io.zstd.UnsafeUtil.UNSAFE; +import static com.boydti.fawe.object.io.zstd.Util.highestBit; +import static com.boydti.fawe.object.io.zstd.Util.verify; + +class FseTableReader +{ + private static final int FSE_MIN_TABLE_LOG = 5; + + public static final int FSE_MAX_SYMBOL_VALUE = 255; + private final short[] nextSymbol = new short[FSE_MAX_SYMBOL_VALUE + 1]; + private final short[] normalizedCounters = new short[FSE_MAX_SYMBOL_VALUE + 1]; + + public int readFseTable(FiniteStateEntropy.Table table, Object inputBase, long inputAddress, long inputLimit, int maxSymbol, int maxTableLog) + { + // read table headers + long input = inputAddress; + verify(inputLimit - inputAddress >= 4, input, "Not enough input bytes"); + + int threshold; + int symbolNumber = 0; + boolean previousIsZero = false; + + int bitStream = UNSAFE.getInt(inputBase, input); + + int tableLog = (bitStream & 0xF) + FSE_MIN_TABLE_LOG; + + int numberOfBits = tableLog + 1; + bitStream >>>= 4; + int bitCount = 4; + + verify(tableLog <= maxTableLog, input, "FSE table size exceeds maximum allowed size"); + + int remaining = (1 << tableLog) + 1; + threshold = 1 << tableLog; + + while (remaining > 1 && symbolNumber <= maxSymbol) { + if (previousIsZero) { + int n0 = symbolNumber; + while ((bitStream & 0xFFFF) == 0xFFFF) { + n0 += 24; + if (input < inputLimit - 5) { + input += 2; + bitStream = (UNSAFE.getInt(inputBase, input) >>> bitCount); + } + else { + // end of bit stream + bitStream >>>= 16; + bitCount += 16; + } + } + while ((bitStream & 3) == 3) { + n0 += 3; + bitStream >>>= 2; + bitCount += 2; + } + n0 += bitStream & 3; + bitCount += 2; + + verify(n0 <= maxSymbol, input, "Symbol larger than max value"); + + while (symbolNumber < n0) { + normalizedCounters[symbolNumber++] = 0; + } + if ((input <= inputLimit - 7) || (input + (bitCount >>> 3) <= inputLimit - 4)) { + input += bitCount >>> 3; + bitCount &= 7; + bitStream = UNSAFE.getInt(inputBase, input) >>> bitCount; + } + else { + bitStream >>>= 2; + } + } + + short max = (short) ((2 * threshold - 1) - remaining); + short count; + + if ((bitStream & (threshold - 1)) < max) { + count = (short) (bitStream & (threshold - 1)); + bitCount += numberOfBits - 1; + } + else { + count = (short) (bitStream & (2 * threshold - 1)); + if (count >= threshold) { + count -= max; + } + bitCount += numberOfBits; + } + count--; // extra accuracy + + remaining -= Math.abs(count); + normalizedCounters[symbolNumber++] = count; + previousIsZero = count == 0; + while (remaining < threshold) { + numberOfBits--; + threshold >>>= 1; + } + + if ((input <= inputLimit - 7) || (input + (bitCount >> 3) <= inputLimit - 4)) { + input += bitCount >>> 3; + bitCount &= 7; + } + else { + bitCount -= (int) (8 * (inputLimit - 4 - input)); + input = inputLimit - 4; + } + bitStream = UNSAFE.getInt(inputBase, input) >>> (bitCount & 31); + } + + verify(remaining == 1 && bitCount <= 32, input, "Input is corrupted"); + + maxSymbol = symbolNumber - 1; + verify(maxSymbol <= FSE_MAX_SYMBOL_VALUE, input, "Max symbol value too large (too many symbols for FSE)"); + + input += (bitCount + 7) >> 3; + + // populate decoding table + int symbolCount = maxSymbol + 1; + int tableSize = 1 << tableLog; + int highThreshold = tableSize - 1; + + table.log2Size = tableLog; + + for (byte symbol = 0; symbol < symbolCount; symbol++) { + if (normalizedCounters[symbol] == -1) { + table.symbol[highThreshold--] = symbol; + nextSymbol[symbol] = 1; + } + else { + nextSymbol[symbol] = normalizedCounters[symbol]; + } + } + + // spread symbols + int tableMask = tableSize - 1; + int step = (tableSize >>> 1) + (tableSize >>> 3) + 3; + int position = 0; + for (byte symbol = 0; symbol < symbolCount; symbol++) { + for (int i = 0; i < normalizedCounters[symbol]; i++) { + table.symbol[position] = symbol; + do { + position = (position + step) & tableMask; + } + while (position > highThreshold); + } + } + + // position must reach all cells once, otherwise normalizedCounter is incorrect + verify(position == 0, input, "Input is corrupted"); + + for (int i = 0; i < tableSize; i++) { + byte symbol = table.symbol[i]; + short nextState = nextSymbol[symbol]++; + table.numberOfBits[i] = (byte) (tableLog - highestBit(nextState)); + table.newState[i] = (short) ((nextState << table.numberOfBits[i]) - tableSize); + } + + return (int) (input - inputAddress); + } + + public static void buildRleTable(FiniteStateEntropy.Table table, byte value) + { + table.log2Size = 0; + table.symbol[0] = value; + table.newState[0] = 0; + table.numberOfBits[0] = 0; + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/Huffman.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/Huffman.java new file mode 100644 index 00000000..990de496 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/Huffman.java @@ -0,0 +1,317 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.boydti.fawe.object.io.zstd; + +import java.util.Arrays; + + +import static com.boydti.fawe.object.io.zstd.BitStream.isEndOfStream; +import static com.boydti.fawe.object.io.zstd.BitStream.peekBitsFast; +import static com.boydti.fawe.object.io.zstd.UnsafeUtil.UNSAFE; +import static com.boydti.fawe.object.io.zstd.Util.isPowerOf2; +import static com.boydti.fawe.object.io.zstd.Util.verify; +import static com.boydti.fawe.object.io.zstd.ZstdFrameDecompressor.SIZE_OF_INT; +import static com.boydti.fawe.object.io.zstd.ZstdFrameDecompressor.SIZE_OF_SHORT; + +class Huffman +{ + private static final int MAX_SYMBOL = 255; + private static final int MAX_TABLE_LOG = 12; + + // stats + private final byte[] weights = new byte[MAX_SYMBOL + 1]; + private final int[] ranks = new int[MAX_TABLE_LOG + 1]; + + // table + private int tableLog = -1; + private final byte[] symbols = new byte[1 << MAX_TABLE_LOG]; + private final byte[] numbersOfBits = new byte[1 << MAX_TABLE_LOG]; + + private final FiniteStateEntropy finiteStateEntropy = new FiniteStateEntropy(6); + + public boolean isLoaded() + { + return tableLog != -1; + } + + public int readTable(final Object inputBase, final long inputAddress, final int size) + { + Arrays.fill(ranks, 0); + long input = inputAddress; + + // read table header + verify(size > 0, input, "Not enough input bytes"); + int inputSize = UNSAFE.getByte(inputBase, input++) & 0xFF; + + int outputSize; + if (inputSize >= 128) { + outputSize = inputSize - 127; + inputSize = ((outputSize + 1) / 2); + + verify(inputSize + 1 <= size, input, "Not enough input bytes"); + verify(outputSize <= MAX_SYMBOL + 1, input, "Input is corrupted"); + + for (int i = 0; i < outputSize; i += 2) { + int value = UNSAFE.getByte(inputBase, input + i / 2) & 0xFF; + weights[i] = (byte) (value >>> 4); + weights[i + 1] = (byte) (value & 0b1111); + } + } + else { + verify(inputSize + 1 <= size, input, "Not enough input bytes"); + + outputSize = finiteStateEntropy.decompress(inputBase, input, input + inputSize, weights); + } + + int totalWeight = 0; + for (int i = 0; i < outputSize; i++) { + ranks[weights[i]]++; + totalWeight += (1 << weights[i]) >> 1; // TODO same as 1 << (weights[n] - 1)? + } + verify(totalWeight != 0, input, "Input is corrupted"); + + tableLog = Util.highestBit(totalWeight) + 1; + verify(tableLog <= MAX_TABLE_LOG, input, "Input is corrupted"); + + int total = 1 << tableLog; + int rest = total - totalWeight; + verify(isPowerOf2(rest), input, "Input is corrupted"); + + int lastWeight = Util.highestBit(rest) + 1; + + weights[outputSize] = (byte) lastWeight; + ranks[lastWeight]++; + + int numberOfSymbols = outputSize + 1; + + // populate table + int nextRankStart = 0; + for (int i = 1; i < tableLog + 1; ++i) { + int current = nextRankStart; + nextRankStart += ranks[i] << (i - 1); + ranks[i] = current; + } + + for (int n = 0; n < numberOfSymbols; n++) { + int weight = weights[n]; + int length = (1 << weight) >> 1; // TODO: 1 << (weight - 1) ?? + + byte symbol = (byte) n; + byte numberOfBits = (byte) (tableLog + 1 - weight); + for (int i = ranks[weight]; i < ranks[weight] + length; i++) { + symbols[i] = symbol; + numbersOfBits[i] = numberOfBits; + } + ranks[weight] += length; + } + + verify(ranks[1] >= 2 && (ranks[1] & 1) == 0, input, "Input is corrupted"); + + return inputSize + 1; + } + + public void decodeSingleStream(final Object inputBase, final long inputAddress, final long inputLimit, final Object outputBase, final long outputAddress, final long outputLimit) + { + BitStream.Initializer initializer = new BitStream.Initializer(inputBase, inputAddress, inputLimit); + initializer.initialize(); + + long bits = initializer.getBits(); + int bitsConsumed = initializer.getBitsConsumed(); + long currentAddress = initializer.getCurrentAddress(); + + int tableLog = this.tableLog; + byte[] numbersOfBits = this.numbersOfBits; + byte[] symbols = this.symbols; + + // 4 symbols at a time + long output = outputAddress; + long fastOutputLimit = outputLimit - 4; + while (output < fastOutputLimit) { + BitStream.Loader loader = new BitStream.Loader(inputBase, inputAddress, currentAddress, bits, bitsConsumed); + boolean done = loader.load(); + bits = loader.getBits(); + bitsConsumed = loader.getBitsConsumed(); + currentAddress = loader.getCurrentAddress(); + if (done) { + break; + } + + bitsConsumed = decodeSymbol(outputBase, output, bits, bitsConsumed, tableLog, numbersOfBits, symbols); + bitsConsumed = decodeSymbol(outputBase, output + 1, bits, bitsConsumed, tableLog, numbersOfBits, symbols); + bitsConsumed = decodeSymbol(outputBase, output + 2, bits, bitsConsumed, tableLog, numbersOfBits, symbols); + bitsConsumed = decodeSymbol(outputBase, output + 3, bits, bitsConsumed, tableLog, numbersOfBits, symbols); + output += SIZE_OF_INT; + } + + decodeTail(inputBase, inputAddress, currentAddress, bitsConsumed, bits, outputBase, output, outputLimit); + } + + public void decode4Streams(final Object inputBase, final long inputAddress, final long inputLimit, final Object outputBase, final long outputAddress, final long outputLimit) + { + verify(inputLimit - inputAddress >= 10, inputAddress, "Input is corrupted"); // jump table + 1 byte per stream + + long start1 = inputAddress + 3 * SIZE_OF_SHORT; // for the shorts we read below + long start2 = start1 + (UNSAFE.getShort(inputBase, inputAddress) & 0xFFFF); + long start3 = start2 + (UNSAFE.getShort(inputBase, inputAddress + 2) & 0xFFFF); + long start4 = start3 + (UNSAFE.getShort(inputBase, inputAddress + 4) & 0xFFFF); + + BitStream.Initializer initializer = new BitStream.Initializer(inputBase, start1, start2); + initializer.initialize(); + int stream1bitsConsumed = initializer.getBitsConsumed(); + long stream1currentAddress = initializer.getCurrentAddress(); + long stream1bits = initializer.getBits(); + + initializer = new BitStream.Initializer(inputBase, start2, start3); + initializer.initialize(); + int stream2bitsConsumed = initializer.getBitsConsumed(); + long stream2currentAddress = initializer.getCurrentAddress(); + long stream2bits = initializer.getBits(); + + initializer = new BitStream.Initializer(inputBase, start3, start4); + initializer.initialize(); + int stream3bitsConsumed = initializer.getBitsConsumed(); + long stream3currentAddress = initializer.getCurrentAddress(); + long stream3bits = initializer.getBits(); + + initializer = new BitStream.Initializer(inputBase, start4, inputLimit); + initializer.initialize(); + int stream4bitsConsumed = initializer.getBitsConsumed(); + long stream4currentAddress = initializer.getCurrentAddress(); + long stream4bits = initializer.getBits(); + + int segmentSize = (int) ((outputLimit - outputAddress + 3) / 4); + + long outputStart2 = outputAddress + segmentSize; + long outputStart3 = outputStart2 + segmentSize; + long outputStart4 = outputStart3 + segmentSize; + + long output1 = outputAddress; + long output2 = outputStart2; + long output3 = outputStart3; + long output4 = outputStart4; + + long fastOutputLimit = outputLimit - 7; + int tableLog = this.tableLog; + byte[] numbersOfBits = this.numbersOfBits; + byte[] symbols = this.symbols; + + while (output4 < fastOutputLimit) { + stream1bitsConsumed = decodeSymbol(outputBase, output1, stream1bits, stream1bitsConsumed, tableLog, numbersOfBits, symbols); + stream2bitsConsumed = decodeSymbol(outputBase, output2, stream2bits, stream2bitsConsumed, tableLog, numbersOfBits, symbols); + stream3bitsConsumed = decodeSymbol(outputBase, output3, stream3bits, stream3bitsConsumed, tableLog, numbersOfBits, symbols); + stream4bitsConsumed = decodeSymbol(outputBase, output4, stream4bits, stream4bitsConsumed, tableLog, numbersOfBits, symbols); + + stream1bitsConsumed = decodeSymbol(outputBase, output1 + 1, stream1bits, stream1bitsConsumed, tableLog, numbersOfBits, symbols); + stream2bitsConsumed = decodeSymbol(outputBase, output2 + 1, stream2bits, stream2bitsConsumed, tableLog, numbersOfBits, symbols); + stream3bitsConsumed = decodeSymbol(outputBase, output3 + 1, stream3bits, stream3bitsConsumed, tableLog, numbersOfBits, symbols); + stream4bitsConsumed = decodeSymbol(outputBase, output4 + 1, stream4bits, stream4bitsConsumed, tableLog, numbersOfBits, symbols); + + stream1bitsConsumed = decodeSymbol(outputBase, output1 + 2, stream1bits, stream1bitsConsumed, tableLog, numbersOfBits, symbols); + stream2bitsConsumed = decodeSymbol(outputBase, output2 + 2, stream2bits, stream2bitsConsumed, tableLog, numbersOfBits, symbols); + stream3bitsConsumed = decodeSymbol(outputBase, output3 + 2, stream3bits, stream3bitsConsumed, tableLog, numbersOfBits, symbols); + stream4bitsConsumed = decodeSymbol(outputBase, output4 + 2, stream4bits, stream4bitsConsumed, tableLog, numbersOfBits, symbols); + + stream1bitsConsumed = decodeSymbol(outputBase, output1 + 3, stream1bits, stream1bitsConsumed, tableLog, numbersOfBits, symbols); + stream2bitsConsumed = decodeSymbol(outputBase, output2 + 3, stream2bits, stream2bitsConsumed, tableLog, numbersOfBits, symbols); + stream3bitsConsumed = decodeSymbol(outputBase, output3 + 3, stream3bits, stream3bitsConsumed, tableLog, numbersOfBits, symbols); + stream4bitsConsumed = decodeSymbol(outputBase, output4 + 3, stream4bits, stream4bitsConsumed, tableLog, numbersOfBits, symbols); + + output1 += SIZE_OF_INT; + output2 += SIZE_OF_INT; + output3 += SIZE_OF_INT; + output4 += SIZE_OF_INT; + + BitStream.Loader loader = new BitStream.Loader(inputBase, start1, stream1currentAddress, stream1bits, stream1bitsConsumed); + boolean done = loader.load(); + stream1bitsConsumed = loader.getBitsConsumed(); + stream1bits = loader.getBits(); + stream1currentAddress = loader.getCurrentAddress(); + + if (done) { + break; + } + + loader = new BitStream.Loader(inputBase, start2, stream2currentAddress, stream2bits, stream2bitsConsumed); + done = loader.load(); + stream2bitsConsumed = loader.getBitsConsumed(); + stream2bits = loader.getBits(); + stream2currentAddress = loader.getCurrentAddress(); + + if (done) { + break; + } + + loader = new BitStream.Loader(inputBase, start3, stream3currentAddress, stream3bits, stream3bitsConsumed); + done = loader.load(); + stream3bitsConsumed = loader.getBitsConsumed(); + stream3bits = loader.getBits(); + stream3currentAddress = loader.getCurrentAddress(); + if (done) { + break; + } + + loader = new BitStream.Loader(inputBase, start4, stream4currentAddress, stream4bits, stream4bitsConsumed); + done = loader.load(); + stream4bitsConsumed = loader.getBitsConsumed(); + stream4bits = loader.getBits(); + stream4currentAddress = loader.getCurrentAddress(); + if (done) { + break; + } + } + + verify(output1 <= outputStart2 && output2 <= outputStart3 && output3 <= outputStart4, inputAddress, "Input is corrupted"); + + /// finish streams one by one + decodeTail(inputBase, start1, stream1currentAddress, stream1bitsConsumed, stream1bits, outputBase, output1, outputStart2); + decodeTail(inputBase, start2, stream2currentAddress, stream2bitsConsumed, stream2bits, outputBase, output2, outputStart3); + decodeTail(inputBase, start3, stream3currentAddress, stream3bitsConsumed, stream3bits, outputBase, output3, outputStart4); + decodeTail(inputBase, start4, stream4currentAddress, stream4bitsConsumed, stream4bits, outputBase, output4, outputLimit); + } + + private void decodeTail(final Object inputBase, final long startAddress, long currentAddress, int bitsConsumed, long bits, final Object outputBase, long outputAddress, final long outputLimit) + { + int tableLog = this.tableLog; + byte[] numbersOfBits = this.numbersOfBits; + byte[] symbols = this.symbols; + + // closer to the end + while (outputAddress < outputLimit) { + BitStream.Loader loader = new BitStream.Loader(inputBase, startAddress, currentAddress, bits, bitsConsumed); + boolean done = loader.load(); + bitsConsumed = loader.getBitsConsumed(); + bits = loader.getBits(); + currentAddress = loader.getCurrentAddress(); + if (done) { + break; + } + + bitsConsumed = decodeSymbol(outputBase, outputAddress++, bits, bitsConsumed, tableLog, numbersOfBits, symbols); + } + + // not more data in bit stream, so no need to reload + while (outputAddress < outputLimit) { + bitsConsumed = decodeSymbol(outputBase, outputAddress++, bits, bitsConsumed, tableLog, numbersOfBits, symbols); + } + + verify(isEndOfStream(startAddress, currentAddress, bitsConsumed), startAddress, "Bit stream is not fully consumed"); + } + + private static int decodeSymbol(Object outputBase, long outputAddress, long bitContainer, int bitsConsumed, int tableLog, byte[] numbersOfBits, byte[] symbols) + { + int value = (int) peekBitsFast(bitsConsumed, bitContainer, tableLog); + UNSAFE.putByte(outputBase, outputAddress, symbols[value]); + return bitsConsumed + numbersOfBits[value]; + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/MalformedInputException.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/MalformedInputException.java new file mode 100644 index 00000000..f0ef81b6 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/MalformedInputException.java @@ -0,0 +1,23 @@ +package com.boydti.fawe.object.io.zstd; + +public class MalformedInputException + extends RuntimeException +{ + private final long offset; + + public MalformedInputException(long offset) + { + this(offset, "Malformed input"); + } + + public MalformedInputException(long offset, String reason) + { + super(reason + ": offset=" + offset); + this.offset = offset; + } + + public long getOffset() + { + return offset; + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/UnsafeUtil.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/UnsafeUtil.java new file mode 100644 index 00000000..6b6bbe16 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/UnsafeUtil.java @@ -0,0 +1,71 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.boydti.fawe.object.io.zstd; + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import sun.misc.Unsafe; + +import java.lang.reflect.Field; +import java.nio.Buffer; + +final class UnsafeUtil +{ + public static final Unsafe UNSAFE; + private static final Field ADDRESS_ACCESSOR; + + private UnsafeUtil() {} + + static { + try { + Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + UNSAFE = (Unsafe) theUnsafe.get(null); + } + catch (Exception e) { + throw new RuntimeException(e); + } + + try { + Field field = Buffer.class.getDeclaredField("address"); + field.setAccessible(true); + ADDRESS_ACCESSOR = field; + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static long getAddress(Buffer buffer) + { + try { + return (long) ADDRESS_ACCESSOR.get(buffer); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/Util.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/Util.java new file mode 100644 index 00000000..48475665 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/Util.java @@ -0,0 +1,48 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.boydti.fawe.object.io.zstd; + +class Util +{ + private Util() + { + } + + public static int highestBit(int value) + { + return 31 - Integer.numberOfLeadingZeros(value); + } + + public static boolean isPowerOf2(int value) + { + return (value & (value - 1)) == 0; + } + + public static int mask(int bits) + { + return (1 << bits) - 1; + } + + public static void verify(boolean condition, long offset, String reason) + { + if (!condition) { + throw new MalformedInputException(offset, reason); + } + } + + public static MalformedInputException fail(long offset, String reason) + { + throw new MalformedInputException(offset, reason); + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdDecompressor.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdDecompressor.java new file mode 100644 index 00000000..fdba71ba --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdDecompressor.java @@ -0,0 +1,108 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.boydti.fawe.object.io.zstd; + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import java.nio.ByteBuffer; + + +import static com.boydti.fawe.object.io.zstd.UnsafeUtil.getAddress; +import static sun.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET; + +public class ZstdDecompressor +{ + private final ZstdFrameDecompressor decompressor = new ZstdFrameDecompressor(); + + public int decompress(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int maxOutputLength) + throws MalformedInputException + { + long inputAddress = ARRAY_BYTE_BASE_OFFSET + inputOffset; + long inputLimit = inputAddress + inputLength; + long outputAddress = ARRAY_BYTE_BASE_OFFSET + outputOffset; + long outputLimit = outputAddress + maxOutputLength; + + return decompressor.decompress(input, inputAddress, inputLimit, output, outputAddress, outputLimit); + } + + public void decompress(ByteBuffer input, ByteBuffer output) + throws MalformedInputException + { + Object inputBase; + long inputAddress; + long inputLimit; + if (input.isDirect()) { + inputBase = null; + long address = getAddress(input); + inputAddress = address + input.position(); + inputLimit = address + input.limit(); + } + else if (input.hasArray()) { + inputBase = input.array(); + inputAddress = ARRAY_BYTE_BASE_OFFSET + input.arrayOffset() + input.position(); + inputLimit = ARRAY_BYTE_BASE_OFFSET + input.arrayOffset() + input.limit(); + } + else { + throw new IllegalArgumentException("Unsupported input ByteBuffer implementation " + input.getClass().getName()); + } + + Object outputBase; + long outputAddress; + long outputLimit; + if (output.isDirect()) { + outputBase = null; + long address = getAddress(output); + outputAddress = address + output.position(); + outputLimit = address + output.limit(); + } + else if (output.hasArray()) { + outputBase = output.array(); + outputAddress = ARRAY_BYTE_BASE_OFFSET + output.arrayOffset() + output.position(); + outputLimit = ARRAY_BYTE_BASE_OFFSET + output.arrayOffset() + output.limit(); + } + else { + throw new IllegalArgumentException("Unsupported output ByteBuffer implementation " + output.getClass().getName()); + } + + // HACK: Assure JVM does not collect Slice wrappers while decompressing, since the + // collection may trigger freeing of the underlying memory resulting in a segfault + // There is no other known way to signal to the JVM that an object should not be + // collected in a block, and technically, the JVM is allowed to eliminate these locks. + synchronized (input) { + synchronized (output) { + int written = new ZstdFrameDecompressor().decompress(inputBase, inputAddress, inputLimit, outputBase, outputAddress, outputLimit); + output.position(output.position() + written); + } + } + } + + public static long getDecompressedSize(byte[] input, int offset, int length) + { + int baseAddress = ARRAY_BYTE_BASE_OFFSET + offset; + return ZstdFrameDecompressor.getDecompressedSize(input, baseAddress, baseAddress + length); + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdFrameDecompressor.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdFrameDecompressor.java new file mode 100644 index 00000000..83c359fb --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdFrameDecompressor.java @@ -0,0 +1,958 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.boydti.fawe.object.io.zstd; + +import java.util.Arrays; + + +import static com.boydti.fawe.object.io.zstd.BitStream.peekBits; +import static com.boydti.fawe.object.io.zstd.UnsafeUtil.UNSAFE; +import static com.boydti.fawe.object.io.zstd.Util.fail; +import static com.boydti.fawe.object.io.zstd.Util.mask; +import static com.boydti.fawe.object.io.zstd.Util.verify; +import static sun.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET; + +class ZstdFrameDecompressor +{ + private static final int MIN_MATCH = 3; + + private static final int[] DEC_32_TABLE = {4, 1, 2, 1, 4, 4, 4, 4}; + private static final int[] DEC_64_TABLE = {0, 0, 0, -1, 0, 1, 2, 3}; + + private static final int MAGIC_NUMBER = 0xFD2FB528; // v0.5 + + private static final int MIN_SEQUENCES_SIZE = 1; + private static final int MIN_BLOCK_SIZE = 1 // block type tag + + 1 // min size of raw or rle length header + + MIN_SEQUENCES_SIZE; + + private static final int MAX_BLOCK_SIZE = 128 * 1024; + + private static final int MIN_WINDOW_LOG = 10; + private static final int MAX_WINDOW_SIZE = 1 << 23; + + public static final int SIZE_OF_BYTE = 1; + public static final int SIZE_OF_SHORT = 2; + public static final int SIZE_OF_INT = 4; + public static final int SIZE_OF_LONG = 8; + + private static final long SIZE_OF_BLOCK_HEADER = 3; + + // block types + private static final int RAW_BLOCK = 0; + private static final int RLE_BLOCK = 1; + private static final int COMPRESSED_BLOCK = 2; + + // literal block types + private static final int RAW_LITERALS_BLOCK = 0; + private static final int RLE_LITERALS_BLOCK = 1; + private static final int COMPRESSED_LITERALS_BLOCK = 2; + private static final int REPEAT_STATS_LITERALS_BLOCK = 3; + + private static final int LONG_NUMBER_OF_SEQUENCES = 0x7F00; + + private static final int MAX_LITERALS_LENGTH_SYMBOL = 35; + private static final int MAX_MATCH_LENGTH_SYMBOL = 52; + private static final int MAX_OFFSET_CODE_SYMBOL = 28; + + private static final int LITERALS_LENGTH_FSE_LOG = 9; + private static final int MATCH_LENGTH_FSE_LOG = 9; + private static final int OFFSET_CODES_FSE_LOG = 8; + + private static final int SET_BASIC = 0; + private static final int SET_RLE = 1; + private static final int SET_COMPRESSED = 2; + private static final int SET_REPEAT = 3; + + private static final int[] LITERALS_LENGTH_BASE = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 18, 20, 22, 24, 28, 32, 40, 48, 64, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, + 0x2000, 0x4000, 0x8000, 0x10000}; + + private static final int[] MATCH_LENGTH_BASE = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 37, 39, 41, 43, 47, 51, 59, 67, 83, 99, 0x83, 0x103, 0x203, 0x403, 0x803, + 0x1003, 0x2003, 0x4003, 0x8003, 0x10003}; + + private static final int[] OFFSET_CODES_BASE = { + 0, 1, 1, 5, 0xD, 0x1D, 0x3D, 0x7D, + 0xFD, 0x1FD, 0x3FD, 0x7FD, 0xFFD, 0x1FFD, 0x3FFD, 0x7FFD, + 0xFFFD, 0x1FFFD, 0x3FFFD, 0x7FFFD, 0xFFFFD, 0x1FFFFD, 0x3FFFFD, 0x7FFFFD, + 0xFFFFFD, 0x1FFFFFD, 0x3FFFFFD, 0x7FFFFFD, 0xFFFFFFD}; + + private static final int[] LITERALS_LENGTH_BITS = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 2, 2, 3, 3, 4, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16}; + + private static final int[] MATCH_LENGTH_BITS = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16}; + + private static final FiniteStateEntropy.Table DEFAULT_LITERALS_LENGTH_TABLE = new FiniteStateEntropy.Table( + 6, + new int[] { + 0, 16, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 32, 0, 0, 32, 0, 32, 0, 32, 0, 0, 32, 0, 32, 0, 32, 0, 0, 16, 32, 0, 0, 48, 16, 32, 32, 32, + 32, 32, 32, 32, 32, 0, 32, 32, 32, 32, 32, 32, 0, 0, 0, 0}, + new byte[] { + 0, 0, 1, 3, 4, 6, 7, 9, 10, 12, 14, 16, 18, 19, 21, 22, 24, 25, 26, 27, 29, 31, 0, 1, 2, 4, 5, 7, 8, 10, 11, 13, 16, 17, 19, 20, 22, 23, 25, 25, 26, 28, 30, 0, + 1, 2, 3, 5, 6, 8, 9, 11, 12, 15, 17, 18, 20, 21, 23, 24, 35, 34, 33, 32}, + new byte[] { + 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 4, 4, 5, 6, 6, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6}); + + private static final FiniteStateEntropy.Table DEFAULT_OFFSET_CODES_TABLE = new FiniteStateEntropy.Table( + 5, + new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0}, + new byte[] {0, 6, 9, 15, 21, 3, 7, 12, 18, 23, 5, 8, 14, 20, 2, 7, 11, 17, 22, 4, 8, 13, 19, 1, 6, 10, 16, 28, 27, 26, 25, 24}, + new byte[] {5, 4, 5, 5, 5, 5, 4, 5, 5, 5, 5, 4, 5, 5, 5, 4, 5, 5, 5, 5, 4, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 5}); + + private static final FiniteStateEntropy.Table DEFAULT_MATCH_LENGTH_TABLE = new FiniteStateEntropy.Table( + 6, + new int[] { + 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 32, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 48, 16, 32, 32, 32, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + new byte[] { + 0, 1, 2, 3, 5, 6, 8, 10, 13, 16, 19, 22, 25, 28, 31, 33, 35, 37, 39, 41, 43, 45, 1, 2, 3, 4, 6, 7, 9, 12, 15, 18, 21, 24, 27, 30, 32, 34, 36, 38, 40, 42, 44, 1, + 1, 2, 4, 5, 7, 8, 11, 14, 17, 20, 23, 26, 29, 52, 51, 50, 49, 48, 47, 46}, + new byte[] { + 6, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6}); + + private final byte[] literals = new byte[MAX_BLOCK_SIZE + SIZE_OF_LONG]; // extra space to allow for long-at-a-time copy + + // current buffer containing literals + private Object literalsBase; + private long literalsAddress; + private long literalsLimit; + + private final int[] previousOffsets = new int[3]; + + private final FiniteStateEntropy.Table literalsLengthTable = new FiniteStateEntropy.Table(LITERALS_LENGTH_FSE_LOG); + private final FiniteStateEntropy.Table offsetCodesTable = new FiniteStateEntropy.Table(OFFSET_CODES_FSE_LOG); + private final FiniteStateEntropy.Table matchLengthTable = new FiniteStateEntropy.Table(MATCH_LENGTH_FSE_LOG); + + private FiniteStateEntropy.Table currentLiteralsLengthTable; + private FiniteStateEntropy.Table currentOffsetCodesTable; + private FiniteStateEntropy.Table currentMatchLengthTable; + + private final Huffman huffman = new Huffman(); + private final FseTableReader fse = new FseTableReader(); + + public int decompress( + final Object inputBase, + final long inputAddress, + final long inputLimit, + final Object outputBase, + final long outputAddress, + final long outputLimit) + { + if (outputAddress == outputLimit) { + return 0; + } + + reset(); + + long input = inputAddress; + long output = outputAddress; + + input += verifyMagic(inputBase, inputAddress, inputLimit); + + FrameHeader frameHeader = readFrameHeader(inputBase, input, inputLimit); + input += frameHeader.headerSize; + + boolean lastBlock; + do { + verify(input + SIZE_OF_BLOCK_HEADER <= inputLimit, input, "Not enough input bytes"); + + // read block header + int header = UNSAFE.getInt(inputBase, input) & 0xFF_FFFF; + input += SIZE_OF_BLOCK_HEADER; + + lastBlock = (header & 1) != 0; + int blockType = (header >>> 1) & 0b11; + int blockSize = (header >>> 3) & 0x1F_FFFF; // 21 bits + + int decodedSize; + switch (blockType) { + case RAW_BLOCK: + verify(inputAddress + blockSize <= inputLimit, input, "Not enough input bytes"); + decodedSize = decodeRawBlock(inputBase, input, blockSize, outputBase, output, outputLimit); + input += blockSize; + break; + case RLE_BLOCK: + verify(inputAddress + 1 <= inputLimit, input, "Not enough input bytes"); + decodedSize = decodeRleBlock(blockSize, inputBase, input, outputBase, output, outputLimit); + input += 1; + break; + case COMPRESSED_BLOCK: + verify(inputAddress + blockSize <= inputLimit, input, "Not enough input bytes"); + decodedSize = decodeCompressedBlock(inputBase, input, blockSize, outputBase, output, outputLimit, frameHeader.windowSize); + input += blockSize; + break; + default: + throw fail(input, "Invalid block type"); + } + + output += decodedSize; + } + while (!lastBlock); + + if (frameHeader.hasChecksum) { + // TODO checksum + } + + return (int) (output - outputAddress); + } + + private void reset() + { + previousOffsets[0] = 1; + previousOffsets[1] = 4; + previousOffsets[2] = 8; + + currentLiteralsLengthTable = null; + currentOffsetCodesTable = null; + currentMatchLengthTable = null; + } + + private static int decodeRawBlock(Object inputBase, long inputAddress, int blockSize, Object outputBase, long outputAddress, long outputLimit) + { + verify(outputAddress + blockSize <= outputLimit, inputAddress, "Output buffer too small"); + + UNSAFE.copyMemory(inputBase, inputAddress, outputBase, outputAddress, blockSize); + return blockSize; + } + + private static int decodeRleBlock(int size, Object inputBase, long inputAddress, Object outputBase, long outputAddress, long outputLimit) + { + verify(outputAddress + size <= outputLimit, inputAddress, "Output buffer too small"); + + long output = outputAddress; + long value = UNSAFE.getByte(inputBase, inputAddress) & 0xFFL; + + int remaining = size; + if (remaining >= SIZE_OF_LONG) { + long packed = value + | (value << 8) + | (value << 16) + | (value << 24) + | (value << 32) + | (value << 40) + | (value << 48) + | (value << 56); + + do { + UNSAFE.putLong(outputBase, output, packed); + output += SIZE_OF_LONG; + remaining -= SIZE_OF_LONG; + } + while (remaining >= SIZE_OF_LONG); + } + + for (int i = 0; i < remaining; i++) { + UNSAFE.putByte(outputBase, output, (byte) value); + output++; + } + + return size; + } + + private int decodeCompressedBlock(Object inputBase, final long inputAddress, int blockSize, Object outputBase, long outputAddress, long outputLimit, int windowSize) + { + long inputLimit = inputAddress + blockSize; + long input = inputAddress; + + verify(blockSize <= MAX_BLOCK_SIZE, input, "Expected match length table to be present"); + verify(blockSize >= MIN_BLOCK_SIZE, input, "Compressed block size too small"); + + // decode literals + int literalsBlockType = UNSAFE.getByte(inputBase, input) & 0b11; + + switch (literalsBlockType) { + case RAW_LITERALS_BLOCK: { + input += decodeRawLiterals(inputBase, input, inputLimit); + break; + } + case RLE_LITERALS_BLOCK: { + input += decodeRleLiterals(inputBase, input, blockSize); + break; + } + case REPEAT_STATS_LITERALS_BLOCK: + verify(huffman.isLoaded(), input, "Dictionary is corrupted"); + case COMPRESSED_LITERALS_BLOCK: { + input += decodeCompressedLiterals(inputBase, input, blockSize, literalsBlockType); + break; + } + default: + throw fail(input, "Invalid literals block encoding type"); + } + + verify(windowSize <= MAX_WINDOW_SIZE, input, "Window size too large (not yet supported)"); + + return decompressSequences( + inputBase, input, inputAddress + blockSize, + outputBase, outputAddress, outputLimit, + literalsBase, literalsAddress, literalsLimit); + } + + private int decompressSequences( + final Object inputBase, final long inputAddress, final long inputLimit, + final Object outputBase, final long outputAddress, final long outputLimit, + final Object literalsBase, final long literalsAddress, final long literalsLimit) + { + final long fastOutputLimit = outputLimit - SIZE_OF_LONG; + + long input = inputAddress; + long output = outputAddress; + + long literalsInput = literalsAddress; + + int size = (int) (inputLimit - inputAddress); + verify(size >= MIN_SEQUENCES_SIZE, input, "Not enough input bytes"); + + // decode header + int sequenceCount = UNSAFE.getByte(inputBase, input++) & 0xFF; + if (sequenceCount != 0) { + if (sequenceCount == 255) { + verify(input + SIZE_OF_SHORT <= inputLimit, input, "Not enough input bytes"); + sequenceCount = (UNSAFE.getShort(inputBase, input) & 0xFFFF) + LONG_NUMBER_OF_SEQUENCES; + input += SIZE_OF_SHORT; + } + else if (sequenceCount > 127) { + verify(input < inputLimit, input, "Not enough input bytes"); + sequenceCount = ((sequenceCount - 128) << 8) + (UNSAFE.getByte(inputBase, input++) & 0xFF); + } + + verify(input + SIZE_OF_INT <= inputLimit, input, "Not enough input bytes"); + + byte type = UNSAFE.getByte(inputBase, input++); + + int literalsLengthType = (type & 0xFF) >>> 6; + int offsetCodesType = (type >>> 4) & 0b11; + int matchLengthType = (type >>> 2) & 0b11; + + input = computeLiteralsTable(literalsLengthType, inputBase, input, inputLimit); + input = computeOffsetsTable(offsetCodesType, inputBase, input, inputLimit); + input = computeMatchLengthTable(matchLengthType, inputBase, input, inputLimit); + + // decompress sequences + BitStream.Initializer initializer = new BitStream.Initializer(inputBase, input, inputLimit); + initializer.initialize(); + int bitsConsumed = initializer.getBitsConsumed(); + long bits = initializer.getBits(); + long currentAddress = initializer.getCurrentAddress(); + + FiniteStateEntropy.Table currentLiteralsLengthTable = this.currentLiteralsLengthTable; + FiniteStateEntropy.Table currentOffsetCodesTable = this.currentOffsetCodesTable; + FiniteStateEntropy.Table currentMatchLengthTable = this.currentMatchLengthTable; + + int literalsLengthState = (int) peekBits(bitsConsumed, bits, currentLiteralsLengthTable.log2Size); + bitsConsumed += currentLiteralsLengthTable.log2Size; + + int offsetCodesState = (int) peekBits(bitsConsumed, bits, currentOffsetCodesTable.log2Size); + bitsConsumed += currentOffsetCodesTable.log2Size; + + int matchLengthState = (int) peekBits(bitsConsumed, bits, currentMatchLengthTable.log2Size); + bitsConsumed += currentMatchLengthTable.log2Size; + + int[] previousOffsets = this.previousOffsets; + + byte[] literalsLengthNumbersOfBits = currentLiteralsLengthTable.numberOfBits; + int[] literalsLengthNewStates = currentLiteralsLengthTable.newState; + byte[] literalsLengthSymbols = currentLiteralsLengthTable.symbol; + + byte[] matchLengthNumbersOfBits = currentMatchLengthTable.numberOfBits; + int[] matchLengthNewStates = currentMatchLengthTable.newState; + byte[] matchLengthSymbols = currentMatchLengthTable.symbol; + + byte[] offsetCodesNumbersOfBits = currentOffsetCodesTable.numberOfBits; + int[] offsetCodesNewStates = currentOffsetCodesTable.newState; + byte[] offsetCodesSymbols = currentOffsetCodesTable.symbol; + + while (sequenceCount > 0) { + sequenceCount--; + + BitStream.Loader loader = new BitStream.Loader(inputBase, input, currentAddress, bits, bitsConsumed); + loader.load(); + bitsConsumed = loader.getBitsConsumed(); + bits = loader.getBits(); + currentAddress = loader.getCurrentAddress(); + if (loader.isOverflow()) { + verify(sequenceCount == 0, input, "Not all sequences were consumed"); + break; + } + + // decode sequence + int literalsLengthCode = literalsLengthSymbols[literalsLengthState]; + int matchLengthCode = matchLengthSymbols[matchLengthState]; + int offsetCode = offsetCodesSymbols[offsetCodesState]; + + int literalsLengthBits = LITERALS_LENGTH_BITS[literalsLengthCode]; + int matchLengthBits = MATCH_LENGTH_BITS[matchLengthCode]; + int offsetBits = offsetCode; + + int offset = OFFSET_CODES_BASE[offsetCode]; + if (offsetCode > 0) { + offset += peekBits(bitsConsumed, bits, offsetBits); + bitsConsumed += offsetBits; + } + + if (offsetCode <= 1) { + if (literalsLengthCode == 0) { + offset++; + } + + if (offset != 0) { + int temp; + if (offset == 3) { + temp = previousOffsets[0] - 1; + } + else { + temp = previousOffsets[offset]; + } + + if (temp == 0) { + temp = 1; + } + + if (offset != 1) { + previousOffsets[2] = previousOffsets[1]; + } + previousOffsets[1] = previousOffsets[0]; + previousOffsets[0] = temp; + + offset = temp; + } + else { + offset = previousOffsets[0]; + } + } + else { + previousOffsets[2] = previousOffsets[1]; + previousOffsets[1] = previousOffsets[0]; + previousOffsets[0] = offset; + } + + int matchLength = MATCH_LENGTH_BASE[matchLengthCode]; + if (matchLengthCode > 31) { + matchLength += peekBits(bitsConsumed, bits, matchLengthBits); + bitsConsumed += matchLengthBits; + } + + int literalsLength = LITERALS_LENGTH_BASE[literalsLengthCode]; + if (literalsLengthCode > 15) { + literalsLength += peekBits(bitsConsumed, bits, literalsLengthBits); + bitsConsumed += literalsLengthBits; + } + + int totalBits = literalsLengthBits + matchLengthBits + offsetBits; + if (totalBits > 64 - 7 - (LITERALS_LENGTH_FSE_LOG + MATCH_LENGTH_FSE_LOG + OFFSET_CODES_FSE_LOG)) { + BitStream.Loader loader1 = new BitStream.Loader(inputBase, input, currentAddress, bits, bitsConsumed); + loader1.load(); + + bitsConsumed = loader1.getBitsConsumed(); + bits = loader1.getBits(); + currentAddress = loader1.getCurrentAddress(); + } + + int numberOfBits; + + numberOfBits = literalsLengthNumbersOfBits[literalsLengthState]; + literalsLengthState = (int) (literalsLengthNewStates[literalsLengthState] + peekBits(bitsConsumed, bits, numberOfBits)); // <= 9 bits + bitsConsumed += numberOfBits; + + numberOfBits = matchLengthNumbersOfBits[matchLengthState]; + matchLengthState = (int) (matchLengthNewStates[matchLengthState] + peekBits(bitsConsumed, bits, numberOfBits)); // <= 9 bits + bitsConsumed += numberOfBits; + + numberOfBits = offsetCodesNumbersOfBits[offsetCodesState]; + offsetCodesState = (int) (offsetCodesNewStates[offsetCodesState] + peekBits(bitsConsumed, bits, numberOfBits)); // <= 8 bits + bitsConsumed += numberOfBits; + + final long literalOutputLimit = output + literalsLength; + final long matchOutputLimit = literalOutputLimit + matchLength; + + verify(matchOutputLimit <= outputLimit, input, "Output buffer too small"); + verify(literalsInput + literalsLength <= literalsLimit, input, "Input is corrupted"); + + long matchAddress = literalOutputLimit - offset; + + if (literalOutputLimit > fastOutputLimit) { + executeLastSequence(outputBase, output, literalOutputLimit, matchOutputLimit, fastOutputLimit, literalsInput, matchAddress); + } + else { + // copy literals. literalOutputLimit <= fastOutputLimit, so we can copy + // long at a time with over-copy + output = copyLiterals(outputBase, literalsBase, output, literalsInput, literalOutputLimit); + copyMatch(outputBase, fastOutputLimit, output, offset, matchOutputLimit, matchAddress); + } + output = matchOutputLimit; + literalsInput += literalsLength; + } + } + + // last literal segment + output = copyLastLiteral(outputBase, literalsBase, literalsLimit, output, literalsInput); + + return (int) (output - outputAddress); + } + + private long copyLastLiteral(Object outputBase, Object literalsBase, long literalsLimit, long output, long literalsInput) + { + long lastLiteralsSize = literalsLimit - literalsInput; + UNSAFE.copyMemory(literalsBase, literalsInput, outputBase, output, lastLiteralsSize); + output += lastLiteralsSize; + return output; + } + + private void copyMatch(Object outputBase, long fastOutputLimit, long output, int offset, long matchOutputLimit, long matchAddress) + { + matchAddress = copyMatchHead(outputBase, output, offset, matchAddress); + output += SIZE_OF_LONG; + + copyMatchTail(outputBase, fastOutputLimit, output, matchOutputLimit, matchAddress); + } + + private void copyMatchTail(Object outputBase, long fastOutputLimit, long output, long matchOutputLimit, long matchAddress) + { + if (matchOutputLimit > fastOutputLimit) { + while (output < fastOutputLimit) { + UNSAFE.putLong(outputBase, output, UNSAFE.getLong(outputBase, matchAddress)); + matchAddress += SIZE_OF_LONG; + output += SIZE_OF_LONG; + } + + while (output < matchOutputLimit) { + UNSAFE.putByte(outputBase, output++, UNSAFE.getByte(outputBase, matchAddress++)); + } + } + else { + while (output < matchOutputLimit) { + UNSAFE.putLong(outputBase, output, UNSAFE.getLong(outputBase, matchAddress)); + matchAddress += SIZE_OF_LONG; + output += SIZE_OF_LONG; + } + } + } + + private long copyMatchHead(Object outputBase, long output, int offset, long matchAddress) + { + // copy match + if (offset < 8) { + // 8 bytes apart so that we can copy long-at-a-time below + int increment32 = DEC_32_TABLE[offset]; + int decrement64 = DEC_64_TABLE[offset]; + + UNSAFE.putByte(outputBase, output, UNSAFE.getByte(outputBase, matchAddress)); + UNSAFE.putByte(outputBase, output + 1, UNSAFE.getByte(outputBase, matchAddress + 1)); + UNSAFE.putByte(outputBase, output + 2, UNSAFE.getByte(outputBase, matchAddress + 2)); + UNSAFE.putByte(outputBase, output + 3, UNSAFE.getByte(outputBase, matchAddress + 3)); + matchAddress += increment32; + + UNSAFE.putInt(outputBase, output + 4, UNSAFE.getInt(outputBase, matchAddress)); + matchAddress -= decrement64; + } + else { + UNSAFE.putLong(outputBase, output, UNSAFE.getLong(outputBase, matchAddress)); + matchAddress += SIZE_OF_LONG; + } + return matchAddress; + } + + private long copyLiterals(Object outputBase, Object literalsBase, long output, long literalsInput, long literalOutputLimit) + { + long literalInput = literalsInput; + do { + UNSAFE.putLong(outputBase, output, UNSAFE.getLong(literalsBase, literalInput)); + output += SIZE_OF_LONG; + literalInput += SIZE_OF_LONG; + } + while (output < literalOutputLimit); + output = literalOutputLimit; // correction in case we over-copied + return output; + } + + private long computeMatchLengthTable(int matchLengthType, Object inputBase, long input, long inputLimit) + { + switch (matchLengthType) { + case SET_RLE: + verify(input < inputLimit, input, "Not enough input bytes"); + + byte value = UNSAFE.getByte(inputBase, input++); + verify(value <= MAX_MATCH_LENGTH_SYMBOL, input, "Value exceeds expected maximum value"); + + FseTableReader.buildRleTable(matchLengthTable, value); + currentMatchLengthTable = matchLengthTable; + break; + case SET_BASIC: + currentMatchLengthTable = DEFAULT_MATCH_LENGTH_TABLE; + break; + case SET_REPEAT: + verify(currentMatchLengthTable != null, input, "Expected match length table to be present"); + break; + case SET_COMPRESSED: + input += fse.readFseTable(matchLengthTable, inputBase, input, inputLimit, MAX_MATCH_LENGTH_SYMBOL, MATCH_LENGTH_FSE_LOG); + currentMatchLengthTable = matchLengthTable; + break; + default: + throw fail(input, "Invalid match length encoding type"); + } + return input; + } + + private long computeOffsetsTable(int offsetCodesType, Object inputBase, long input, long inputLimit) + { + switch (offsetCodesType) { + case SET_RLE: + verify(input < inputLimit, input, "Not enough input bytes"); + + byte value = UNSAFE.getByte(inputBase, input++); + verify(value <= MAX_OFFSET_CODE_SYMBOL, input, "Value exceeds expected maximum value"); + + FseTableReader.buildRleTable(offsetCodesTable, value); + currentOffsetCodesTable = offsetCodesTable; + break; + case SET_BASIC: + currentOffsetCodesTable = DEFAULT_OFFSET_CODES_TABLE; + break; + case SET_REPEAT: + verify(currentOffsetCodesTable != null, input, "Expected match length table to be present"); + break; + case SET_COMPRESSED: + input += fse.readFseTable(offsetCodesTable, inputBase, input, inputLimit, MAX_OFFSET_CODE_SYMBOL, OFFSET_CODES_FSE_LOG); + currentOffsetCodesTable = offsetCodesTable; + break; + default: + throw fail(input, "Invalid offset code encoding type"); + } + return input; + } + + private long computeLiteralsTable(int literalsLengthType, Object inputBase, long input, long inputLimit) + { + switch (literalsLengthType) { + case SET_RLE: + verify(input < inputLimit, input, "Not enough input bytes"); + + byte value = UNSAFE.getByte(inputBase, input++); + verify(value <= MAX_LITERALS_LENGTH_SYMBOL, input, "Value exceeds expected maximum value"); + + FseTableReader.buildRleTable(literalsLengthTable, value); + currentLiteralsLengthTable = literalsLengthTable; + break; + case SET_BASIC: + currentLiteralsLengthTable = DEFAULT_LITERALS_LENGTH_TABLE; + break; + case SET_REPEAT: + verify(currentLiteralsLengthTable != null, input, "Expected match length table to be present"); + break; + case SET_COMPRESSED: + input += fse.readFseTable(literalsLengthTable, inputBase, input, inputLimit, MAX_LITERALS_LENGTH_SYMBOL, LITERALS_LENGTH_FSE_LOG); + currentLiteralsLengthTable = literalsLengthTable; + break; + default: + throw fail(input, "Invalid literals length encoding type"); + } + return input; + } + + private void executeLastSequence(Object outputBase, long output, long literalOutputLimit, long matchOutputLimit, long fastOutputLimit, long literalInput, long matchAddress) + { + // copy literals + if (output < fastOutputLimit) { + // wild copy + do { + UNSAFE.putLong(outputBase, output, UNSAFE.getLong(literalsBase, literalInput)); + output += SIZE_OF_LONG; + literalInput += SIZE_OF_LONG; + } + while (output < fastOutputLimit); + + literalInput -= output - fastOutputLimit; + output = fastOutputLimit; + } + + while (output < literalOutputLimit) { + UNSAFE.putByte(outputBase, output, UNSAFE.getByte(literalsBase, literalInput)); + output++; + literalInput++; + } + + // copy match + while (output < matchOutputLimit) { + UNSAFE.putByte(outputBase, output, UNSAFE.getByte(outputBase, matchAddress)); + output++; + matchAddress++; + } + } + + private int decodeCompressedLiterals(Object inputBase, final long inputAddress, int blockSize, int literalsBlockType) + { + long input = inputAddress; + verify(blockSize >= 5, input, "Not enough input bytes"); + + // compressed + int compressedSize; + int uncompressedSize; + boolean singleStream = false; + int headerSize; + int type = (UNSAFE.getByte(inputBase, input) >> 2) & 0b11; + switch (type) { + case 0: + singleStream = true; + case 1: { + int header = UNSAFE.getInt(inputBase, input); + + headerSize = 3; + uncompressedSize = (header >>> 4) & mask(10); + compressedSize = (header >>> 14) & mask(10); + break; + } + case 2: { + int header = UNSAFE.getInt(inputBase, input); + + headerSize = 4; + uncompressedSize = (header >>> 4) & mask(14); + compressedSize = (header >>> 18) & mask(14); + break; + } + case 3: { + // read 5 little-endian bytes + long header = UNSAFE.getByte(inputBase, input) & 0xFF | + (UNSAFE.getInt(inputBase, input + 1) & 0xFFFF_FFFFL) << 8; + + headerSize = 5; + uncompressedSize = (int) ((header >>> 4) & mask(18)); + compressedSize = (int) ((header >>> 22) & mask(18)); + break; + } + default: + throw fail(input, "Invalid literals header size type"); + } + + verify(uncompressedSize <= MAX_BLOCK_SIZE, input, "Block exceeds maximum size"); + verify(headerSize + compressedSize <= blockSize, input, "Input is corrupted"); + + input += headerSize; + + long inputLimit = input + compressedSize; + if (literalsBlockType != REPEAT_STATS_LITERALS_BLOCK) { + input += huffman.readTable(inputBase, input, compressedSize); + } + + literalsBase = literals; + literalsAddress = ARRAY_BYTE_BASE_OFFSET; + literalsLimit = ARRAY_BYTE_BASE_OFFSET + uncompressedSize; + + if (singleStream) { + huffman.decodeSingleStream(inputBase, input, inputLimit, literals, literalsAddress, literalsLimit); + } + else { + huffman.decode4Streams(inputBase, input, inputLimit, literals, literalsAddress, literalsLimit); + } + + return headerSize + compressedSize; + } + + private int decodeRleLiterals(Object inputBase, final long inputAddress, int blockSize) + { + long input = inputAddress; + int outputSize; + + int type = (UNSAFE.getByte(inputBase, input) >> 2) & 0b11; + switch (type) { + case 0: + case 2: + outputSize = (UNSAFE.getByte(inputBase, input) & 0xFF) >>> 3; + input++; + break; + case 1: + outputSize = (UNSAFE.getShort(inputBase, input) & 0xFFFF) >>> 4; + input += 2; + break; + case 3: + // we need at least 4 bytes (3 for the header, 1 for the payload) + verify(blockSize >= SIZE_OF_INT, input, "Not enough input bytes"); + outputSize = (UNSAFE.getInt(inputBase, input) & 0xFF_FFFF) >>> 4; + input += 3; + break; + default: + throw fail(input, "Invalid RLE literals header encoding type"); + } + + verify(outputSize <= MAX_BLOCK_SIZE, input, "Output exceeds maximum block size"); + + byte value = UNSAFE.getByte(inputBase, input++); + Arrays.fill(literals, 0, outputSize + SIZE_OF_LONG, value); + + literalsBase = literals; + literalsAddress = ARRAY_BYTE_BASE_OFFSET; + literalsLimit = ARRAY_BYTE_BASE_OFFSET + outputSize; + + return (int) (input - inputAddress); + } + + private int decodeRawLiterals(Object inputBase, final long inputAddress, long inputLimit) + { + long input = inputAddress; + int type = (UNSAFE.getByte(inputBase, input) >> 2) & 0b11; + + int literalSize; + switch (type) { + case 0: + case 2: + literalSize = (UNSAFE.getByte(inputBase, input) & 0xFF) >>> 3; + input++; + break; + case 1: + literalSize = (UNSAFE.getShort(inputBase, input) & 0xFFFF) >>> 4; + input += 2; + break; + case 3: + // read 3 little-endian bytes + int header = ((UNSAFE.getByte(inputBase, input) & 0xFF) | + ((UNSAFE.getShort(inputBase, input + 1) & 0xFFFF) << 8)); + + literalSize = header >>> 4; + input += 3; + break; + default: + throw fail(input, "Invalid raw literals header encoding type"); + } + + verify(input + literalSize <= inputLimit, input, "Not enough input bytes"); + + // Set literals pointer to [input, literalSize], but only if we can copy 8 bytes at a time during sequence decoding + // Otherwise, copy literals into buffer that's big enough to guarantee that + if (literalSize > (inputLimit - input) - SIZE_OF_LONG) { + literalsBase = literals; + literalsAddress = ARRAY_BYTE_BASE_OFFSET; + literalsLimit = ARRAY_BYTE_BASE_OFFSET + literalSize; + + UNSAFE.copyMemory(inputBase, input, literals, literalsAddress, literalSize); + Arrays.fill(literals, literalSize, literalSize + SIZE_OF_LONG, (byte) 0); + } + else { + literalsBase = inputBase; + literalsAddress = input; + literalsLimit = literalsAddress + literalSize; + } + input += literalSize; + + return (int) (input - inputAddress); + } + + private static FrameHeader readFrameHeader(final Object inputBase, final long inputAddress, final long inputLimit) + { + long input = inputAddress; + verify(input < inputLimit, input, "Not enough input bytes"); + + int frameHeaderDescriptor = UNSAFE.getByte(inputBase, input++) & 0xFF; + boolean singleSegment = (frameHeaderDescriptor & 0b100000) != 0; + int dictionaryDescriptor = frameHeaderDescriptor & 0b11; + int contentSizeDescriptor = frameHeaderDescriptor >>> 6; + + int headerSize = 1 + + (singleSegment ? 0 : 1) + + (dictionaryDescriptor == 0 ? 0 : (1 << (dictionaryDescriptor - 1))) + + (contentSizeDescriptor == 0 ? (singleSegment ? 1 : 0) : (1 << contentSizeDescriptor)); + + verify(headerSize <= inputLimit - inputAddress, input, "Not enough input bytes"); + + // decode window size + int windowSize = -1; + if (!singleSegment) { + int windowDescriptor = UNSAFE.getByte(inputBase, input++) & 0xFF; + int exponent = windowDescriptor >>> 3; + int mantissa = windowDescriptor & 0b111; + + int base = 1 << (MIN_WINDOW_LOG + exponent); + windowSize = base + (base / 8) * mantissa; + } + + // decode dictionary id + long dictionaryId = -1; + switch (dictionaryDescriptor) { + case 1: + dictionaryId = UNSAFE.getByte(inputBase, input) & 0xFF; + input += SIZE_OF_BYTE; + break; + case 2: + dictionaryId = UNSAFE.getShort(inputBase, input) & 0xFFFF; + input += SIZE_OF_SHORT; + break; + case 3: + dictionaryId = UNSAFE.getInt(inputBase, input) & 0xFFFF_FFFFL; + input += SIZE_OF_INT; + break; + } + verify(dictionaryId == -1, input, "Custom dictionaries not supported"); + + // decode content size + long contentSize = -1; + switch (contentSizeDescriptor) { + case 0: + if (singleSegment) { + contentSize = UNSAFE.getByte(inputBase, input) & 0xFF; + input += SIZE_OF_BYTE; + } + break; + case 1: + contentSize = UNSAFE.getShort(inputBase, input) & 0xFFFF; + contentSize += 256; + input += SIZE_OF_SHORT; + break; + case 2: + contentSize = UNSAFE.getInt(inputBase, input) & 0xFFFF_FFFFL; + input += SIZE_OF_INT; + break; + case 3: + contentSize = UNSAFE.getLong(inputBase, input); + input += SIZE_OF_LONG; + break; + } + + boolean hasChecksum = (frameHeaderDescriptor & 0b100) != 0; + + return new FrameHeader( + input - inputAddress, + windowSize, + contentSize, + dictionaryId, + hasChecksum); + } + + public static long getDecompressedSize(final Object inputBase, final long inputAddress, final long inputLimit) + { + long input = inputAddress; + input += verifyMagic(inputBase, input, inputLimit); + return readFrameHeader(inputBase, input, inputLimit).contentSize; + } + + private static int verifyMagic(Object inputBase, long inputAddress, long inputLimit) + { + verify(inputLimit - inputAddress >= 4, inputAddress, "Not enough input bytes"); + + int magic = UNSAFE.getInt(inputBase, inputAddress); + if (magic != MAGIC_NUMBER) { + throw new MalformedInputException(inputAddress, "Invalid magic prefix: " + Integer.toHexString(magic)); + } + + return SIZE_OF_INT; + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdInputStream.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdInputStream.java new file mode 100644 index 00000000..1f3e94d3 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdInputStream.java @@ -0,0 +1,179 @@ +package com.github.luben.zstd; + +import java.io.InputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.lang.IndexOutOfBoundsException; + +import com.github.luben.zstd.util.Native; +import com.github.luben.zstd.Zstd; + +/** + * InputStream filter that decompresses the data provided + * by the underlying InputStream using Zstd compression. + * + * It does not support mark/reset methods + * + */ + +public class ZstdInputStream extends FilterInputStream { + + static { + Native.load(); + } + + // Opaque pointer to Zstd context object + private long stream; + private long dstPos = 0; + private long srcPos = 0; + private long srcSize = 0; + private byte[] src = null; + private static final int srcBuffSize = (int) recommendedDInSize(); + + private boolean isContinuous = false; + private boolean frameFinished = false; + private boolean isClosed = false; + + /* JNI methods */ + private static native long recommendedDInSize(); + private static native long recommendedDOutSize(); + private static native long createDStream(); + private static native int freeDStream(long stream); + private native int initDStream(long stream); + private native int decompressStream(long stream, byte[] dst, int dst_size, byte[] src, int src_size); + + // The main constuctor / legacy version dispatcher + public ZstdInputStream(InputStream inStream) throws IOException { + // FilterInputStream constructor + super(inStream); + + // allocate input buffer with max frame header size + src = new byte[srcBuffSize]; + if (src == null) { + throw new IOException("Error allocating the input buffer of size " + srcBuffSize); + } + stream = createDStream(); + int size = initDStream(stream); + if (Zstd.isError(size)) { + throw new IOException("Decompression error: " + Zstd.getErrorName(size)); + } + } + + /** + * Don't break on unfinished frames + * + * Use case: decompressing files that are not + * yet finished writing and compressing + */ + public ZstdInputStream setContinuous(boolean b) { + isContinuous = b; + return this; + } + + public boolean getContinuous() { + return this.isContinuous; + } + + public int read(byte[] dst, int offset, int len) throws IOException { + + if (isClosed) { + throw new IOException("Stream closed"); + } + + // guard agains buffer overflows + if (offset < 0 || len > dst.length - offset) { + throw new IndexOutOfBoundsException("Requested lenght " + len + + " from offset " + offset + " in buffer of size " + dst.length); + } + int dstSize = offset + len; + dstPos = offset; + + while (dstPos < dstSize) { + if (srcSize - srcPos == 0) { + srcSize = in.read(src, 0, srcBuffSize); + srcPos = 0; + if (srcSize < 0) { + srcSize = 0; + if (frameFinished) { + return -1; + } else if (isContinuous) { + return (int)(dstPos - offset); + } else { + throw new IOException("Read error or truncated source"); + } + } + frameFinished = false; + } + + int size = decompressStream(stream, dst, dstSize, src, (int) srcSize); + + if (Zstd.isError(size)) { + throw new IOException("Decompression error: " + Zstd.getErrorName(size)); + } + + // we have completed a frame + if (size == 0) { + frameFinished = true; + // re-init the codec so it can start decoding next frame + size = initDStream(stream); + if (Zstd.isError(size)) { + throw new IOException("Decompression error: " + Zstd.getErrorName(size)); + } + return (int)(dstPos - offset); + } + } + return len; + } + + public int read() throws IOException { + byte[] oneByte = new byte[1]; + int result = read(oneByte, 0, 1); + if (result > 0) { + return oneByte[0] & 0xff; + } else { + return result; + } + } + + public int available() throws IOException { + if (isClosed) { + throw new IOException("Stream closed"); + } + if (srcSize - srcPos > 0) { + return 1; + } else { + return 0; + } + } + + /* we don't support mark/reset */ + public boolean markSupported() { + return false; + } + + /* we can skip forward */ + public long skip(long toSkip) throws IOException { + if (isClosed) { + throw new IOException("Stream closed"); + } + long skipped = 0; + int dstSize = (int) recommendedDOutSize(); + byte[] dst = new byte[dstSize]; + while (toSkip > dstSize) { + long size = read(dst, 0, dstSize); + toSkip -= size; + skipped += size; + } + skipped += read(dst, 0, (int) toSkip); + return skipped; + } + + public void close() throws IOException { + if (isClosed) { + return; + } + freeDStream(stream); + in.close(); + isClosed = true; + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java b/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java index 57aa4f0a..d192f7d9 100644 --- a/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java +++ b/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java @@ -56,8 +56,8 @@ public class AngleMask extends SolidBlockMask implements ResettableMask { try { int rx = x - cacheBotX; int rz = z - cacheBotZ; - int index = rx + (rz << 8); - if (index < 0 || index >= 65536) { + int index; + if (((rx + 16) & 0xFF) != rx + 16 || ((rz + 16) & 0xFF) != rz + 16) { cacheBotX = x - 16; cacheBotZ = z - 16; rx = x - cacheBotX; @@ -68,6 +68,8 @@ public class AngleMask extends SolidBlockMask implements ResettableMask { } else { Arrays.fill(cacheHeights, (byte) 0); } + } else { + index = rx + (rz << 8); } int result = cacheHeights[index] & 0xFF; if (result == 0) { diff --git a/core/src/main/java/com/boydti/fawe/object/schematic/FaweFormat.java b/core/src/main/java/com/boydti/fawe/object/schematic/FaweFormat.java index c9ad20f5..eda2f585 100644 --- a/core/src/main/java/com/boydti/fawe/object/schematic/FaweFormat.java +++ b/core/src/main/java/com/boydti/fawe/object/schematic/FaweFormat.java @@ -16,7 +16,6 @@ import com.sk89q.jnbt.ListTag; import com.sk89q.jnbt.NamedTag; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; -import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.MutableBlockVector; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEditException; @@ -272,7 +271,7 @@ public class FaweFormat implements ClipboardReader, ClipboardWriter { out.writeInt(origin.getBlockZ()); for (Vector pt : clipboard.getRegion()) { BaseBlock block = clipboard.getBlock(pt); - if (block == EditSession.nullBlock) { + if (block.getId() == 0) { continue; } int x = pt.getBlockX() - min.getBlockX(); @@ -321,7 +320,7 @@ public class FaweFormat implements ClipboardReader, ClipboardWriter { for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { mutable.mutZ(z); BaseBlock block = clipboard.getBlock(mutable); - if (block == EditSession.nullBlock) { + if (block.getId() == 0) { out.writeShort((short) 0); } else { out.writeShort((short) FaweCache.getCombined(block)); diff --git a/core/src/main/java/com/boydti/fawe/object/schematic/PNGWriter.java b/core/src/main/java/com/boydti/fawe/object/schematic/PNGWriter.java index db7fa478..e7cf5f9a 100644 --- a/core/src/main/java/com/boydti/fawe/object/schematic/PNGWriter.java +++ b/core/src/main/java/com/boydti/fawe/object/schematic/PNGWriter.java @@ -108,7 +108,7 @@ public class PNGWriter implements ClipboardWriter { for (int y = y0; y < y0 + height; y++) { mutable.mutY(y); BaseBlock block = clipboard.getBlock(mutable); - if (block == EditSession.nullBlock) { + if (block.getId() == 0) { continue; } mutableTop.mutY(y + 1); diff --git a/core/src/main/java/com/boydti/fawe/object/schematic/Schematic.java b/core/src/main/java/com/boydti/fawe/object/schematic/Schematic.java index fc647f7f..b1ba505b 100644 --- a/core/src/main/java/com/boydti/fawe/object/schematic/Schematic.java +++ b/core/src/main/java/com/boydti/fawe/object/schematic/Schematic.java @@ -53,7 +53,7 @@ public class Schematic { */ public Schematic(Region region) { checkNotNull(region); - checkNotNull(region.getWorld()); + checkNotNull(region.getWorld(), "World cannot be null (use the other constructor for the region)"); EditSession session = new EditSessionBuilder(region.getWorld()).allowedRegionsEverywhere().autoQueue(false).build(); this.clipboard = new BlockArrayClipboard(region, ReadOnlyClipboard.of(session, region)); } @@ -206,7 +206,7 @@ public class Schematic { @Override public boolean apply(Vector mutable) throws WorldEditException { BaseBlock block = clipboard.getBlock(mutable); - if (block == EditSession.nullBlock && !pasteAir) { + if (block.getId() == 0 && !pasteAir) { return false; } extent.setBlock(mutable.getBlockX() + relx, mutable.getBlockY() + rely, mutable.getBlockZ() + relz, block); diff --git a/core/src/main/java/com/boydti/fawe/regions/FaweMask.java b/core/src/main/java/com/boydti/fawe/regions/FaweMask.java index 2982fdbc..65a2c991 100644 --- a/core/src/main/java/com/boydti/fawe/regions/FaweMask.java +++ b/core/src/main/java/com/boydti/fawe/regions/FaweMask.java @@ -58,8 +58,6 @@ public class FaweMask { return false; } - ; - public BlockVector[] getBounds() { final BlockVector[] BlockVectors = {this.position1, this.position2}; return BlockVectors; diff --git a/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java b/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java index 8f582838..01089079 100644 --- a/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java +++ b/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java @@ -611,7 +611,7 @@ public class CreateFromImage extends Command { } if (arg.startsWith("file://")) { arg = arg.substring(7); - File file = MainUtil.getFile(Fawe.imp().getDirectory(), com.boydti.fawe.config.Settings.IMP.PATHS.HEIGHTMAP + File.separator + arg); + File file = MainUtil.getFile(MainUtil.getFile(Fawe.imp().getDirectory(), com.boydti.fawe.config.Settings.IMP.PATHS.HEIGHTMAP), arg); return MainUtil.toRGB(ImageIO.read(file)); } return null; diff --git a/core/src/main/java/com/boydti/fawe/util/DocumentationPrinter.java b/core/src/main/java/com/boydti/fawe/util/DocumentationPrinter.java index aa793b97..8883bf14 100644 --- a/core/src/main/java/com/boydti/fawe/util/DocumentationPrinter.java +++ b/core/src/main/java/com/boydti/fawe/util/DocumentationPrinter.java @@ -116,9 +116,9 @@ public final class DocumentationPrinter { writePermissionsWikiTable(stream, builder, "/", BrushOptionsCommands.class); writePermissionsWikiTable(stream, builder, "/tool ", ToolCommands.class); writePermissionsWikiTable(stream, builder, "/brush ", BrushCommands.class); - writePermissionsWikiTable(stream, builder, "/masks ", MaskCommands.class); - writePermissionsWikiTable(stream, builder, "/patterns ", PatternCommands.class); - writePermissionsWikiTable(stream, builder, "/transforms ", TransformCommands.class); + writePermissionsWikiTable(stream, builder, "", MaskCommands.class); + writePermissionsWikiTable(stream, builder, "", PatternCommands.class); + writePermissionsWikiTable(stream, builder, "", TransformCommands.class); stream.println(); stream.print("#### Uncategorized\n"); stream.append("| Aliases | Permission | flags | Usage |\n"); @@ -151,10 +151,14 @@ public final class DocumentationPrinter { stream.append("\n"); Command cmd = cls.getAnnotation(Command.class); if (cmd != null) { - stream.append(" (" + (cmd.help().isEmpty() ? cmd.desc() : cmd.help()) + ")"); + if (!cmd.desc().isEmpty()) { + stream.append("> (" + (cmd.desc()) + ") \n"); + } + if (!cmd.help().isEmpty()) { + stream.append("" + (cmd.help()) + " \n"); + } } stream.append("\n"); - stream.append("\n"); stream.append("---"); stream.append("\n"); stream.append("\n"); diff --git a/core/src/main/java/com/boydti/fawe/util/MainUtil.java b/core/src/main/java/com/boydti/fawe/util/MainUtil.java index 5dc251bb..f72481de 100644 --- a/core/src/main/java/com/boydti/fawe/util/MainUtil.java +++ b/core/src/main/java/com/boydti/fawe/util/MainUtil.java @@ -43,6 +43,7 @@ import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLClassLoader; import java.net.URLConnection; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; @@ -469,6 +470,25 @@ public class MainUtil { } } + private static final Class[] parameters = new Class[]{URL.class}; + + public static void loadURLClasspath(URL u) throws IOException { + + URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); + Class sysclass = URLClassLoader.class; + + try { + Method method = sysclass.getDeclaredMethod("addURL", parameters); + method.setAccessible(true); + method.invoke(sysloader, new Object[]{u}); + } catch (Throwable t) { + t.printStackTrace(); + throw new IOException("Error, could not add URL to system classloader"); + } + + } + + public static File getJarFile() { try { return getJarFile(Fawe.class); @@ -907,7 +927,7 @@ public class MainUtil { long age = now - file.lastModified(); if (age > timeDiff) { file.delete(); - BBC.SCHEMATIC_DELETE.send(null, file); + BBC.FILE_DELETED.send(null, file); } } }); @@ -923,7 +943,7 @@ public class MainUtil { deleteDirectory(files[i]); } else { file.delete(); - BBC.SCHEMATIC_DELETE.send(null, file); + BBC.FILE_DELETED.send(null, file); } } } diff --git a/core/src/main/java/com/boydti/fawe/util/MemUtil.java b/core/src/main/java/com/boydti/fawe/util/MemUtil.java index 5550bf2a..4aa7f465 100644 --- a/core/src/main/java/com/boydti/fawe/util/MemUtil.java +++ b/core/src/main/java/com/boydti/fawe/util/MemUtil.java @@ -27,6 +27,15 @@ public class MemUtil { return false; } + public static long getUsedBytes() { + long used = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + return used; + } + + public static long getFreeBytes() { + return Runtime.getRuntime().maxMemory() - getUsedBytes(); + } + public static int calculateMemory() { final long heapSize = Runtime.getRuntime().totalMemory(); final long heapMaxSize = Runtime.getRuntime().maxMemory(); diff --git a/core/src/main/java/com/boydti/fawe/util/SetQueue.java b/core/src/main/java/com/boydti/fawe/util/SetQueue.java index 8bddd1c0..5d4c0968 100644 --- a/core/src/main/java/com/boydti/fawe/util/SetQueue.java +++ b/core/src/main/java/com/boydti/fawe/util/SetQueue.java @@ -74,6 +74,7 @@ public class SetQueue { tasks = new ConcurrentLinkedDeque<>(); activeQueues = new ConcurrentLinkedDeque(); inactiveQueues = new ConcurrentLinkedDeque<>(); + if (TaskManager.IMP == null) return; TaskManager.IMP.repeat(new Runnable() { @Override public void run() { @@ -236,6 +237,13 @@ public class SetQueue { } public void flush(FaweQueue queue) { + int parallelThreads; + if (Fawe.get().isMainThread()) { + parallelThreads = Settings.IMP.QUEUE.PARALLEL_THREADS; + Settings.IMP.QUEUE.PARALLEL_THREADS = 1; + } else { + parallelThreads = 0; + } try { queue.startSet(Settings.IMP.QUEUE.PARALLEL_THREADS > 1); queue.next(Settings.IMP.QUEUE.PARALLEL_THREADS, Long.MAX_VALUE); @@ -247,6 +255,9 @@ public class SetQueue { queue.endSet(Settings.IMP.QUEUE.PARALLEL_THREADS > 1); queue.setStage(QueueStage.NONE); queue.runTasks(); + if (parallelThreads != 0) { + Settings.IMP.QUEUE.PARALLEL_THREADS = parallelThreads; + } } } diff --git a/core/src/main/java/com/boydti/fawe/util/StringMan.java b/core/src/main/java/com/boydti/fawe/util/StringMan.java index e58132f2..bf7b52cb 100644 --- a/core/src/main/java/com/boydti/fawe/util/StringMan.java +++ b/core/src/main/java/com/boydti/fawe/util/StringMan.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.function.Function; public class StringMan { public static String replaceFromMap(final String string, final Map replacements) { @@ -32,6 +33,25 @@ public class StringMan { return sb.toString(); } + public static String removeFromSet(final String string, final Collection replacements) { + final StringBuilder sb = new StringBuilder(string); + int size = string.length(); + for (final String key : replacements) { + if (size == 0) { + break; + } + int start = sb.indexOf(key, 0); + while (start > -1) { + final int end = start + key.length(); + final int nextSearchStart = start + 0; + sb.delete(start, end); + size -= end - start; + start = sb.indexOf(key, nextSearchStart); + } + } + return sb.toString(); + } + public static int indexOf(String input, int start, char... values) { for (int i = start; i < input.length(); i++) { for (char c : values) { @@ -262,6 +282,19 @@ public class StringMan { return p[n]; } + public static String join(Collection arr, final String delimiter, Function funx) { + final StringBuilder result = new StringBuilder(); + int i = 0; + for (T obj : arr) { + if (i > 0) { + result.append(delimiter); + } + result.append(funx.apply(obj)); + i++; + } + return result.toString(); + } + public static String join(final Object[] array, final String delimiter) { final StringBuilder result = new StringBuilder(); for (int i = 0, j = array.length; i < j; i++) { diff --git a/core/src/main/java/com/boydti/fawe/util/TextureUtil.java b/core/src/main/java/com/boydti/fawe/util/TextureUtil.java index 787d01c1..a78c0e4c 100644 --- a/core/src/main/java/com/boydti/fawe/util/TextureUtil.java +++ b/core/src/main/java/com/boydti/fawe/util/TextureUtil.java @@ -453,6 +453,7 @@ public class TextureUtil { } public void loadModTextures() throws IOException { + BundledBlockData.getInstance().loadFromResource(); Int2ObjectOpenHashMap colorMap = new Int2ObjectOpenHashMap<>(); Int2ObjectOpenHashMap distanceMap = new Int2ObjectOpenHashMap<>(); Gson gson = new Gson(); diff --git a/core/src/main/java/com/boydti/fawe/util/chat/ChatManager.java b/core/src/main/java/com/boydti/fawe/util/chat/ChatManager.java new file mode 100644 index 00000000..de847001 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/util/chat/ChatManager.java @@ -0,0 +1,21 @@ +package com.boydti.fawe.util.chat; + +import com.boydti.fawe.object.FawePlayer; + +public interface ChatManager { + T builder(); + + void color(Message message, String color); + + void tooltip(Message message, Message... tooltip); + + void command(Message message, String command); + + void text(Message message, String text); + + void send(Message message, FawePlayer player); + + void suggest(Message message, String command); + + void link(Message message, String url); +} diff --git a/core/src/main/java/com/boydti/fawe/util/chat/Message.java b/core/src/main/java/com/boydti/fawe/util/chat/Message.java new file mode 100644 index 00000000..b50db7e8 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/util/chat/Message.java @@ -0,0 +1,130 @@ +package com.boydti.fawe.util.chat; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FawePlayer; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.Actor; +import java.util.Objects; + +public class Message { + + private Object builder; + private boolean active; + + public Message() { + try { + reset(Fawe.get().getChatManager()); + } catch (Throwable e) { + Fawe.debug("Doesn't support fancy chat for " + Fawe.imp().getPlatform()); + Fawe.get().setChatManager(new PlainChatManager()); + reset(Fawe.get().getChatManager()); + } + active = !(Fawe.get().getChatManager() instanceof PlainChatManager); + } + + public Message(BBC caption, Object... args) { + this(BBC.getPrefix() + caption.format(args)); + } + + public Message(String text) { + this(); + text(text); + } + + public T $(ChatManager manager) { + return (T) this.builder; + } + + public T reset(ChatManager manager) { + return (T) (this.builder = manager.builder()); + } + + public Message activeText(String text) { + if (active) { + text(text); + } + return this; + } + + public Message text(BBC caption, Object... args) { + return text(caption.format(args)); + } + + public Message text(Object text) { + Fawe.get().getChatManager().text(this, BBC.color(Objects.toString(text))); + return this; + } + + public Message link(String text) { + Fawe.get().getChatManager().link(this, text); + return this; + } + + public Message tooltip(Message... tooltip) { + Fawe.get().getChatManager().tooltip(this, tooltip); + return this; + } + + public Message tooltip(String tooltip) { + return tooltip(new Message(tooltip)); + } + + public Message command(String command) { + Fawe.get().getChatManager().command(this, (WorldEdit.getInstance().getConfiguration().noDoubleSlash ? "" : "/") + command); + return this; + } + + public Message prefix() { + return text(BBC.getPrefix()); + } + + public Message newline() { + return text("\n"); + } + + public Message cmdTip(String commandAndTooltip) { + return tooltip(commandAndTooltip).command(commandAndTooltip); + } + + public Message suggestTip(String commandAndTooltip) { + return tooltip(commandAndTooltip).suggest(commandAndTooltip); + } + + public Message suggest(String command) { + Fawe.get().getChatManager().suggest(this, command); + return this; + } + + public Message color(String color) { + Fawe.get().getChatManager().color(this, BBC.color(color)); + return this; + } + + public void send(Actor player) { + send(FawePlayer.wrap(player)); + } + + public void send(FawePlayer player) { + Fawe.get().getChatManager().send(this, player); + } + + public Message paginate(String baseCommand, int page, int totalPages) { + if (!active) { + return text(BBC.PAGE_FOOTER.f(baseCommand, page + 1)); + } + if (page < totalPages && page > 1) { // Back | Next + this.text("&f<<").command(baseCommand + " " + (page - 1)).text("&8 | ").text("&f>>") + .command(baseCommand + " " + (page + 1)); + } else if (page <= 1 && totalPages > page) { // Next + this.text("&8 -").text(" | ").text("&f>>") + .command(baseCommand + " " + (page + 1)); + + } else if (page == totalPages && totalPages > 1) { // Back + this.text("&f<<").command(baseCommand + " " + (page - 1)).text("&8 | ").text("- "); + } else { + this.text("&8 - | - "); + } + return this; + } +} diff --git a/core/src/main/java/com/boydti/fawe/util/chat/PlainChatManager.java b/core/src/main/java/com/boydti/fawe/util/chat/PlainChatManager.java new file mode 100644 index 00000000..d39fd222 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/util/chat/PlainChatManager.java @@ -0,0 +1,47 @@ +package com.boydti.fawe.util.chat; + + +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FawePlayer; +import java.util.ArrayList; +import java.util.List; + +public class PlainChatManager implements ChatManager> { + + @Override + public List builder() { + return new ArrayList<>(); + } + + @Override + public void color(Message message, String color) { + List parts = message.$(this); + parts.get(parts.size() - 1).insert(0, color); + } + + @Override + public void tooltip(Message message, Message... tooltips) {} + + @Override + public void command(Message message, String command) {} + + @Override + public void text(Message message, String text) { + message.$(this).add(new StringBuilder(BBC.color(text))); + } + + @Override + public void send(Message plotMessage, FawePlayer player) { + StringBuilder built = new StringBuilder(); + for (StringBuilder sb : plotMessage.$(this)) { + built.append(sb); + } + player.sendMessage(built.toString()); + } + + @Override + public void suggest(Message plotMessage, String command) {} + + @Override + public void link(Message message, String url) {} +} \ No newline at end of file diff --git a/core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java b/core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java index 6a416d3f..0146b767 100644 --- a/core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java +++ b/core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java @@ -19,8 +19,11 @@ package com.sk89q.jnbt; +import com.boydti.fawe.object.io.LittleEndianOutputStream; import java.io.Closeable; +import java.io.DataOutput; import java.io.DataOutputStream; +import java.io.Flushable; import java.io.IOException; import java.io.OutputStream; import java.util.List; @@ -42,7 +45,7 @@ public final class NBTOutputStream implements Closeable { /** * The output stream. */ - private final DataOutputStream os; + private DataOutput os; /** * Creates a new {@code NBTOutputStream}, which will write data to the @@ -55,10 +58,20 @@ public final class NBTOutputStream implements Closeable { this.os = new DataOutputStream(os); } - public DataOutputStream getOutputStream() { + public NBTOutputStream(DataOutput os) throws IOException { + this.os = os; + } + + public DataOutput getOutputStream() { return os; } + public void setLittleEndian() { + if (!(os instanceof LittleEndianOutputStream)) { + this.os = new LittleEndianOutputStream((OutputStream) os); + } + } + /** * Writes a tag. * @@ -363,7 +376,7 @@ public final class NBTOutputStream implements Closeable { @Override public void close() throws IOException { - os.close(); + if (os instanceof Closeable) ((Closeable) os).close(); } /** @@ -372,7 +385,7 @@ public final class NBTOutputStream implements Closeable { * @throws IOException */ public void flush() throws IOException { - this.os.flush(); + if (os instanceof Flushable) ((Flushable) os).flush(); } public static Class inject() { diff --git a/core/src/main/java/com/sk89q/worldedit/EditSession.java b/core/src/main/java/com/sk89q/worldedit/EditSession.java index 98820bfd..726c2b69 100644 --- a/core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -198,7 +198,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting public static final UUID CONSOLE = UUID.fromString("1-1-3-3-7"); public static final BaseBiome nullBiome = new BaseBiome(0); - public static final BaseBlock nullBlock = FaweCache.CACHE_BLOCK[0]; + public static final BaseBlock nullBlock = new BaseBlock(0, 0); private static final Vector[] recurseDirections = { PlayerDirection.NORTH.vector(), PlayerDirection.EAST.vector(), @@ -565,10 +565,10 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting Fawe.debug("&8 - &7For area restrictions, it is recommended to use the FaweAPI"); Fawe.debug("&8 - &7For block logging, it is recommended to use use BlocksHub"); Fawe.debug("&8 - &7To allow this plugin add it to the FAWE `allowed-plugins` list"); - Fawe.debug("&8 - &7To hide this message set `debug` to false in the config.yml"); + Fawe.debug("&8 - &7To hide this message set `debug` to false in the FAWE config.yml"); if (toReturn.getClass().getName().contains("CoreProtect")) { Fawe.debug("Note on CoreProtect: "); - Fawe.debug(" - If you disable CoreProtect's WorldEdit logger (CP config) it still tries to add it (CP bug?)"); + Fawe.debug(" - If you disable CP's WE logger (CP config) and this still shows, please update CP"); Fawe.debug(" - Use BlocksHub and set `debug` false in the FAWE config"); } } @@ -1148,7 +1148,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting } @Override - public boolean setBlock(final Vector position, final BaseBlock block, final boolean ignorePhysics) throws MaxChangedBlocksException { + public boolean setBlock(final Vector position, final BaseBlock block, final boolean ignorePhysics) { return setBlockFast(position, block); } @@ -1162,7 +1162,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting } @SuppressWarnings("deprecation") - public boolean setBlock(final Vector position, final Pattern pattern) throws MaxChangedBlocksException { + public boolean setBlock(final Vector position, final Pattern pattern) { this.changes++; try { return pattern.apply(this.extent, position, position); @@ -1172,7 +1172,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting } @SuppressWarnings("deprecation") - public int setBlocks(final Set vset, final Pattern pattern) throws MaxChangedBlocksException { + public int setBlocks(final Set vset, final Pattern pattern) { RegionVisitor visitor = new RegionVisitor(vset, new BlockReplace(extent, pattern), this); Operations.completeBlindly(visitor); changes += visitor.getAffected(); @@ -1189,7 +1189,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return whether a block was changed * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public boolean setChanceBlockIfAir(final Vector position, final BaseBlock block, final double probability) throws MaxChangedBlocksException { + public boolean setChanceBlockIfAir(final Vector position, final BaseBlock block, final double probability) { return (FaweCache.RANDOM.random(65536) <= (probability * 65536)) && this.setBlockIfAir(position, block); } @@ -1203,7 +1203,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @deprecated Use your own method */ @Deprecated - public boolean setBlockIfAir(final Vector position, final BaseBlock block) throws MaxChangedBlocksException { + public boolean setBlockIfAir(final Vector position, final BaseBlock block) { return this.getBlock(position).isAir() && this.setBlockFast(position, block); } @@ -1450,7 +1450,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int fillXZ(final Vector origin, final BaseBlock block, final double radius, final int depth, final boolean recursive) throws MaxChangedBlocksException { + public int fillXZ(final Vector origin, final BaseBlock block, final double radius, final int depth, final boolean recursive) { return this.fillXZ(origin, (Pattern) block, radius, depth, recursive); } @@ -1466,7 +1466,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int fillXZ(final Vector origin, final Pattern pattern, final double radius, final int depth, final boolean recursive) throws MaxChangedBlocksException { + public int fillXZ(final Vector origin, final Pattern pattern, final double radius, final int depth, final boolean recursive) { checkNotNull(origin); checkNotNull(pattern); checkArgument(radius >= 0, "radius >= 0"); @@ -1494,7 +1494,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting return this.changes = visitor.getAffected(); } -// public int fillDirection(final Vector origin, PlayerDirection direction, final Pattern pattern, final double radius, final int depth, final boolean recursive) throws MaxChangedBlocksException { +// public int fillDirection(final Vector origin, PlayerDirection direction, final Pattern pattern, final double radius, final int depth, final boolean recursive) { // checkNotNull(origin); // checkNotNull(pattern); // checkArgument(radius >= 0, "radius >= 0"); @@ -1562,7 +1562,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int removeAbove(final Vector position, final int apothem, final int height) throws MaxChangedBlocksException { + public int removeAbove(final Vector position, final int apothem, final int height) { checkNotNull(position); checkArgument(apothem >= 1, "apothem >= 1"); checkArgument(height >= 1, "height >= 1"); @@ -1583,7 +1583,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int removeBelow(final Vector position, final int apothem, final int height) throws MaxChangedBlocksException { + public int removeBelow(final Vector position, final int apothem, final int height) { checkNotNull(position); checkArgument(apothem >= 1, "apothem >= 1"); checkArgument(height >= 1, "height >= 1"); @@ -1604,7 +1604,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int removeNear(final Vector position, final int blockType, final int apothem) throws MaxChangedBlocksException { + public int removeNear(final Vector position, final int blockType, final int apothem) { checkNotNull(position); checkArgument(apothem >= 1, "apothem >= 1"); @@ -1656,7 +1656,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int setBlocks(final Region region, final BaseBlock block) throws MaxChangedBlocksException { + public int setBlocks(final Region region, final BaseBlock block) { checkNotNull(region); checkNotNull(block); if (canBypassAll(region, false, true) && !block.hasNbtData()) { @@ -1675,8 +1675,6 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting } } } - } catch (final MaxChangedBlocksException e) { - throw e; } catch (final WorldEditException e) { throw new RuntimeException("Unexpected exception", e); } @@ -1692,7 +1690,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int setBlocks(final Region region, final Pattern pattern) throws MaxChangedBlocksException { + public int setBlocks(final Region region, final Pattern pattern) { checkNotNull(region); checkNotNull(pattern); if (pattern instanceof BlockPattern) { @@ -1718,7 +1716,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int replaceBlocks(final Region region, final Set filter, final BaseBlock replacement) throws MaxChangedBlocksException { + public int replaceBlocks(final Region region, final Set filter, final BaseBlock replacement) { // if (canBypassAll()) { // queue.replaceBlocks(regionWrapper, blocks, block); // return changes = region.getArea(); @@ -1739,7 +1737,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int replaceBlocks(final Region region, final Set filter, final Pattern pattern) throws MaxChangedBlocksException { + public int replaceBlocks(final Region region, final Set filter, final Pattern pattern) { // if (pattern instanceof BaseBlock) { // return replaceBlocks(region, filter, ((BaseBlock) pattern)); // } @@ -1747,7 +1745,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting return this.replaceBlocks(region, mask, pattern); } -// public int replaceBlocks(final Region region, final Mask mask, final BaseBlock block) throws MaxChangedBlocksException { +// public int replaceBlocks(final Region region, final Mask mask, final BaseBlock block) { // TODO fast replace // } @@ -1762,7 +1760,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int replaceBlocks(final Region region, final Mask mask, final Pattern pattern) throws MaxChangedBlocksException { + public int replaceBlocks(final Region region, final Mask mask, final Pattern pattern) { checkNotNull(region); checkNotNull(mask); checkNotNull(pattern); @@ -1784,7 +1782,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int center(final Region region, final Pattern pattern) throws MaxChangedBlocksException { + public int center(final Region region, final Pattern pattern) { checkNotNull(region); checkNotNull(pattern); @@ -1803,7 +1801,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int makeCuboidFaces(final Region region, final BaseBlock block) throws MaxChangedBlocksException { + public int makeCuboidFaces(final Region region, final BaseBlock block) { return this.makeCuboidFaces(region, (Pattern) (block)); } @@ -1816,7 +1814,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int makeCuboidFaces(final Region region, final Pattern pattern) throws MaxChangedBlocksException { + public int makeCuboidFaces(final Region region, final Pattern pattern) { checkNotNull(region); checkNotNull(pattern); @@ -1857,7 +1855,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int makeCuboidWalls(final Region region, final BaseBlock block) throws MaxChangedBlocksException { + public int makeCuboidWalls(final Region region, final BaseBlock block) { return this.makeCuboidWalls(region, (Pattern) (block)); } @@ -1871,7 +1869,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int makeCuboidWalls(final Region region, final Pattern pattern) throws MaxChangedBlocksException { + public int makeCuboidWalls(final Region region, final Pattern pattern) { checkNotNull(region); checkNotNull(pattern); @@ -1925,7 +1923,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int overlayCuboidBlocks(final Region region, final BaseBlock block) throws MaxChangedBlocksException { + public int overlayCuboidBlocks(final Region region, final BaseBlock block) { checkNotNull(block); return this.overlayCuboidBlocks(region, (Pattern) (block)); } @@ -1940,7 +1938,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @throws MaxChangedBlocksException thrown if too many blocks are changed */ @SuppressWarnings("deprecation") - public int overlayCuboidBlocks(final Region region, final Pattern pattern) throws MaxChangedBlocksException { + public int overlayCuboidBlocks(final Region region, final Pattern pattern) { checkNotNull(region); checkNotNull(pattern); final BlockReplace replace = new BlockReplace(EditSession.this, pattern); @@ -1961,7 +1959,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks affected * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int naturalizeCuboidBlocks(final Region region) throws MaxChangedBlocksException { + public int naturalizeCuboidBlocks(final Region region) { checkNotNull(region); final Naturalizer naturalizer = new Naturalizer(EditSession.this); final FlatRegion flatRegion = Regions.asFlatRegion(region); @@ -1980,7 +1978,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks affected * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int stackCuboidRegion(final Region region, final Vector dir, final int count, final boolean copyAir) throws MaxChangedBlocksException { + public int stackCuboidRegion(final Region region, final Vector dir, final int count, final boolean copyAir) { checkNotNull(region); checkNotNull(dir); checkArgument(count >= 1, "count >= 1 required"); @@ -2013,7 +2011,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks moved * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int moveRegion(final Region region, final Vector dir, final int distance, final boolean copyAir, final BaseBlock replacement) throws MaxChangedBlocksException { + public int moveRegion(final Region region, final Vector dir, final int distance, final boolean copyAir, final BaseBlock replacement) { checkNotNull(region); checkNotNull(dir); checkArgument(distance >= 1, "distance >= 1 required"); @@ -2067,7 +2065,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks moved * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int moveCuboidRegion(final Region region, final Vector dir, final int distance, final boolean copyAir, final BaseBlock replacement) throws MaxChangedBlocksException { + public int moveCuboidRegion(final Region region, final Vector dir, final int distance, final boolean copyAir, final BaseBlock replacement) { return this.moveRegion(region, dir, distance, copyAir, replacement); } @@ -2079,7 +2077,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks affected * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int drainArea(final Vector origin, final double radius) throws MaxChangedBlocksException { + public int drainArea(final Vector origin, final double radius) { checkNotNull(origin); checkArgument(radius >= 0, "radius >= 0 required"); Mask liquidMask; @@ -2123,7 +2121,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks affected * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int fixLiquid(final Vector origin, final double radius, final int moving, final int stationary) throws MaxChangedBlocksException { + public int fixLiquid(final Vector origin, final double radius, final int moving, final int stationary) { checkNotNull(origin); checkArgument(radius >= 0, "radius >= 0 required"); // Our origins can only be liquids @@ -2183,7 +2181,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks changed * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int makeCylinder(final Vector pos, final Pattern block, final double radius, final int height, final boolean filled) throws MaxChangedBlocksException { + public int makeCylinder(final Vector pos, final Pattern block, final double radius, final int height, final boolean filled) { return this.makeCylinder(pos, block, radius, radius, height, filled); } @@ -2199,7 +2197,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks changed * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int makeCylinder(Vector pos, final Pattern block, double radiusX, double radiusZ, int height, final boolean filled) throws MaxChangedBlocksException { + public int makeCylinder(Vector pos, final Pattern block, double radiusX, double radiusZ, int height, final boolean filled) { radiusX += 0.5; radiusZ += 0.5; @@ -2265,7 +2263,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting return this.changes; } - public int makeCircle(Vector pos, final Pattern block, double radiusX, double radiusY, double radiusZ, boolean filled, Vector normal) throws MaxChangedBlocksException { + public int makeCircle(Vector pos, final Pattern block, double radiusX, double radiusY, double radiusZ, boolean filled, Vector normal) { radiusX += 0.5; radiusY += 0.5; radiusZ += 0.5; @@ -2362,7 +2360,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks changed * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int makeSphere(final Vector pos, final Pattern block, final double radius, final boolean filled) throws MaxChangedBlocksException { + public int makeSphere(final Vector pos, final Pattern block, final double radius, final boolean filled) { return this.makeSphere(pos, block, radius, radius, radius, filled); } @@ -2378,7 +2376,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks changed * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int makeSphere(final Vector pos, final Pattern block, double radiusX, double radiusY, double radiusZ, final boolean filled) throws MaxChangedBlocksException { + public int makeSphere(final Vector pos, final Pattern block, double radiusX, double radiusY, double radiusZ, final boolean filled) { radiusX += 0.5; radiusY += 0.5; radiusZ += 0.5; @@ -2457,7 +2455,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks changed * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int makePyramid(final Vector position, final Pattern block, int size, final boolean filled) throws MaxChangedBlocksException { + public int makePyramid(final Vector position, final Pattern block, int size, final boolean filled) { final int height = size; for (int y = 0; y <= height; ++y) { @@ -2485,7 +2483,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks affected * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int thaw(final Vector position, final double radius) throws MaxChangedBlocksException { + public int thaw(final Vector position, final double radius) { final double radiusSq = radius * radius; final int ox = position.getBlockX(); @@ -2536,7 +2534,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks affected * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int simulateSnow(final Vector position, final double radius) throws MaxChangedBlocksException { + public int simulateSnow(final Vector position, final double radius) { final double radiusSq = radius * radius; @@ -2598,7 +2596,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @deprecated Use {@link #green(Vector, double, boolean)}. */ @Deprecated - public int green(final Vector position, final double radius) throws MaxChangedBlocksException { + public int green(final Vector position, final double radius) { return this.green(position, radius, true); } @@ -2611,7 +2609,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks affected * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int green(final Vector position, final double radius, final boolean onlyNormalDirt) throws MaxChangedBlocksException { + public int green(final Vector position, final double radius, final boolean onlyNormalDirt) { final double radiusSq = radius * radius; @@ -2673,7 +2671,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of patches created * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int makePumpkinPatches(final Vector position, final int apothem) throws MaxChangedBlocksException { + public int makePumpkinPatches(final Vector position, final int apothem) { // We want to generate pumpkins final GardenPatchGenerator generator = new GardenPatchGenerator(EditSession.this); generator.setPlant(GardenPatchGenerator.getPumpkinPattern()); @@ -2938,7 +2936,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks affected * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int hollowOutRegion(final Region region, final int thickness, final Pattern pattern) throws MaxChangedBlocksException { + public int hollowOutRegion(final Region region, final int thickness, final Pattern pattern) { final Set outside = new LocalBlockVectorSet(); @@ -3010,7 +3008,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting return changes; } - public int drawLine(final Pattern pattern, final Vector pos1, final Vector pos2, final double radius, final boolean filled) throws MaxChangedBlocksException { + public int drawLine(final Pattern pattern, final Vector pos1, final Vector pos2, final double radius, final boolean filled) { return drawLine(pattern, pos1, pos2, radius, filled, false); } @@ -3025,7 +3023,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks affected * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int drawLine(final Pattern pattern, final Vector pos1, final Vector pos2, final double radius, final boolean filled, boolean flat) throws MaxChangedBlocksException { + public int drawLine(final Pattern pattern, final Vector pos1, final Vector pos2, final double radius, final boolean filled, boolean flat) { LocalBlockVectorSet vset = new LocalBlockVectorSet(); boolean notdrawn = true; @@ -3098,7 +3096,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks affected * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int drawSpline(final Pattern pattern, final List nodevectors, final double tension, final double bias, final double continuity, final double quality, final double radius, final boolean filled) throws MaxChangedBlocksException { + public int drawSpline(final Pattern pattern, final List nodevectors, final double tension, final double bias, final double continuity, final double quality, final double radius, final boolean filled) { LocalBlockVectorSet vset = new LocalBlockVectorSet(); final List nodes = new ArrayList(nodevectors.size()); @@ -3463,14 +3461,18 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting } } - public boolean generateTree(TreeGenerator.TreeType type, Vector position) throws MaxChangedBlocksException { + public boolean generateTree(TreeGenerator.TreeType type, Vector position) { return generateTree(type, this, position); } @Override - public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, Vector position) throws MaxChangedBlocksException { + public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, Vector position) { if (getWorld() != null) { - return getWorld().generateTree(type, editSession, position); + try { + return getWorld().generateTree(type, editSession, position); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } } return false; } diff --git a/core/src/main/java/com/sk89q/worldedit/blocks/BaseBlock.java b/core/src/main/java/com/sk89q/worldedit/blocks/BaseBlock.java index c3f58811..9fe94f31 100644 --- a/core/src/main/java/com/sk89q/worldedit/blocks/BaseBlock.java +++ b/core/src/main/java/com/sk89q/worldedit/blocks/BaseBlock.java @@ -33,6 +33,7 @@ import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.world.registry.WorldData; import java.io.DataInputStream; +import java.io.DataOutput; import java.io.IOException; import java.io.Serializable; import java.util.Collection; @@ -141,7 +142,7 @@ public class BaseBlock implements TileEntityBlock, Pattern, Serializable { this(other.getId(), other.getData(), other.getNbtData()); } - public final int getCombined() { + public int getCombined() { return FaweCache.getCombined(this); } @@ -456,7 +457,7 @@ public class BaseBlock implements TileEntityBlock, Pattern, Serializable { stream.writeChar(getCombined()); stream.writeBoolean(nbtData != null); if (nbtData != null) { - new NBTOutputStream(stream).writeTag(nbtData); + new NBTOutputStream((DataOutput) stream).writeTag(nbtData); } } diff --git a/core/src/main/java/com/sk89q/worldedit/blocks/ImmutableBlock.java b/core/src/main/java/com/sk89q/worldedit/blocks/ImmutableBlock.java index ba7206ca..720103f5 100644 --- a/core/src/main/java/com/sk89q/worldedit/blocks/ImmutableBlock.java +++ b/core/src/main/java/com/sk89q/worldedit/blocks/ImmutableBlock.java @@ -3,8 +3,16 @@ package com.sk89q.worldedit.blocks; import com.sk89q.worldedit.CuboidClipboard; public class ImmutableBlock extends BaseBlock { + private final int combined; + public ImmutableBlock(int id, int data) { super(id, data); + this.combined = super.getCombined(); + } + + @Override + public final int getCombined() { + return combined; } @Override diff --git a/core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java b/core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java index 895dde8e..0732699f 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java @@ -20,8 +20,10 @@ package com.sk89q.worldedit.command; import com.boydti.fawe.config.BBC; +import com.boydti.fawe.config.Commands; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.visitor.Fast2DIterator; +import com.boydti.fawe.util.chat.Message; import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandPermissions; @@ -57,16 +59,13 @@ import java.util.Collections; import java.util.List; -import static com.google.common.base.Preconditions.checkNotNull; import static com.sk89q.minecraft.util.commands.Logging.LogMode.REGION; /** * Implements biome-related commands such as "/biomelist". */ @Command(aliases = {}, desc = "Change, list and inspect biomes") -public class BiomeCommands { - - private final WorldEdit worldEdit; +public class BiomeCommands extends MethodCommands { /** * Create a new instance. @@ -74,8 +73,7 @@ public class BiomeCommands { * @param worldEdit reference to WorldEdit */ public BiomeCommands(WorldEdit worldEdit) { - checkNotNull(worldEdit); - this.worldEdit = worldEdit; + super(worldEdit); } @Command( @@ -94,28 +92,31 @@ public class BiomeCommands { page = 1; offset = 0; } else { - offset = (page - 1) * 19; + offset = (page - 1) * 18; } BiomeRegistry biomeRegistry = player.getWorld().getWorldData().getBiomeRegistry(); List biomes = biomeRegistry.getBiomes(); int totalPages = biomes.size() / 19 + 1; - BBC.BIOME_LIST_HEADER.send(player, page, totalPages); + Message msg = BBC.BIOME_LIST_HEADER.m(page, totalPages); + String setBiome = Commands.getAlias(BiomeCommands.class, "/setbiome"); for (BaseBiome biome : biomes) { if (offset > 0) { offset--; } else { BiomeData data = biomeRegistry.getData(biome); if (data != null) { - player.print(BBC.getPrefix() + " " + data.getName()); - if (++count == 19) { - break; - } + msg.newline().text(data.getName()).cmdTip(setBiome + " " + data.getName()); } else { - player.print(BBC.getPrefix() + " "); + msg.newline().text("").cmdTip(setBiome + " " + biome.getId()); + } + if (++count == 18) { + break; } } } + msg.newline().paginate(getCommand().aliases()[0], page, totalPages); + msg.send(player); } @Command( diff --git a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java index 77e26fc4..f08f8eaa 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -101,7 +101,7 @@ import java.util.List; /** * Commands to set brush shape. */ -@Command(aliases = {"brush", "br", "/b"}, +@Command(aliases = {"brush", "br", "tool"}, desc = "Commands to build and draw from far away. [More Info](https://git.io/vSPYf)" ) public class BrushCommands extends MethodCommands { @@ -669,7 +669,7 @@ public class BrushCommands extends MethodCommands { " - The `-r` flag enables random off-axis rotation\n" + " - The `-l` flag will work on snow layers\n" + " - The `-s` flag disables smoothing", - desc = "This brush raises and lowers land towards the clicked point\n", + desc = "This brush raises or lowers land towards the clicked point", min = 1, max = 4 ) diff --git a/core/src/main/java/com/sk89q/worldedit/command/BrushOptionsCommands.java b/core/src/main/java/com/sk89q/worldedit/command/BrushOptionsCommands.java index f24e16b4..98b61446 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/BrushOptionsCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/BrushOptionsCommands.java @@ -54,9 +54,10 @@ public class BrushOptionsCommands extends MethodCommands { } @Command( - aliases = {"/savebrush"}, + aliases = {"savebrush", "save"}, usage = "[name]", - desc = "Save your current brush\n" + + desc = "Save your current brush", + help = "Save your current brush\n" + "use the -g flag to save globally", min = 1 ) @@ -91,7 +92,7 @@ public class BrushOptionsCommands extends MethodCommands { } @Command( - aliases = {"/loadbrush"}, + aliases = {"loadbrush", "load"}, desc = "load a brush", usage = "[name]", min = 1 @@ -281,12 +282,16 @@ public class BrushOptionsCommands extends MethodCommands { @Command( aliases = {"visualize", "visual", "vis"}, - usage = "[mode]", + usage = "[mode=0]", desc = "Toggle between different visualization modes", + help = "Toggle between different visualization modes\n" + + "0 = No visualization\n" + + "1 = Single block at target position\n" + + "2 = Glass showing what blocks will be changed", min = 0, max = 1 ) - public void visual(Player player, LocalSession session, @Optional("0") int mode) throws WorldEditException { + public void visual(Player player, LocalSession session, int mode) throws WorldEditException { BrushTool tool = session.getBrushTool(player, false); if (tool == null) { BBC.BRUSH_NONE.send(player); diff --git a/core/src/main/java/com/sk89q/worldedit/command/MaskCommands.java b/core/src/main/java/com/sk89q/worldedit/command/MaskCommands.java index dda73a78..f662207a 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/MaskCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/MaskCommands.java @@ -46,7 +46,13 @@ import com.sk89q.worldedit.util.command.parametric.Optional; import com.sk89q.worldedit.world.biome.BaseBiome; @Command(aliases = {"masks"}, - desc = "Help for the various masks. [More Info](https://git.io/v9r4K)" + desc = "Help for the various masks. [More Info](https://git.io/v9r4K)", + help = "Masks determine if a block can be placed\n" + + " - Use [brackets] for arguments\n" + + " - Use , to OR multiple\n" + + " - Use & to AND multiple\n" + + "e.g. >[stone,dirt],#light[0][5],$jungle\n" + + "More Info: https://git.io/v9r4K" ) public class MaskCommands extends MethodCommands { public MaskCommands(WorldEdit worldEdit) { diff --git a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java index a457981e..b00ad7d7 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java @@ -19,8 +19,10 @@ package com.sk89q.worldedit.command; +import com.boydti.fawe.Fawe; import com.boydti.fawe.config.BBC; import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.clipboard.ClipboardRemapper; import com.boydti.fawe.object.clipboard.MultiClipboardHolder; import com.boydti.fawe.object.schematic.StructureFormat; import com.boydti.fawe.util.MainUtil; @@ -115,6 +117,23 @@ public class SchematicCommands { } } + @Command( + aliases = {"remap"}, + help = "Remap a clipboard between MCPE/PC values\n", + desc = "Remap a clipboard between MCPE/PC values\n" + ) + @Deprecated + @CommandPermissions({"worldedit.schematic.remap"}) + public void remap(final Player player, final LocalSession session) throws WorldEditException { + ClipboardHolder holder = session.getClipboard(); + Clipboard clipboard = holder.getClipboard(); + if (Fawe.imp().getPlatform().equalsIgnoreCase("nukkit")) { + new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE).apply(clipboard); + } else { + new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PE, ClipboardRemapper.RemapPlatform.PC).apply(clipboard); + } + } + @Command(aliases = {"load"}, usage = "[] ", desc = "Load a schematic into your clipboard") @Deprecated @CommandPermissions({"worldedit.clipboard.load", "worldedit.schematic.load", "worldedit.schematic.upload"}) diff --git a/core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java b/core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java index 08c99157..0d8c23e8 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java @@ -292,7 +292,7 @@ public class SelectionCommands { session.setToolControl(!session.isToolControlEnabled()); if (session.isToolControlEnabled()) { - BBC.SELECTION_WAND_ENABLE.send(player); + BBC.SELECTION_WAND_ENABLE.m().send(player); } else { BBC.SELECTION_WAND_DISABLE.send(player); } diff --git a/core/src/main/java/com/sk89q/worldedit/command/TransformCommands.java b/core/src/main/java/com/sk89q/worldedit/command/TransformCommands.java index abba660c..86b4ba68 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/TransformCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/TransformCommands.java @@ -22,7 +22,12 @@ import com.sk89q.worldedit.util.command.parametric.Optional; import java.util.Set; @Command(aliases = {"transforms"}, - desc = "Help for the various transforms. [More Info](https://git.io/v9KHO)" + desc = "Help for the various transforms. [More Info](https://git.io/v9KHO)", + help = "Transforms modify how a block is placed\n" + + " - Use [brackets] for arguments\n" + + " - Use , to OR multiple\n" + + " - Use & to AND multiple\n" + + "More Info: https://git.io/v9KHO" ) public class TransformCommands extends MethodCommands { public TransformCommands(WorldEdit worldEdit) { diff --git a/core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java b/core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java index 8108a3b9..c28e3ef1 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java @@ -20,10 +20,12 @@ package com.sk89q.worldedit.command; import com.boydti.fawe.config.BBC; +import com.boydti.fawe.config.Commands; import com.boydti.fawe.object.FaweLimit; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.StringMan; +import com.boydti.fawe.util.chat.Message; import com.google.common.base.Joiner; import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.CommandContext; @@ -76,7 +78,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; -import java.util.LinkedHashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -134,7 +136,7 @@ public class UtilityCommands extends MethodCommands { public void fillr(Player player, LocalSession session, EditSession editSession, Pattern pattern, double radius, @Optional("1") double depth) throws WorldEditException { worldEdit.checkMaxRadius(radius); Vector pos = session.getPlacementPosition(player); - int affected = 0; + int affected; if (pattern instanceof BaseBlock) { affected = editSession.fillXZ(pos, ((BaseBlock) pattern), radius, (int) depth, true); } else { @@ -614,7 +616,7 @@ public class UtilityCommands extends MethodCommands { } File[] files = new File[fileList.size()]; fileList.toArray(files); - final int perPage = actor instanceof Player ? 8 : 20; // More pages for console + final int perPage = actor instanceof Player ? 12 : 20; // More pages for console int pageCount = (files.length + perPage - 1) / perPage; if (page < 1) { BBC.SCHEMATIC_PAGE.send(actor, ">0"); @@ -722,7 +724,7 @@ public class UtilityCommands extends MethodCommands { int page = -1; String category = null; - final int perPage = actor instanceof Player ? 8 : 20; // More pages for console + final int perPage = actor instanceof Player ? 12 : 20; // More pages for console int effectiveLength = args.argsLength(); // Detect page from args @@ -738,6 +740,8 @@ public class UtilityCommands extends MethodCommands { } } catch (NumberFormatException ignored) { } + String baseCommand = (prefix.equals("/") ? Commands.getAlias(UtilityCommands.class, "/help") : prefix); + if (effectiveLength > 0) baseCommand += " " + args.getString(0, effectiveLength - 1); boolean isRootLevel = true; List visited = new ArrayList(); @@ -748,10 +752,11 @@ public class UtilityCommands extends MethodCommands { // Get a list of aliases List aliases = new ArrayList(dispatcher.getCommands()); + List prefixes = Collections.nCopies(aliases.size(), ""); // Group by callable if (page == -1 || effectiveLength > 0) { - Map> grouped = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + Map> grouped = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); for (CommandMapping mapping : aliases) { CommandCallable c = mapping.getCallable(); String group; @@ -773,20 +778,23 @@ public class UtilityCommands extends MethodCommands { } group = group.replace("/", ""); group = StringMan.toProperCase(group); - Set queue = grouped.get(group); + Map queue = grouped.get(group); if (queue == null) { - queue = new LinkedHashSet<>(); + queue = new LinkedHashMap<>(); grouped.put(group, queue); } if (c instanceof Dispatcher) { - queue.addAll(((Dispatcher) c).getCommands()); + for (CommandMapping m : ((Dispatcher) c).getCommands()) { + queue.put(m, mapping.getPrimaryAlias() + " "); + } } else { - queue.add(mapping); + // Sub commands get priority + queue.putIfAbsent(mapping, ""); } } if (effectiveLength > 0) { String cat = args.getString(0); - Set mappings = effectiveLength == 1 ? grouped.get(cat) : null; + Map mappings = effectiveLength == 1 ? grouped.get(cat) : null; if (mappings == null) { // Drill down to the command for (int i = 0; i < effectiveLength; i++) { @@ -852,28 +860,37 @@ public class UtilityCommands extends MethodCommands { } } if (!(callable instanceof Dispatcher)) { + // TODO interactive box actor.printRaw(BBC.getPrefix() + ColorCodeBuilder.asColorCodes(new CommandUsageBox(callable, Joiner.on(" ").join(visited)))); return; } dispatcher = (Dispatcher) callable; aliases = new ArrayList(dispatcher.getCommands()); + prefixes = Collections.nCopies(aliases.size(), ""); } else { - aliases = new ArrayList<>(mappings); - visited.add(cat); + aliases = new ArrayList<>(); + prefixes = new ArrayList<>(); + for (Map.Entry entry : mappings.entrySet()) { + aliases.add(entry.getKey()); + prefixes.add(entry.getValue()); + } } page = Math.max(0, page); } else if (grouped.size() > 1) { - StringBuilder message = new StringBuilder(); - message.append(BBC.getPrefix() + BBC.HELP_HEADER_CATEGORIES.s() + "\n"); - StringBuilder builder = new StringBuilder(); + Message msg = new Message(); + msg.prefix().text(BBC.HELP_HEADER_CATEGORIES).newline(); boolean first = true; - for (Map.Entry> entry : grouped.entrySet()) { - String s1 = "&a//help " + entry.getKey(); + for (Map.Entry> entry : grouped.entrySet()) { + String s1 = Commands.getAlias(UtilityCommands.class, "/help") + " " + entry.getKey(); String s2 = entry.getValue().size() + ""; - message.append(BBC.HELP_ITEM_ALLOWED.format(s1, s2) + "\n"); + msg.text(BBC.HELP_ITEM_ALLOWED, "&a" + s1, s2); + msg.tooltip(StringMan.join(entry.getValue().keySet(), ", ", cm -> cm.getPrimaryAlias())); + msg.command(s1); + msg.newline(); } - message.append(BBC.HELP_HEADER_FOOTER.s()); - actor.print(BBC.color(message.toString())); + msg.text(BBC.HELP_FOOTER).link("https://git.io/vSKE5").newline(); + msg.paginate(baseCommand, 0, 1); + msg.send(actor); return; } } @@ -886,20 +903,24 @@ public class UtilityCommands extends MethodCommands { int pageTotal = (int) Math.ceil(aliases.size() / (double) perPage); // Box - StringBuilder message = new StringBuilder(); + Message msg = new Message(); if (offset >= aliases.size()) { - message.append("&c").append(String.format("There is no page %d (total number of pages is %d).", page + 1, pageTotal)); + msg.text("&c").text(String.format("There is no page %d (total number of pages is %d).", page + 1, pageTotal)); } else { - message.append(BBC.getPrefix() + BBC.HELP_HEADER.format(page + 1, pageTotal) + "\n"); - List list = aliases.subList(offset, Math.min(offset + perPage, aliases.size())); + msg.prefix().text(BBC.HELP_HEADER, page + 1, pageTotal).newline(); + int end = Math.min(offset + perPage, aliases.size()); + List subAliases = aliases.subList(offset, end); + List subPrefixes = prefixes.subList(offset, end); boolean first = true; // Add each command - for (CommandMapping mapping : list) { - CommandCallable c = mapping.getCallable(); + for (int i = 0; i < subAliases.size(); i++) { StringBuilder s1 = new StringBuilder(); s1.append(prefix); + s1.append(subPrefixes.get(i)); + CommandMapping mapping = subAliases.get(i); + CommandCallable c = mapping.getCallable(); if (!visited.isEmpty()) { s1.append(Joiner.on(" ").join(visited)); s1.append(" "); @@ -907,14 +928,21 @@ public class UtilityCommands extends MethodCommands { s1.append(mapping.getPrimaryAlias()); String s2 = mapping.getDescription().getDescription(); if (c.testPermission(locals)) { - message.append(BBC.HELP_ITEM_ALLOWED.format(s1, s2) + "\n"); + // TODO interactive + // Hover -> command help + // Click -> Suggest command + msg.text(BBC.HELP_ITEM_ALLOWED, s1, s2); + msg.newline(); } else { - message.append(BBC.HELP_ITEM_DENIED.format(s1, s2) + "\n"); + msg.text(BBC.HELP_ITEM_DENIED, s1, s2).newline(); } } - message.append(BBC.HELP_HEADER_FOOTER.f()); + if (args.argsLength() == 0) { + msg.text(BBC.HELP_FOOTER).newline(); + } + msg.paginate(baseCommand, page + 1, pageTotal); } - actor.print(BBC.color(message.toString())); + msg.send(actor); } } else { actor.printRaw(BBC.getPrefix() + ColorCodeBuilder.asColorCodes(new CommandUsageBox(callable, Joiner.on(" ").join(visited)))); diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java index d69f3651..5994b8e5 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java @@ -103,12 +103,13 @@ public enum ClipboardFormat { @Override public ClipboardWriter getWriter(OutputStream outputStream) throws IOException { - PGZIPOutputStream gzip; + OutputStream gzip; if (outputStream instanceof PGZIPOutputStream || outputStream instanceof GZIPOutputStream) { - gzip = (PGZIPOutputStream) outputStream; + gzip = outputStream; } else { outputStream = new BufferedOutputStream(outputStream); - gzip = new PGZIPOutputStream(outputStream); + PGZIPOutputStream pigz = new PGZIPOutputStream(outputStream); + gzip = pigz; } NBTOutputStream nbtStream = new NBTOutputStream(new BufferedOutputStream(gzip)); return new SchematicWriter(nbtStream); @@ -163,7 +164,8 @@ public enum ClipboardFormat { if (outputStream instanceof PGZIPOutputStream || outputStream instanceof GZIPOutputStream) { gzip = outputStream; } else { - gzip = new PGZIPOutputStream(outputStream); + PGZIPOutputStream pigz = new PGZIPOutputStream(outputStream); + gzip = pigz; } NBTOutputStream nbtStream = new NBTOutputStream(new BufferedOutputStream(gzip)); return new StructureFormat(nbtStream); diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicWriter.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicWriter.java index ea02abb7..fd19b5d1 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicWriter.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicWriter.java @@ -1,5 +1,6 @@ package com.sk89q.worldedit.extent.clipboard.io; +import com.boydti.fawe.Fawe; import com.boydti.fawe.FaweCache; import com.boydti.fawe.jnbt.NBTStreamer; import com.boydti.fawe.object.clipboard.FaweClipboard; @@ -25,7 +26,7 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.registry.WorldData; -import java.io.DataOutputStream; +import java.io.DataOutput; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -88,7 +89,7 @@ public class SchematicWriter implements ClipboardWriter { blockData[index] = (byte) block.getData(); if (id > 255) { if (addBlocks == null) { // Lazily create section - addBlocks = new byte[(blocks.length >> 1) + 1]; + addBlocks = new byte[((blocks.length + 1) >> 1)]; } addBlocks[index >> 1] = (byte) (((index & 1) == 0) ? addBlocks[index >> 1] & 0xF0 | (id >> 8) & 0xF : addBlocks[index >> 1] & 0xF | ((id >> 8) & 0xF) << 4); } @@ -132,7 +133,7 @@ public class SchematicWriter implements ClipboardWriter { if (length > MAX_SIZE) { throw new IllegalArgumentException("Length of region too large for a .schematic"); } - final DataOutputStream rawStream = outputStream.getOutputStream(); + final DataOutput rawStream = outputStream.getOutputStream(); outputStream.writeLazyCompoundTag("Schematic", new NBTOutputStream.LazyWrite() { private boolean hasAdd = false; private boolean hasTile = false; @@ -152,6 +153,7 @@ public class SchematicWriter implements ClipboardWriter { out.writeNamedTag("WEOffsetX", (offset.getBlockX())); out.writeNamedTag("WEOffsetY", (offset.getBlockY())); out.writeNamedTag("WEOffsetZ", (offset.getBlockZ())); + out.writeNamedTag("Platform", Fawe.imp().getPlatform()); out.writeNamedTagName("Blocks", NBTConstants.TYPE_BYTE_ARRAY); out.getOutputStream().writeInt(volume); @@ -196,17 +198,29 @@ public class SchematicWriter implements ClipboardWriter { if (hasAdd) { out.writeNamedTagName("AddBlocks", NBTConstants.TYPE_BYTE_ARRAY); - out.getOutputStream().writeInt(volume); + int addLength = (volume + 1) >> 1; + out.getOutputStream().writeInt(addLength); + + final int[] lastAdd = new int[1]; + final boolean[] write = new boolean[1]; + clipboard.IMP.streamIds(new NBTStreamer.ByteReader() { @Override public void run(int index, int byteValue) { - try { - rawStream.writeByte(byteValue >> 8); - } catch (IOException e) { - e.printStackTrace(); + if (write[0] ^= true) { + try { + rawStream.write(((byteValue >> 8) << 4) + (lastAdd[0])); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + lastAdd[0] = byteValue >> 8; } } }); + if (write[0]) { + rawStream.write(lastAdd[0]); + } } if (hasTile) { @@ -281,6 +295,7 @@ public class SchematicWriter implements ClipboardWriter { schematic.put("WEOffsetX", new IntTag(offset.getBlockX())); schematic.put("WEOffsetY", new IntTag(offset.getBlockY())); schematic.put("WEOffsetZ", new IntTag(offset.getBlockZ())); + schematic.put("Platform", new StringTag(Fawe.imp().getPlatform())); final byte[] blocks = new byte[width * height * length]; byte[] addBlocks; diff --git a/core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java b/core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java index 0826f268..eeb3bac7 100644 --- a/core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java +++ b/core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java @@ -20,10 +20,13 @@ package com.sk89q.worldedit.regions.selector; import com.boydti.fawe.config.BBC; +import com.boydti.fawe.config.Commands; +import com.boydti.fawe.util.chat.Message; import com.sk89q.worldedit.BlockVector; import com.sk89q.worldedit.IncompleteRegionException; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.command.SelectionCommands; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.internal.cui.CUIRegion; import com.sk89q.worldedit.internal.cui.SelectionPointEvent; @@ -152,11 +155,14 @@ public class CuboidRegionSelector extends com.sk89q.worldedit.regions.CuboidRegi checkNotNull(session); checkNotNull(pos); + Message msg; if (position1 != null && position2 != null) { - BBC.SELECTOR_CUBOID_POS1.send(player, position1, "(" + region.getArea() + ")"); + msg = BBC.SELECTOR_CUBOID_POS1.m(position1, "(" + region.getArea() + ")"); } else { - BBC.SELECTOR_CUBOID_POS1.send(player, position1, ""); + msg = BBC.SELECTOR_CUBOID_POS1.m(position1, ""); } + String cmd = Commands.getAlias(SelectionCommands.class, "/pos1") + " " + pos.getBlockX() + "," + pos.getBlockY() + "," + pos.getBlockZ(); + msg.suggestTip(cmd).send(player); session.dispatchCUIEvent(player, new SelectionPointEvent(0, pos, getArea())); } @@ -167,11 +173,14 @@ public class CuboidRegionSelector extends com.sk89q.worldedit.regions.CuboidRegi checkNotNull(session); checkNotNull(pos); + Message msg; if (position1 != null && position2 != null) { - BBC.SELECTOR_CUBOID_POS2.send(player, position2, "(" + region.getArea() + ")"); + msg = BBC.SELECTOR_CUBOID_POS2.m(position2, "(" + region.getArea() + ")"); } else { - BBC.SELECTOR_CUBOID_POS2.send(player, position2, ""); + msg = BBC.SELECTOR_CUBOID_POS2.m(position2, ""); } + String cmd = Commands.getAlias(SelectionCommands.class, "/pos2") + " " + pos.getBlockX() + "," + pos.getBlockY() + "," + pos.getBlockZ(); + msg.suggestTip(cmd).send(player); session.dispatchCUIEvent(player, new SelectionPointEvent(1, pos, getArea())); } diff --git a/core/src/main/java/com/sk89q/worldedit/util/command/SimpleCommandMapping.java b/core/src/main/java/com/sk89q/worldedit/util/command/SimpleCommandMapping.java new file mode 100644 index 00000000..903b88de --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/util/command/SimpleCommandMapping.java @@ -0,0 +1,102 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.util.command; + +import com.sk89q.worldedit.util.command.parametric.ParametricCallable; +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * Tracks a command registration. + */ +public class SimpleCommandMapping implements CommandMapping { + + private final String[] aliases; + private final CommandCallable callable; + + /** + * Create a new instance. + * + * @param callable the command callable + * @param alias a list of all aliases, where the first one is the primary one + */ + public SimpleCommandMapping(CommandCallable callable, String... alias) { + super(); + this.aliases = alias; + this.callable = callable; + } + + @Override + public String getPrimaryAlias() { + return aliases[0]; + } + + @Override + public String[] getAllAliases() { + return aliases; + } + + @Override + public CommandCallable getCallable() { + return callable; + } + + @Override + public Description getDescription() { + return getCallable().getDescription(); + } + + @Override + public int hashCode() { + return getPrimaryAlias().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof CommandMapping)) { + return false; + } + CommandMapping other = (CommandMapping) obj; + if (other.getCallable() != getCallable()) { + if (other.getCallable().getClass() != getCallable().getClass() || !(getCallable() instanceof ParametricCallable)) { + return false; + } + Method oMeth = ((ParametricCallable) other.getCallable()).getMethod(); + Method meth = ((ParametricCallable) getCallable()).getMethod(); + if (!oMeth.equals(meth)) { + return false; + } + } + return other.getPrimaryAlias().equals(getPrimaryAlias()); + } + + @Override + public String toString() { + return "CommandMapping{" + + "aliases=" + Arrays.toString(aliases) + + ", callable=" + callable + + '}'; + } + + public static Class inject() { + return SimpleCommandMapping.class; + } + +} diff --git a/core/src/main/resources/de/message.yml b/core/src/main/resources/de/message.yml index aa1ffe90..1343094b 100644 --- a/core/src/main/resources/de/message.yml +++ b/core/src/main/resources/de/message.yml @@ -1,5 +1,6 @@ info: prefix: '&4&lFAWE:&f&7' + file_deleted: '%s0 wurde gelöscht.' schematic_pasting: '&7Die Schematic wird eingefügt. Dies kann nicht rückgängig gemacht werden.' updated_lighting_selection: '&7Das Licht wird in %s0 Chunks aktualisiert. @@ -183,7 +184,6 @@ worldedit: tool_radius_error: 'Maximal erlaubter Pinsel Radius: %s0.' superpickaxe_area_enabled: 'Modus geändert. Linksklick mit einer Spitzhacke. // zum deaktivieren.' schematic: - schematic_delete: 'Schematic %s0 wurde gelöscht.' schematic_format: 'Verfügbare Zwischenablage Formate (Name: Suche Namen)' schematic_loaded: 'Schematic %s0 geladen. Platziere sie mit //paste' schematic_saved: 'Schematic %s0 gespeichert.' diff --git a/core/src/main/resources/ru/message.yml b/core/src/main/resources/ru/message.yml index 97a44176..1e910110 100644 --- a/core/src/main/resources/ru/message.yml +++ b/core/src/main/resources/ru/message.yml @@ -1,5 +1,6 @@ info: prefix: '&7' + file_deleted: '%s0 был удален.' schematic_pasting: '&7Вставка схематического файла. Это не может быть отменено.' lighting_propogate_selection: '&7Освещение было распространено в %s0 чанках. (Примечание: Для удаления освещения используйте //removelight)' @@ -189,7 +190,6 @@ worldedit: tool_radius_error: 'Максимально допустимый радиус кисти: %s0.' superpickaxe_area_enabled: Режим изменен. Щелкните левой кнопкой мыши с киркой. // для отключения. schematic: - schematic_delete: '%s0 был удален.' schematic_format: 'Доступные форматы буфера обмена (Имя: Имена поиска)' schematic_loaded: '%s0 загружен. Вставить его //paste' schematic_saved: '%s0 сохранен.' diff --git a/forge112/build.gradle b/forge112/build.gradle new file mode 100644 index 00000000..db1316d8 --- /dev/null +++ b/forge112/build.gradle @@ -0,0 +1,93 @@ +buildscript { + repositories { + jcenter() + maven { + name = "forge" + url = "http://files.minecraftforge.net/maven" + } + maven {url = "https://oss.sonatype.org/content/repositories/snapshots/"} + } + dependencies { + classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT' + } +} + +apply plugin: 'net.minecraftforge.gradle.forge' +apply plugin: 'com.github.johnrengelman.shadow' + +dependencies { + compile project(':core') + compile 'org.spongepowered:spongeapi:3.1.0-SNAPSHOT' + compile 'com.sk89q.worldedit:worldedit-forge-mc1.8.9:6.1.1' +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +repositories { + maven { + name = 'forge' + url = 'http://files.minecraftforge.net/maven' + } + maven { + name = "Sponge" + url = "https://repo.spongepowered.org/maven" + } + maven { + name = "Sponge Metrics" + url = "http://repo.mcstats.org/content/repositories/releases/" + } +} +minecraft { + version = "1.12-14.21.1.2410" + mappings = "snapshot_20170713" + runDir = 'run' +} + +project.archivesBaseName = "${project.archivesBaseName}-mc${minecraft.version}" + +processResources { + from(sourceSets.main.resources.srcDirs) { + expand 'version': project.version, + 'mcVersion': project.minecraft.version + exclude 'mcmod.info' + } +} + +shadowJar { + relocate 'org.yaml.snakeyaml', 'com.boydti.fawe.yaml' + dependencies { + include(dependency('com.github.luben:zstd-jni:1.1.1')) +// include(dependency('org.javassist:javassist:3.22.0-CR1')) + include(dependency('co.aikar:fastutil-lite:1.0')) + include(dependency(':core')) + include(dependency('org.yaml:snakeyaml:1.16')) + } + archiveName = "${parent.name}-${project.name}-${parent.version}.jar" + destinationDir = file '../target' + manifest { + attributes("Main-Class": "com.boydti.fawe.installer.InstallerFrame") + } +} +shadowJar.doLast { + task -> + ant.checksum file: task.archivePath +} + + +reobf { + shadowJar { + mappingType = 'SEARGE' + } +} + +task deobfJar(type: Jar) { + from sourceSets.main.output + classifier = 'dev' +} + +artifacts { + archives deobfJar +} + +build.dependsOn(shadowJar) diff --git a/forge112/src/main/java/com/boydti/fawe/forge/FaweForge.java b/forge112/src/main/java/com/boydti/fawe/forge/FaweForge.java new file mode 100644 index 00000000..17373345 --- /dev/null +++ b/forge112/src/main/java/com/boydti/fawe/forge/FaweForge.java @@ -0,0 +1,179 @@ +package com.boydti.fawe.forge; + + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.IFawe; +import com.boydti.fawe.forge.v112.ForgeQueue_All; +import com.boydti.fawe.object.FaweCommand; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.regions.FaweMaskManager; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.TaskManager; +import com.boydti.fawe.wrappers.WorldWrapper; +import com.mojang.authlib.GameProfile; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.forge.ForgeWorld; +import com.sk89q.worldedit.world.World; +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.UUID; +import javax.management.InstanceAlreadyExistsException; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.ModMetadata; +import org.apache.logging.log4j.Logger; + +public class FaweForge implements IFawe { + + private final ForgeMain parent; + private final File directory; + private final Logger logger; + private final ModMetadata mod; + + public FaweForge(ForgeMain plugin, Logger logger, ModMetadata mod, File directory) { + this.parent = plugin; + this.logger = logger; + this.directory = directory; + this.mod = mod; + try { + Fawe.set(this); + setupInjector(); + } catch (InstanceAlreadyExistsException e) { + MainUtil.handleError(e); + } + } + + public void setupInjector() { + try { + Fawe.setupInjector(); + com.sk89q.worldedit.forge.ForgePlayer.inject(); + } catch (Throwable e) { + Fawe.debug("Failed to inject WorldEdit classes."); + } + } + + @Override + public void debug(String s) { + logger.debug(s); + } + + @Override + public File getDirectory() { + return directory; + } + + private HashMap commands = new HashMap<>(); + + public HashMap getCommands() { + return commands; + } + + @Override + public void setupCommand(String label, FaweCommand cmd) { + this.commands.put(label, cmd); + } + + @Override + public FawePlayer wrap(Object obj) { + EntityPlayerMP player = null; + if (obj instanceof String) { + MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + player = server.getPlayerList().getPlayerByUsername((String) obj); + } else if (obj instanceof UUID) { + MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + player = server.getPlayerList().getPlayerByUUID((UUID) obj); + } else if (obj instanceof EntityPlayerMP) { + player = (EntityPlayerMP) obj; + } + if (player == null) { + return null; + } + FawePlayer existing = Fawe.get().getCachedPlayer(player.getName()); + return existing != null ? existing : new ForgePlayer(player); + } + + @Override + public void setupVault() { + // Do nothing + } + + @Override + public TaskManager getTaskManager() { + return new com.boydti.fawe.forge.ForgeTaskMan(512); + } + + @Override + public String getWorldName(World world) { + if (world instanceof WorldWrapper) { + return getWorldName(((WorldWrapper) world).getParent()); + } + else if (world instanceof EditSession) { + return getWorldName(((EditSession) world).getWorld()); + } + return getWorldName(((ForgeWorld) world).getWorld()); + + } + + public String getWorldName(net.minecraft.world.World w) { + return w.getWorldInfo().getWorldName() + ";" + w.provider.getDimension(); + } + + @Override + public FaweQueue getNewQueue(World world, boolean dontCareIfFast) { + return new ForgeQueue_All(world); + } + + @Override + public FaweQueue getNewQueue(String world, boolean dontCareIfFast) { + return new ForgeQueue_All(world); + } + + @Override + public Collection getMaskManagers() { + return new ArrayList<>(); + } + + @Override + public void startMetrics() { + try { + com.boydti.fawe.forge.ForgeMetrics metrics = new com.boydti.fawe.forge.ForgeMetrics("FastAsyncWorldEdit", "3.5.1"); + metrics.start(); + } catch (Throwable e) { + debug("[FAWE] &cFailed to load up metrics."); + } + } + + @Override + public String getPlatform() { + return "forge"; + } + + @Override + public UUID getUUID(String name) { + try { + GameProfile profile = FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerProfileCache().getGameProfileForUsername(name); + return profile.getId(); + } catch (Throwable e) { + return null; + } + } + + @Override + public String getName(UUID uuid) { + try { + GameProfile profile = FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerProfileCache().getProfileByUUID(uuid); + return profile.getName(); + } catch (Throwable e) { + return null; + } + } + + @Override + public Object getBlocksHubApi() { + return null; + } +} diff --git a/forge112/src/main/java/com/boydti/fawe/forge/ForgeCommand.java b/forge112/src/main/java/com/boydti/fawe/forge/ForgeCommand.java new file mode 100644 index 00000000..c4cd4883 --- /dev/null +++ b/forge112/src/main/java/com/boydti/fawe/forge/ForgeCommand.java @@ -0,0 +1,42 @@ +package com.boydti.fawe.forge; + +import com.boydti.fawe.object.FaweCommand; +import com.boydti.fawe.object.FawePlayer; +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; + +public class ForgeCommand extends CommandBase { + + private final String name; + private final FaweCommand cmd; + + public ForgeCommand(String name, FaweCommand cmd) { + this.name = name; + this.cmd = cmd; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getUsage(ICommandSender iCommandSender) { + return "/" + name; + } + + @Override + public void execute(MinecraftServer minecraftServer, ICommandSender sender, String[] args) throws CommandException { + if ((sender instanceof EntityPlayerMP)) { + EntityPlayerMP player = (EntityPlayerMP) sender; + if (player.world.isRemote) { + return; + } + FawePlayer fp = FawePlayer.wrap(player); + cmd.executeSafe(fp, args); + } + } +} diff --git a/forge112/src/main/java/com/boydti/fawe/forge/ForgeMain.java b/forge112/src/main/java/com/boydti/fawe/forge/ForgeMain.java new file mode 100644 index 00000000..a7eba924 --- /dev/null +++ b/forge112/src/main/java/com/boydti/fawe/forge/ForgeMain.java @@ -0,0 +1,77 @@ +package com.boydti.fawe.forge; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.object.FaweCommand; +import com.boydti.fawe.object.FawePlayer; +import java.io.File; +import java.util.Map; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.EntityJoinWorldEvent; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.event.FMLServerStartingEvent; +import net.minecraftforge.fml.common.event.FMLServerStoppingEvent; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; +import org.apache.logging.log4j.Logger; + +@Mod(modid = "com.boydti.fawe", name = "FastAsyncWorldEdit", version = "3.5.1", acceptableRemoteVersions = "*", dependencies = "before:worldedit") +public class ForgeMain { + private static com.boydti.fawe.forge.FaweForge IMP; + private Logger logger; + + @Mod.EventHandler + public void preInit(FMLPreInitializationEvent event) { + this.logger = event.getModLog(); + File directory = new File(event.getModConfigurationDirectory() + File.separator + "FastAsyncWorldEdit"); + MinecraftForge.EVENT_BUS.register(this); + FMLCommonHandler.instance().bus().register(this); + this.IMP = new FaweForge(this, event.getModLog(), event.getModMetadata(), directory); + } + + @Mod.EventHandler + public void serverLoad(FMLServerStartingEvent event) { + for (Map.Entry entry : IMP.getCommands().entrySet()) { + event.registerServerCommand(new ForgeCommand(entry.getKey(), entry.getValue())); + } + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onPlayerQuit(PlayerEvent.PlayerLoggedOutEvent event) { + if (event.player.world.isRemote) { + return; + } + handleQuit((EntityPlayerMP) event.player); + } + + @Mod.EventHandler + public void serverStopping(FMLServerStoppingEvent event) { + for (EntityPlayerMP player : FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerList().getPlayers()) { + handleQuit(player); + } + } + + public void handleQuit(EntityPlayerMP player) { + FawePlayer fp = FawePlayer.wrap(player); + if (fp != null) { + fp.unregister(); + } + Fawe.get().unregister(player.getName()); + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + public void onPlayerChangedWorld(EntityJoinWorldEvent event) { + Entity entity = event.getEntity(); + if (!(entity instanceof EntityPlayerMP)) { + return; + } + EntityPlayerMP player = (EntityPlayerMP) entity; + if (player.world.isRemote) { + return; + } + } +} diff --git a/forge112/src/main/java/com/boydti/fawe/forge/ForgeMetrics.java b/forge112/src/main/java/com/boydti/fawe/forge/ForgeMetrics.java new file mode 100644 index 00000000..3142ebc7 --- /dev/null +++ b/forge112/src/main/java/com/boydti/fawe/forge/ForgeMetrics.java @@ -0,0 +1,476 @@ +/* + * Copyright 2011-2013 Tyler Blair. All rights reserved. + * Ported to Minecraft Forge by Mike Primm + * 1.7.x update by Dries007 + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and contributors and should not be interpreted as representing official policies, + * either expressed or implied, of anybody else. + */ + +package com.boydti.fawe.forge; + +import com.boydti.fawe.object.io.FastByteArrayOutputStream; +import com.boydti.fawe.object.io.PGZIPOutputStream; +import com.boydti.fawe.util.MainUtil; +import java.io.*; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.util.UUID; +import net.minecraft.server.MinecraftServer; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.FMLLog; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +public class ForgeMetrics { + + /** + * The current revision number + */ + private final static int REVISION = 7; + + /** + * The base url of the metrics domain + */ + private static final String BASE_URL = "http://report.mcstats.org"; + + /** + * The url used to report a server's status + */ + private static final String REPORT_URL = "/plugin/%s"; + + /** + * Interval of time to ping (in minutes) + */ + private static final int PING_INTERVAL = 15; + + /** + * The mod this metrics submits for + */ + private final String modName; + + private final String modVersion; + + /** + * The metrics configuration file + */ + private final Configuration configuration; + + /** + * The metrics configuration file + */ + private final File configurationFile; + + /** + * Unique server id + */ + private final String guid; + + /** + * Debug mode + */ + private final boolean debug; + + private Thread thread = null; + private boolean firstPost = true; + int tickCount; + + public ForgeMetrics(final String modName, final String modVersion) throws IOException { + if (modName == null || modVersion == null) { + throw new IllegalArgumentException("modName and modVersion cannot be null"); + } + + this.modName = modName; + this.modVersion = modVersion; + + // load the config + configurationFile = getConfigFile(); + configuration = new Configuration(configurationFile); + + // Get values, and add some defaults, if needed + configuration.get(Configuration.CATEGORY_GENERAL, "opt-out", false, "Set to true to disable all reporting"); + guid = configuration.get(Configuration.CATEGORY_GENERAL, "guid", UUID.randomUUID().toString(), "Server unique ID").getString(); + debug = configuration.get(Configuration.CATEGORY_GENERAL, "debug", false, "Set to true for verbose debug").getBoolean(false); + configuration.save(); + } + + /** + * Start measuring statistics. This will immediately create an async + * repeating task as the plugin and send the initial data to the metrics + * backend, and then after that it will post in increments of PING_INTERVAL + * * 1200 ticks. + * + * @return True if statistics measuring is running, otherwise false. + */ + public boolean start() { + // Did we opt out? + if (isOptOut()) { + return false; + } + + FMLCommonHandler.instance().bus().register(this); + + return true; + } + + @SubscribeEvent + public void tick(TickEvent.ServerTickEvent tick) { + if (tick.phase != TickEvent.Phase.END) return; + + if (tickCount++ % (PING_INTERVAL * 1200) != 0) return; + + if (thread == null) { + thread = new Thread(new Runnable() { + public void run() { + try { + // Disable Task, if it is running and the server owner decided + // to opt-out + if (isOptOut()) { + FMLCommonHandler.instance().bus().unregister(ForgeMetrics.this); + return; + } + // We use the inverse of firstPost because if it + // is the first time we are posting, + // it is not a interval ping, so it evaluates to + // FALSE + // Each time thereafter it will evaluate to + // TRUE, i.e PING! + postPlugin(!firstPost); + // After the first post we set firstPost to + // false + // Each post thereafter will be a ping + firstPost = false; + } catch (IOException e) { + if (debug) { + FMLLog.info("[Metrics] Exception - %s", e.getMessage()); + } + } finally { + thread = null; + } + } + }); + thread.start(); + } + } + + /** + * Stop processing + */ + public void stop() { + } + + /** + * Has the server owner denied plugin metrics? + * + * @return true if metrics should be opted out of it + */ + public boolean isOptOut() { + // Reload the metrics file + configuration.load(); + return configuration.get(Configuration.CATEGORY_GENERAL, "opt-out", false).getBoolean(false); + } + + /** + * Enables metrics for the server by setting "opt-out" to false in the + * config file and starting the metrics task. + * + * @throws java.io.IOException + */ + public void enable() throws IOException { + // Check if the server owner has already set opt-out, if not, set it. + if (isOptOut()) { + configuration.getCategory(Configuration.CATEGORY_GENERAL).get("opt-out").set("false"); + configuration.save(); + } + // Enable Task, if it is not running + FMLCommonHandler.instance().bus().register(this); + } + + /** + * Disables metrics for the server by setting "opt-out" to true in the + * config file and canceling the metrics task. + * + * @throws java.io.IOException + */ + public void disable() throws IOException { + // Check if the server owner has already set opt-out, if not, set it. + if (!isOptOut()) { + configuration.getCategory(Configuration.CATEGORY_GENERAL).get("opt-out").set("true"); + configuration.save(); + } + FMLCommonHandler.instance().bus().unregister(this); + } + + /** + * Gets the File object of the config file that should be used to store data + * such as the GUID and opt-out status + * + * @return the File object for the config file + */ + public File getConfigFile() { + return new File(Loader.instance().getConfigDir(), "PluginMetrics.cfg"); + } + + /** + * Generic method that posts a plugin to the metrics website + */ + private void postPlugin(final boolean isPing) throws IOException { + // Server software specific section + String pluginName = modName; + MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + boolean onlineMode = server.isServerInOnlineMode(); + String pluginVersion = modVersion; + String serverVersion; + if (server.isDedicatedServer()) { + serverVersion = "MinecraftForge (MC: " + server.getMinecraftVersion() + ")"; + } else { + serverVersion = "MinecraftForgeSSP (MC: " + server.getMinecraftVersion() + ")"; + } + int playersOnline = server.getCurrentPlayerCount(); + + // END server software specific section -- all code below does not use any code outside of this class / Java + + // Construct the post data + StringBuilder json = new StringBuilder(1024); + json.append('{'); + + // The plugin's description file containg all of the plugin data such as name, version, author, etc + appendJSONPair(json, "guid", guid); + appendJSONPair(json, "plugin_version", pluginVersion); + appendJSONPair(json, "server_version", serverVersion); + appendJSONPair(json, "players_online", Integer.toString(playersOnline)); + + // New data as of R6 + String osname = System.getProperty("os.name"); + String osarch = System.getProperty("os.arch"); + String osversion = System.getProperty("os.version"); + String java_version = System.getProperty("java.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + // normalize os arch .. amd64 -> x86_64 + if (osarch.equals("amd64")) { + osarch = "x86_64"; + } + + appendJSONPair(json, "osname", osname); + appendJSONPair(json, "osarch", osarch); + appendJSONPair(json, "osversion", osversion); + appendJSONPair(json, "cores", Integer.toString(coreCount)); + appendJSONPair(json, "auth_mode", onlineMode ? "1" : "0"); + appendJSONPair(json, "java_version", java_version); + + // If we're pinging, append it + if (isPing) { + appendJSONPair(json, "ping", "1"); + } + + // close json + json.append('}'); + + // Create the url + URL url = new URL(BASE_URL + String.format(REPORT_URL, urlEncode(pluginName))); + + // Connect to the website + URLConnection connection; + + // Mineshafter creates a socks proxy, so we can safely bypass it + // It does not reroute POST requests so we need to go around it + if (isMineshafterPresent()) { + connection = url.openConnection(Proxy.NO_PROXY); + } else { + connection = url.openConnection(); + } + + + byte[] uncompressed = json.toString().getBytes(); + byte[] compressed = gzip(json.toString()); + + // Headers + connection.addRequestProperty("User-Agent", "MCStats/" + REVISION); + connection.addRequestProperty("Content-Type", "application/json"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Content-Length", Integer.toString(compressed.length)); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + + connection.setDoOutput(true); + + // Write the data + OutputStream os = connection.getOutputStream(); + os.write(compressed); + os.flush(); + + // Now read the response + final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String response = reader.readLine(); + + // close resources + os.close(); + reader.close(); + + if (response == null || response.startsWith("ERR") || response.startsWith("7")) { + if (response == null) { + response = "null"; + } else if (response.startsWith("7")) { + response = response.substring(response.startsWith("7,") ? 2 : 1); + } + + throw new IOException(response); + } + } + + /** + * GZip compress a string of bytes + * + * @param input + * @return + */ + public static byte[] gzip(String input) { + FastByteArrayOutputStream baos = new FastByteArrayOutputStream(); + PGZIPOutputStream gzos = null; + + try { + gzos = new PGZIPOutputStream(baos); + gzos.write(input.getBytes("UTF-8")); + } catch (IOException e) { + MainUtil.handleError(e); + } finally { + if (gzos != null) try { + gzos.close(); + } catch (IOException ignore) { + } + } + + return baos.toByteArray(); + } + + /** + * Check if mineshafter is present. If it is, we need to bypass it to send POST requests + * + * @return true if mineshafter is installed on the server + */ + private boolean isMineshafterPresent() { + try { + Class.forName("mineshafter.MineServer"); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * Appends a json encoded key/value pair to the given string builder. + * + * @param json + * @param key + * @param value + * @throws java.io.UnsupportedEncodingException + */ + private static void appendJSONPair(StringBuilder json, String key, String value) throws UnsupportedEncodingException { + boolean isValueNumeric = false; + + try { + if (value.equals("0") || !value.endsWith("0")) { + Double.parseDouble(value); + isValueNumeric = true; + } + } catch (NumberFormatException e) { + isValueNumeric = false; + } + + if (json.charAt(json.length() - 1) != '{') { + json.append(','); + } + + json.append(escapeJSON(key)); + json.append(':'); + + if (isValueNumeric) { + json.append(value); + } else { + json.append(escapeJSON(value)); + } + } + + /** + * Escape a string to create a valid JSON string + * + * @param text + * @return + */ + private static String escapeJSON(String text) { + StringBuilder builder = new StringBuilder(); + + builder.append('"'); + for (int index = 0; index < text.length(); index++) { + char chr = text.charAt(index); + + switch (chr) { + case '"': + case '\\': + builder.append('\\'); + builder.append(chr); + break; + case '\b': + builder.append("\\b"); + break; + case '\t': + builder.append("\\t"); + break; + case '\n': + builder.append("\\n"); + break; + case '\r': + builder.append("\\r"); + break; + default: + if (chr < ' ') { + String t = "000" + Integer.toHexString(chr); + builder.append("\\u" + t.substring(t.length() - 4)); + } else { + builder.append(chr); + } + break; + } + } + builder.append('"'); + + return builder.toString(); + } + + /** + * Encode text as UTF-8 + * + * @param text the text to encode + * @return the encoded text, as UTF-8 + */ + private static String urlEncode(final String text) throws UnsupportedEncodingException { + return URLEncoder.encode(text, "UTF-8"); + } + +} \ No newline at end of file diff --git a/forge112/src/main/java/com/boydti/fawe/forge/ForgePlayer.java b/forge112/src/main/java/com/boydti/fawe/forge/ForgePlayer.java new file mode 100644 index 00000000..ed2461a6 --- /dev/null +++ b/forge112/src/main/java/com/boydti/fawe/forge/ForgePlayer.java @@ -0,0 +1,77 @@ +package com.boydti.fawe.forge; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.FaweLocation; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.wrappers.PlayerWrapper; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.forge.ForgeWorldEdit; +import java.util.UUID; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.world.World; + +public class ForgePlayer extends FawePlayer { + public ForgePlayer(EntityPlayerMP parent) { + super(parent); + } + + @Override + public void sendTitle(String head, String sub) { // Not supported + Settings.IMP.QUEUE.PROGRESS.DISPLAY = "false"; + } + + @Override + public void resetTitle() { // Not supported + Settings.IMP.QUEUE.PROGRESS.DISPLAY = "false"; + } + + @Override + public String getName() { + return parent.getName(); + } + + @Override + public UUID getUUID() { + return parent.getUniqueID(); + } + + @Override + public boolean hasPermission(String perm) { + Object meta = getMeta(perm); + return meta instanceof Boolean ? (boolean) meta : ForgeWorldEdit.inst.getPermissionsProvider().hasPermission(parent, perm); + } + + @Override + public void setPermission(String perm, boolean flag) { + setMeta(perm, flag); + } + + @Override + public void sendMessage(String msg) { + msg = BBC.color(msg); + for (String line : msg.split("\n")) { + this.parent.sendMessage(new TextComponentString(line)); + } + } + + @Override + public void executeCommand(String substring) { + throw new UnsupportedOperationException("NOT IMPLEMENTED"); + } + + @Override + public FaweLocation getLocation() { + World world = parent.world; + BlockPos pos = parent.getPosition(); + return new FaweLocation(Fawe.imp().getWorldName(world), pos.getX(), pos.getY(), pos.getZ()); + } + + @Override + public Player toWorldEditPlayer() { + return PlayerWrapper.wrap(ForgeWorldEdit.inst.wrap(this.parent)); + } +} diff --git a/forge112/src/main/java/com/boydti/fawe/forge/ForgePlayerBlockBag.java b/forge112/src/main/java/com/boydti/fawe/forge/ForgePlayerBlockBag.java new file mode 100644 index 00000000..183ee439 --- /dev/null +++ b/forge112/src/main/java/com/boydti/fawe/forge/ForgePlayerBlockBag.java @@ -0,0 +1,199 @@ +package com.boydti.fawe.forge; + +import com.sk89q.worldedit.WorldVector; +import com.sk89q.worldedit.blocks.BaseItem; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.blocks.BlockID; +import com.sk89q.worldedit.blocks.ItemType; +import com.sk89q.worldedit.extent.inventory.BlockBag; +import com.sk89q.worldedit.extent.inventory.BlockBagException; +import com.sk89q.worldedit.extent.inventory.OutOfBlocksException; +import com.sk89q.worldedit.extent.inventory.OutOfSpaceException; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +public class ForgePlayerBlockBag extends BlockBag { + + private EntityPlayerMP player; + private ItemStack[] items; + private boolean changed; + + /** + * Construct the object. + * + * @param player the player + */ + public ForgePlayerBlockBag(EntityPlayerMP player) { + this.player = player; + } + + /** + * Loads inventory on first use. + */ + private void loadInventory() { + if (items == null) { + items = new ItemStack[player.inventory.getSizeInventory()]; + for (int i = 0; i < player.inventory.getSizeInventory(); i++) { + items[i] = player.inventory.getStackInSlot(i); + } + } + } + + /** + * Get the player. + * + * @return the player + */ + public EntityPlayerMP getPlayer() { + return player; + } + + @Override + public void fetchItem(BaseItem item) throws BlockBagException { + final int id = item.getType(); + final int damage = item.getData(); + int amount = (item instanceof BaseItemStack) ? ((BaseItemStack) item).getAmount() : 1; + assert(amount == 1); + boolean usesDamageValue = ItemType.usesDamageValue(id); + + if (id == BlockID.AIR) { + throw new IllegalArgumentException("Can't fetch air block"); + } + + loadInventory(); + + boolean found = false; + + for (int slot = 0; slot < items.length; ++slot) { + ItemStack forgeItem = items[slot]; + + if (forgeItem == null) { + continue; + } + int itemId = Item.getIdFromItem(forgeItem.getItem()); + if (itemId != id) { + // Type id doesn't fit + continue; + } + + if (usesDamageValue && forgeItem.getItemDamage() != damage) { + // Damage value doesn't fit. + continue; + } + + int currentAmount = forgeItem.getCount(); + if (currentAmount < 0) { + // Unlimited + return; + } + + changed = true; + + if (currentAmount > 1) { + forgeItem.setCount(forgeItem.getCount() - 1);; + found = true; + } else { + items[slot] = null; + found = true; + } + + break; + } + + if (!found) { + throw new OutOfBlocksException(); + } + } + + @Override + public void storeItem(BaseItem item) throws BlockBagException { + final int id = item.getType(); + final int damage = item.getData(); + int amount = (item instanceof BaseItemStack) ? ((BaseItemStack) item).getAmount() : 1; + assert(amount <= 64); + boolean usesDamageValue = ItemType.usesDamageValue(id); + + if (id == BlockID.AIR) { + throw new IllegalArgumentException("Can't store air block"); + } + + loadInventory(); + + int freeSlot = -1; + + for (int slot = 0; slot < items.length; ++slot) { + ItemStack forgeItem = items[slot]; + + if (forgeItem == null) { + // Delay using up a free slot until we know there are no stacks + // of this item to merge into + + if (freeSlot == -1) { + freeSlot = slot; + } + continue; + } + + int itemId = Item.getIdFromItem(forgeItem.getItem()); + if (itemId != id) { + // Type id doesn't fit + continue; + } + + if (usesDamageValue && forgeItem.getItemDamage() != damage) { + // Damage value doesn't fit. + continue; + } + + int currentAmount = forgeItem.getCount(); + if (currentAmount < 0) { + // Unlimited + return; + } + if (currentAmount >= 64) { + // Full stack + continue; + } + + changed = true; + + int spaceLeft = 64 - currentAmount; + if (spaceLeft >= amount) { + forgeItem.setCount(forgeItem.getCount() + amount); + return; + } + + forgeItem.setCount(64); + amount -= spaceLeft; + } + + if (freeSlot > -1) { + changed = true; + items[freeSlot] = new ItemStack(Item.getItemById(id), amount); + return; + } + + throw new OutOfSpaceException(id); + } + + @Override + public void flushChanges() { + if (items != null && changed) { + for (int i = 0; i < items.length; i++) { + player.inventory.setInventorySlotContents(i, items[i]); + } + items = null; + changed = false; + } + } + + @Override + public void addSourcePosition(WorldVector pos) { + } + + @Override + public void addSingleSourcePosition(WorldVector pos) { + } + +} \ No newline at end of file diff --git a/forge112/src/main/java/com/boydti/fawe/forge/ForgeTaskMan.java b/forge112/src/main/java/com/boydti/fawe/forge/ForgeTaskMan.java new file mode 100644 index 00000000..39eb1155 --- /dev/null +++ b/forge112/src/main/java/com/boydti/fawe/forge/ForgeTaskMan.java @@ -0,0 +1,168 @@ +package com.boydti.fawe.forge; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.TaskManager; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +public class ForgeTaskMan extends TaskManager { + + private final ConcurrentLinkedDeque syncTasks = new ConcurrentLinkedDeque<>(); + private final ConcurrentLinkedDeque asyncTasks = new ConcurrentLinkedDeque<>(); + + private final ConcurrentHashMap taskIdMap = new ConcurrentHashMap<>(8, 0.9f, 1); + + + private final AtomicInteger taskId = new AtomicInteger(); + private final ExecutorService executor; + + public ForgeTaskMan(int size) { + this.executor = Executors.newFixedThreadPool(size); + FMLCommonHandler.instance().bus().register(this); + } + + + @Override + public int repeat(final Runnable r, final int interval) { + if (r == null) { + return -1; + } + int id = taskId.incrementAndGet(); + taskIdMap.put(id, r); + task(new Runnable() { + @Override + public void run() { + if (!taskIdMap.containsKey(id)) { + return; + } + try { + r.run(); + } catch (Throwable e) { + MainUtil.handleError(e); + } + later(this, interval); + } + }); + return id; + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + public void onServerTick(TickEvent.ServerTickEvent event) { + Fawe.get().setMainThread(); + int asyncSize = asyncTasks.size(); + for (int i = 0; i < asyncSize; i++) { + Runnable item = asyncTasks.poll(); + if (item != null) { + async(item); + } + } + int syncSize = syncTasks.size(); + for (int i = 0; i < syncSize; i++) { + Runnable item = syncTasks.poll(); + if (item != null) { + try { + item.run(); + } catch (Throwable e) { + MainUtil.handleError(e); + } + } + } + } + + @Override + public int repeatAsync(Runnable r, int interval) { + if (r == null) { + return -1; + } + int id = taskId.incrementAndGet(); + taskIdMap.put(id, r); + async(new Runnable() { + @Override + public void run() { + if (!taskIdMap.containsKey(id)) { + return; + } + try { + r.run(); + } catch (Throwable e) { + MainUtil.handleError(e); + } + laterAsync(this, interval); + } + }); + return id; + } + + @Override + public void async(Runnable r) { + if (r == null) { + return; + } + executor.execute(r); + } + + @Override + public void task(Runnable r) { + if (r == null) { + return; + } + syncTasks.add(r); + } + + @Override + public void later(Runnable r, int delay) { + if (r == null) { + return; + } + AtomicInteger remaining = new AtomicInteger(delay); + task(new Runnable() { + @Override + public void run() { + if (remaining.decrementAndGet() <= 0) { + try { + r.run(); + } catch (Throwable e) { + MainUtil.handleError(e); + } + return; + } + task(this); + } + }); + } + + @Override + public void laterAsync(Runnable r, int delay) { + if (r == null) { + return; + } + AtomicInteger remaining = new AtomicInteger(delay); + task(new Runnable() { + @Override + public void run() { + if (remaining.decrementAndGet() <= 0) { + try { + async(r); + } catch (Throwable e) { + MainUtil.handleError(e); + } + return; + } + task(this); + } + }); + } + + @Override + public void cancel(int task) { + taskIdMap.remove(task); + } +} diff --git a/forge112/src/main/java/com/boydti/fawe/forge/MutableGenLayer.java b/forge112/src/main/java/com/boydti/fawe/forge/MutableGenLayer.java new file mode 100644 index 00000000..70b06c13 --- /dev/null +++ b/forge112/src/main/java/com/boydti/fawe/forge/MutableGenLayer.java @@ -0,0 +1,26 @@ +package com.boydti.fawe.forge; + +import java.util.Arrays; +import net.minecraft.world.gen.layer.GenLayer; +import net.minecraft.world.gen.layer.IntCache; + +public class MutableGenLayer extends GenLayer { + + private int biome; + + public MutableGenLayer(long seed) { + super(seed); + } + + public MutableGenLayer set(int biome) { + this.biome = biome; + return this; + } + + @Override + public int[] getInts(int areaX, int areaY, int areaWidth, int areaHeight) { + int[] biomes = IntCache.getIntCache(areaWidth * areaHeight); + Arrays.fill(biomes, biome); + return biomes; + } +} diff --git a/forge112/src/main/java/com/boydti/fawe/forge/v112/ForgeChunk_All.java b/forge112/src/main/java/com/boydti/fawe/forge/v112/ForgeChunk_All.java new file mode 100644 index 00000000..258d3e41 --- /dev/null +++ b/forge112/src/main/java/com/boydti/fawe/forge/v112/ForgeChunk_All.java @@ -0,0 +1,388 @@ +package com.boydti.fawe.forge.v112; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.example.CharFaweChunk; +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.util.MainUtil; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.BitArray; +import net.minecraft.util.ClassInheritanceMultiMap; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.BlockStateContainer; +import net.minecraft.world.chunk.BlockStatePaletteRegistry; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.IBlockStatePalette; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; + +public class ForgeChunk_All extends CharFaweChunk { + + public BlockStateContainer[] sectionPalettes; + + public static Map entityKeys; + + /** + * A FaweSections object represents a chunk and the blocks that you wish to change in it. + * + * @param parent + * @param x + * @param z + */ + public ForgeChunk_All(FaweQueue parent, int x, int z) { + super(parent, x, z); + } + + public ForgeChunk_All(FaweQueue parent, int x, int z, char[][] ids, short[] count, short[] air, byte[] heightMap) { + super(parent, x, z, ids, count, air, heightMap); + } + + @Override + public CharFaweChunk copy(boolean shallow) { + ForgeChunk_All copy; + if (shallow) { + copy = new ForgeChunk_All(getParent(), getX(), getZ(), ids, count, air, heightMap); + copy.biomes = biomes; + copy.chunk = chunk; + } else { + copy = new ForgeChunk_All(getParent(), getX(), getZ(), (char[][]) MainUtil.copyNd(ids), count.clone(), air.clone(), heightMap.clone()); + copy.biomes = biomes; + copy.chunk = chunk; + copy.biomes = biomes.clone(); + copy.chunk = chunk; + } + if (sectionPalettes != null) { + copy.sectionPalettes = new BlockStateContainer[16]; + try { + Field fieldBits = BlockStateContainer.class.getDeclaredField("field_186021_b"); // storage + fieldBits.setAccessible(true); + Field fieldPalette = BlockStateContainer.class.getDeclaredField("field_186022_c"); // palettes + fieldPalette.setAccessible(true); + Field fieldSize = BlockStateContainer.class.getDeclaredField("field_186024_e"); // bits + fieldSize.setAccessible(true); + for (int i = 0; i < sectionPalettes.length; i++) { + BlockStateContainer current = sectionPalettes[i]; + if (current == null) { + continue; + } + // Clone palette + IBlockStatePalette currentPalette = (IBlockStatePalette) fieldPalette.get(current); + if (!(currentPalette instanceof BlockStatePaletteRegistry)) { + current.onResize(128, null); + } + BlockStateContainer paletteBlock = new BlockStateContainer(); + currentPalette = (IBlockStatePalette) fieldPalette.get(current); + if (!(currentPalette instanceof BlockStatePaletteRegistry)) { + throw new RuntimeException("Palette must be global!"); + } + fieldPalette.set(paletteBlock, currentPalette); + // Clone size + fieldSize.set(paletteBlock, fieldSize.get(current)); + // Clone palette + BitArray currentBits = (BitArray) fieldBits.get(current); + BitArray newBits = new BitArray(1, 0); + for (Field field : BitArray.class.getDeclaredFields()) { + field.setAccessible(true); + Object currentValue = field.get(currentBits); + if (currentValue instanceof long[]) { + currentValue = ((long[]) currentValue).clone(); + } + field.set(newBits, currentValue); + } + fieldBits.set(paletteBlock, newBits); + copy.sectionPalettes[i] = paletteBlock; + } + } catch (Throwable e) { + MainUtil.handleError(e); + } + } + return copy; + } + + @Override + public Chunk getNewChunk() { + World world = ((ForgeQueue_All) getParent()).getWorld(); + return world.getChunkProvider().provideChunk(getX(), getZ()); + } + + public void optimize() { + if (sectionPalettes != null) { + return; + } + char[][] arrays = getCombinedIdArrays(); + char lastChar = Character.MAX_VALUE; + for (int layer = 0; layer < 16; layer++) { + if (getCount(layer) > 0) { + if (sectionPalettes == null) { + sectionPalettes = new BlockStateContainer[16]; + } + BlockStateContainer palette = new BlockStateContainer(); + char[] blocks = getIdArray(layer); + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + char combinedId = blocks[FaweCache.CACHE_J[y][z][x]]; + if (combinedId > 1) { + palette.set(x, y, z, Block.getBlockById(combinedId >> 4).getStateFromMeta(combinedId & 0xF)); + } + } + } + } + } + } + } + + @Override + public ForgeChunk_All call() { + net.minecraft.world.chunk.Chunk nmsChunk = this.getChunk(); + int bx = this.getX() << 4; + int bz = this.getZ() << 4; + nmsChunk.setModified(true); + net.minecraft.world.World nmsWorld = getParent().getWorld(); + try { + boolean flag = nmsWorld.provider.hasSkyLight(); + // Sections + ExtendedBlockStorage[] sections = nmsChunk.getBlockStorageArray(); + Map tiles = nmsChunk.getTileEntityMap(); + ClassInheritanceMultiMap[] entities = nmsChunk.getEntityLists(); + + // Set heightmap + getParent().setHeightMap(this, heightMap); + // Remove entities + for (int i = 0; i < 16; i++) { + int count = this.getCount(i); + if (count == 0) { + continue; + } else if (count >= 4096) { + ClassInheritanceMultiMap ents = entities[i]; + if (ents != null && !ents.isEmpty()) { + entities[i] = new ClassInheritanceMultiMap<>(Entity.class); + for (Entity ent : ents) { + nmsWorld.removeEntity(ent); + } + } + } else { + char[] array = this.getIdArray(i); + if (array == null || entities[i] == null || entities[i].isEmpty()) continue; + Collection ents = new ArrayList<>(entities[i]); + for (Entity entity : ents) { + if (entity instanceof EntityPlayer) { + continue; + } + int x = ((int) Math.round(entity.posX) & 15); + int z = ((int) Math.round(entity.posZ) & 15); + int y = (int) Math.round(entity.posY); + if (y < 0 || y > 255) continue; + if (array[FaweCache.CACHE_J[y][z][x]] != 0) { + nmsWorld.removeEntity(entity); + } + } + } + } + HashSet entsToRemove = this.getEntityRemoves(); + if (!entsToRemove.isEmpty()) { + for (int i = 0; i < entities.length; i++) { + Collection ents = new ArrayList<>(entities[i]); + for (Entity entity : ents) { + if (entsToRemove.contains(entity.getUniqueID())) { + nmsWorld.removeEntity(entity); + } + } + } + } + // Set entities + Set createdEntities = new HashSet<>(); + Set entitiesToSpawn = this.getEntities(); + for (CompoundTag nativeTag : entitiesToSpawn) { + Map entityTagMap = nativeTag.getValue(); + StringTag idTag = (StringTag) entityTagMap.get("Id"); + ListTag posTag = (ListTag) entityTagMap.get("Pos"); + ListTag rotTag = (ListTag) entityTagMap.get("Rotation"); + if (idTag == null || posTag == null || rotTag == null) { + Fawe.debug("Unknown entity tag: " + nativeTag); + continue; + } + double x = posTag.getDouble(0); + double y = posTag.getDouble(1); + double z = posTag.getDouble(2); + float yaw = rotTag.getFloat(0); + float pitch = rotTag.getFloat(1); + String id = idTag.getValue(); + if (entityKeys == null) { + entityKeys = new HashMap<>(); + for (ResourceLocation key : EntityList.getEntityNameList()) { + String currentId = EntityList.getTranslationName(key); + entityKeys.put(currentId, key); + entityKeys.put(key.getResourcePath(), key); + } + } + ResourceLocation entityKey = entityKeys.get(id); + if (entityKey != null) { + Entity entity = EntityList.createEntityByIDFromName(entityKey, nmsWorld); + if (entity != null) { + NBTTagCompound tag = (NBTTagCompound)ForgeQueue_All.methodFromNative.invoke(null, nativeTag); + entity.readFromNBT(tag); + tag.removeTag("UUIDMost"); + tag.removeTag("UUIDLeast"); + entity.setPositionAndRotation(x, y, z, yaw, pitch); + nmsWorld.spawnEntity(entity); + } + } + + } + // Run change task if applicable + if (getParent().getChangeTask() != null) { + CharFaweChunk previous = getParent().getPrevious(this, sections, tiles, entities, createdEntities, false); + getParent().getChangeTask().run(previous, this); + } + // Trim tiles + if (!tiles.isEmpty()) { + Set> entryset = tiles.entrySet(); + Iterator> iterator = entryset.iterator(); + while (iterator.hasNext()) { + Map.Entry tile = iterator.next(); + BlockPos pos = tile.getKey(); + int lx = pos.getX() & 15; + int ly = pos.getY(); + int lz = pos.getZ() & 15; + int j = FaweCache.CACHE_I[ly][lz][lx]; + char[] array = this.getIdArray(j); + if (array == null) { + continue; + } + int k = FaweCache.CACHE_J[ly][lz][lx]; + if (array[k] != 0) { + synchronized (ForgeChunk_All.class) { + tile.getValue().invalidate(); + iterator.remove(); + } + } + } + } + // Efficiently merge sections + for (int j = 0; j < sections.length; j++) { + int count = this.getCount(j); + if (count == 0) { + continue; + } + final char[] array = this.getIdArray(j); + if (array == null) { + continue; + } + int countAir = this.getAir(j); + ExtendedBlockStorage section = sections[j]; + if (section == null) { + if (count == countAir) { + continue; + } + if (this.sectionPalettes != null && this.sectionPalettes[j] != null) { + section = sections[j] = new ExtendedBlockStorage(j << 4, flag); + getParent().setPalette(section, this.sectionPalettes[j]); + getParent().setCount(0, count - this.getAir(j), section); + continue; + } else { + sections[j] = section = new ExtendedBlockStorage(j << 4, flag); + } + } else if (count >= 4096) { + if (count == countAir) { + sections[j] = null; + continue; + } + if (this.sectionPalettes != null && this.sectionPalettes[j] != null) { + getParent().setPalette(section, this.sectionPalettes[j]); + getParent().setCount(0, count - this.getAir(j), section); + continue; + } + } + IBlockState existing; + int by = j << 4; + BlockStateContainer nibble = section.getData(); + int nonEmptyBlockCount = 0; + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + char combinedId = array[FaweCache.CACHE_J[y][z][x]]; + switch (combinedId) { + case 0: + continue; + case 1: + existing = nibble.get(x, y, z); + if (existing != ForgeQueue_All.air) { + if (existing.getLightValue() > 0) { + getParent().getRelighter().addLightUpdate(bx + x, by + y, bz + z); + } + nonEmptyBlockCount--; + } + nibble.set(x, y, z, ForgeQueue_All.air); + continue; + default: + existing = nibble.get(x, y, z); + if (existing != ForgeQueue_All.air) { + if (existing.getLightValue() > 0) { + getParent().getRelighter().addLightUpdate(bx + x, by + y, bz + z); + } + } else { + nonEmptyBlockCount++; + } + nibble.set(x, y, z, Block.getBlockById(combinedId >> 4).getStateFromMeta(combinedId & 0xF)); + } + } + } + } + getParent().setCount(0, getParent().getNonEmptyBlockCount(section) + nonEmptyBlockCount, section); + } + // Set biomes + if (this.biomes != null) { + byte[] currentBiomes = nmsChunk.getBiomeArray(); + for (int i = 0 ; i < this.biomes.length; i++) { + if (this.biomes[i] != 0) { + currentBiomes[i] = this.biomes[i]; + } + } + } + // Set tiles + Map tilesToSpawn = this.getTiles(); + + for (Map.Entry entry : tilesToSpawn.entrySet()) { + CompoundTag nativeTag = entry.getValue(); + short blockHash = entry.getKey(); + int x = (blockHash >> 12 & 0xF) + bx; + int y = (blockHash & 0xFF); + int z = (blockHash >> 8 & 0xF) + bz; + BlockPos pos = new BlockPos(x, y, z); // Set pos + TileEntity tileEntity = nmsWorld.getTileEntity(pos); + if (tileEntity != null) { + NBTTagCompound tag = (NBTTagCompound) ForgeQueue_All.methodFromNative.invoke(null, nativeTag); + tag.setInteger("x", pos.getX()); + tag.setInteger("y", pos.getY()); + tag.setInteger("z", pos.getZ()); + tileEntity.readFromNBT(tag); // ReadTagIntoTile + } + } + sectionPalettes = null; + } catch (Throwable e) { + MainUtil.handleError(e); + } + return this; + } +} diff --git a/forge112/src/main/java/com/boydti/fawe/forge/v112/ForgeQueue_All.java b/forge112/src/main/java/com/boydti/fawe/forge/v112/ForgeQueue_All.java new file mode 100644 index 00000000..0b247bef --- /dev/null +++ b/forge112/src/main/java/com/boydti/fawe/forge/v112/ForgeQueue_All.java @@ -0,0 +1,663 @@ +package com.boydti.fawe.forge.v112; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.example.CharFaweChunk; +import com.boydti.fawe.example.NMSMappedFaweQueue; +import com.boydti.fawe.forge.ForgePlayer; +import com.boydti.fawe.forge.MutableGenLayer; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.brush.visualization.VisualChunk; +import com.boydti.fawe.object.visitor.FaweChunkVisitor; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.ReflectionUtils; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.world.biome.BaseBiome; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.LongAdder; +import net.minecraft.block.Block; +import net.minecraft.block.BlockFalling; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.Blocks; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.PacketBuffer; +import net.minecraft.network.play.server.SPacketChunkData; +import net.minecraft.network.play.server.SPacketMultiBlockChange; +import net.minecraft.server.management.PlayerChunkMap; +import net.minecraft.server.management.PlayerChunkMapEntry; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ClassInheritanceMultiMap; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.EnumSkyBlock; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; +import net.minecraft.world.biome.BiomeCache; +import net.minecraft.world.biome.BiomeProvider; +import net.minecraft.world.chunk.BlockStateContainer; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.IChunkProvider; +import net.minecraft.world.chunk.NibbleArray; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; +import net.minecraft.world.gen.ChunkGeneratorOverworld; +import net.minecraft.world.gen.ChunkProviderServer; +import net.minecraft.world.gen.IChunkGenerator; +import net.minecraft.world.storage.WorldInfo; +import net.minecraftforge.common.DimensionManager; + +public class ForgeQueue_All extends NMSMappedFaweQueue { + + protected final static Method methodFromNative; + protected final static Method methodToNative; + protected final static Field fieldTickingBlockCount; + protected final static Field fieldNonEmptyBlockCount; + + protected static Field fieldBiomes; + protected static Field fieldChunkGenerator; + protected static Field fieldSeed; + protected static Field fieldBiomeCache; + protected static Field fieldBiomes2; + protected static Field fieldGenLayer1; + protected static Field fieldGenLayer2; + protected static ExtendedBlockStorage emptySection; + private static MutableGenLayer genLayer; + + static { + try { + emptySection = new ExtendedBlockStorage(0, true); + Class converter = Class.forName("com.sk89q.worldedit.forge.NBTConverter"); + methodFromNative = converter.getDeclaredMethod("toNative", Tag.class); + methodToNative = converter.getDeclaredMethod("fromNative", NBTBase.class); + methodFromNative.setAccessible(true); + methodToNative.setAccessible(true); + + fieldBiomes = ChunkGeneratorOverworld.class.getDeclaredField("field_185981_C"); // biomesForGeneration + fieldBiomes.setAccessible(true); + fieldChunkGenerator = ChunkProviderServer.class.getDeclaredField("field_186029_c"); // chunkGenerator + fieldChunkGenerator.setAccessible(true); + fieldSeed = WorldInfo.class.getDeclaredField("field_76100_a"); // randomSeed + fieldSeed.setAccessible(true); + fieldBiomeCache = BiomeProvider.class.getDeclaredField("field_76942_f"); // biomeCache + fieldBiomeCache.setAccessible(true); + fieldBiomes2 = BiomeProvider.class.getDeclaredField("field_76943_g"); // biomesToSpawnIn + fieldBiomes2.setAccessible(true); + fieldGenLayer1 = BiomeProvider.class.getDeclaredField("field_76944_d"); // genBiomes + fieldGenLayer2 = BiomeProvider.class.getDeclaredField("field_76945_e"); // biomeIndexLayer + fieldGenLayer1.setAccessible(true); + fieldGenLayer2.setAccessible(true); + + fieldTickingBlockCount = ExtendedBlockStorage.class.getDeclaredField("field_76683_c"); + fieldNonEmptyBlockCount = ExtendedBlockStorage.class.getDeclaredField("field_76682_b"); + fieldTickingBlockCount.setAccessible(true); + fieldNonEmptyBlockCount.setAccessible(true); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public ForgeQueue_All(com.sk89q.worldedit.world.World world) { + super(world); + getImpWorld(); + } + + public ForgeQueue_All(String world) { + super(world); + getImpWorld(); + } + + @Override + public void saveChunk(Chunk chunk) { + chunk.setModified(true); + } + + @Override + public ExtendedBlockStorage[] getSections(Chunk chunk) { + return chunk.getBlockStorageArray(); + } + + @Override + public int getBiome(Chunk chunk, int x, int z) { + return chunk.getBiomeArray()[((z & 15) << 4) + (x & 15)]; + } + + @Override + public Chunk loadChunk(World world, int x, int z, boolean generate) { + ChunkProviderServer provider = (ChunkProviderServer) world.getChunkProvider(); + if (generate) { + return provider.provideChunk(x, z); + } else { + return provider.loadChunk(x, z); + } + } + + @Override + public ExtendedBlockStorage[] getCachedSections(World world, int cx, int cz) { + Chunk chunk = world.getChunkProvider().getLoadedChunk(cx, cz); + if (chunk != null) { + return chunk.getBlockStorageArray(); + } + return null; + } + + @Override + public Chunk getCachedChunk(World world, int cx, int cz) { + return world.getChunkProvider().getLoadedChunk(cx, cz); + } + + @Override + public ExtendedBlockStorage getCachedSection(ExtendedBlockStorage[] ExtendedBlockStorages, int cy) { + return ExtendedBlockStorages[cy]; + } + + @Override + public void sendBlockUpdate(FaweChunk chunk, FawePlayer... players) { + try { + PlayerChunkMap playerManager = ((WorldServer) getWorld()).getPlayerChunkMap(); + boolean watching = false; + boolean[] watchingArr = new boolean[players.length]; + for (int i = 0; i < players.length; i++) { + EntityPlayerMP player = (EntityPlayerMP) ((ForgePlayer) players[i]).parent; + if (playerManager.isPlayerWatchingChunk(player, chunk.getX(), chunk.getZ())) { + watchingArr[i] = true; + watching = true; + } + } + if (!watching) return; + final LongAdder size = new LongAdder(); + if (chunk instanceof VisualChunk) { + size.add(((VisualChunk) chunk).size()); + } else if (chunk instanceof CharFaweChunk) { + size.add(((CharFaweChunk) chunk).getTotalCount()); + } else { + chunk.forEachQueuedBlock(new FaweChunkVisitor() { + @Override + public void run(int localX, int y, int localZ, int combined) { + size.add(1); + } + }); + } + if (size.intValue() == 0) return; + SPacketMultiBlockChange packet = new SPacketMultiBlockChange(); + ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(); + final PacketBuffer buffer = new PacketBuffer(byteBuf); + buffer.writeInt(chunk.getX()); + buffer.writeInt(chunk.getZ()); + buffer.writeVarInt(size.intValue()); + chunk.forEachQueuedBlock(new FaweChunkVisitor() { + @Override + public void run(int localX, int y, int localZ, int combined) { + short index = (short) (localX << 12 | localZ << 8 | y); + buffer.writeShort(index); + buffer.writeVarInt(combined); + } + }); + packet.readPacketData(buffer); + for (int i = 0; i < players.length; i++) { + if (watchingArr[i]) ((EntityPlayerMP) ((ForgePlayer) players[i]).parent).connection.sendPacket(packet); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void setHeightMap(FaweChunk chunk, byte[] heightMap) { + Chunk forgeChunk = (Chunk) chunk.getChunk(); + if (forgeChunk != null) { + int[] otherMap = forgeChunk.getHeightMap(); + for (int i = 0; i < heightMap.length; i++) { + int newHeight = heightMap[i] & 0xFF; + int currentHeight = otherMap[i]; + if (newHeight > currentHeight) { + otherMap[i] = newHeight; + } + } + } + } + + protected BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(0, 0, 0); + + @Override + public CompoundTag getTileEntity(Chunk chunk, int x, int y, int z) { + Map tiles = chunk.getTileEntityMap(); + pos.setPos(x, y, z); + TileEntity tile = tiles.get(pos); + return tile != null ? getTag(tile) : null; + } + + public CompoundTag getTag(TileEntity tile) { + try { + NBTTagCompound tag = new NBTTagCompound(); + tile.writeToNBT(tag); // readTagIntoEntity + CompoundTag result = (CompoundTag) methodToNative.invoke(null, tag); + return result; + } catch (Exception e) { + MainUtil.handleError(e); + return null; + } + } + + @Override + public boolean regenerateChunk(net.minecraft.world.World world, int x, int z, BaseBiome biome, Long seed) { + if (biome != null) { + try { + if (seed == null) { + seed = world.getSeed(); + } + nmsWorld.getWorldInfo().getSeed(); + boolean result; + ChunkGeneratorOverworld generator = new ChunkGeneratorOverworld(nmsWorld, seed, false, ""); + net.minecraft.world.biome.Biome base = net.minecraft.world.biome.Biome.getBiome(biome.getId()); + net.minecraft.world.biome.Biome[] existingBiomes = new net.minecraft.world.biome.Biome[256]; + Arrays.fill(existingBiomes, base); + fieldBiomes.set(generator, existingBiomes); + boolean cold = base.getTemperature() <= 1; + IChunkGenerator existingGenerator = (IChunkGenerator) fieldChunkGenerator.get(nmsWorld.getChunkProvider()); + long existingSeed = world.getSeed(); + { + if (genLayer == null) genLayer = new MutableGenLayer(seed); + genLayer.set(biome.getId()); + Object existingGenLayer1 = fieldGenLayer1.get(nmsWorld.provider.getBiomeProvider()); + Object existingGenLayer2 = fieldGenLayer2.get(nmsWorld.provider.getBiomeProvider()); + fieldGenLayer1.set(nmsWorld.provider.getBiomeProvider(), genLayer); + fieldGenLayer2.set(nmsWorld.provider.getBiomeProvider(), genLayer); + + fieldSeed.set(nmsWorld.getWorldInfo(), seed); + + ReflectionUtils.setFailsafeFieldValue(fieldBiomeCache, this.nmsWorld.provider.getBiomeProvider(), new BiomeCache(this.nmsWorld.provider.getBiomeProvider())); + + ReflectionUtils.setFailsafeFieldValue(fieldChunkGenerator, this.nmsWorld.getChunkProvider(), generator); + + result = regenerateChunk(world, x, z); + + ReflectionUtils.setFailsafeFieldValue(fieldChunkGenerator, this.nmsWorld.getChunkProvider(), existingGenerator); + + fieldSeed.set(nmsWorld.getWorldInfo(), existingSeed); + + fieldGenLayer1.set(nmsWorld.provider.getBiomeProvider(), existingGenLayer1); + fieldGenLayer2.set(nmsWorld.provider.getBiomeProvider(), existingGenLayer2); + } + return result; + } catch (Throwable e) { + e.printStackTrace(); + } + } + return regenerateChunk(world, x, z); + } + + public boolean regenerateChunk(World world, int x, int z) { + IChunkProvider provider = world.getChunkProvider(); + if (!(provider instanceof ChunkProviderServer)) { + return false; + } + BlockFalling.fallInstantly = true; + try { + ChunkProviderServer chunkServer = (ChunkProviderServer) provider; + IChunkGenerator gen = (IChunkGenerator) fieldChunkGenerator.get(chunkServer); + long pos = ChunkPos.asLong(x, z); + Chunk mcChunk; + if (chunkServer.chunkExists(x, z)) { + mcChunk = chunkServer.loadChunk(x, z); + mcChunk.onUnload(); + } + PlayerChunkMap playerManager = ((WorldServer) getWorld()).getPlayerChunkMap(); + List oldWatchers = null; + if (chunkServer.chunkExists(x, z)) { + mcChunk = chunkServer.loadChunk(x, z); + PlayerChunkMapEntry entry = playerManager.getEntry(x, z); + if (entry != null) { + Field fieldPlayers = PlayerChunkMapEntry.class.getDeclaredField("field_187283_c"); + fieldPlayers.setAccessible(true); + oldWatchers = (List) fieldPlayers.get(entry); + playerManager.removeEntry(entry); + } + mcChunk.onUnload(); + } + try { + Field droppedChunksSetField = chunkServer.getClass().getDeclaredField("field_73248_b"); + droppedChunksSetField.setAccessible(true); + Set droppedChunksSet = (Set) droppedChunksSetField.get(chunkServer); + droppedChunksSet.remove(pos); + } catch (Throwable e) { + MainUtil.handleError(e); + } + Long2ObjectMap id2ChunkMap = chunkServer.id2ChunkMap; + id2ChunkMap.remove(pos); + mcChunk = gen.generateChunk(x, z); + id2ChunkMap.put(pos, mcChunk); + if (mcChunk != null) { + mcChunk.onLoad(); + mcChunk.populate(chunkServer, gen); + } + if (oldWatchers != null) { + for (EntityPlayerMP player : oldWatchers) { + playerManager.addPlayer(player); + } + } + return true; + } catch (Throwable t) { + MainUtil.handleError(t); + return false; + } finally { + BlockFalling.fallInstantly = false; + } + } + + @Override + public int getCombinedId4Data(ExtendedBlockStorage section, int x, int y, int z) { + IBlockState ibd = section.getData().get(x & 15, y & 15, z & 15); + Block block = ibd.getBlock(); + int id = Block.getIdFromBlock(block); + if (FaweCache.hasData(id)) { + return (id << 4) + block.getMetaFromState(ibd); + } else { + return id << 4; + } + } + + public int getNonEmptyBlockCount(ExtendedBlockStorage section) throws IllegalAccessException { + return (int) fieldNonEmptyBlockCount.get(section); + } + + public void setCount(int tickingBlockCount, int nonEmptyBlockCount, ExtendedBlockStorage section) throws NoSuchFieldException, IllegalAccessException { + fieldTickingBlockCount.set(section, tickingBlockCount); + fieldNonEmptyBlockCount.set(section, nonEmptyBlockCount); + } + + @Override + public CharFaweChunk getPrevious(CharFaweChunk fs, ExtendedBlockStorage[] sections, Map tilesGeneric, Collection[] entitiesGeneric, Set createdEntities, boolean all) throws Exception { + Map tiles = (Map) tilesGeneric; + ClassInheritanceMultiMap[] entities = (ClassInheritanceMultiMap[]) entitiesGeneric; + CharFaweChunk previous = (CharFaweChunk) getFaweChunk(fs.getX(), fs.getZ()); + char[][] idPrevious = previous.getCombinedIdArrays(); + for (int layer = 0; layer < sections.length; layer++) { + if (fs.getCount(layer) != 0 || all) { + ExtendedBlockStorage section = sections[layer]; + if (section != null) { + short solid = 0; + char[] previousLayer = idPrevious[layer] = new char[4096]; + BlockStateContainer blocks = section.getData(); + for (int j = 0; j < 4096; j++) { + int x = FaweCache.CACHE_X[0][j]; + int y = FaweCache.CACHE_Y[0][j]; + int z = FaweCache.CACHE_Z[0][j]; + IBlockState ibd = blocks.get(x, y, z); + Block block = ibd.getBlock(); + int combined = Block.getIdFromBlock(block); + if (FaweCache.hasData(combined)) { + combined = (combined << 4) + block.getMetaFromState(ibd); + } else { + combined = combined << 4; + } + if (combined > 1) { + solid++; + } + previousLayer[j] = (char) combined; + } + previous.count[layer] = solid; + previous.air[layer] = (short) (4096 - solid); + } + } + } + if (tiles != null) { + for (Map.Entry entry : tiles.entrySet()) { + TileEntity tile = entry.getValue(); + NBTTagCompound tag = new NBTTagCompound(); + tile.writeToNBT(tag); // readTileEntityIntoTag + BlockPos pos = entry.getKey(); + CompoundTag nativeTag = (CompoundTag) methodToNative.invoke(null, tag); + previous.setTile(pos.getX(), pos.getY(), pos.getZ(), nativeTag); + } + } + if (entities != null) { + for (Collection entityList : entities) { + for (Entity ent : entityList) { + if (ent instanceof EntityPlayer || (!createdEntities.isEmpty() && createdEntities.contains(ent.getUniqueID()))) { + continue; + } + int x = ((int) Math.round(ent.posX) & 15); + int z = ((int) Math.round(ent.posZ) & 15); + int y = (int) Math.round(ent.posY); + int i = FaweCache.CACHE_I[y][z][x]; + char[] array = fs.getIdArray(i); + if (array == null) { + continue; + } + int j = FaweCache.CACHE_J[y][z][x]; + if (array[j] != 0) { + String id = EntityList.getEntityString(ent); + if (id != null) { + NBTTagCompound tag = ent.getEntityData(); // readEntityIntoTag + CompoundTag nativeTag = (CompoundTag) methodToNative.invoke(null, tag); + Map map = ReflectionUtils.getMap(nativeTag.getValue()); + map.put("Id", new StringTag(id)); + previous.setEntity(nativeTag); + } + } + } + } + } + return previous; + } + + protected final static IBlockState air = Blocks.AIR.getDefaultState(); + + public void setPalette(ExtendedBlockStorage section, BlockStateContainer palette) throws NoSuchFieldException, IllegalAccessException { + Field fieldSection = ExtendedBlockStorage.class.getDeclaredField("data"); + fieldSection.setAccessible(true); + fieldSection.set(section, palette); + } + + @Override + public void sendChunk(int x, int z, int bitMask) { + Chunk chunk = getCachedChunk(getWorld(), x, z); + if (chunk != null) { + sendChunk(chunk, bitMask); + } + } + + @Override + public void refreshChunk(FaweChunk fc) { + Chunk chunk = getCachedChunk(getWorld(), fc.getX(), fc.getZ()); + if (chunk != null) { + sendChunk(chunk, fc.getBitMask()); + } + } + + public void sendChunk(Chunk nmsChunk, int mask) { + if (!nmsChunk.isLoaded()) { + return; + } + try { + ChunkPos pos = nmsChunk.getPos(); + WorldServer w = (WorldServer) nmsChunk.getWorld(); + PlayerChunkMap chunkMap = w.getPlayerChunkMap(); + int x = pos.x; + int z = pos.z; + PlayerChunkMapEntry chunkMapEntry = chunkMap.getEntry(x, z); + if (chunkMapEntry == null) { + return; + } + final ArrayDeque players = new ArrayDeque<>(); + chunkMapEntry.hasPlayerMatching(input -> { + players.add(input); + return false; + }); + boolean empty = false; + ExtendedBlockStorage[] sections = nmsChunk.getBlockStorageArray(); + for (int i = 0; i < sections.length; i++) { + if (sections[i] == null) { + sections[i] = emptySection; + empty = true; + } + } + if (mask == 0 || mask == 65535 && hasEntities(nmsChunk)) { + SPacketChunkData packet = new SPacketChunkData(nmsChunk, 65280); + for (EntityPlayerMP player : players) { + player.connection.sendPacket(packet); + } + mask = 255; + } + SPacketChunkData packet = new SPacketChunkData(nmsChunk, mask); + for (EntityPlayerMP player : players) { + player.connection.sendPacket(packet); + } + if (empty) { + for (int i = 0; i < sections.length; i++) { + if (sections[i] == emptySection) { + sections[i] = null; + } + } + } + } catch (Throwable e) { + MainUtil.handleError(e); + } + } + + public boolean hasEntities(Chunk nmsChunk) { + ClassInheritanceMultiMap[] entities = nmsChunk.getEntityLists(); + for (int i = 0; i < entities.length; i++) { + ClassInheritanceMultiMap slice = entities[i]; + if (slice != null && !slice.isEmpty()) { + return true; + } + } + return false; + } + + + @Override + public FaweChunk getFaweChunk(int x, int z) { + return new ForgeChunk_All(this, x, z); + } + + @Override + public boolean removeLighting(ExtendedBlockStorage[] sections, RelightMode mode, boolean sky) { + if (mode == RelightMode.ALL) { + for (int i = 0; i < sections.length; i++) { + ExtendedBlockStorage section = sections[i]; + if (section != null) { + section.setBlockLight(new NibbleArray()); + if (sky) { + section.setSkyLight(new NibbleArray()); + } + } + } + } + return true; + } + + @Override + public boolean hasSky() { + return nmsWorld.provider.hasSkyLight(); + } + + @Override + public void setFullbright(ExtendedBlockStorage[] sections) { + for (int i = 0; i < sections.length; i++) { + ExtendedBlockStorage section = sections[i]; + if (section != null) { + byte[] bytes = section.getSkyLight().getData(); + Arrays.fill(bytes, (byte) 255); + } + } + } + + @Override + public void relight(int x, int y, int z) { + pos.setPos(x, y, z); + nmsWorld.checkLight(pos); + } + + protected WorldServer nmsWorld; + + @Override + public World getImpWorld() { + if (nmsWorld != null || getWorldName() == null) { + return nmsWorld; + } + String[] split = getWorldName().split(";"); + int id = Integer.parseInt(split[split.length - 1]); + nmsWorld = DimensionManager.getWorld(id); + return nmsWorld; + } + + @Override + public void setSkyLight(ExtendedBlockStorage section, int x, int y, int z, int value) { + section.getSkyLight().set(x & 15, y & 15, z & 15, value); + } + + @Override + public void setBlockLight(ExtendedBlockStorage section, int x, int y, int z, int value) { + section.getBlockLight().set(x & 15, y & 15, z & 15, value); + } + + @Override + public int getSkyLight(ExtendedBlockStorage section, int x, int y, int z) { + return section.getSkyLight(x & 15, y & 15, z & 15); + } + + @Override + public int getEmmittedLight(ExtendedBlockStorage section, int x, int y, int z) { + return section.getBlockLight(x & 15, y & 15, z & 15); + } + + @Override + public int getOpacity(ExtendedBlockStorage section, int x, int y, int z) { + BlockStateContainer dataPalette = section.getData(); + IBlockState ibd = dataPalette.get(x & 15, y & 15, z & 15); + return ibd.getLightOpacity(); + } + + @Override + public int getBrightness(ExtendedBlockStorage section, int x, int y, int z) { + BlockStateContainer dataPalette = section.getData(); + IBlockState ibd = dataPalette.get(x & 15, y & 15, z & 15); + return ibd.getLightValue(); + } + + @Override + public int getOpacityBrightnessPair(ExtendedBlockStorage section, int x, int y, int z) { + BlockStateContainer dataPalette = section.getData(); + IBlockState ibd = dataPalette.get(x & 15, y & 15, z & 15); + return MathMan.pair16(ibd.getLightOpacity(), ibd.getLightValue()); + } + + @Override + public void relightBlock(int x, int y, int z) { + pos.setPos(x, y, z); + nmsWorld.checkLightFor(EnumSkyBlock.BLOCK, pos); + } + + @Override + public void relightSky(int x, int y, int z) { + pos.setPos(x, y, z); + nmsWorld.checkLightFor(EnumSkyBlock.SKY, pos); + } + + @Override + public File getSaveFolder() { + return new File(((WorldServer) getWorld()).getChunkSaveLocation(), "region"); + } +} diff --git a/forge112/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java b/forge112/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java new file mode 100644 index 00000000..9a13da4d --- /dev/null +++ b/forge112/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java @@ -0,0 +1,231 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.forge; + +import com.boydti.fawe.forge.ForgePlayerBlockBag; +import com.sk89q.util.StringUtil; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldVector; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extension.platform.AbstractPlayerActor; +import com.sk89q.worldedit.extent.inventory.BlockBag; +import com.sk89q.worldedit.internal.LocalWorldAdapter; +import com.sk89q.worldedit.internal.cui.CUIEvent; +import com.sk89q.worldedit.session.SessionKey; +import com.sk89q.worldedit.util.Location; +import io.netty.buffer.Unpooled; +import java.util.UUID; +import javax.annotation.Nullable; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketBuffer; +import net.minecraft.network.play.server.SPacketCustomPayload; +import net.minecraft.util.EnumHand; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextFormatting; + +public class ForgePlayer extends AbstractPlayerActor { + + private final EntityPlayerMP player; + + protected ForgePlayer(EntityPlayerMP player) { + this.player = player; + ThreadSafeCache.getInstance().getOnlineIds().add(getUniqueId()); + } + + @Override + public UUID getUniqueId() { + return player.getUniqueID(); + } + + @Override + public int getItemInHand() { + ItemStack is = this.player.getHeldItem(EnumHand.MAIN_HAND); + return is == null ? 0 : Item.getIdFromItem(is.getItem()); + } + + @Override + public BaseBlock getBlockInHand() { + ItemStack is = this.player.getHeldItem(EnumHand.MAIN_HAND); + return is == null ? EditSession.nullBlock : new BaseBlock(Item.getIdFromItem(is.getItem()), is.isItemStackDamageable() ? 0 : is.getItemDamage()); + } + + @Override + public String getName() { + return this.player.getName(); + } + + @Override + public BaseEntity getState() { + throw new UnsupportedOperationException("Cannot create a state from this object"); + } + + @Override + public Location getLocation() { + Vector position = new Vector(this.player.posX, this.player.posY, this.player.posZ); + return new Location( + ForgeWorldEdit.inst.getWorld(this.player.world), + position, + this.player.rotationYaw, + this.player.rotationPitch); + } + + @SuppressWarnings("deprecation") + @Override + public WorldVector getPosition() { + return new WorldVector(LocalWorldAdapter.adapt(ForgeWorldEdit.inst.getWorld(this.player.world)), this.player.posX, this.player.posY, this.player.posZ); + } + + @Override + public com.sk89q.worldedit.world.World getWorld() { + return ForgeWorldEdit.inst.getWorld(this.player.world); + } + + @Override + public double getPitch() { + return this.player.rotationPitch; + } + + @Override + public double getYaw() { + return this.player.rotationYaw; + } + + @Override + public void giveItem(int type, int amt) { + this.player.inventory.addItemStackToInventory(new ItemStack(Item.getItemById(type), amt, 0)); + } + + @Override + public void dispatchCUIEvent(CUIEvent event) { + String[] params = event.getParameters(); + String send = event.getTypeId(); + if (params.length > 0) { + send = send + "|" + StringUtil.joinString(params, "|"); + } + PacketBuffer buffer = new PacketBuffer(Unpooled.copiedBuffer(send.getBytes(WECUIPacketHandler.UTF_8_CHARSET))); + SPacketCustomPayload packet = new SPacketCustomPayload(ForgeWorldEdit.CUI_PLUGIN_CHANNEL, buffer); + this.player.connection.sendPacket(packet); + } + + @Override + public void printRaw(String msg) { + for (String part : msg.split("\n")) { + this.player.sendMessage(new TextComponentString(part)); + } + } + + @Override + public void printDebug(String msg) { + sendColorized(msg, TextFormatting.GRAY); + } + + @Override + public void print(String msg) { + sendColorized(msg, TextFormatting.LIGHT_PURPLE); + } + + @Override + public void printError(String msg) { + sendColorized(msg, TextFormatting.RED); + } + + private void sendColorized(String msg, TextFormatting formatting) { + for (String part : msg.split("\n")) { + TextComponentString component = new TextComponentString(part); + component.getStyle().setColor(formatting); + this.player.sendMessage(component); + } + } + + @Override + public void setPosition(Vector pos, float pitch, float yaw) { + this.player.connection.setPlayerLocation(pos.getX(), pos.getY(), pos.getZ(), yaw, pitch); + } + + @Override + public String[] getGroups() { + return new String[]{}; // WorldEditMod.inst.getPermissionsResolver().getGroups(this.player.username); + } + + @Override + public BlockBag getInventoryBlockBag() { + return new ForgePlayerBlockBag(player); + } + + @Override + public boolean hasPermission(String perm) { + return ForgeWorldEdit.inst.getPermissionsProvider().hasPermission(player, perm); + } + + @Nullable + @Override + public T getFacet(Class cls) { + return null; + } + + @Override + public SessionKey getSessionKey() { + return new SessionKeyImpl(player.getUniqueID(), player.getName()); + } + + private static class SessionKeyImpl implements SessionKey { + // If not static, this will leak a reference + + private final UUID uuid; + private final String name; + + private SessionKeyImpl(UUID uuid, String name) { + this.uuid = uuid; + this.name = name; + } + + @Override + public UUID getUniqueId() { + return uuid; + } + + @Nullable + @Override + public String getName() { + return name; + } + + @Override + public boolean isActive() { + // We can't directly check if the player is online because + // the list of players is not thread safe + return ThreadSafeCache.getInstance().getOnlineIds().contains(uuid); + } + + @Override + public boolean isPersistent() { + return true; + } + + } + + public static Class inject() { + return ForgePlayer.class; + } +} \ No newline at end of file diff --git a/nukkit/build.gradle b/nukkit/build.gradle index 67a0c81d..22d339c3 100644 --- a/nukkit/build.gradle +++ b/nukkit/build.gradle @@ -3,8 +3,8 @@ repositories { } dependencies { compile project(':core') - compile group: "cn.nukkit", name: "nukkit", version: "1.0-20170704.231613-609", changing: true - compile name: 'worldedit-core-6.1.4-SNAPSHOT-dist', changing: true + compile group: "cn.nukkit", name: "nukkit", version: "1.0-20170704.231613-609" + compile name: 'worldedit-core-6.1.4-SNAPSHOT-dist' } processResources { @@ -33,6 +33,9 @@ shadowJar { } archiveName = "${parent.name}-${project.name}-${parent.version}.jar" destinationDir = file '../target' + manifest { + attributes("Main-Class": "com.boydti.fawe.nukkit.core.converter.ConverterFrame") + } relocate('com.google.gson', 'com.sk89q.worldedit.internal.gson') relocate 'org.yaml.snakeyaml', 'com.boydti.fawe.yaml' diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorldEdit.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorldEdit.java index 97742a25..69a1f64b 100644 --- a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorldEdit.java +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorldEdit.java @@ -26,6 +26,7 @@ import cn.nukkit.command.CommandSender; import cn.nukkit.plugin.PluginBase; import com.boydti.fawe.Fawe; import com.boydti.fawe.config.Settings; +import com.boydti.fawe.nukkit.core.converter.ConvertCommands; import com.boydti.fawe.nukkit.optimization.FaweNukkit; import com.google.common.base.Joiner; import com.sk89q.util.yaml.YAMLProcessor; @@ -34,6 +35,7 @@ import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.event.platform.CommandEvent; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.CommandManager; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -84,6 +86,10 @@ public class NukkitWorldEdit extends PluginBase { this.platform = new NukkitPlatform(this); getServer().getPluginManager().registerEvents(new WorldEditListener(this), this); WorldEdit.getInstance().getPlatformManager().register(platform); + { + CommandManager cmdMan = CommandManager.getInstance(); + cmdMan.registerCommands(new ConvertCommands(WorldEdit.getInstance())); + } logger.info("WorldEdit for Nukkit (version " + getInternalVersion() + ") is loaded"); WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent()); } catch (Throwable e) { diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConvertCommands.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConvertCommands.java new file mode 100644 index 00000000..7c71c888 --- /dev/null +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConvertCommands.java @@ -0,0 +1,65 @@ +package com.boydti.fawe.nukkit.core.converter; + +import com.boydti.fawe.command.AnvilCommands; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.jnbt.anvil.MCAChunk; +import com.boydti.fawe.jnbt.anvil.MCAFile; +import com.boydti.fawe.jnbt.anvil.MCAFilter; +import com.boydti.fawe.jnbt.anvil.filters.DelegateMCAFilter; +import com.boydti.fawe.jnbt.anvil.filters.RemapFilter; +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.object.RunnableVal; +import com.boydti.fawe.object.clipboard.ClipboardRemapper; +import com.boydti.fawe.object.number.MutableLong; +import com.boydti.fawe.util.SetQueue; +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandPermissions; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.command.MethodCommands; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.util.command.binding.Switch; +import java.io.IOException; + +public class ConvertCommands extends MethodCommands { + public ConvertCommands(WorldEdit worldEdit) { + super(worldEdit); + } + + @Command( + aliases = {"anvil2leveldb"}, + usage = "", + help = "Convert the world between MCPE/PC values\n", + desc = "Convert the world between MCPE/PC values\n", + min = 1, + max = 1 + ) + @CommandPermissions("worldedit.anvil.anvil2leveldb") + public void anvil2leveldb(Player player, String folder, @Switch('f') boolean force) throws WorldEditException { + ClipboardRemapper mapper; + RemapFilter filter = new RemapFilter(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE); + + FaweQueue defaultQueue = SetQueue.IMP.getNewQueue(folder, true, false); + try (MCAFile2LevelDB converter = new MCAFile2LevelDB(defaultQueue.getSaveFolder().getParentFile())) { + + DelegateMCAFilter delegate = new DelegateMCAFilter(filter) { + @Override + public void finishFile(MCAFile file, MutableLong cache) { + file.forEachChunk(new RunnableVal() { + @Override + public void run(MCAChunk value) { + try { + converter.write(value); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + file.clear(); + } + }; + MCAFilter result = AnvilCommands.runWithWorld(player, folder, delegate, force); + if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(filter.getTotal())); + } + } +} diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java new file mode 100644 index 00000000..2d056c8e --- /dev/null +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java @@ -0,0 +1,395 @@ +package com.boydti.fawe.nukkit.core.converter; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweVersion; +import com.boydti.fawe.installer.BrowseButton; +import com.boydti.fawe.installer.CloseButton; +import com.boydti.fawe.installer.ImagePanel; +import com.boydti.fawe.installer.InteractiveButton; +import com.boydti.fawe.installer.InvisiblePanel; +import com.boydti.fawe.installer.MinimizeButton; +import com.boydti.fawe.installer.MovablePanel; +import com.boydti.fawe.installer.TextAreaOutputStream; +import com.boydti.fawe.installer.URLButton; +import com.boydti.fawe.jnbt.anvil.MCAFilter; +import com.boydti.fawe.jnbt.anvil.MCAQueue; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.wrappers.FakePlayer; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.Arrays; +import java.util.Date; +import java.util.Map; +import javax.imageio.ImageIO; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.SwingConstants; +import javax.swing.border.EmptyBorder; + +public class ConverterFrame extends JFrame { + private final InvisiblePanel loggerPanel; + private Color LIGHT_GRAY = new Color(0x66, 0x66, 0x66); + private Color GRAY = new Color(0x44, 0x44, 0x46); + private Color DARK_GRAY = new Color(0x33, 0x33, 0x36); + private Color DARKER_GRAY = new Color(0x26, 0x26, 0x28); + private Color INVISIBLE = new Color(0, 0, 0, 0); + private Color OFF_WHITE = new Color(200, 200, 200); + + private JTextArea loggerTextArea; + private BrowseButton browseLoad; + private BrowseButton browseSave; + + public ConverterFrame() throws Exception { + final MovablePanel movable = new MovablePanel(this); + movable.setBorder(BorderFactory.createLineBorder(new Color(0x28, 0x28, 0x29))); + + Container content = this.getContentPane(); + content.add(movable); + this.setSize(720, 640); + this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + this.setUndecorated(true); + Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize(); + int x = (int) ((dimension.getWidth() - this.getWidth()) / 2); + int y = (int) ((dimension.getHeight() - this.getHeight()) / 2); + this.setLocation(x, y); + this.setVisible(true); + this.setOpacity(0); + movable.setBackground(DARK_GRAY); + movable.setLayout(new BorderLayout()); + + fadeIn(); + + JPanel topBar = new InvisiblePanel(new BorderLayout()); + { + JPanel topBarLeft = new InvisiblePanel(); + JPanel topBarCenter = new InvisiblePanel(); + JPanel topBarRight = new InvisiblePanel(); + + JLabel title = new JLabel("(FAWE) Anvil to LevelDB converter"); + title.setHorizontalAlignment(SwingConstants.CENTER); + title.setAlignmentX(Component.RIGHT_ALIGNMENT); + title.setForeground(Color.LIGHT_GRAY); + title.setFont(new Font("Lucida Sans Unicode", Font.PLAIN, 15)); + + MinimizeButton minimize = new MinimizeButton(this); + CloseButton exit = new CloseButton(); + + topBarLeft.setPreferredSize(new Dimension(96, 36)); + try { + BufferedImage image = ImageIO.read(getClass().getResource("/axe-logo.png")); + setIconImage(image); + ImagePanel imgPanel = new ImagePanel(image); + imgPanel.setPreferredSize(new Dimension(32, 36)); + topBarLeft.add(imgPanel); + } catch (IOException ignore) {} + + topBarCenter.add(title); + topBarRight.add(minimize); + topBarRight.add(exit); + + topBar.add(topBarLeft, BorderLayout.WEST); + topBar.add(topBarCenter, BorderLayout.CENTER); + topBar.add(topBarRight, BorderLayout.EAST); + } + final JPanel mainContent = new InvisiblePanel(new BorderLayout()); + { + File world = MainUtil.getWorkingDirectory("minecraft"); + if (world != null && world.exists()) { + File saves = new File(world, "saves"); + if (saves.exists()) { + for (File file : saves.listFiles()) { + if (file.isDirectory()) { + world = file; + break; + } + } + } + } + + final InteractiveButton browseLoadText = new InteractiveButton(world.getPath(), DARKER_GRAY) { + @Override + public void actionPerformed(ActionEvent e) { + browseLoad.browse(new File(getText())); + } + }; + final InteractiveButton browseSaveText = new InteractiveButton(getDefaultOutput().getPath(), DARKER_GRAY) { + @Override + public void actionPerformed(ActionEvent e) { + browseSave.browse(new File(getText())); + } + }; + for (JButton button : Arrays.asList(browseLoadText, browseSaveText)) { + button.setForeground(OFF_WHITE); + button.setBackground(DARKER_GRAY); + button.setOpaque(true); + button.setBorder(new EmptyBorder(4, 4, 4, 4)); + } + browseLoad = new BrowseButton() { + @Override + public void onSelect(File folder) { + browseLoadText.setText(folder.getPath()); + movable.repaint(); + } + }; + browseSave = new BrowseButton() { + @Override + public void onSelect(File folder) { + browseSaveText.setText(folder.getPath()); + movable.repaint(); + } + }; + + final JPanel browseContent = new InvisiblePanel(new BorderLayout()); + final JPanel browseLoadContent = new InvisiblePanel(new BorderLayout()); + final JPanel browseSaveContent = new InvisiblePanel(new BorderLayout()); + browseSaveContent.setBorder(new EmptyBorder(10, 0, 0, 0)); + JLabel selectWorld = new JLabel("Select World:"); + selectWorld.setForeground(OFF_WHITE); + selectWorld.setPreferredSize(new Dimension(120, 0)); + JLabel output = new JLabel("Output:"); + output.setForeground(OFF_WHITE); + output.setPreferredSize(new Dimension(120, 0)); + + browseLoadContent.add(selectWorld, BorderLayout.WEST); + browseLoadContent.add(browseLoadText, BorderLayout.CENTER); + browseLoadContent.add(browseLoad, BorderLayout.EAST); + browseSaveContent.add(output, BorderLayout.WEST); + browseSaveContent.add(browseSaveText, BorderLayout.CENTER); + browseSaveContent.add(browseSave, BorderLayout.EAST); + browseContent.add(browseLoadContent, BorderLayout.NORTH); + browseContent.add(browseSaveContent, BorderLayout.SOUTH); + + InteractiveButton install = new InteractiveButton(">> Convert World <<", DARKER_GRAY) { + @Override + public void actionPerformed(ActionEvent e) { + try { + install(browseLoadText.getText(), browseSaveText.getText()); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + }; + + final JPanel installContent = new InvisiblePanel(new FlowLayout()); + install.setPreferredSize(new Dimension(Integer.MAX_VALUE, 32)); + installContent.add(install); + installContent.setBorder(new EmptyBorder(10, 0, 10, 0)); + this.loggerPanel = new InvisiblePanel(new BorderLayout()); + this.loggerPanel.setBackground(Color.GREEN); + loggerPanel.setPreferredSize(new Dimension(416, 442)); + loggerTextArea = new JTextArea(); + loggerTextArea.setBackground(Color.GRAY); + loggerTextArea.setForeground(Color.DARK_GRAY); + loggerTextArea.setFont(new Font(loggerTextArea.getFont().getName(), Font.BOLD, 9)); + loggerTextArea.setBorder(BorderFactory.createCompoundBorder(loggerTextArea.getBorder(), BorderFactory.createEmptyBorder(6, 6, 6, 6))); + JScrollPane scroll = new JScrollPane(loggerTextArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + scroll.setBackground(DARK_GRAY); + scroll.setBorder(new EmptyBorder(0, 0, 0, 0)); + loggerPanel.add(scroll); + loggerPanel.setVisible(false); + + mainContent.setBorder(new EmptyBorder(6, 32, 6, 32)); + mainContent.add(browseContent, BorderLayout.NORTH); + mainContent.add(installContent, BorderLayout.CENTER); + mainContent.add(loggerPanel, BorderLayout.SOUTH); + } + JPanel bottomBar = new InvisiblePanel(); + { + try { + InputStream stream = getClass().getResourceAsStream("/fawe.properties"); + java.util.Scanner scanner = new java.util.Scanner(stream).useDelimiter("\\A"); + String versionString = scanner.next().trim(); + scanner.close(); + FaweVersion version = new FaweVersion(versionString); + String date = new Date(100 + version.year, version.month, version.day).toGMTString(); + String build = "https://ci.athion.net/job/FastAsyncWorldEdit/" + version.build; + String commit = "https://github.com/boy0001/FastAsyncWorldedit/commit/" + Integer.toHexString(version.hash); + String footerMessage = "FAWE v" + version.major + "." + version.minor + "." + version.patch + " by Empire92 (c) 2017 (GPL v3.0)"; + URL licenseUrl = new URL("https://github.com/boy0001/FastAsyncWorldedit/blob/master/LICENSE"); + URLButton licenseButton = new URLButton(licenseUrl, footerMessage); + bottomBar.add(licenseButton); + } catch (Throwable ignore) { + ignore.printStackTrace(); + } + URL chat = new URL("https://discord.gg/ngZCzbU"); + URLButton chatButton = new URLButton(chat, "Chat"); + bottomBar.add(chatButton); + URL wiki = new URL("https://github.com/boy0001/FastAsyncWorldedit/wiki"); + URLButton wikiButton = new URLButton(wiki, "Wiki"); + bottomBar.add(wikiButton); + URL issue = new URL("https://github.com/boy0001/FastAsyncWorldedit/issues/new"); + URLButton issueButton = new URLButton(issue, "Report Issue"); + bottomBar.add(issueButton); + bottomBar.setBackground(new Color(0x26, 0x26, 0x28)); + + bottomBar.add(new InteractiveButton("Debug") { + @Override + public void actionPerformed(ActionEvent e) { + Map stacks = Thread.getAllStackTraces(); + for (Map.Entry entry : stacks.entrySet()) { + Thread thread = entry.getKey(); + Fawe.debug("--------------------------------------------------------------------------------------------"); + Fawe.debug("Thread: " + thread.getName() + " | Id: " + thread.getId() + " | Alive: " + thread.isAlive()); + for (StackTraceElement elem : entry.getValue()) { + Fawe.debug(elem.toString()); + } + } + } + }); + } + + // We want to add these a bit later + movable.add(topBar, BorderLayout.NORTH); + this.setVisible(true); + this.repaint(); + movable.add(mainContent, BorderLayout.CENTER); + this.setVisible(true); + this.repaint(); + movable.add(bottomBar, BorderLayout.SOUTH); + this.setVisible(true); + this.repaint(); + } + + private File getDefaultOutput() { + if (MainUtil.getPlatform() == MainUtil.OS.WINDOWS) { + String applicationData = System.getenv("APPDATA"); + if (applicationData != null && new File(applicationData).exists()) { + File saves = new File(new File(applicationData).getParentFile(), "Local/Packages/Microsoft.MinecraftUWP_8wekyb3d8bbwe/LocalState/games/com.mojang/minecraftWorlds"); + if (saves.exists()) return saves.getAbsoluteFile(); + } + } + return new File("."); + } + + public void prompt(String message) { + JOptionPane.showMessageDialog(null, message); + } + + public void debug(String m) { + System.out.println(m); + } + + public void install(String input, String output) throws Exception { + if (!loggerPanel.isVisible()) { + loggerPanel.setVisible(true); + this.repaint(); + TextAreaOutputStream logger = new TextAreaOutputStream(loggerTextArea); + System.setOut(logger); + System.setErr(logger); + } + if (input == null || input.isEmpty()) { + prompt("No world selected"); + return; + } + if (output == null || output.isEmpty()) { + prompt("No output folder selection"); + return; + } + if (new File(output, new File(input).getName()).exists()) { + prompt("Please select another output directory, or delete it, as there are files already there."); + return; + } + final File dirMc = new File(input); + if (!dirMc.exists()) { + prompt("Folder does not exist"); + return; + } + if (!dirMc.isDirectory()) { + prompt("You must select a folder, not a file"); + return; + } + Thread installThread = new Thread(new Runnable() { + @Override + public void run() { + FakePlayer console = FakePlayer.getConsole(); + try { + debug("Loading nukkit.jar"); + File nukkit = new File("nukkit.jar"); + if (!nukkit.exists()) { + debug("Downloading: http://ci.mengcraft.com:8080/job/nukkit/614/artifact/target/nukkit-1.0-SNAPSHOT.jar"); + URL url = new URL("http://ci.mengcraft.com:8080/job/nukkit/614/artifact/target/nukkit-1.0-SNAPSHOT.jar"); + ReadableByteChannel rbc = Channels.newChannel(url.openStream()); + FileOutputStream fos = new FileOutputStream(nukkit); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + } + MainUtil.loadURLClasspath(nukkit.toURL()); + + File newWorldFile = new File(output, dirMc.getName()); + try (MCAFile2LevelDB converter = new MCAFile2LevelDB(newWorldFile)) { + debug("Starting world conversion"); + + MCAFilter filter = converter.toFilter(); + MCAQueue queue = new MCAQueue(null, new File(dirMc, "region"), true); + MCAFilter result = queue.filterWorld(filter); + + File levelDat = new File(dirMc, "level.dat"); + if (levelDat.exists()) { + converter.copyLevelDat(levelDat); + } + converter.close(); + prompt( + "Conversion complete!\n" + + " - The world save is still being compacted, but you can close the program anytime\n" + + " - There will be another prompt when this finishes\n" + + "\n" + + "What is not converted?\n" + + " - Inventory is not copied\n" + + " - Some block nbt may not copy\n" + + " - Any custom generator settings may not work\n" + + " - May not match up with new terrain" + ); + converter.compact(); + prompt("Compaction complete!"); + } + } catch (Throwable e) { + e.printStackTrace(); + prompt("[ERROR] Conversion failed, you will have to do it manually (Nukkit server + anvil2leveldb command)"); + return; + } + } + }); + installThread.start(); + } + + public void fadeIn() { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + for (float i = 0; i <= 1.015; i += 0.016) { + ConverterFrame.this.setOpacity(Math.min(1, i)); + try { + Thread.sleep(16); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }); + thread.start(); + } + + public static void main(String[] args) throws Exception { + ConverterFrame window = new ConverterFrame(); + } +} diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/LevelDBToMCAFile.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/LevelDBToMCAFile.java new file mode 100644 index 00000000..f252cd05 --- /dev/null +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/LevelDBToMCAFile.java @@ -0,0 +1,73 @@ +package com.boydti.fawe.nukkit.core.converter; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.object.clipboard.ClipboardRemapper; +import com.boydti.fawe.util.MemUtil; +import com.sk89q.worldedit.world.registry.BundledBlockData; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import org.iq80.leveldb.DB; +import org.iq80.leveldb.Options; +import org.iq80.leveldb.impl.Iq80DBFactory; + +public class LevelDBToMCAFile implements Closeable, Runnable{ + + private final DB db; + private final ClipboardRemapper remapper; + private final ForkJoinPool pool; + + public LevelDBToMCAFile(File folder) { + try { + this.pool = new ForkJoinPool(); + this.remapper = new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE); + BundledBlockData.getInstance().loadFromResource(); + int bufferSize = (int) Math.min(Integer.MAX_VALUE, Math.max((long) (MemUtil.getFreeBytes() * 0.8), 134217728)); + this.db = Iq80DBFactory.factory.open(new File(folder, "db"), + new Options() + .createIfMissing(false) + .verifyChecksums(false) + .blockSize(262144) // 256K + .cacheSize(bufferSize) // 8MB + ); + try { + this.db.suspendCompactions(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void close() { + try { + pool.shutdown(); + pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS); + db.close(); + Fawe.debug("Done!"); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + @Override + public void run() { + db.forEach(new Consumer>() { + @Override + public void accept(Map.Entry entry) { + byte[] key = entry.getKey(); + if (key.length != 10) { + return; + } +// byte[] value = entry.getValue(); + } + }); + // TODO + } +} diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java new file mode 100644 index 00000000..48b23c76 --- /dev/null +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java @@ -0,0 +1,462 @@ +package com.boydti.fawe.nukkit.core.converter; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.jnbt.anvil.MCAChunk; +import com.boydti.fawe.jnbt.anvil.MCAFile; +import com.boydti.fawe.jnbt.anvil.MCAFilter; +import com.boydti.fawe.jnbt.anvil.filters.DelegateMCAFilter; +import com.boydti.fawe.jnbt.anvil.filters.RemapFilter; +import com.boydti.fawe.object.RunnableVal; +import com.boydti.fawe.object.clipboard.ClipboardRemapper; +import com.boydti.fawe.object.collection.ByteStore; +import com.boydti.fawe.object.io.LittleEndianOutputStream; +import com.boydti.fawe.object.number.MutableLong; +import com.boydti.fawe.util.ReflectionUtils; +import com.boydti.fawe.util.StringMan; +import com.sk89q.jnbt.ByteTag; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.IntTag; +import com.sk89q.jnbt.LongTag; +import com.sk89q.jnbt.NBTInputStream; +import com.sk89q.jnbt.NBTOutputStream; +import com.sk89q.jnbt.NamedTag; +import com.sk89q.jnbt.ShortTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.world.registry.BundledBlockData; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.DataOutput; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.LongAdder; +import java.util.zip.GZIPInputStream; +import org.iq80.leveldb.DB; +import org.iq80.leveldb.Options; +import org.iq80.leveldb.impl.Iq80DBFactory; + +public class MCAFile2LevelDB implements Closeable { + + private final ByteStore bufFinalizedState = new ByteStore(4); + private final ByteStore keyStore9 = new ByteStore(9); + private final ByteStore keyStore10 = new ByteStore(10); + private final ByteStore bufData2D = new ByteStore(512 + 256); + private final ByteStore bufSubChunkPrefix = new ByteStore(1 + 4096 + 2058 + 2048 + 2048); + + private final byte[] VERSION = new byte[] { 4 }; + private final byte[] COMPLETE_STATE = new byte[] { 2, 0, 0, 0 }; + + private final DB db; + private final ClipboardRemapper remapper; + private final ForkJoinPool pool; + private final File folder; + private boolean closed; + private LongAdder submitted = new LongAdder(); + + public MCAFile2LevelDB(File folder) { + try { + this.folder = folder; + if (!folder.exists()) { + folder.mkdirs(); + } + String worldName = folder.getName(); + try (PrintStream out = new PrintStream(new FileOutputStream(new File(folder, "levelname.txt")))) { + out.print(worldName); + } + this.pool = new ForkJoinPool(); + this.remapper = new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE); + BundledBlockData.getInstance().loadFromResource(); + this.db = Iq80DBFactory.factory.open(new File(folder, "db"), + new Options() + .createIfMissing(true) + .verifyChecksums(false) + .blockSize(262144) // 256K + .cacheSize(8388608) // 8MB + .writeBufferSize(134217728) // >=128MB + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public MCAFilter toFilter() { + RemapFilter filter = new RemapFilter(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE); + DelegateMCAFilter delegate = new DelegateMCAFilter(filter) { + @Override + public void finishFile(MCAFile file, MutableLong cache) { + file.forEachChunk(new RunnableVal() { + @Override + public void run(MCAChunk value) { + try { + write(value); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + file.clear(); + } + }; + return delegate; + } + + @Override + public void close() { + try { + if (closed == (closed = true)) return; + Fawe.debug("Collecting threads"); + pool.shutdown(); + pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS); + Fawe.debug("Closing"); + db.close(); + Fawe.debug("Done! (but still compacting)"); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + public void compact() { + // Since the library doesn't support it, only way to flush the cache is to loop over everything + try (DB newDb = Iq80DBFactory.factory.open(new File(folder, "db"), new Options() + .verifyChecksums(false) + .blockSize(262144) // 256K + .cacheSize(8388608) // 8MB + .writeBufferSize(134217728) // >=128MB + )) { + newDb.close(); + } catch (IOException e) { + e.printStackTrace(); + } + Fawe.debug("Done compacting!"); + } + + public void copyLevelDat(File in) throws IOException { + File levelDat = new File(folder, "level.dat"); + try (NBTInputStream nis = new NBTInputStream(new GZIPInputStream(new FileInputStream(in)))) { + if (!levelDat.exists()) { + levelDat.createNewFile(); + } + NamedTag named = nis.readNamedTag(); + com.sk89q.jnbt.CompoundTag tag = (CompoundTag) ((CompoundTag) (named.getTag())).getValue().get("Data"); + Map map = ReflectionUtils.getMap(tag.getValue()); + + HashSet allowed = new HashSet<>(Arrays.asList( + "Difficulty", "GameType", "Generator", "LastPlayed", "RandomSeed", "StorageVersion", "Time", "commandsEnabled", "currentTick", "rainTime", "spawnMobs", "GameRules", "SpawnX", "SpawnY", "SpawnZ" + )); + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (!allowed.contains(entry.getKey())) { + iterator.remove(); + } + } + { + Map gameRules = ((CompoundTag) map.remove("GameRules")).getValue(); + for (Map.Entry entry : gameRules.entrySet()) { + String key = entry.getKey().toLowerCase(); + String value = ((StringTag) entry.getValue()).getValue(); + if (StringMan.isEqualIgnoreCaseToAny(value, "true", "false")) { + map.put(key, new ByteTag((byte) (value.equals("true") ? 1 : 0))); + } + } + map.put("LevelName", new StringTag(folder.getName())); + map.put("StorageVersion", new IntTag(5)); + Byte difficulty = tag.getByte("Difficulty"); + map.put("Difficulty", new IntTag(difficulty == null ? 2 : difficulty)); + String generatorName = tag.getString("generatorName"); + map.put("Generator", new IntTag("flat".equalsIgnoreCase(generatorName) ? 2 : 1)); + map.put("commandsEnabled", new ByteTag((byte) 1)); + Long time = tag.getLong("Time"); + map.put("CurrentTick", new LongTag(time == null ? 0L : time)); + map.put("spawnMobs", new ByteTag((byte) 1)); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (NBTOutputStream nos = new NBTOutputStream((DataOutput) new LittleEndianOutputStream(baos))) { + nos.writeNamedTag("Name", tag); + } + LittleEndianOutputStream leos = new LittleEndianOutputStream(new FileOutputStream(levelDat)); + leos.writeInt(5); + leos.writeInt(baos.toByteArray().length); + leos.write(baos.toByteArray()); + leos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void write(MCAChunk chunk) throws IOException { + submitted.add(1); + if ((submitted.longValue() & 127) == 0) { + pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS); + long queued = pool.getQueuedTaskCount() + pool.getQueuedSubmissionCount(); + if (queued > 127) { + System.gc(); + while (queued > 64) { + try { + Thread.sleep(5); + queued = pool.getQueuedTaskCount() + pool.getQueuedSubmissionCount(); + } catch (InterruptedException e) { + break; + } + } + } + } + pool.submit(new Runnable() { + @Override + public void run() { + try { + update(getKey(chunk, Tag.Version), VERSION); + update(getKey(chunk, Tag.FinalizedState), COMPLETE_STATE); + + ByteBuffer data2d = ByteBuffer.wrap(bufData2D.get()); + int[] heightMap = chunk.getHeightMapArray(); + for (int i = 0; i < heightMap.length; i++) { + data2d.putShort((short) heightMap[i]); + } + if (chunk.biomes != null) { + System.arraycopy(chunk.biomes, 0, data2d.array(), 512, 256); + } + update(getKey(chunk, Tag.Data2D), data2d.array()); + + if (!chunk.tiles.isEmpty()) { + List tiles = new ArrayList<>(); + for (Map.Entry entry : chunk.getTiles().entrySet()) { + CompoundTag tag = entry.getValue(); + tiles.add(transform(tag)); + } + update(getKey(chunk, Tag.BlockEntity), write(tiles)); + } + + if (!chunk.entities.isEmpty()) { + List entities = new ArrayList<>(); + for (com.sk89q.jnbt.CompoundTag tag : chunk.getEntities()) { + entities.add(transform(tag)); + } + update(getKey(chunk, Tag.Entity), write(entities)); + } + + int maxLayer = chunk.ids.length - 1; + while (maxLayer >= 0 && chunk.ids[maxLayer] == null) maxLayer--; + if (maxLayer >= 0) { + byte[] key = getSectionKey(chunk, 0); + for (int layer = 0; layer <= maxLayer; layer++) { + // Set layer + key[9] = (byte) layer; + byte[] value = bufSubChunkPrefix.get(); + byte[] ids = chunk.ids[layer]; + if (ids == null) { + Arrays.fill(value, (byte) 0); + } else { + byte[] data = chunk.data[layer]; + byte[] skyLight = chunk.skyLight[layer]; + byte[] blockLight = chunk.blockLight[layer]; + + copySection(ids, value, 1); + copySection(data, value, 1 + 4096); + copySection(skyLight, value, 1 + 4096 + 2048); + copySection(blockLight, value, 1 + 4096 + 2048 + 2048); + } + update(key, value); + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + }); + } + + private void update(byte[] key, byte[] value) { + db.put(key.clone(), value.clone()); + } + + private void copySection(byte[] src, byte[] dest, int destPos) { + int len = src.length; + switch (src.length) { + case 4096: { + int index = 0; + int i1, i2, i3; + for (int y = 0; y < 16; y++) { + i1 = y; + for (int z = 0; z < 16; z++) { + i2 = i1 + (z << 4); + for (int x = 0; x < 16; x++) { + i3 = i2 + (x << 8); + dest[destPos + i3] = src[index]; + index++; + } + } + } + break; + } + case 2048: { + int index = 0; + int i1, i2, i3, i4; + for (int x = 0; x < 16;) { + { + i1 = x; + for (int z = 0; z < 16; z++) { + i2 = i1 + (z << 4); + for (int y = 0; y < 16; y += 2) { + i3 = i2 + (y << 8); + i4 = i2 + ((y + 1) << 8); + byte newVal = (byte) ((src[i3 >> 1] & 0xF) + ((src[i4 >> 1] & 0xF) << 4)); + dest[destPos + index] = newVal; + index++; + } + } + } + x++; + { + i1 = x; + for (int z = 0; z < 16; z++) { + i2 = i1 + (z << 4); + for (int y = 0; y < 16; y += 2) { + i3 = i2 + (y << 8); + i4 = i2 + ((y + 1) << 8); + byte newVal = (byte) (((src[i3 >> 1] & 0xF0) >> 4) + ((src[i4 >> 1] & 0xF0))); + dest[destPos + index] = newVal; + index++; + } + } + } + x++; + + } + break; + } + default: + System.arraycopy(src, 0, dest, destPos, len); + } + } + + private enum Tag { + Data2D(45), + @Deprecated Data2DLegacy(46), + SubChunkPrefix(47), + @Deprecated LegacyTerrain(48), + BlockEntity(49), + Entity(50), + PendingTicks(51), + BlockExtraData(52), + BiomeState(53), + FinalizedState(54), + Version(118), + + ; + public final byte value; + + Tag(int value) { + this.value = (byte) value; + } + + public byte[] fill(int chunkX, int chunkZ, byte[] key) { + key[0] = (byte) (chunkX & 255); + key[1] = (byte) (chunkX >>> 8 & 255); + key[2] = (byte) (chunkX >>> 16 & 255); + key[3] = (byte) (chunkX >>> 24 & 255); + key[4] = (byte) (chunkZ & 255); + key[5] = (byte) (chunkZ >>> 8 & 255); + key[6] = (byte) (chunkZ >>> 16 & 255); + key[7] = (byte) (chunkZ >>> 24 & 255); + key[8] = value; + return key; + } + } + + private byte[] write(Collection tags) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + NBTOutputStream nos = new NBTOutputStream(baos); + nos.setLittleEndian(); + for (com.sk89q.jnbt.Tag tag : tags) { + nos.writeNamedTag("", tag); + } + nos.close(); + return baos.toByteArray(); + } + + private String convertId(String input) { + input = input.replace("minecraft:", ""); + StringBuilder result = new StringBuilder(); + boolean toUpper = false; + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + if (i == 0) toUpper = true; + if (c == '_') { + toUpper = true; + } else { + result.append(toUpper ? Character.toUpperCase(c) : c); + toUpper = false; + } + } + return result.toString(); + } + + private CompoundTag transform(CompoundTag tag) { + try { + String id = tag.getString("id"); + if (id != null) { + Map map = ReflectionUtils.getMap(tag.getValue()); + id = convertId(id); + map.put("id", new StringTag(id)); + { // Convert items + com.sk89q.jnbt.ListTag items = tag.getListTag("Items"); + for (CompoundTag item : (List) (List) items.getValue()) { + String itemId = item.getString("id"); + BaseBlock state = BundledBlockData.getInstance().findByState(itemId); + if (state != null) { + int legacy = state.getId(); + ReflectionUtils.getMap(item.getValue()).put("id", new ShortTag((short) legacy)); + } + } + } + switch (id) { + case "MobSpawner": { + map.clear(); + break; + } + case "Sign": { + for (int line = 1; line <= 4; line++) { + String key = "Text" + line; + String text = tag.getString(key); + if (text != null && text.startsWith("{")) { + map.put(key, new StringTag(BBC.jsonToString(text))); + } + + } + } + } + } + } catch (Throwable e) { + Fawe.debug("Error converting tag: " + tag); + e.printStackTrace(); + } + return tag; + } + + private byte[] getSectionKey(MCAChunk chunk, int layer) { + byte[] key = Tag.SubChunkPrefix.fill(chunk.getX(), chunk.getZ(), keyStore10.get()); + key[9] = (byte) layer; + return key; + } + + private byte[] getKey(MCAChunk chunk, Tag tag) { + + return tag.fill(chunk.getX(), chunk.getZ(), keyStore9.get()); + } +} diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitQueue.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitQueue.java index 1fbbe319..e95aedd7 100644 --- a/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitQueue.java +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitQueue.java @@ -130,6 +130,9 @@ public class NukkitQueue extends NMSMappedFaweQueueBUMaDxy-;mc3YJ?->>mNtlMcN+x9y z6%rZYOIl_%N+K+@D3+8;nOIq_v24tdHrHC~p8hz$v1d>B*>j(J&wbtf!NuR0JNKU7 zIrn?Mzn63FAf=Q=CXq>x@$DkNw+-n*ik)|_#OXN)gINLSVN4`DiTWUcAiGFsG6`66 z1yDqhpH0Ao6`*cXs(}ii6F3s|WXw6h4@rHUUFsGv2ABkl2hImZ12sS;Fih;y4YUFE zzn$bK~%)p9Wk5Oc%qKBc}l0h6zFCFeo>AkPy`0?<;{i;9j8ChnJaT_8i*Oja1xy8=1x_`H6k~&W<}^W26|fw* zH2`*WYz9I7>l1-D<=-IW%%I*}D?+$1rC$bY5778M16&bA!}&Bci9xyRfLDVuPJx^V z{1{X)F6f|>=?h>k@LEX5M;tff_Q{0=L8j$KU{%O(?Fm6TVk8KHW<_9pfYbBtSq=m2 zS5I0v`Xb=O z-+EG5&||_vqL!Qb>0_mYEQXe2pRgP;KXNv3PfTENig?AK^9J_=Uje@Zhk>7fL%=)o z+$D)Z+#RtT@HucsjGk#Xun>3!o!IGb&3IhiUfaz81Q`qRDm~6tlI2lgX)y*B=cIfk z@I8G)!ATSwm;t<3DimSSH{@Du1uu8C_j=W(!1z6OSXiu|ULCP=7h*5)HJ|iIO)&_v zT)aL}=#yf$@cNa{iuKni7UU-hZmMQyCdmwsJ4rjRqF8^N95JheUhb?e*Kq^5G$qDw z1E!%%S{(ZuM;0^=_#p*vz+N%_zEX}(+#qOH3XK0TaIL%nRw;@UCkO(#B;m$?8Mp#y z#bZV+N)}`R2Z0^vTFf@Ny7PXY6IwS~{Di8Q4ZwHk!d_Q>cZvnYfd!T0Esp~x1LvZP zvZ8k08iA964mbZftXNQ3dQN(S*aBSOA^EAR-g zp1xVMqHjPFA;_&%pyTr^2|uyZ0W<*b10Ml<;_iqAp?9Nm%Htv!{2{V7tpIkRlS{E> zK|$yhzze|bF@eRAX<|X8q#9Uc#kWnpuz~ zGkzV>ma#a&`B}0c$2u0|S7sA#(XLxrD;~a;R!0orVd36!3+Ay9pV-L&$elRpb@~^^cGC{Sv!J|Wx%Fl?I#_AYSC3_qhm@4K1GMuyZVa- zC6(!1C5x@!aI_6PgRbek(;#&12hMSTo|3VkB*+86QwE;76J7qYT0#}O4B4RxxDYpc z%9K);Qi{CGim_g)lv<_C$RVZF1$pgTN~!Gz+C89@n&7ryPEJ=`*-j1cA$lXWU)dv0 z(bj^X-5Q_m?nWnu_PXs?QXpu8gcVGj`>+YP1$`D!p^O54#@iOC4Zv;aIw1Fw00_ER z;KHx`tCU)zl**O3?>8RWUM}sH-A4+HAE(PwO5LrL`p1Cr|5QrNa(UmCWwd)#DOKmE zpE(ETDh`rEAH1=^5F?oe>~z`3T`t>g0j@^ZeOi7x8i&wvzCKJMnI_5;8_;#8O=Ud8 zP8aQdfKIx%VqlwUoEF>hve=q9cw@aRwjE{uT@^ZaJVFfI3>=VvTB9%=V~6?0i3Od9 z-ilgdpsgn*DjNH|MqD6h9`GW%(8`Z=0e6XKYm<SFE`NEK6*>Rt)vY=6c9947F!cVQrm+a=etRX z1Pwtw)oCV9vE7C~e7lA8Y+()b)Wpfa7lw?#8lBv3A>p4a!C{+f5Q91!eV(U@#dZ-o zX`afjdYFbFOW4U0LyPSkdQTa#(mDX9SkMr3nMj?1=W3M2_G{Al6%XSqXf(P&%ESR1 zUrJ1;nE@asAgC5y8aT$l^Yp~6^8gv51?~X$oU7fZR9a7H3 zB?K)pVEj(>MuoKl)w%M+g8ZH;a*)0y+Zo#_lLa*a$9XjVPGLH~Wb@iP1T}gbfO>SP zNP7l3{#ejr58;95gk~PeSdD)cG#Guj-ShNc&2SXGWpoWXXKQD3*_$|R&=`dNZsJDZ zB>5RYiT;-?v|BRs1>=Y*^fsIYz;D3c!sQPGzlwvlHG`4hGh&0$`QDMhF+wq0(PtxP l&Q*XAlu5)8l<6Vr{{iqu_R|~%0HXi^002ovPDHLkV1fb(yz&45 literal 0 HcmV?d00001 diff --git a/settings.gradle b/settings.gradle index 6e956331..5f4acd2d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,3 @@ rootProject.name = 'FastAsyncWorldEdit' -include 'core', 'bukkit' , 'favs', 'nukkit', 'sponge', 'forge1710', 'forge189', 'forge194', 'forge110', 'forge111' \ No newline at end of file +include 'core', 'bukkit', 'favs', 'nukkit', 'forge1710', 'forge189', 'forge194', 'forge110', 'forge111', 'sponge', 'forge112' \ No newline at end of file diff --git a/sponge/src/main/java/com/boydti/fawe/sponge/FaweSponge.java b/sponge/src/main/java/com/boydti/fawe/sponge/FaweSponge.java index a007812c..6882dca9 100644 --- a/sponge/src/main/java/com/boydti/fawe/sponge/FaweSponge.java +++ b/sponge/src/main/java/com/boydti/fawe/sponge/FaweSponge.java @@ -10,6 +10,7 @@ import com.boydti.fawe.object.FaweQueue; import com.boydti.fawe.regions.FaweMaskManager; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.TaskManager; +import com.sk89q.worldedit.sponge.chat.SpongeChatManager; import com.sk89q.worldedit.world.World; import java.io.File; import java.util.ArrayList; @@ -37,6 +38,7 @@ public class FaweSponge implements IFawe { Fawe.set(this); Fawe.setupInjector(); com.sk89q.worldedit.sponge.SpongePlayer.inject(); + Fawe.get().setChatManager(new SpongeChatManager()); } catch (final Throwable e) { MainUtil.handleError(e); } diff --git a/sponge/src/main/java/com/sk89q/worldedit/sponge/chat/SpongeChatManager.java b/sponge/src/main/java/com/sk89q/worldedit/sponge/chat/SpongeChatManager.java new file mode 100644 index 00000000..d343f637 --- /dev/null +++ b/sponge/src/main/java/com/sk89q/worldedit/sponge/chat/SpongeChatManager.java @@ -0,0 +1,167 @@ +package com.sk89q.worldedit.sponge.chat; + +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.sponge.SpongePlayer; +import com.boydti.fawe.util.chat.ChatManager; +import com.boydti.fawe.util.chat.Message; +import com.boydti.fawe.wrappers.FakePlayer; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.text.action.TextActions; +import org.spongepowered.api.text.format.TextColor; +import org.spongepowered.api.text.format.TextColors; +import org.spongepowered.api.text.format.TextStyle; +import org.spongepowered.api.text.format.TextStyles; +import org.spongepowered.api.text.serializer.TextSerializers; + +public class SpongeChatManager implements ChatManager { + + @Override + public Text.Builder builder() { + return Text.builder(); + } + + @Override + public void color(Message message, String color) { + TextColor tc = null; + TextStyle ts = null; + switch (color.charAt(1)) { + case 'a': + tc = TextColors.GREEN; + break; + case 'b': + tc = TextColors.AQUA; + break; + case 'c': + tc = TextColors.RED; + break; + case 'd': + tc = TextColors.LIGHT_PURPLE; + break; + case 'e': + tc = TextColors.YELLOW; + break; + case 'f': + tc = TextColors.WHITE; + break; + case '1': + tc = TextColors.DARK_BLUE; + break; + case '2': + tc = TextColors.DARK_GREEN; + break; + case '3': + tc = TextColors.DARK_AQUA; + break; + case '4': + tc = TextColors.DARK_RED; + break; + case '5': + tc = TextColors.DARK_PURPLE; + break; + case '6': + tc = TextColors.GOLD; + break; + case '7': + tc = TextColors.GRAY; + break; + case '8': + tc = TextColors.DARK_GRAY; + break; + case '9': + tc = TextColors.BLUE; + break; + case '0': + tc = TextColors.BLACK; + break; + case 'k': + ts = TextStyles.OBFUSCATED; + break; + case 'l': + ts = TextStyles.BOLD; + break; + case 'm': + ts = TextStyles.UNDERLINE; + break; + case 'n': + ts = TextStyles.STRIKETHROUGH; + break; + case 'o': + ts = TextStyles.ITALIC; + break; + case 'r': + tc = TextColors.RESET; + break; + } + if (tc != null) { + apply(message, getChild(message).color(tc)); + } + if (ts != null) { + apply(message, getChild(message).style(ts)); + } + } + + public Text.Builder getChild(Message m) { + Text.Builder builder = m.$(this); + List children = builder.getChildren(); + Text last = children.get(children.size() - 1); + builder.remove(last); + return Text.builder().append(last); + } + + public void apply(Message m, Text.Builder builder) { + m.$(this).append(builder.build()); + } + + @Override + public void tooltip(Message message, Message... tooltips) { + Text.Builder builder = Text.builder(); + boolean lb = false; + for (Message tooltip : tooltips) { + if (lb) { + builder.append(Text.of("\n")); + } + builder.append(tooltip.$(this).build()); + lb = true; + } + apply(message, getChild(message).onHover(TextActions.showText(builder.toText()))); + } + + @Override + public void command(Message message, String command) { + apply(message, getChild(message).onClick(TextActions.runCommand(command))); + } + + @Override + public void text(Message message, String text) { + message.$(this).append(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(BBC.color(text))); + } + + @Override + public void send(Message Message, FawePlayer player) { + if (player == FakePlayer.getConsole().toFawePlayer()) { + player.sendMessage(Message.$(this).build().toPlain()); + } else { + ((SpongePlayer) player).parent.sendMessage(Message.$(this).build()); + } + } + + @Override + public void suggest(Message Message, String command) { + apply(Message, getChild(Message).onClick(TextActions.suggestCommand(command))); + } + + @Override + public void link(Message message, String url) { + try { + if (!url.isEmpty()) { + apply(message, getChild(message).onClick(TextActions.openUrl(new URL(url)))); + } + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } +}