diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 87c970593..a6086b9a6 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -24,7 +24,7 @@ assignees: '' **Required Information** - FAWE Version Number (`/version FastAsyncWorldEdit`): - Spigot/Paper Version Number (`/version`): -- Minecraft Version: [e.g. 1.16.3] +- Minecraft Version: [e.g. 1.16.4] **Describe the bug** A clear and concise description of what the bug is. @@ -43,5 +43,5 @@ Steps to reproduce the behavior: - [] I included all information required in the sections above - [] I made sure there are no duplicates of this report [(Use Search)](https://github.com/IntellectualSites/FastAsyncWorldEdit/issues?q=is%3Aissue) -- [] I made sure I am using an up-to-date version of [FastAsyncWorldEdit for 1.16.3](https://ci.athion.net/job/FastAsyncWorldEdit-1.16/) +- [] I made sure I am using an up-to-date version of [FastAsyncWorldEdit for 1.16.4](https://ci.athion.net/job/FastAsyncWorldEdit-1.16/) - [] I made sure the bug/error is not caused by any other plugin diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 0695e00a9..9bff9d47b 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,7 +2,7 @@ name: Feature request about: Suggest an idea for this project title: '' -labels: "[+] Enhancement" +labels: "Enhancement" assignees: '' --- diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..5ea2507b6 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,18 @@ +daysUntilStale: 60 +daysUntilClose: 7 +only: issues +exemptLabels: + - "Bug" + - "Enhancement" + - "Approved" + - "Priority" + - "Under investigation" +staleLabel: "resolution: stale" +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +closeComment: > + This issue has been automatically closed because it has not had activity in + a long time. If the issue still applies to the most recent supported + version, please open a new issue referencing this original issue. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..eb9597bb4 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,50 @@ +name: "build" + +on: ["pull_request", "push"] + +jobs: + build: + strategy: + matrix: + java: ["1.8", "11"] + os: ["ubuntu-18.04"] + runs-on: "${{ matrix.os }}" + steps: + - name: "Checkout Repository" + uses: "actions/checkout@v2.3.4" + - name: "Setup JDK ${{ matrix.java }}" + uses: "actions/setup-java@v1.4.3" + with: + java-version: "${{ matrix.java }}" + - name: "Cache Gradle" + uses: "actions/cache@v2.1.3" + with: + path: | + "~/.gradle/caches" + "~/.gradle/wrapper" + key: "${{ runner.os }}-${{ matrix.java }}-gradle-${{ hashFiles('**/*.gradle*') }}" + restore-keys: | + "${{ runner.os }}-${{ matrix.java }}-gradle-" + - name: "Cache Local Maven Repository" + uses: "actions/cache@v2.1.3" + with: + path: "~/.m2/repository" + key: "${{ runner.os }}-${{ matrix.java }}-maven-${{ hashFiles('**/pom.xml') }}" + restore-keys: | + "${{ runner.os }}-${{ matrix.java }}-maven-" + - name: "Cache BuildTools Decompiled Code" + uses: "actions/cache@v2.1.3" + with: + path: "$GITHUB_WORKSPACE/work" + key: "${{ runner.os }}-buildtools" + restore-keys: | + "${{ runner.os }}-buildtools" + - name: "Test Enviornment" + run: "echo $GITHUB_WORKSPACE" + - name: "Download BuildTools" + run: "wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar" + - name: "Run BuildTools" + run: "java -jar BuildTools.jar --rev 1.16.4" + - name: "Clean Build" + run: "./gradlew clean build sourcesJar javadocJar" + diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml deleted file mode 100644 index 5f45bcd47..000000000 --- a/.github/workflows/gradle.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Java CI - -on: - push: - branches: - - 'main' - pull_request: - branches: - - 'main' - -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - java: [1.8, 1.11] - - steps: - - name: Checkout - uses: actions/checkout@v2.3.2 - - name: Setup Java JDK - uses: actions/setup-java@v1.4.2 - with: - java-version: ${{ matrix.java }} - - name: Cache Gradle - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-${{ matrix.java }}-gradle-${{ hashFiles('**/*.gradle*') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.java }}-gradle- - - name: Cache local Maven repository - uses: actions/cache@v2 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-${{ matrix.java }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.java }}-maven- - - name: Cache BuildTools decompiled code - uses: actions/cache@v2 - with: - path: $GITHUB_WORKSPACE/work - key: ${{ runner.os }}-buildtools - restore-keys: | - ${{ runner.os }}-buildtools - - name: Test enviornment - run: echo $GITHUB_WORKSPACE - - name: Download BuildTools - run: wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar - - name: Run BuildTools - run: java -jar BuildTools.jar --rev 1.16.2 - - name: Test with Gradle - run: ./gradlew clean build sourcesJar javadocJar - diff --git a/.github/workflows/validate-gradle-wrapper.yml b/.github/workflows/validate-gradle-wrapper.yml new file mode 100644 index 000000000..b4c46f704 --- /dev/null +++ b/.github/workflows/validate-gradle-wrapper.yml @@ -0,0 +1,12 @@ +name: "validate gradle wrapper" + +on: ["pull_request", "push"] + +jobs: + build: + runs-on: "ubuntu-18.04" + steps: + - name: "Checkout Repository" + uses: "actions/checkout@v2.3.4" + - name: "Validate Gradle Wrapper" + uses: "gradle/wrapper-validation-action@v1.0.3" \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/LibsConfig.kt b/buildSrc/src/main/kotlin/LibsConfig.kt index 172be5d39..a84f4f5d9 100644 --- a/buildSrc/src/main/kotlin/LibsConfig.kt +++ b/buildSrc/src/main/kotlin/LibsConfig.kt @@ -25,6 +25,11 @@ fun Project.applyLibrariesConfiguration() { group = "${rootProject.group}.worldedit-libs" + val relocations = mapOf( + "net.kyori.text" to "com.sk89q.worldedit.util.formatting.text", + "net.kyori.minecraft" to "com.sk89q.worldedit.util.kyori" + ) + tasks.register("jar") { configurations = listOf(project.configurations["shade"]) archiveClassifier.set("") @@ -36,13 +41,15 @@ fun Project.applyLibrariesConfiguration() { exclude(dependency("org.slf4j:slf4j-api")) } - relocate("net.kyori.text", "com.sk89q.worldedit.util.formatting.text") + relocations.forEach { (from, to) -> + relocate(from, to) + } } val altConfigFiles = { artifactType: String -> val deps = configurations["shade"].incoming.dependencies .filterIsInstance() .map { it.copy() } - .map { dependency: ModuleDependency -> + .map { dependency -> dependency.artifact { name = dependency.name type = artifactType @@ -61,13 +68,15 @@ fun Project.applyLibrariesConfiguration() { from({ altConfigFiles("sources") }) - val filePattern = Regex("(.*)net/kyori/text((?:/|$).*)") - val textPattern = Regex("net\\.kyori\\.text") - eachFile { - filter { - it.replaceFirst(textPattern, "com.sk89q.worldedit.util.formatting.text") + relocations.forEach { (from, to) -> + val filePattern = Regex("(.*)${from.replace('.', '/')}((?:/|$).*)") + val textPattern = Regex.fromLiteral(from) + eachFile { + filter { + it.replaceFirst(textPattern, to) + } + path = path.replaceFirst(filePattern, "$1${to.replace('.', '/')}$2") } - path = path.replaceFirst(filePattern, "$1com/sk89q/worldedit/util/formatting/text$2") } archiveClassifier.set("sources") } diff --git a/contrib/craftscripts/README.txt b/contrib/craftscripts/README.md similarity index 66% rename from contrib/craftscripts/README.txt rename to contrib/craftscripts/README.md index c44072d8f..aac91ed62 100644 --- a/contrib/craftscripts/README.txt +++ b/contrib/craftscripts/README.md @@ -5,10 +5,9 @@ Example usage: /cs maze.js glowstone 10 10 You may or may not install these scripts -- it is optional. If you are, however, -place the entire craftscripts/ folder into the respective directory for the platform +place the entire `craftscripts/` folder into the respective directory for the platform that you have installed WorldEdit. In order to be able to use CraftScripts, you must install the Rhino JavaScript library. The installation page linked above has information about that. More information -about scripts in general can be found at -https://worldedit.enginehub.org/en/latest/usage/other/craftscripts/ \ No newline at end of file +about scripts in general can be found [on the WorldEdit docs](https://worldedit.enginehub.org/en/latest/usage/other/craftscripts/) \ No newline at end of file diff --git a/contrib/craftscripts/SUBMITTING.md b/contrib/craftscripts/SUBMITTING.md new file mode 100644 index 000000000..06d016bda --- /dev/null +++ b/contrib/craftscripts/SUBMITTING.md @@ -0,0 +1,5 @@ +Write a cool script? You can submit a pull request to [our GitHub Repository](https://github.com/IntellectualSites/FastAsyncWorldEdit). +We will consider your script for inclusion in the FastAsyncWorldEdit repository. CraftScripts in the FastAsyncWorldEdit repository are +licensed under GPLv3, like the rest of FastAsybcWorldEdit. + +You can also post your scripts on [our Discord](https://discord.gg/KxkjDVg) in the `#sharing-is-caring` channel. \ No newline at end of file diff --git a/contrib/craftscripts/SUBMITTING.txt b/contrib/craftscripts/SUBMITTING.txt deleted file mode 100644 index c16ebc290..000000000 --- a/contrib/craftscripts/SUBMITTING.txt +++ /dev/null @@ -1,10 +0,0 @@ -Write a cool script? Send it to sk89q (somehow) or you can submit a pull -request on https://github.com/sk89q/worldedit. He will consider your script -for inclusion in WorldEdit releases. Please license your script with a -permissive open source license such as GPLv2, MIT, BSD, WTFPL, etc. - -Note: Legally you should not release things to the public domain as not -all countries have the concept of public domain in their copyright law. - -You can also post your scripts here: -https://discord.gg/KxkjDVg or http://forum.sk89q.com/forums/craftscripts.6/ \ No newline at end of file diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index d592b694e..eb767c152 100644 --- a/worldedit-bukkit/build.gradle.kts +++ b/worldedit-bukkit/build.gradle.kts @@ -39,16 +39,23 @@ dependencies { "api"(project(":worldedit-core")) "api"(project(":worldedit-libs:bukkit")) "compile"(":worldedit-adapters:") + // Paper-patched NMS jars + "compile"("com.destroystokyo.paperv1_15_r1:paperv1_15_r1:1_15_r1") + "compile"("com.destroystokyo.paperv1_16_r1:paperv1_16_r1:1_16_r1") + "compile"("com.destroystokyo.paperv1_16_r2:paperv1_16_r2:1_16_r2") + "compile"("com.destroystokyo.paperv1_16_r3:paperv1_16_r3:1_16_r3") "compile"("org.spigotmcv1_15_r1:spigotmcv1_15_r1:1_15_r1") "compile"("org.spigotmcv1_16_r1:spigotmcv1_16_r1:1_16_r1") + "compile"("org.spigotmcv1_16_r2:spigotmcv1_16_r2:1_16_r2") + "compile"("org.spigotmcv1_16_r3:spigotmcv1_16_r3:1_16_r3") "implementation"("it.unimi.dsi:fastutil:${Versions.FAST_UTIL}") - "api"("com.destroystokyo.paper:paper-api:1.16.2-R0.1-SNAPSHOT") { + "api"("com.destroystokyo.paper:paper-api:1.16.4-R0.1-SNAPSHOT") { exclude("junit", "junit") isTransitive = false } "compileOnly"("org.jetbrains:annotations:20.1.0") "testCompileOnly"("org.jetbrains:annotations:20.1.0") - "compileOnly"("org.spigotmc:spigot:1.16.2-R0.1-SNAPSHOT") + "compileOnly"("org.spigotmc:spigot:1.16.4-R0.1-SNAPSHOT") "implementation"("io.papermc:paperlib:1.0.4") "compileOnly"("com.sk89q:dummypermscompat:1.10") { exclude("com.github.MilkBowl", "VaultAPI") diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java index d8006bbc6..7079aea51 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java @@ -21,7 +21,7 @@ import com.boydti.fawe.bukkit.util.ItemUtil; import com.boydti.fawe.bukkit.util.image.BukkitImageViewer; import com.boydti.fawe.config.Settings; import com.boydti.fawe.regions.FaweMaskManager; -import com.boydti.fawe.util.Jars; +import com.boydti.fawe.util.ThirdPartyManager; import com.boydti.fawe.util.TaskManager; import com.boydti.fawe.util.WEManager; import com.boydti.fawe.util.image.ImageViewer; @@ -123,7 +123,7 @@ public class FaweBukkit implements IFawe, Listener { if (manager.getPlugin("PacketListenerApi") == null) { File output = new File(plugin.getDataFolder().getParentFile(), "PacketListenerAPI_v3.7.6-SNAPSHOT.jar"); - byte[] jarData = Jars.PL_v3_7_6.download(); + byte[] jarData = ThirdPartyManager.PacketListenerAPI.download(); try (FileOutputStream fos = new FileOutputStream(output)) { fos.write(jarData); } @@ -131,7 +131,7 @@ public class FaweBukkit implements IFawe, Listener { if (manager.getPlugin("MapManager") == null) { File output = new File(plugin.getDataFolder().getParentFile(), "MapManager_v1.7.8-SNAPSHOT.jar"); - byte[] jarData = Jars.MM_v1_7_8.download(); + byte[] jarData = ThirdPartyManager.MapManager.download(); try (FileOutputStream fos = new FileOutputStream(output)) { fos.write(jarData); } diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_2/BukkitAdapter_1_16_2.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_2/BukkitAdapter_1_16_2.java index d0c5fb2cd..f75477317 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_2/BukkitAdapter_1_16_2.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_2/BukkitAdapter_1_16_2.java @@ -9,6 +9,7 @@ import com.boydti.fawe.object.collection.BitArrayUnstretched; import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.ReflectionUtils; import com.boydti.fawe.util.TaskManager; +import com.destroystokyo.paper.util.misc.PooledLinkedHashSets; import com.mojang.datafixers.util.Either; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.block.BlockState; @@ -25,6 +26,7 @@ import net.minecraft.server.v1_16_R2.DataBits; import net.minecraft.server.v1_16_R2.DataPalette; import net.minecraft.server.v1_16_R2.DataPaletteBlock; import net.minecraft.server.v1_16_R2.DataPaletteLinear; +import net.minecraft.server.v1_16_R2.EntityPlayer; import net.minecraft.server.v1_16_R2.GameProfileSerializer; import net.minecraft.server.v1_16_R2.IBlockData; import net.minecraft.server.v1_16_R2.PacketPlayOutLightUpdate; @@ -205,14 +207,40 @@ public final class BukkitAdapter_1_16_2 extends NMSAdapter { playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { p.playerConnection.sendPacket(chunkpacket); }); - } - if (lighting) { - boolean trustEdges = true; //This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad) - PacketPlayOutLightUpdate packet = new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine(), trustEdges); - playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { - p.playerConnection.sendPacket(packet); - }); + if (lighting) { + boolean trustEdges = true; //This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad) + PacketPlayOutLightUpdate packet = new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine(), trustEdges); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(packet); + }); + } + } else if (PaperLib.isPaper()) { + //Require generic here to work with multiple dependencies trying to take control. + PooledLinkedHashSets.PooledObjectLinkedOpenHashSet objects = + nmsWorld.getChunkProvider().playerChunkMap.playerViewDistanceNoTickMap.getObjectsInRange(chunkX, chunkZ); + if (objects == null) { + return null; + } + for (Object obj : objects.getBackingSet()) { + if (obj == null) { + continue; + } + EntityPlayer p = (EntityPlayer) obj; + Chunk chunk = nmsWorld.getChunkProvider().getChunkAtIfLoadedImmediately(chunkX, chunkZ); + if (chunk != null) { + PacketPlayOutMapChunk chunkpacket = new PacketPlayOutMapChunk(chunk, 65535); + p.playerConnection.sendPacket(chunkpacket); + + if (lighting) { + boolean trustEdges = + true; //This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad) + PacketPlayOutLightUpdate packet = + new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine(), trustEdges); + p.playerConnection.sendPacket(packet); + } + } + } } return null; }); diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BlockMaterial_1_16_4.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BlockMaterial_1_16_4.java new file mode 100644 index 000000000..f3d40b448 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BlockMaterial_1_16_4.java @@ -0,0 +1,160 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_4; + +import com.sk89q.util.ReflectionUtil; +import com.sk89q.worldedit.world.registry.BlockMaterial; +import net.minecraft.server.v1_16_R3.Block; +import net.minecraft.server.v1_16_R3.BlockAccessAir; +import net.minecraft.server.v1_16_R3.BlockBase; +import net.minecraft.server.v1_16_R3.BlockPosition; +import net.minecraft.server.v1_16_R3.EnumPistonReaction; +import net.minecraft.server.v1_16_R3.IBlockData; +import net.minecraft.server.v1_16_R3.ITileEntity; +import net.minecraft.server.v1_16_R3.Material; +import org.bukkit.craftbukkit.v1_16_R3.block.data.CraftBlockData; + +public class BlockMaterial_1_16_4 implements BlockMaterial { + private final Block block; + private final IBlockData defaultState; + private final Material material; + private final boolean isTranslucent; + private final CraftBlockData craftBlockData; + private final org.bukkit.Material craftMaterial; + private final int opacity; + + public BlockMaterial_1_16_4(Block block) { + this(block, block.getBlockData()); + } + + public BlockMaterial_1_16_4(Block block, IBlockData defaultState) { + this.block = block; + this.defaultState = defaultState; + this.material = defaultState.getMaterial(); + this.craftBlockData = CraftBlockData.fromData(defaultState); + this.craftMaterial = craftBlockData.getMaterial(); + BlockBase.Info blockInfo = ReflectionUtil.getField(Block.class, block, "aB"); + this.isTranslucent = !(boolean)ReflectionUtil.getField(BlockBase.Info.class, blockInfo, "n"); + opacity = defaultState.b(BlockAccessAir.INSTANCE, BlockPosition.ZERO); + } + + public Block getBlock() { + return block; + } + + public IBlockData getState() { + return defaultState; + } + + public CraftBlockData getCraftBlockData() { + return craftBlockData; + } + + public Material getMaterial() { + return material; + } + + @Override + public boolean isAir() { + return defaultState.isAir(); + } + + @Override + public boolean isFullCube() { + return craftMaterial.isOccluding(); + } + + @Override + public boolean isOpaque() { + return material.f(); + } + + @Override + public boolean isPowerSource() { + return defaultState.isPowerSource(); + } + + @Override + public boolean isLiquid() { + return material.isLiquid(); + } + + @Override + public boolean isSolid() { + return material.isBuildable(); + } + + @Override + public float getHardness() { + return craftBlockData.getState().strength; + } + + @Override + public float getResistance() { + return block.getDurability(); + } + + @Override + public float getSlipperiness() { + return block.getFrictionFactor(); + } + + @Override + public int getLightValue() { + return defaultState.f(); + } + + @Override + public int getLightOpacity() { + return opacity; + } + + @Override + public boolean isFragileWhenPushed() { + return material.getPushReaction() == EnumPistonReaction.DESTROY; + } + + @Override + public boolean isUnpushable() { + return material.getPushReaction() == EnumPistonReaction.BLOCK; + } + + @Override + public boolean isTicksRandomly() { + return block.isTicking(defaultState); + } + + @Override + public boolean isMovementBlocker() { + return material.isSolid(); + } + + @Override + public boolean isBurnable() { + return material.isBurnable(); + } + + @Override + public boolean isToolRequired() { + //TODO Removed in 1.16.1 Replacement not found. + return true; + } + + @Override + public boolean isReplacedDuringPlacement() { + return material.isReplaceable(); + } + + @Override + public boolean isTranslucent() { + return isTranslucent; + } + + @Override + public boolean hasContainer() { + return block instanceof ITileEntity; + } + + @Override + public int getMapColor() { + return material.h().rgb; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitAdapter_1_16_4.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitAdapter_1_16_4.java new file mode 100644 index 000000000..6e0ca22a1 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitAdapter_1_16_4.java @@ -0,0 +1,354 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_4; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.bukkit.adapter.DelegateLock; +import com.boydti.fawe.bukkit.adapter.NMSAdapter; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.collection.BitArrayUnstretched; +import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.ReflectionUtils; +import com.boydti.fawe.util.TaskManager; +import com.destroystokyo.paper.util.misc.PooledLinkedHashSets; +import com.mojang.datafixers.util.Either; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import io.papermc.lib.PaperLib; +import net.jpountz.util.UnsafeUtils; +import net.minecraft.server.v1_16_R3.BiomeBase; +import net.minecraft.server.v1_16_R3.BiomeStorage; +import net.minecraft.server.v1_16_R3.Block; +import net.minecraft.server.v1_16_R3.Chunk; +import net.minecraft.server.v1_16_R3.ChunkCoordIntPair; +import net.minecraft.server.v1_16_R3.ChunkSection; +import net.minecraft.server.v1_16_R3.DataBits; +import net.minecraft.server.v1_16_R3.DataPalette; +import net.minecraft.server.v1_16_R3.DataPaletteBlock; +import net.minecraft.server.v1_16_R3.DataPaletteLinear; +import net.minecraft.server.v1_16_R3.EntityPlayer; +import net.minecraft.server.v1_16_R3.GameProfileSerializer; +import net.minecraft.server.v1_16_R3.IBlockData; +import net.minecraft.server.v1_16_R3.PacketPlayOutLightUpdate; +import net.minecraft.server.v1_16_R3.PacketPlayOutMapChunk; +import net.minecraft.server.v1_16_R3.PlayerChunk; +import net.minecraft.server.v1_16_R3.PlayerChunkMap; +import net.minecraft.server.v1_16_R3.World; +import net.minecraft.server.v1_16_R3.WorldServer; +import org.bukkit.craftbukkit.v1_16_R3.CraftChunk; +import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; +import sun.misc.Unsafe; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; + +public final class BukkitAdapter_1_16_4 extends NMSAdapter { + /* + NMS fields + */ + public static final Field fieldBits; + public static final Field fieldPalette; + public static final Field fieldSize; + + public static final Field fieldBitsPerEntry; + + public static final Field fieldFluidCount; + public static final Field fieldTickingBlockCount; + public static final Field fieldNonEmptyBlockCount; + + private static final Field fieldDirty; + private static final Field fieldDirtyBlocks; + + private static final Field fieldBiomeArray; + + private static final MethodHandle methodGetVisibleChunk; + + private static final int CHUNKSECTION_BASE; + private static final int CHUNKSECTION_SHIFT; + + private static final Field fieldLock; + + static { + try { + fieldSize = DataPaletteBlock.class.getDeclaredField("i"); + fieldSize.setAccessible(true); + fieldBits = DataPaletteBlock.class.getDeclaredField("a"); + fieldBits.setAccessible(true); + fieldPalette = DataPaletteBlock.class.getDeclaredField("h"); + fieldPalette.setAccessible(true); + + fieldBitsPerEntry = DataBits.class.getDeclaredField("c"); + fieldBitsPerEntry.setAccessible(true); + + fieldFluidCount = ChunkSection.class.getDeclaredField("e"); + fieldFluidCount.setAccessible(true); + fieldTickingBlockCount = ChunkSection.class.getDeclaredField("tickingBlockCount"); + fieldTickingBlockCount.setAccessible(true); + fieldNonEmptyBlockCount = ChunkSection.class.getDeclaredField("nonEmptyBlockCount"); + fieldNonEmptyBlockCount.setAccessible(true); + + fieldDirty = PlayerChunk.class.getDeclaredField("p"); + fieldDirty.setAccessible(true); + fieldDirtyBlocks = PlayerChunk.class.getDeclaredField("dirtyBlocks"); + fieldDirtyBlocks.setAccessible(true); + + fieldBiomeArray = BiomeStorage.class.getDeclaredField("h"); + fieldBiomeArray.setAccessible(true); + + Method declaredGetVisibleChunk = PlayerChunkMap.class.getDeclaredMethod("getVisibleChunk", long.class); + declaredGetVisibleChunk.setAccessible(true); + methodGetVisibleChunk = MethodHandles.lookup().unreflect(declaredGetVisibleChunk); + + Field tmp = DataPaletteBlock.class.getDeclaredField("j"); + ReflectionUtils.setAccessibleNonFinal(tmp); + fieldLock = tmp; + fieldLock.setAccessible(true); + + Unsafe unsafe = UnsafeUtils.getUNSAFE(); + CHUNKSECTION_BASE = unsafe.arrayBaseOffset(ChunkSection[].class); + int scale = unsafe.arrayIndexScale(ChunkSection[].class); + if ((scale & (scale - 1)) != 0) { + throw new Error("data type scale not a power of two"); + } + CHUNKSECTION_SHIFT = 31 - Integer.numberOfLeadingZeros(scale); + + Class clsShortArraySet; + try { //paper + clsShortArraySet = Class.forName(new String(new char[]{'i', 't', '.', 'u', 'n', 'i', 'm', 'i', '.', 'd', 's', 'i', '.', 'f', 'a', 's', 't', 'u', 't', 'i', 'l', '.', 's', 'h', 'o', 'r', 't', 's', '.', 'S', 'h', 'o', 'r', 't', 'A', 'r', 'r', 'a', 'y', 'S', 'e', 't'})); + } catch (Throwable t) { // still using spigot boo + clsShortArraySet = Class.forName(new String(new char[]{'o', 'r', 'g', '.', 'b', 'u', 'k', 'k', 'i', 't', '.', 'c', 'r', 'a', 'f', 't', 'b', 'u', 'k', 'k', 'i', 't', '.', 'l', 'i', 'b', 's', '.', 'i', 't', '.', 'u', 'n', 'i', 'm', 'i', '.', 'd', 's', 'i', '.', 'f', 'a', 's', 't', 'u', 't', 'i', 'l', '.', 's', 'h', 'o', 'r', 't', 's', '.', 'S', 'h', 'o', 'r', 't', 'A', 'r', 'r', 'a', 'y', 'S', 'e', 't'})); + } + } catch (RuntimeException e) { + throw e; + } catch (Throwable rethrow) { + rethrow.printStackTrace(); + throw new RuntimeException(rethrow); + } + } + + protected static boolean setSectionAtomic(ChunkSection[] sections, ChunkSection expected, ChunkSection value, int layer) { + long offset = ((long) layer << CHUNKSECTION_SHIFT) + CHUNKSECTION_BASE; + if (layer >= 0 && layer < sections.length) { + return UnsafeUtils.getUNSAFE().compareAndSwapObject(sections, offset, expected, value); + } + return false; + } + + protected static DelegateLock applyLock(ChunkSection section) { + //todo there has to be a better way to do this. Maybe using a() in DataPaletteBlock which acquires the lock in NMS? + try { + synchronized (section) { + DataPaletteBlock blocks = section.getBlocks(); + ReentrantLock currentLock = (ReentrantLock) fieldLock.get(blocks); + if (currentLock instanceof DelegateLock) { + return (DelegateLock) currentLock; + } + DelegateLock newLock = new DelegateLock(currentLock); + fieldLock.set(blocks, newLock); + return newLock; + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + public static Chunk ensureLoaded(World nmsWorld, int chunkX, int chunkZ) { + Chunk nmsChunk = nmsWorld.getChunkProvider().getChunkAt(chunkX, chunkZ, false); + if (nmsChunk != null) { + return nmsChunk; + } + if (Fawe.isMainThread()) { + return nmsWorld.getChunkAt(chunkX, chunkZ); + } + if (PaperLib.isPaper()) { + CraftWorld craftWorld = nmsWorld.getWorld(); + CompletableFuture future = craftWorld.getChunkAtAsync(chunkX, chunkZ, true); + try { + CraftChunk chunk = (CraftChunk) future.get(); + return chunk.getHandle(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + // TODO optimize + return TaskManager.IMP.sync(() -> nmsWorld.getChunkAt(chunkX, chunkZ)); + } + + public static PlayerChunk getPlayerChunk(WorldServer nmsWorld, final int chunkX, final int chunkZ) { + PlayerChunkMap chunkMap = nmsWorld.getChunkProvider().playerChunkMap; + try { + return (PlayerChunk) methodGetVisibleChunk.invoke(chunkMap, ChunkCoordIntPair.pair(chunkX, chunkZ)); + } catch (Throwable thr) { + throw new RuntimeException(thr); + } + } + + public static void sendChunk(WorldServer nmsWorld, int chunkX, int chunkZ, int mask, boolean lighting) { + PlayerChunk playerChunk = getPlayerChunk(nmsWorld, chunkX, chunkZ); + if (playerChunk == null) { + return; + } + if (playerChunk.hasBeenLoaded()) { + TaskManager.IMP.sync(() -> { + ChunkCoordIntPair chunkCoordIntPair = new ChunkCoordIntPair(chunkX, chunkZ); + Optional optional = ((Either) playerChunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); + if (optional.isPresent()) { + PacketPlayOutMapChunk chunkpacket = new PacketPlayOutMapChunk(optional.get(), 65535); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(chunkpacket); + }); + + if (lighting) { + boolean trustEdges = true; //This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad) + PacketPlayOutLightUpdate packet = new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine(), trustEdges); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(packet); + }); + } + } else if (PaperLib.isPaper()) { + //Require generic here to work with multiple dependencies trying to take control. + PooledLinkedHashSets.PooledObjectLinkedOpenHashSet objects = + nmsWorld.getChunkProvider().playerChunkMap.playerViewDistanceNoTickMap.getObjectsInRange(chunkX, chunkZ); + if (objects == null) { + return null; + } + for (Object obj : objects.getBackingSet()) { + if (obj == null) { + continue; + } + EntityPlayer p = (EntityPlayer) obj; + Chunk chunk = nmsWorld.getChunkProvider().getChunkAtIfLoadedImmediately(chunkX, chunkZ); + if (chunk != null) { + PacketPlayOutMapChunk chunkpacket = new PacketPlayOutMapChunk(chunk, 65535); + p.playerConnection.sendPacket(chunkpacket); + + if (lighting) { + boolean trustEdges = + true; //This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad) + PacketPlayOutLightUpdate packet = + new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine(), trustEdges); + p.playerConnection.sendPacket(packet); + } + } + } + } + return null; + }); + } + } + + /* + NMS conversion + */ + public static ChunkSection newChunkSection(final int layer, final char[] blocks, boolean fastmode) { + return newChunkSection(layer, null, blocks, fastmode); + } + + public static ChunkSection newChunkSection(final int layer, final Function get, char[] set, boolean fastmode) { + if (set == null) { + return newChunkSection(layer); + } + final int[] blockToPalette = FaweCache.IMP.BLOCK_TO_PALETTE.get(); + final int[] paletteToBlock = FaweCache.IMP.PALETTE_TO_BLOCK.get(); + final long[] blockStates = FaweCache.IMP.BLOCK_STATES.get(); + final int[] blocksCopy = FaweCache.IMP.SECTION_BLOCKS.get(); + try { + int[] num_palette_buffer = new int[1]; + Map ticking_blocks = new HashMap<>(); + int air; + if (get == null) { + air = createPalette(blockToPalette, paletteToBlock, blocksCopy, num_palette_buffer, + set, ticking_blocks, fastmode); + } else { + air = createPalette(layer, blockToPalette, paletteToBlock, blocksCopy, + num_palette_buffer, get, set, ticking_blocks, fastmode); + } + int num_palette = num_palette_buffer[0]; + // BlockStates + int bitsPerEntry = MathMan.log2nlz(num_palette - 1); + if (Settings.IMP.PROTOCOL_SUPPORT_FIX || num_palette != 1) { + bitsPerEntry = Math.max(bitsPerEntry, 4); // Protocol support breaks <4 bits per entry + } else { + bitsPerEntry = Math.max(bitsPerEntry, 1); // For some reason minecraft needs 4096 bits to store 0 entries + } + + final int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntry); + final int blockBitArrayEnd = MathMan.ceilZero((float) 4096 / blocksPerLong); + + if (num_palette == 1) { + for (int i = 0; i < blockBitArrayEnd; i++) { + blockStates[i] = 0; + } + } else { + final BitArrayUnstretched bitArray = new BitArrayUnstretched(bitsPerEntry, 4096, blockStates); + bitArray.fromRaw(blocksCopy); + } + + ChunkSection section = newChunkSection(layer); + // set palette & data bits + final DataPaletteBlock dataPaletteBlocks = section.getBlocks(); + // private DataPalette h; + // protected DataBits a; + final long[] bits = Arrays.copyOfRange(blockStates, 0, blockBitArrayEnd); + final DataBits nmsBits = new DataBits(bitsPerEntry, 4096, bits); + final DataPalette palette; + palette = new DataPaletteLinear<>(Block.REGISTRY_ID, bitsPerEntry, dataPaletteBlocks, GameProfileSerializer::c); + + // set palette + for (int i = 0; i < num_palette; i++) { + final int ordinal = paletteToBlock[i]; + blockToPalette[ordinal] = Integer.MAX_VALUE; + final BlockState state = BlockTypesCache.states[ordinal]; + final IBlockData ibd = ((BlockMaterial_1_16_4) state.getMaterial()).getState(); + palette.a(ibd); + } + try { + fieldBits.set(dataPaletteBlocks, nmsBits); + fieldPalette.set(dataPaletteBlocks, palette); + fieldSize.set(dataPaletteBlocks, bitsPerEntry); + setCount(ticking_blocks.size(), 4096 - air, section); + if (!fastmode) { + ticking_blocks.forEach((pos, ordinal) -> section + .setType(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ(), + Block.getByCombinedId(ordinal))); + } + } catch (final IllegalAccessException e) { + throw new RuntimeException(e); + } + + return section; + } catch (final Throwable e) { + Arrays.fill(blockToPalette, Integer.MAX_VALUE); + throw e; + } + } + + private static ChunkSection newChunkSection(int layer) { + return new ChunkSection(layer << 4); + } + + public static void setCount(final int tickingBlockCount, final int nonEmptyBlockCount, final ChunkSection section) throws IllegalAccessException { + fieldFluidCount.setShort(section, (short) 0); // TODO FIXME + fieldTickingBlockCount.setShort(section, (short) tickingBlockCount); + fieldNonEmptyBlockCount.setShort(section, (short) nonEmptyBlockCount); + } + + public static BiomeBase[] getBiomeArray(BiomeStorage storage) { + try { + return (BiomeBase[]) fieldBiomeArray.get(storage); + } catch (IllegalAccessException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitGetBlocks_1_16_4.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitGetBlocks_1_16_4.java new file mode 100644 index 000000000..962fd3a7b --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitGetBlocks_1_16_4.java @@ -0,0 +1,855 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_4; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.beta.IChunkGet; +import com.boydti.fawe.beta.IChunkSet; +import com.boydti.fawe.beta.implementation.blocks.CharBlocks; +import com.boydti.fawe.beta.implementation.blocks.CharGetBlocks; +import com.boydti.fawe.beta.implementation.lighting.HeightMapType; +import com.boydti.fawe.beta.implementation.queue.QueueHandler; +import com.boydti.fawe.bukkit.adapter.DelegateLock; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.nbt.LazyCompoundTag_1_16_4; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.collection.AdaptedMap; +import com.boydti.fawe.object.collection.BitArrayUnstretched; +import com.google.common.base.Suppliers; +import com.google.common.collect.Iterables; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.FAWE_Spigot_v1_16_R3; +import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BlockTypes; +import net.minecraft.server.v1_16_R3.BiomeBase; +import net.minecraft.server.v1_16_R3.BiomeStorage; +import net.minecraft.server.v1_16_R3.BlockPosition; +import net.minecraft.server.v1_16_R3.Chunk; +import net.minecraft.server.v1_16_R3.ChunkSection; +import net.minecraft.server.v1_16_R3.DataBits; +import net.minecraft.server.v1_16_R3.DataPalette; +import net.minecraft.server.v1_16_R3.DataPaletteBlock; +import net.minecraft.server.v1_16_R3.DataPaletteHash; +import net.minecraft.server.v1_16_R3.DataPaletteLinear; +import net.minecraft.server.v1_16_R3.Entity; +import net.minecraft.server.v1_16_R3.EntityTypes; +import net.minecraft.server.v1_16_R3.EnumSkyBlock; +import net.minecraft.server.v1_16_R3.HeightMap; +import net.minecraft.server.v1_16_R3.IBlockData; +import net.minecraft.server.v1_16_R3.IRegistry; +import net.minecraft.server.v1_16_R3.LightEngine; +import net.minecraft.server.v1_16_R3.NBTTagCompound; +import net.minecraft.server.v1_16_R3.NBTTagInt; +import net.minecraft.server.v1_16_R3.NibbleArray; +import net.minecraft.server.v1_16_R3.SectionPosition; +import net.minecraft.server.v1_16_R3.TileEntity; +import net.minecraft.server.v1_16_R3.WorldServer; +import org.bukkit.World; +import org.bukkit.block.Biome; +import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_16_R3.block.CraftBlock; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.function.Function; + +import static org.slf4j.LoggerFactory.getLogger; + +public class BukkitGetBlocks_1_16_4 extends CharGetBlocks { + + private static final Logger log = LoggerFactory.getLogger(BukkitGetBlocks_1_16_4.class); + + private static final Function posNms2We = v -> BlockVector3.at(v.getX(), v.getY(), v.getZ()); + private static final Function nmsTile2We = tileEntity -> new LazyCompoundTag_1_16_4(Suppliers.memoize(() -> tileEntity.save(new NBTTagCompound()))); + public ChunkSection[] sections; + public Chunk nmsChunk; + public WorldServer world; + public int chunkX; + public int chunkZ; + public NibbleArray[] blockLight = new NibbleArray[16]; + public NibbleArray[] skyLight = new NibbleArray[16]; + private boolean createCopy = false; + private BukkitGetBlocks_1_16_4_Copy copy = null; + + public BukkitGetBlocks_1_16_4(World world, int chunkX, int chunkZ) { + this(((CraftWorld) world).getHandle(), chunkX, chunkZ); + } + + public BukkitGetBlocks_1_16_4(WorldServer world, int chunkX, int chunkZ) { + this.world = world; + this.chunkX = chunkX; + this.chunkZ = chunkZ; + } + + public int getChunkX() { + return chunkX; + } + + @Override + public void setCreateCopy(boolean createCopy) { + this.createCopy = createCopy; + } + + @Override + public boolean isCreateCopy() { + return createCopy; + } + + @Override + public IChunkGet getCopy() { + return copy; + } + + public int getChunkZ() { + return chunkZ; + } + + @Override + public BiomeType getBiomeType(int x, int y, int z) { + BiomeStorage index = getChunk().getBiomeIndex(); + BiomeBase base = null; + if (y == -1) { + for (y = 0; y < FaweCache.IMP.WORLD_HEIGHT; y++) { + base = index.getBiome(x >> 2, y >> 2, z >> 2); + if (base != null) { + break; + } + } + } else { + base = index.getBiome(x >> 2, y >> 2, z >> 2); + } + return base != null ? BukkitAdapter.adapt(CraftBlock.biomeBaseToBiome(world.r().b(IRegistry.ay), base)) : null; + } + + @Override + public CompoundTag getTile(int x, int y, int z) { + TileEntity tileEntity = getChunk().getTileEntity(new BlockPosition((x & 15) + ( + chunkX << 4), y, (z & 15) + ( + chunkZ << 4))); + if (tileEntity == null) { + return null; + } + return new LazyCompoundTag_1_16_4(Suppliers.memoize(() -> tileEntity.save(new NBTTagCompound()))); + } + + @Override + public Map getTiles() { + Map nmsTiles = getChunk().getTileEntities(); + if (nmsTiles.isEmpty()) { + return Collections.emptyMap(); + } + return AdaptedMap.immutable(nmsTiles, posNms2We, nmsTile2We); + } + + @Override + public int getSkyLight(int x, int y, int z) { + int layer = y >> 4; + if (skyLight[layer] == null) { + SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), layer); + NibbleArray nibbleArray = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.SKY).a(sectionPosition); + // If the server hasn't generated the section's NibbleArray yet, it will be null + if (nibbleArray == null) { + byte[] a = new byte[2048]; + // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway. + Arrays.fill(a, (byte) 15); + nibbleArray = new NibbleArray(a); + ((LightEngine) world.getChunkProvider().getLightEngine()).a(EnumSkyBlock.SKY, sectionPosition, nibbleArray, true); + } + skyLight[layer] = nibbleArray; + } + long l = BlockPosition.a(x, y, z); + return skyLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l))); + } + + @Override + public int getEmmittedLight(int x, int y, int z) { + int layer = y >> 4; + if (blockLight[layer] == null) { + SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), layer); + NibbleArray nibbleArray = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.BLOCK).a(sectionPosition); + // If the server hasn't generated the section's NibbleArray yet, it will be null + if (nibbleArray == null) { + byte[] a = new byte[2048]; + // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway. + Arrays.fill(a, (byte) 15); + nibbleArray = new NibbleArray(a); + ((LightEngine) world.getChunkProvider().getLightEngine()).a(EnumSkyBlock.BLOCK, sectionPosition, nibbleArray, true); + } + blockLight[layer] = nibbleArray; + } + long l = BlockPosition.a(x, y, z); + return blockLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l))); + } + + @Override + public int[] getHeightMap(HeightMapType type) { + long[] longArray = getChunk().heightMap.get(HeightMap.Type.valueOf(type.name())).a(); + BitArrayUnstretched bitArray = new BitArrayUnstretched(9, 256, longArray); + return bitArray.toRaw(new int[256]); + } + + @Override + public CompoundTag getEntity(UUID uuid) { + Entity entity = world.getEntity(uuid); + if (entity != null) { + org.bukkit.entity.Entity bukkitEnt = entity.getBukkitEntity(); + return BukkitAdapter.adapt(bukkitEnt).getState().getNbtData(); + } + for (List entry : getChunk().getEntitySlices()) { + if (entry != null) { + for (Entity ent : entry) { + if (uuid.equals(ent.getUniqueID())) { + org.bukkit.entity.Entity bukkitEnt = ent.getBukkitEntity(); + return BukkitAdapter.adapt(bukkitEnt).getState().getNbtData(); + } + } + } + } + return null; + } + + @Override + public Set getEntities() { + List[] slices = getChunk().getEntitySlices(); + int size = 0; + for (List slice : slices) { + if (slice != null) { + size += slice.size(); + } + } + if (slices.length == 0) { + return Collections.emptySet(); + } + int finalSize = size; + return new AbstractSet() { + @Override + public int size() { + return finalSize; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean contains(Object get) { + if (!(get instanceof CompoundTag)) { + return false; + } + CompoundTag getTag = (CompoundTag) get; + Map value = getTag.getValue(); + CompoundTag getParts = (CompoundTag) value.get("UUID"); + UUID getUUID = new UUID(getParts.getLong("Most"), getParts.getLong("Least")); + for (List slice : slices) { + if (slice != null) { + for (Entity entity : slice) { + UUID uuid = entity.getUniqueID(); + if (uuid.equals(getUUID)) { + return true; + } + } + } + } + return false; + } + + @NotNull + @Override + public Iterator iterator() { + Iterable result = Iterables.transform(Iterables.concat(slices), new com.google.common.base.Function() { + @Nullable + @Override + public CompoundTag apply(@Nullable Entity input) { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + NBTTagCompound tag = new NBTTagCompound(); + return (CompoundTag) adapter.toNative(input.save(tag)); + } + }); + return result.iterator(); + } + }; + } + + private void updateGet(BukkitGetBlocks_1_16_4 get, Chunk nmsChunk, ChunkSection[] sections, ChunkSection section, char[] arr, int layer) { + synchronized (get) { + if (this.nmsChunk != nmsChunk) { + this.nmsChunk = nmsChunk; + this.sections = sections.clone(); + this.reset(); + } + if (this.sections == null) { + this.sections = sections.clone(); + } + if (this.sections[layer] != section) { + this.sections[layer] = new ChunkSection[]{section}.clone()[0]; + } + this.blocks[layer] = arr; + } + } + + private void removeEntity(Entity entity) { + entity.die(); + } + + public Chunk ensureLoaded(net.minecraft.server.v1_16_R3.World nmsWorld, int chunkX, int chunkZ) { + return BukkitAdapter_1_16_4.ensureLoaded(nmsWorld, chunkX, chunkZ); + } + + @Override + public > T call(IChunkSet set, Runnable finalizer) { + copy = createCopy ? new BukkitGetBlocks_1_16_4_Copy(world, getChunkX(), getChunkZ()) : null; + try { + WorldServer nmsWorld = world; + Chunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ); + boolean fastmode = set.isFastMode() && Settings.IMP.QUEUE.NO_TICK_FASTMODE; + + // Remove existing tiles + { + // Create a copy so that we can remove blocks + Map tiles = new HashMap<>(nmsChunk.getTileEntities()); + if (!tiles.isEmpty()) { + for (Map.Entry entry : tiles.entrySet()) { + final BlockPosition pos = entry.getKey(); + final int lx = pos.getX() & 15; + final int ly = pos.getY(); + final int lz = pos.getZ() & 15; + final int layer = ly >> 4; + if (!set.hasSection(layer)) { + continue; + } + + int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); + if (ordinal != 0) { + TileEntity tile = entry.getValue(); + nmsChunk.removeTileEntity(tile.getPosition()); + if (createCopy) { + copy.storeTile(tile); + } + } + } + } + } + + int bitMask = 0; + synchronized (nmsChunk) { + ChunkSection[] sections = nmsChunk.getSections(); + + for (int layer = 0; layer < 16; layer++) { + if (!set.hasSection(layer)) { + continue; + } + if (createCopy) { + copy.storeSection(layer); + } + + bitMask |= 1 << layer; + + char[] setArr = set.load(layer); + ChunkSection newSection; + ChunkSection existingSection = sections[layer]; + if (existingSection == null) { + newSection = BukkitAdapter_1_16_4.newChunkSection(layer, setArr, fastmode); + if (BukkitAdapter_1_16_4.setSectionAtomic(sections, null, newSection, layer)) { + updateGet(this, nmsChunk, sections, newSection, setArr, layer); + continue; + } else { + existingSection = sections[layer]; + if (existingSection == null) { + log.error("Skipping invalid null section. chunk:" + chunkX + "," + + chunkZ + " layer: " + layer); + continue; + } + } + } + BukkitAdapter_1_16_4.fieldTickingBlockCount.set(existingSection, (short) 0); + + //ensure that the server doesn't try to tick the chunksection while we're editing it. + DelegateLock lock = BukkitAdapter_1_16_4.applyLock(existingSection); + + synchronized (this) { + synchronized (lock) { + lock.untilFree(); + if (this.nmsChunk != nmsChunk) { + this.nmsChunk = nmsChunk; + this.sections = null; + this.reset(); + } else if (existingSection != getSections()[layer]) { + this.sections[layer] = existingSection; + this.reset(); + } else if (!Arrays.equals(update(layer, new char[4096]), load(layer))) { + this.reset(layer); + } else if (lock.isModified()) { + this.reset(layer); + } + newSection = BukkitAdapter_1_16_4 + .newChunkSection(layer, this::load, setArr, fastmode); + if (!BukkitAdapter_1_16_4 + .setSectionAtomic(sections, existingSection, newSection, layer)) { + log.error("Failed to set chunk section:" + chunkX + "," + chunkZ + " layer: " + layer); + continue; + } else { + updateGet(this, nmsChunk, sections, newSection, setArr, layer); + } + } + } + } + + // Biomes + BiomeType[] biomes = set.getBiomes(); + if (biomes != null) { + // set biomes + BiomeStorage currentBiomes = nmsChunk.getBiomeIndex(); + if (createCopy) { + copy.storeBiomes(currentBiomes); + } + for (int y = 0, i = 0; y < 64; y++) { + for (int z = 0; z < 4; z++) { + for (int x = 0; x < 4; x++, i++) { + final BiomeType biome = biomes[i]; + if (biome != null) { + final Biome craftBiome = BukkitAdapter.adapt(biome); + BiomeBase nmsBiome = CraftBlock.biomeToBiomeBase(nmsWorld.r().b(IRegistry.ay), craftBiome); + currentBiomes.setBiome(x, y, z, nmsBiome); + } + } + } + } + } + + Map heightMaps = set.getHeightMaps(); + for (Map.Entry entry : heightMaps.entrySet()) { + BitArrayUnstretched bitArray = new BitArrayUnstretched(9, 256); + bitArray.fromRaw(entry.getValue()); + nmsChunk.heightMap.get(HeightMap.Type.valueOf(entry.getKey().name())).a(bitArray.getData()); + } + + boolean lightUpdate = false; + + // Lighting + char[][] light = set.getLight(); + if (light != null) { + lightUpdate = true; + try { + fillLightNibble(light, EnumSkyBlock.BLOCK); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + char[][] skyLight = set.getSkyLight(); + if (skyLight != null) { + lightUpdate = true; + try { + fillLightNibble(skyLight, EnumSkyBlock.SKY); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + Runnable[] syncTasks = null; + + int bx = chunkX << 4; + int bz = chunkZ << 4; + + Set entityRemoves = set.getEntityRemoves(); + if (entityRemoves != null && !entityRemoves.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[3]; + } + + syncTasks[2] = () -> { + final List[] entities = nmsChunk.getEntitySlices(); + + for (final Collection ents : entities) { + if (!ents.isEmpty()) { + final Iterator iter = ents.iterator(); + while (iter.hasNext()) { + final Entity entity = iter.next(); + if (entityRemoves.contains(entity.getUniqueID())) { + if (createCopy) { + copy.storeEntity(entity); + } + iter.remove(); + removeEntity(entity); + } + } + } + } + }; + } + + Set entities = set.getEntities(); + if (entities != null && !entities.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[2]; + } + + syncTasks[1] = () -> { + for (final CompoundTag nativeTag : entities) { + final Map entityTagMap = nativeTag.getValue(); + final StringTag idTag = (StringTag) entityTagMap.get("Id"); + final ListTag posTag = (ListTag) entityTagMap.get("Pos"); + final ListTag rotTag = (ListTag) entityTagMap.get("Rotation"); + if (idTag == null || posTag == null || rotTag == null) { + getLogger( + BukkitGetBlocks_1_16_4.class).debug("Unknown entity tag: " + nativeTag); + continue; + } + final double x = posTag.getDouble(0); + final double y = posTag.getDouble(1); + final double z = posTag.getDouble(2); + final float yaw = rotTag.getFloat(0); + final float pitch = rotTag.getFloat(1); + final String id = idTag.getValue(); + + EntityTypes type = EntityTypes.a(id).orElse(null); + if (type != null) { + Entity entity = type.a(nmsWorld); + if (entity != null) { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + final NBTTagCompound tag = (NBTTagCompound) adapter.fromNative(nativeTag); + for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); + } + entity.load(tag); + entity.setLocation(x, y, z, yaw, pitch); + nmsWorld.addEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM); + } + } + } + }; + + } + + // set tiles + Map tiles = set.getTiles(); + if (tiles != null && !tiles.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[1]; + } + + syncTasks[0] = () -> { + for (final Map.Entry entry : tiles.entrySet()) { + final CompoundTag nativeTag = entry.getValue(); + final BlockVector3 blockHash = entry.getKey(); + final int x = blockHash.getX() + bx; + final int y = blockHash.getY(); + final int z = blockHash.getZ() + bz; + final BlockPosition pos = new BlockPosition(x, y, z); + + synchronized (nmsWorld) { + TileEntity tileEntity = nmsWorld.getTileEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsWorld.removeTileEntity(pos); + tileEntity = nmsWorld.getTileEntity(pos); + } + if (tileEntity != null) { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + final NBTTagCompound tag = (NBTTagCompound) adapter.fromNative(nativeTag); + tag.set("x", NBTTagInt.a(x)); + tag.set("y", NBTTagInt.a(y)); + tag.set("z", NBTTagInt.a(z)); + tileEntity.load(tileEntity.getBlock(), tag); + } + } + } + }; + } + + Runnable callback; + if (bitMask == 0 && biomes == null && !lightUpdate) { + callback = null; + } else { + int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; + boolean finalLightUpdate = lightUpdate; + callback = () -> { + // Set Modified + nmsChunk.d(true); // Set Modified + nmsChunk.mustNotSave = false; + nmsChunk.markDirty(); + // send to player + BukkitAdapter_1_16_4.sendChunk(nmsWorld, chunkX, chunkZ, finalMask, finalLightUpdate); + if (finalizer != null) { + finalizer.run(); + } + }; + } + if (syncTasks != null) { + QueueHandler queueHandler = Fawe.get().getQueueHandler(); + Runnable[] finalSyncTasks = syncTasks; + + // Chain the sync tasks and the callback + Callable chain = () -> { + try { + // Run the sync tasks + for (Runnable task : finalSyncTasks) { + if (task != null) { + task.run(); + } + } + if (callback == null) { + if (finalizer != null) { + finalizer.run(); + } + return null; + } else { + return queueHandler.async(callback, null); + } + } catch (Throwable e) { + e.printStackTrace(); + throw e; + } + }; + return (T) (Future) queueHandler.sync(chain); + } else { + if (callback == null) { + if (finalizer != null) { + finalizer.run(); + } + } else { + callback.run(); + } + } + } + return null; + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } + + @Override + public synchronized char[] update(int layer, char[] data) { + ChunkSection section = getSections()[layer]; + // Section is null, return empty array + if (section == null) { + data = new char[4096]; + Arrays.fill(data, (char) 1); + return data; + } + if (data == null || data == FaweCache.IMP.EMPTY_CHAR_4096) { + data = new char[4096]; + Arrays.fill(data, (char) 1); + } + DelegateLock lock = BukkitAdapter_1_16_4.applyLock(section); + synchronized (lock) { + lock.untilFree(); + lock.setModified(false); + // Efficiently convert ChunkSection to raw data + try { + FAWE_Spigot_v1_16_R3 adapter = ((FAWE_Spigot_v1_16_R3) WorldEditPlugin.getInstance().getBukkitImplAdapter()); + + final DataPaletteBlock blocks = section.getBlocks(); + final DataBits bits = (DataBits) BukkitAdapter_1_16_4.fieldBits.get(blocks); + final DataPalette palette = (DataPalette) BukkitAdapter_1_16_4.fieldPalette.get(blocks); + + final int bitsPerEntry = (int) BukkitAdapter_1_16_4.fieldBitsPerEntry.get(bits); + final long[] blockStates = bits.a(); + + new BitArrayUnstretched(bitsPerEntry, 4096, blockStates).toRaw(data); + + int num_palette; + if (palette instanceof DataPaletteLinear) { + num_palette = ((DataPaletteLinear) palette).b(); + } else if (palette instanceof DataPaletteHash) { + num_palette = ((DataPaletteHash) palette).b(); + } else { + num_palette = 0; + int[] paletteToBlockInts = FaweCache.IMP.PALETTE_TO_BLOCK.get(); + char[] paletteToBlockChars = FaweCache.IMP.PALETTE_TO_BLOCK_CHAR.get(); + try { + for (int i = 0; i < 4096; i++) { + char paletteVal = data[i]; + char ordinal = paletteToBlockChars[paletteVal]; + if (ordinal == Character.MAX_VALUE) { + paletteToBlockInts[num_palette++] = paletteVal; + IBlockData ibd = palette.a(data[i]); + if (ibd == null) { + ordinal = BlockTypes.AIR.getDefaultState().getOrdinalChar(); + } else { + ordinal = adapter.adaptToChar(ibd); + } + paletteToBlockChars[paletteVal] = ordinal; + } + // Don't read "empty". + if (ordinal == 0) { + ordinal = 1; + } + data[i] = ordinal; + } + } finally { + for (int i = 0; i < num_palette; i++) { + int paletteVal = paletteToBlockInts[i]; + paletteToBlockChars[paletteVal] = Character.MAX_VALUE; + } + } + return data; + } + + char[] paletteToOrdinal = FaweCache.IMP.PALETTE_TO_BLOCK_CHAR.get(); + try { + if (num_palette != 1) { + for (int i = 0; i < num_palette; i++) { + char ordinal = ordinal(palette.a(i), adapter); + paletteToOrdinal[i] = ordinal; + } + for (int i = 0; i < 4096; i++) { + char paletteVal = data[i]; + char val = paletteToOrdinal[paletteVal]; + if (val == Character.MAX_VALUE) { + val = ordinal(palette.a(i), adapter); + paletteToOrdinal[i] = val; + } + // Don't read "empty". + if (val == 0) { + val = 1; + } + data[i] = val; + } + } else { + char ordinal = ordinal(palette.a(0), adapter); + // Don't read "empty". + if (ordinal == 0) { + ordinal = 1; + } + Arrays.fill(data, ordinal); + } + } finally { + for (int i = 0; i < num_palette; i++) { + paletteToOrdinal[i] = Character.MAX_VALUE; + } + } + return data; + } catch (IllegalAccessException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + } + + private final char ordinal(IBlockData ibd, FAWE_Spigot_v1_16_R3 adapter) { + if (ibd == null) { + return BlockTypes.AIR.getDefaultState().getOrdinalChar(); + } else { + return adapter.adaptToChar(ibd); + } + } + + public ChunkSection[] getSections() { + ChunkSection[] tmp = sections; + if (tmp == null) { + synchronized (this) { + tmp = sections; + if (tmp == null) { + Chunk chunk = getChunk(); + sections = tmp = chunk.getSections().clone(); + } + } + } + return tmp; + } + + public Chunk getChunk() { + Chunk tmp = nmsChunk; + if (tmp == null) { + synchronized (this) { + tmp = nmsChunk; + if (tmp == null) { + nmsChunk = tmp = ensureLoaded(this.world, chunkX, chunkZ); + } + } + } + return tmp; + } + + private void fillLightNibble(char[][] light, EnumSkyBlock skyBlock) { + for (int Y = 0; Y < 16; Y++) { + if (light[Y] == null) { + continue; + } + SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), Y); + NibbleArray nibble = world.getChunkProvider().getLightEngine().a(skyBlock).a(sectionPosition); + if (nibble == null) { + byte[] a = new byte[2048]; + Arrays.fill(a, skyBlock == EnumSkyBlock.SKY ? (byte) 15 : (byte) 0); + nibble = new NibbleArray(a); + ((LightEngine) world.getChunkProvider().getLightEngine()).a(skyBlock, sectionPosition, nibble, true); + } + synchronized (nibble) { + for (int i = 0; i < 4096; i++) { + if (light[Y][i] < 16) { + nibble.a(i, light[Y][i]); + } + } + } + } + } + + @Override + public boolean hasSection(int layer) { + return getSections()[layer] != null; + } + + @Override + public boolean trim(boolean aggressive) { + skyLight = new NibbleArray[16]; + blockLight = new NibbleArray[16]; + if (aggressive) { + sections = null; + nmsChunk = null; + return super.trim(true); + } else { + for (int i = 0; i < 16; i++) { + if (!hasSection(i) || super.sections[i] == CharBlocks.EMPTY) { + continue; + } + ChunkSection existing = getSections()[i]; + try { + final DataPaletteBlock blocksExisting = existing.getBlocks(); + + final DataPalette palette = (DataPalette) BukkitAdapter_1_16_4.fieldPalette.get(blocksExisting); + int paletteSize; + + if (palette instanceof DataPaletteLinear) { + paletteSize = ((DataPaletteLinear) palette).b(); + } else if (palette instanceof DataPaletteHash) { + paletteSize = ((DataPaletteHash) palette).b(); + } else { + super.trim(false, i); + continue; + } + if (paletteSize == 1) { + //If the cached palette size is 1 then no blocks can have been changed i.e. do not need to update these chunks. + continue; + } + super.trim(false, i); + } catch (IllegalAccessException ignored) { + super.trim(false, i); + } + } + return true; + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitGetBlocks_1_16_4_Copy.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitGetBlocks_1_16_4_Copy.java new file mode 100644 index 000000000..2eec240cb --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitGetBlocks_1_16_4_Copy.java @@ -0,0 +1,130 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_4; + + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.nbt.LazyCompoundTag_1_16_4; +import com.google.common.base.Suppliers; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import net.minecraft.server.v1_16_R3.BiomeBase; +import net.minecraft.server.v1_16_R3.BiomeStorage; +import net.minecraft.server.v1_16_R3.Entity; +import net.minecraft.server.v1_16_R3.IRegistry; +import net.minecraft.server.v1_16_R3.NBTTagCompound; +import net.minecraft.server.v1_16_R3.TileEntity; +import net.minecraft.server.v1_16_R3.WorldServer; +import org.bukkit.craftbukkit.v1_16_R3.block.CraftBlock; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public class BukkitGetBlocks_1_16_4_Copy extends BukkitGetBlocks_1_16_4 { + + private final Map tiles = new HashMap<>(); + private final Set entities = new HashSet<>(); + private BiomeStorage biomeStorage; + private final char[][] blocks = new char[16][4096]; + + protected BukkitGetBlocks_1_16_4_Copy(WorldServer world, int X, int Z) { + super(world, X, Z); + } + + protected void storeTile(TileEntity tile) { + tiles.put(BlockVector3.at(tile.getPosition().getX(), tile.getPosition().getY(), tile.getPosition().getZ()), + new LazyCompoundTag_1_16_4(Suppliers.memoize(() -> tile.save(new NBTTagCompound())))); + } + + @Override + public Map getTiles() { + return tiles; + } + + @Override + @Nullable + public CompoundTag getTile(int x, int y, int z) { + return tiles.get(BlockVector3.at(x, y, z)); + } + + protected void storeEntity(Entity entity) { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + NBTTagCompound tag = new NBTTagCompound(); + entities.add((CompoundTag) adapter.toNative(entity.save(tag))); + } + + @Override + public Set getEntities() { + return this.entities; + } + + @Override + public CompoundTag getEntity(UUID uuid) { + for (CompoundTag tag : entities) { + UUID tagUUID; + if (tag.containsKey("UUID")) { + int[] arr = tag.getIntArray("UUID"); + tagUUID = new UUID((long) arr[0] << 32 | (arr[1] & 0xFFFFFFFFL), (long) arr[2] << 32 | (arr[3] & 0xFFFFFFFFL)); + } else if (tag.containsKey("UUIDMost")) { + tagUUID = new UUID(tag.getLong("UUIDMost"), tag.getLong("UUIDLeast")); + } else if (tag.containsKey("PersistentIDMSB")) { + tagUUID = new UUID(tag.getLong("PersistentIDMSB"), tag.getLong("PersistentIDLSB")); + } else { + return null; + } + if (uuid.equals(tagUUID)) { + return tag; + } + } + return null; + } + + protected void storeBiomes(BiomeStorage biomeStorage) { + this.biomeStorage = new BiomeStorage(biomeStorage.g, BukkitAdapter_1_16_4.getBiomeArray(biomeStorage).clone()); + } + + @Override + public BiomeType getBiomeType(int x, int y, int z) { + BiomeBase base = null; + if (y == -1) { + for (y = 0; y < FaweCache.IMP.WORLD_HEIGHT; y++) { + base = biomeStorage.getBiome(x >> 2, y >> 2, z >> 2); + if (base != null) break; + } + } else { + base = biomeStorage.getBiome(x >> 2, y >> 2, z >> 2); + } + return base != null ? BukkitAdapter.adapt(CraftBlock.biomeBaseToBiome(world.r().b(IRegistry.ay), base)) : null; + } + + protected void storeSection(int layer) { + blocks[layer] = update(layer, null).clone(); + } + + @Override + public BaseBlock getFullBlock(int x, int y, int z) { + BlockState state = BlockTypesCache.states[get(x, y, z)]; + return state.toBaseBlock(this, x, y, z); + } + + @Override + public BlockState getBlock(int x, int y, int z) { + return BlockTypesCache.states[get(x, y, z)]; + } + + @Override + public char get(int x, int y, int z) { + final int layer = y >> 4; + final int index = (y & 15) << 8 | z << 4 | x; + return blocks[layer][index]; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/FAWEWorldNativeAccess_1_16.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/FAWEWorldNativeAccess_1_16.java new file mode 100644 index 000000000..b86902067 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/FAWEWorldNativeAccess_1_16.java @@ -0,0 +1,176 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_4; + +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.FAWE_Spigot_v1_16_R2; +import com.sk89q.worldedit.bukkit.adapter.impl.FAWE_Spigot_v1_16_R3; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import net.minecraft.server.v1_16_R3.Block; +import net.minecraft.server.v1_16_R3.BlockPosition; +import net.minecraft.server.v1_16_R3.Chunk; +import net.minecraft.server.v1_16_R3.ChunkProviderServer; +import net.minecraft.server.v1_16_R3.EnumDirection; +import net.minecraft.server.v1_16_R3.IBlockData; +import net.minecraft.server.v1_16_R3.NBTBase; +import net.minecraft.server.v1_16_R3.NBTTagCompound; +import net.minecraft.server.v1_16_R3.PlayerChunk; +import net.minecraft.server.v1_16_R3.TileEntity; +import net.minecraft.server.v1_16_R3.World; +import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_16_R3.block.data.CraftBlockData; +import org.bukkit.event.block.BlockPhysicsEvent; + +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; +import java.util.Objects; + +public class FAWEWorldNativeAccess_1_16 implements WorldNativeAccess { + private static final int UPDATE = 1; + private static final int NOTIFY = 2; + + private final FAWE_Spigot_v1_16_R3 adapter; + private final WeakReference world; + private SideEffectSet sideEffectSet; + + public FAWEWorldNativeAccess_1_16(FAWE_Spigot_v1_16_R3 adapter, WeakReference world) { + this.adapter = adapter; + this.world = world; + } + + private World getWorld() { + return Objects.requireNonNull(world.get(), "The reference to the world was lost"); + } + + @Override + public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { + this.sideEffectSet = sideEffectSet; + } + + @Override + public Chunk getChunk(int x, int z) { + return getWorld().getChunkAt(x, z); + } + + @Override + public IBlockData toNative(BlockState state) { + int stateId = BlockStateIdAccess.getBlockStateId(state); + return BlockStateIdAccess.isValidInternalId(stateId) + ? Block.getByCombinedId(stateId) + : ((CraftBlockData) BukkitAdapter.adapt(state)).getState(); + } + + @Override + public IBlockData getBlockState(Chunk chunk, BlockPosition position) { + return chunk.getType(position); + } + + @Nullable + @Override + public IBlockData setBlockState(Chunk chunk, BlockPosition position, IBlockData state) { + return chunk.setType(position, state, false); + } + + @Override + public IBlockData getValidBlockForPosition(IBlockData block, BlockPosition position) { + return Block.b(block, getWorld(), position); + } + + @Override + public BlockPosition getPosition(int x, int y, int z) { + return new BlockPosition(x, y, z); + } + + @Override + public void updateLightingForBlock(BlockPosition position) { + getWorld().getChunkProvider().getLightEngine().a(position); + } + + @Override + public boolean updateTileEntity(BlockPosition position, CompoundTag tag) { + // We will assume that the tile entity was created for us, + // though we do not do this on the other versions + TileEntity tileEntity = getWorld().getTileEntity(position); + if (tileEntity == null) { + return false; + } + NBTBase nativeTag = adapter.fromNative(tag); + tileEntity.load(tileEntity.getBlock(), (NBTTagCompound) nativeTag); + return true; + } + + @Override + public void notifyBlockUpdate(BlockPosition position, IBlockData oldState, IBlockData newState) { + getWorld().notify(position, oldState, newState, UPDATE | NOTIFY); + } + + @Override + public boolean isChunkTicking(Chunk chunk) { + return chunk.getState().isAtLeast(PlayerChunk.State.TICKING); + } + + @Override + public void markBlockChanged(BlockPosition position) { + ((ChunkProviderServer) getWorld().getChunkProvider()).flagDirty(position); + } + + private static final EnumDirection[] NEIGHBOUR_ORDER = { + EnumDirection.WEST, EnumDirection.EAST, + EnumDirection.DOWN, EnumDirection.UP, + EnumDirection.NORTH, EnumDirection.SOUTH + }; + + @Override + public void notifyNeighbors(BlockPosition pos, IBlockData oldState, IBlockData newState) { + World world = getWorld(); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + world.update(pos, oldState.getBlock()); + } else { + // When we don't want events, manually run the physics without them. + // Un-nest neighbour updating + for (EnumDirection direction : NEIGHBOUR_ORDER) { + BlockPosition shifted = pos.shift(direction); + world.getType(shifted).doPhysics(world, shifted, oldState.getBlock(), pos, false); + } + } + if (newState.isComplexRedstone()) { + world.updateAdjacentComparators(pos, newState.getBlock()); + } + } + + @Override + public void updateNeighbors(BlockPosition pos, IBlockData oldState, IBlockData newState, int recursionLimit) { + World world = getWorld(); + // a == updateNeighbors + // b == updateDiagonalNeighbors + oldState.b(world, pos, NOTIFY, recursionLimit); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + CraftWorld craftWorld = world.getWorld(); + if (craftWorld != null) { + BlockPhysicsEvent event = new BlockPhysicsEvent(craftWorld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), CraftBlockData.fromData(newState)); + world.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + } + } + newState.a(world, pos, NOTIFY, recursionLimit); + newState.b(world, pos, NOTIFY, recursionLimit); + } + + @Override + public void onBlockStateChange(BlockPosition pos, IBlockData oldState, IBlockData newState) { + getWorld().a(pos, oldState, newState); + } + + @Override + public > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { + return this.adapter.setBlock(this.getChunk(position.getBlockX() >> 4, position.getBlockZ() >> 4).bukkitChunk, position.getBlockX(), position.getBlockY(), position.getBlockZ(), block, sideEffectSet.shouldApply(SideEffect.LIGHTING)); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/MapChunkUtil_1_16_4.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/MapChunkUtil_1_16_4.java new file mode 100644 index 000000000..37993c9fe --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/MapChunkUtil_1_16_4.java @@ -0,0 +1,28 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_4; + +import com.boydti.fawe.bukkit.adapter.MapChunkUtil; +import net.minecraft.server.v1_16_R3.PacketPlayOutMapChunk; + +public class MapChunkUtil_1_16_4 extends MapChunkUtil { + public MapChunkUtil_1_16_4() throws NoSuchFieldException { + fieldX = PacketPlayOutMapChunk.class.getDeclaredField("a"); + fieldZ = PacketPlayOutMapChunk.class.getDeclaredField("b"); + fieldBitMask = PacketPlayOutMapChunk.class.getDeclaredField("c"); + fieldHeightMap = PacketPlayOutMapChunk.class.getDeclaredField("d"); + fieldChunkData = PacketPlayOutMapChunk.class.getDeclaredField("f"); + fieldBlockEntities = PacketPlayOutMapChunk.class.getDeclaredField("g"); + fieldFull = PacketPlayOutMapChunk.class.getDeclaredField("h"); + fieldX.setAccessible(true); + fieldZ.setAccessible(true); + fieldBitMask.setAccessible(true); + fieldHeightMap.setAccessible(true); + fieldChunkData.setAccessible(true); + fieldBlockEntities.setAccessible(true); + fieldFull.setAccessible(true); + } + + @Override + public PacketPlayOutMapChunk createPacket() { + return new PacketPlayOutMapChunk(); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/nbt/LazyCompoundTag_1_16_4.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/nbt/LazyCompoundTag_1_16_4.java new file mode 100644 index 000000000..7cc5cdc87 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/nbt/LazyCompoundTag_1_16_4.java @@ -0,0 +1,152 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_4.nbt; + +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import net.minecraft.server.v1_16_R3.NBTBase; +import net.minecraft.server.v1_16_R3.NBTNumber; +import net.minecraft.server.v1_16_R3.NBTTagCompound; +import net.minecraft.server.v1_16_R3.NBTTagList; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +public class LazyCompoundTag_1_16_4 extends CompoundTag { + private final Supplier nmsTag; + + public LazyCompoundTag_1_16_4(Supplier tag) { + super(null); + this.nmsTag = tag; + } + + public LazyCompoundTag_1_16_4(NBTTagCompound tag) { + this(() -> tag); + } + + public NBTTagCompound get() { + return nmsTag.get(); + } + + @Override + public Map getValue() { + Map value = super.getValue(); + if (value == null) { + Tag tag = WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(nmsTag.get()); + setValue(((CompoundTag) tag).getValue()); + } + return super.getValue(); + } + + public boolean containsKey(String key) { + return nmsTag.get().hasKey(key); + } + + public byte[] getByteArray(String key) { + return nmsTag.get().getByteArray(key); + } + + public byte getByte(String key) { + return nmsTag.get().getByte(key); + } + + public double getDouble(String key) { + return nmsTag.get().getDouble(key); + } + + public double asDouble(String key) { + NBTBase value = nmsTag.get().get(key); + if (value instanceof NBTNumber) { + return ((NBTNumber) value).asDouble(); + } + return 0; + } + + public float getFloat(String key) { + return nmsTag.get().getFloat(key); + } + + public int[] getIntArray(String key) { + return nmsTag.get().getIntArray(key); + } + + public int getInt(String key) { + return nmsTag.get().getInt(key); + } + + public int asInt(String key) { + NBTBase value = nmsTag.get().get(key); + if (value instanceof NBTNumber) { + return ((NBTNumber) value).asInt(); + } + return 0; + } + + public List getList(String key) { + NBTBase tag = nmsTag.get().get(key); + if (tag instanceof NBTTagList) { + ArrayList list = new ArrayList<>(); + NBTTagList nbtList = (NBTTagList) tag; + for (NBTBase elem : nbtList) { + if (elem instanceof NBTTagCompound) { + list.add(new LazyCompoundTag_1_16_4((NBTTagCompound) elem)); + } else { + list.add(WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(elem)); + } + } + return list; + } + return Collections.emptyList(); + } + + public ListTag getListTag(String key) { + NBTBase tag = nmsTag.get().get(key); + if (tag instanceof NBTTagList) { + return (ListTag) WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(tag); + } + return new ListTag(StringTag.class, Collections.emptyList()); + } + + @SuppressWarnings("unchecked") + public List getList(String key, Class listType) { + ListTag listTag = getListTag(key); + if (listTag.getType().equals(listType)) { + return (List) listTag.getValue(); + } else { + return Collections.emptyList(); + } + } + + public long[] getLongArray(String key) { + return nmsTag.get().getLongArray(key); + } + + public long getLong(String key) { + return nmsTag.get().getLong(key); + } + + public long asLong(String key) { + NBTBase value = nmsTag.get().get(key); + if (value instanceof NBTNumber) { + return ((NBTNumber) value).asLong(); + } + return 0; + } + + public short getShort(String key) { + return nmsTag.get().getShort(key); + } + + public String getString(String key) { + return nmsTag.get().getString(key); + } + + @Override + public String toString() { + return nmsTag.get().toString(); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/JavaVersionCheck.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/JavaVersionCheck.java new file mode 100644 index 000000000..b0fe317e2 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/util/JavaVersionCheck.java @@ -0,0 +1,53 @@ +package com.boydti.fawe.bukkit.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class JavaVersionCheck { + + public static final Logger logger = LoggerFactory.getLogger(JavaVersionCheck.class); + + private static int checkJavaVersion() { + String javaVersion = System.getProperty("java.version"); + final Matcher matcher = Pattern.compile("(?:1\\.)?(\\d+)").matcher(javaVersion); + if (!matcher.find()) { + logger.warn("Failed to determine Java version; Could not parse: {}", javaVersion); + return -1; + } + + final String version = matcher.group(1); + try { + return Integer.parseInt(version); + } catch (final NumberFormatException e) { + logger.warn("Failed to determine Java version; Could not parse {} from {}", version, javaVersion, e); + return -1; + } + } + + public static void checkJvm() { + if (checkJavaVersion() < 11) { + logger.warn("************************************************************"); + logger.warn("* WARNING - YOU ARE RUNNING AN OUTDATED VERSION OF JAVA."); + logger.warn("* FASTASYNCWORLDEDIT WILL STOP BEING COMPATIBLE WITH THIS VERSION OF"); + logger.warn("* JAVA WHEN MINECRAFT 1.17 IS RELEASED."); + logger.warn("*"); + logger.warn("* Please update the version of Java to 11. When Minecraft 1.17"); + logger.warn("* is released, support for versions of Java prior to 11 will"); + logger.warn("* be dropped."); + logger.warn("*"); + logger.warn("* Current Java version: {}", System.getProperty("java.version")); + logger.warn("************************************************************"); + } + if (checkJavaVersion() >= 15) { + logger.warn("************************************************************"); + logger.warn("* FastAsyncWorldEdit uses Nashorn for the craftscript engine."); + logger.warn("* Within Java 15, Nashorn has been removed from Java."); + logger.warn("* Until we add a suitable workaround, you should stick to Java 11"); + logger.warn("* to use all features of FastAsyncWorldEdit."); + logger.warn("************************************************************"); + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncBlock.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncBlock.java index 1f5c73c05..59c2dc706 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncBlock.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncBlock.java @@ -398,13 +398,18 @@ public class AsyncBlock implements Block { } public boolean applyBoneMeal(@NotNull BlockFace face) { - throw new UnsupportedOperationException("FAWE does not support this method"); + throw new UnsupportedOperationException("FAWE does not support this yet"); } public String getTranslationKey() { throw new UnsupportedOperationException("FAWE does not support this yet"); } + @NotNull + @Override + public float getDestroySpeed(@NotNull ItemStack itemStack) { + throw new UnsupportedOperationException("FAWE does not support this yet"); + } @NotNull @Override diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java index e934d45ea..590668be6 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java @@ -9,6 +9,7 @@ import org.bukkit.World; import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Entity; +import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; @@ -51,6 +52,11 @@ public class AsyncChunk implements Chunk { return z; } + @Override + public long getChunkKey() { + return Chunk.getChunkKey(getX(), getZ()); + } + @Override public AsyncWorld getWorld() { return world; @@ -140,7 +146,7 @@ public class AsyncChunk implements Chunk { @Override public boolean isSlimeChunk() { - return false; + return TaskManager.IMP.sync(() -> world.getChunkAt(x, z).isSlimeChunk()); } @Override @@ -170,17 +176,21 @@ public class AsyncChunk implements Chunk { @Override public long getInhabitedTime() { - return 0; //todo + return TaskManager.IMP.sync(() -> world.getChunkAt(x, z).getInhabitedTime()); } @Override public void setInhabitedTime(long ticks) { - //todo + world.getChunkAt(x, z).setInhabitedTime(ticks); } @Override public boolean contains(@NotNull BlockData block) { - //todo - return false; + return TaskManager.IMP.sync(() -> world.getChunkAt(x, z).contains(block)); + } + + @Override + public @NotNull PersistentDataContainer getPersistentDataContainer() { + return TaskManager.IMP.sync(() -> world.getChunkAt(x, z).getPersistentDataContainer()); } } diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncWorld.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncWorld.java index 8b230cb44..69ae9b7b6 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncWorld.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncWorld.java @@ -649,6 +649,21 @@ public class AsyncWorld extends PassthroughExtent implements World { parent.setThunderDuration(duration); } + @Override + public boolean isClearWeather() { + return parent.isClearWeather(); + } + + @Override + public void setClearWeatherDuration(int duration) { + parent.setClearWeatherDuration(duration); + } + + @Override + public int getClearWeatherDuration() { + return parent.getClearWeatherDuration(); + } + @Override public boolean createExplosion(double x, double y, double z, float power) { return this.createExplosion(x, y, z, power, false, true); diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBiomeRegistry.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBiomeRegistry.java index d61d833fe..5bccb3283 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBiomeRegistry.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBiomeRegistry.java @@ -19,6 +19,9 @@ package com.sk89q.worldedit.bukkit; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.util.translation.TranslationManager; import com.sk89q.worldedit.world.biome.BiomeData; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.registry.BiomeRegistry; @@ -34,6 +37,14 @@ class BukkitBiomeRegistry implements BiomeRegistry { BukkitBiomeRegistry() { } + @Override + public Component getRichName(BiomeType biomeType) { + return TranslatableComponent.of( + TranslationManager.makeTranslationKey("biome", biomeType.getId()) + ); + } + + @Deprecated @Nullable @Override public BiomeData getData(BiomeType biome) { diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockRegistry.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockRegistry.java index 6decfd7f2..221980f2d 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockRegistry.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockRegistry.java @@ -20,6 +20,7 @@ package com.sk89q.worldedit.bukkit; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockType; @@ -39,6 +40,14 @@ public class BukkitBlockRegistry extends BundledBlockRegistry { private BukkitBlockMaterial[] materialMap; + @Override + public Component getRichName(BlockType blockType) { + if (WorldEditPlugin.getInstance().getBukkitImplAdapter() != null) { + return WorldEditPlugin.getInstance().getBukkitImplAdapter().getRichBlockName(blockType); + } + return super.getRichName(blockType); + } + @Nullable @Override public BlockMaterial getMaterial(BlockType blockType) { diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntityProperties.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntityProperties.java index f0ae173bb..08d2a2f84 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntityProperties.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntityProperties.java @@ -40,6 +40,7 @@ import org.bukkit.entity.Projectile; import org.bukkit.entity.TNTPrimed; import org.bukkit.entity.Tameable; import org.bukkit.entity.Villager; +import org.bukkit.entity.WaterMob; import org.bukkit.entity.minecart.ExplosiveMinecart; import static com.google.common.base.Preconditions.checkNotNull; @@ -147,4 +148,9 @@ class BukkitEntityProperties implements EntityProperties { public boolean isPasteable() { return !(entity instanceof Player || entity instanceof ComplexEntityPart); } + + @Override + public boolean isWaterCreature() { + return entity instanceof WaterMob; + } } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitItemRegistry.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitItemRegistry.java index 2c49a6a5f..b76c06fbd 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitItemRegistry.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitItemRegistry.java @@ -19,13 +19,32 @@ package com.sk89q.worldedit.bukkit; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BundledItemRegistry; import org.bukkit.Material; import java.util.ArrayList; import java.util.Collection; -public class BukkitItemRegistry extends BundledItemRegistry { +class BukkitItemRegistry extends BundledItemRegistry { + @Override + public Component getRichName(ItemType itemType) { + if (WorldEditPlugin.getInstance().getBukkitImplAdapter() != null) { + return WorldEditPlugin.getInstance().getBukkitImplAdapter().getRichItemName(itemType); + } + return super.getRichName(itemType); + } + + @Override + public Component getRichName(BaseItemStack itemStack) { + if (WorldEditPlugin.getInstance().getBukkitImplAdapter() != null) { + return WorldEditPlugin.getInstance().getBukkitImplAdapter().getRichItemName(itemStack); + } + return super.getRichName(itemStack); + } + @Override public Collection values() { ArrayList values = new ArrayList<>(); diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitRegistries.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitRegistries.java index 005526a00..db865281b 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitRegistries.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitRegistries.java @@ -22,7 +22,6 @@ package com.sk89q.worldedit.bukkit; import com.sk89q.worldedit.world.registry.BiomeRegistry; import com.sk89q.worldedit.world.registry.BlockCategoryRegistry; import com.sk89q.worldedit.world.registry.BlockRegistry; -import com.sk89q.worldedit.world.registry.BundledItemRegistry; import com.sk89q.worldedit.world.registry.BundledRegistries; import com.sk89q.worldedit.world.registry.EntityRegistry; import com.sk89q.worldedit.world.registry.ItemCategoryRegistry; @@ -35,8 +34,8 @@ class BukkitRegistries extends BundledRegistries { private static final BukkitRegistries INSTANCE = new BukkitRegistries(); private final BlockRegistry blockRegistry = new BukkitBlockRegistry(); - private final ItemRegistry itemRegistry = new BukkitItemRegistry(); private final BiomeRegistry biomeRegistry = new BukkitBiomeRegistry(); + private final ItemRegistry itemRegistry = new BukkitItemRegistry(); private final EntityRegistry entityRegistry = new BukkitEntityRegistry(); private final BlockCategoryRegistry blockCategoryRegistry = new BukkitBlockCategoryRegistry(); private final ItemCategoryRegistry itemCategoryRegistry = new BukkitItemCategoryRegistry(); @@ -57,6 +56,11 @@ class BukkitRegistries extends BundledRegistries { return biomeRegistry; } + @Override + public ItemRegistry getItemRegistry() { + return itemRegistry; + } + @Override public BlockCategoryRegistry getBlockCategoryRegistry() { return blockCategoryRegistry; @@ -81,8 +85,4 @@ class BukkitRegistries extends BundledRegistries { return INSTANCE; } - @Override - public ItemRegistry getItemRegistry() { - return itemRegistry; - } } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index b1f476616..f2d2e1347 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -34,6 +34,7 @@ import com.sk89q.worldedit.bukkit.adapter.BukkitImplLoader; import com.sk89q.worldedit.bukkit.adapter.impl.FAWE_Spigot_v1_15_R2; import com.sk89q.worldedit.bukkit.adapter.impl.FAWE_Spigot_v1_16_R1; import com.sk89q.worldedit.bukkit.adapter.impl.FAWE_Spigot_v1_16_R2; +import com.sk89q.worldedit.bukkit.adapter.impl.FAWE_Spigot_v1_16_R3; import com.sk89q.worldedit.event.platform.CommandEvent; import com.sk89q.worldedit.event.platform.CommandSuggestionEvent; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; @@ -83,6 +84,7 @@ import java.util.Locale; import java.util.Optional; import java.util.logging.Level; +import static com.boydti.fawe.bukkit.util.JavaVersionCheck.checkJvm; import static com.google.common.base.Preconditions.checkNotNull; import static com.sk89q.worldedit.internal.anvil.ChunkDeleter.DELCHUNKS_FILE_NAME; @@ -185,6 +187,9 @@ public class WorldEditPlugin extends JavaPlugin { //implements TabCompleter // Setup metrics new Metrics(this, BSTATS_PLUGIN_ID); + + // Check whether the server runs on 11 or greater + checkJvm(); } private void setupPreWorldData() { @@ -299,6 +304,7 @@ public class WorldEditPlugin extends JavaPlugin { //implements TabCompleter adapterLoader.addClass(FAWE_Spigot_v1_15_R2.class); adapterLoader.addClass(FAWE_Spigot_v1_16_R1.class); adapterLoader.addClass(FAWE_Spigot_v1_16_R2.class); + adapterLoader.addClass(FAWE_Spigot_v1_16_R3.class); } catch (Throwable throwable) { throwable.printStackTrace(); } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java index cd3e86499..52286440a 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java @@ -36,12 +36,14 @@ import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.DataFixer; import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import org.bukkit.Location; import org.bukkit.World; @@ -52,10 +54,10 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import javax.annotation.Nullable; import java.util.Map; import java.util.OptionalInt; import java.util.Set; -import javax.annotation.Nullable; /** * An interface for adapters of various Bukkit implementations. @@ -127,6 +129,30 @@ public interface BukkitImplAdapter extends IBukkitAdapter { @Nullable Entity createEntity(Location location, BaseEntity state); + /** + * Gets the name for the given block. + * + * @param blockType the block + * @return The name + */ + Component getRichBlockName(BlockType blockType); + + /** + * Gets the name for the given item. + * + * @param itemType the item + * @return The name + */ + Component getRichItemName(ItemType itemType); + + /** + * Gets the name for the given item stack. + * + * @param itemStack the item stack + * @return The name + */ + Component getRichItemName(BaseItemStack itemStack); + /** * Get a map of {@code string -> property}. * diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplLoader.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplLoader.java index 78482c962..ed2d4d063 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplLoader.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplLoader.java @@ -48,7 +48,7 @@ public class BukkitImplLoader { private static final String LOAD_ERROR_MESSAGE = "\n**********************************************\n" - + "** This WorldEdit version does not fully support your version of Bukkit.\n" + + "** This FastAsyncWorldEdit version does not fully support your version of Bukkit.\n" + "**\n" + "** When working with blocks or undoing, chests will be empty, signs\n" + "** will be blank, and so on. There will be no support for entity\n" + "** and block property-related functions.\n" diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java index e97f894cd..58e86eb92 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java @@ -48,6 +48,7 @@ import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; @@ -57,6 +58,7 @@ import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.block.BlockTypesCache; import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import net.minecraft.server.v1_15_R1.BiomeBase; import net.minecraft.server.v1_15_R1.Block; @@ -279,6 +281,21 @@ public final class FAWE_Spigot_v1_15_R2 extends CachedBukkitAdapter implements I } } + @Override + public Component getRichBlockName(BlockType blockType) { + return parent.getRichBlockName(blockType); + } + + @Override + public Component getRichItemName(ItemType itemType) { + return parent.getRichItemName(itemType); + } + + @Override + public Component getRichItemName(BaseItemStack itemStack) { + return parent.getRichItemName(itemStack); + } + @Override public OptionalInt getInternalBlockStateId(BlockState state) { BlockMaterial_1_15_2 material = (BlockMaterial_1_15_2) state.getMaterial(); diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R1.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R1.java index cda1690cd..cd3625917 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R1.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R1.java @@ -47,6 +47,7 @@ import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; @@ -56,6 +57,7 @@ import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.block.BlockTypesCache; import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import net.minecraft.server.v1_16_R1.BiomeBase; import net.minecraft.server.v1_16_R1.Block; @@ -278,6 +280,21 @@ public final class FAWE_Spigot_v1_16_R1 extends CachedBukkitAdapter implements I } } + @Override + public Component getRichBlockName(BlockType blockType) { + return parent.getRichBlockName(blockType); + } + + @Override + public Component getRichItemName(ItemType itemType) { + return parent.getRichItemName(itemType); + } + + @Override + public Component getRichItemName(BaseItemStack itemStack) { + return parent.getRichItemName(itemStack); + } + @Override public OptionalInt getInternalBlockStateId(BlockState state) { BlockMaterial_1_16_1 material = (BlockMaterial_1_16_1) state.getMaterial(); diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R2.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R2.java index 056988439..966831373 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R2.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R2.java @@ -47,6 +47,7 @@ import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; @@ -56,6 +57,7 @@ import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.block.BlockTypesCache; import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import net.minecraft.server.v1_16_R2.BiomeBase; import net.minecraft.server.v1_16_R2.Block; @@ -279,6 +281,21 @@ public final class FAWE_Spigot_v1_16_R2 extends CachedBukkitAdapter implements I } } + @Override + public Component getRichBlockName(BlockType blockType) { + return parent.getRichBlockName(blockType); + } + + @Override + public Component getRichItemName(ItemType itemType) { + return parent.getRichItemName(itemType); + } + + @Override + public Component getRichItemName(BaseItemStack itemStack) { + return parent.getRichItemName(itemStack); + } + @Override public OptionalInt getInternalBlockStateId(BlockState state) { BlockMaterial_1_16_2 material = (BlockMaterial_1_16_2) state.getMaterial(); @@ -413,7 +430,7 @@ public final class FAWE_Spigot_v1_16_R2 extends CachedBukkitAdapter implements I @Override public int getInternalBiomeId(BiomeType biome) { - BiomeBase base = CraftBlock.biomeToBiomeBase(MinecraftServer.getServer().aX().b(IRegistry.ay), BukkitAdapter.adapt(biome)); - return MinecraftServer.getServer().aX().b(IRegistry.ay).a(base); + BiomeBase base = CraftBlock.biomeToBiomeBase(MinecraftServer.getServer().getCustomRegistry().b(IRegistry.ay), BukkitAdapter.adapt(biome)); + return MinecraftServer.getServer().getCustomRegistry().b(IRegistry.ay).a(base); } } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R3.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R3.java new file mode 100644 index 000000000..79fc3499b --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R3.java @@ -0,0 +1,437 @@ +/* + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl; + +import com.bekvon.bukkit.residence.commands.material; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.beta.IChunkGet; +import com.boydti.fawe.beta.implementation.packet.ChunkPacket; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.BlockMaterial_1_16_4; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.BukkitAdapter_1_16_4; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.BukkitGetBlocks_1_16_4; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.FAWEWorldNativeAccess_1_16; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.MapChunkUtil_1_16_4; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.nbt.LazyCompoundTag_1_16_4; +import com.google.common.base.Preconditions; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.blocks.TileEntityBlock; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.CachedBukkitAdapter; +import com.sk89q.worldedit.bukkit.adapter.IDelegateBukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.regen.Regen_v1_16_R3; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.LazyBaseEntity; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.world.RegenOptions; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.item.ItemType; +import com.sk89q.worldedit.world.registry.BlockMaterial; +import net.minecraft.server.v1_16_R3.BiomeBase; +import net.minecraft.server.v1_16_R3.Block; +import net.minecraft.server.v1_16_R3.BlockPosition; +import net.minecraft.server.v1_16_R3.Chunk; +import net.minecraft.server.v1_16_R3.ChunkCoordIntPair; +import net.minecraft.server.v1_16_R3.ChunkSection; +import net.minecraft.server.v1_16_R3.Entity; +import net.minecraft.server.v1_16_R3.EntityPlayer; +import net.minecraft.server.v1_16_R3.EntityTypes; +import net.minecraft.server.v1_16_R3.IBlockData; +import net.minecraft.server.v1_16_R3.IRegistry; +import net.minecraft.server.v1_16_R3.ItemStack; +import net.minecraft.server.v1_16_R3.MinecraftKey; +import net.minecraft.server.v1_16_R3.MinecraftServer; +import net.minecraft.server.v1_16_R3.NBTBase; +import net.minecraft.server.v1_16_R3.NBTTagCompound; +import net.minecraft.server.v1_16_R3.NBTTagInt; +import net.minecraft.server.v1_16_R3.PacketPlayOutMapChunk; +import net.minecraft.server.v1_16_R3.PlayerChunk; +import net.minecraft.server.v1_16_R3.TileEntity; +import net.minecraft.server.v1_16_R3.World; +import net.minecraft.server.v1_16_R3.WorldServer; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.v1_16_R3.CraftChunk; +import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_16_R3.block.CraftBlock; +import org.bukkit.craftbukkit.v1_16_R3.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_16_R3.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; +import org.bukkit.entity.Player; + +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; +import java.util.Map; +import java.util.OptionalInt; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static org.slf4j.LoggerFactory.getLogger; + +public final class FAWE_Spigot_v1_16_R3 extends CachedBukkitAdapter implements IDelegateBukkitImplAdapter { + private final Spigot_v1_16_R3 parent; + private char[] ibdToStateOrdinal; + + // ------------------------------------------------------------------------ + // Code that may break between versions of Minecraft + // ------------------------------------------------------------------------ + + public FAWE_Spigot_v1_16_R3() throws NoSuchFieldException, NoSuchMethodException { + this.parent = new Spigot_v1_16_R3(); + } + + @Override + public BukkitImplAdapter getParent() { + return parent; + } + + private synchronized boolean init() { + if (ibdToStateOrdinal != null && ibdToStateOrdinal[1] != 0) { + return false; + } + ibdToStateOrdinal = new char[Block.REGISTRY_ID.a()]; // size + for (int i = 0; i < ibdToStateOrdinal.length; i++) { + BlockState state = BlockTypesCache.states[i]; + BlockMaterial_1_16_4 material = (BlockMaterial_1_16_4) state.getMaterial(); + int id = Block.REGISTRY_ID.getId(material.getState()); + ibdToStateOrdinal[id] = state.getOrdinalChar(); + } + return true; + } + + @Override + public BlockMaterial getMaterial(BlockType blockType) { + Block block = getBlock(blockType); + return new BlockMaterial_1_16_4(block); + } + + @Override + public BlockMaterial getMaterial(BlockState state) { + IBlockData bs = ((CraftBlockData) Bukkit.createBlockData(state.getAsString())).getState(); + return new BlockMaterial_1_16_4(bs.getBlock(), bs); + } + + public Block getBlock(BlockType blockType) { + return IRegistry.BLOCK.get(new MinecraftKey(blockType.getNamespace(), blockType.getResource())); + } + + @SuppressWarnings("deprecation") + @Override + public BaseBlock getBlock(Location location) { + Preconditions.checkNotNull(location); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + final WorldServer handle = craftWorld.getHandle(); + Chunk chunk = handle.getChunkAt(x >> 4, z >> 4); + final BlockPosition blockPos = new BlockPosition(x, y, z); + org.bukkit.block.Block bukkitBlock = location.getBlock(); + BlockState state = BukkitAdapter.adapt(bukkitBlock.getBlockData()); + if (state.getBlockType().getMaterial().hasContainer()) { + + // Read the NBT data + TileEntity te = chunk.a(blockPos, Chunk.EnumTileEntityState.CHECK); + if (te != null) { + NBTTagCompound tag = new NBTTagCompound(); + te.save(tag); // readTileEntityIntoTag - load data + return state.toBaseBlock((CompoundTag) toNative(tag)); + } + } + + return state.toBaseBlock(); + } + + @Override + public Set getSupportedSideEffects() { + return SideEffectSet.defaults().getSideEffectsToApply(); + } + + public boolean setBlock(org.bukkit.Chunk chunk, int x, int y, int z, BlockStateHolder state, boolean update) { + CraftChunk craftChunk = (CraftChunk) chunk; + Chunk nmsChunk = craftChunk.getHandle(); + World nmsWorld = nmsChunk.getWorld(); + + BlockPosition blockPos = new BlockPosition(x, y, z); + IBlockData blockData = ((BlockMaterial_1_16_4) state.getMaterial()).getState(); + ChunkSection[] sections = nmsChunk.getSections(); + int y4 = y >> 4; + ChunkSection section = sections[y4]; + + IBlockData existing; + if (section == null) { + existing = ((BlockMaterial_1_16_4) BlockTypes.AIR.getDefaultState().getMaterial()).getState(); + } else { + existing = section.getType(x & 15, y & 15, z & 15); + } + + + nmsChunk.removeTileEntity(blockPos); // Force delete the old tile entity + + CompoundTag nativeTag = state instanceof BaseBlock ? ((BaseBlock)state).getNbtData() : null; + if (nativeTag != null || existing instanceof TileEntityBlock) { + nmsWorld.setTypeAndData(blockPos, blockData, 0); + // remove tile + if (nativeTag != null) { + // We will assume that the tile entity was created for us, + // though we do not do this on the Forge version + TileEntity tileEntity = nmsWorld.getTileEntity(blockPos); + if (tileEntity != null) { + NBTTagCompound tag = (NBTTagCompound) fromNative(nativeTag); + tag.set("x", NBTTagInt.a(x)); + tag.set("y", NBTTagInt.a(y)); + tag.set("z", NBTTagInt.a(z)); + tileEntity.load(tileEntity.getBlock(), tag); // readTagIntoTileEntity - load data + } + } + } else { + if (existing == blockData) { + return true; + } + if (section == null) { + if (blockData.isAir()) { + return true; + } + sections[y4] = section = new ChunkSection(y4 << 4); + } + nmsChunk.setType(blockPos, blockData, false); + } + if (update) { + nmsWorld.getMinecraftWorld().notify(blockPos, existing, blockData, 0); + } + return true; + } + + @Override + public WorldNativeAccess createWorldNativeAccess(org.bukkit.World world) { + return new FAWEWorldNativeAccess_1_16(this, + new WeakReference<>(((CraftWorld)world).getHandle())); + } + + @Nullable + private static String getEntityId(Entity entity) { + MinecraftKey minecraftkey = EntityTypes.getName(entity.getEntityType()); + return minecraftkey == null ? null : minecraftkey.toString(); + } + + private static void readEntityIntoTag(Entity entity, NBTTagCompound tag) { + entity.save(tag); + } + + @Override + public BaseEntity getEntity(org.bukkit.entity.Entity entity) { + Preconditions.checkNotNull(entity); + + CraftEntity craftEntity = ((CraftEntity) entity); + Entity mcEntity = craftEntity.getHandle(); + + String id = getEntityId(mcEntity); + + if (id != null) { + EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); + Supplier saveTag = () -> { + NBTTagCompound tag = new NBTTagCompound(); + readEntityIntoTag(mcEntity, tag); + + //add Id for AbstractChangeSet to work + CompoundTag natve = (CompoundTag) toNative(tag); + natve.getValue().put("Id", new StringTag(id)); + return natve; + }; + return new LazyBaseEntity(type, saveTag); + } else { + return null; + } + } + + @Override + public Component getRichBlockName(BlockType blockType) { + return parent.getRichBlockName(blockType); + } + + @Override + public Component getRichItemName(ItemType itemType) { + return parent.getRichItemName(itemType); + } + + @Override + public Component getRichItemName(BaseItemStack itemStack) { + return parent.getRichItemName(itemStack); + } + + @Override + public OptionalInt getInternalBlockStateId(BlockState state) { + BlockMaterial_1_16_4 material = (BlockMaterial_1_16_4) state.getMaterial(); + IBlockData mcState = material.getCraftBlockData().getState(); + return OptionalInt.of(Block.REGISTRY_ID.getId(mcState)); + } + + @Override + public BlockState adapt(BlockData blockData) { + CraftBlockData cbd = ((CraftBlockData) blockData); + IBlockData ibd = cbd.getState(); + return adapt(ibd); + } + + public BlockState adapt(IBlockData ibd) { + return BlockTypesCache.states[adaptToChar(ibd)]; + } + + /** + * @deprecated + * Method unused. Use #adaptToChar(IBlockData). + */ + @Deprecated + public int adaptToInt(IBlockData ibd) { + synchronized (this) { + try { + int id = Block.REGISTRY_ID.getId(ibd); + return ibdToStateOrdinal[id]; + } catch (NullPointerException e) { + init(); + return adaptToInt(ibd); + } + } + } + + public char adaptToChar(IBlockData ibd) { + synchronized (this) { + try { + int id = Block.REGISTRY_ID.getId(ibd); + return ibdToStateOrdinal[id]; + } catch (NullPointerException e) { + init(); + return adaptToChar(ibd); + } catch (ArrayIndexOutOfBoundsException e1) { + getLogger(FAWE_Spigot_v1_16_R3.class) + .error("Attempted to convert {} with ID {} to char. ibdToStateOrdinal length: {}. Defaulting to air!", + ibd.getBlock(), Block.REGISTRY_ID.getId(ibd), ibdToStateOrdinal.length, e1); + return 0; + } + } + } + + @Override + public > BlockData adapt(B state) { + BlockMaterial_1_16_4 material = (BlockMaterial_1_16_4) state.getMaterial(); + return material.getCraftBlockData(); + } + + private MapChunkUtil_1_16_4 mapUtil = new MapChunkUtil_1_16_4(); + + @Override + public void sendFakeChunk(org.bukkit.World world, Player player, ChunkPacket packet) { + WorldServer nmsWorld = ((CraftWorld) world).getHandle(); + PlayerChunk map = BukkitAdapter_1_16_4.getPlayerChunk(nmsWorld, packet.getChunkX(), packet.getChunkZ()); + if (map != null && map.hasBeenLoaded()) { + boolean flag = false; + PlayerChunk.d players = map.players; + Stream stream = players.a(new ChunkCoordIntPair(packet.getChunkX(), packet.getChunkZ()), flag); + + EntityPlayer checkPlayer = player == null ? null : ((CraftPlayer) player).getHandle(); + stream.filter(entityPlayer -> checkPlayer == null || entityPlayer == checkPlayer) + .forEach(entityPlayer -> { + synchronized (packet) { + PacketPlayOutMapChunk nmsPacket = (PacketPlayOutMapChunk) packet.getNativePacket(); + if (nmsPacket == null) { + nmsPacket = mapUtil.create( this, packet); + packet.setNativePacket(nmsPacket); + } + try { + FaweCache.IMP.CHUNK_FLAG.get().set(true); + entityPlayer.playerConnection.sendPacket(nmsPacket); + } finally { + FaweCache.IMP.CHUNK_FLAG.get().set(false); + } + } + }); + } + } + + @Override + public Map> getProperties(BlockType blockType) { + return getParent().getProperties(blockType); + } + + @Override + public org.bukkit.inventory.ItemStack adapt(BaseItemStack item) { + ItemStack stack = new ItemStack(IRegistry.ITEM.get(MinecraftKey.a(item.getType().getId())), item.getAmount()); + stack.setTag(((NBTTagCompound) fromNative(item.getNbtData()))); + return CraftItemStack.asCraftMirror(stack); + } + + @Override + public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { + final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack); + final BaseItemStack weStack = new BaseItemStack(BukkitAdapter.asItemType(itemStack.getType()), itemStack.getAmount()); + weStack.setNbtData(((CompoundTag) toNative(nmsStack.getTag()))); + return weStack; + } + + @Override + public Tag toNative(NBTBase foreign) { + return parent.toNative(foreign); + } + + @Override + public NBTBase fromNative(Tag foreign) { + if (foreign instanceof LazyCompoundTag_1_16_4) { + return ((LazyCompoundTag_1_16_4) foreign).get(); + } + return parent.fromNative(foreign); + } + + @Override + public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception { + return new Regen_v1_16_R3(bukkitWorld, region, target, options).regenerate(); + } + + @Override + public IChunkGet get(org.bukkit.World world, int chunkX, int chunkZ) { + return new BukkitGetBlocks_1_16_4(world, chunkX, chunkZ); + } + + @Override + public int getInternalBiomeId(BiomeType biome) { + BiomeBase base = CraftBlock.biomeToBiomeBase(MinecraftServer.getServer().getCustomRegistry().b(IRegistry.ay), BukkitAdapter.adapt(biome)); + return MinecraftServer.getServer().getCustomRegistry().b(IRegistry.ay).a(base); + } +} diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_15_R2.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_15_R2.java index 937fc5de8..14edd27a8 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_15_R2.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_15_R2.java @@ -505,5 +505,9 @@ public class Regen_v1_15_R2 extends Regenerator { + + private static final Field serverWorldsField; + private static final Field worldPaperConfigField; + private static final Field flatBedrockField; + private static final Field generatorSettingBaseSupplierField; + private static final Field generatorSettingFlatField; + private static final Field delegateField; + private static final Field chunkProviderField; + + //list of chunk stati in correct order without FULL + private static final Map chunkStati = new LinkedHashMap<>(); + + static { + chunkStati.put(ChunkStatus.EMPTY, Regenerator.Concurrency.FULL); // radius -1, does nothing + chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Regenerator.Concurrency.NONE); // uses unsynchronized maps + chunkStati.put(ChunkStatus.STRUCTURE_REFERENCES, Regenerator.Concurrency.FULL); // radius 8, but no writes to other chunks, only current chunk + chunkStati.put(ChunkStatus.BIOMES, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.NOISE, Regenerator.Concurrency.RADIUS); // radius 8 + chunkStati.put(ChunkStatus.SURFACE, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results + chunkStati.put(ChunkStatus.LIQUID_CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results + chunkStati.put(ChunkStatus.FEATURES, Regenerator.Concurrency.NONE); // uses unsynchronized maps + chunkStati.put(ChunkStatus.LIGHT, Regenerator.Concurrency.FULL); // radius 1, but no writes to other chunks, only current chunk + chunkStati.put(ChunkStatus.SPAWN, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.HEIGHTMAPS, Regenerator.Concurrency.FULL); // radius 0 + + try { + serverWorldsField = CraftServer.class.getDeclaredField("worlds"); + serverWorldsField.setAccessible(true); + + Field tmpPaperConfigField = null; + Field tmpFlatBedrockField = null; + try { //only present on paper + tmpPaperConfigField = World.class.getDeclaredField("paperConfig"); + tmpPaperConfigField.setAccessible(true); + + tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock"); + tmpFlatBedrockField.setAccessible(true); + } catch (Exception e) { + tmpPaperConfigField = null; + tmpFlatBedrockField = null; + } + worldPaperConfigField = tmpPaperConfigField; + flatBedrockField = tmpFlatBedrockField; + + generatorSettingBaseSupplierField = ChunkGeneratorAbstract.class.getDeclaredField("h"); + generatorSettingBaseSupplierField.setAccessible(true); + + generatorSettingFlatField = ChunkProviderFlat.class.getDeclaredField("e"); + generatorSettingFlatField.setAccessible(true); + + delegateField = CustomChunkGenerator.class.getDeclaredField("delegate"); + delegateField.setAccessible(true); + + chunkProviderField = WorldServer.class.getDeclaredField("chunkProvider"); + chunkProviderField.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + //runtime + private WorldServer originalNMSWorld; + private ChunkProviderServer originalChunkProvider; + private WorldServer freshNMSWorld; + private ChunkProviderServer freshChunkProvider; + private Convertable.ConversionSession session; + private DefinedStructureManager structureManager; + private LightEngineThreaded lightEngine; + private ChunkGenerator generator; + + private Path tempDir; + + private boolean generateFlatBedrock = false; + + public Regen_v1_16_R3(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) { + super(originalBukkitWorld, region, target, options); + } + + @Override + protected boolean prepare() { + this.originalNMSWorld = ((CraftWorld) originalBukkitWorld).getHandle(); + originalChunkProvider = originalNMSWorld.getChunkProvider(); + if (!(originalChunkProvider instanceof ChunkProviderServer)) { + return false; + } + + //flat bedrock? (only on paper) + try { + generateFlatBedrock = flatBedrockField.getBoolean(worldPaperConfigField.get(originalNMSWorld)); + } catch (Exception ignored) { + } + + seed = options.getSeed().orElse(originalNMSWorld.getSeed()); + chunkStati.forEach((s, c) -> super.chunkStati.put(new ChunkStatusWrap(s), c)); + + return true; + } + + @Override + protected boolean initNewWorld() throws Exception { + //world folder + tempDir = java.nio.file.Files.createTempDirectory("WorldEditWorldGen"); + + //prepare for world init (see upstream implementation for reference) + org.bukkit.World.Environment env = originalBukkitWorld.getEnvironment(); + org.bukkit.generator.ChunkGenerator gen = originalBukkitWorld.getGenerator(); + Convertable convertable = Convertable.a(tempDir); + ResourceKey worldDimKey = getWorldDimKey(env); + session = convertable.c("worldeditregentempworld", worldDimKey); + WorldDataServer originalWorldData = originalNMSWorld.worldDataServer; + + MinecraftServer server = originalNMSWorld.getServer().getServer(); + WorldDataServer levelProperties = (WorldDataServer) server.getSaveData(); + RegistryReadOps nbtRegOps = RegistryReadOps.a(DynamicOpsNBT.a, server.dataPackResources.h(), IRegistryCustom.b()); + GeneratorSettings newOpts = GeneratorSettings.a.encodeStart(nbtRegOps, levelProperties.getGeneratorSettings()).flatMap(tag -> GeneratorSettings.a.parse(this.recursivelySetSeed(new Dynamic<>(nbtRegOps, tag), seed, new HashSet<>()))).result().orElseThrow(() -> new IllegalStateException("Unable to map GeneratorOptions")); + WorldSettings newWorldSettings = new WorldSettings("worldeditregentempworld", originalWorldData.b.getGameType(), originalWorldData.b.hardcore, originalWorldData.b.getDifficulty(), originalWorldData.b.e(), originalWorldData.b.getGameRules(), originalWorldData.b.g()); + WorldDataServer newWorldData = new WorldDataServer(newWorldSettings, newOpts, Lifecycle.stable()); + + //init world + freshNMSWorld = Fawe.get().getQueueHandler().sync((Supplier) () -> new WorldServer(server, server.executorService, session, newWorldData, originalNMSWorld.getDimensionKey(), originalNMSWorld.getDimensionManager(), new RegenNoOpWorldLoadListener(), ((WorldDimension) newOpts.d().a(worldDimKey)).c(), originalNMSWorld.isDebugWorld(), seed, ImmutableList.of(), false, env, gen) { + @Override + public void doTick(BooleanSupplier booleansupplier) { //no ticking + } + + private final BiomeBase singleBiome = options.hasBiomeType() ? RegistryGeneration.WORLDGEN_BIOME.get(MinecraftKey.a(options.getBiomeType().getId())) : null; + + @Override + public BiomeBase a(int i, int j, int k) { + if (options.hasBiomeType()) { + return singleBiome; + } + return this.getChunkProvider().getChunkGenerator().getWorldChunkManager().getBiome(i, j, k); + } + }).get(); + freshNMSWorld.savingDisabled = true; + removeWorldFromWorldsMap(); + newWorldData.checkName(originalNMSWorld.worldDataServer.getName()); //rename to original world name + + freshChunkProvider = new ChunkProviderServer(freshNMSWorld, session, server.getDataFixer(), server.getDefinedStructureManager(), server.executorService, originalChunkProvider.chunkGenerator, freshNMSWorld.spigotConfig.viewDistance, server.isSyncChunkWrites(), new RegenNoOpWorldLoadListener(), () -> server.E().getWorldPersistentData()) { + // redirect to our protoChunks list + @Override + public IChunkAccess getChunkAt(int x, int z, ChunkStatus chunkstatus, boolean flag) { + return getProtoChunkAt(x, z); + } + }; + chunkProviderField.set(freshNMSWorld, freshChunkProvider); + + //generator + if (originalChunkProvider.getChunkGenerator() instanceof ChunkProviderFlat) { + GeneratorSettingsFlat generatorSettingFlat = (GeneratorSettingsFlat) generatorSettingFlatField.get(originalChunkProvider.getChunkGenerator()); + generator = new ChunkProviderFlat(generatorSettingFlat); + } else if (originalChunkProvider.getChunkGenerator() instanceof ChunkGeneratorAbstract) { + Supplier generatorSettingBaseSupplier = (Supplier) generatorSettingBaseSupplierField.get(originalChunkProvider.getChunkGenerator()); + WorldChunkManager chunkManager = originalChunkProvider.getChunkGenerator().getWorldChunkManager(); + if (chunkManager instanceof WorldChunkManagerOverworld) { + chunkManager = fastOverWorldChunkManager(chunkManager); + } + generator = new ChunkGeneratorAbstract(chunkManager, seed, generatorSettingBaseSupplier); + } else if (originalChunkProvider.getChunkGenerator() instanceof CustomChunkGenerator) { + ChunkGenerator delegate = (ChunkGenerator) delegateField.get(originalChunkProvider.getChunkGenerator()); + generator = delegate; + } else { + System.out.println("Unsupported generator type " + originalChunkProvider.getChunkGenerator().getClass().getName()); + return false; + } + if (originalNMSWorld.generator != null) { + // wrap custom world generator + generator = new CustomChunkGenerator(freshNMSWorld, generator, originalNMSWorld.generator); + generateConcurrent = originalNMSWorld.generator.isParallelCapable(); + } + + //lets start then + structureManager = server.getDefinedStructureManager(); + lightEngine = freshChunkProvider.getLightEngine(); + + return true; + } + + @Override + protected void cleanup() { + try { + session.close(); + } catch (Exception e) { + } + + //shutdown chunk provider + try { + Fawe.get().getQueueHandler().sync(() -> { + try { + freshChunkProvider.close(false); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (Exception e) { + } + + //remove world from server + try { + Fawe.get().getQueueHandler().sync(() -> { + removeWorldFromWorldsMap(); + }); + } catch (Exception e) { + } + + //delete directory + try { + SafeFiles.tryHardToDeleteDir(tempDir); + } catch (Exception e) { + } + } + + @Override + protected ProtoChunk createProtoChunk(int x, int z) { + return new ProtoChunk(new ChunkCoordIntPair(x, z), ChunkConverter.a) { + public boolean generateFlatBedrock() { + return generateFlatBedrock; + } + }; + } + + @Override + protected Chunk createChunk(ProtoChunk protoChunk) { + return new Chunk(freshNMSWorld, protoChunk); + } + + @Override + protected ChunkStatusWrap getFullChunkStatus() { + return new ChunkStatusWrap(ChunkStatus.FULL); + } + + @Override + protected List getBlockPopulators() { + return originalNMSWorld.getWorld().getPopulators(); + } + + @Override + protected void populate(Chunk chunk, Random random, BlockPopulator pop) { + pop.populate(freshNMSWorld.getWorld(), random, chunk.bukkitChunk); + } + + @Override + protected IChunkCache initSourceQueueCache() { + return (chunkX, chunkZ) -> new BukkitGetBlocks_1_16_4(freshNMSWorld, chunkX, chunkZ) { + @Override + public Chunk ensureLoaded(World nmsWorld, int x, int z) { + return getChunkAt(x, z); + } + }; + } + + protected class ChunkStatusWrap extends Regenerator.ChunkStatusWrapper { + + private final ChunkStatus chunkStatus; + + public ChunkStatusWrap(ChunkStatus chunkStatus) { + this.chunkStatus = chunkStatus; + } + + @Override + public int requiredNeigborChunkRadius() { + return chunkStatus.f(); + } + + @Override + public String name() { + return chunkStatus.d(); + } + + @Override + public void processChunk(Long xz, List accessibleChunks) { + chunkStatus.a(freshNMSWorld, + generator, + structureManager, + lightEngine, + c -> CompletableFuture.completedFuture(Either.left(c)), + accessibleChunks); + } + } + + //util + private void removeWorldFromWorldsMap() { + Fawe.get().getQueueHandler().sync(() -> { + try { + Map map = (Map) serverWorldsField.get(Bukkit.getServer()); + map.remove("worldeditregentempworld"); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + } + + private ResourceKey getWorldDimKey(org.bukkit.World.Environment env) { + switch (env) { + case NETHER: + return WorldDimension.THE_NETHER; + case THE_END: + return WorldDimension.THE_END; + case NORMAL: + default: + return WorldDimension.OVERWORLD; + } + } + + private Dynamic recursivelySetSeed(Dynamic dynamic, long seed, Set> seen) { + return !seen.add(dynamic) ? dynamic : dynamic.updateMapValues((pair) -> { + if (((Dynamic) pair.getFirst()).asString("").equals("seed")) { + return pair.mapSecond((v) -> { + return v.createLong(seed); + }); + } else { + return ((Dynamic) pair.getSecond()).getValue() instanceof NBTTagCompound ? pair.mapSecond((v) -> { + return this.recursivelySetSeed((Dynamic) v, seed, seen); + }) : pair; + + } + }); + } + + private WorldChunkManager fastOverWorldChunkManager(WorldChunkManager chunkManager) throws Exception { + Field legacyBiomeInitLayerField = WorldChunkManagerOverworld.class.getDeclaredField("i"); + legacyBiomeInitLayerField.setAccessible(true); + Field largeBiomesField = WorldChunkManagerOverworld.class.getDeclaredField("j"); + largeBiomesField.setAccessible(true); + Field biomeRegistryField = WorldChunkManagerOverworld.class.getDeclaredField("k"); + biomeRegistryField.setAccessible(true); + Field areaLazyField = GenLayer.class.getDeclaredField("b"); + areaLazyField.setAccessible(true); + Method initAreaFactoryMethod = GenLayers.class.getDeclaredMethod("a", boolean.class, int.class, int.class, LongFunction.class); + initAreaFactoryMethod.setAccessible(true); + + //init new WorldChunkManagerOverworld + boolean legacyBiomeInitLayer = legacyBiomeInitLayerField.getBoolean(chunkManager); + boolean largebiomes = largeBiomesField.getBoolean(chunkManager); + IRegistry biomeRegistrynms = (IRegistry) biomeRegistryField.get(chunkManager); + IRegistry biomeRegistry; + if (options.hasBiomeType()) { + BiomeBase biome = RegistryGeneration.WORLDGEN_BIOME.get(MinecraftKey.a(options.getBiomeType().getId())); + biomeRegistry = new RegistryMaterials<>(ResourceKey.a(new MinecraftKey("fawe_biomes")), Lifecycle.experimental()); + ((RegistryMaterials) biomeRegistry).a(0, RegistryGeneration.WORLDGEN_BIOME.c(biome).get(), biome, Lifecycle.experimental()); + } else { + biomeRegistry = biomeRegistrynms; + } + chunkManager = new FastWorldChunkManagerOverworld(seed, legacyBiomeInitLayer, largebiomes, biomeRegistry); + + //replace genLayer + AreaFactory factory = (AreaFactory) initAreaFactoryMethod.invoke(null, legacyBiomeInitLayer, largebiomes ? 6 : 4, 4, (LongFunction) (l -> new FastWorldGenContextArea(seed, l))); + ((FastWorldChunkManagerOverworld) chunkManager).genLayer = new FastGenLayer(factory); + + return chunkManager; + } + + private static class FastWorldChunkManagerOverworld extends WorldChunkManager { + + private GenLayer genLayer; + private final IRegistry k; + private final boolean isSingleRegistry; + + public FastWorldChunkManagerOverworld(long seed, boolean legacyBiomeInitLayer, boolean largeBiomes, IRegistry biomeRegistry) { + super(biomeRegistry.g().collect(Collectors.toList())); + this.k = biomeRegistry; + this.isSingleRegistry = biomeRegistry.d().size() == 1; + this.genLayer = GenLayers.a(seed, legacyBiomeInitLayer, largeBiomes ? 6 : 4, 4); + } + + @Override + protected Codec a() { + return WorldChunkManagerOverworld.e; + } + + @Override + public BiomeBase getBiome(int i, int i1, int i2) { + if (this.isSingleRegistry) { + return this.k.fromId(0); + } + return this.genLayer.a(this.k, i, i2); + } + } + + + private static class FastWorldGenContextArea implements AreaContextTransformed { + + private final ConcurrentHashMap sharedAreaMap = new ConcurrentHashMap<>(); + private final NoiseGeneratorPerlin perlinNoise; + private final long magicrandom; + private final ConcurrentHashMap map = new ConcurrentHashMap<>(); //needed for multithreaded generation + + public FastWorldGenContextArea(long seed, long lconst) { + this.magicrandom = mix(seed, lconst); + this.perlinNoise = new NoiseGeneratorPerlin(new Random(seed)); + } + + @Override + public FastAreaLazy a(AreaTransformer8 var0) { + return new FastAreaLazy(sharedAreaMap, var0); + } + + @Override + public void a(long x, long z) { + long l = this.magicrandom; + l = LinearCongruentialGenerator.a(l, x); + l = LinearCongruentialGenerator.a(l, z); + l = LinearCongruentialGenerator.a(l, x); + l = LinearCongruentialGenerator.a(l, z); + this.map.put(Thread.currentThread().getId(), l); + } + + @Override + public int a(int y) { + long tid = Thread.currentThread().getId(); + long e = this.map.computeIfAbsent(tid, i -> 0L); + int mod = (int) Math.floorMod(e >> 24L, (long) y); + this.map.put(tid, LinearCongruentialGenerator.a(e, this.magicrandom)); + return mod; + } + + @Override + public NoiseGeneratorPerlin b() { + return this.perlinNoise; + } + + private static long mix(long seed, long lconst) { + long l1 = lconst; + l1 = LinearCongruentialGenerator.a(l1, lconst); + l1 = LinearCongruentialGenerator.a(l1, lconst); + l1 = LinearCongruentialGenerator.a(l1, lconst); + long l2 = seed; + l2 = LinearCongruentialGenerator.a(l2, l1); + l2 = LinearCongruentialGenerator.a(l2, l1); + l2 = LinearCongruentialGenerator.a(l2, l1); + return l2; + } + } + + private static class FastGenLayer extends GenLayer { + + private final FastAreaLazy areaLazy; + + public FastGenLayer(AreaFactory factory) throws Exception { + super(() -> null); + this.areaLazy = factory.make(); + } + + @Override + public BiomeBase a(IRegistry registry, int x, int z) { + ResourceKey key = BiomeRegistry.a(this.areaLazy.a(x, z)); + if (key == null) + return registry.a(BiomeRegistry.a(0)); + BiomeBase biome = registry.a(key); + if (biome == null) + return registry.a(BiomeRegistry.a(0)); + return biome; + } + } + + private static class FastAreaLazy implements Area { + + private final AreaTransformer8 transformer; + //ConcurrentHashMap is 50% faster that Long2IntLinkedOpenHashMap in a syncronized context + //using a map for each thread worsens the performance significantly due to cache misses (factor 5) + private final ConcurrentHashMap sharedMap; + + public FastAreaLazy(ConcurrentHashMap sharedMap, AreaTransformer8 transformer) { + this.sharedMap = sharedMap; + this.transformer = transformer; + } + + @Override + public int a(int x, int z) { + long zx = ChunkCoordIntPair.pair(x, z); + return this.sharedMap.computeIfAbsent(zx, i -> this.transformer.apply(x, z)); + } + } + + private static class RegenNoOpWorldLoadListener implements WorldLoadListener { + + private RegenNoOpWorldLoadListener() { + } + + @Override + public void a(ChunkCoordIntPair chunkCoordIntPair) { + } + + @Override + public void a(ChunkCoordIntPair chunkCoordIntPair, @Nullable ChunkStatus chunkStatus) { + } + + @Override + public void b() { + } + + @Override + public void setChunkRadius(int i) { + } + } +} diff --git a/worldedit-bukkit/src/main/resources/plugin.yml b/worldedit-bukkit/src/main/resources/plugin.yml index 2f968bff8..7bc13f4d8 100644 --- a/worldedit-bukkit/src/main/resources/plugin.yml +++ b/worldedit-bukkit/src/main/resources/plugin.yml @@ -5,10 +5,10 @@ load: STARTUP api-version: 1.15 softdepend: [Vault] provides: [WorldEdit] -website: https://intellectualsites.github.io/download/fawe.html +website: https://www.spigotmc.org/resources/13932/ description: Blazingly fast world manipulation for builders, large networks and developers. authors: [Empire92, MattBDev, IronApollo, dordsor21, NotMyFault] -loadbefore: [BannerBoard, WorldGuard, PlotSquared] +loadbefore: [WorldGuard, PlotSquared] database: false permissions: fawe.plotsquared: diff --git a/worldedit-bukkit/src/main/resources/worldedit-adapters.jar b/worldedit-bukkit/src/main/resources/worldedit-adapters.jar index d86472521..cc42ab3c0 100644 Binary files a/worldedit-bukkit/src/main/resources/worldedit-adapters.jar and b/worldedit-bukkit/src/main/resources/worldedit-adapters.jar differ diff --git a/worldedit-core/src/main/java/com/boydti/fawe/FaweVersion.java b/worldedit-core/src/main/java/com/boydti/fawe/FaweVersion.java index 01a16086a..97a44f701 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/FaweVersion.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/FaweVersion.java @@ -1,5 +1,6 @@ package com.boydti.fawe; +/** An internal FAWE class not meant for public use. **/ public class FaweVersion { public final int year; public final int month; diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharSetBlocks.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharSetBlocks.java index 249b3e1b0..85ba848a6 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharSetBlocks.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharSetBlocks.java @@ -57,7 +57,7 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { if (biomes == null) { return null; } - return biomes[y << 2 | z & 12 | x >> 2]; + return biomes[(y >> 2) << 4 | (z & 3) << 2 | x & 3]; } @Override @@ -90,7 +90,7 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { if (biomes == null) { biomes = new BiomeType[1024]; } - biomes[y << 2 | z & 12 | x >> 2] = biome; + biomes[(y >> 2) << 4 | (z & 3) << 2 | x & 3] = biome; return true; } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/filter/DistrFilter.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/filter/DistrFilter.java index 38494b82e..936ccbd82 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/filter/DistrFilter.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/filter/DistrFilter.java @@ -40,7 +40,11 @@ public class DistrFilter extends ForkedFilter { @Override public final void applyBlock(FilterBlock block) { - counter[block.getOrdinal()]++; + int ordinal = block.getOrdinal(); + if (ordinal == 0) { + ordinal = 1; + } + counter[ordinal]++; } public int getTotal(ABlockMask mask) { diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/queue/ParallelQueueExtent.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/queue/ParallelQueueExtent.java index e558e2cea..0314c1b6e 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/queue/ParallelQueueExtent.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/queue/ParallelQueueExtent.java @@ -178,12 +178,12 @@ public class ParallelQueueExtent extends PassthroughExtent implements IQueueWrap @Override public List> getBlockDistributionWithData(Region region) { - return apply(region, new DistrFilter(), false).getDistribution(); + return apply(region, new DistrFilter(), true).getDistribution(); } @Override public List> getBlockDistribution(Region region) { - return apply(region, new DistrFilter(), false).getTypeDistribution(); + return apply(region, new DistrFilter(), true).getTypeDistribution(); } /** diff --git a/worldedit-core/src/main/java/com/boydti/fawe/config/Settings.java b/worldedit-core/src/main/java/com/boydti/fawe/config/Settings.java index 5c3c91203..7edd24c78 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/config/Settings.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/config/Settings.java @@ -394,6 +394,18 @@ public class Settings extends Config { " - Requires combine_stages = true" }) public boolean SEND_BEFORE_HISTORY = false; + + @Comment({ + "Sets a maximum limit (in kb) for the size of a player's schematics directory (per-player mode only)", + "Set to -1 to disable" + }) + public int PER_PLAYER_FILE_SIZE_LIMIT = -1; + + @Comment({ + "Sets a maximum limit for the amount of schematics in a player's schematics directory (per-player mode only)", + "Set to -1 to disable" + }) + public int PER_PLAYER_FILE_NUM_LIMIT = -1; } public static class PLOTSQUARED_INTEGRATION { diff --git a/worldedit-core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java b/worldedit-core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java index dac042616..a4150f274 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java @@ -481,7 +481,7 @@ public class MCAChunk implements IChunk { @Override public BiomeType getBiomeType(int x, int y, int z) { - return this.biomes[y << 2 | z & 12 | x >> 2]; + return this.biomes[(y >> 2) << 4 | (z & 3) << 2 | x & 3]; } @Override @@ -505,7 +505,7 @@ public class MCAChunk implements IChunk { @Override public boolean setBiome(int x, int y, int z, BiomeType biome) { setModified(); - biomes[y << 2 | z & 12 | x >> 2] = biome; + biomes[(y >> 2) << 4 | (z & 3) << 2 | x & 3] = biome; return true; } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/CPUOptimizedClipboard.java b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/CPUOptimizedClipboard.java index 859b3a233..944fb5446 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/CPUOptimizedClipboard.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/CPUOptimizedClipboard.java @@ -116,14 +116,8 @@ public class CPUOptimizedClipboard extends LinearClipboard { return nbtMapIndex.get(index); } - private int yLast; - private int yLastI; - private int zLast; - private int zLastI; - public int getIndex(int x, int y, int z) { - return x + ((yLast == y) ? yLastI : (yLastI = (yLast = y) * getArea())) + ((zLast == z) ? zLastI - : (zLastI = (zLast = z) * getWidth())); + return x + y * getArea() + z * getWidth(); } @Override diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java index aa2160029..c7100a531 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java @@ -324,8 +324,7 @@ public class DiskOptimizedClipboard extends LinearClipboard implements Closeable } public int getIndex(int x, int y, int z) { - return x + (ylast == y ? ylasti : (ylasti = (ylast = y) * getArea())) + (zlast == z - ? zlasti : (zlasti = (zlast = z) * getWidth())); + return x + y * getArea() + z * getWidth(); } @Override diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java index 341fa7d4e..766057964 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java @@ -193,13 +193,8 @@ public class MemoryOptimizedClipboard extends LinearClipboard { return nbtMap.values(); } - private int ylast; - private int ylasti; - private int zlast; - private int zlasti; - public int getIndex(int x, int y, int z) { - return x + ((ylast == y) ? ylasti : (ylasti = (ylast = y) * getArea())) + ((zlast == z) ? zlasti : (zlasti = (zlast = z) * getWidth())); + return x + y * getArea() + z * getWidth(); } @Override diff --git a/worldedit-core/src/main/java/com/boydti/fawe/util/Jars.java b/worldedit-core/src/main/java/com/boydti/fawe/util/ThirdPartyManager.java similarity index 79% rename from worldedit-core/src/main/java/com/boydti/fawe/util/Jars.java rename to worldedit-core/src/main/java/com/boydti/fawe/util/ThirdPartyManager.java index 260245178..19e8ecddc 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/util/Jars.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/util/ThirdPartyManager.java @@ -9,13 +9,14 @@ import java.util.Base64; import static org.slf4j.LoggerFactory.getLogger; -public enum Jars { +/** An internal FAWE class not meant for public use. **/ +public enum ThirdPartyManager { - MM_v1_7_8( + MapManager( "https://github.com/InventivetalentDev/MapManager/releases/download/1.7.8-SNAPSHOT/MapManager_v1.7.8-SNAPSHOT.jar", "m3YLUqZz66k2DmvdcYLeu38u3zKRKhrAXqGGpVKfO6g=", 544000), - PL_v3_7_6( + PacketListenerAPI( "https://github.com/InventivetalentDev/PacketListenerAPI/releases/download/3.7.6-SNAPSHOT/PacketListenerAPI_v3.7.6-SNAPSHOT.jar", "etdBRzLn5pRVDfr/mSQdPm6Jjer3wQOKhcn8fUxo5zM=", 143000), @@ -30,7 +31,7 @@ public enum Jars { * @param digest The Base64-encoded SHA-256 digest * @param fileSize Size of this jar in bytes */ - Jars(String url, String digest, int fileSize) { + ThirdPartyManager(String url, String digest, int fileSize) { this.url = url; this.digest = digest; this.fileSize = fileSize; @@ -45,7 +46,7 @@ public enum Jars { try (DataInputStream dis = new DataInputStream(url.openConnection().getInputStream())) { dis.readFully(jarBytes); if (dis.read() != -1) { // assert that we've read everything - throw new IllegalStateException("downloaded jar is longer than expected"); + throw new IllegalStateException("Downloaded jar is longer than expected"); } MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] jarDigestBytes = md.digest(jarBytes); @@ -53,12 +54,12 @@ public enum Jars { String jarDigest = Base64.getEncoder().encodeToString(jarDigestBytes); if (this.digest.equals(jarDigest)) { - getLogger(Jars.class).debug("++++ HASH CHECK ++++"); - getLogger(Jars.class).debug(this.url); - getLogger(Jars.class).debug(this.digest); + getLogger(ThirdPartyManager.class).debug("++++ HASH CHECK ++++"); + getLogger(ThirdPartyManager.class).debug(this.url); + getLogger(ThirdPartyManager.class).debug(this.digest); return jarBytes; } else { - getLogger(Jars.class).debug(jarDigest + " | " + url); + getLogger(ThirdPartyManager.class).debug(jarDigest + " | " + url); throw new IllegalStateException("The downloaded jar does not match the hash"); } } catch (NoSuchAlgorithmException e) { diff --git a/worldedit-core/src/main/java/com/boydti/fawe/wrappers/SilentPlayerWrapper.java b/worldedit-core/src/main/java/com/boydti/fawe/wrappers/SilentPlayerWrapper.java index d99f25891..bbed41799 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/wrappers/SilentPlayerWrapper.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/wrappers/SilentPlayerWrapper.java @@ -3,6 +3,8 @@ package com.boydti.fawe.wrappers; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.util.formatting.text.Component; + +//TODO: Add proper documenation for this class describing what it is. Is it just a wrapper for a player so no messages are printed anywhere? /** * Avoids printing any messages */ diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java index 15325cd47..846bc0794 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -1326,7 +1326,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { */ @Deprecated public > int makeCuboidFaces(Region region, B block) throws MaxChangedBlocksException { - return makeCuboidFaces(region, block); + return makeCuboidFaces(region, (Pattern) block); } /** @@ -2559,7 +2559,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { int zv = (int) (z.getValue() * unit.getZ() + zero2.getZ()); BlockState get; - if (yv >= 0 && yv < 265) { + if (yv >= 0 && yv < 256) { get = getBlock(xv, yv, zv); } else { get = BlockTypes.AIR.getDefaultState(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java index 5094f4827..e215d5339 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java @@ -1066,13 +1066,13 @@ public class LocalSession implements TextureHolder { private transient boolean loadDefaults = true; public Tool getTool(BaseItem item, Player player) { + loadDefaults(player, false); if (Settings.IMP.EXPERIMENTAL.PERSISTENT_BRUSHES && item.getNativeItem() != null) { BrushTool tool = BrushCache.getTool(player, this, item); if (tool != null) { return tool; } } - loadDefaults(player, false); return getTool(item.getType()); } @@ -1122,6 +1122,9 @@ public class LocalSession implements TextureHolder { } public BrushTool getBrushTool(BaseItem item, Player player, boolean create) throws InvalidToolBindException { + if (item.getType().hasBlockType()) { + throw new InvalidToolBindException(item.getType(), "Blocks can't be used"); + } Tool tool = getTool(item, player); if (!(tool instanceof BrushTool)) { if (create) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java b/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java index 25c14e5e4..289c82bb7 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java @@ -591,15 +591,17 @@ public final class WorldEdit { Map missingBlocks = editSession.popMissingBlocks(); if (!missingBlocks.isEmpty()) { - StringBuilder str = new StringBuilder(); + TextComponent.Builder str = TextComponent.builder(); str.append("Missing these blocks: "); int size = missingBlocks.size(); int i = 0; for (Map.Entry blockTypeIntegerEntry : missingBlocks.entrySet()) { - str.append((blockTypeIntegerEntry.getKey()).getName()); + str.append((blockTypeIntegerEntry.getKey()).getRichName()); - str.append(" [Amt: ").append(blockTypeIntegerEntry.getValue()).append("]"); + str.append(" [Amt: ") + .append(String.valueOf(blockTypeIntegerEntry.getValue())) + .append("]"); ++i; @@ -608,7 +610,7 @@ public final class WorldEdit { } } - actor.printError(str.toString()); + actor.printError(str.build()); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/BaseItemStack.java b/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/BaseItemStack.java index 291875342..2f22ebb93 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/BaseItemStack.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/BaseItemStack.java @@ -20,6 +20,9 @@ package com.sk89q.worldedit.blocks; import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.item.ItemType; /** @@ -65,7 +68,7 @@ public class BaseItemStack extends BaseItem { /** * Get the number of items in the stack. - * + * * @return the amount */ public int getAmount() { @@ -74,10 +77,15 @@ public class BaseItemStack extends BaseItem { /** * Set the amount of items in the stack. - * + * * @param amount the amount to set */ public void setAmount(int amount) { this.amount = amount; } + + public Component getRichName() { + return WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS) + .getRegistries().getItemRegistry().getRichName(this); + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java index c4fc98694..793fd19c2 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java @@ -32,11 +32,14 @@ import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.function.FlatRegionFunction; import com.sk89q.worldedit.function.FlatRegionMaskingFilter; +import com.sk89q.worldedit.function.RegionFunction; +import com.sk89q.worldedit.function.RegionMaskingFilter; import com.sk89q.worldedit.function.biome.BiomeReplace; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask2D; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.function.visitor.FlatRegionVisitor; +import com.sk89q.worldedit.function.visitor.RegionVisitor; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; @@ -49,7 +52,6 @@ import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; import com.sk89q.worldedit.world.World; -import com.sk89q.worldedit.world.biome.BiomeData; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.registry.BiomeRegistry; import org.enginehub.piston.annotation.Command; @@ -87,23 +89,19 @@ public class BiomeCommands { @ArgFlag(name = 'p', desc = "Page number.", def = "1") int page) { WorldEditAsyncCommandBuilder.createAndSendMessage(actor, () -> { - BiomeRegistry biomeRegistry = - WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS) - .getRegistries().getBiomeRegistry(); + BiomeRegistry biomeRegistry = WorldEdit.getInstance().getPlatformManager() + .queryCapability(Capability.GAME_HOOKS).getRegistries().getBiomeRegistry(); - PaginationBox paginationBox = PaginationBox - .fromStrings("Available Biomes", "/biomelist -p %page%", - BiomeType.REGISTRY.values().stream().map(biomeType -> { - String id = biomeType.getId(); - final BiomeData data = biomeRegistry.getData(biomeType); - if (data != null) { - String name = data.getName(); - return id + " (" + name + ")"; - } else { - return id; - } - }).collect(Collectors.toList())); - return paginationBox.create(page); + PaginationBox paginationBox = PaginationBox.fromComponents("Available Biomes", "/biomelist -p %page%", + BiomeType.REGISTRY.values().stream() + .map(biomeType -> TextComponent.builder() + .append(biomeType.getId()) + .append(" (") + .append(biomeRegistry.getRichName(biomeType)) + .append(")") + .build()) + .collect(Collectors.toList())); + return paginationBox.create(page); }, (Component) null); } @@ -150,14 +148,11 @@ public class BiomeCommands { messageKey = "worldedit.biomeinfo.selection"; } - List components = biomes.stream().map(biome -> { - BiomeData data = biomeRegistry.getData(biome); - if (data != null) { - return TextComponent.of(data.getName()).hoverEvent(HoverEvent.showText(TextComponent.of(biome.getId()))); - } else { - return TextComponent.of(biome.getId()); - } - }).collect(Collectors.toList()); + List components = biomes.stream().map(biome -> + biomeRegistry.getRichName(biome).hoverEvent( + HoverEvent.showText(TextComponent.of(biome.getId())) + ) + ).collect(Collectors.toList()); player.printInfo(TranslatableComponent.of(messageKey, TextUtils.join(components, TextComponent.of(", ")))); } @@ -175,7 +170,6 @@ public class BiomeCommands { World world = player.getWorld(); Region region; Mask mask = editSession.getMask(); - Mask2D mask2d = mask != null ? mask.toMask2D() : null; if (atPosition) { final BlockVector3 pos = player.getLocation().toVector().toBlockPoint(); @@ -184,11 +178,11 @@ public class BiomeCommands { region = session.getSelection(world); } - FlatRegionFunction replace = new BiomeReplace(editSession, target); - if (mask2d != null) { - replace = new FlatRegionMaskingFilter(mask2d, replace); + RegionFunction replace = new BiomeReplace(editSession, target); + if (mask != null) { + replace = new RegionMaskingFilter(editSession, mask, replace); } - FlatRegionVisitor visitor = new FlatRegionVisitor(Regions.asFlatRegion(region), replace); + RegionVisitor visitor = new RegionVisitor(region, replace); Operations.completeLegacy(visitor); player.printInfo(TranslatableComponent.of( diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java index 9d97eb8a8..916a93ae5 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -153,15 +153,6 @@ public class BrushCommands { this.worldEdit = worldEdit; } - @Command( - name = "none", - aliases = "unbind", - desc = "Unbind a bound brush from your current item" - ) - void none(Player player, LocalSession session) throws WorldEditException { - ToolCommands.setToolNone(player, session, true); - } - @Command(name = "blendball", aliases = { "bb", @@ -1012,7 +1003,9 @@ public class BrushCommands { @Switch(name = 'f', desc = "Also kill all friendly mobs (Applies the flags `-abgnpt`)") boolean killFriendly, @Switch(name = 'r', desc = "Also destroy armor stands") - boolean killArmorStands, InjectedValueAccess context) throws WorldEditException { + boolean killArmorStands, + @Switch(name = 'w', desc = "Also kill water mobs") + boolean killWater, InjectedValueAccess context) throws WorldEditException { worldEdit.checkMaxBrushRadius(radius); CreatureButcher flags = new CreatureButcher(player); @@ -1024,6 +1017,7 @@ public class BrushCommands { flags.or(CreatureButcher.Flags.AMBIENT, killAmbient, "worldedit.butcher.ambient"); flags.or(CreatureButcher.Flags.TAGGED, killWithName, "worldedit.butcher.tagged"); flags.or(CreatureButcher.Flags.ARMOR_STAND, killArmorStands, "worldedit.butcher.armorstands"); + flags.or(CreatureButcher.Flags.WATER, killWater, "worldedit.butcher.water"); set(context, new ButcherBrush(flags)).setSize(radius); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ChunkCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ChunkCommands.java index 38ef18f21..2b075e56c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ChunkCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ChunkCommands.java @@ -174,17 +174,22 @@ public class ChunkCommands { .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, "/stop")))); } - private static class ChunkListPaginationBox extends PaginationBox.ListPaginationBox { + private static class ChunkListPaginationBox extends PaginationBox { //private final Region region; private final List chunks = null; ChunkListPaginationBox(Region region) { - super("Selected Chunks", "/listchunks -p %page%", region.getChunks()); + super("Selected Chunks", "/listchunks -p %page%"); } @Override public Component getComponent(int number) { - return create(number); + return TextComponent.of(chunks.get(number).toString()); + } + + @Override + public int getComponentsSize() { + return chunks.size(); } } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java index cbc0fcff1..3d48aef5f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java @@ -28,6 +28,7 @@ import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.RandomTextureUtil; import com.boydti.fawe.util.StringMan; import com.boydti.fawe.util.TextureUtil; +import com.google.common.collect.ImmutableList; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalSession; @@ -36,6 +37,7 @@ import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.command.util.CommandPermissions; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.HookMode; +import com.sk89q.worldedit.command.util.WorldEditAsyncCommandBuilder; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.input.DisallowedUsageException; import com.sk89q.worldedit.extension.input.InputParseException; @@ -44,6 +46,8 @@ import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.internal.command.CommandRegistrationHandler; +import com.sk89q.worldedit.internal.command.CommandUtil; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.util.formatting.component.PaginationBox; @@ -54,6 +58,9 @@ import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.util.formatting.text.format.TextColor; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.item.ItemType; +import org.enginehub.piston.CommandManager; +import org.enginehub.piston.CommandManagerService; +import org.enginehub.piston.CommandParameters; import org.enginehub.piston.annotation.Command; import org.enginehub.piston.annotation.CommandContainer; import org.enginehub.piston.annotation.param.Arg; @@ -65,8 +72,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.TreeMap; import java.util.concurrent.Callable; +import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; @@ -76,6 +85,65 @@ import static com.google.common.base.Preconditions.checkNotNull; @CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class) public class GeneralCommands { + public static void register(CommandRegistrationHandler registration, + CommandManager commandManager, + CommandManagerService commandManagerService, + WorldEdit worldEdit) { + // Collect the tool commands + CommandManager collect = commandManagerService.newCommandManager(); + + registration.register( + collect, + GeneralCommandsRegistration.builder(), + new GeneralCommands(worldEdit) + ); + + + Set commands = collect.getAllCommands() + .collect(Collectors.toSet()); + for (org.enginehub.piston.Command command : commands) { + /*if in FAWE, //fast will remain for now + (command.getName().equals("/fast")) { + + // deprecate to `//perf` + commandManager.register(CommandUtil.deprecate( + command, "//fast duplicates //perf " + + "and will be removed in WorldEdit 8", + GeneralCommands::replaceFastForPerf + )); + continue; + } + */ + + commandManager.register(command); + } + } + + private static Component replaceFastForPerf(org.enginehub.piston.Command oldCmd, + CommandParameters oldParams) { + if (oldParams.getMetadata() == null) { + return CommandUtil.createNewCommandReplacementText("//perf"); + } + ImmutableList args = oldParams.getMetadata().getArguments(); + if (args.isEmpty()) { + return TextComponent.of("There is not yet a replacement for //fast" + + " with no arguments"); + } + String arg0 = args.get(0).toLowerCase(Locale.ENGLISH); + String flipped; + switch (arg0) { + case "on": + flipped = "off"; + break; + case "off": + flipped = "on"; + break; + default: + return TextComponent.of("There is no replacement for //fast " + arg0); + } + return CommandUtil.createNewCommandReplacementText("//perf " + flipped); + } + private final WorldEdit worldEdit; /** @@ -145,22 +213,48 @@ public class GeneralCommands { @Command( name = "/fast", - desc = "Toggle fast mode side effects" + desc = "Toggle fast mode" ) @CommandPermissions("worldedit.fast") - public void fast(Actor actor, LocalSession session, - @Arg(desc = "The side effect", def = "") - SideEffect sideEffect, - @Arg(desc = "The new side effect state", def = "") - SideEffect.State newState, - @Switch(name = 'h', desc = "Show the info box") - boolean showInfoBox) throws WorldEditException { + @Deprecated + void fast(Actor actor, LocalSession session, + @Arg(desc = "The new fast mode state", def = "") + Boolean fastMode) { + boolean hasFastMode = session.hasFastMode(); + if (fastMode != null && fastMode == hasFastMode) { + actor.printError(TranslatableComponent.of(fastMode ? "worldedit.fast.enabled.already" : "worldedit.fast.disabled.already")); + return; + } + + if (hasFastMode) { + session.setFastMode(false); + actor.printInfo(TranslatableComponent.of("worldedit.fast.disabled")); + } else { + session.setFastMode(true); + actor.printInfo(TranslatableComponent.of("worldedit.fast.enabled")); + } + } + + @Command( + name = "/perf", + desc = "Toggle side effects for performance", + descFooter = "Note that this command is GOING to change in the future." + + " Do not depend on the exact format of this command yet." + ) + @CommandPermissions("worldedit.perf") + void perf(Actor actor, LocalSession session, + @Arg(desc = "The side effect", def = "") + SideEffect sideEffect, + @Arg(desc = "The new side effect state", def = "") + SideEffect.State newState, + @Switch(name = 'h', desc = "Show the info box") + boolean showInfoBox) throws WorldEditException { if (sideEffect != null) { SideEffect.State currentState = session.getSideEffectSet().getState(sideEffect); if (newState != null && newState == currentState) { if (!showInfoBox) { actor.printError(TranslatableComponent.of( - "worldedit.fast.sideeffect.already-set", + "worldedit.perf.sideeffect.already-set", TranslatableComponent.of(sideEffect.getDisplayName()), TranslatableComponent.of(newState.getDisplayName()) )); @@ -172,14 +266,14 @@ public class GeneralCommands { session.setSideEffectSet(session.getSideEffectSet().with(sideEffect, newState)); if (!showInfoBox) { actor.printInfo(TranslatableComponent.of( - "worldedit.fast.sideeffect.set", + "worldedit.perf.sideeffect.set", TranslatableComponent.of(sideEffect.getDisplayName()), TranslatableComponent.of(newState.getDisplayName()) )); } } else { actor.printInfo(TranslatableComponent.of( - "worldedit.fast.sideeffect.get", + "worldedit.perf.sideeffect.get", TranslatableComponent.of(sideEffect.getDisplayName()), TranslatableComponent.of(currentState.getDisplayName()) )); @@ -192,7 +286,7 @@ public class GeneralCommands { session.setSideEffectSet(applier); if (!showInfoBox) { actor.printInfo(TranslatableComponent.of( - "worldedit.fast.sideeffect.set-all", + "worldedit.perf.sideeffect.set-all", TranslatableComponent.of(newState.getDisplayName()) )); } @@ -309,6 +403,7 @@ public class GeneralCommands { aliases = {"/toggleplace"}, desc = "Switch between your position and pos1 for placement" ) + @CommandPermissions("worldedit.toggleplace") public void togglePlace(Player player, LocalSession session) { if (session.togglePlacementPosition()) { player.printInfo(TranslatableComponent.of("worldedit.toggleplace.pos1")); @@ -331,7 +426,7 @@ public class GeneralCommands { @ArgFlag(name = 'p', desc = "Page of results to return", def = "1") int page, @Arg(desc = "Search query", variable = true) - List query) throws Exception { + List query) { String search = String.join(" ", query); if (search.length() <= 2) { actor.printError(TranslatableComponent.of("worldedit.searchitem.too-short")); @@ -342,7 +437,8 @@ public class GeneralCommands { return; } - actor.print(new ItemSearcher(search, blocksOnly, itemsOnly, page).call()); + WorldEditAsyncCommandBuilder.createAndSendMessage(actor, new ItemSearcher(search, blocksOnly, itemsOnly, page), + TranslatableComponent.of("worldedit.searchitem.searching")); } private static class ItemSearcher implements Callable { @@ -361,7 +457,7 @@ public class GeneralCommands { @Override public Component call() throws Exception { String command = "/searchitem " + (blocksOnly ? "-b " : "") + (itemsOnly ? "-i " : "") + "-p %page% " + search; - Map results = new TreeMap<>(); + Map results = new TreeMap<>(); String idMatch = search.replace(' ', '_'); String nameMatch = search.toLowerCase(Locale.ROOT); for (ItemType searchType : ItemType.REGISTRY) { @@ -373,15 +469,17 @@ public class GeneralCommands { continue; } final String id = searchType.getId(); - String name = searchType.getName(); - final boolean hasName = !name.equals(id); - name = name.toLowerCase(Locale.ROOT); - if (id.contains(idMatch) || (hasName && name.contains(nameMatch))) { - results.put(id, name + (hasName ? " (" + id + ")" : "")); + if (id.contains(idMatch)) { + Component name = searchType.getRichName(); + results.put(id, TextComponent.builder() + .append(name) + .append(" (" + id + ")") + .build()); } } - List list = new ArrayList<>(results.values()); - return PaginationBox.fromStrings("Search results for '" + search + "'", command, list).create(page); + List list = new ArrayList<>(results.values()); + return PaginationBox.fromComponents("Search results for '" + search + "'", command, list) + .create(page); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/HistoryCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/HistoryCommands.java index d5c5e7615..bac775360 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/HistoryCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/HistoryCommands.java @@ -28,6 +28,7 @@ import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.annotation.Confirm; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import org.enginehub.piston.annotation.Command; @@ -60,7 +61,7 @@ public class HistoryCommands { desc = "Undoes the last action (from history)" ) @CommandPermissions({"worldedit.history.undo", "worldedit.history.undo.self"}) - public void undo(Player player, LocalSession session, + public void undo(Actor actor, LocalSession session, @Confirm(Confirm.Processor.LIMIT) @Arg(desc = "Number of undoes to perform", def = "1") int times, @Arg(name = "player", desc = "Undo this player's operations", def = "") @@ -68,31 +69,32 @@ public class HistoryCommands { times = Math.max(1, times); LocalSession undoSession = session; if (session.hasFastMode()) { - player.print(TranslatableComponent.of("fawe.worldedit.history.command.undo.disabled")); + actor.print(TranslatableComponent.of("fawe.worldedit.history.command.undo.disabled")); return; } if (playerName != null) { - player.checkPermission("worldedit.history.undo.other"); + actor.checkPermission("worldedit.history.undo.other"); undoSession = worldEdit.getSessionManager().findByName(playerName); if (undoSession == null) { - player.printError(TranslatableComponent.of("worldedit.session.cant-find-session", TextComponent.of(playerName))); + actor.printError(TranslatableComponent.of("worldedit.session.cant-find-session", TextComponent.of(playerName))); return; } } int timesUndone = 0; for (int i = 0; i < times; ++i) { - EditSession undone = undoSession.undo(undoSession.getBlockBag(player), player); + BlockBag blockBag = actor instanceof Player ? undoSession.getBlockBag((Player) actor) : null; + EditSession undone = undoSession.undo(blockBag, actor); if (undone != null) { timesUndone++; - worldEdit.flushBlockBag(player, undone); + worldEdit.flushBlockBag(actor, undone); } else { break; } } if (timesUndone > 0) { - player.printInfo(TranslatableComponent.of("worldedit.undo.undone", TextComponent.of(timesUndone))); + actor.printInfo(TranslatableComponent.of("worldedit.undo.undone", TextComponent.of(timesUndone))); } else { - player.printError(TranslatableComponent.of("worldedit.undo.none")); + actor.printError(TranslatableComponent.of("worldedit.undo.none")); } } @@ -102,7 +104,7 @@ public class HistoryCommands { desc = "Redoes the last action (from history)" ) @CommandPermissions({"worldedit.history.redo", "worldedit.history.redo.self"}) - public void redo(Player player, LocalSession session, + public void redo(Actor actor, LocalSession session, @Confirm(Confirm.Processor.LIMIT) @Arg(desc = "Number of redoes to perform", def = "1") int times, @Arg(name = "player", desc = "Redo this player's operations", def = "") @@ -110,27 +112,28 @@ public class HistoryCommands { times = Math.max(1, times); LocalSession redoSession = session; if (playerName != null) { - player.checkPermission("worldedit.history.redo.other"); + actor.checkPermission("worldedit.history.redo.other"); redoSession = worldEdit.getSessionManager().findByName(playerName); if (redoSession == null) { - player.printError(TranslatableComponent.of("worldedit.session.cant-find-session", TextComponent.of(playerName))); + actor.printError(TranslatableComponent.of("worldedit.session.cant-find-session", TextComponent.of(playerName))); return; } } int timesRedone = 0; for (int i = 0; i < times; ++i) { - EditSession redone = redoSession.redo(redoSession.getBlockBag(player), player); + BlockBag blockBag = actor instanceof Player ? redoSession.getBlockBag((Player) actor) : null; + EditSession redone = redoSession.redo(blockBag, actor); if (redone != null) { timesRedone++; - worldEdit.flushBlockBag(player, redone); + worldEdit.flushBlockBag(actor, redone); } else { break; } } if (timesRedone > 0) { - player.printInfo(TranslatableComponent.of("worldedit.redo.redone", TextComponent.of(timesRedone))); + actor.printInfo(TranslatableComponent.of("worldedit.redo.redone", TextComponent.of(timesRedone))); } else { - player.printError(TranslatableComponent.of("worldedit.redo.none")); + actor.printError(TranslatableComponent.of("worldedit.redo.none")); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java index a77369af1..037cae63c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java @@ -80,6 +80,7 @@ import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -94,7 +95,6 @@ import static com.boydti.fawe.util.ReflectionUtils.as; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -//import com.boydti.fawe.object.schematic.visualizer.SchemVis; /** * Commands that work with schematic files. @@ -115,6 +115,31 @@ public class SchematicCommands { this.worldEdit = worldEdit; } + //TODO filtering for directories, global, and private scheamtics needs to be reimplemented here + private static List getFiles(File root, String filter, ClipboardFormat format) { + File[] files = root.listFiles(); + if (files == null) { + return null; + } + //Only get the files that match the format parameter + if (format != null) { + files = Arrays.stream(files).filter(format::isFormat).toArray(File[]::new); + } + List fileList = new ArrayList<>(); + for (File f : files) { + if (f.isDirectory()) { + List subFiles = getFiles(f, filter, format); + if (subFiles == null) { + continue; // empty subdir + } + fileList.addAll(subFiles); + } else { + fileList.add(f); + } + } + return fileList; + } + @Command( name = "loadall", desc = "Load multiple clipboards (paste will randomly choose one)" @@ -122,12 +147,12 @@ public class SchematicCommands { @Deprecated @CommandPermissions({"worldedit.clipboard.load", "worldedit.schematic.load", "worldedit.schematic.load.web", "worldedit.schematic.load.asset"}) public void loadall(Player player, LocalSession session, - @Arg(desc = "Format name.", def = "schematic") - String formatName, - @Arg(desc = "File name.") - String filename, - @Switch(name = 'r', desc = "Apply random rotation") - boolean randomRotate) throws FilenameException { + @Arg(desc = "Format name.", def = "schematic") + String formatName, + @Arg(desc = "File name.") + String filename, + @Switch(name = 'r', desc = "Apply random rotation") + boolean randomRotate) throws FilenameException { final ClipboardFormat format = ClipboardFormats.findByAlias(formatName); if (format == null) { player.print(Caption.of("fawe.worldedit.clipboard.clipboard.invalid.format", formatName)); @@ -355,19 +380,19 @@ public class SchematicCommands { if (parent != null && !parent.exists()) { if (!parent.mkdirs()) { throw new StopExecutionException(TranslatableComponent.of( - "worldedit.schematic.save.failed-directory")); + "worldedit.schematic.save.failed-directory")); } } ClipboardHolder holder = session.getClipboard(); - SchematicSaveTask task = new SchematicSaveTask(actor, f, format, holder, overwrite); + SchematicSaveTask task = new SchematicSaveTask(actor, f, dir, format, holder, overwrite); AsyncCommandBuilder.wrap(task, actor) - .registerWithSupervisor(worldEdit.getSupervisor(), "Saving schematic " + filename) - .sendMessageAfterDelay(TranslatableComponent.of("worldedit.schematic.save.saving")) - .onSuccess(filename + " saved" + (overwrite ? " (overwriting previous file)." : "."), null) - .onFailure("Failed to save schematic", worldEdit.getPlatformManager().getPlatformCommandManager().getExceptionConverter()) - .buildAndExec(worldEdit.getExecutorService()); + .registerWithSupervisor(worldEdit.getSupervisor(), "Saving schematic " + filename) + .sendMessageAfterDelay(TranslatableComponent.of("worldedit.schematic.save.saving")) + .onSuccess(filename + " saved" + (overwrite ? " (overwriting previous file)." : "."), null) + .onFailure("Failed to save schematic", worldEdit.getPlatformManager().getPlatformCommandManager().getExceptionConverter()) + .buildAndExec(worldEdit.getExecutorService()); } @Command( @@ -531,22 +556,22 @@ public class SchematicCommands { if (loaded) { msg.append(TextComponent.of("[-]", TextColor.RED) - .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, unload + " " + path)) - .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Unload")))); + .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, unload + " " + path)) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Unload")))); } else { msg.append(TextComponent.of("[+]", TextColor.GREEN) - .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, loadMulti + " " + path)) - .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Add to clipboard")))); + .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, loadMulti + " " + path)) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Add to clipboard")))); } if (type != UtilityCommands.URIType.DIRECTORY) { msg.append(TextComponent.of("[X]", TextColor.DARK_RED) - .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, delete + " " + path)) - .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("delete"))) + .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, delete + " " + path)) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("delete"))) ); } else if (hasShow) { msg.append(TextComponent.of("[O]", TextColor.DARK_AQUA) - .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, showCmd + " " + path)) - .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("visualize"))) + .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, showCmd + " " + path)) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("visualize"))) ); } TextComponent msgElem = TextComponent.of(name, color); @@ -559,173 +584,47 @@ public class SchematicCommands { } msg.append(msgElem); + if (type == UtilityCommands.URIType.FILE) { + long filesize = 0; + try { + filesize = Files.size(Paths.get(dir.getAbsolutePath() + File.separator + + (playerFolder ? (uuid.toString() + File.separator) : "") + path)); + } catch (IOException e) { + e.printStackTrace(); + } + TextComponent sizeElem = TextComponent.of(String.format(" (%.1f kb)", filesize / 1000.0), TextColor.GRAY); + msg.append(sizeElem); + } return msg.create(); }); - PaginationBox paginationBox = PaginationBox.fromStrings("Available schematics", pageCommand, components); + + long totalBytes = 0; + File parentDir = new File(dir.getAbsolutePath() + (playerFolder ? File.separator + uuid.toString() : "")); + try { + List toAddUp = getFiles(parentDir, null, null); + if (toAddUp != null && toAddUp.size() != 0) { + for (File schem : toAddUp) { + if (schem.getName().endsWith(".schem") || schem.getName().endsWith(".schematic")) { + totalBytes += Files.size(Paths.get(schem.getAbsolutePath())); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + + String headerBytesElem = String.format("%.1fkb", totalBytes / 1000.0); + + if (Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS && Settings.IMP.EXPERIMENTAL.PER_PLAYER_FILE_SIZE_LIMIT > -1) { + headerBytesElem += String.format(" / %dkb", + Settings.IMP.EXPERIMENTAL.PER_PLAYER_FILE_SIZE_LIMIT ); + } + + String fullHeader = "| Schematics: " + headerBytesElem + " |"; + PaginationBox paginationBox = PaginationBox.fromComponents(fullHeader, pageCommand, components); actor.print(paginationBox.create(page)); } - private static class SchematicLoadTask implements Callable { - private final Actor actor; - private final File file; - private final ClipboardFormat format; - - SchematicLoadTask(Actor actor, File file, ClipboardFormat format) { - this.actor = actor; - this.file = file; - this.format = format; - } - - @Override - public ClipboardHolder call() throws Exception { - try (Closer closer = Closer.create()) { - FileInputStream fis = closer.register(new FileInputStream(file)); - BufferedInputStream bis = closer.register(new BufferedInputStream(fis)); - ClipboardReader reader = closer.register(format.getReader(bis)); - - Clipboard clipboard = reader.read(); - log.info(actor.getName() + " loaded " + file.getCanonicalPath()); - return new ClipboardHolder(clipboard); - } - } - } - - private static class SchematicSaveTask implements Callable { - private final Actor actor; - private final File file; - private final ClipboardFormat format; - private final ClipboardHolder holder; - private final boolean overwrite; - - SchematicSaveTask(Actor actor, File file, ClipboardFormat format, ClipboardHolder holder, boolean overwrite) { - this.actor = actor; - this.file = file; - this.format = format; - this.holder = holder; - this.overwrite = overwrite; - } - - @Override - public Void call() throws Exception { - Clipboard clipboard = holder.getClipboard(); - Transform transform = holder.getTransform(); - Clipboard target; - - // If we have a transform, bake it into the copy - if (transform.isIdentity()) { - target = clipboard; - } else { - FlattenedClipboardTransform result = FlattenedClipboardTransform.transform(clipboard, transform); - target = new BlockArrayClipboard(result.getTransformedRegion()); - target.setOrigin(clipboard.getOrigin()); - Operations.completeLegacy(result.copyTo(target)); - } - - try (Closer closer = Closer.create()) { - FileOutputStream fos = closer.register(new FileOutputStream(file)); - BufferedOutputStream bos = closer.register(new BufferedOutputStream(fos)); - ClipboardWriter writer = closer.register(format.getWriter(bos)); - URI uri = null; - if (holder instanceof URIClipboardHolder) { - uri = ((URIClipboardHolder) holder).getURI(clipboard); - } - if (new ActorSaveClipboardEvent(actor, clipboard, uri, file.toURI()).call()) { - if (writer instanceof MinecraftStructure) { - ((MinecraftStructure) writer).write(target, actor.getName()); - } else { - writer.write(target); - } - log.info(actor.getName() + " saved " + file.getCanonicalPath()); - actor.print(Caption.of("fawe.worldedit.schematic.schematic.saved", file.getName())); - } else { - actor.printError(TranslatableComponent.of("fawe.cancel.worldedit.cancel.reason.manual")); - } - } - return null; - } - } - - private static class SchematicListTask implements Callable { - private final String prefix; - private final int sortType; - private final int page; - private final File rootDir; - private final String pageCommand; - private final String filter; - private String formatName; - - SchematicListTask(String prefix, int sortType, int page, String pageCommand, - String filter, String formatName) { - this.prefix = prefix; - this.sortType = sortType; - this.page = page; - this.rootDir = WorldEdit.getInstance().getWorkingDirectoryFile(prefix); - this.pageCommand = pageCommand; - this.filter = filter; - this.formatName = formatName; - } - - @Override - public Component call() throws Exception { - ClipboardFormat format = ClipboardFormats.findByAlias(formatName); - List fileList = getFiles(rootDir,filter,format); - - if (fileList == null || fileList.isEmpty()) { - return ErrorFormat.wrap("No schematics found."); - } - - File[] files = new File[fileList.size()]; - fileList.toArray(files); - // cleanup file list - Arrays.sort(files, (f1, f2) -> { - // http://stackoverflow.com/questions/203030/best-way-to-list-files-in-java-sorted-by-date-modified - int res; - if (sortType == 0) { // use name by default - int p = f1.getParent().compareTo(f2.getParent()); - if (p == 0) { // same parent, compare names - res = f1.getName().compareTo(f2.getName()); - } else { // different parent, sort by that - res = p; - } - } else { - res = Long.compare(f1.lastModified(), f2.lastModified()); // use date if there is a flag - if (sortType == 1) { - res = -res; // flip date for newest first instead of oldest first - } - } - return res; - }); - - PaginationBox paginationBox = new SchematicPaginationBox(prefix, files, pageCommand); - return paginationBox.create(page); - } - } - - //TODO filtering for directories, global, and private scheamtics needs to be reimplemented here - private static List getFiles(File root, String filter, ClipboardFormat format) { - File[] files = root.listFiles(); - if (files == null) { - return null; - } - //Only get the files that match the format parameter - if (format != null) { - files = Arrays.stream(files).filter(format::isFormat).toArray(File[]::new); - } - List fileList = new ArrayList<>(); - for (File f : files) { - if (f.isDirectory()) { - List subFiles = getFiles(f, filter, format); - if (subFiles == null) { - continue; // empty subdir - } - fileList.addAll(subFiles); - } else { - fileList.add(f); - } - } - return fileList; - } - @Command( name = "delete", aliases = {"d"}, @@ -776,6 +675,249 @@ public class SchematicCommands { return false; } + private static class SchematicLoadTask implements Callable { + private final Actor actor; + private final ClipboardFormat format; + private final File file; + + SchematicLoadTask(Actor actor, File file, ClipboardFormat format) { + this.actor = actor; + this.file = file; + this.format = format; + } + + @Override + public ClipboardHolder call() throws Exception { + try (Closer closer = Closer.create()) { + FileInputStream fis = closer.register(new FileInputStream(file)); + BufferedInputStream bis = closer.register(new BufferedInputStream(fis)); + ClipboardReader reader = closer.register(format.getReader(bis)); + + Clipboard clipboard = reader.read(); + log.info(actor.getName() + " loaded " + file.getCanonicalPath()); + return new ClipboardHolder(clipboard); + } + } + } + + private static class SchematicSaveTask implements Callable { + private final Actor actor; + private final ClipboardFormat format; + private final ClipboardHolder holder; + private final boolean overwrite; + private final File rootDir; + private File file; + + SchematicSaveTask(Actor actor, File file, File rootDir, ClipboardFormat format, ClipboardHolder holder, boolean overwrite) { + this.actor = actor; + this.file = file; + this.rootDir = rootDir; + this.format = format; + this.holder = holder; + this.overwrite = overwrite; + } + + @Override + public Void call() throws Exception { + Clipboard clipboard = holder.getClipboard(); + Transform transform = holder.getTransform(); + Clipboard target; + + boolean checkFilesize = false; + + if (Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS + && Settings.IMP.EXPERIMENTAL.PER_PLAYER_FILE_SIZE_LIMIT > -1) { + checkFilesize = true; + } + + double directorysizeKb = 0; + String curFilepath = file.getAbsolutePath(); + final String SCHEMATIC_NAME = file.getName(); + + double oldKbOverwritten = 0; + String overwrittenPath = curFilepath; + + int numFiles = -1; + if (checkFilesize) { + List toAddUp = getFiles(rootDir, null, null); + if (toAddUp != null && toAddUp.size() != 0) { + for (File child : toAddUp) { + if (child.getName().endsWith(".schem") || child.getName().endsWith(".schematic")) { + directorysizeKb += Files.size(Paths.get(child.getAbsolutePath())) / 1000.0; + numFiles++; + } + } + } + if (overwrite) { + oldKbOverwritten = Files.size(Paths.get(file.getAbsolutePath())) / 1000.0; + int iter = 1; + while (new File(overwrittenPath + "." + iter + "." + format.getPrimaryFileExtension()).exists()) { + iter++; + } + file = new File(overwrittenPath + "." + iter + "." + format.getPrimaryFileExtension()); + } + } + + + if (Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS && Settings.IMP.EXPERIMENTAL.PER_PLAYER_FILE_NUM_LIMIT > -1) { + + if (numFiles == -1) { + numFiles = 0; + List toAddUp = getFiles(rootDir, null, null); + if (toAddUp != null && toAddUp.size() != 0) { + for (File child : toAddUp) { + if (child.getName().endsWith(".schem") || child.getName().endsWith(".schematic")) { + numFiles++; + } + } + } + } + int limit = Settings.IMP.EXPERIMENTAL.PER_PLAYER_FILE_NUM_LIMIT; + + if (numFiles >= limit) { + TextComponent noSlotsErr = TextComponent.of( //TODO - to be moved into captions/translatablecomponents + String.format("You have " + numFiles + "/" + limit + " saved schematics. Delete some to save this one!", + TextColor.RED)); + log.info(actor.getName() + " failed to save " + file.getCanonicalPath() + " - too many schematics!"); + throw new WorldEditException(noSlotsErr) { + }; + } + } + + // If we have a transform, bake it into the copy + if (transform.isIdentity()) { + target = clipboard; + } else { + FlattenedClipboardTransform result = FlattenedClipboardTransform.transform(clipboard, transform); + target = new BlockArrayClipboard(result.getTransformedRegion()); + target.setOrigin(clipboard.getOrigin()); + Operations.completeLegacy(result.copyTo(target)); + } + + try (Closer closer = Closer.create()) { + FileOutputStream fos = closer.register(new FileOutputStream(file)); + BufferedOutputStream bos = closer.register(new BufferedOutputStream(fos)); + ClipboardWriter writer = closer.register(format.getWriter(bos)); + URI uri = null; + if (holder instanceof URIClipboardHolder) { + uri = ((URIClipboardHolder) holder).getURI(clipboard); + } + if (new ActorSaveClipboardEvent(actor, clipboard, uri, file.toURI()).call()) { + if (writer instanceof MinecraftStructure) { + ((MinecraftStructure) writer).write(target, actor.getName()); + } else { + writer.write(target); + } + + closer.close(); // release the new .schem file so that its size can be measured + double filesizeKb = Files.size(Paths.get(file.getAbsolutePath())) / 1000.0; + + TextComponent filesizeNotif = TextComponent.of( //TODO - to be moved into captions/translatablecomponents + SCHEMATIC_NAME + " size: " + String.format("%.1f", filesizeKb) + "kb", TextColor.GRAY); + actor.print(filesizeNotif); + + if (checkFilesize) { + + double curKb = filesizeKb + directorysizeKb; + int allocatedKb = Settings.IMP.EXPERIMENTAL.PER_PLAYER_FILE_SIZE_LIMIT; + + if (overwrite) { + curKb -= oldKbOverwritten; + } + + if ((curKb) > allocatedKb) { + file.delete(); + TextComponent notEnoughKbErr = TextComponent.of( //TODO - to be moved into captions/translatablecomponents + "You're about to be at " + String.format("%.1f", curKb) + "kb of schematics. (" + + String.format("%dkb", allocatedKb) + " available) Delete some first to save this one!", + TextColor.RED); + log.info(actor.getName() + " failed to save " + SCHEMATIC_NAME + " - not enough space!"); + throw new WorldEditException(notEnoughKbErr) { + }; + } + if (overwrite) { + new File(curFilepath).delete(); + file.renameTo(new File(curFilepath)); + } else { + numFiles++; + } + TextComponent kbRemainingNotif = TextComponent.of( //TODO - to be moved into captions/translatablecomponents + "You have " + String.format("%.1f", (allocatedKb - curKb)) + "kb left for schematics.", TextColor.GRAY); + actor.print(kbRemainingNotif); + } + + if (Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS && Settings.IMP.EXPERIMENTAL.PER_PLAYER_FILE_NUM_LIMIT > -1) { + + TextComponent slotsRemainingNotif = TextComponent.of( //TODO - to be moved into captions/translatablecomponents + "You have " + (Settings.IMP.EXPERIMENTAL.PER_PLAYER_FILE_NUM_LIMIT - numFiles) + + " schematic file slots left.", TextColor.GRAY); + actor.print(slotsRemainingNotif); + } + log.info(actor.getName() + " saved " + file.getCanonicalPath()); + } else { + actor.printError(TranslatableComponent.of("fawe.cancel.worldedit.cancel.reason.manual")); + } + } + return null; + } + } + + private static class SchematicListTask implements Callable { + private final String prefix; + private final int sortType; + private final int page; + private final File rootDir; + private final String pageCommand; + private final String filter; + private final String formatName; + + SchematicListTask(String prefix, int sortType, int page, String pageCommand, + String filter, String formatName) { + this.prefix = prefix; + this.sortType = sortType; + this.page = page; + this.rootDir = WorldEdit.getInstance().getWorkingDirectoryFile(prefix); + this.pageCommand = pageCommand; + this.filter = filter; + this.formatName = formatName; + } + + @Override + public Component call() throws Exception { + ClipboardFormat format = ClipboardFormats.findByAlias(formatName); + List fileList = getFiles(rootDir, filter, format); + + if (fileList == null || fileList.isEmpty()) { + return ErrorFormat.wrap("No schematics found."); + } + + File[] files = new File[fileList.size()]; + fileList.toArray(files); + // cleanup file list + Arrays.sort(files, (f1, f2) -> { + // http://stackoverflow.com/questions/203030/best-way-to-list-files-in-java-sorted-by-date-modified + int res; + if (sortType == 0) { // use name by default + int p = f1.getParent().compareTo(f2.getParent()); + if (p == 0) { // same parent, compare names + res = f1.getName().compareTo(f2.getName()); + } else { // different parent, sort by that + res = p; + } + } else { + res = Long.compare(f1.lastModified(), f2.lastModified()); // use date if there is a flag + if (sortType == 1) { + res = -res; // flip date for newest first instead of oldest first + } + } + return res; + }); + + PaginationBox paginationBox = new SchematicPaginationBox(prefix, files, pageCommand); + return paginationBox.create(page); + } + } + private static class SchematicPaginationBox extends PaginationBox { private final String prefix; private final File[] files; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java index 47ce2b41b..40179d4f0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java @@ -212,7 +212,8 @@ public class SelectionCommands { @Command( name = "/chunk", - desc = "Set the selection to your current chunk." + desc = "Set the selection to your current chunk.", + descFooter = "This command selects 256-block-tall areas,\nwhich can be specified by the y-coordinate.\nE.g. -c x,1,z will select from y=256 to y=511." ) @Logging(POSITION) @CommandPermissions("worldedit.selection.chunk") @@ -308,19 +309,21 @@ public class SelectionCommands { @Command( name = "toggleeditwand", aliases = { "/toggleeditwand" }, - desc = "Remind the user that the wand is now a tool and can be unbound with /none." + desc = "Remind the user that the wand is now a tool and can be unbound with /tool none." ) @CommandPermissions("worldedit.wand.toggle") public void toggleWand(Player player) { - player.printInfo(TextComponent.of("The selection wand is now a normal tool. You can disable it with ") - .append(TextComponent.of("/none", TextColor.AQUA).clickEvent( - ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/none"))) - .append(TextComponent.of(" and rebind it to any item with ")) - .append(TextComponent.of("//selwand", TextColor.AQUA).clickEvent( - ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "//selwand"))) - .append(TextComponent.of(" or get a new wand with ")) - .append(TextComponent.of("//wand", TextColor.AQUA).clickEvent( - ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "//wand")))); + player.printInfo( + TranslatableComponent.of( + "worldedit.wand.selwand.now.tool", + TextComponent.of("/tool none", TextColor.AQUA).clickEvent( + ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/tool none")), + TextComponent.of("/tool selwand", TextColor.AQUA).clickEvent( + ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/tool selwand")), + TextComponent.of("//wand", TextColor.AQUA).clickEvent( + ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "//wand")) + ) + ); } @Command( @@ -570,6 +573,7 @@ public class SelectionCommands { aliases = { ";", "/desel", "/deselect" }, desc = "Choose a region selector" ) + @CommandPermissions("worldedit.analysis.sel") public void select(Actor actor, World world, LocalSession session, @Arg(desc = "Selector to switch to", def = "") SelectorChoice selector, @@ -715,7 +719,7 @@ public class SelectionCommands { final BlockState state = c.getID(); final BlockType blockType = state.getBlockType(); - TextComponent blockName = TextComponent.of(blockType.getName(), TextColor.LIGHT_PURPLE); + Component blockName = blockType.getRichName().color(TextColor.LIGHT_PURPLE); TextComponent toolTip; if (separateStates && state != blockType.getDefaultState()) { toolTip = TextComponent.of(state.getAsString(), TextColor.GRAY); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java index b2f2872a0..8cbeab989 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java @@ -25,8 +25,10 @@ import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.command.tool.BlockDataCyler; import com.sk89q.worldedit.command.tool.BlockReplacer; +import com.sk89q.worldedit.command.tool.BrushTool; import com.sk89q.worldedit.command.tool.DistanceWand; import com.sk89q.worldedit.command.tool.FloatingTreeRemover; import com.sk89q.worldedit.command.tool.FloodFillTool; @@ -35,19 +37,23 @@ import com.sk89q.worldedit.command.tool.LongRangeBuildTool; import com.sk89q.worldedit.command.tool.NavigationWand; import com.sk89q.worldedit.command.tool.QueryTool; import com.sk89q.worldedit.command.tool.SelectionWand; +import com.sk89q.worldedit.command.tool.StackTool; +import com.sk89q.worldedit.command.tool.Tool; import com.sk89q.worldedit.command.tool.TreePlanter; import com.sk89q.worldedit.command.util.CommandPermissions; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; +import com.sk89q.worldedit.command.util.SubCommandPermissionCondition; import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.internal.command.CommandRegistrationHandler; import com.sk89q.worldedit.internal.command.CommandUtil; import com.sk89q.worldedit.util.HandSide; import com.sk89q.worldedit.util.TreeGenerator; +import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.world.block.BlockStateHolder; -import com.sk89q.worldedit.world.item.ItemType; import org.enginehub.piston.CommandManager; import org.enginehub.piston.CommandManagerService; import org.enginehub.piston.CommandMetadata; @@ -82,14 +88,19 @@ public class ToolCommands { .collect(Collectors.toSet()); for (org.enginehub.piston.Command command : commands) { if (command.getAliases().contains("unbind")) { - // Don't register new /tool unbind alias + // Don't register new /tool alias command = command.toBuilder().aliases( Collections2.filter(command.getAliases(), alias -> !"unbind".equals(alias)) ).build(); } + if (command.getName().equals("stacker")) { + // Don't register /stacker + continue; + } commandManager.register(CommandUtil.deprecate( command, "Global tool names cause conflicts " - + "and will be removed in WorldEdit 8", ToolCommands::asNonGlobal + + "and will be removed in WorldEdit 8", + CommandUtil.ReplacementMessageGenerator.forNewCommand(ToolCommands::asNonGlobal) )); } @@ -110,6 +121,8 @@ public class ToolCommands { .required() .build()); command.description(TextComponent.of("Binds a tool to the item in your hand")); + + command.condition(new SubCommandPermissionCondition.Generator(nonGlobalCommands).build()); }); } @@ -124,15 +137,34 @@ public class ToolCommands { static void setToolNone(Player player, LocalSession session, boolean isBrush) throws InvalidToolBindException { - session.setTool(player, null); + isBrush = session.getTool(player) instanceof BrushTool; + session.setTool(player.getItemInHand(HandSide.MAIN_HAND).getType(), null); player.printInfo(TranslatableComponent.of(isBrush ? "worldedit.brush.none.equip" : "worldedit.tool.none.equip")); } + + private static void setTool(Player player, LocalSession session, Tool tool, + String translationKey) throws InvalidToolBindException { + BaseItemStack itemStack = player.getItemInHand(HandSide.MAIN_HAND); + session.setTool(itemStack.getType(), tool); + player.printInfo(TranslatableComponent.of(translationKey, itemStack.getRichName())); + } + private final WorldEdit we; public ToolCommands(WorldEdit we) { this.we = we; } + @Command( + name = "none", + aliases = "unbind", + desc = "Unbind a bound tool from your current item" + ) + @CommandPermissions("worldedit.tool.none") + public void none(Player player, LocalSession session) throws WorldEditException { + setToolNone(player, session, false); + } + @Command( name = "selwand", aliases = "/selwand", @@ -140,9 +172,7 @@ public class ToolCommands { ) @CommandPermissions("worldedit.setwand") public void selwand(Player player, LocalSession session) throws WorldEditException { - final ItemType itemType = player.getItemInHand(HandSide.MAIN_HAND).getType(); - session.setTool(player, SelectionWand.INSTANCE); - player.printInfo(TranslatableComponent.of("worldedit.tool.selwand.equip", TextComponent.of(itemType.getName()))); + setTool(player, session, SelectionWand.INSTANCE, "worldedit.tool.selwand.equip"); } @Command( @@ -152,10 +182,7 @@ public class ToolCommands { ) @CommandPermissions("worldedit.setwand") public void navwand(Player player, LocalSession session) throws WorldEditException { - - final ItemType itemType = player.getItemInHand(HandSide.MAIN_HAND).getType(); - session.setTool(player, NavigationWand.INSTANCE); - player.printInfo(TranslatableComponent.of("worldedit.tool.navwand.equip", TextComponent.of(itemType.getName()))); + setTool(player, session, NavigationWand.INSTANCE, "worldedit.tool.navwand.equip"); } @Command( @@ -165,10 +192,7 @@ public class ToolCommands { ) @CommandPermissions("worldedit.tool.info") public void info(Player player, LocalSession session) throws WorldEditException { - - final ItemType itemType = player.getItemInHand(HandSide.MAIN_HAND).getType(); - session.setTool(player, new QueryTool()); - player.printInfo(TranslatableComponent.of("worldedit.tool.info.equip", TextComponent.of(itemType.getName()))); + setTool(player, session, new QueryTool(), "worldedit.tool.info.equip"); } @Command( @@ -178,9 +202,7 @@ public class ToolCommands { ) @CommandPermissions("worldedit.tool.inspect") public void inspectBrush(Player player, LocalSession session) throws WorldEditException { - final ItemType itemType = player.getItemInHand(HandSide.MAIN_HAND).getType(); - session.setTool(player, new InspectBrush()); - player.printInfo(TranslatableComponent.of("worldedit.tool.inspect.equip", TextComponent.of(itemType.getName()))); + setTool(player, session, new InspectBrush(), "worldedit.tool.info.equip"); } @Command( @@ -192,10 +214,20 @@ public class ToolCommands { public void tree(Player player, LocalSession session, @Arg(desc = "Type of tree to generate", def = "tree") TreeGenerator.TreeType type) throws WorldEditException { + setTool(player, session, new TreePlanter(type), "worldedit.tool.tree.equip"); + } - final ItemType itemType = player.getItemInHand(HandSide.MAIN_HAND).getType(); - session.setTool(player, new TreePlanter(type)); - player.printInfo(TranslatableComponent.of("worldedit.tool.tree.equip", TextComponent.of(itemType.getName()))); + @Command( + name = "stacker", + desc = "Block stacker tool" + ) + @CommandPermissions("worldedit.tool.stack") + public void stacker(Player player, LocalSession session, + @Arg(desc = "The max range of the stack", def = "10") + int range, + @Arg(desc = "The mask to stack until", def = "!#existing") + Mask mask) throws WorldEditException { + setTool(player, session, new StackTool(range, mask), "worldedit.tool.stack.equip"); } @Command( @@ -207,10 +239,7 @@ public class ToolCommands { public void repl(Player player, LocalSession session, @Arg(desc = "The pattern of blocks to place") Pattern pattern) throws WorldEditException { - - final ItemType itemType = player.getItemInHand(HandSide.MAIN_HAND).getType(); - session.setTool(player, new BlockReplacer(pattern)); - player.printInfo(TranslatableComponent.of("worldedit.tool.repl.equip", TextComponent.of(itemType.getName()))); + setTool(player, session, new BlockReplacer(pattern), "worldedit.tool.repl.equip"); } @Command( @@ -220,10 +249,7 @@ public class ToolCommands { ) @CommandPermissions("worldedit.tool.data-cycler") public void cycler(Player player, LocalSession session) throws WorldEditException { - - final ItemType itemType = player.getItemInHand(HandSide.MAIN_HAND).getType(); - session.setTool(player, new BlockDataCyler()); - player.printInfo(TranslatableComponent.of("worldedit.tool.data-cycler.equip", TextComponent.of(itemType.getName()))); + setTool(player, session, new BlockDataCyler(), "worldedit.tool.data-cycler.equip"); } @Command( @@ -241,13 +267,10 @@ public class ToolCommands { LocalConfiguration config = we.getConfiguration(); if (range > config.maxSuperPickaxeSize) { - player.printError(TranslatableComponent.of("worldedit.superpickaxe.max-range", TextComponent.of(config.maxSuperPickaxeSize))); + player.printError(TranslatableComponent.of("worldedit.tool.superpickaxe.max-range", TextComponent.of(config.maxSuperPickaxeSize))); return; } - - final ItemType itemType = player.getItemInHand(HandSide.MAIN_HAND).getType(); - session.setTool(player, new FloodFillTool(range, pattern)); - player.printInfo(TranslatableComponent.of("worldedit.tool.floodfill.equip", TextComponent.of(itemType.getName()))); + setTool(player, session, new FloodFillTool(range, pattern), "worldedit.tool.floodfill.equip"); } @Command( @@ -257,10 +280,7 @@ public class ToolCommands { ) @CommandPermissions("worldedit.tool.deltree") public void deltree(Player player, LocalSession session) throws WorldEditException { - - final ItemType itemType = player.getItemInHand(HandSide.MAIN_HAND).getType(); - session.setTool(player, new FloatingTreeRemover()); - player.printInfo(TranslatableComponent.of("worldedit.tool.deltree.equip", TextComponent.of(itemType.getName()))); + setTool(player, session, new FloatingTreeRemover(), "worldedit.tool.deltree.equip"); } @Command( @@ -270,9 +290,7 @@ public class ToolCommands { ) @CommandPermissions("worldedit.tool.farwand") public void farwand(Player player, LocalSession session) throws WorldEditException { - final ItemType itemType = player.getItemInHand(HandSide.MAIN_HAND).getType(); - session.setTool(player, new DistanceWand()); - player.printInfo(TranslatableComponent.of("worldedit.tool.farwand.equip", TextComponent.of(itemType.getName()))); + setTool(player, session, new DistanceWand(), "worldedit.tool.farwand.equip"); } @Command( @@ -286,18 +304,19 @@ public class ToolCommands { Pattern primary, @Arg(desc = "Pattern to set on right-click") Pattern secondary) throws WorldEditException { - - final ItemType itemType = player.getItemInHand(HandSide.MAIN_HAND).getType(); - session.setTool(player, new LongRangeBuildTool(primary, secondary)); - player.printInfo(TranslatableComponent.of("worldedit.tool.lrbuild.equip", TextComponent.of(itemType.getName()))); - String primaryName = "pattern"; - String secondaryName = "pattern"; + setTool(player, session, new LongRangeBuildTool(primary, secondary), "worldedit.tool.lrbuild.equip"); + Component primaryName; + Component secondaryName; if (primary instanceof BlockStateHolder) { - primaryName = ((BlockStateHolder) primary).getBlockType().getName(); + primaryName = ((BlockStateHolder) primary).getBlockType().getRichName(); + } else { + primaryName = TextComponent.of("pattern"); } if (secondary instanceof BlockStateHolder) { - secondaryName = ((BlockStateHolder) secondary).getBlockType().getName(); + secondaryName = ((BlockStateHolder) secondary).getBlockType().getRichName(); + } else { + secondaryName = TextComponent.of("pattern"); } - player.printInfo(TranslatableComponent.of("worldedit.tool.lrbuild.set", TextComponent.of(primaryName), TextComponent.of(secondaryName))); + player.printInfo(TranslatableComponent.of("worldedit.tool.lrbuild.set", primaryName, secondaryName)); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java index 17daf0287..c6d94f35c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java @@ -569,7 +569,9 @@ public class UtilityCommands { @Switch(name = 'f', desc = "Also kill all friendly mobs (Applies the flags `-abgnpt`)") boolean killFriendly, @Switch(name = 'r', desc = "Also destroy armor stands") - boolean killArmorStands) throws WorldEditException { + boolean killArmorStands, + @Switch(name = 'w', desc = "Also kill water mobs") + boolean killWater) throws WorldEditException { LocalConfiguration config = we.getConfiguration(); if (radius == null) { @@ -595,6 +597,7 @@ public class UtilityCommands { flags.or(CreatureButcher.Flags.AMBIENT, killAmbient, "worldedit.butcher.ambient"); flags.or(CreatureButcher.Flags.TAGGED, killWithName, "worldedit.butcher.tagged"); flags.or(CreatureButcher.Flags.ARMOR_STAND, killArmorStands, "worldedit.butcher.armorstands"); + flags.or(CreatureButcher.Flags.WATER, killWater, "worldedit.butcher.water"); int killed = killMatchingEntities(radius, actor, flags::createFunction); @@ -675,7 +678,11 @@ public class UtilityCommands { try { expression = Expression.compile(String.join(" ", input)); } catch (ExpressionException e) { - actor.printError(TranslatableComponent.of("worldedit.calc.invalid", TextComponent.of(String.join(" ", input)))); + actor.printError(TranslatableComponent.of( + "worldedit.calc.invalid.with-error", + TextComponent.of(String.join(" ", input)), + TextComponent.of(e.getMessage()) + )); return; } WorldEditAsyncCommandBuilder.createAndSendMessage(actor, () -> { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java index f777c3a44..ad07203a6 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java @@ -45,6 +45,7 @@ import org.enginehub.piston.inject.Key; import java.lang.reflect.Field; import java.util.List; +import java.util.Locale; import java.util.stream.Collectors; public final class RegistryConverter implements ArgumentConverter { @@ -98,7 +99,7 @@ public final class RegistryConverter implements ArgumentConvert @Override public ConversionResult convert(String argument, InjectedValueAccess injectedValueAccess) { - V result = registry.get(argument); + V result = registry.get(argument.toLowerCase(Locale.ROOT)); return result == null ? FailedConversion.from(new IllegalArgumentException( "Not a valid " + registry.getName() + ": " + argument)) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/BlockReplacer.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/BlockReplacer.java index 14479c2a8..5c3aabb9e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/BlockReplacer.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/BlockReplacer.java @@ -78,7 +78,7 @@ public class BlockReplacer implements DoubleActionBlockTool { if (targetBlock != null) { pattern = targetBlock; - player.printInfo(TranslatableComponent.of("worldedit.tool.repl.switched", TextComponent.of(targetBlock.getBlockType().getName()))); + player.printInfo(TranslatableComponent.of("worldedit.tool.repl.switched", targetBlock.getBlockType().getRichName())); } return true; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/QueryTool.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/QueryTool.java index 1f192aea1..ebce5559e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/QueryTool.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/QueryTool.java @@ -59,7 +59,7 @@ public class QueryTool implements BlockTool { TextComponent.Builder builder = TextComponent.builder(); builder.append(TextComponent.of("@" + clicked.toVector().toBlockPoint() + ": ", TextColor.BLUE)); - builder.append(TextComponent.of(block.getBlockType().getName(), TextColor.YELLOW)); + builder.append(block.getBlockType().getRichName().color(TextColor.YELLOW)); builder.append(TextComponent.of(" (" + block + ") ", TextColor.GRAY) .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TranslatableComponent.of("worldedit.tool.info.blockstate.hover")))); final int internalId = BlockStateIdAccess.getBlockStateId(block.toImmutableState()); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/CreatureButcher.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/CreatureButcher.java index 9524f99f8..d19ad7087 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/CreatureButcher.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/CreatureButcher.java @@ -36,8 +36,9 @@ public class CreatureButcher { public static final int GOLEMS = 1 << 3; public static final int AMBIENT = 1 << 4; public static final int TAGGED = 1 << 5; - public static final int FRIENDLY = PETS | NPCS | ANIMALS | GOLEMS | AMBIENT | TAGGED; public static final int ARMOR_STAND = 1 << 6; + public static final int WATER = 1 << 7; + public static final int FRIENDLY = PETS | NPCS | ANIMALS | GOLEMS | AMBIENT | TAGGED | WATER; private Flags() { } @@ -73,6 +74,7 @@ public class CreatureButcher { boolean killAmbient = (flags & Flags.AMBIENT) != 0; boolean killTagged = (flags & Flags.TAGGED) != 0; boolean killArmorStands = (flags & Flags.ARMOR_STAND) != 0; + boolean killWaterCreatures = (flags & Flags.WATER) != 0; EntityProperties type = entity.getFacet(EntityProperties.class); @@ -116,6 +118,10 @@ public class CreatureButcher { return false; } + if (!killWaterCreatures && type.isWaterCreature()) { + return false; + } + entity.remove(); return true; }; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/SubCommandPermissionCondition.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/SubCommandPermissionCondition.java index 5ce55eb41..c3ac2f59d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/SubCommandPermissionCondition.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/SubCommandPermissionCondition.java @@ -19,10 +19,12 @@ package com.sk89q.worldedit.command.util; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.enginehub.piston.Command; import org.enginehub.piston.inject.InjectedValueAccess; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; @@ -45,8 +47,8 @@ public final class SubCommandPermissionCondition extends PermissionCondition { public static class Generator { private final List subCommands; - public Generator(List subCommands) { - this.subCommands = subCommands; + public Generator(Collection subCommands) { + this.subCommands = ImmutableList.copyOf(subCommands); } public Command.Condition build() { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/entity/metadata/EntityProperties.java b/worldedit-core/src/main/java/com/sk89q/worldedit/entity/metadata/EntityProperties.java index 8f987d6d6..eae08e3fa 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/entity/metadata/EntityProperties.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/entity/metadata/EntityProperties.java @@ -161,4 +161,11 @@ public interface EntityProperties { * @return true if pasteable */ boolean isPasteable(); + + /** + * Test whether the entity is a water creature. + * + * @return true if water creature + */ + boolean isWaterCreature(); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java index c221acb6b..dc4741215 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java @@ -83,6 +83,7 @@ import com.sk89q.worldedit.command.WorldEditCommands; import com.sk89q.worldedit.command.WorldEditCommandsRegistration; import com.sk89q.worldedit.command.argument.Arguments; import com.sk89q.worldedit.command.argument.BooleanConverter; +import com.sk89q.worldedit.command.argument.Chunk3dVectorConverter; import com.sk89q.worldedit.command.argument.CommaSeparatedValuesConverter; import com.sk89q.worldedit.command.argument.DirectionConverter; import com.sk89q.worldedit.command.argument.DirectionVectorConverter; @@ -90,7 +91,9 @@ import com.sk89q.worldedit.command.argument.EntityRemoverConverter; import com.sk89q.worldedit.command.argument.EnumConverter; import com.sk89q.worldedit.command.argument.ExpressionConverter; import com.sk89q.worldedit.command.argument.FactoryConverter; +import com.sk89q.worldedit.command.argument.HeightConverter; import com.sk89q.worldedit.command.argument.LocationConverter; +import com.sk89q.worldedit.command.argument.OffsetConverter; import com.sk89q.worldedit.command.argument.RegionFactoryConverter; import com.sk89q.worldedit.command.argument.RegistryConverter; import com.sk89q.worldedit.command.argument.SideEffectConverter; @@ -250,6 +253,7 @@ public final class PlatformCommandManager { ); } VectorConverter.register(commandManager); + Chunk3dVectorConverter.register(commandManager); EnumConverter.register(commandManager); RegistryConverter.register(commandManager); ZonedDateTimeConverter.register(commandManager); @@ -260,6 +264,8 @@ public final class PlatformCommandManager { LocationConverter.register(commandManager); ExpressionConverter.register(commandManager); SideEffectConverter.register(commandManager); + HeightConverter.register(commandManager); + OffsetConverter.register(worldEdit, commandManager); registerBindings(new ConsumeBindings(worldEdit, this)); registerBindings(new PrimitiveBindings(worldEdit)); @@ -359,9 +365,8 @@ public final class PlatformCommandManager { } private void registerSubCommands(String name, List aliases, String desc, - CommandManager commandManager, - Consumer> handlerInstance, - @NotNull Consumer additionalConfig) { + Consumer> handlerInstance, + @NotNull Consumer additionalConfig) { commandManager.register(name, cmd -> { cmd.aliases(aliases); cmd.description(TextComponent.of(desc)); @@ -438,7 +443,6 @@ public final class PlatformCommandManager { "brush", Lists.newArrayList("br", "/brush", "/br", "/tool", "tool"), "Brushing commands", - commandManager, c -> { c.accept(BrushCommandsRegistration.builder(), new BrushCommands(worldEdit)); c.accept(ToolCommandsRegistration.builder(), new ToolCommands(worldEdit)); @@ -486,10 +490,11 @@ public final class PlatformCommandManager { ClipboardCommandsRegistration.builder(), new ClipboardCommands() ); - this.registration.register( - commandManager, - GeneralCommandsRegistration.builder(), - new GeneralCommands(worldEdit) + GeneralCommands.register( + registration, + commandManager, + commandManagerService, + worldEdit ); this.registration.register( commandManager, diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java index 1c9348fa9..f16a67d5b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java @@ -421,6 +421,10 @@ public interface Extent extends InputExtent, OutputExtent { for (final BlockVector3 pt : region) { BlockType type = getBlock(pt).getBlockType(); + if (type == BlockTypes.__RESERVED__) { + counter[1]++; + continue; + } counter[type.getInternalId()]++; } List> distribution = new ArrayList<>(); @@ -446,6 +450,13 @@ public interface Extent extends InputExtent, OutputExtent { for (final BlockVector3 pt : region) { BlockState blk = this.getBlock(pt); BlockType type = blk.getBlockType(); + if (type == BlockTypes.__RESERVED__) { + int[] stateCounter = counter[1]; + if (stateCounter == null) { + counter[1] = stateCounter = new int[BlockTypes.AIR.getMaxStateId() + 1]; + } + stateCounter[BlockTypes.AIR.getDefaultState().getInternalPropertiesId()]++; + } int[] stateCounter = counter[type.getInternalId()]; if (stateCounter == null) { counter[type.getInternalId()] = stateCounter = new int[type.getMaxStateId() + 1]; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/PassthroughExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/PassthroughExtent.java index 73b6f9b37..1f3cee85e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/PassthroughExtent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/PassthroughExtent.java @@ -22,7 +22,6 @@ import com.sk89q.worldedit.world.block.BlockType; import java.util.List; import java.util.Set; -import java.util.UUID; import javax.annotation.Nullable; public class PassthroughExtent extends AbstractDelegateExtent { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicWriter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicWriter.java index a13667eb8..092bc94d0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicWriter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicWriter.java @@ -159,15 +159,16 @@ public class FastSchematicWriter implements ClipboardWriter { if (nbt != null) { Map values = nbt.getValue(); - values.remove("id"); // Remove 'id' if it exists. We want 'Id' - // Positions are kept in NBT, we don't want that. values.remove("x"); values.remove("y"); values.remove("z"); - if (!values.containsKey("Id")) { - values.put("Id", new StringTag(block.getNbtId())); - } + values.put("Id", new StringTag(block.getNbtId())); + + // Remove 'id' if it exists. We want 'Id'. + // Do this after we get "getNbtId" cos otherwise "getNbtId" doesn't work. + // Dum. + values.remove("id"); values.put("Pos", new IntArrayTag(new int[]{ pos.getX(), pos.getY(), diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/NBTSchematicReader.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/NBTSchematicReader.java index cb2faae93..af2b85a2f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/NBTSchematicReader.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/NBTSchematicReader.java @@ -32,12 +32,14 @@ public abstract class NBTSchematicReader implements ClipboardReader { protected static T requireTag(Map items, String key, Class expected) throws IOException { if (!items.containsKey(key)) { - throw new IOException("Schematic file is missing a \"" + key + "\" tag"); + throw new IOException("Schematic file is missing a \"" + key + "\" tag of type " + + expected.getName()); } Tag tag = items.get(key); if (!expected.isInstance(tag)) { - throw new IOException(key + " tag is not of tag type " + expected.getName()); + throw new IOException(key + " tag is not of tag type " + expected.getName() + ", got " + + tag.getClass().getName() + " instead"); } return expected.cast(tag); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/ForestGenerator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/ForestGenerator.java index 50756d9b4..b6427453f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/ForestGenerator.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/ForestGenerator.java @@ -54,28 +54,22 @@ public class ForestGenerator implements RegionFunction { BlockState block = editSession.getBlock(position); BlockType t = block.getBlockType(); - switch (t.getInternalId()) { - case BlockID.GRASS_BLOCK: - case BlockID.DIRT: - case BlockID.PODZOL: - case BlockID.COARSE_DIRT: + if (t.getMaterial().isSolid()) { return treeType.generate(editSession, position.add(0, 1, 0)); - default: - if (t.getMaterial().isReplacedDuringPlacement()) { - // since the implementation's tree generators generally don't generate in non-air spots, - // we trick editsession history here in the first call - editSession.setBlock(position, BlockTypes.AIR.getDefaultState()); - // and then trick the generator here by directly setting into the world - editSession.getWorld().setBlock(position, BlockTypes.AIR.getDefaultState()); - // so that now the generator can generate the tree - boolean success = treeType.generate(editSession, position); - if (!success) { - editSession.setBlock(position, block); // restore on failure - } - return success; - } else { // Trees won't grow on this! - return false; + } else if (t.getMaterial().isReplacedDuringPlacement()) { + // since the implementation's tree generators generally don't generate in non-air spots, + // we trick editsession history here in the first call + editSession.setBlock(position, BlockTypes.AIR.getDefaultState()); + // and then trick the generator here by directly setting into the world + editSession.getWorld().setBlock(position, BlockTypes.AIR.getDefaultState()); + // so that now the generator can generate the tree + boolean success = treeType.generate(editSession, position); + if (!success) { + editSession.setBlock(position, block); // restore on failure } + return success; + } else { // Trees won't grow on this! + return false; } } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/RepeatingExtentPattern.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/RepeatingExtentPattern.java index e39051981..e4d5488ef 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/RepeatingExtentPattern.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/RepeatingExtentPattern.java @@ -32,7 +32,6 @@ import static com.google.common.base.Preconditions.checkNotNull; public class RepeatingExtentPattern extends AbstractExtentPattern { private final BlockVector3 size; - private final MutableBlockVector3 mutable; private BlockVector3 origin; private BlockVector3 offset; @@ -47,7 +46,6 @@ public class RepeatingExtentPattern extends AbstractExtentPattern { setOrigin(origin); setOffset(offset); size = extent.getMaximumPoint().subtract(extent.getMinimumPoint()).add(1, 1, 1); - this.mutable = new MutableBlockVector3(); } /** @@ -93,7 +91,7 @@ public class RepeatingExtentPattern extends AbstractExtentPattern { int x = Math.abs(position.getX() + offset.getX()) % size.getBlockX() + origin.getX(); int y = Math.abs(position.getY() + offset.getY()) % size.getBlockY() + origin.getY(); int z = Math.abs(position.getZ() + offset.getZ()) % size.getBlockZ() + origin.getZ(); - return getExtent().getFullBlock(mutable.setComponents(x, y, z)); + return getExtent().getFullBlock(x, y, z); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/CommandUtil.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/CommandUtil.java index b55ba39d2..c880f365b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/CommandUtil.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/CommandUtil.java @@ -52,14 +52,12 @@ public class CommandUtil { private static final Component DEPRECATION_MARKER = TextComponent.of("This command is deprecated."); - private static Component makeDeprecatedFooter(String reason, Component newCommand) { + private static Component makeDeprecatedFooter(String reason, Component replacement) { return TextComponent.builder() .append(DEPRECATION_MARKER) .append(" " + reason + ".") .append(TextComponent.newline()) - .append(TextComponent.of("Use ", TextColor.GOLD, TextDecoration.ITALIC)) - .append(newCommand) - .append(TextComponent.of(" instead.", TextColor.GOLD, TextDecoration.ITALIC)) + .append(replacement.color(TextColor.GOLD).decoration(TextDecoration.ITALIC, true)) .build(); } @@ -69,20 +67,48 @@ public class CommandUtil { } + public interface ReplacementMessageGenerator { + + /** + * Generate text that says "Please use [cmd] instead." and allows clicking to dump + * the command to the text box. + */ + static ReplacementMessageGenerator forNewCommand(NewCommandGenerator generator) { + return (oldCommand, oldParameters) -> { + String suggestedCommand = generator.newCommand(oldCommand, oldParameters); + return createNewCommandReplacementText(suggestedCommand); + }; + } + + Component getReplacement(Command oldCommand, CommandParameters oldParameters); + + } + + public static Component createNewCommandReplacementText(String suggestedCommand) { + return TextComponent.builder("Please use ", TextColor.GOLD) + .append(TextComponent.of(suggestedCommand) + .decoration(TextDecoration.UNDERLINED, true) + .clickEvent(ClickEvent.suggestCommand(suggestedCommand))) + .append(" instead.") + .build(); + } + public static Command deprecate(Command command, String reason, - NewCommandGenerator newCommandGenerator) { + ReplacementMessageGenerator replacementMessageGenerator) { Component deprecatedWarning = makeDeprecatedFooter( reason, - newCommandSuggestion(newCommandGenerator, - NoInputCommandParameters.builder().build(), - command) + replacementMessageGenerator.getReplacement( + command, + NoInputCommandParameters.builder().build() + ) ); return command.toBuilder() .action(parameters -> - deprecatedCommandWarning(parameters, command, reason, newCommandGenerator)) + deprecatedCommandWarning(parameters, command, reason, replacementMessageGenerator)) .footer(command.getFooter() .map(existingFooter -> existingFooter - .append(TextComponent.newline()).append(deprecatedWarning)) + .append(TextComponent.newline()) + .append(deprecatedWarning)) .orElse(deprecatedWarning)) .build(); } @@ -139,26 +165,28 @@ public class CommandUtil { CommandParameters parameters, Command command, String reason, - NewCommandGenerator generator + ReplacementMessageGenerator generator ) throws Exception { parameters.injectedValue(Key.of(Actor.class)) - .ifPresent(actor -> { - Component suggestion = newCommandSuggestion(generator, parameters, command); - actor.print(TextComponent.of(reason + ". Please use ", TextColor.GOLD) - .append(suggestion) - .append(TextComponent.of(" instead.")) - ); - }); + .ifPresent(actor -> + sendDeprecationMessage(parameters, command, reason, generator, actor) + ); return command.getAction().run(parameters); } - private static Component newCommandSuggestion(NewCommandGenerator generator, - CommandParameters parameters, - Command command) { - String suggestedCommand = generator.newCommand(command, parameters); - return TextComponent.of(suggestedCommand) - .decoration(TextDecoration.UNDERLINED, true) - .clickEvent(ClickEvent.suggestCommand(suggestedCommand)); + private static void sendDeprecationMessage( + CommandParameters parameters, + Command command, + String reason, + ReplacementMessageGenerator generator, + Actor actor + ) { + Component replacement = generator.getReplacement(command, parameters); + actor.print( + TextComponent.builder(reason + ". ", TextColor.GOLD) + .append(replacement) + .build() + ); } public static Map getSubCommands(Command currentCommand) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/exception/WorldEditExceptionConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/exception/WorldEditExceptionConverter.java index 549ea1d59..194a67be0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/exception/WorldEditExceptionConverter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/exception/WorldEditExceptionConverter.java @@ -36,6 +36,7 @@ import com.sk89q.worldedit.command.InsufficientArgumentsException; import com.sk89q.worldedit.command.tool.InvalidToolBindException; import com.sk89q.worldedit.internal.expression.ExpressionException; import com.sk89q.worldedit.regions.RegionOperationException; +import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.io.file.FileSelectionAbortedException; import com.sk89q.worldedit.util.io.file.FilenameResolutionException; @@ -62,7 +63,11 @@ public class WorldEditExceptionConverter extends ExceptionConverterHelper { } private CommandException newCommandException(String message, Throwable cause) { - return new CommandException(TextComponent.of(String.valueOf(message)), cause, ImmutableList.of()); + return newCommandException(TextComponent.of(String.valueOf(message)), cause); + } + + private CommandException newCommandException(Component message, Throwable cause) { + return new CommandException(message, cause, ImmutableList.of()); } @ExceptionMatch @@ -158,7 +163,13 @@ public class WorldEditExceptionConverter extends ExceptionConverterHelper { @ExceptionMatch public void convert(InvalidToolBindException e) throws CommandException { - throw newCommandException("Can't bind tool to " + e.getItemType().getName() + ": " + e.getMessage(), e); + throw newCommandException( + TextComponent.builder("Can't bind tool to ") + .append(e.getItemType().getRichName()) + .append(": " + e.getMessage()) + .build(), + e + ); } @ExceptionMatch diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionHelper.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionHelper.java index 916460ceb..922657976 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionHelper.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionHelper.java @@ -65,9 +65,22 @@ public class ExpressionHelper { Set matchingFns = functions.getMap().get(fnName); check(!matchingFns.isEmpty(), ctx, "Unknown function '" + fnName + "'"); for (MethodHandle function : matchingFns) { + if (function.isVarargsCollector()) { + int nParams = function.type().parameterCount(); + // last param is the array, turn that varargs + int keptParams = nParams - 1; + function = function.asCollector( + // collect into the last array + function.type().parameterType(nParams - 1), + // collect the variable args (args over kept) + ctx.args.size() - keptParams + ); + // re-wrap it for the inner arguments + function = function.asType(function.type().wrap()); + } MethodType type = function.type(); // Validate argc if not varargs - if (!function.isVarargsCollector() && type.parameterCount() != ctx.args.size()) { + if (type.parameterCount() != ctx.args.size()) { // skip non-matching function continue; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java index 0b8f651e0..b40c91a94 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java @@ -63,6 +63,7 @@ public final class Functions { } private static MethodHandle clean(MethodHandle handle) { + boolean wasVarargs = handle.isVarargsCollector(); // box it all first handle = handle.asType(handle.type().wrap()); if (handle.type().returnType() != Double.class) { @@ -72,6 +73,12 @@ public final class Functions { handle = handle.asType(handle.type().changeReturnType(Number.class)); handle = filterReturnValue(handle, DOUBLE_VALUE); } + // return vararg-ity + if (wasVarargs) { + handle = handle.asVarargsCollector( + handle.type().parameterType(handle.type().parameterCount() - 1) + ); + } return handle; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WorldNativeAccess.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WorldNativeAccess.java index d005e00e2..6ae961b77 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WorldNativeAccess.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WorldNativeAccess.java @@ -178,7 +178,7 @@ public interface WorldNativeAccess { } // Make connection updates optional - if (sideEffectSet.shouldApply(SideEffect.VALIDATION)) { + if (sideEffectSet.shouldApply(SideEffect.NEIGHBORS)) { updateNeighbors(pos, oldState, newState, 512); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector2.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector2.java index 390e4fd87..48c579ac8 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector2.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector2.java @@ -610,4 +610,12 @@ public class BlockVector2 { public String toString() { return "(" + x + ", " + z + ")"; } + + /** + * Returns a string representation that is supported by the parser. + * @return string + */ + public String toParserString() { + return x + "," + z; + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java index 6cd3600c0..801cdece1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java @@ -787,6 +787,14 @@ public abstract class BlockVector3 { return "(" + getX() + ", " + getY() + ", " + getZ() + ")"; } + /** + * Returns a string representation that is supported by the parser. + * @return string + */ + public String toParserString() { + return getX() + "," + getY() + "," + getZ(); + } + //Used by VS fork public BlockVector3 plus(BlockVector3 other) { return add(other); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/Vector2.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/Vector2.java index 3eabbcf40..f2e418b4d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/math/Vector2.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/Vector2.java @@ -478,4 +478,12 @@ public final class Vector2 { return "(" + x + ", " + z + ")"; } + /** + * Returns a string representation that is supported by the parser. + * @return string + */ + public String toParserString() { + return x + "," + z; + } + } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/Vector3.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/Vector3.java index 11ef929c3..79469ae54 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/math/Vector3.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/Vector3.java @@ -639,4 +639,12 @@ public abstract class Vector3 { return "(" + x + ", " + y + ", " + z + ")"; } + /** + * Returns a string representation that is supported by the parser. + * @return string + */ + public String toParserString() { + return getX() + "," + getY() + "," + getZ(); + } + } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/AbstractRegion.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/AbstractRegion.java index 6dea98712..ede52fe48 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/AbstractRegion.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/AbstractRegion.java @@ -211,11 +211,11 @@ public abstract class AbstractRegion extends AbstractSet implement // Sub-class utilities protected final int getWorldMinY() { - return world == null ? 0 : world.getMinY(); + return world == null ? Integer.MIN_VALUE : world.getMinY(); } protected final int getWorldMaxY() { - return world == null ? 255 : world.getMaxY(); + return world == null ? Integer.MAX_VALUE : world.getMaxY(); } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java index 20759cd1a..cb90aacd3 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java @@ -33,6 +33,8 @@ import com.sk89q.worldedit.regions.selector.limit.SelectorLimits; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; import com.sk89q.worldedit.world.World; import java.util.ArrayList; @@ -256,11 +258,15 @@ public class CuboidRegionSelector implements RegionSelector, CUIRegion { final List lines = new ArrayList<>(); if (position1 != null) { - lines.add(TranslatableComponent.of("worldedit.selection.cuboid.info.pos1", TextComponent.of(position1.toString()))); + lines.add(TranslatableComponent.of("worldedit.selection.cuboid.info.pos1", TextComponent.of(position1.toString()) + .clickEvent(ClickEvent.of(ClickEvent.Action.COPY_TO_CLIPBOARD, position1.toParserString())) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to copy"))))); } if (position2 != null) { - lines.add(TranslatableComponent.of("worldedit.selection.cuboid.info.pos2", TextComponent.of(position2.toString()))); + lines.add(TranslatableComponent.of("worldedit.selection.cuboid.info.pos2", TextComponent.of(position2.toString()) + .clickEvent(ClickEvent.of(ClickEvent.Action.COPY_TO_CLIPBOARD, position2.toParserString())) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to copy"))))); } return lines; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/CylinderRegionSelector.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/CylinderRegionSelector.java index 444b7c0cd..43df6ef09 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/CylinderRegionSelector.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/CylinderRegionSelector.java @@ -37,6 +37,8 @@ import com.sk89q.worldedit.regions.selector.limit.SelectorLimits; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; import com.sk89q.worldedit.world.World; import java.text.NumberFormat; @@ -249,7 +251,10 @@ public class CylinderRegionSelector implements RegionSelector, CUIRegion { final List lines = new ArrayList<>(); if (!region.getCenter().equals(Vector3.ZERO)) { - lines.add(TranslatableComponent.of("worldedit.selection.cylinder.info.center", TextComponent.of(region.getCenter().toString()))); + Vector3 center = region.getCenter(); + lines.add(TranslatableComponent.of("worldedit.selection.cylinder.info.center", TextComponent.of(center.toString()) + .clickEvent(ClickEvent.of(ClickEvent.Action.COPY_TO_CLIPBOARD, center.toParserString())) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to copy"))))); } if (!region.getRadius().equals(Vector2.ZERO)) { lines.add(TranslatableComponent.of("worldedit.selection.cylinder.info.radius", TextComponent.of(region.getRadius().toString()))); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/EllipsoidRegionSelector.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/EllipsoidRegionSelector.java index e16a1f30b..93b0cf8b2 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/EllipsoidRegionSelector.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/EllipsoidRegionSelector.java @@ -34,6 +34,8 @@ import com.sk89q.worldedit.regions.selector.limit.SelectorLimits; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; import com.sk89q.worldedit.world.World; import java.util.ArrayList; @@ -225,7 +227,9 @@ public class EllipsoidRegionSelector implements RegionSelector, CUIRegion { final Vector3 center = region.getCenter(); if (center.lengthSq() > 0) { - lines.add(TranslatableComponent.of("worldedit.selection.ellipsoid.info.center", TextComponent.of(center.toString()))); + lines.add(TranslatableComponent.of("worldedit.selection.ellipsoid.info.center", TextComponent.of(center.toString()) + .clickEvent(ClickEvent.of(ClickEvent.Action.COPY_TO_CLIPBOARD, center.toParserString())) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to copy"))))); } final Vector3 radius = region.getRadius(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/TreeGenerator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/TreeGenerator.java index c67731f56..b1a5799ec 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/TreeGenerator.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/TreeGenerator.java @@ -82,10 +82,12 @@ public class TreeGenerator { JUNGLE_BUSH("Jungle bush", "junglebush", "jungleshrub"), RED_MUSHROOM("Red mushroom", "redmushroom", "redgiantmushroom"), BROWN_MUSHROOM("Brown mushroom", "brownmushroom", "browngiantmushroom"), + CRIMSON_FUNGUS("Crimson fungus", "crimsonfungus", "rednethermushroom"), + WARPED_FUNGUS("Warped fungus", "warpedfungus", "greennethermushroom"), RANDOM_MUSHROOM("Random mushroom", "randmushroom", "randommushroom") { @Override public boolean generate(EditSession editSession, BlockVector3 pos) throws MaxChangedBlocksException { - TreeType[] choices = { RED_MUSHROOM, BROWN_MUSHROOM }; + TreeType[] choices = { RED_MUSHROOM, BROWN_MUSHROOM, CRIMSON_FUNGUS, WARPED_FUNGUS }; return choices[TreeGenerator.RANDOM.nextInt(choices.length)].generate(editSession, pos); } }, @@ -99,6 +101,13 @@ public class TreeGenerator { return true; } }, + CHORUS_PLANT("Chorus plant", "chorusplant") { + @Override + public boolean generate(EditSession editSession, BlockVector3 pos) throws MaxChangedBlocksException { + // chorus plants have to generate starting in the end stone itself, not the air above the ground + return editSession.getWorld().generateTree(this, editSession, pos.subtract(0, 1, 0)); + } + }, RANDOM("Random tree", "rand", "random") { @Override public boolean generate(EditSession editSession, BlockVector3 pos) throws MaxChangedBlocksException { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/PaginationBox.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/PaginationBox.java index 9301a0493..c4086b08f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/PaginationBox.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/PaginationBox.java @@ -21,17 +21,19 @@ package com.sk89q.worldedit.util.formatting.component; import com.google.common.base.Function; import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; +import com.sk89q.worldedit.history.changeset.ChangeSet; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import javax.annotation.Nullable; import java.util.Collection; -import java.util.Iterator; import java.util.List; import java.util.function.Supplier; -import javax.annotation.Nullable; +import java.util.stream.Collectors; public abstract class PaginationBox extends MessageBox { @@ -138,53 +140,30 @@ public abstract class PaginationBox extends MessageBox { } public static PaginationBox fromStrings(String header, @Nullable String pageCommand, Collection lines, Function adapt) { - return fromStrings(header, pageCommand, Collections2.transform(lines, adapt)); + return fromComponents(header, pageCommand, Collections2.transform(lines, adapt)); } - public static PaginationBox fromStrings(String header, @Nullable String pageCommand, Collection lines) { + public static PaginationBox fromStrings(String header, @Nullable String pageCommand, Collection lines) { + return fromComponents(header, pageCommand, lines.stream() + .map(TextComponent::of) + .collect(Collectors.toList())); + } + + public static PaginationBox fromComponents(String header, @Nullable String pageCommand, Collection lines) { return new ListPaginationBox(header, pageCommand, lines); } - public static PaginationBox fromStrings(String header, @Nullable String pageCommand, List lines) { - return fromStrings(header, pageCommand, (Collection) lines); - } + private static class ListPaginationBox extends PaginationBox { + private final List lines; - public static class ListPaginationBox extends PaginationBox { - private final Collection lines; - private int iterIndex; - private Iterator iterator; - - public ListPaginationBox(String header, String pageCommand, List lines) { - this(header, pageCommand, (Collection) lines); - } - - public ListPaginationBox(String header, String pageCommand, Collection lines) { + ListPaginationBox(String header, String pageCommand, Collection lines) { super(header, pageCommand); - this.lines = lines; + this.lines = ImmutableList.copyOf(lines); } @Override public Component getComponent(int number) { - Object obj; - if (lines instanceof List) { - obj = ((List) lines).get(number); - } else { - if (iterator == null || iterIndex > number) { - iterator = lines.iterator(); - iterIndex = 0; - } - do { - obj = iterator.next(); - iterIndex++; - } while (iterIndex < number); - } - if (obj instanceof Supplier) { - obj = ((Supplier) obj).get(); - } - if (obj instanceof Component) { - return (Component) obj; - } - return TextComponent.of(obj + ""); + return lines.get(number); } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/SideEffectBox.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/SideEffectBox.java index 3e3e77f81..dc0272e1e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/SideEffectBox.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/SideEffectBox.java @@ -70,7 +70,7 @@ public class SideEffectBox extends PaginationBox { for (SideEffect.State uiState : SHOWN_VALUES) { builder = builder.append(TextComponent.space()); builder = builder.append(TranslatableComponent.of(uiState.getDisplayName(), uiState == state ? TextColor.WHITE : TextColor.GRAY) - .clickEvent(ClickEvent.runCommand("//fast -h " + effect.name().toLowerCase(Locale.US) + " " + uiState.name().toLowerCase(Locale.US))) + .clickEvent(ClickEvent.runCommand("//perf -h " + effect.name().toLowerCase(Locale.US) + " " + uiState.name().toLowerCase(Locale.US))) .hoverEvent(HoverEvent.showText(uiState == state ? TranslatableComponent.of("worldedit.sideeffect.box.current") : TranslatableComponent.of("worldedit.sideeffect.box.change-to", TranslatableComponent.of(uiState.getDisplayName())) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeData.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeData.java index 4d1a09b91..72307720f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeData.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeData.java @@ -19,6 +19,8 @@ package com.sk89q.worldedit.world.biome; +import com.sk89q.worldedit.world.registry.BiomeRegistry; + /** * Provides information about a biome. * @@ -32,6 +34,8 @@ public interface BiomeData { * particular convention. * * @return the biome's name + * @deprecated This method does not work on the server. + * Use {@link BiomeRegistry#getRichName(BiomeType)}. */ @Deprecated String getName(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java index 36af08c10..c4dda7db1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java @@ -49,13 +49,8 @@ import static com.google.common.base.Preconditions.checkNotNull; */ public class BaseBlock implements BlockStateHolder, TileEntityBlock { - private BlockState blockState; - @Nullable private CompoundTag nbtData; - - @Deprecated - public BaseBlock() { - this(BlockTypes.AIR.getDefaultState()); - } + private final BlockState blockState; + @Nullable private final CompoundTag nbtData; /** * Construct a block with the given type and default data. @@ -151,6 +146,9 @@ public class BaseBlock implements BlockStateHolder, TileEntityBlock { return ""; } Tag idTag = nbtData.getValue().get("id"); + if (idTag == null) { + idTag = nbtData.getValue().get("Id"); + } if (idTag instanceof StringTag) { return ((StringTag) idTag).getValue(); } else { @@ -164,6 +162,11 @@ public class BaseBlock implements BlockStateHolder, TileEntityBlock { return this.nbtData; } + @Override + public void setNbtData(@Nullable CompoundTag nbtData) { + throw new UnsupportedOperationException("This class is immutable."); + } + /** * Checks whether the type ID and data value are equal. */ diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java index 8ccb2b136..3686d8ed9 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java @@ -33,6 +33,7 @@ import com.sk89q.worldedit.registry.state.AbstractProperty; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.registry.state.PropertyKey; import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.item.ItemTypes; import com.sk89q.worldedit.world.registry.BlockMaterial; @@ -96,6 +97,11 @@ public class BlockType implements Keyed, Pattern { return this.id; } + public Component getRichName() { + return WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS) + .getRegistries().getBlockRegistry().getRichName(this); + } + public String getNamespace() { String id = getId(); int i = id.indexOf(':'); @@ -111,15 +117,11 @@ public class BlockType implements Keyed, Pattern { * Gets the name of this block, or the ID if the name cannot be found. * * @return The name, or ID + * @deprecated The name is now translatable, use {@link #getRichName()}. */ @Deprecated public String getName() { - String name = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS).getRegistries().getBlockRegistry().getName(this); - if (name == null) { - return getId(); - } else { - return name; - } + return getRichName().toString(); } /* @@ -274,9 +276,7 @@ public class BlockType implements Keyed, Pattern { /** * Gets the legacy ID. Needed for legacy reasons. * - *

* DO NOT USE THIS. - *

* * @return legacy id or 0, if unknown */ @@ -286,11 +286,39 @@ public class BlockType implements Keyed, Pattern { return combinedId == null ? 0 : combinedId; } + /** + * Gets the legacy data. Needed for legacy reasons. + * + * DO NOT USE THIS. + * + * @return legacy data or 0, if unknown + */ @Deprecated public int getLegacyId() { return computeLegacy(0); } + /** + * Gets the legacy data. Needed for legacy reasons. + * + *

+ * DO NOT USE THIS. + *

+ * + * @return legacy data or 0, if unknown + */ + @Deprecated + public int getLegacyData() { + return computeLegacy(1); + } + + private int computeLegacy(int index) { + if (this.legacyCombinedId == null) { + this.legacyCombinedId = LegacyMapper.getInstance().getLegacyCombined(this.getDefaultState()); + } + return index == 0 ? legacyCombinedId >> 4 : legacyCombinedId & 15; + } + /** * The internal index of this type. * @@ -336,25 +364,4 @@ public class BlockType implements Keyed, Pattern { public SingleBlockTypeMask toMask(Extent extent) { return new SingleBlockTypeMask(extent, this); } - - /** - * Gets the legacy data. Needed for legacy reasons. - * - *

- * DO NOT USE THIS. - *

- * - * @return legacy data or 0, if unknown - */ - @Deprecated - public int getLegacyData() { - return computeLegacy(1); - } - - private int computeLegacy(int index) { - if (this.legacyCombinedId == null) { - this.legacyCombinedId = LegacyMapper.getInstance().getLegacyCombined(this.getDefaultState()); - } - return index == 0 ? legacyCombinedId >> 4 : legacyCombinedId & 15; - } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/BiomeRegistry.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/BiomeRegistry.java index 05e6b075a..3e18c4777 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/BiomeRegistry.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/BiomeRegistry.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.world.registry; +import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.biome.BiomeData; import com.sk89q.worldedit.world.biome.BiomeType; @@ -29,11 +30,21 @@ import javax.annotation.Nullable; */ public interface BiomeRegistry { + /** + * Get the name of the biome, usually as a translatable component. + * + * @param biomeType the biome type + * @return the name of the biome + */ + Component getRichName(BiomeType biomeType); + /** * Get data about a biome. * * @param biome the biome * @return a data object or null if information is not known + * @deprecated This method no longer returns any useful information. + * Use {@link #getRichName(BiomeType)} for the name of the biome. */ @Deprecated @Nullable diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/BlockRegistry.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/BlockRegistry.java index de439dcf9..e7bad3d86 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/BlockRegistry.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/BlockRegistry.java @@ -20,29 +20,41 @@ package com.sk89q.worldedit.world.registry; import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockType; +import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.OptionalInt; -import javax.annotation.Nullable; /** * Provides information on blocks and provides methods to create them. */ public interface BlockRegistry { + /** + * Gets the name for the given block. + * + * @param blockType the block + * @return The name + */ + Component getRichName(BlockType blockType); + /** * Gets the name for the given block. * * @param blockType the block * @return The name, or null if it's unknown + * @deprecated Names are now translatable, use {@link #getRichName(BlockType)}. */ @Deprecated @Nullable - String getName(BlockType blockType); + default String getName(BlockType blockType) { + return getRichName(blockType).toString(); + } /** * Get the material for the given block. diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/BundledBlockRegistry.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/BundledBlockRegistry.java index 435fb21c7..25e0b999b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/BundledBlockRegistry.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/BundledBlockRegistry.java @@ -20,6 +20,10 @@ package com.sk89q.worldedit.world.registry; import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.util.translation.TranslationManager; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockType; @@ -34,11 +38,19 @@ import javax.annotation.Nullable; */ public class BundledBlockRegistry implements BlockRegistry { - @Nullable @Override - public String getName(BlockType blockType) { + public Component getRichName(BlockType blockType) { BundledBlockData.BlockEntry blockEntry = BundledBlockData.getInstance().findById(blockType.getId()); - return blockEntry != null ? blockEntry.localizedName : null; + if (blockEntry != null) { + // This is more likely to be "right", but not translated + // Some vanilla MC blocks have overrides so we need this name here + // Most platforms should be overriding this anyways, so it likely doesn't matter + // too much! + return TextComponent.of(blockEntry.localizedName); + } + return TranslatableComponent.of( + TranslationManager.makeTranslationKey("block", blockType.getId()) + ); } @Nullable diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/ItemRegistry.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/ItemRegistry.java index 274dbf171..7458da4d9 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/ItemRegistry.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/ItemRegistry.java @@ -23,9 +23,9 @@ import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.item.ItemType; +import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; -import javax.annotation.Nullable; public interface ItemRegistry { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/NullBiomeRegistry.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/NullBiomeRegistry.java index b26e10fda..7951b25a0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/NullBiomeRegistry.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/NullBiomeRegistry.java @@ -19,6 +19,9 @@ package com.sk89q.worldedit.world.registry; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.util.translation.TranslationManager; import com.sk89q.worldedit.world.biome.BiomeData; import com.sk89q.worldedit.world.biome.BiomeType; @@ -35,6 +38,14 @@ public class NullBiomeRegistry implements BiomeRegistry { public NullBiomeRegistry() { } + @Override + public Component getRichName(BiomeType biomeType) { + return TranslatableComponent.of( + TranslationManager.makeTranslationKey("biome", biomeType.getId()) + ); + } + + @Deprecated @Nullable @Override public BiomeData getData(BiomeType biome) { diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json index 5e9d7f986..4bfae49f3 100644 --- a/worldedit-core/src/main/resources/lang/strings.json +++ b/worldedit-core/src/main/resources/lang/strings.json @@ -212,13 +212,13 @@ "worldedit.timeout.set": "Timeout time set to {0}ms.", "worldedit.timeout.return-to-default": " (Use //timeout to go back to the default.)", "worldedit.fast.disabled": "Fast mode disabled.", - "worldedit.fast.enabled": "Fast mode enabled. Lighting in the affected chunks may be wrong and/or you may need to rejoin to see changes.", + "worldedit.fast.enabled": "Fast mode enabled. Changes won't be written to history (//undo is disabled). Lighting in the affected chunks may be wrong and/or you may need to rejoin to see changes.", "worldedit.fast.disabled.already": "Fast mode already disabled.", "worldedit.fast.enabled.already": "Fast mode already enabled.", - "worldedit.fast.sideeffect.set": "Side effect \"{0}\" set to {1}", - "worldedit.fast.sideeffect.get": "Side effect \"{0}\" is set to {1}", - "worldedit.fast.sideeffect.already-set": "Side effect \"{0}\" is already {1}", - "worldedit.fast.sideeffect.set-all": "All side effects set to {0}", + "worldedit.perf.sideeffect.set": "Side effect \"{0}\" set to {1}", + "worldedit.perf.sideeffect.get": "Side effect \"{0}\" is set to {1}", + "worldedit.perf.sideeffect.already-set": "Side effect \"{0}\" is already {1}", + "worldedit.perf.sideeffect.set-all": "All side effects set to {0}", "worldedit.reorder.current": "The reorder mode is {0}", "worldedit.reorder.set": "The reorder mode is now {0}", "worldedit.gmask.disabled": "Global mask disabled.", @@ -282,9 +282,10 @@ "worldedit.hpos.no-block": "No block in sight!", "worldedit.hpos.already-set": "Position already set.", "worldedit.chunk.selected-multiple": "Chunks selected: ({0}, {1}) - ({2}, {3})", - "worldedit.chunk.selected": "Chunk selected: {0}, {1}", + "worldedit.chunk.selected": "Chunk selected: {0}, {1}, {2}", "worldedit.wand.invalid": "Wand item is mis-configured or disabled.", "worldedit.wand.selwand.info": "Left click: select pos #1; Right click: select pos #2", + "worldedit.wand.selwand.now.tool": "The selection wand is now a normal tool. You can disable it with {0} and rebind it to any item with {1} or get a new wand with {2}.", "worldedit.wand.navwand.info": "Left click: jump to location; Right click: pass through walls", "worldedit.contract.contracted": "Region contracted {0} blocks.", "worldedit.shift.shifted": "Region shifted.", @@ -341,6 +342,7 @@ "worldedit.remove.removed": "{0} entities have been marked for removal.", "worldedit.remove.explain-all": "Use -1 to remove all entities in loaded chunks", "worldedit.calc.invalid": "'{0}' could not be parsed as a valid expression", + "worldedit.calc.invalid.with-error": "'{0}' could not be parsed as a valid expression: '{1}'", "worldedit.paste.pasted": "The clipboard has been pasted at {0}", "worldedit.paste.selected": "Selected clipboard paste region.", @@ -430,7 +432,7 @@ "worldedit.tool.tree.equip": "Tree tool bound to {0}.", "worldedit.tool.tree.obstructed": "A tree can't go there.", "worldedit.tool.info.equip": "Info tool bound to {0}.", - "worldedit.tool.inspect.equip": "Inspect tool bound to {0}.", + "worldedit.tool.inspect.equip": "Inspect tool bound to {0}.", "worldedit.tool.info.blockstate.hover": "Block state", "worldedit.tool.info.internalid.hover": "Internal ID", "worldedit.tool.info.legacy.hover": "Legacy id:data", @@ -442,6 +444,7 @@ "worldedit.tool.farwand.equip": "Far wand tool bound to {0}.", "worldedit.tool.lrbuild.equip": "Long-range building tool bound to {0}.", "worldedit.tool.lrbuild.set": "Left-click set to {0}; right-click set to {1}.", + "worldedit.tool.stack.equip": "Stack tool bound to {0}.", "worldedit.tool.superpickaxe.mode.single": "Mode is now single. Left click with a pickaxe. // to disable.", "worldedit.tool.superpickaxe.mode.area": "Mode is now area. Left click with a pickaxe. // to disable.", diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java index 27ca1e513..6d39e20f7 100644 --- a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java @@ -51,6 +51,9 @@ class ExpressionTest extends BaseExpressionTest { // check functions testCase("sin(5)", sin(5)), testCase("atan2(3, 4)", atan2(3, 4)), + testCase("min(1, 2)", 1), + testCase("max(1, 2)", 2), + testCase("max(1, 2, 3, 4, 5)", 5), // check conditionals testCase("0 || 5", 5), testCase("2 || 5", 2), diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBiomeRegistry.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBiomeRegistry.java index ea2c48849..a005feaf8 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBiomeRegistry.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBiomeRegistry.java @@ -19,6 +19,8 @@ package com.sk89q.worldedit.fabric; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.world.biome.BiomeData; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.registry.BiomeRegistry; @@ -29,6 +31,12 @@ import net.minecraft.world.biome.Biome; */ class FabricBiomeRegistry implements BiomeRegistry { + @Override + public Component getRichName(BiomeType biomeType) { + return TranslatableComponent.of(FabricAdapter.adapt(biomeType).getTranslationKey()); + } + + @Deprecated @Override public BiomeData getData(BiomeType biome) { return new FabricBiomeData(FabricAdapter.adapt(biome)); @@ -37,6 +45,7 @@ class FabricBiomeRegistry implements BiomeRegistry { /** * Cached biome data information. */ + @Deprecated private static class FabricBiomeData implements BiomeData { private final Biome biome; @@ -49,6 +58,7 @@ class FabricBiomeRegistry implements BiomeRegistry { this.biome = biome; } + @SuppressWarnings("deprecation") @Override public String getName() { return biome.getName().asFormattedString(); diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBlockRegistry.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBlockRegistry.java index c54a27769..1d9e62eae 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBlockRegistry.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBlockRegistry.java @@ -20,17 +20,15 @@ package com.sk89q.worldedit.fabric; import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.registry.BlockMaterial; import com.sk89q.worldedit.world.registry.BundledBlockRegistry; -import net.fabricmc.api.EnvType; -import net.fabricmc.loader.api.FabricLoader; import net.minecraft.block.Block; import net.minecraft.block.Material; -import javax.annotation.Nullable; - import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -41,15 +39,9 @@ public class FabricBlockRegistry extends BundledBlockRegistry { private Map materialMap = new HashMap<>(); - @Nullable @Override - public String getName(BlockType blockType) { - Block block = FabricAdapter.adapt(blockType); - if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { - return block.getName().asFormattedString(); - } else { - return super.getName(blockType); - } + public Component getRichName(BlockType blockType) { + return TranslatableComponent.of(FabricAdapter.adapt(blockType).getTranslationKey()); } @Override diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricEntityProperties.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricEntityProperties.java index 2be4ebd02..32a76023d 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricEntityProperties.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricEntityProperties.java @@ -35,6 +35,7 @@ import net.minecraft.entity.decoration.ItemFrameEntity; import net.minecraft.entity.decoration.painting.PaintingEntity; import net.minecraft.entity.mob.AmbientEntity; import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.mob.WaterCreatureEntity; import net.minecraft.entity.passive.AnimalEntity; import net.minecraft.entity.passive.TameableEntity; import net.minecraft.entity.passive.GolemEntity; @@ -148,4 +149,9 @@ public class FabricEntityProperties implements EntityProperties { public boolean isPasteable() { return !(entity instanceof ServerPlayerEntity || entity instanceof EnderDragonEntity); } + + @Override + public boolean isWaterCreature() { + return entity instanceof WaterCreatureEntity; + } } diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricItemRegistry.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricItemRegistry.java index 6f19555c6..8218b932f 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricItemRegistry.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricItemRegistry.java @@ -19,24 +19,26 @@ package com.sk89q.worldedit.fabric; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BundledItemRegistry; -import net.fabricmc.api.EnvType; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.item.Item; - -import javax.annotation.Nullable; public class FabricItemRegistry extends BundledItemRegistry { - @Nullable @Override - public String getName(ItemType itemType) { - if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { - final Item item = FabricAdapter.adapt(itemType); - return I18n.translate(item.getTranslationKey()); - } - return super.getName(itemType); + public Component getRichName(ItemType itemType) { + return TranslatableComponent.of( + FabricAdapter.adapt(itemType).getTranslationKey() + ); } + + @Override + public Component getRichName(BaseItemStack itemStack) { + return TranslatableComponent.of( + FabricAdapter.adapt(itemStack).getTranslationKey() + ); + } + } diff --git a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeBiomeRegistry.java b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeBiomeRegistry.java index c8fa3f8da..38b24d78b 100644 --- a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeBiomeRegistry.java +++ b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeBiomeRegistry.java @@ -19,6 +19,8 @@ package com.sk89q.worldedit.forge; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.world.biome.BiomeData; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.registry.BiomeRegistry; @@ -29,6 +31,12 @@ import net.minecraft.world.biome.Biome; */ class ForgeBiomeRegistry implements BiomeRegistry { + @Override + public Component getRichName(BiomeType biomeType) { + return TranslatableComponent.of(ForgeAdapter.adapt(biomeType).getTranslationKey()); + } + + @Deprecated @Override public BiomeData getData(BiomeType biome) { return new ForgeBiomeData(ForgeAdapter.adapt(biome)); @@ -37,6 +45,7 @@ class ForgeBiomeRegistry implements BiomeRegistry { /** * Cached biome data information. */ + @Deprecated private static class ForgeBiomeData implements BiomeData { private final Biome biome; @@ -49,6 +58,7 @@ class ForgeBiomeRegistry implements BiomeRegistry { this.biome = biome; } + @SuppressWarnings("deprecation") @Override public String getName() { return biome.getDisplayName().getString(); diff --git a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeBlockRegistry.java b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeBlockRegistry.java index a6c3f45b9..cdb67bb57 100644 --- a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeBlockRegistry.java +++ b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeBlockRegistry.java @@ -20,6 +20,8 @@ package com.sk89q.worldedit.forge; import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.registry.BlockMaterial; @@ -27,9 +29,7 @@ import com.sk89q.worldedit.world.registry.BundledBlockRegistry; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.state.IProperty; -import net.minecraftforge.fml.loading.FMLLoader; -import javax.annotation.Nullable; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -40,15 +40,9 @@ public class ForgeBlockRegistry extends BundledBlockRegistry { private Map materialMap = new HashMap<>(); - @Nullable @Override - public String getName(BlockType blockType) { - Block block = ForgeAdapter.adapt(blockType); - if (block != null && FMLLoader.getDist().isClient()) { - return block.getNameTextComponent().getFormattedText(); - } else { - return super.getName(blockType); - } + public Component getRichName(BlockType blockType) { + return TranslatableComponent.of(ForgeAdapter.adapt(blockType).getTranslationKey()); } @Override diff --git a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeEntityProperties.java b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeEntityProperties.java index fa994dcb9..08a982dbd 100644 --- a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeEntityProperties.java +++ b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeEntityProperties.java @@ -41,6 +41,7 @@ import net.minecraft.entity.item.minecart.AbstractMinecartEntity; import net.minecraft.entity.passive.AmbientEntity; import net.minecraft.entity.passive.AnimalEntity; import net.minecraft.entity.passive.TameableEntity; +import net.minecraft.entity.passive.WaterMobEntity; import net.minecraft.entity.passive.GolemEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.ServerPlayerEntity; @@ -148,4 +149,9 @@ public class ForgeEntityProperties implements EntityProperties { public boolean isPasteable() { return !(entity instanceof ServerPlayerEntity || entity instanceof EnderDragonPartEntity); } + + @Override + public boolean isWaterCreature() { + return entity instanceof WaterMobEntity; + } } diff --git a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeItemRegistry.java b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeItemRegistry.java index 8bf23c6cb..873eab3cf 100644 --- a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeItemRegistry.java +++ b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeItemRegistry.java @@ -19,33 +19,26 @@ package com.sk89q.worldedit.forge; -import com.sk89q.worldedit.blocks.BaseItem; -import com.sk89q.worldedit.world.item.ItemTypes; -import com.sk89q.worldedit.world.registry.ItemRegistry; -import net.minecraft.item.Item; -import net.minecraft.util.ResourceLocation; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BundledItemRegistry; -import javax.annotation.Nullable; -import net.minecraft.client.resources.I18n; -import net.minecraft.item.Item; -import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.loading.FMLLoader; -import net.minecraftforge.registries.RegistryManager; - public class ForgeItemRegistry extends BundledItemRegistry { - @Nullable @Override - public String getName(ItemType itemType) { - if (FMLLoader.getDist().isClient()) { - final Item item = RegistryManager.ACTIVE.getRegistry(Item.class) - .getValue(ResourceLocation.tryCreate(itemType.getId())); - if (item != null) { - return I18n.format(item.getTranslationKey()); - } - } - return super.getName(itemType); + public Component getRichName(ItemType itemType) { + return TranslatableComponent.of( + ForgeAdapter.adapt(itemType).getTranslationKey() + ); } + + @Override + public Component getRichName(BaseItemStack itemStack) { + return TranslatableComponent.of( + ForgeAdapter.adapt(itemStack).getTranslationKey() + ); + } + } diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBiomeRegistry.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBiomeRegistry.java index dba2a904c..fd012d294 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBiomeRegistry.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBiomeRegistry.java @@ -19,6 +19,9 @@ package com.sk89q.worldedit.sponge; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.util.translation.TranslationManager; import com.sk89q.worldedit.world.biome.BiomeData; import com.sk89q.worldedit.world.registry.BiomeRegistry; import org.spongepowered.api.world.biome.BiomeType; @@ -30,12 +33,21 @@ import javax.annotation.Nullable; */ class SpongeBiomeRegistry implements BiomeRegistry { + @Override + public Component getRichName(com.sk89q.worldedit.world.biome.BiomeType biomeType) { + return TranslatableComponent.of( + TranslationManager.makeTranslationKey("biome", biomeType.getId()) + ); + } + + @Deprecated @Nullable @Override public BiomeData getData(com.sk89q.worldedit.world.biome.BiomeType biome) { return new SpongeBiomeData(SpongeAdapter.adapt(biome)); } + @Deprecated private static class SpongeBiomeData implements BiomeData { private final BiomeType biome; @@ -48,6 +60,7 @@ class SpongeBiomeRegistry implements BiomeRegistry { this.biome = biome; } + @SuppressWarnings("deprecation") @Override public String getName() { return biome.getName(); diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeEntityProperties.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeEntityProperties.java index c1fcb0f55..18766d98e 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeEntityProperties.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeEntityProperties.java @@ -149,4 +149,9 @@ public class SpongeEntityProperties implements EntityProperties { public boolean isPasteable() { return !(entity instanceof Player || entity instanceof ComplexLivingPart); } + + @Override + public boolean isWaterCreature() { + return false; // TODO api8 + } }