diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 83d9f52d8..8a134b81e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -32,7 +32,6 @@ body: - '1.19.4' - '1.18.2' - '1.17.1' - - '1.16.5' validations: required: true diff --git a/.github/renovate.json b/.github/renovate.json index 787e44030..526af5a60 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -19,8 +19,6 @@ "net.fabricmc:fabric-loader", "net.fabricmc.fabric-api:fabric-api", "com.github.luben:zstd-jni", - "net.kyori", - "net.kyori:adventure-nbt", "org.jetbrains.kotlin.jvm", "log4j" ], diff --git a/build.gradle.kts b/build.gradle.kts index 781f1fba2..3f6169bb7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,7 +34,7 @@ logger.lifecycle(""" ******************************************* """) -var rootVersion by extra("2.6.5") +var rootVersion by extra("2.7.1") var snapshot by extra("SNAPSHOT") var revision: String by extra("") var buildNumber by extra("") @@ -83,7 +83,7 @@ allprojects { } applyCommonConfiguration() -val supportedVersions = listOf("1.16.5", "1.17.1", "1.18.2", "1.19.4", "1.20", "1.20.1") +val supportedVersions = listOf("1.17.1", "1.18.2", "1.19.4", "1.20", "1.20.1") tasks { supportedVersions.forEach { diff --git a/buildSrc/src/main/kotlin/AdapterConfig.kt b/buildSrc/src/main/kotlin/AdapterConfig.kt index 9fb5595da..6cad58e3b 100644 --- a/buildSrc/src/main/kotlin/AdapterConfig.kt +++ b/buildSrc/src/main/kotlin/AdapterConfig.kt @@ -15,7 +15,7 @@ fun Project.applyPaperweightAdapterConfiguration() { dependencies { "implementation"(project(":worldedit-bukkit")) - "implementation"(platform("com.intellectualsites.bom:bom-1.18.x:1.9")) + "implementation"(platform("com.intellectualsites.bom:bom-newest:1.33")) } tasks.named("assemble") { diff --git a/buildSrc/src/main/kotlin/CommonJavaConfig.kt b/buildSrc/src/main/kotlin/CommonJavaConfig.kt index 8542fe252..73389ce83 100644 --- a/buildSrc/src/main/kotlin/CommonJavaConfig.kt +++ b/buildSrc/src/main/kotlin/CommonJavaConfig.kt @@ -45,7 +45,7 @@ fun Project.applyCommonJavaConfiguration(sourcesJar: Boolean, banSlf4j: Boolean "testImplementation"("org.mockito:mockito-core:5.1.1") "testImplementation"("org.mockito:mockito-junit-jupiter:5.1.1") "testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:5.9.2") - "implementation"(platform("com.intellectualsites.bom:bom-1.18.x:1.9")) + "implementation"(platform("com.intellectualsites.bom:bom-newest:1.33")) } // Java 8 turns on doclint which we fail diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4588bc54c..9ff63fb98 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,13 +11,13 @@ mapmanager = "1.8.0-SNAPSHOT" griefprevention = "16.18.1" griefdefender = "2.1.0-SNAPSHOT" residence = "4.5._13.1" -towny = "0.99.2.5" +towny = "0.99.5.4" # Third party bstats = "3.0.2" sparsebitset = "1.2" parallelgzip = "1.0.5" -adventure = "4.9.3" +adventure = "4.14.0" truezip = "6.8.4" auto-value = "1.10.2" findbugs = "3.0.2" @@ -30,7 +30,6 @@ jchronic = "0.2.4a" lz4-java = "1.8.0" lz4-stream = "1.0.0" ## Internal -adventure-text-minimessage = "4.2.0-SNAPSHOT" text-adapter = "3.0.6" text = "3.0.4" piston = "0.5.7" @@ -39,7 +38,7 @@ piston = "0.5.7" mockito = "5.4.0" # Gradle plugins -pluginyml = "0.5.3" +pluginyml = "0.6.0" [libraries] # Minecraft expectations diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79e..033e24c4c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d3f..9f4197d5f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aeb74cbb4..fcb6fca14 100755 --- a/gradlew +++ b/gradlew @@ -130,10 +130,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks.java index 85be84901..aab9e5aa7 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks.java @@ -445,7 +445,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc bitMask |= 1 << layer; - char[] setArr = set.load(layerNo); + // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to + // this chunk GET when #updateGet is called. Future dords, please listen this time. + char[] tmp = set.load(layerNo); + char[] setArr = new char[tmp.length]; + System.arraycopy(tmp, 0, setArr, 0, tmp.length); // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was // submitted to keep loaded internal chunks to queue target size. diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPlatformAdapter.java index 69ab5fe9b..688a84a14 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPlatformAdapter.java @@ -12,6 +12,7 @@ import com.fastasyncworldedit.core.util.ReflectionUtils; import com.fastasyncworldedit.core.util.TaskManager; import com.mojang.datafixers.util.Either; import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BlockState; @@ -48,6 +49,8 @@ import net.minecraft.world.level.chunk.Palette; import net.minecraft.world.level.chunk.PalettedContainer; import net.minecraft.world.level.gameevent.GameEventDispatcher; import net.minecraft.world.level.gameevent.GameEventListener; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_17_R1.CraftChunk; import sun.misc.Unsafe; @@ -61,6 +64,8 @@ import java.util.Locale; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Function; import java.util.stream.Stream; @@ -91,6 +96,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { private static final Field fieldRemove; + private static final Logger LOGGER = LogManagerCompat.getLogger(); + static { try { fieldBits = PalettedContainer.class.getDeclaredField(Refraction.pickName("bits", "l")); @@ -225,7 +232,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } CompletableFuture future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true); try { - CraftChunk chunk = (CraftChunk) future.get(); + CraftChunk chunk; + try { + chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS); + } catch (TimeoutException e) { + String world = serverLevel.getWorld().getName(); + // We've already taken 10 seconds we can afford to wait a little here. + boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null); + if (loaded) { + LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world); + // Retry chunk load + chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get(); + } else { + throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!"); + } + } return chunk.getHandle(); } catch (Throwable e) { e.printStackTrace(); diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks.java index 4e435e7ee..91a5abede 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks.java @@ -491,7 +491,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc bitMask |= 1 << getSectionIndex; - char[] setArr = set.load(layerNo); + // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to + // this chunk GET when #updateGet is called. Future dords, please listen this time. + char[] tmp = set.load(layerNo); + char[] setArr = new char[tmp.length]; + System.arraycopy(tmp, 0, setArr, 0, tmp.length); // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was // submitted to keep loaded internal chunks to queue target size. diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPlatformAdapter.java index 8e982a84e..dbf7f88f3 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPlatformAdapter.java @@ -13,17 +13,16 @@ import com.mojang.datafixers.util.Either; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypesCache; import io.papermc.lib.PaperLib; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.IdMap; import net.minecraft.core.Registry; -import net.minecraft.core.SectionPos; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; @@ -41,7 +40,6 @@ import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.GlobalPalette; import net.minecraft.world.level.chunk.HashMapPalette; @@ -51,8 +49,8 @@ import net.minecraft.world.level.chunk.LinearPalette; import net.minecraft.world.level.chunk.Palette; import net.minecraft.world.level.chunk.PalettedContainer; import net.minecraft.world.level.chunk.SingleValuePalette; -import net.minecraft.world.level.gameevent.GameEventDispatcher; -import net.minecraft.world.level.gameevent.GameEventListener; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_18_R2.CraftChunk; import sun.misc.Unsafe; @@ -75,6 +73,8 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Function; public final class PaperweightPlatformAdapter extends NMSAdapter { @@ -106,6 +106,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { private static final Field fieldRemove; + private static final Logger LOGGER = LogManagerCompat.getLogger(); + static { try { fieldData = PalettedContainer.class.getDeclaredField(Refraction.pickName("data", "d")); @@ -253,7 +255,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } CompletableFuture future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true); try { - CraftChunk chunk = (CraftChunk) future.get(); + CraftChunk chunk; + try { + chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS); + } catch (TimeoutException e) { + String world = serverLevel.getWorld().getName(); + // We've already taken 10 seconds we can afford to wait a little here. + boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null); + if (loaded) { + LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world); + // Retry chunk load + chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get(); + } else { + throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!"); + } + } return chunk.getHandle(); } catch (Throwable e) { e.printStackTrace(); diff --git a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightGetBlocks.java index ba1bfe574..c715e5fc2 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightGetBlocks.java @@ -332,6 +332,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc @Override public Set getEntities() { + ensureLoaded(serverLevel, chunkX, chunkZ); List entities = PaperweightPlatformAdapter.getEntities(getChunk()); if (entities.isEmpty()) { return Collections.emptySet(); @@ -490,7 +491,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc bitMask |= 1 << getSectionIndex; - char[] setArr = set.load(layerNo); + // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to + // this chunk GET when #updateGet is called. Future dords, please listen this time. + char[] tmp = set.load(layerNo); + char[] setArr = new char[tmp.length]; + System.arraycopy(tmp, 0, setArr, 0, tmp.length); // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was // submitted to keep loaded internal chunks to queue target size. diff --git a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightPlatformAdapter.java index 5af8f2806..d351bbacd 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightPlatformAdapter.java @@ -14,6 +14,7 @@ import com.mojang.datafixers.util.Either; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BlockState; @@ -43,7 +44,6 @@ import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.GlobalPalette; import net.minecraft.world.level.chunk.HashMapPalette; @@ -54,6 +54,8 @@ import net.minecraft.world.level.chunk.Palette; import net.minecraft.world.level.chunk.PalettedContainer; import net.minecraft.world.level.chunk.SingleValuePalette; import net.minecraft.world.level.entity.PersistentEntitySectionManager; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_19_R3.CraftChunk; import sun.misc.Unsafe; @@ -61,7 +63,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -77,9 +78,10 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Function; -import static java.lang.invoke.MethodType.methodType; import static net.minecraft.core.registries.Registries.BIOME; public final class PaperweightPlatformAdapter extends NMSAdapter { @@ -111,6 +113,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { private static final Field fieldRemove; + private static final Logger LOGGER = LogManagerCompat.getLogger(); + static final boolean POST_CHUNK_REWRITE; private static Method PAPER_CHUNK_GEN_ALL_ENTITIES; private static Field LEVEL_CHUNK_ENTITIES; @@ -287,7 +291,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } CompletableFuture future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true); try { - CraftChunk chunk = (CraftChunk) future.get(); + CraftChunk chunk; + try { + chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS); + } catch (TimeoutException e) { + String world = serverLevel.getWorld().getName(); + // We've already taken 10 seconds we can afford to wait a little here. + boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null); + if (loaded) { + LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world); + // Retry chunk load + chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get(); + } else { + throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!"); + } + } addTicket(serverLevel, chunkX, chunkZ); return (LevelChunk) chunk.getHandle(ChunkStatus.FULL); } catch (Throwable e) { diff --git a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightGetBlocks.java index 0dd292207..08d2f1069 100644 --- a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightGetBlocks.java @@ -310,6 +310,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc @Override public Set getEntities() { + ensureLoaded(serverLevel, chunkX, chunkZ); List entities = PaperweightPlatformAdapter.getEntities(getChunk()); if (entities.isEmpty()) { return Collections.emptySet(); @@ -468,7 +469,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc bitMask |= 1 << getSectionIndex; - char[] setArr = set.load(layerNo); + // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to + // this chunk GET when #updateGet is called. Future dords, please listen this time. + char[] tmp = set.load(layerNo); + char[] setArr = new char[tmp.length]; + System.arraycopy(tmp, 0, setArr, 0, tmp.length); // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was // submitted to keep loaded internal chunks to queue target size. diff --git a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightPlatformAdapter.java index f66e7dd6a..8cb8a8662 100644 --- a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightPlatformAdapter.java @@ -14,6 +14,7 @@ import com.mojang.datafixers.util.Either; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BlockState; @@ -54,6 +55,8 @@ import net.minecraft.world.level.chunk.Palette; import net.minecraft.world.level.chunk.PalettedContainer; import net.minecraft.world.level.chunk.SingleValuePalette; import net.minecraft.world.level.entity.PersistentEntitySectionManager; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_20_R1.CraftChunk; import sun.misc.Unsafe; @@ -77,6 +80,8 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Function; import static java.lang.invoke.MethodType.methodType; @@ -117,6 +122,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { private static final Field fieldRemove; + private static final Logger LOGGER = LogManagerCompat.getLogger(); + static final boolean POST_CHUNK_REWRITE; private static Method PAPER_CHUNK_GEN_ALL_ENTITIES; private static Field LEVEL_CHUNK_ENTITIES; @@ -307,7 +314,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } CompletableFuture future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true); try { - CraftChunk chunk = (CraftChunk) future.get(); + CraftChunk chunk; + try { + chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS); + } catch (TimeoutException e) { + String world = serverLevel.getWorld().getName(); + // We've already taken 10 seconds we can afford to wait a little here. + boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null); + if (loaded) { + LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world); + // Retry chunk load + chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get(); + } else { + throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!"); + } + } addTicket(serverLevel, chunkX, chunkZ); return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk); } catch (Throwable e) { diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index 9a9ffd86b..1962ff078 100644 --- a/worldedit-bukkit/build.gradle.kts +++ b/worldedit-bukkit/build.gradle.kts @@ -3,7 +3,7 @@ import io.papermc.paperweight.userdev.attribute.Obfuscation plugins { `java-library` - id("com.modrinth.minotaur") version "2.8.1" + id("com.modrinth.minotaur") version "2.8.2" } project.description = "Bukkit" @@ -101,8 +101,8 @@ dependencies { compileOnly(libs.griefdefender) { isTransitive = false } compileOnly(libs.residence) { isTransitive = false } compileOnly(libs.towny) { isTransitive = false } - compileOnly("com.plotsquared:PlotSquared-Bukkit") { isTransitive = false } - compileOnly("com.plotsquared:PlotSquared-Core") { isTransitive = false } + compileOnly("com.intellectualsites.plotsquared:plotsquared-bukkit") { isTransitive = false } + compileOnly("com.intellectualsites.plotsquared:plotsquared-core") { isTransitive = false } // Third party implementation("io.papermc:paperlib") @@ -183,7 +183,7 @@ tasks.named("shadowJar") { include(dependency("org.lz4:lz4-java:1.8.0")) } relocate("net.kyori", "com.fastasyncworldedit.core.adventure") { - include(dependency("net.kyori:adventure-nbt:4.9.3")) + include(dependency("net.kyori:adventure-nbt:4.14.0")) } relocate("com.zaxxer", "com.fastasyncworldedit.core.math") { include(dependency("com.zaxxer:SparseBitSet:1.2")) diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java index 46bf125c5..90db4368c 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java @@ -108,6 +108,14 @@ public class FaweBukkit implements IFawe, Listener { if (version.isEqualOrHigherThan(MinecraftVersion.CAVES_18) && Settings.settings().HISTORY.SMALL_EDITS) { LOGGER.warn("Small-edits enabled (maximum y range of 0 -> 256) with 1.18 world heights. Are you sure?"); } + + if (version.isEqualOrLowerThan(MinecraftVersion.ONE_DOT_SIXTEEN_EOL)) { + LOGGER.warn("You are running Minecraft 1.16.5. This version has been released over two years ago (January 2021)."); + LOGGER.warn("FastAsyncWorldEdit will stop operating on this version in the near future."); + LOGGER.warn("Neither Mojang, nor Spigot or other software vendors support this version anymore." + + "Please update your server to a newer version of Minecraft (1.20+) to continue receiving updates and " + + "support."); + } } @Override @@ -317,18 +325,9 @@ public class FaweBukkit implements IFawe, Listener { if (plotSquared == null) { return; } - if (PlotSquared.get().getVersion().version[0] == 6) { + if (PlotSquared.get().getVersion().version[0] == 7) { WEManager.weManager().addManager(new com.fastasyncworldedit.bukkit.regions.plotsquared.PlotSquaredFeature()); - LOGGER.info("Plugin 'PlotSquared' v6 found. Using it now."); - } else if (PlotSquared.get().getVersion().version[0] == 7) { - WEManager.weManager().addManager(new com.fastasyncworldedit.bukkit.regions.plotsquared.PlotSquaredFeature()); - LOGGER.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - LOGGER.error("!! !!"); - LOGGER.error("!! ERROR: PlotSquared v7 found. This FAWE version does not support PlotSquared V7 !!"); - LOGGER.error("!! Follow the instructions when notified of v7 release candidates and use FAWE from !!"); - LOGGER.error("!! https://ci.athion.net/job/FastAsyncWorldEdit-Pull-Requests/view/change-requests/job/PR-2075/ !!"); - LOGGER.error("!! !!"); - LOGGER.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + LOGGER.info("Plugin 'PlotSquared' v7 found. Using it now."); } else { LOGGER.error("Incompatible version of PlotSquared found. Please use PlotSquared v6."); LOGGER.info("https://www.spigotmc.org/resources/77506/"); diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/NMSAdapter.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/NMSAdapter.java index b41da1679..c146153fc 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/NMSAdapter.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/NMSAdapter.java @@ -30,8 +30,7 @@ public class NMSAdapter implements FAWEPlatformAdapterImpl { ordinal = BlockTypesCache.ReservedIDs.AIR; nonAir--; } - case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR, BlockTypesCache.ReservedIDs.VOID_AIR -> - nonAir--; + case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR, BlockTypesCache.ReservedIDs.VOID_AIR -> nonAir--; } int palette = blockToPalette[ordinal]; if (palette == Integer.MAX_VALUE) { @@ -74,8 +73,6 @@ public class NMSAdapter implements FAWEPlatformAdapterImpl { CachedBukkitAdapter adapter, short[] nonEmptyBlockCount ) { - // Write to new array to avoid editing SET array - char[] copy = new char[set.length]; short nonAir = 4096; int num_palette = 0; char[] getArr = null; @@ -86,19 +83,23 @@ public class NMSAdapter implements FAWEPlatformAdapterImpl { if (getArr == null) { getArr = get.apply(layer); } - switch (ordinal = getArr[i]) { + // write to set array as this should be a copied array, and will be important when the changes are written + // to the GET chunk cached by FAWE. Future dords, actually read this comment please. + set[i] = switch (ordinal = getArr[i]) { case BlockTypesCache.ReservedIDs.__RESERVED__ -> { nonAir--; - ordinal = BlockTypesCache.ReservedIDs.AIR; + yield (ordinal = BlockTypesCache.ReservedIDs.AIR); } - case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR, BlockTypesCache.ReservedIDs.VOID_AIR -> - nonAir--; - } + case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR, + BlockTypesCache.ReservedIDs.VOID_AIR -> { + nonAir--; + yield ordinal; + } + default -> ordinal; + }; } - case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR, BlockTypesCache.ReservedIDs.VOID_AIR -> - nonAir--; + case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR, BlockTypesCache.ReservedIDs.VOID_AIR -> nonAir--; } - copy[i] = ordinal; int palette = blockToPalette[ordinal]; if (palette == Integer.MAX_VALUE) { blockToPalette[ordinal] = num_palette; @@ -116,7 +117,7 @@ public class NMSAdapter implements FAWEPlatformAdapterImpl { System.arraycopy(adapter.getOrdinalToIbdID(), 0, blockToPalette, 0, adapter.getOrdinalToIbdID().length); } for (int i = 0; i < 4096; i++) { - char ordinal = copy[i]; + char ordinal = set[i]; if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { LOGGER.error("Empty (__RESERVED__) ordinal given where not expected, default to air."); ordinal = BlockTypesCache.ReservedIDs.AIR; diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateRegionManager.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateRegionManager.java index e20c73809..4ef1a3d05 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateRegionManager.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateRegionManager.java @@ -171,18 +171,17 @@ public class FaweDelegateRegionManager { .limitUnlimited() .changeSetNull() .build(); - File schematicFile = new File(hybridPlotWorld.getRoot(), "plot.schem"); + File schematicFile = new File(hybridPlotWorld.getSchematicRoot(), "plot.schem"); if (!schematicFile.exists()) { - schematicFile = new File(hybridPlotWorld.getRoot(), "plot.schematic"); + schematicFile = new File(hybridPlotWorld.getSchematicRoot(), "plot.schematic"); } - BlockVector3 to = plot.getBottomAbs().getBlockVector3().withY(Settings.Schematics.PASTE_ON_TOP - ? hybridPlotWorld.SCHEM_Y - : hybridPlotWorld.getMinBuildHeight()); + BlockVector3 to = plot.getBottomAbs().getBlockVector3().withY(hybridPlotWorld.getPlotYStart()); try { Clipboard clip = ClipboardFormats .findByFile(schematicFile) .getReader(new FileInputStream(schematicFile)) .read(); + clip.setOrigin(clip.getRegion().getMinimumPoint()); clip.paste(scheditsession, to, true, true, true); } catch (IOException e) { e.printStackTrace(); @@ -214,7 +213,7 @@ public class FaweDelegateRegionManager { ) { TaskManager.taskManager().async(() -> { synchronized (FaweDelegateRegionManager.class) { - //todo because of the following code this should proably be in the Bukkit module + //todo because of the following code this should probably be in the Bukkit module World pos1World = BukkitAdapter.adapt(getWorld(pos1.getWorldName())); World pos3World = BukkitAdapter.adapt(getWorld(swapPos.getWorldName())); EditSession sessionA = WorldEdit.getInstance().newEditSessionBuilder().world(pos1World) diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateSchematicHandler.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateSchematicHandler.java index 83de13f66..6cf09da28 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateSchematicHandler.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateSchematicHandler.java @@ -152,6 +152,7 @@ public class FaweDelegateSchematicHandler { final BlockVector3 to = BlockVector3 .at(region.getMinimumPoint().getX() + xOffset, y_offset_actual, region.getMinimumPoint().getZ() + zOffset); final Clipboard clipboard = schematic.getClipboard(); + clipboard.setOrigin(clipboard.getRegion().getMinimumPoint()); clipboard.paste(editSession, to, true, false, true); if (whenDone != null) { whenDone.value = true; diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweTrim.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweTrim.java index 8bfab4939..665fa47e4 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweTrim.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweTrim.java @@ -1,13 +1,15 @@ package com.fastasyncworldedit.bukkit.regions.plotsquared; import com.fastasyncworldedit.core.util.TaskManager; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.tag.Tag; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import com.plotsquared.core.PlotSquared; import com.plotsquared.core.command.CommandCategory; import com.plotsquared.core.command.CommandDeclaration; import com.plotsquared.core.command.RequiredType; import com.plotsquared.core.command.SubCommand; import com.plotsquared.core.configuration.caption.StaticCaption; -import com.plotsquared.core.configuration.caption.Templates; import com.plotsquared.core.configuration.caption.TranslatableCaption; import com.plotsquared.core.player.PlotPlayer; @@ -33,7 +35,7 @@ public class FaweTrim extends SubCommand { return false; } if (!PlotSquared.platform().worldUtil().isWorld(strings[0])) { - plotPlayer.sendMessage(TranslatableCaption.of("errors.not_valid_plot_world"), Templates.of("value", strings[0])); + plotPlayer.sendMessage(TranslatableCaption.of("errors.not_valid_plot_world"), TagResolver.resolver("value", Tag.inserting(Component.text(strings[0])))); return false; } ran = true; diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/PlotSetBiome.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/PlotSetBiome.java index cb007bfb3..ef903b716 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/PlotSetBiome.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/PlotSetBiome.java @@ -6,11 +6,9 @@ import com.plotsquared.core.command.CommandCategory; import com.plotsquared.core.command.CommandDeclaration; import com.plotsquared.core.command.MainCommand; import com.plotsquared.core.command.RequiredType; -import com.plotsquared.core.configuration.caption.Templates; import com.plotsquared.core.configuration.caption.TranslatableCaption; import com.plotsquared.core.player.PlotPlayer; import com.plotsquared.core.plot.Plot; -import com.plotsquared.core.util.Permissions; import com.plotsquared.core.util.StringMan; import com.plotsquared.core.util.task.RunnableVal2; import com.plotsquared.core.util.task.RunnableVal3; @@ -24,6 +22,9 @@ import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.biome.Biomes; import com.sk89q.worldedit.world.registry.BiomeRegistry; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.tag.Tag; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import org.bukkit.Bukkit; import java.util.Collection; @@ -56,7 +57,7 @@ public class PlotSetBiome extends Command { ) throws CommandException { final Plot plot = check(player.getCurrentPlot(), TranslatableCaption.of("errors.not_in_plot")); checkTrue( - plot.isOwner(player.getUUID()) || Permissions.hasPermission(player, "plots.admin.command.generatebiome"), + plot.isOwner(player.getUUID()) || player.hasPermission("plots.admin.command.generatebiome"), TranslatableCaption.of("permission.no_plot_perms") ); if (plot.getRunning() != 0) { @@ -64,7 +65,7 @@ public class PlotSetBiome extends Command { return null; } checkTrue(args.length == 1, TranslatableCaption.of("commandconfig.command_syntax"), - Templates.of("value", getUsage()) + TagResolver.resolver("value", Tag.inserting(Component.text(getUsage()))) ); final Set regions = plot.getRegions(); BiomeRegistry biomeRegistry = @@ -80,7 +81,7 @@ public class PlotSetBiome extends Command { player.sendMessage(TranslatableCaption.of("biome.need_biome")); player.sendMessage( TranslatableCaption.of("commandconfig.subcommand_set_options_header"), - Templates.of("values", biomes) + TagResolver.resolver("value", Tag.inserting(Component.text(biomes))) ); return CompletableFuture.completedFuture(false); } diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/PlotSquaredFeature.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/PlotSquaredFeature.java index 46ba12f68..e1ac20617 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/PlotSquaredFeature.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/PlotSquaredFeature.java @@ -23,9 +23,11 @@ import com.sk89q.worldedit.world.World; import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; +import javax.annotation.Nonnull; import java.lang.ref.WeakReference; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -40,7 +42,7 @@ public class PlotSquaredFeature extends FaweMaskManager { if (Settings.FAWE_Components.FAWE_HOOK) { Settings.Enabled_Components.WORLDEDIT_RESTRICTIONS = false; if (Settings.PLATFORM.toLowerCase(Locale.ROOT).startsWith("bukkit")) { - new FaweTrim(); + // new FaweTrim(); } // TODO: revisit this later on /* @@ -192,6 +194,10 @@ public class PlotSquaredFeature extends FaweMaskManager { maskedRegion = new RegionIntersection(world, weRegions); } + if (plot == null) { + return new FaweMask(maskedRegion); + } + return new PlotSquaredMask(maskedRegion, finalPlot); } @@ -201,9 +207,9 @@ public class PlotSquaredFeature extends FaweMaskManager { private final WeakReference> connectedPlots; private final boolean singlePlot; - private PlotSquaredMask(Region region, Plot plot) { + private PlotSquaredMask(@Nonnull Region region, @Nonnull Plot plot) { super(region); - this.plot = plot; + this.plot = Objects.requireNonNull(plot); Set connected = plot.getConnectedPlots(); connectedPlots = new WeakReference<>(connected); singlePlot = connected.size() == 1; @@ -211,8 +217,9 @@ public class PlotSquaredFeature extends FaweMaskManager { @Override public boolean isValid(Player player, MaskType type, boolean notify) { - if ((!connectedPlots.refersTo(plot.getConnectedPlots()) && !singlePlot) || (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone( - plot))) { + if ((!connectedPlots.refersTo(plot.getConnectedPlots()) && (!singlePlot || plot + .getConnectedPlots() + .size() > 1)) || (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot))) { return false; } return isAllowed(player, plot, type, notify); diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/MinecraftVersion.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/MinecraftVersion.java index 08ee52de6..f719d430d 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/MinecraftVersion.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/MinecraftVersion.java @@ -13,6 +13,7 @@ import java.util.regex.Pattern; public class MinecraftVersion implements Comparable { public static final MinecraftVersion NETHER = new MinecraftVersion(1, 16); + public static final MinecraftVersion ONE_DOT_SIXTEEN_EOL = new MinecraftVersion(1, 16, 5); public static final MinecraftVersion CAVES_17 = new MinecraftVersion(1, 17); public static final MinecraftVersion CAVES_18 = new MinecraftVersion(1, 18); private static MinecraftVersion current = null; diff --git a/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java b/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java index 9ef3d3894..981812e1b 100644 --- a/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java +++ b/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java @@ -19,6 +19,8 @@ package com.sk89q.wepif; +import com.destroystokyo.paper.profile.PlayerProfile; +import org.bukkit.BanEntry; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; @@ -30,10 +32,11 @@ import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionAttachment; import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.plugin.Plugin; -import org.bukkit.profile.PlayerProfile; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.annotation.Nonnull; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; @@ -158,6 +161,15 @@ public class TestOfflinePermissible implements OfflinePlayer, Permissible { throw new UnsupportedOperationException("Not supported yet."); } + @Override + public @Nullable BanEntry ban( + @Nullable final String reason, + @Nullable final Date expires, + @Nullable final String source + ) { + return null; + } + @Override public boolean isWhitelisted() { throw new UnsupportedOperationException("Not supported yet."); @@ -323,4 +335,9 @@ public class TestOfflinePermissible implements OfflinePlayer, Permissible { } + @Override + public @Nullable Location getLastDeathLocation() { + return null; + } + } diff --git a/worldedit-core/build.gradle.kts b/worldedit-core/build.gradle.kts index c9b34d5b1..4e624bd3b 100644 --- a/worldedit-core/build.gradle.kts +++ b/worldedit-core/build.gradle.kts @@ -28,14 +28,13 @@ dependencies { implementation("com.google.code.gson:gson") // Platform expectations - // TODO update bom-newest - implementation("org.yaml:snakeyaml:2.0") + implementation("org.yaml:snakeyaml") // Logging implementation("org.apache.logging.log4j:log4j-api") // Plugins - compileOnly("com.plotsquared:PlotSquared-Core") { isTransitive = false } + compileOnly("com.intellectualsites.plotsquared:plotsquared-core") { isTransitive = false } // ensure this is on the classpath for the AP annotationProcessor(libs.guava) diff --git a/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/MobSpawnerBlock.java b/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/MobSpawnerBlock.java index 929354ab9..0420d0994 100644 --- a/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/MobSpawnerBlock.java +++ b/worldedit-core/src/legacy/java/com/sk89q/worldedit/blocks/MobSpawnerBlock.java @@ -134,15 +134,29 @@ public class MobSpawnerBlock extends BaseBlock { values.put("MaxNearbyEntities", new ShortTag(maxNearbyEntities)); values.put("RequiredPlayerRange", new ShortTag(requiredPlayerRange)); if (spawnData == null) { - values.put("SpawnData", new CompoundTag(ImmutableMap.of("id", new StringTag(mobType)))); + values.put( + "SpawnData", + new CompoundTag(ImmutableMap.of("entity", new CompoundTag(ImmutableMap.of("id", new StringTag(mobType))))) + ); } else { values.put("SpawnData", new CompoundTag(spawnData.getValue())); } if (spawnPotentials == null) { - values.put("SpawnPotentials", new ListTag(CompoundTag.class, ImmutableList.of( - new CompoundTag(ImmutableMap.of("Weight", new IntTag(1), "Entity", - new CompoundTag(ImmutableMap.of("id", new StringTag(mobType))) - ))))); + values.put( + "SpawnPotentials", + new ListTag( + CompoundTag.class, + ImmutableList.of(new CompoundTag(ImmutableMap.of( + "weight", + new IntTag(1), + "data", + new CompoundTag(ImmutableMap.of( + "entity", + new CompoundTag(ImmutableMap.of("id", new StringTag(mobType))) + )) + ))) + ) + ); } else { values.put("SpawnPotentials", new ListTag(CompoundTag.class, spawnPotentials.getValue())); } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java index eb7eaf8ce..b4d2816c3 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java @@ -449,7 +449,6 @@ public class Fawe { * @return Executor used for clipboard IO if clipboard on disk is enabled or null * @since 2.6.2 */ - @Nullable public KeyQueuedExecutorService getClipboardExecutor() { return this.clipboardExecutor; } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/CommandBrush.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/CommandBrush.java index 4072828bf..227960c1d 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/CommandBrush.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/CommandBrush.java @@ -22,9 +22,27 @@ import java.util.List; public class CommandBrush implements Brush { private final String command; + private final boolean print; + /** + * New instance + * + * @deprecated Use {@link CommandBrush#CommandBrush(String, boolean)} + */ + @Deprecated(forRemoval = true) public CommandBrush(String command) { - this.command = command.charAt(0) == '/' ? "/" + command : command; + this(command, false); + } + + /** + * New instance + * + * @param command command to run, or commands split by ';' + * @param print if output should be printed to the actor for the run commands + */ + public CommandBrush(String command, boolean print) { + this.command = command; + this.print = print; } @Override @@ -36,7 +54,7 @@ public class CommandBrush implements Brush { position.subtract(radius, radius, radius), position.add(radius, radius, radius) ); - String replaced = command.replace("{x}", position.getBlockX() + "") + String replaced = command.replace("{x}", Integer.toString(position.getBlockX())) .replace("{y}", Integer.toString(position.getBlockY())) .replace("{z}", Integer.toString(position.getBlockZ())) .replace("{world}", editSession.getWorld().getName()) @@ -46,21 +64,22 @@ public class CommandBrush implements Brush { if (!(actor instanceof Player player)) { throw FaweCache.PLAYER_ONLY; } - //Use max world height to allow full coverage of the world height - Location face = player.getBlockTraceFace(editSession.getWorld().getMaxY(), true); - if (face == null) { - position = position.add(0, 1, 1); - } else { - position = position.add(face.getDirection().toBlockPoint()); - } player.setSelection(selector); - AsyncPlayer wePlayer = new SilentPlayerWrapper(new LocationMaskedPlayerWrapper( + AsyncPlayer wePlayer = new LocationMaskedPlayerWrapper( player, new Location(player.getExtent(), position.toVector3()) - )); + ); + if (!print) { + wePlayer = new SilentPlayerWrapper(wePlayer); + } List cmds = StringMan.split(replaced, ';'); for (String cmd : cmds) { - CommandEvent event = new CommandEvent(wePlayer, cmd); + if (cmd.isBlank()) { + continue; + } + cmd = cmd.charAt(0) != '/' ? "/" + cmd : cmd; + cmd = cmd.length() >1 && cmd.charAt(1) == '/' ? cmd.substring(1) : cmd; + CommandEvent event = new CommandEvent(wePlayer, cmd, editSession); PlatformCommandManager.getInstance().handleCommandOnCurrentThread(event); } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/ScatterCommand.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/ScatterCommand.java index 81be2640c..1d7841355 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/ScatterCommand.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/ScatterCommand.java @@ -3,7 +3,9 @@ package com.fastasyncworldedit.core.command.tool.brush; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.math.LocalBlockVectorSet; import com.fastasyncworldedit.core.util.StringMan; +import com.fastasyncworldedit.core.wrappers.AsyncPlayer; import com.fastasyncworldedit.core.wrappers.LocationMaskedPlayerWrapper; +import com.fastasyncworldedit.core.wrappers.SilentPlayerWrapper; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.entity.Player; @@ -13,7 +15,7 @@ import com.sk89q.worldedit.extension.platform.PlatformCommandManager; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; -import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.Location; import java.util.List; @@ -43,7 +45,7 @@ public class ScatterCommand extends ScatterBrush { position.subtract(radius, radius, radius), position.add(radius, radius, radius) ); - String replaced = command.replace("{x}", position.getBlockX() + "") + String replaced = command.replace("{x}", Integer.toString(position.getBlockX())) .replace("{y}", Integer.toString(position.getBlockY())) .replace("{z}", Integer.toString(position.getBlockZ())) .replace("{world}", editSession.getWorld().getName()) @@ -55,41 +57,22 @@ public class ScatterCommand extends ScatterBrush { } player.setSelection(selector); List cmds = StringMan.split(replaced, ';'); + AsyncPlayer wePlayer = new LocationMaskedPlayerWrapper( + player, + new Location(player.getExtent(), position.toVector3()) + ); + if (!print) { + wePlayer = new SilentPlayerWrapper(wePlayer); + } for (String cmd : cmds) { - Player p = print ? - new LocationMaskedPlayerWrapper(player, player.getLocation().setPosition(position.toVector3()), false) : - new ScatterCommandPlayerWrapper(player, position); - CommandEvent event = new CommandEvent(p, cmd, editSession); + if (cmd.isBlank()) { + continue; + } + cmd = cmd.charAt(0) != '/' ? "/" + cmd : cmd; + cmd = cmd.length() >1 && cmd.charAt(1) == '/' ? cmd.substring(1) : cmd; + CommandEvent event = new CommandEvent(wePlayer, cmd, editSession); PlatformCommandManager.getInstance().handleCommandOnCurrentThread(event); } } - private static final class ScatterCommandPlayerWrapper extends LocationMaskedPlayerWrapper { - - ScatterCommandPlayerWrapper(Player player, BlockVector3 position) { - super(player, player.getLocation().setPosition(position.toVector3()), false); - } - - @Override - public void print(String msg) { - } - - @Override - public void print(Component component) { - } - - @Override - public void printDebug(String msg) { - } - - @Override - public void printError(String msg) { - } - - @Override - public void printRaw(String msg) { - } - - } - } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/concurrent/ReentrantWrappedStampedLock.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/concurrent/ReentrantWrappedStampedLock.java deleted file mode 100644 index b8e726620..000000000 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/concurrent/ReentrantWrappedStampedLock.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.fastasyncworldedit.core.concurrent; - -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.StampedLock; - -/** - * Allows for reentrant behaviour of a wrapped {@link StampedLock}. Will not count the number of times it is re-entered. - * - * @since 2.3.0 - */ -public class ReentrantWrappedStampedLock implements Lock { - - private final StampedLock parent = new StampedLock(); - private volatile Thread owner; - private volatile long stamp = 0; - - @Override - public void lock() { - if (Thread.currentThread() == owner) { - return; - } - stamp = parent.writeLock(); - owner = Thread.currentThread(); - } - - @Override - public void lockInterruptibly() throws InterruptedException { - if (Thread.currentThread() == owner) { - return; - } - stamp = parent.writeLockInterruptibly(); - owner = Thread.currentThread(); - } - - @Override - public boolean tryLock() { - if (Thread.currentThread() == owner) { - return true; - } - if (parent.isWriteLocked()) { - return false; - } - stamp = parent.writeLock(); - owner = Thread.currentThread(); - return true; - } - - @Override - public boolean tryLock(final long time, @NotNull final TimeUnit unit) throws InterruptedException { - if (Thread.currentThread() == owner) { - return true; - } - if (!parent.isWriteLocked()) { - stamp = parent.writeLock(); - owner = Thread.currentThread(); - return true; - } - stamp = parent.tryWriteLock(time, unit); - owner = Thread.currentThread(); - return false; - } - - @Override - public void unlock() { - if (owner != Thread.currentThread()) { - throw new IllegalCallerException("The lock should only be unlocked by the owning thread when a stamp is not supplied"); - } - unlock(stamp); - } - - @NotNull - @Override - public Condition newCondition() { - throw new UnsupportedOperationException("Conditions are not supported by StampedLock"); - } - - /** - * Retrieves the stamp associated with the current lock. 0 if the wrapped {@link StampedLock} is not write-locked. This method is - * thread-checking. - * - * @return lock stam[ or 0 if not locked. - * @throws IllegalCallerException if the {@link StampedLock} is write-locked and the calling thread is not the lock owner - * @since 2.3.0 - */ - public long getStampChecked() { - if (stamp != 0 && owner != Thread.currentThread()) { - throw new IllegalCallerException("The stamp should be be acquired by a thread that does not own the lock"); - } - return stamp; - } - - /** - * Unlock the wrapped {@link StampedLock} using the given stamp. This can be called by any thread. - * - * @param stamp Stamp to unlock with - * @throws IllegalMonitorStateException if the given stamp does not match the lock's stamp - * @since 2.3.0 - */ - public void unlock(final long stamp) { - parent.unlockWrite(stamp); - this.stamp = 0; - owner = null; - } - - /** - * Returns true if the lock is currently held. - * - * @return true if the lock is currently held. - * @since 2.3.0 - */ - public boolean isLocked() { - return owner == null && this.stamp == 0 && parent.isWriteLocked(); // Be verbose - } - -} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java index bb5f16eb3..174d59f37 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java @@ -468,24 +468,6 @@ public class Settings extends Config { }) public int BUFFER_SIZE = 531441; - - @Comment({ - "The maximum time in milliseconds to wait for a chunk to load for an edit.", - " (50ms = 1 server tick, 0 = Fastest).", - " The default value of 100 should be safe for most cases.", - "", - "Actions which require loaded chunks (e.g. copy) which do not load in time", - " will use the last chunk as filler, which may appear as bands of duplicated blocks.", - "Actions usually wait about 25-50ms for the chunk to load, more if the server is lagging.", - "A value of 100ms does not force it to wait 100ms if the chunk loads in 10ms.", - "", - "This value is a timeout in case a chunk is never going to load (for whatever odd reason).", - "If the action times out, the operation continues by using the previous chunk as filler,", - " and displaying an error message. In this case, either copy a smaller section,", - " or increase chunk-wait-ms.", - "A value of 0 is faster simply because it doesn't bother loading the chunks or waiting.", - }) - public int CHUNK_WAIT_MS = 1000; @Comment("Delete history on disk after a number of days") public int DELETE_AFTER_DAYS = 7; @Comment("Delete history in memory on logout (does not effect disk)") @@ -493,6 +475,7 @@ public class Settings extends Config { @Comment({ "If history should be enabled by default for plugins using WorldEdit:", " - It is faster to have disabled", + " - It is faster to have disabled", " - Use of the FAWE API will not be effected" }) public boolean ENABLE_FOR_CONSOLE = true; @@ -515,10 +498,12 @@ public class Settings extends Config { @Create public static PROGRESS PROGRESS; + @Comment({ "This should equal the number of processors you have", }) public int PARALLEL_THREADS = Math.max(1, Runtime.getRuntime().availableProcessors()); + @Comment({ "When doing edits that effect more than this many chunks:", " - FAWE will start placing before all calculations are finished", @@ -530,14 +515,6 @@ public class Settings extends Config { }) public int TARGET_SIZE = 8 * Runtime.getRuntime().availableProcessors(); - @Comment({ - "Force FAWE to start placing chunks regardless of whether an edit is finished processing", - " - A larger value will use slightly less CPU time", - " - A smaller value will reduce memory usage", - " - A value too small may break some operations (deform?)" - }) - //TODO Find out where this was used and why the usage was removed - public int MAX_WAIT_MS = 1000; @Comment({ "Increase or decrease queue intensity (ms) [-50,50]:", @@ -566,13 +543,6 @@ public class Settings extends Config { }) public boolean POOL = true; - @Comment({ - "Discard edits which have been idle for a certain amount of time (ms)", - " - E.g. A plugin creates an EditSession but never does anything with it", - " - This only applies to plugins improperly using WorldEdit's legacy API" - }) - public int DISCARD_AFTER_MS = 60000; - @Comment({ "When using fastmode do not bother to tick existing/placed blocks/fluids", "Only works in versions up to 1.17.1" @@ -639,7 +609,7 @@ public class Settings extends Config { public boolean REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL = true; @Comment({ - "Other experimental features" + "Increased debug logging for brush actions and processor setup" }) public boolean OTHER = false; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/pattern/RichPatternParser.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/pattern/RichPatternParser.java index 3ebd84fa6..bf2027c9b 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/pattern/RichPatternParser.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/pattern/RichPatternParser.java @@ -117,7 +117,7 @@ public class RichPatternParser extends FaweParser { if (addBrackets) { value += "["; } - value += StringMan.join(entry.getValue(), " "); + value += StringMan.join(entry.getValue(), "]["); if (addBrackets) { value += "]"; } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/pattern/TypeSwapPatternParser.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/pattern/TypeSwapPatternParser.java new file mode 100644 index 000000000..5bf33d3a0 --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/pattern/TypeSwapPatternParser.java @@ -0,0 +1,54 @@ +package com.fastasyncworldedit.core.extension.factory.parser.pattern; + +import com.fastasyncworldedit.core.configuration.Caption; +import com.fastasyncworldedit.core.extension.factory.parser.RichParser; +import com.fastasyncworldedit.core.function.pattern.TypeSwapPattern; +import com.fastasyncworldedit.core.util.Permission; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.util.formatting.text.TextComponent; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.stream.Stream; + +public class TypeSwapPatternParser extends RichParser { + + private static final List SUGGESTIONS = List.of("oak", "spruce", "stone", "sandstone"); + + /** + * Create a new rich parser with a defined prefix for the result, e.g. {@code #simplex}. + * + * @param worldEdit the worldedit instance. + */ + public TypeSwapPatternParser(WorldEdit worldEdit) { + super(worldEdit, "#typeswap", "#ts", "#swaptype"); + } + + @Override + public Stream getSuggestions(String argumentInput, int index) { + if (index > 2) { + return Stream.empty(); + } + return SUGGESTIONS.stream(); + } + + @Override + public Pattern parseFromInput(@Nonnull String[] input, ParserContext context) throws InputParseException { + if (input.length != 2) { + throw new InputParseException(Caption.of( + "fawe.error.command.syntax", + TextComponent.of(getPrefix() + "[input][output] (e.g. " + getPrefix() + "[spruce][oak])") + )); + } + return new TypeSwapPattern( + context.requireExtent(), + input[0], + input[1], + Permission.hasPermission(context.requireActor(), "fawe.pattern.typeswap.regex") + ); + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/DiskOptimizedClipboard.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/DiskOptimizedClipboard.java index a9704e36b..3f7c746c3 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/DiskOptimizedClipboard.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/DiskOptimizedClipboard.java @@ -39,6 +39,8 @@ import java.net.URI; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -46,6 +48,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; /** * A clipboard with disk backed storage. (lower memory + loads on crash) @@ -59,6 +62,7 @@ public class DiskOptimizedClipboard extends LinearClipboard { private static final int HEADER_SIZE = 27; // Current header size private static final int VERSION_1_HEADER_SIZE = 22; // Header size of "version 1" private static final int VERSION_2_HEADER_SIZE = 27; // Header size of "version 2" i.e. when NBT/entities could be saved + private static final Map LOCK_HOLDER_CACHE = new ConcurrentHashMap<>(); private final HashMap nbtMap; private final File file; @@ -301,7 +305,23 @@ public class DiskOptimizedClipboard extends LinearClipboard { private void init() throws IOException { if (this.fileChannel == null) { this.fileChannel = braf.getChannel(); - this.fileChannel.lock(); + try { + FileLock lock = this.fileChannel.lock(); + LOCK_HOLDER_CACHE.put(file.getName(), new LockHolder(lock)); + } catch (OverlappingFileLockException e) { + LockHolder existing = LOCK_HOLDER_CACHE.get(file.getName()); + if (existing != null) { + long ms = System.currentTimeMillis() - existing.lockHeldSince; + LOGGER.error( + "Cannot lock clipboard file {} acquired by thread {}, {}ms ago", + file.getName(), + existing.thread, + ms + ); + } + // Rethrow to prevent clipboard access + throw e; + } this.byteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, braf.length()); } } @@ -737,4 +757,18 @@ public class DiskOptimizedClipboard extends LinearClipboard { return false; } + private static class LockHolder { + + final FileLock lock; + final long lockHeldSince; + final String thread; + + LockHolder(FileLock lock) { + this.lock = lock; + lockHeldSince = System.currentTimeMillis(); + this.thread = Thread.currentThread().getName(); + } + } + + } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/EntityInBlockRemovingProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/EntityInBlockRemovingProcessor.java index 0a8839409..58baf8633 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/EntityInBlockRemovingProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/EntityInBlockRemovingProcessor.java @@ -6,38 +6,46 @@ import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkSet; import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.block.BlockTypes; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; /** * Processor that removes existing entities that would not be in air after the edit * - * @since TODO + * @since 2.7.0 */ public class EntityInBlockRemovingProcessor implements IBatchProcessor { + private static final Logger LOGGER = LogManagerCompat.getLogger(); + @Override public IChunkSet processSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) { - for (CompoundTag tag : get.getEntities()) { - // Empty tags for seemingly non-existent entities can exist? - if (tag.getList("Pos").size() == 0) { - continue; - } - BlockVector3 pos = tag.getEntityPosition().toBlockPoint(); - int x = pos.getX() & 15; - int y = pos.getY(); - int z = pos.getZ() & 15; - if (!set.hasSection(y >> 4)) { - continue; - } - if (set.getBlock(x, y, z).getBlockType() != BlockTypes.__RESERVED__ && !set - .getBlock(x, y, z) - .getBlockType() - .getMaterial() - .isAir()) { - set.removeEntity(tag.getUUID()); + try { + for (CompoundTag tag : get.getEntities()) { + // Empty tags for seemingly non-existent entities can exist? + if (tag.getList("Pos").size() == 0) { + continue; + } + BlockVector3 pos = tag.getEntityPosition().toBlockPoint(); + int x = pos.getX() & 15; + int y = pos.getY(); + int z = pos.getZ() & 15; + if (!set.hasSection(y >> 4)) { + continue; + } + if (set.getBlock(x, y, z).getBlockType() != BlockTypes.__RESERVED__ && !set + .getBlock(x, y, z) + .getBlockType() + .getMaterial() + .isAir()) { + set.removeEntity(tag.getUUID()); + } } + } catch (Exception e) { + LOGGER.warn("Could not remove entities in blocks in chunk {},{}", chunk.getX(), chunk.getZ(), e); } return set; } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/IdMask.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/IdMask.java index e76c78a89..49c90183a 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/IdMask.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/IdMask.java @@ -5,9 +5,11 @@ import com.sk89q.worldedit.function.mask.AbstractExtentMask; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.math.BlockVector3; +import java.util.concurrent.atomic.AtomicInteger; + public class IdMask extends AbstractExtentMask implements ResettableMask { - private transient int id = -1; + private final AtomicInteger id = new AtomicInteger(-1); public IdMask(Extent extent) { super(extent); @@ -15,12 +17,9 @@ public class IdMask extends AbstractExtentMask implements ResettableMask { @Override public boolean test(Extent extent, BlockVector3 vector) { - if (id != -1) { - return extent.getBlock(vector).getInternalBlockTypeId() == id; - } else { - id = extent.getBlock(vector).getInternalBlockTypeId(); - return true; - } + int blockID = extent.getBlock(vector).getInternalBlockTypeId(); + int testId = id.compareAndExchange(-1, blockID); + return blockID == testId || testId == -1; } @Override @@ -30,12 +29,12 @@ public class IdMask extends AbstractExtentMask implements ResettableMask { @Override public void reset() { - this.id = -1; + this.id.set(-1); } @Override public Mask copy() { - return new IdMask(getExtent()); + return this; } @Override diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/pattern/TypeSwapPattern.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/pattern/TypeSwapPattern.java new file mode 100644 index 000000000..efc122b5d --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/pattern/TypeSwapPattern.java @@ -0,0 +1,103 @@ +package com.fastasyncworldedit.core.function.pattern; + +import com.fastasyncworldedit.core.extent.filter.block.FilterBlock; +import com.fastasyncworldedit.core.util.StringMan; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +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.block.BlockTypes; + +import java.util.regex.Pattern; + +/** + * Pattern that replaces blocks based on their ID, matching for an "input" and replacing with an "output" string. The "input" + * string may be regex. Keeps as much of the block state as possible, excluding NBT data. + * + * @since 2.7.0 + */ +public class TypeSwapPattern extends AbstractExtentPattern { + + private static final Pattern SPLITTER = Pattern.compile("[|,]"); + + private final String inputString; + private final String outputString; + private final String[] inputs; + private Pattern inputPattern = null; + + /** + * Create a new instance + * + * @param extent extent to use + * @param inputString string to replace. May be regex. + * @param outputString string to replace with + * @param allowRegex if regex should be allowed for input string matching + * @since 2.7.0 + */ + public TypeSwapPattern(Extent extent, String inputString, String outputString, boolean allowRegex) { + super(extent); + this.inputString = inputString; + this.outputString = outputString; + if (!StringMan.isAlphanumericUnd(inputString)) { + if (allowRegex) { + this.inputPattern = Pattern.compile(inputString.replace(",", "|")); + inputs = null; + } else { + inputs = SPLITTER.split(inputString); + } + } else { + inputs = null; + } + } + + @Override + public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { + BlockState existing = get.getBlock(extent); + BlockState newBlock = getNewBlock(existing); + if (newBlock == null) { + return false; + } + return set.setBlock(extent, newBlock); + } + + @Override + public void applyBlock(final FilterBlock block) { + BlockState existing = block.getBlock(); + BlockState newState = getNewBlock(existing); + if (newState != null) { + block.setBlock(newState); + } + } + + @Override + public BaseBlock applyBlock(final BlockVector3 position) { + BaseBlock existing = position.getFullBlock(getExtent()); + BlockState newState = getNewBlock(existing.toBlockState()); + return newState == null ? existing : newState.toBaseBlock(); + } + + private BlockState getNewBlock(BlockState existing) { + String oldId = existing.getBlockType().getId(); + String newId = oldId; + if (inputPattern != null) { + newId = inputPattern.matcher(oldId).replaceAll(outputString); + } else if (inputs != null && inputs.length > 0) { + for (String input : inputs) { + newId = newId.replace(input, outputString); + } + } else { + newId = oldId.replace(inputString, outputString); + } + if (newId.equals(oldId)) { + return null; + } + BlockType newType = BlockTypes.get(newId); + if (newType == null) { + return null; + } + return newType.getDefaultState().withProperties(existing); + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweException.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweException.java index fd74ac5de..7a71b831b 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweException.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweException.java @@ -41,7 +41,7 @@ public class FaweException extends RuntimeException { * New instance of a given {@link FaweException.Type} * * @param ignorable if an edit can continue if this exception is caught, e.g. by {@link com.fastasyncworldedit.core.extent.LimitExtent} - * @since TODO + * @since 2.7.0 */ public FaweException(Component reason, Type type, boolean ignorable) { this.message = reason; @@ -70,7 +70,7 @@ public class FaweException extends RuntimeException { /** * If an edit can continue if this exception is caught, e.g. by {@link com.fastasyncworldedit.core.extent.LimitExtent} * - * @since TODO + * @since 2.7.0 */ public boolean ignorable() { return ignorable; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java index 807a90fb4..5eeafe28e 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java @@ -85,7 +85,7 @@ public interface IBatchProcessor { } for (int layer = maxLayer; layer < set.getMaxSectionPosition(); layer++) { if (set.hasSection(layer)) { - if (layer == minLayer) { + if (layer == maxLayer) { char[] arr = set.loadIfPresent(layer); if (arr != null) { int index = ((maxY + 1) & 15) << 8; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkExtent.java index 63c793ebd..ec6162798 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkExtent.java @@ -2,9 +2,7 @@ package com.fastasyncworldedit.core.queue; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.DoubleTag; -import com.sk89q.jnbt.IntArrayTag; import com.sk89q.jnbt.ListTag; -import com.sk89q.jnbt.LongTag; import com.sk89q.jnbt.NBTUtils; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java index 7f604a277..014b94fce 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java @@ -27,6 +27,7 @@ import java.util.Queue; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.Future; @@ -52,6 +53,7 @@ public abstract class QueueHandler implements Trimable, Runnable { null, false ); + /** * Secondary queue should be used for "cleanup" tasks that are likely to be shorter in life than those submitted to the * primary queue. They may be IO-bound tasks. @@ -508,4 +510,28 @@ public abstract class QueueHandler implements Trimable, Runnable { return result; } + /** + * Primary queue should be used for tasks that are unlikely to wait on other tasks, IO, etc. (i.e. spend most of their + * time utilising CPU. + *

+ * Internal API usage only. + * + * @since 2.7.0 + */ + public ExecutorService getForkJoinPoolPrimary() { + return forkJoinPoolPrimary; + } + + /** + * Secondary queue should be used for "cleanup" tasks that are likely to be shorter in life than those submitted to the + * primary queue. They may be IO-bound tasks. + *

+ * Internal API usage only. + * + * @since 2.7.0 + */ + public ExecutorService getForkJoinPoolSecondary() { + return forkJoinPoolSecondary; + } + } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java index 59427c8ef..198782ee3 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java @@ -83,17 +83,6 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen this.maxY = maxY; } - /** - * Safety check to ensure that the thread being used matches the one being initialized on. - Can - * be removed later - */ - private void checkThread() { - if (Thread.currentThread() != currentThread && currentThread != null) { - throw new UnsupportedOperationException( - "This class must be used from a single thread. Use multiple queues for concurrent operations"); - } - } - @Override public void enableQueue() { enabledQueue = true; @@ -154,10 +143,10 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen return; } if (!this.chunks.isEmpty()) { + getChunkLock.lock(); for (IChunk chunk : this.chunks.values()) { chunk.recycle(); } - getChunkLock.lock(); this.chunks.clear(); getChunkLock.unlock(); } @@ -233,9 +222,21 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen */ private > V submitUnchecked(IQueueChunk chunk) { if (chunk.isEmpty()) { - chunk.recycle(); - Future result = Futures.immediateFuture(null); - return (V) result; + if (chunk instanceof ChunkHolder holder) { + long age = holder.initAge(); + // Ensure we've given time for the chunk to be used - it was likely used for a reason! + if (age < 5) { + try { + Thread.sleep(5 - age); + } catch (InterruptedException ignored) { + } + } + } + if (chunk.isEmpty()) { + chunk.recycle(); + Future result = Futures.immediateFuture(null); + return (V) result; + } } if (Fawe.isMainThread()) { @@ -451,6 +452,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen @Override public synchronized void flush() { if (!chunks.isEmpty()) { + getChunkLock.lock(); if (MemUtil.isMemoryLimited()) { for (IQueueChunk chunk : chunks.values()) { final Future future = submitUnchecked(chunk); @@ -467,7 +469,6 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen } } } - getChunkLock.lock(); chunks.clear(); getChunkLock.unlock(); } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java index 9172d8b3e..ec556d845 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java @@ -1,7 +1,6 @@ package com.fastasyncworldedit.core.queue.implementation.chunk; import com.fastasyncworldedit.core.FaweCache; -import com.fastasyncworldedit.core.concurrent.ReentrantWrappedStampedLock; import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.extent.filter.block.ChunkFilterBlock; import com.fastasyncworldedit.core.extent.processor.EmptyBatchProcessor; @@ -26,6 +25,8 @@ import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.Future; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * An abstract {@link IChunk} class that implements basic get/set blocks. @@ -43,7 +44,7 @@ public class ChunkHolder> implements IQueueChunk { return POOL.poll(); } - private final ReentrantWrappedStampedLock calledLock = new ReentrantWrappedStampedLock(); + private final Lock calledLock = new ReentrantLock(); private volatile IChunkGet chunkExisting; // The existing chunk (e.g. a clipboard, or the world, before changes) private volatile IChunkSet chunkSet; // The blocks to be set to the chunkExisting @@ -55,6 +56,7 @@ public class ChunkHolder> implements IQueueChunk { private int bitMask = -1; // Allow forceful setting of bitmask (for lighting) private boolean isInit = false; // Lighting handles queue differently. It relies on the chunk cache and not doing init. private boolean createCopy = false; + private long initTime = -1L; private ChunkHolder() { this.delegate = NULL; @@ -66,6 +68,7 @@ public class ChunkHolder> implements IQueueChunk { @Override public synchronized void recycle() { + calledLock.lock(); delegate = NULL; if (chunkSet != null) { chunkSet.recycle(); @@ -74,6 +77,11 @@ public class ChunkHolder> implements IQueueChunk { chunkExisting = null; extent = null; POOL.offer(this); + calledLock.unlock(); + } + + public long initAge() { + return System.currentTimeMillis() - initTime; } public synchronized IBlockDelegate getDelegate() { @@ -84,10 +92,10 @@ public class ChunkHolder> implements IQueueChunk { * If the chunk is currently being "called", this method will block until completed. */ private void checkAndWaitOnCalledLock() { - if (calledLock.isLocked()) { + if (!calledLock.tryLock()) { calledLock.lock(); - calledLock.unlock(); } + calledLock.unlock(); } @Override @@ -1024,6 +1032,7 @@ public class ChunkHolder> implements IQueueChunk { @Override public synchronized void init(IQueueExtent extent, int chunkX, int chunkZ) { + this.initTime = System.currentTimeMillis(); this.extent = extent; this.chunkX = chunkX; this.chunkZ = chunkZ; @@ -1040,14 +1049,15 @@ public class ChunkHolder> implements IQueueChunk { @Override public synchronized T call() { calledLock.lock(); - final long stamp = calledLock.getStampChecked(); if (chunkSet != null && !chunkSet.isEmpty()) { this.delegate = GET; chunkSet.setBitMask(bitMask); try { IChunkSet copy = chunkSet.createCopy(); chunkSet = null; - return this.call(copy, () -> calledLock.unlock(stamp)); + return this.call(copy, () -> { + // Do nothing + }); } catch (Throwable t) { calledLock.unlock(); throw t; @@ -1072,6 +1082,7 @@ public class ChunkHolder> implements IQueueChunk { } else { finalizer = finalize; } + calledLock.unlock(); return get.call(set, finalizer); } return null; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/regions/FaweMask.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/regions/FaweMask.java index 4ea8c2c06..f50c1a67e 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/regions/FaweMask.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/regions/FaweMask.java @@ -4,12 +4,15 @@ import com.fastasyncworldedit.core.extent.processor.ProcessorScope; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.regions.Region; +import javax.annotation.Nonnull; +import java.util.Objects; + public class FaweMask implements IDelegateRegion { private final Region region; - public FaweMask(Region region) { - this.region = region; + public FaweMask(@Nonnull Region region) { + this.region = Objects.requireNonNull(region); } @Override @@ -35,7 +38,7 @@ public class FaweMask implements IDelegateRegion { * @param type type of mask * @param notify if the player should be notified * @return if still valid - * @since TODO + * @since 2.7.0 */ public boolean isValid(Player player, FaweMaskManager.MaskType type, boolean notify) { return isValid(player, type); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/regions/FaweMaskManager.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/regions/FaweMaskManager.java index c52cc17e7..29136a178 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/regions/FaweMaskManager.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/regions/FaweMaskManager.java @@ -30,7 +30,7 @@ public abstract class FaweMaskManager { /** * Get a {@link FaweMask} for the given player and {@link MaskType}. If isWhitelist is false, will return a "blacklist" mask. * - * @since TODO + * @since 2.7.0 */ public FaweMask getMask(final Player player, MaskType type, boolean isWhitelist, boolean notify) { return getMask(player, type, isWhitelist); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ExtentTraverser.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ExtentTraverser.java index b4822863c..e7038deaa 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ExtentTraverser.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ExtentTraverser.java @@ -4,6 +4,7 @@ import com.sk89q.worldedit.extent.AbstractDelegateExtent; import com.sk89q.worldedit.extent.Extent; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.lang.reflect.Field; public class ExtentTraverser { @@ -24,6 +25,7 @@ public class ExtentTraverser { return root != null; } + @Nullable public T get() { return root; } @@ -50,6 +52,7 @@ public class ExtentTraverser { } @SuppressWarnings("unchecked") + @Nullable public U findAndGet(Class clazz) { ExtentTraverser traverser = find(clazz); return (traverser != null) ? (U) traverser.get() : null; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ProcessorTraverser.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ProcessorTraverser.java new file mode 100644 index 000000000..3c3a601a1 --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ProcessorTraverser.java @@ -0,0 +1,44 @@ +package com.fastasyncworldedit.core.util; + +import com.fastasyncworldedit.core.extent.processor.BatchProcessorHolder; +import com.fastasyncworldedit.core.extent.processor.MultiBatchProcessor; +import com.fastasyncworldedit.core.queue.IBatchProcessor; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import org.apache.logging.log4j.Logger; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayDeque; +import java.util.Queue; + +public class ProcessorTraverser { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + private final T root; + + public ProcessorTraverser(@Nonnull T root) { + this.root = root; + } + + public @Nullable U find(Class clazz) { + try { + Queue processors = new ArrayDeque<>(); + IBatchProcessor processor = root; + do { + if (clazz.isAssignableFrom(processor.getClass())) { + return clazz.cast(processor); + } else if (processor instanceof MultiBatchProcessor multiProcessor) { + processors.addAll(multiProcessor.getBatchProcessors()); + } else if (processor instanceof BatchProcessorHolder holder) { + processors.add(holder.getProcessor()); + } + } while ((processor = processors.poll()) != null); + return null; + } catch (Throwable e) { + LOGGER.error("Error traversing processors", e); + return null; + } + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/TaskManager.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/TaskManager.java index f3abf0ac3..176e02673 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/TaskManager.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/TaskManager.java @@ -88,7 +88,10 @@ public abstract class TaskManager { /** * Run a bunch of tasks in parallel using the shared thread pool. + * + * @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do. */ + @Deprecated(forRemoval = true, since = "2.7.0") public void parallel(Collection runables) { for (Runnable run : runables) { pool.submit(run); @@ -101,8 +104,9 @@ public abstract class TaskManager { * * @param runnables the tasks to run * @param numThreads number of threads (null = config.yml parallel threads) + * @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do. */ - @Deprecated + @Deprecated(forRemoval = true, since = "2.7.0") public void parallel(Collection runnables, @Nullable Integer numThreads) { if (runnables == null) { return; @@ -271,13 +275,17 @@ public abstract class TaskManager { }); } + /** + * @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do. + */ + @Deprecated(forRemoval = true, since = "2.7.0") public void wait(AtomicBoolean running, int timeout) { try { long start = System.currentTimeMillis(); synchronized (running) { while (running.get()) { running.wait(timeout); - if (running.get() && System.currentTimeMillis() - start > Settings.settings().QUEUE.DISCARD_AFTER_MS) { + if (running.get() && System.currentTimeMillis() - start > 60000) { new RuntimeException("FAWE is taking a long time to execute a task (might just be a symptom): ").printStackTrace(); LOGGER.info("For full debug information use: /fawe threads"); } @@ -288,6 +296,10 @@ public abstract class TaskManager { } } + /** + * @deprecated Deprecated without replacement as unused internally, and poor implementation of what it's designed to do. + */ + @Deprecated(forRemoval = true, since = "2.7.0") public void notify(AtomicBoolean running) { running.set(false); synchronized (running) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/UpdateNotification.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/UpdateNotification.java index d50bf7e02..13effcc46 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/UpdateNotification.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/UpdateNotification.java @@ -14,7 +14,13 @@ import org.w3c.dom.Document; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; -import java.net.URL; +import java.io.InputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.time.temporal.ChronoUnit; public class UpdateNotification { @@ -28,38 +34,54 @@ public class UpdateNotification { */ public static void doUpdateCheck() { if (Settings.settings().ENABLED_COMPONENTS.UPDATE_NOTIFICATIONS) { - try { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); - DocumentBuilder db = dbf.newDocumentBuilder(); - Document doc = db.parse(new URL("https://ci.plex.us.org/job/Plex-FAWE/api/xml/").openStream()); - faweVersion = doc.getElementsByTagName("lastSuccessfulBuild").item(0).getFirstChild().getTextContent(); - FaweVersion faweVersion = Fawe.instance().getVersion(); - if (faweVersion.build == 0) { - LOGGER.warn("You are using a snapshot or a custom version of FAWE. This is not an official build distributed " + - "via https://www.spigotmc.org/resources/13932/"); - return; - } - if (faweVersion.build < Integer.parseInt(UpdateNotification.faweVersion)) { - hasUpdate = true; - int versionDifference = Integer.parseInt(UpdateNotification.faweVersion) - faweVersion.build; - LOGGER.warn( - """ - An update for FastAsyncWorldEdit is available. You are {} build(s) out of date. - You are running build {}, the latest version is build {}. - Update at https://ci.plex.us.org/job/Plex-FAWE/""", - versionDifference, - faweVersion.build, - UpdateNotification.faweVersion - ); - } - } catch (Exception e) { - LOGGER.error("Unable to check for updates. Skipping."); - } + final HttpRequest request = HttpRequest + .newBuilder() + .uri(URI.create("https://ci.athion.net/job/FastAsyncWorldEdit/api/xml/")) + .timeout(Duration.of(10L, ChronoUnit.SECONDS)) + .build(); + HttpClient.newHttpClient() + .sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()) + .whenComplete((response, thrown) -> { + if (thrown != null) { + LOGGER.error("Update check failed: {} ", thrown.getMessage()); + } + processResponseBody(response.body()); + }); } } + private static void processResponseBody(InputStream body) { + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.parse(body); + faweVersion = doc.getElementsByTagName("lastSuccessfulBuild").item(0).getFirstChild().getTextContent(); + FaweVersion faweVersion = Fawe.instance().getVersion(); + if (faweVersion.build == 0) { + LOGGER.warn("You are using a snapshot or a custom version of FAWE. This is not an official build distributed " + + "via https://www.spigotmc.org/resources/13932/"); + return; + } + if (faweVersion.build < Integer.parseInt(UpdateNotification.faweVersion)) { + hasUpdate = true; + int versionDifference = Integer.parseInt(UpdateNotification.faweVersion) - faweVersion.build; + LOGGER.warn( + """ + An update for FastAsyncWorldEdit is available. You are {} build(s) out of date. + You are running build {}, the latest version is build {}. + Update at https://www.spigotmc.org/resources/13932/""", + versionDifference, + faweVersion.build, + UpdateNotification.faweVersion + ); + } + } catch (Exception ignored) { + LOGGER.error("Unable to check for updates. Skipping."); + } + } + /** * Trigger an update notification based on captions. Useful to notify server administrators ingame. * @@ -76,8 +98,8 @@ public class UpdateNotification { faweVersion.build, UpdateNotification.faweVersion, TextComponent - .of("https://ci.plex.us.org/job/Plex-FAWE/") - .clickEvent(ClickEvent.openUrl("https://ci.plex.us.org/job/Plex-FAWE/")) + .of("https://www.spigotmc.org/resources/13932/") + .clickEvent(ClickEvent.openUrl("https://www.spigotmc.org/resources/13932/")) )); } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/AsyncNotifyKeyedQueue.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/AsyncNotifyKeyedQueue.java new file mode 100644 index 000000000..cb6747cd2 --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/AsyncNotifyKeyedQueue.java @@ -0,0 +1,81 @@ +package com.fastasyncworldedit.core.util.task; + +import com.fastasyncworldedit.core.configuration.Settings; + +import java.io.Closeable; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.Future; +import java.util.function.Supplier; + +/** + * async queue that accepts a {@link Thread.UncaughtExceptionHandler} for exception handling per instance, delegating to a + * parent {@link KeyQueuedExecutorService}. + * + * @since 2.7.0 + */ +public class AsyncNotifyKeyedQueue implements Closeable { + + private static final KeyQueuedExecutorService QUEUE_SUBMISSIONS = new KeyQueuedExecutorService<>(new ForkJoinPool( + Settings.settings().QUEUE.PARALLEL_THREADS, + new FaweForkJoinWorkerThreadFactory("AsyncNotifyKeyedQueue - %s"), + null, + false + )); + + private final Thread.UncaughtExceptionHandler handler; + private final Supplier key; + private volatile boolean closed; + + /** + * New instance + * + * @param handler exception handler + * @param key supplier of UUID key + */ + public AsyncNotifyKeyedQueue(Thread.UncaughtExceptionHandler handler, Supplier key) { + this.handler = handler; + this.key = key; + } + + public Thread.UncaughtExceptionHandler getHandler() { + return handler; + } + + public Future run(Runnable task) { + return call(() -> { + task.run(); + return null; + }); + } + + public Future call(Callable task) { + Future[] self = new Future[1]; + Callable wrapped = () -> { + if (!closed) { + try { + return task.call(); + } catch (Throwable e) { + handler.uncaughtException(Thread.currentThread(), e); + } + } + if (self[0] != null) { + self[0].cancel(true); + } + return null; + }; + self[0] = QUEUE_SUBMISSIONS.submit(key.get(), wrapped); + return self[0]; + } + + @Override + public void close() { + closed = true; + } + + public boolean isClosed() { + return closed; + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/AsyncNotifyQueue.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/AsyncNotifyQueue.java index 7cd91f87a..9cf9ddef2 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/AsyncNotifyQueue.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/AsyncNotifyQueue.java @@ -1,13 +1,9 @@ package com.fastasyncworldedit.core.util.task; import com.fastasyncworldedit.core.Fawe; -import com.fastasyncworldedit.core.configuration.Settings; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.io.Closeable; import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -15,13 +11,6 @@ import java.util.function.Supplier; public class AsyncNotifyQueue implements Closeable { - private static final ForkJoinPool QUEUE_SUBMISSIONS = new ForkJoinPool( - Settings.settings().QUEUE.PARALLEL_THREADS, - new FaweForkJoinWorkerThreadFactory("AsyncNotifyQueue - %s"), - null, - false - ); - private final Lock lock = new ReentrantLock(true); private final Thread.UncaughtExceptionHandler handler; private boolean closed; @@ -56,9 +45,6 @@ public class AsyncNotifyQueue implements Closeable { return task.call(); } catch (Throwable e) { handler.uncaughtException(Thread.currentThread(), e); - if (self[0] != null) { - self[0].cancel(true); - } } } } finally { @@ -70,7 +56,7 @@ public class AsyncNotifyQueue implements Closeable { } return null; }; - self[0] = QUEUE_SUBMISSIONS.submit(wrapped); + self[0] = Fawe.instance().getQueueHandler().async(wrapped); return self[0]; } 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 f8a08148b..ecf859896 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -30,6 +30,7 @@ import com.fastasyncworldedit.core.extent.ResettableExtent; import com.fastasyncworldedit.core.extent.SingleRegionExtent; import com.fastasyncworldedit.core.extent.SourceMaskExtent; import com.fastasyncworldedit.core.extent.clipboard.WorldCopyClipboard; +import com.fastasyncworldedit.core.extent.processor.ExtentBatchProcessorHolder; import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter; import com.fastasyncworldedit.core.extent.processor.lighting.Relighter; import com.fastasyncworldedit.core.function.SurfaceRegionFunction; @@ -55,6 +56,7 @@ import com.fastasyncworldedit.core.queue.implementation.preloader.Preloader; import com.fastasyncworldedit.core.util.ExtentTraverser; import com.fastasyncworldedit.core.util.MaskTraverser; import com.fastasyncworldedit.core.util.MathMan; +import com.fastasyncworldedit.core.util.ProcessorTraverser; import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.collection.BlockVector3Set; import com.fastasyncworldedit.core.util.task.RunnableVal; @@ -524,9 +526,17 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { * @return mask, may be null */ public Mask getMask() { - //FAWE start - ExtendTraverser & MaskingExtents - ExtentTraverser maskingExtent = new ExtentTraverser<>(getExtent()).find(MaskingExtent.class); - return maskingExtent != null ? maskingExtent.get().getMask() : null; + //FAWE start - ExtentTraverser & MaskingExtents + MaskingExtent maskingExtent = new ExtentTraverser<>(getExtent()).findAndGet(MaskingExtent.class); + if (maskingExtent == null) { + ExtentBatchProcessorHolder processorExtent = + new ExtentTraverser<>(getExtent()).findAndGet(ExtentBatchProcessorHolder.class); + if (processorExtent != null) { + maskingExtent = + new ProcessorTraverser<>(processorExtent.getProcessor()).find(MaskingExtent.class); + } + } + return maskingExtent != null ? maskingExtent.getMask() : null; //FAWE end } @@ -609,23 +619,31 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { //FAWE start - use MaskingExtent & ExtentTraverser /** - * Set a mask. + * Set a mask. Combines with any existing masks, set null to clear existing masks. * * @param mask mask or null */ - public void setMask(Mask mask) { + public void setMask(@Nullable Mask mask) { if (mask == null) { mask = Masks.alwaysTrue(); } else { new MaskTraverser(mask).reset(this); } - ExtentTraverser maskingExtent = new ExtentTraverser<>(getExtent()).find(MaskingExtent.class); - if (maskingExtent != null && maskingExtent.get() != null) { - Mask oldMask = maskingExtent.get().getMask(); + MaskingExtent maskingExtent = new ExtentTraverser<>(getExtent()).findAndGet(MaskingExtent.class); + if (maskingExtent == null && mask != Masks.alwaysTrue()) { + ExtentBatchProcessorHolder processorExtent = + new ExtentTraverser<>(getExtent()).findAndGet(ExtentBatchProcessorHolder.class); + if (processorExtent != null) { + maskingExtent = + new ProcessorTraverser<>(processorExtent.getProcessor()).find(MaskingExtent.class); + } + } + if (maskingExtent != null) { + Mask oldMask = maskingExtent.getMask(); if (oldMask instanceof ResettableMask) { ((ResettableMask) oldMask).reset(); } - maskingExtent.get().setMask(mask); + maskingExtent.setMask(mask); } else if (mask != Masks.alwaysTrue()) { addProcessor(new MaskingExtent(getExtent(), mask)); } @@ -2270,6 +2288,90 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { //FAWE end } + /** + * Makes a cone. + * + * @param pos Center of the cone + * @param block The block pattern to use + * @param radiusX The cone's largest north/south extent + * @param radiusZ The cone's largest east/west extent + * @param height The cone's up/down extent. If negative, extend downward. + * @param filled If false, only a shell will be generated. + * @param thickness The cone's wall thickness, if it's hollow. + * @return number of blocks changed + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int makeCone( + BlockVector3 pos, + Pattern block, + double radiusX, + double radiusZ, + int height, + boolean filled, + double thickness + ) throws MaxChangedBlocksException { + int affected = 0; + + final int ceilRadiusX = (int) Math.ceil(radiusX); + final int ceilRadiusZ = (int) Math.ceil(radiusZ); + + double rx2 = Math.pow(radiusX, 2); + double ry2 = Math.pow(height, 2); + double rz2 = Math.pow(radiusZ, 2); + + int cx = pos.getX(); + int cy = pos.getY(); + int cz = pos.getZ(); + + for (int y = 0; y < height; ++y) { + double ySquaredMinusHeightOverHeightSquared = Math.pow(y - height, 2) / ry2; + int yy = cy + y; + forX: + for (int x = 0; x <= ceilRadiusX; ++x) { + double xSquaredOverRadiusX = Math.pow(x, 2) / rx2; + int xx = cx + x; + forZ: + for (int z = 0; z <= ceilRadiusZ; ++z) { + int zz = cz + z; + double zSquaredOverRadiusZ = Math.pow(z, 2) / rz2; + double distanceFromOriginMinusHeightSquared = xSquaredOverRadiusX + zSquaredOverRadiusZ - ySquaredMinusHeightOverHeightSquared; + + if (distanceFromOriginMinusHeightSquared > 1) { + if (z == 0) { + break forX; + } + break forZ; + } + + if (!filled) { + double xNext = Math.pow(x + thickness, 2) / rx2 + zSquaredOverRadiusZ - ySquaredMinusHeightOverHeightSquared; + double yNext = xSquaredOverRadiusX + zSquaredOverRadiusZ - Math.pow(y + thickness - height, 2) / ry2; + double zNext = xSquaredOverRadiusX + Math.pow(z + thickness, 2) / rz2 - ySquaredMinusHeightOverHeightSquared; + if (xNext <= 0 && zNext <= 0 && (yNext <= 0 && y + thickness != height)) { + continue; + } + } + + if (distanceFromOriginMinusHeightSquared <= 0) { + if (setBlock(xx, yy, zz, block)) { + ++affected; + } + if (setBlock(xx, yy, zz, block)) { + ++affected; + } + if (setBlock(xx, yy, zz, block)) { + ++affected; + } + if (setBlock(xx, yy, zz, block)) { + ++affected; + } + } + } + } + } + return affected; + } + /** * Move the blocks in a region a certain direction. * @@ -2991,9 +3093,10 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { } catch (ExpressionTimeoutException e) { timedOut[0] = timedOut[0] + 1; return null; + } catch (RuntimeException e) { + throw e; } catch (Exception e) { - LOGGER.warn("Failed to create shape", e); - return null; + throw new RuntimeException(e); } } }; 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 1ef2af6d6..3391bad80 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java @@ -23,8 +23,10 @@ import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.configuration.Caption; import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.extent.ResettableExtent; +import com.fastasyncworldedit.core.extent.clipboard.DiskOptimizedClipboard; import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder; import com.fastasyncworldedit.core.history.DiskStorageHistory; +import com.fastasyncworldedit.core.internal.exception.FaweClipboardVersionMismatchException; import com.fastasyncworldedit.core.internal.io.FaweInputStream; import com.fastasyncworldedit.core.internal.io.FaweOutputStream; import com.fastasyncworldedit.core.limit.FaweLimit; @@ -50,6 +52,8 @@ import com.sk89q.worldedit.command.tool.Tool; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Locatable; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; +import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.operation.ChangeSetExecutor; @@ -93,6 +97,7 @@ import java.util.ListIterator; import java.util.Objects; import java.util.TimeZone; import java.util.UUID; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -105,8 +110,8 @@ import static com.google.common.base.Preconditions.checkNotNull; */ public class LocalSession implements TextureHolder { - private static final transient int CUI_VERSION_UNINITIALIZED = -1; - public static transient int MAX_HISTORY_SIZE = 15; + public static int MAX_HISTORY_SIZE = 15; + private static final int CUI_VERSION_UNINITIALIZED = -1; // Non-session related fields private transient LocalConfiguration config; @@ -877,6 +882,58 @@ public class LocalSession implements TextureHolder { } } } + + /** + * Load a clipboard from disk and into this localsession. Synchronises with other clipboard setting/getting to and from + * this session + * + * @param file Clipboard file to load + * @throws FaweClipboardVersionMismatchException in clipboard version mismatch (between saved and internal, expected, version) + * @throws ExecutionException if the computation threw an exception + * @throws InterruptedException if the current thread was interrupted while waiting + */ + public void loadClipboardFromDisk(File file) throws FaweClipboardVersionMismatchException, ExecutionException, + InterruptedException { + synchronized (clipboardLock) { + if (file.exists() && file.length() > 5) { + try { + if (getClipboard() != null) { + return; + } + } catch (EmptyClipboardException ignored) { + } + DiskOptimizedClipboard doc = Fawe.instance().getClipboardExecutor().submit( + uuid, + () -> DiskOptimizedClipboard.loadFromFile(file) + ).get(); + Clipboard clip = doc.toClipboard(); + ClipboardHolder holder = new ClipboardHolder(clip); + setClipboard(holder); + } + } + } + + public void deleteClipboardOnDisk() { + synchronized (clipboardLock) { + ClipboardHolder holder = getExistingClipboard(); + if (holder != null) { + for (Clipboard clipboard : holder.getClipboards()) { + DiskOptimizedClipboard doc; + if (clipboard instanceof DiskOptimizedClipboard) { + doc = (DiskOptimizedClipboard) clipboard; + } else if (clipboard instanceof BlockArrayClipboard && ((BlockArrayClipboard) clipboard).getParent() instanceof DiskOptimizedClipboard) { + doc = (DiskOptimizedClipboard) ((BlockArrayClipboard) clipboard).getParent(); + } else { + continue; + } + Fawe.instance().getClipboardExecutor().submit(uuid, () -> { + doc.close(); // Ensure closed before deletion + doc.getFile().delete(); + }); + } + } + } + } //FAWE end /** 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 358f75413..76cb60c64 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 @@ -929,11 +929,13 @@ public class BrushCommands { @Arg(desc = "Expression") Expression radius, @Arg(desc = "Command to run") - List input + List input, + @Switch(name = 'p', desc = "Show any printed output") + boolean print ) throws WorldEditException { worldEdit.checkMaxBrushRadius(radius); String cmd = StringMan.join(input, " "); - set(context, new CommandBrush(cmd), "worldedit.brush.command").setSize(radius); + set(context, new CommandBrush(cmd, print), "worldedit.brush.command").setSize(radius); } @Command( diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java index 6ce86f455..c2779f28b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.command; +import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.FaweAPI; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.configuration.Caption; @@ -28,6 +29,7 @@ import com.fastasyncworldedit.core.extent.clipboard.DiskOptimizedClipboard; import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder; import com.fastasyncworldedit.core.extent.clipboard.ReadOnlyClipboard; import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder; +import com.fastasyncworldedit.core.internal.exception.FaweException; import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream; import com.fastasyncworldedit.core.limit.FaweLimit; import com.fastasyncworldedit.core.util.ImgurUtility; @@ -160,7 +162,7 @@ public class ClipboardCommands { session.getPlacementPosition(actor)); ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint()); copy.setCopyingEntities(copyEntities); - createCopy(session, editSession, copyBiomes, mask, clipboard, copy); + createCopy(actor, session, editSession, copyBiomes, mask, clipboard, copy); copy.getStatusMessages().forEach(actor::print); //FAWE end @@ -271,7 +273,7 @@ public class ClipboardCommands { copy.setSourceFunction(new BlockReplace(editSession, leavePattern)); copy.setCopyingEntities(copyEntities); copy.setRemovingEntities(true); - createCopy(session, editSession, copyBiomes, mask, clipboard, copy); + createCopy(actor, session, editSession, copyBiomes, mask, clipboard, copy); if (!actor.hasPermission("fawe.tips")) { actor.print(Caption.of("fawe.tips.tip.lazycut")); @@ -281,6 +283,7 @@ public class ClipboardCommands { } private void createCopy( + final Actor actor, final LocalSession session, final EditSession editSession, final boolean copyBiomes, @@ -311,9 +314,22 @@ public class ClipboardCommands { try { Operations.completeLegacy(copy); - } finally { - clipboard.flush(); + } catch (Exception e) { + DiskOptimizedClipboard doc; + if (clipboard instanceof DiskOptimizedClipboard) { + doc = (DiskOptimizedClipboard) clipboard; + } else if (clipboard instanceof BlockArrayClipboard && ((BlockArrayClipboard) clipboard).getParent() instanceof DiskOptimizedClipboard) { + doc = (DiskOptimizedClipboard) ((BlockArrayClipboard) clipboard).getParent(); + } else { + throw e; + } + Fawe.instance().getClipboardExecutor().submit(actor.getUniqueId(), () -> { + clipboard.close(); + doc.getFile().delete(); + }); + throw e; } + clipboard.flush(); session.setClipboard(new ClipboardHolder(clipboard)); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java index ca67ad132..dbd698b24 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java @@ -188,6 +188,49 @@ public class GenerationCommands { return affected; } + @Command( + name = "/cone", + desc = "Generates a cone." + ) + @CommandPermissions("worldedit.generation.cone") + @Logging(PLACEMENT) + public int cone(Actor actor, LocalSession session, EditSession editSession, + @Arg(desc = "The pattern of blocks to generate") + Pattern pattern, + @Arg(desc = "The radii of the cone. 1st is N/S, 2nd is E/W") + @Radii(2) + List radii, + @Arg(desc = "The height of the cone", def = "1") + int height, + @Switch(name = 'h', desc = "Make a hollow cone") + boolean hollow, + @Arg(desc = "Thickness of the hollow cone", def = "1") + double thickness + ) throws WorldEditException { + double radiusX; + double radiusZ; + switch (radii.size()) { + case 1 -> radiusX = radiusZ = Math.max(1, radii.get(0)); + case 2 -> { + radiusX = Math.max(1, radii.get(0)); + radiusZ = Math.max(1, radii.get(1)); + } + default -> { + actor.printError(Caption.of("worldedit.cone.invalid-radius")); + return 0; + } + } + + worldEdit.checkMaxRadius(radiusX); + worldEdit.checkMaxRadius(radiusZ); + worldEdit.checkMaxRadius(height); + + BlockVector3 pos = session.getPlacementPosition(actor); + int affected = editSession.makeCone(pos, pattern, radiusX, radiusZ, height, !hollow, thickness); + actor.printInfo(Caption.of("worldedit.cone.created", TextComponent.of(affected))); + return affected; + } + @Command( name = "/hsphere", desc = "Generates a hollow sphere." 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 f064868b4..aca13bc46 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 @@ -50,12 +50,19 @@ import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats; import com.sk89q.worldedit.function.EntityFunction; +import com.sk89q.worldedit.function.block.BlockReplace; import com.sk89q.worldedit.function.mask.BlockTypeMask; +import com.sk89q.worldedit.function.mask.BoundedHeightMask; import com.sk89q.worldedit.function.mask.ExistingBlockMask; import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.mask.MaskIntersection; +import com.sk89q.worldedit.function.mask.Masks; +import com.sk89q.worldedit.function.mask.RegionMask; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.function.visitor.DownwardVisitor; import com.sk89q.worldedit.function.visitor.EntityVisitor; +import com.sk89q.worldedit.function.visitor.RecursiveVisitor; import com.sk89q.worldedit.internal.annotation.Direction; import com.sk89q.worldedit.internal.annotation.VertHeight; import com.sk89q.worldedit.internal.expression.EvaluationException; @@ -63,8 +70,10 @@ import com.sk89q.worldedit.internal.expression.Expression; import com.sk89q.worldedit.internal.expression.ExpressionException; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector2; +import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.CylinderRegion; +import com.sk89q.worldedit.regions.EllipsoidRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.formatting.component.SubtleFormat; import com.sk89q.worldedit.util.formatting.text.Component; @@ -836,6 +845,55 @@ public class UtilityCommands { } } +// @Command( +// name = "/hollowr", +// desc = "Hollow out a space recursively with a pattern" +// ) +// @CommandPermissions("worldedit.hollowr") +// @Logging(PLACEMENT) +// public int hollowr( +// Actor actor, +// LocalSession session, +// EditSession editSession, +// @Arg(desc = "The radius to hollow out") Expression radiusExp, +// @ArgFlag(name = 'p', desc = "The blocks to fill with") Pattern pattern, +// @ArgFlag(name = 'm', desc = "The blocks remove", def = "") Mask mask +// ) throws WorldEditException { +// //FAWE start +// double radius = radiusExp.evaluate(); +// //FAWE end +// radius = Math.max(1, radius); +// we.checkMaxRadius(radius); +// if (mask == null) { +// Mask mask = new MaskIntersection( +// new RegionMask(new EllipsoidRegion(null, origin, Vector3.at(radius, radius, radius))), +// new BoundedHeightMask( +// Math.max(lowerBound, minY), +// Math.min(maxY, origin.getBlockY()) +// ), +// Masks.negate(new ExistingBlockMask(this)) +// ); +// } +// +// // Want to replace blocks +// BlockReplace replace = new BlockReplace(this, pattern); +// +// // Pick how we're going to visit blocks +// RecursiveVisitor visitor; +// //FAWE start - provide extent for preloading, min/max y +// if (recursive) { +// visitor = new RecursiveVisitor(mask, replace, (int) (radius * 2 + 1), minY, maxY, this); +// } else { +// visitor = new DownwardVisitor(mask, replace, origin.getBlockY(), (int) (radius * 2 + 1), minY, maxY, this); +// } +// //FAWE end +// +// BlockVector3 pos = session.getPlacementPosition(actor); +// int affected = editSession.res(pos, pattern, radius, depth, true); +// actor.print(Caption.of("worldedit.fillr.created", TextComponent.of(affected))); +// return affected; +// } + public static List> filesToEntry(final File root, final List files, final UUID uuid) { return files.stream() .map(input -> { // Keep this functional, as transform is evaluated lazily diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Player.java b/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Player.java index 8f5b5cb53..277d755b1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Player.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Player.java @@ -426,23 +426,7 @@ public interface Player extends Entity, Actor { cancel(true); LocalSession session = getSession(); if (Settings.settings().CLIPBOARD.USE_DISK && Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT) { - ClipboardHolder holder = session.getExistingClipboard(); - if (holder != null) { - for (Clipboard clipboard : holder.getClipboards()) { - DiskOptimizedClipboard doc; - if (clipboard instanceof DiskOptimizedClipboard) { - doc = (DiskOptimizedClipboard) clipboard; - } else if (clipboard instanceof BlockArrayClipboard && ((BlockArrayClipboard) clipboard).getParent() instanceof DiskOptimizedClipboard) { - doc = (DiskOptimizedClipboard) ((BlockArrayClipboard) clipboard).getParent(); - } else { - continue; - } - Fawe.instance().getClipboardExecutor().submit(getUniqueId(), () -> { - doc.close(); // Ensure closed before deletion - doc.getFile().delete(); - }); - } - } + session.deleteClipboardOnDisk(); } else if (Settings.settings().CLIPBOARD.USE_DISK) { Fawe.instance().getClipboardExecutor().submit(getUniqueId(), () -> session.setClipboard(null)); } else if (Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT) { @@ -464,22 +448,7 @@ public interface Player extends Entity, Actor { Settings.settings().PATHS.CLIPBOARD + File.separator + getUniqueId() + ".bd" ); try { - if (file.exists() && file.length() > 5) { - LocalSession session = getSession(); - try { - if (session.getClipboard() != null) { - return; - } - } catch (EmptyClipboardException ignored) { - } - DiskOptimizedClipboard doc = Fawe.instance().getClipboardExecutor().submit( - getUniqueId(), - () -> DiskOptimizedClipboard.loadFromFile(file) - ).get(); - Clipboard clip = doc.toClipboard(); - ClipboardHolder holder = new ClipboardHolder(clip); - session.setClipboard(holder); - } + getSession().loadClipboardFromDisk(file); } catch (FaweClipboardVersionMismatchException e) { print(e.getComponent()); } catch (RuntimeException e) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java index 420e58d2a..a76759833 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java @@ -42,6 +42,7 @@ import com.fastasyncworldedit.core.extension.factory.parser.pattern.OffsetPatter import com.fastasyncworldedit.core.extension.factory.parser.pattern.PerlinPatternParser; import com.fastasyncworldedit.core.extension.factory.parser.pattern.RandomFullClipboardPatternParser; import com.fastasyncworldedit.core.extension.factory.parser.pattern.RandomOffsetPatternParser; +import com.fastasyncworldedit.core.extension.factory.parser.pattern.TypeSwapPatternParser; import com.sk89q.worldedit.extension.factory.parser.pattern.RandomPatternParser; import com.fastasyncworldedit.core.extension.factory.parser.pattern.RelativePatternParser; import com.fastasyncworldedit.core.extension.factory.parser.pattern.RichPatternParser; @@ -133,6 +134,7 @@ public final class PatternFactory extends AbstractFactory { register(new SimplexPatternParser(worldEdit)); register(new SolidRandomOffsetPatternParser(worldEdit)); register(new SurfaceRandomOffsetPatternParser(worldEdit)); + register(new TypeSwapPatternParser(worldEdit)); register(new VoronoiPatternParser(worldEdit)); //FAWE end } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractNonPlayerActor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractNonPlayerActor.java index 0b8909198..8337a7327 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractNonPlayerActor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractNonPlayerActor.java @@ -19,10 +19,9 @@ package com.sk89q.worldedit.extension.platform; -import com.fastasyncworldedit.core.configuration.Caption; import com.fastasyncworldedit.core.internal.exception.FaweException; import com.fastasyncworldedit.core.util.TaskManager; -import com.fastasyncworldedit.core.util.task.AsyncNotifyQueue; +import com.fastasyncworldedit.core.util.task.AsyncNotifyKeyedQueue; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.internal.cui.CUIEvent; import com.sk89q.worldedit.util.formatting.text.TextComponent; @@ -68,7 +67,7 @@ public abstract class AbstractNonPlayerActor implements Actor { // Queue for async tasks private final AtomicInteger runningCount = new AtomicInteger(); - private final AsyncNotifyQueue asyncNotifyQueue = new AsyncNotifyQueue((thread, throwable) -> { + private final AsyncNotifyKeyedQueue asyncNotifyQueue = new AsyncNotifyKeyedQueue((thread, throwable) -> { while (throwable.getCause() != null) { throwable = throwable.getCause(); } @@ -82,7 +81,7 @@ public abstract class AbstractNonPlayerActor implements Actor { throwable.printStackTrace(); } } - }); + }, this::getUniqueId); /** * Run a task either async, or on the current thread. diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java index d02819dc6..ae4eb2144 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java @@ -25,7 +25,7 @@ import com.fastasyncworldedit.core.math.MutableBlockVector3; import com.fastasyncworldedit.core.regions.FaweMaskManager; import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.WEManager; -import com.fastasyncworldedit.core.util.task.AsyncNotifyQueue; +import com.fastasyncworldedit.core.util.task.AsyncNotifyKeyedQueue; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.WorldEdit; @@ -81,7 +81,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable { // Queue for async tasks private final AtomicInteger runningCount = new AtomicInteger(); - private final AsyncNotifyQueue asyncNotifyQueue = new AsyncNotifyQueue( + private final AsyncNotifyKeyedQueue asyncNotifyQueue = new AsyncNotifyKeyedQueue( (thread, throwable) -> { while (throwable.getCause() != null) { throwable = throwable.getCause(); @@ -96,7 +96,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable { throwable.printStackTrace(); } } - }); + }, this::getUniqueId); public AbstractPlayerActor(Map meta) { this.meta = meta; @@ -709,7 +709,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable { @Override public void checkPermission(String permission) throws AuthorizationException { if (!hasPermission(permission)) { - throw new AuthorizationException(); + throw new AuthorizationException(Caption.of("fawe.error.no-perm", permission)); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/session/SessionManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/session/SessionManager.java index d93a6b298..646b18bc0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/session/SessionManager.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/session/SessionManager.java @@ -45,8 +45,8 @@ import com.sk89q.worldedit.world.item.ItemTypes; import org.apache.logging.log4j.Logger; import javax.annotation.Nullable; -import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -354,7 +354,7 @@ public class SessionManager { @Subscribe public void onConfigurationLoad(ConfigurationLoadEvent event) { LocalConfiguration config = event.getConfiguration(); - File dir = new File(config.getWorkingDirectoryPath().toFile(), "sessions"); + Path dir = config.getWorkingDirectoryPath().resolve("sessions"); store = new JsonFileSessionStore(dir); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/session/storage/JsonFileSessionStore.java b/worldedit-core/src/main/java/com/sk89q/worldedit/session/storage/JsonFileSessionStore.java index b383bda21..eb4b1ee1a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/session/storage/JsonFileSessionStore.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/session/storage/JsonFileSessionStore.java @@ -36,6 +36,10 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.UUID; import static com.google.common.base.Preconditions.checkNotNull; @@ -49,20 +53,31 @@ public class JsonFileSessionStore implements SessionStore { private static final Logger LOGGER = LogManagerCompat.getLogger(); private final Gson gson; - private final File dir; + private final Path dir; + + /** + * Create a new session store. + * + * @param dir the directory + * @deprecated Use {@link #JsonFileSessionStore(Path)} + */ + @Deprecated + public JsonFileSessionStore(File dir) { + this(dir.toPath()); + } /** * Create a new session store. * * @param dir the directory */ - public JsonFileSessionStore(File dir) { + public JsonFileSessionStore(Path dir) { checkNotNull(dir); - if (!dir.isDirectory()) { - if (!dir.mkdirs()) { - LOGGER.warn("Failed to create directory '" + dir.getPath() + "' for sessions"); - } + try { + Files.createDirectories(dir); + } catch (IOException e) { + LOGGER.warn("Failed to create directory '" + dir + "' for sessions", e); } this.dir = dir; @@ -77,21 +92,19 @@ public class JsonFileSessionStore implements SessionStore { * @param id the ID * @return the file */ - private File getPath(UUID id) { + private Path getPath(UUID id) { checkNotNull(id); - return new File(dir, id + ".json"); + return dir.resolve(id + ".json"); } @Override public LocalSession load(UUID id) throws IOException { - File file = getPath(id); - try (Closer closer = Closer.create()) { - FileReader fr = closer.register(new FileReader(file)); - BufferedReader br = closer.register(new BufferedReader(fr)); - LocalSession session = gson.fromJson(br, LocalSession.class); + Path file = getPath(id); + try (var reader = Files.newBufferedReader(file)) { + LocalSession session = gson.fromJson(reader, LocalSession.class); if (session == null) { LOGGER.warn("Loaded a null session from {}, creating new session", file); - if (!file.delete()) { + if (!Files.deleteIfExists(file)) { LOGGER.warn("Failed to delete corrupted session {}", file); } session = new LocalSession(); @@ -99,7 +112,7 @@ public class JsonFileSessionStore implements SessionStore { return session; } catch (JsonParseException e) { throw new IOException(e); - } catch (FileNotFoundException e) { + } catch (NoSuchFileException e) { return new LocalSession(); } } @@ -107,29 +120,26 @@ public class JsonFileSessionStore implements SessionStore { @Override public void save(UUID id, LocalSession session) throws IOException { checkNotNull(session); - File finalFile = getPath(id); - File tempFile = new File(finalFile.getParentFile(), finalFile.getName() + ".tmp"); + Path finalFile = getPath(id); + Path tempFile = finalFile.getParent().resolve(finalFile.getFileName() + ".tmp"); - try (Closer closer = Closer.create()) { - FileWriter fr = closer.register(new FileWriter(tempFile)); - BufferedWriter bw = closer.register(new BufferedWriter(fr)); - gson.toJson(session, bw); + try (var writer = Files.newBufferedWriter(tempFile)) { + gson.toJson(session, writer); } catch (JsonIOException e) { throw new IOException(e); } - if (finalFile.exists()) { - if (!finalFile.delete()) { - LOGGER.warn("Failed to delete " + finalFile.getPath() + " so the .tmp file can replace it"); - } - } - - if (tempFile.length() == 0) { + if (Files.size(tempFile) == 0) { throw new IllegalStateException("Gson wrote zero bytes"); } - if (!tempFile.renameTo(finalFile)) { - LOGGER.warn("Failed to rename temporary session file to " + finalFile.getPath()); + try { + Files.move( + tempFile, finalFile, + StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING + ); + } catch (IOException e) { + LOGGER.warn("Failed to rename temporary session file to " + finalFile.toAbsolutePath(), e); } } 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 e5a466e29..7762ee532 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 @@ -86,7 +86,7 @@ public class BlockType implements Keyed, Pattern { * a specific requirement to actually create new block types, please contact the FAWE devs to discuss. Use * {@link BlockTypes#get(String)} instead. */ - @Deprecated(since = "TODO") + @Deprecated(since = "2.7.0") //FAWE end public BlockType(String id) { this(id, null); @@ -98,7 +98,7 @@ public class BlockType implements Keyed, Pattern { * a specific requirement to actually create new block types, please contact the FAWE devs to discuss. Use * {@link BlockTypes#get(String)} instead. */ - @Deprecated(since = "TODO") + @Deprecated(since = "2.7.0") //FAWE end public BlockType(String id, Function values) { // If it has no namespace, assume minecraft. diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemType.java index ecba92f8b..5a1e7653f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemType.java @@ -43,7 +43,7 @@ public class ItemType implements RegistryItem, Keyed { private final String id; @SuppressWarnings("deprecation") - private final LazyReference name = LazyReference.from(() -> { + private transient final LazyReference name = LazyReference.from(() -> { String name = GuavaUtil.firstNonNull( WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS) .getRegistries().getItemRegistry().getName(this), @@ -51,18 +51,18 @@ public class ItemType implements RegistryItem, Keyed { ); return name.isEmpty() ? getId() : name; }); - private final LazyReference richName = LazyReference.from(() -> + private transient final LazyReference richName = LazyReference.from(() -> WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS) .getRegistries().getItemRegistry().getRichName(this) ); - private final LazyReference itemMaterial = LazyReference.from(() -> + private transient final LazyReference itemMaterial = LazyReference.from(() -> WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS) .getRegistries().getItemRegistry().getMaterial(this) ); //FAWE start - private BlockType blockType; - private boolean initBlockType; - private BaseItem defaultState; + private transient BlockType blockType; + private transient boolean initBlockType; + private transient BaseItem defaultState; //FAWE end public ItemType(String id) { diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json index 790f7b6fa..5e922cdae 100644 --- a/worldedit-core/src/main/resources/lang/strings.json +++ b/worldedit-core/src/main/resources/lang/strings.json @@ -489,6 +489,8 @@ "worldedit.jumpto.none": "No block in sight (or too far away)!", "worldedit.up.obstructed": "You would hit something above you.", "worldedit.up.moved": "Woosh!", + "worldedit.cone.invalid-radius": "You must either specify 1 or 2 radius values.", + "worldedit.cone.created": "{0} blocks have been created.", "worldedit.cyl.invalid-radius": "You must either specify 1 or 2 radius values.", "worldedit.cyl.created": "{0} blocks have been created.", "worldedit.hcyl.thickness-too-large": "Thickness cannot be larger than x or z radii.",