From 1745c50878435cb24aeed98ee70f8caad9692b6c Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Sat, 15 Jul 2023 09:26:00 +0200 Subject: [PATCH 01/60] Improve error behaviour of //generate --- .../src/main/java/com/sk89q/worldedit/EditSession.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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..780ba90e1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -2991,9 +2991,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); } } }; From 8da530ee8044bf37ea129cab12c0df6a337bdbb8 Mon Sep 17 00:00:00 2001 From: dordsor21 Date: Sat, 15 Jul 2023 12:27:33 +0100 Subject: [PATCH 02/60] gracefully handle error in EntityInBlockRemovingProcessor - ensures edit continues on chunk correctly --- .../EntityInBlockRemovingProcessor.java | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) 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..1ffea1266 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,8 +6,10 @@ 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; /** @@ -17,27 +19,33 @@ import org.jetbrains.annotations.Nullable; */ 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; } From f4da4b0287d431b30cf39f77cf6089683bb38d32 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sat, 15 Jul 2023 16:40:29 +0100 Subject: [PATCH 03/60] fix: fix adding/getting masks to/from MaskExtent when it is a processor (#2351) - fixes #2350 --- .../core/util/ProcessorTraverser.java | 44 +++++++++++++++++++ .../java/com/sk89q/worldedit/EditSession.java | 32 ++++++++++---- 2 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ProcessorTraverser.java 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/sk89q/worldedit/EditSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java index 780ba90e1..03b838de6 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,15 @@ 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) { + ExtentTraverser processorExtent = + new ExtentTraverser<>(getExtent()).find(ExtentBatchProcessorHolder.class); + maskingExtent = + new ProcessorTraverser<>(processorExtent.get().getProcessor()).find(MaskingExtent.class); + } + return maskingExtent != null ? maskingExtent.getMask() : null; //FAWE end } @@ -609,23 +617,29 @@ 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()) { + ExtentTraverser processorExtent = + new ExtentTraverser<>(getExtent()).find(ExtentBatchProcessorHolder.class); + maskingExtent = + new ProcessorTraverser<>(processorExtent.get().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)); } From e6b1308590098db40ea346c8d2412d02dcdb35bc Mon Sep 17 00:00:00 2001 From: Jordan Date: Sat, 15 Jul 2023 16:40:52 +0100 Subject: [PATCH 04/60] feat: synchronise disk clipboard loading and deletion to clipboardLock (#2342) - another possible help towards the OverlappingFileLockException --- .../com/fastasyncworldedit/core/Fawe.java | 1 - .../com/sk89q/worldedit/LocalSession.java | 57 +++++++++++++++++++ .../com/sk89q/worldedit/entity/Player.java | 35 +----------- 3 files changed, 59 insertions(+), 34 deletions(-) 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/sk89q/worldedit/LocalSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java index dfa29a2e3..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; @@ -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/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) { From b1e0ad4ef7f39a5d376b374180f69930927168c3 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sat, 15 Jul 2023 16:41:04 +0100 Subject: [PATCH 05/60] feat: re-submit chunk load request after 10s after checking world is loaded (#2339) - #2332 makes it seem like paper forgets to load a chunk sometimes - resubmit chunk load request after a second to attempt to counter this --- .../PaperweightPlatformAdapter.java | 23 ++++++++++++++- .../v1_18_R2/PaperweightPlatformAdapter.java | 28 +++++++++++++++---- .../v1_19_R3/PaperweightPlatformAdapter.java | 26 ++++++++++++++--- .../v1_20_R1/PaperweightPlatformAdapter.java | 23 ++++++++++++++- 4 files changed, 88 insertions(+), 12 deletions(-) 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/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/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/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) { From e0507e64402315f4829aa077cf30d8428ea6ab0e Mon Sep 17 00:00:00 2001 From: Chris Lang Date: Sat, 15 Jul 2023 00:22:45 -0700 Subject: [PATCH 06/60] Added cone generation command (#2251) * Added cone generation command * Fix formatting problems --------- Co-authored-by: Madeline Miller --- .../java/com/sk89q/worldedit/EditSession.java | 84 +++++++++++++++++++ .../worldedit/command/GenerationCommands.java | 43 ++++++++++ .../src/main/resources/lang/strings.json | 2 + 3 files changed, 129 insertions(+) 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 03b838de6..f60a1cbf2 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -2284,6 +2284,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. * 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/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.", From 51670733262407ef964debde6fb84502fdefe700 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 17 Jul 2023 21:47:47 +0100 Subject: [PATCH 07/60] fix: regions not trimming Y correctly when restricted (#2363) --- .../java/com/fastasyncworldedit/core/queue/IBatchProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From cdd546ee5e1a3f010b8daf8674c830925c197a03 Mon Sep 17 00:00:00 2001 From: Jordan Date: Thu, 20 Jul 2023 16:55:59 +0100 Subject: [PATCH 08/60] fix: correct NBT for spawners (#2362) --- .../worldedit/blocks/MobSpawnerBlock.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) 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())); } From f65c4743bda5d9cea218c7f7edbf324042a9dcf0 Mon Sep 17 00:00:00 2001 From: Jordan Date: Thu, 20 Jul 2023 16:56:11 +0100 Subject: [PATCH 09/60] fix: make IdMask thread-safe (#2361) --- .../core/function/mask/IdMask.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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 From fe1859e9d23caba0462593c8d26f90aa9edf9fe4 Mon Sep 17 00:00:00 2001 From: Jordan Date: Thu, 20 Jul 2023 16:56:30 +0100 Subject: [PATCH 10/60] fix: do not retain clipboards that completed exceptionally (#2358) --- .../worldedit/command/ClipboardCommands.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) 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 40ca530c8..e39bbc641 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)); } From 0a8a4792143c692acc4bd3f64273b1767990f06e Mon Sep 17 00:00:00 2001 From: Jordan Date: Thu, 20 Jul 2023 16:57:29 +0100 Subject: [PATCH 11/60] fix: do not clash ChunkHolder recycling with processors that extend outside the chunk (#2353) --- .../ReentrantWrappedStampedLock.java | 119 ------------------ .../core/queue/IChunkExtent.java | 2 - .../SingleThreadQueueExtent.java | 33 ++--- .../implementation/chunk/ChunkHolder.java | 23 +++- 4 files changed, 34 insertions(+), 143 deletions(-) delete mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/concurrent/ReentrantWrappedStampedLock.java 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/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/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; From 18df87a4e8b2f3da815ecd3ce0e4a4ad36240f37 Mon Sep 17 00:00:00 2001 From: Jordan Date: Thu, 20 Jul 2023 16:57:48 +0100 Subject: [PATCH 12/60] feat: implement a new "type swap" pattern (#2346) --- .../parser/pattern/TypeSwapPatternParser.java | 54 +++++++++ .../function/pattern/TypeSwapPattern.java | 103 ++++++++++++++++++ .../extension/factory/PatternFactory.java | 2 + 3 files changed, 159 insertions(+) create mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/pattern/TypeSwapPatternParser.java create mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/function/pattern/TypeSwapPattern.java 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/function/pattern/TypeSwapPattern.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/pattern/TypeSwapPattern.java new file mode 100644 index 000000000..89876f658 --- /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 TODO + */ +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 TODO + */ + 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/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 } From 9543adc776c1afcb81477e2113920f9746ea88a6 Mon Sep 17 00:00:00 2001 From: Jordan Date: Thu, 20 Jul 2023 16:58:01 +0100 Subject: [PATCH 13/60] Implement async notify queue that submits to a KeyQueuedExecutorService (#2334) --- .../queue/implementation/QueueHandler.java | 26 ++++++ .../core/util/task/AsyncNotifyKeyedQueue.java | 81 +++++++++++++++++++ .../core/util/task/AsyncNotifyQueue.java | 16 +--- .../platform/AbstractNonPlayerActor.java | 7 +- .../platform/AbstractPlayerActor.java | 6 +- 5 files changed, 114 insertions(+), 22 deletions(-) create mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/AsyncNotifyKeyedQueue.java 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..bc7f9ddec 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 TODO + */ + 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 TODO + */ + public ExecutorService getForkJoinPoolSecondary() { + return forkJoinPoolSecondary; + } + } 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..9df6b197c --- /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 TODO + */ +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/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 6adf7236c..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; From 4bdcf9a510baf2df82e18f7e69ba3d5d85c8cfdb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 22 Jul 2023 10:00:15 +0000 Subject: [PATCH 14/60] Update dependency com.palmergames.bukkit.towny:towny to v0.99.5.4 (#2372) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cf04ae8f8..e1d802e37 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ mapmanager = "1.8.0-SNAPSHOT" griefprevention = "16.18.1" griefdefender = "2.1.0-SNAPSHOT" residence = "4.5._13.1" -towny = "0.99.2.7" +towny = "0.99.5.4" # Third party bstats = "3.0.2" From c91b477e2969239b35bfa84977a75e4fe0c1c8a3 Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Sat, 22 Jul 2023 12:01:33 +0200 Subject: [PATCH 15/60] Update PlotSquared integration to v7 (#2075) Co-authored-by: dordsor21 --- .github/renovate.json | 2 -- buildSrc/src/main/kotlin/AdapterConfig.kt | 2 +- buildSrc/src/main/kotlin/CommonJavaConfig.kt | 2 +- gradle/libs.versions.toml | 3 +-- worldedit-bukkit/build.gradle.kts | 6 +++--- .../fastasyncworldedit/bukkit/FaweBukkit.java | 13 ++----------- .../FaweDelegateRegionManager.java | 10 ++++------ .../bukkit/regions/plotsquared/FaweTrim.java | 6 ++++-- .../regions/plotsquared/PlotSetBiome.java | 11 ++++++----- .../plotsquared/PlotSquaredFeature.java | 2 +- .../sk89q/wepif/TestOfflinePermissible.java | 19 ++++++++++++++++++- worldedit-core/build.gradle.kts | 5 ++--- 12 files changed, 43 insertions(+), 38 deletions(-) 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/buildSrc/src/main/kotlin/AdapterConfig.kt b/buildSrc/src/main/kotlin/AdapterConfig.kt index 21cab4c73..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.31")) + "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 759e45740..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.31")) + "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 e1d802e37..9ff63fb98 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ towny = "0.99.5.4" 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" diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index 9a9ffd86b..a6d31be03 100644 --- a/worldedit-bukkit/build.gradle.kts +++ b/worldedit-bukkit/build.gradle.kts @@ -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 2e25c8c56..90db4368c 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java @@ -325,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/regions/plotsquared/FaweDelegateRegionManager.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateRegionManager.java index 66a9ef369..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,13 +171,11 @@ 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) @@ -215,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/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 a34fac51f..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 @@ -42,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 /* 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) From 34a98a03c1c43440ea93c6042763b7d1bf5f3aa0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 22 Jul 2023 10:02:22 +0000 Subject: [PATCH 16/60] Update dependency gradle to v8.2.1 (#2373) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 62f495dfe..9f4197d5f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From f58f00e97f30c72f0834d37b2aa0e0b1260af8ef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 22 Jul 2023 12:21:08 +0200 Subject: [PATCH 17/60] Update plugin com.modrinth.minotaur to v2.8.2 (#2374) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- worldedit-bukkit/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index a6d31be03..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" From 37bfe426bc2661686f146ba5224ad86f4c4467c8 Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Sat, 22 Jul 2023 12:27:54 +0200 Subject: [PATCH 18/60] Release 2.7.0 --- build.gradle.kts | 4 ++-- .../extent/processor/EntityInBlockRemovingProcessor.java | 2 +- .../core/function/pattern/TypeSwapPattern.java | 4 ++-- .../core/internal/exception/FaweException.java | 4 ++-- .../core/queue/implementation/QueueHandler.java | 4 ++-- .../com/fastasyncworldedit/core/regions/FaweMask.java | 2 +- .../fastasyncworldedit/core/regions/FaweMaskManager.java | 2 +- .../com/fastasyncworldedit/core/util/TaskManager.java | 8 ++++---- .../core/util/task/AsyncNotifyKeyedQueue.java | 2 +- .../java/com/sk89q/worldedit/world/block/BlockType.java | 4 ++-- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index de0d714cc..a0930e8ec 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.0") var snapshot by extra("SNAPSHOT") var revision: String by extra("") var buildNumber by extra("") @@ -52,7 +52,7 @@ ext { } } -version = String.format("%s-%s", rootVersion, buildNumber) +version = String.format("%s", rootVersion) if (!project.hasProperty("gitCommitHash")) { apply(plugin = "org.ajoberstar.grgit") 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 1ffea1266..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 @@ -15,7 +15,7 @@ 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 { 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 index 89876f658..efc122b5d 100644 --- 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 @@ -16,7 +16,7 @@ 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 TODO + * @since 2.7.0 */ public class TypeSwapPattern extends AbstractExtentPattern { @@ -34,7 +34,7 @@ public class TypeSwapPattern extends AbstractExtentPattern { * @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 TODO + * @since 2.7.0 */ public TypeSwapPattern(Extent extent, String inputString, String outputString, boolean allowRegex) { super(extent); 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/implementation/QueueHandler.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java index bc7f9ddec..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 @@ -516,7 +516,7 @@ public abstract class QueueHandler implements Trimable, Runnable { *

* Internal API usage only. * - * @since TODO + * @since 2.7.0 */ public ExecutorService getForkJoinPoolPrimary() { return forkJoinPoolPrimary; @@ -528,7 +528,7 @@ public abstract class QueueHandler implements Trimable, Runnable { *

* Internal API usage only. * - * @since TODO + * @since 2.7.0 */ public ExecutorService getForkJoinPoolSecondary() { return forkJoinPoolSecondary; 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 478ac90c9..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 @@ -38,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/TaskManager.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/TaskManager.java index 65025bcd0..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 @@ -91,7 +91,7 @@ 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 = "TODO") + @Deprecated(forRemoval = true, since = "2.7.0") public void parallel(Collection runables) { for (Runnable run : runables) { pool.submit(run); @@ -106,7 +106,7 @@ public abstract class TaskManager { * @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(forRemoval = true, since = "TODO") + @Deprecated(forRemoval = true, since = "2.7.0") public void parallel(Collection runnables, @Nullable Integer numThreads) { if (runnables == null) { return; @@ -278,7 +278,7 @@ 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 = "TODO") + @Deprecated(forRemoval = true, since = "2.7.0") public void wait(AtomicBoolean running, int timeout) { try { long start = System.currentTimeMillis(); @@ -299,7 +299,7 @@ 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 = "TODO") + @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/task/AsyncNotifyKeyedQueue.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/AsyncNotifyKeyedQueue.java index 9df6b197c..cb6747cd2 100644 --- 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 @@ -13,7 +13,7 @@ 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 TODO + * @since 2.7.0 */ public class AsyncNotifyKeyedQueue implements Closeable { 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. From 006ccd6887d06b2de5e96ab15f9f595f5b76d9c8 Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Sat, 22 Jul 2023 12:44:37 +0200 Subject: [PATCH 19/60] Back to snapshot for development --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a0930e8ec..3f6169bb7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,7 +34,7 @@ logger.lifecycle(""" ******************************************* """) -var rootVersion by extra("2.7.0") +var rootVersion by extra("2.7.1") var snapshot by extra("SNAPSHOT") var revision: String by extra("") var buildNumber by extra("") @@ -52,7 +52,7 @@ ext { } } -version = String.format("%s", rootVersion) +version = String.format("%s-%s", rootVersion, buildNumber) if (!project.hasProperty("gitCommitHash")) { apply(plugin = "org.ajoberstar.grgit") From 2f6de996e3d3cfc542e10b01fc379e1450450623 Mon Sep 17 00:00:00 2001 From: dordsor21 Date: Sat, 22 Jul 2023 13:14:01 +0100 Subject: [PATCH 20/60] fix: processorExtent can be null in editsession - fixes #2375 --- .../fastasyncworldedit/core/util/ExtentTraverser.java | 3 +++ .../src/main/java/com/sk89q/worldedit/EditSession.java | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) 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/sk89q/worldedit/EditSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java index f60a1cbf2..983292ca5 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -529,10 +529,12 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { //FAWE start - ExtentTraverser & MaskingExtents MaskingExtent maskingExtent = new ExtentTraverser<>(getExtent()).findAndGet(MaskingExtent.class); if (maskingExtent == null) { - ExtentTraverser processorExtent = - new ExtentTraverser<>(getExtent()).find(ExtentBatchProcessorHolder.class); - maskingExtent = - new ProcessorTraverser<>(processorExtent.get().getProcessor()).find(MaskingExtent.class); + 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 From 48be6ac94bb559d563eab6f2e5b92b807f2e089e Mon Sep 17 00:00:00 2001 From: dordsor21 Date: Sat, 22 Jul 2023 17:44:05 +0100 Subject: [PATCH 21/60] fix: processorExtent can be null when setting masks too - fixes #2379 --- .../src/main/java/com/sk89q/worldedit/EditSession.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 983292ca5..ecf859896 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -631,10 +631,12 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { } MaskingExtent maskingExtent = new ExtentTraverser<>(getExtent()).findAndGet(MaskingExtent.class); if (maskingExtent == null && mask != Masks.alwaysTrue()) { - ExtentTraverser processorExtent = - new ExtentTraverser<>(getExtent()).find(ExtentBatchProcessorHolder.class); - maskingExtent = - new ProcessorTraverser<>(processorExtent.get().getProcessor()).find(MaskingExtent.class); + 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(); From 8b39e41a1a11c8ae2b7898da3a586bfca81c5278 Mon Sep 17 00:00:00 2001 From: Jordan Date: Thu, 27 Jul 2023 09:59:11 +0100 Subject: [PATCH 22/60] fix: don't warn on release build (#2369) --- .../com/fastasyncworldedit/core/util/UpdateNotification.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 13effcc46..73ad01361 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 @@ -59,7 +59,7 @@ public class UpdateNotification { Document doc = db.parse(body); faweVersion = doc.getElementsByTagName("lastSuccessfulBuild").item(0).getFirstChild().getTextContent(); FaweVersion faweVersion = Fawe.instance().getVersion(); - if (faweVersion.build == 0) { + if (faweVersion.build == 0 && !faweVersion.snapshot) { 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; From d42854845acf4c658ac8104cc26df0afe8e4db5a Mon Sep 17 00:00:00 2001 From: Jordan Date: Thu, 27 Jul 2023 10:02:41 +0100 Subject: [PATCH 23/60] fix: disallowed regions can be length 0 (#2368) --- .../com/sk89q/worldedit/EditSessionBuilder.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java index e5c53a45b..ca6f3b6a5 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java @@ -607,20 +607,17 @@ public final class EditSessionBuilder { } FaweRegionExtent regionExtent = null; - if (disallowedRegions != null) { // Always use MultiRegionExtent if we have blacklist regions + // Always use MultiRegionExtent if we have blacklist regions + if (allowedRegions != null && allowedRegions.length == 0) { + regionExtent = new NullExtent(this.extent, FaweCache.NO_REGION); + } else if (disallowedRegions != null && disallowedRegions.length != 0) { regionExtent = new MultiRegionExtent(this.extent, this.limit, allowedRegions, disallowedRegions); } else if (allowedRegions == null) { allowedRegions = new Region[]{RegionWrapper.GLOBAL()}; + } else if (allowedRegions.length == 1) { + regionExtent = new SingleRegionExtent(this.extent, this.limit, allowedRegions[0]); } else { - if (allowedRegions.length == 0) { - regionExtent = new NullExtent(this.extent, FaweCache.NO_REGION); - } else { - if (allowedRegions.length == 1) { - regionExtent = new SingleRegionExtent(this.extent, this.limit, allowedRegions[0]); - } else { - regionExtent = new MultiRegionExtent(this.extent, this.limit, allowedRegions, null); - } - } + regionExtent = new MultiRegionExtent(this.extent, this.limit, allowedRegions, null); } if (regionExtent != null) { if (placeChunks) { From 9ae04b8d40b3901ee283530a0b51fda659de1f6e Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Mon, 31 Jul 2023 11:44:14 +0200 Subject: [PATCH 24/60] Update v7 notification --- .../src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 90db4368c..c8945b625 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java @@ -329,7 +329,7 @@ public class FaweBukkit implements IFawe, Listener { WEManager.weManager().addManager(new com.fastasyncworldedit.bukkit.regions.plotsquared.PlotSquaredFeature()); LOGGER.info("Plugin 'PlotSquared' v7 found. Using it now."); } else { - LOGGER.error("Incompatible version of PlotSquared found. Please use PlotSquared v6."); + LOGGER.error("Incompatible version of PlotSquared found. Please use PlotSquared v7."); LOGGER.info("https://www.spigotmc.org/resources/77506/"); } } From 0dbc0db43f8e4d888263576a1da5ec7150eec58e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 2 Aug 2023 14:07:19 +0200 Subject: [PATCH 25/60] Update plugin com.modrinth.minotaur to v2.8.3 (#2392) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- worldedit-bukkit/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index 1962ff078..1e3bbf4a1 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.2" + id("com.modrinth.minotaur") version "2.8.3" } project.description = "Bukkit" From 4ca0a95e1850601db6be9783a6b4c374c0e0c78d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 2 Aug 2023 14:07:27 +0200 Subject: [PATCH 26/60] Update dependency com.sk89q.worldguard:worldguard-bukkit to v7.0.9 (#2391) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9ff63fb98..1a25597c1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ log4j = "2.19.0" # Plugins dummypermscompat = "1.10" -worldguard-bukkit = "7.0.8" +worldguard-bukkit = "7.0.9" mapmanager = "1.8.0-SNAPSHOT" griefprevention = "16.18.1" griefdefender = "2.1.0-SNAPSHOT" From a6f1c6ac82242bb62c665bbd8e721736535b7b17 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 2 Aug 2023 14:07:35 +0200 Subject: [PATCH 27/60] Update dependency com.palmergames.bukkit.towny:towny to v0.99.5.7 (#2390) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1a25597c1..3f463bc9c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ mapmanager = "1.8.0-SNAPSHOT" griefprevention = "16.18.1" griefdefender = "2.1.0-SNAPSHOT" residence = "4.5._13.1" -towny = "0.99.5.4" +towny = "0.99.5.7" # Third party bstats = "3.0.2" From 50e45f07f096539c9754e2295d6f632f9654a1b2 Mon Sep 17 00:00:00 2001 From: Jeff P Date: Wed, 2 Aug 2023 08:08:58 -0400 Subject: [PATCH 28/60] fix: Do not tab complete if command not owned by FAWE (#2386) --- .../java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index f1edf65f7..e881416c5 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -672,6 +672,13 @@ public class WorldEditPlugin extends JavaPlugin { String label = buffer.substring(0, firstSpace); // Strip leading slash, if present. label = label.startsWith("/") ? label.substring(1) : label; + + // If command not owned by FAWE, do not tab complete + Plugin owner = platform.getDynamicCommands().getCommandOwner(label); + if (owner != WorldEditPlugin.this) { + return; + } + final Optional command = WorldEdit.getInstance().getPlatformManager().getPlatformCommandManager().getCommandManager().getCommand( label); From e81980651e1512e797ffc1beac66acad03ffc5a2 Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Wed, 9 Aug 2023 20:52:18 +0200 Subject: [PATCH 29/60] Cleanup dependencies --- buildSrc/src/main/kotlin/AdapterConfig.kt | 1 - buildSrc/src/main/kotlin/CommonJavaConfig.kt | 11 ++++--- gradle/libs.versions.toml | 29 ++++++++++++++++++ .../adapters/adapter-1_17_1/build.gradle.kts | 2 +- .../adapters/adapter-1_18_2/build.gradle.kts | 2 +- .../adapters/adapter-1_19_4/build.gradle.kts | 2 +- .../adapters/adapter-1_20/build.gradle.kts | 2 +- worldedit-bukkit/build.gradle.kts | 30 +++++++++---------- .../sk89q/wepif/TestOfflinePermissible.java | 20 +++++++++++++ worldedit-cli/build.gradle.kts | 8 ++--- worldedit-core/build.gradle.kts | 18 +++++------ 11 files changed, 86 insertions(+), 39 deletions(-) diff --git a/buildSrc/src/main/kotlin/AdapterConfig.kt b/buildSrc/src/main/kotlin/AdapterConfig.kt index 6cad58e3b..60f9a5733 100644 --- a/buildSrc/src/main/kotlin/AdapterConfig.kt +++ b/buildSrc/src/main/kotlin/AdapterConfig.kt @@ -15,7 +15,6 @@ fun Project.applyPaperweightAdapterConfiguration() { dependencies { "implementation"(project(":worldedit-bukkit")) - "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 73389ce83..549a26037 100644 --- a/buildSrc/src/main/kotlin/CommonJavaConfig.kt +++ b/buildSrc/src/main/kotlin/CommonJavaConfig.kt @@ -40,12 +40,11 @@ fun Project.applyCommonJavaConfiguration(sourcesJar: Boolean, banSlf4j: Boolean dependencies { "compileOnly"("com.google.code.findbugs:jsr305:3.0.2") - "testImplementation"("org.junit.jupiter:junit-jupiter-api:5.9.2") - "testImplementation"("org.junit.jupiter:junit-jupiter-params:5.9.2") - "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-newest:1.33")) + "testImplementation"("org.junit.jupiter:junit-jupiter-api:5.10.0") + "testImplementation"("org.junit.jupiter:junit-jupiter-params:5.10.0") + "testImplementation"("org.mockito:mockito-core:5.4.0") + "testImplementation"("org.mockito:mockito-junit-jupiter:5.4.0") + "testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:5.10.0") } // Java 8 turns on doclint which we fail diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3f463bc9c..e694c8c8d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,11 @@ [versions] # Minecraft expectations +paper = "1.20.1-R0.1-SNAPSHOT" fastutil = "8.5.9" guava = "31.1-jre" log4j = "2.19.0" +gson = "2.10" +snakeyaml = "2.0" # Plugins dummypermscompat = "1.10" @@ -12,12 +15,15 @@ griefprevention = "16.18.1" griefdefender = "2.1.0-SNAPSHOT" residence = "4.5._13.1" towny = "0.99.5.7" +plotsquared = "7.0.0-rc.4" # Third party bstats = "3.0.2" sparsebitset = "1.2" parallelgzip = "1.0.5" adventure = "4.14.0" +adventure-bukkit = "4.3.0" +checkerqual = "3.37.0" truezip = "6.8.4" auto-value = "1.10.2" findbugs = "3.0.2" @@ -29,6 +35,11 @@ jlibnoise = "1.0.0" jchronic = "0.2.4a" lz4-java = "1.8.0" lz4-stream = "1.0.0" +commons-cli = "1.5.0" +paperlib = "1.0.8" +paster = "1.1.5" +vault = "1.7.1" +serverlib = "2.3.1" ## Internal text-adapter = "3.0.6" text = "3.0.4" @@ -39,12 +50,17 @@ mockito = "5.4.0" # Gradle plugins pluginyml = "0.6.0" +minotaur = "2.8.3" [libraries] # Minecraft expectations +paper = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" } fastutil = { group = "it.unimi.dsi", name = "fastutil", version.ref = "fastutil" } log4jBom = { group = "org.apache.logging.log4j", name = "log4j-bom", version.ref = "log4j" } +log4jApi = { group = "org.apache.logging.log4j", name = "log4j-api", version.ref = "log4j" } guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } +gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } +snakeyaml = { group = "org.yaml", name = "snakeyaml", version.ref = "snakeyaml" } # Plugins dummypermscompat = { group = "com.sk89q", name = "dummypermscompat", version.ref = "dummypermscompat" } @@ -54,9 +70,12 @@ griefprevention = { group = "com.github.TechFortress", name = "GriefPrevention", griefdefender = { group = "com.griefdefender", name = "api", version.ref = "griefdefender" } residence = { group = "com.bekvon.bukkit.residence", name = "Residence", version.ref = "residence" } towny = { group = "com.palmergames.bukkit.towny", name = "towny", version.ref = "towny" } +plotSquaredCore = { group = "com.intellectualsites.plotsquared", name = "plotsquared-core", version.ref = "plotsquared" } +plotSquaredBukkit = { group = "com.intellectualsites.plotsquared", name = "plotsquared-bukkit", version.ref = "plotsquared" } # Third Party bstatsBase = { group = "org.bstats", name = "bstats-base", version.ref = "bstats" } +bstatsBukkit = { group = "org.bstats", name = "bstats-bukkit", version.ref = "bstats" } sparsebitset = { group = "com.zaxxer", name = "SparseBitSet", version.ref = "sparsebitset" } parallelgzip = { group = "org.anarres", name = "parallelgzip", version.ref = "parallelgzip" } adventureNbt = { group = "net.kyori", name = "adventure-nbt", version.ref = "adventure" } @@ -73,6 +92,15 @@ jlibnoise = { group = "com.sk89q.lib", name = "jlibnoise", version.ref = "jlibno jchronic = { group = "com.sk89q", name = "jchronic", version.ref = "jchronic" } lz4Java = { group = "org.lz4", name = "lz4-java", version.ref = "lz4-java" } lz4JavaStream = { group = "net.jpountz", name = "lz4-java-stream", version.ref = "lz4-stream" } +commonsCli = { group = "commons-cli", name = "commons-cli", version.ref = "commons-cli" } +paperlib = { group = "io.papermc", name = "paperlib", version.ref = "paperlib" } +adventureApi = { group = "net.kyori", name = "adventure-api", version.ref = "adventure" } +adventureMiniMessage = { group = "net.kyori", name = "adventure-text-minimessage", version.ref = "adventure" } +adventureBukkit = { group = "net.kyori", name = "adventure-platform-bukkit", version.ref = "adventure-bukkit" } +paster = { group = "com.intellectualsites.paster", name = "Paster", version.ref = "paster" } +vault = { group = "com.github.MilkBowl", name = "VaultAPI", version.ref = "vault" } +serverlib = { group = "dev.notmyfault.serverlib", name = "ServerLib", version.ref = "serverlib" } +checkerqual = { group = "org.checkerframework", name = "checker-qual", version.ref = "checkerqual" } # Internal ## Text @@ -94,3 +122,4 @@ log4jCore = { group = "org.apache.logging.log4j", name = "log4j-core", version.r [plugins] pluginyml = { id = "net.minecrell.plugin-yml.bukkit", version.ref = "pluginyml" } +minotaur = { id = "com.modrinth.minotaur", version.ref = "minotaur" } diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_17_1/build.gradle.kts index 940dfcb0b..24c121c3f 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_17_1/build.gradle.kts @@ -22,5 +22,5 @@ configurations.all { dependencies { the().paperDevBundle("1.17.1-R0.1-20220414.034903-210") - compileOnly("io.papermc:paperlib") + compileOnly(libs.paperlib) } diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_18_2/build.gradle.kts index aaa4643d2..f7f40ce66 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_18_2/build.gradle.kts @@ -13,5 +13,5 @@ repositories { dependencies { // https://papermc.io/repo/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/ the().paperDevBundle("1.18.2-R0.1-20220920.010157-167") - compileOnly("io.papermc:paperlib") + compileOnly(libs.paperlib) } diff --git a/worldedit-bukkit/adapters/adapter-1_19_4/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_19_4/build.gradle.kts index 5e1f312bc..df27ae5d6 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_4/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_19_4/build.gradle.kts @@ -12,5 +12,5 @@ repositories { dependencies { the().paperDevBundle("1.19.4-R0.1-20230608.201059-104") - compileOnly("io.papermc:paperlib") + compileOnly(libs.paperlib) } diff --git a/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts index cf13348ff..8d116f3bc 100644 --- a/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts @@ -13,5 +13,5 @@ repositories { dependencies { // https://repo.papermc.io/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/ the().paperDevBundle("1.20.1-R0.1-20230623.105806-29") - compileOnly("io.papermc:paperlib") + compileOnly(libs.paperlib) } diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index 1e3bbf4a1..a6e853eef 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.3" + alias(libs.plugins.minotaur) } project.description = "Bukkit" @@ -74,19 +74,19 @@ dependencies { implementation(libs.fastutil) // Platform expectations - compileOnly("io.papermc.paper:paper-api") { + compileOnly(libs.paper) { exclude("junit", "junit") exclude(group = "org.slf4j", module = "slf4j-api") } // Logging - localImplementation("org.apache.logging.log4j:log4j-api") + localImplementation(libs.log4jApi) localImplementation(libs.log4jBom) { because("Spigot provides Log4J (sort of, not in API, implicitly part of server)") } // Plugins - compileOnly("com.github.MilkBowl:VaultAPI") { isTransitive = false } + compileOnly(libs.vault) { isTransitive = false } compileOnly(libs.dummypermscompat) { exclude("com.github.MilkBowl", "VaultAPI") } @@ -101,26 +101,26 @@ dependencies { compileOnly(libs.griefdefender) { isTransitive = false } compileOnly(libs.residence) { isTransitive = false } compileOnly(libs.towny) { isTransitive = false } - compileOnly("com.intellectualsites.plotsquared:plotsquared-bukkit") { isTransitive = false } - compileOnly("com.intellectualsites.plotsquared:plotsquared-core") { isTransitive = false } + compileOnly(libs.plotSquaredBukkit) { isTransitive = false } + compileOnly(libs.plotSquaredCore) { isTransitive = false } // Third party - implementation("io.papermc:paperlib") - implementation("org.bstats:bstats-bukkit") { isTransitive = false } + implementation(libs.paperlib) + implementation(libs.bstatsBukkit) { isTransitive = false } implementation(libs.bstatsBase) { isTransitive = false } - implementation("dev.notmyfault.serverlib:ServerLib") - implementation("com.intellectualsites.paster:Paster") { isTransitive = false } + implementation(libs.serverlib) + implementation(libs.paster) { isTransitive = false } api(libs.lz4Java) { isTransitive = false } api(libs.sparsebitset) { isTransitive = false } api(libs.parallelgzip) { isTransitive = false } - compileOnly("net.kyori:adventure-api") - compileOnlyApi("org.checkerframework:checker-qual") + compileOnly(libs.adventureApi) + compileOnlyApi(libs.checkerqual) // Tests testImplementation(libs.mockito) - testImplementation("net.kyori:adventure-api") - testImplementation("org.checkerframework:checker-qual") - testImplementation("io.papermc.paper:paper-api") { isTransitive = true } + testImplementation(libs.adventureApi) + testImplementation(libs.checkerqual) + testImplementation(libs.paper) { isTransitive = true } } tasks.named("processResources") { 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 981812e1b..c63e98663 100644 --- a/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java +++ b/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java @@ -36,6 +36,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.annotation.Nonnull; +import java.time.Duration; +import java.time.Instant; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -161,6 +163,24 @@ public class TestOfflinePermissible implements OfflinePlayer, Permissible { throw new UnsupportedOperationException("Not supported yet."); } + @Override + public > @Nullable E ban( + @Nullable final String reason, + @Nullable final Instant expires, + @Nullable final String source + ) { + return null; + } + + @Override + public > @Nullable E ban( + @Nullable final String reason, + @Nullable final Duration duration, + @Nullable final String source + ) { + return null; + } + @Override public @Nullable BanEntry ban( @Nullable final String reason, diff --git a/worldedit-cli/build.gradle.kts b/worldedit-cli/build.gradle.kts index ff0c9b5c1..6f0335e0e 100644 --- a/worldedit-cli/build.gradle.kts +++ b/worldedit-cli/build.gradle.kts @@ -27,16 +27,16 @@ dependencies { // Minecraft expectations annotationProcessor(libs.guava) - implementation("com.google.guava:guava") - implementation("com.google.code.gson:gson") + implementation(libs.guava) + implementation(libs.gson) // Logging implementation(libs.log4jBom) { because("We control Log4J on this platform") } - implementation("org.apache.logging.log4j:log4j-api") + implementation(libs.log4jApi) implementation(libs.log4jCore) - implementation("commons-cli:commons-cli:1.5.0") + implementation(libs.commonsCli) api(libs.parallelgzip) { isTransitive = false } api(libs.lz4Java) } diff --git a/worldedit-core/build.gradle.kts b/worldedit-core/build.gradle.kts index 4e624bd3b..a58230b2c 100644 --- a/worldedit-core/build.gradle.kts +++ b/worldedit-core/build.gradle.kts @@ -11,7 +11,7 @@ applyPlatformAndCoreConfiguration() dependencies { constraints { - implementation("org.yaml:snakeyaml") { + implementation(libs.snakeyaml) { version { strictly("2.0") } because("Bukkit provides SnakeYaml") } @@ -24,17 +24,17 @@ dependencies { // Minecraft expectations implementation(libs.fastutil) - implementation("com.google.guava:guava") - implementation("com.google.code.gson:gson") + implementation(libs.guava) + implementation(libs.gson) // Platform expectations - implementation("org.yaml:snakeyaml") + implementation(libs.snakeyaml) // Logging - implementation("org.apache.logging.log4j:log4j-api") + implementation(libs.log4jApi) // Plugins - compileOnly("com.intellectualsites.plotsquared:plotsquared-core") { isTransitive = false } + compileOnly(libs.plotSquaredCore) { isTransitive = false } // ensure this is on the classpath for the AP annotationProcessor(libs.guava) @@ -45,11 +45,11 @@ dependencies { compileOnly(libs.truezip) implementation(libs.findbugs) implementation(libs.rhino) - compileOnly("net.kyori:adventure-api") + compileOnly(libs.adventureApi) compileOnlyApi(libs.adventureNbt) - compileOnlyApi("net.kyori:adventure-text-minimessage") + compileOnlyApi(libs.adventureMiniMessage) implementation(libs.zstd) { isTransitive = false } - compileOnly("com.intellectualsites.paster:Paster") + compileOnly(libs.paster) compileOnly(libs.lz4Java) { isTransitive = false } compileOnly(libs.sparsebitset) compileOnly(libs.parallelgzip) { isTransitive = false } From a3e855882c5fb5c5a0c9fe5a49f7d0bb716b2678 Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Wed, 9 Aug 2023 20:54:46 +0200 Subject: [PATCH 30/60] [ci skip] Ignore snakeyaml updates --- .github/renovate.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index 526af5a60..c4f38d66b 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -20,7 +20,8 @@ "net.fabricmc.fabric-api:fabric-api", "com.github.luben:zstd-jni", "org.jetbrains.kotlin.jvm", - "log4j" + "org.apache.logging.log4j:log4j-api", + "org.yaml:snakeyaml" ], "labels": ["Renovate"], "rebaseWhen": "conflicted", From 7b3c674e1c9baadc2a4c853f006b1b12e4b7e323 Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Wed, 9 Aug 2023 20:56:15 +0200 Subject: [PATCH 31/60] [ci skip] Ignore snakeyaml updates --- .github/renovate.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/renovate.json b/.github/renovate.json index c4f38d66b..314458a6d 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -20,7 +20,12 @@ "net.fabricmc.fabric-api:fabric-api", "com.github.luben:zstd-jni", "org.jetbrains.kotlin.jvm", + "log4j", "org.apache.logging.log4j:log4j-api", + "org.apache.logging.log4j:log4j-bom", + "org.apache.logging.log4j:log4j-slf4j-impl", + "org.bstats:bstats-sponge", + "org.spongepowered:spongeapi", "org.yaml:snakeyaml" ], "labels": ["Renovate"], From acb79469fb28ba7a15734b461586378eba7bc391 Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Wed, 9 Aug 2023 20:59:27 +0200 Subject: [PATCH 32/60] [ci skip] Update towny --- .github/renovate.json | 1 + gradle/libs.versions.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index 314458a6d..d0e59d791 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -24,6 +24,7 @@ "org.apache.logging.log4j:log4j-api", "org.apache.logging.log4j:log4j-bom", "org.apache.logging.log4j:log4j-slf4j-impl", + "org.apache.logging.log4j:log4j-core", "org.bstats:bstats-sponge", "org.spongepowered:spongeapi", "org.yaml:snakeyaml" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e694c8c8d..2172a6189 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ mapmanager = "1.8.0-SNAPSHOT" griefprevention = "16.18.1" griefdefender = "2.1.0-SNAPSHOT" residence = "4.5._13.1" -towny = "0.99.5.7" +towny = "0.99.5.10" plotsquared = "7.0.0-rc.4" # Third party From a088e6a96b7caf5e959b23ea84faae990467dfb6 Mon Sep 17 00:00:00 2001 From: Maddy Miller Date: Wed, 9 Aug 2023 18:32:37 -0400 Subject: [PATCH 33/60] Remove the Bukkit legacy data folder migration code from 11 years ago (#2383) (cherry picked from commit EngineHub/WorldEdit@7f81aad028f2b8244351764b56d7e9f1e9f3d1ec) --- .../worldedit/bukkit/BukkitConfiguration.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java index eaa592395..46ca0bb55 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java @@ -25,7 +25,6 @@ import com.sk89q.worldedit.util.YAMLConfiguration; import com.sk89q.worldedit.util.report.Unreported; import org.apache.logging.log4j.LogManager; -import java.io.File; import java.nio.file.Path; /** @@ -53,26 +52,6 @@ public class BukkitConfiguration extends YAMLConfiguration { WorldEdit.logger.warn("Editing without a Bukkit adapter has been enabled. You will not receive support " + "for any issues that arise as a result."); } - migrateLegacyFolders(); - } - - private void migrateLegacyFolders() { - migrate(scriptsDir, "craftscripts"); - migrate(saveDir, "schematics"); - migrate("drawings", "draw.js images"); - } - - private void migrate(String file, String name) { - File fromDir = new File(".", file); - File toDir = new File(getWorkingDirectoryPath().toFile(), file); - if (fromDir.exists() & !toDir.exists()) { - if (fromDir.renameTo(toDir)) { - plugin.getLogger().info("Migrated " + name + " folder '" + file - + "' from server root to plugin data folder."); - } else { - plugin.getLogger().warning("Error while migrating " + name + " folder!"); - } - } } @Override From fe33be5795ad26b17a17b508e93de37b66e7a01c Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Fri, 18 Aug 2023 13:40:16 +0200 Subject: [PATCH 34/60] Update userdev --- worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts | 2 +- .../bukkit/adapter/ext/fawe/v1_20_R1/PaperweightAdapter.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts index 8d116f3bc..2f65af4ce 100644 --- a/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts @@ -12,6 +12,6 @@ repositories { dependencies { // https://repo.papermc.io/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/ - the().paperDevBundle("1.20.1-R0.1-20230623.105806-29") + the().paperDevBundle("1.20.1-R0.1-20230818.021330-113") compileOnly(libs.paperlib) } diff --git a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R1/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R1/PaperweightAdapter.java index e495a078c..428176c5f 100644 --- a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R1/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R1/PaperweightAdapter.java @@ -637,7 +637,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter Date: Thu, 24 Aug 2023 10:19:29 +0200 Subject: [PATCH 35/60] Update plotsquared to v7.0.0 (#2407) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2172a6189..480b8e07c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ griefprevention = "16.18.1" griefdefender = "2.1.0-SNAPSHOT" residence = "4.5._13.1" towny = "0.99.5.10" -plotsquared = "7.0.0-rc.4" +plotsquared = "7.0.0" # Third party bstats = "3.0.2" From 8efb1aa5334add383ac1ec289325649d4acb60f5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:20:09 +0200 Subject: [PATCH 36/60] Update dependency gradle to v8.3 (#2408) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.jar | Bin 63375 -> 63721 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 3 ++- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 033e24c4cdf41af1ab109bc7f253b2b887023340..7f93135c49b765f8051ef9d0a6055ff8e46073d8 100644 GIT binary patch delta 28216 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9f4197d5f..ac72c34e8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index fcb6fca14..0adc8e1a5 100755 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum From 6f9c3c3e01b16e76299809b7741b9d890b7b5c09 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:20:19 +0200 Subject: [PATCH 37/60] Update dependency org.mockito:mockito-core to v5.5.0 (#2409) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- worldedit-sponge/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 480b8e07c..b1d1e61c0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -46,7 +46,7 @@ text = "3.0.4" piston = "0.5.7" # Tests -mockito = "5.4.0" +mockito = "5.5.0" # Gradle plugins pluginyml = "0.6.0" diff --git a/worldedit-sponge/build.gradle.kts b/worldedit-sponge/build.gradle.kts index aba91acea..3c6783886 100644 --- a/worldedit-sponge/build.gradle.kts +++ b/worldedit-sponge/build.gradle.kts @@ -28,7 +28,7 @@ dependencies { }) api("org.apache.logging.log4j:log4j-api") api("org.bstats:bstats-sponge:1.7") - testImplementation("org.mockito:mockito-core:5.4.0") + testImplementation("org.mockito:mockito-core:5.5.0") } <<<<<<< HEAD From 4def201daad9d9ddb26594bb74de6c9c6cce2269 Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Sat, 26 Aug 2023 17:13:24 +0200 Subject: [PATCH 38/60] Adapt new test methods --- build.gradle.kts | 2 +- .../test/java/com/sk89q/wepif/TestOfflinePermissible.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3f6169bb7..90f33890d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,7 +52,7 @@ ext { } } -version = String.format("%s-%s", rootVersion, buildNumber) +version = String.format("%s", rootVersion) if (!project.hasProperty("gitCommitHash")) { apply(plugin = "org.ajoberstar.grgit") 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 c63e98663..052c772d5 100644 --- a/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java +++ b/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java @@ -144,6 +144,11 @@ public class TestOfflinePermissible implements OfflinePlayer, Permissible { return false; } + @Override + public boolean isConnected() { + return false; + } + @Override public String getName() { return "Tester"; From f5a5eae1a39daef560b7757f68dd6cf07f3a50b7 Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Sat, 26 Aug 2023 17:34:37 +0200 Subject: [PATCH 39/60] Back to snapshot for development --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 90f33890d..e441c1336 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,7 +34,7 @@ logger.lifecycle(""" ******************************************* """) -var rootVersion by extra("2.7.1") +var rootVersion by extra("2.7.2") var snapshot by extra("SNAPSHOT") var revision: String by extra("") var buildNumber by extra("") @@ -52,7 +52,7 @@ ext { } } -version = String.format("%s", rootVersion) +version = String.format("%s-%s", rootVersion, buildNumber) if (!project.hasProperty("gitCommitHash")) { apply(plugin = "org.ajoberstar.grgit") From 8c611c5324f925647466f77748a721cf0dc040c6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 08:32:13 +0200 Subject: [PATCH 40/60] Update dependency dev.notmyfault.serverlib:ServerLib to v2.3.4 (#2415) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- worldedit-bukkit/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index a6e853eef..4f7211db5 100644 --- a/worldedit-bukkit/build.gradle.kts +++ b/worldedit-bukkit/build.gradle.kts @@ -174,7 +174,7 @@ tasks.named("shadowJar") { include(dependency("it.unimi.dsi:fastutil")) } relocate("org.incendo.serverlib", "com.fastasyncworldedit.serverlib") { - include(dependency("dev.notmyfault.serverlib:ServerLib:2.3.1")) + include(dependency("dev.notmyfault.serverlib:ServerLib:2.3.4")) } relocate("com.intellectualsites.paster", "com.fastasyncworldedit.paster") { include(dependency("com.intellectualsites.paster:Paster")) From a1ee56569e0f7392718bfbfebff74275a96f97cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 08:32:34 +0200 Subject: [PATCH 41/60] Update dependency dev.notmyfault.serverlib:ServerLib to v2.3.4 (#2414) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b1d1e61c0..7c13f7c27 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,7 +39,7 @@ commons-cli = "1.5.0" paperlib = "1.0.8" paster = "1.1.5" vault = "1.7.1" -serverlib = "2.3.1" +serverlib = "2.3.4" ## Internal text-adapter = "3.0.6" text = "3.0.4" From 7288393a39e4438731440b2c4e9a4526e123021d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 08:37:58 +0200 Subject: [PATCH 42/60] Update dependency com.palmergames.bukkit.towny:towny to v0.99.5.16 (#2413) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7c13f7c27..74858196d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ mapmanager = "1.8.0-SNAPSHOT" griefprevention = "16.18.1" griefdefender = "2.1.0-SNAPSHOT" residence = "4.5._13.1" -towny = "0.99.5.10" +towny = "0.99.5.16" plotsquared = "7.0.0" # Third party From 60a3994d628d6d19c2983582fee4a70a35e65398 Mon Sep 17 00:00:00 2001 From: Hannes Greule Date: Sat, 9 Sep 2023 16:07:29 +0200 Subject: [PATCH 43/60] Clean up some regen code (#2405) --- .../v1_17_R1_2/regen/PaperweightRegen.java | 7 +- .../fawe/v1_18_R2/regen/PaperweightRegen.java | 7 +- .../fawe/v1_19_R3/regen/PaperweightRegen.java | 7 +- .../fawe/v1_20_R1/regen/PaperweightRegen.java | 7 +- .../bukkit/adapter/Regenerator.java | 184 +++++++++--------- 5 files changed, 98 insertions(+), 114 deletions(-) diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/regen/PaperweightRegen.java index 3d9694af9..4d7a749de 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/regen/PaperweightRegen.java @@ -184,9 +184,6 @@ public class PaperweightRegen extends Regenerator super.chunkStati.put(new ChunkStatusWrap(s), c)); + chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c)); return true; } @@ -680,7 +677,7 @@ public class PaperweightRegen extends Regenerator processChunk(Long xz, List accessibleChunks) { + public CompletableFuture processChunk(List accessibleChunks) { return chunkStatus.generate( Runnable::run, // TODO revisit, we might profit from this somehow? freshWorld, diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/regen/PaperweightRegen.java index 51bd22bdb..34e18f748 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/regen/PaperweightRegen.java @@ -178,9 +178,6 @@ public class PaperweightRegen extends Regenerator super.chunkStati.put(new ChunkStatusWrap(s), c)); + chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c)); return true; } @@ -523,7 +520,7 @@ public class PaperweightRegen extends Regenerator processChunk(Long xz, List accessibleChunks) { + public CompletableFuture processChunk(List accessibleChunks) { return chunkStatus.generate( Runnable::run, // TODO revisit, we might profit from this somehow? freshWorld, diff --git a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/regen/PaperweightRegen.java index 4f22e8734..22bae54fe 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/regen/PaperweightRegen.java @@ -192,9 +192,6 @@ public class PaperweightRegen extends Regenerator super.chunkStati.put(new ChunkStatusWrap(s), c)); + chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c)); return true; } @@ -554,7 +551,7 @@ public class PaperweightRegen extends Regenerator processChunk(Long xz, List accessibleChunks) { + public CompletableFuture processChunk(List accessibleChunks) { return chunkStatus.generate( Runnable::run, // TODO revisit, we might profit from this somehow? freshWorld, diff --git a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/regen/PaperweightRegen.java index b5d5a2733..812cf1d4c 100644 --- a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/regen/PaperweightRegen.java @@ -192,9 +192,6 @@ public class PaperweightRegen extends Regenerator super.chunkStati.put(new ChunkStatusWrap(s), c)); + chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c)); return true; } @@ -555,7 +552,7 @@ public class PaperweightRegen extends Regenerator processChunk(Long xz, List accessibleChunks) { + public CompletableFuture processChunk(List accessibleChunks) { return chunkStatus.generate( Runnable::run, // TODO revisit, we might profit from this somehow? freshWorld, diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java index 7c00ebb2b..d0d9bb652 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java @@ -22,14 +22,14 @@ import com.sk89q.worldedit.world.block.BaseBlock; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; import org.apache.logging.log4j.Logger; -import org.bukkit.World; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BlockPopulator; import org.bukkit.generator.WorldInfo; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; @@ -42,7 +42,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Function; -import java.util.stream.Collectors; /** * Represents an abstract regeneration handler. @@ -62,7 +61,7 @@ public abstract class Regenerator chunkStati = new LinkedHashMap<>(); + protected final Map chunkStatuses = new LinkedHashMap<>(); private final Long2ObjectLinkedOpenHashMap protoChunks = new Long2ObjectLinkedOpenHashMap<>(); private final Long2ObjectOpenHashMap chunks = new Long2ObjectOpenHashMap<>(); protected boolean generateConcurrent = true; @@ -85,19 +84,19 @@ public abstract class Regenerator> chunkCoordsForRadius = new Int2ObjectOpenHashMap<>(); - chunkStati.keySet().stream().map(ChunkStatusWrapper::requiredNeighborChunkRadius0).distinct().forEach(radius -> { + Int2ObjectOpenHashMap chunkCoordsForRadius = new Int2ObjectOpenHashMap<>(); + chunkStatuses.keySet().stream().mapToInt(ChunkStatusWrapper::requiredNeighborChunkRadius0).distinct().forEach(radius -> { if (radius == -1) { //ignore ChunkStatus.EMPTY return; } @@ -186,19 +185,19 @@ public abstract class Regenerator>> worldlimits = new Int2ObjectOpenHashMap<>(); - chunkStati.keySet().stream().map(ChunkStatusWrapper::requiredNeighborChunkRadius0).distinct().forEach(radius -> { + Int2ObjectOpenHashMap>> worldLimits = new Int2ObjectOpenHashMap<>(); + chunkStatuses.keySet().stream().mapToInt(ChunkStatusWrapper::requiredNeighborChunkRadius0).distinct().forEach(radius -> { if (radius == -1) { //ignore ChunkStatus.EMPTY return; } Long2ObjectOpenHashMap> map = new Long2ObjectOpenHashMap<>(); - for (Long xz : chunkCoordsForRadius.get(radius)) { + for (long xz : chunkCoordsForRadius.get(radius)) { int x = MathMan.unpairIntX(xz); int z = MathMan.unpairIntY(xz); List l = new ArrayList<>((radius + 1 + radius) * (radius + 1 + radius)); @@ -209,80 +208,63 @@ public abstract class Regenerator entry : chunkStati.entrySet()) { + for (Map.Entry entry : chunkStatuses.entrySet()) { ChunkStatus chunkStatus = entry.getKey(); int radius = chunkStatus.requiredNeighborChunkRadius0(); - List coords = chunkCoordsForRadius.get(radius); + long[] coords = chunkCoordsForRadius.get(radius); + Long2ObjectOpenHashMap> limitsForRadius = worldLimits.get(radius); if (this.generateConcurrent && entry.getValue() == Concurrency.RADIUS) { - SequentialTasks>> tasks = getChunkStatusTaskRows(coords, radius); - for (ConcurrentTasks> para : tasks) { + SequentialTasks> tasks = getChunkStatusTaskRows(coords, radius); + for (ConcurrentTasks para : tasks) { List scheduled = new ArrayList<>(tasks.size()); - for (SequentialTasks row : para) { + for (LongList row : para) { scheduled.add(() -> { - for (Long xz : row) { - chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz)); + for (long xz : row) { + chunkStatus.processChunkSave(xz, limitsForRadius.get(xz)); } }); } - try { - List> futures = new ArrayList<>(); - scheduled.forEach(task -> futures.add(executor.submit(task))); - for (Future future : futures) { - future.get(); - } - } catch (Exception e) { - e.printStackTrace(); - } + runAndWait(scheduled); } } else if (this.generateConcurrent && entry.getValue() == Concurrency.FULL) { // every chunk can be processed individually - List scheduled = new ArrayList<>(coords.size()); + List scheduled = new ArrayList<>(coords.length); for (long xz : coords) { - scheduled.add(() -> { - chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz)); - }); - } - try { - List> futures = new ArrayList<>(); - scheduled.forEach(task -> futures.add(executor.submit(task))); - for (Future future : futures) { - future.get(); - } - } catch (Exception e) { - e.printStackTrace(); + scheduled.add(() -> chunkStatus.processChunkSave(xz, limitsForRadius.get(xz))); } + runAndWait(scheduled); } else { // Concurrency.NONE or generateConcurrent == false // run sequential but submit to different thread // running regen on the main thread otherwise triggers async-only events on the main thread executor.submit(() -> { for (long xz : coords) { - chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz)); + chunkStatus.processChunkSave(xz, limitsForRadius.get(xz)); } }).get(); // wait until finished this step } } //convert to proper chunks - for (Long xz : chunkCoordsForRadius.get(0)) { + for (long xz : chunkCoordsForRadius.get(0)) { ProtoChunk proto = protoChunks.get(xz); chunks.put(xz, createChunk(proto)); } //final chunkstatus ChunkStatus FULL = getFullChunkStatus(); - for (Long xz : chunkCoordsForRadius.get(0)) { //FULL.requiredNeighbourChunkRadius() == 0! + for (long xz : chunkCoordsForRadius.get(0)) { //FULL.requiredNeighbourChunkRadius() == 0! Chunk chunk = chunks.get(xz); - FULL.processChunkSave(xz, Arrays.asList(chunk)); + FULL.processChunkSave(xz, List.of(chunk)); } //populate List populators = getBlockPopulators(); - for (Long xz : chunkCoordsForRadius.get(0)) { + for (long xz : chunkCoordsForRadius.get(0)) { int x = MathMan.unpairIntX(xz); int z = MathMan.unpairIntY(xz); @@ -302,6 +284,18 @@ public abstract class Regenerator tasks) { + try { + List> futures = new ArrayList<>(); + tasks.forEach(task -> futures.add(executor.submit(task))); + for (Future future : futures) { + future.get(); + } + } catch (Exception e) { + LOGGER.catching(e); + } + } + private void copyToWorld() { //Setting Blocks boolean genbiomes = options.shouldRegenBiomes(); @@ -437,7 +431,7 @@ public abstract class Regenerator initSourceQueueCache(); //algorithms - private List getChunkCoordsRegen(Region region, int border) { //needs to be square num of chunks + private long[] getChunkCoordsRegen(Region region, int border) { //needs to be square num of chunks BlockVector3 oldMin = region.getMinimumPoint(); BlockVector3 newMin = BlockVector3.at( (oldMin.getX() >> 4 << 4) - border * 16, @@ -455,76 +449,79 @@ public abstract class Regenerator MathMan.pairInt(c.getX(), c.getZ())) - .collect(Collectors.toList()); + .mapToLong(c -> MathMan.pairInt(c.getX(), c.getZ())) + .toArray(); } /** * Creates a list of chunkcoord rows that may be executed concurrently * - * @param allcoords the coords that should be sorted into rows, must be sorted by z and x + * @param allCoords the coords that should be sorted into rows, must be sorted by z and x * @param requiredNeighborChunkRadius the radius of neighbor chunks that may not be written to concurrently (ChunkStatus * .requiredNeighborRadius) * @return a list of chunkcoords rows that may be executed concurrently */ - private SequentialTasks>> getChunkStatusTaskRows( - List allcoords, + private SequentialTasks> getChunkStatusTaskRows( + long[] allCoords, int requiredNeighborChunkRadius ) { - int requiredneighbors = Math.max(0, requiredNeighborChunkRadius); + int requiredNeighbors = Math.max(0, requiredNeighborChunkRadius); - int minx = allcoords.isEmpty() ? 0 : MathMan.unpairIntX(allcoords.get(0)); - int maxx = allcoords.isEmpty() ? 0 : MathMan.unpairIntX(allcoords.get(allcoords.size() - 1)); - int minz = allcoords.isEmpty() ? 0 : MathMan.unpairIntY(allcoords.get(0)); - int maxz = allcoords.isEmpty() ? 0 : MathMan.unpairIntY(allcoords.get(allcoords.size() - 1)); - SequentialTasks>> tasks; - if (maxz - minz > maxx - minx) { - int numlists = Math.min(requiredneighbors * 2 + 1, maxx - minx + 1); + final int coordsCount = allCoords.length; + long first = coordsCount == 0 ? 0 : allCoords[0]; + long last = coordsCount == 0 ? 0 : allCoords[coordsCount - 1]; + int minX = MathMan.unpairIntX(first); + int maxX = MathMan.unpairIntX(last); + int minZ = MathMan.unpairIntY(first); + int maxZ = MathMan.unpairIntY(last); + SequentialTasks> tasks; + if (maxZ - minZ > maxX - minX) { + int numlists = Math.min(requiredNeighbors * 2 + 1, maxX - minX + 1); - Int2ObjectOpenHashMap> byx = new Int2ObjectOpenHashMap(); - int expectedListLength = (allcoords.size() + 1) / (maxx - minx); + Int2ObjectOpenHashMap byX = new Int2ObjectOpenHashMap<>(); + int expectedListLength = (coordsCount + 1) / (maxX - minX); //init lists - for (int i = minx; i <= maxx; i++) { - byx.put(i, new SequentialTasks(expectedListLength)); + for (int i = minX; i <= maxX; i++) { + byX.put(i, new LongArrayList(expectedListLength)); } //sort into lists by x coord - for (Long xz : allcoords) { - byx.get(MathMan.unpairIntX(xz)).add(xz); + for (long allCoord : allCoords) { + byX.get(MathMan.unpairIntX(allCoord)).add(allCoord); } //create parallel tasks - tasks = new SequentialTasks(numlists); + tasks = new SequentialTasks<>(numlists); for (int offset = 0; offset < numlists; offset++) { - ConcurrentTasks> para = new ConcurrentTasks((maxz - minz + 1) / numlists + 1); - for (int i = 0; minx + i * numlists + offset <= maxx; i++) { - para.add(byx.get(minx + i * numlists + offset)); + ConcurrentTasks para = new ConcurrentTasks<>((maxZ - minZ + 1) / numlists + 1); + for (int i = 0; minX + i * numlists + offset <= maxX; i++) { + para.add(byX.get(minX + i * numlists + offset)); } tasks.add(para); } } else { - int numlists = Math.min(requiredneighbors * 2 + 1, maxz - minz + 1); + int numlists = Math.min(requiredNeighbors * 2 + 1, maxZ - minZ + 1); - Int2ObjectOpenHashMap> byz = new Int2ObjectOpenHashMap(); - int expectedListLength = (allcoords.size() + 1) / (maxz - minz); + Int2ObjectOpenHashMap byZ = new Int2ObjectOpenHashMap<>(); + int expectedListLength = (coordsCount + 1) / (maxZ - minZ); //init lists - for (int i = minz; i <= maxz; i++) { - byz.put(i, new SequentialTasks(expectedListLength)); + for (int i = minZ; i <= maxZ; i++) { + byZ.put(i, new LongArrayList(expectedListLength)); } //sort into lists by x coord - for (Long xz : allcoords) { - byz.get(MathMan.unpairIntY(xz)).add(xz); + for (long allCoord : allCoords) { + byZ.get(MathMan.unpairIntY(allCoord)).add(allCoord); } //create parallel tasks - tasks = new SequentialTasks(numlists); + tasks = new SequentialTasks<>(numlists); for (int offset = 0; offset < numlists; offset++) { - ConcurrentTasks> para = new ConcurrentTasks((maxx - minx + 1) / numlists + 1); - for (int i = 0; minz + i * numlists + offset <= maxz; i++) { - para.add(byz.get(minz + i * numlists + offset)); + ConcurrentTasks para = new ConcurrentTasks<>((maxX - minX + 1) / numlists + 1); + for (int i = 0; minZ + i * numlists + offset <= maxZ; i++) { + para.add(byZ.get(minZ + i * numlists + offset)); } tasks.add(para); } @@ -576,15 +573,14 @@ public abstract class Regenerator processChunk(Long xz, List accessibleChunks); + public abstract CompletableFuture processChunk(List accessibleChunks); - void processChunkSave(Long xz, List accessibleChunks) { + void processChunkSave(long xz, List accessibleChunks) { try { - processChunk(xz, accessibleChunks).get(); + processChunk(accessibleChunks).get(); } catch (Exception e) { LOGGER.error( "Error while running " + name() + " on chunk " + MathMan.unpairIntX(xz) + "/" + MathMan.unpairIntY(xz), @@ -597,16 +593,16 @@ public abstract class Regenerator extends Tasks { - public SequentialTasks(int expectedsize) { - super(expectedsize); + public SequentialTasks(int expectedSize) { + super(expectedSize); } } public static class ConcurrentTasks extends Tasks { - public ConcurrentTasks(int expectedsize) { - super(expectedsize); + public ConcurrentTasks(int expectedSize) { + super(expectedSize); } } @@ -615,8 +611,8 @@ public abstract class Regenerator tasks; - public Tasks(int expectedsize) { - tasks = new ArrayList(expectedsize); + public Tasks(int expectedSize) { + tasks = new ArrayList<>(expectedSize); } public void add(T task) { From f36c5d42c7f250e11bfd57b06355a22b71eaf7f0 Mon Sep 17 00:00:00 2001 From: Hannes Greule Date: Sat, 9 Sep 2023 16:07:37 +0200 Subject: [PATCH 44/60] Avoid Unsafe usage where possible (#2403) --- .../PaperweightPlatformAdapter.java | 24 +++---------- .../v1_17_R1_2/regen/PaperweightRegen.java | 2 +- .../v1_18_R2/PaperweightPlatformAdapter.java | 35 ++++--------------- .../fawe/v1_18_R2/regen/PaperweightRegen.java | 2 +- .../v1_19_R3/PaperweightPlatformAdapter.java | 34 ++++-------------- .../fawe/v1_19_R3/regen/PaperweightRegen.java | 2 +- .../v1_20_R1/PaperweightPlatformAdapter.java | 34 ++++-------------- .../fawe/v1_20_R1/regen/PaperweightRegen.java | 2 +- .../core/util/ReflectionUtils.java | 19 ++++++++-- 9 files changed, 43 insertions(+), 111 deletions(-) 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 688a84a14..0854fc7ad 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 @@ -85,11 +85,7 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { private static final MethodHandle methodGetVisibleChunk; - private static final int CHUNKSECTION_BASE; - private static final int CHUNKSECTION_SHIFT; - private static final Field fieldLock; - private static final long fieldLockOffset; private static final Field fieldGameEventDispatcherSections; private static final MethodHandle methodremoveBlockEntityTicker; @@ -127,15 +123,12 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { getVisibleChunkIfPresent.setAccessible(true); methodGetVisibleChunk = MethodHandles.lookup().unreflect(getVisibleChunkIfPresent); - Unsafe unsafe = ReflectionUtils.getUnsafe(); if (!PaperLib.isPaper()) { - fieldLock = PalettedContainer.class.getDeclaredField(Refraction.pickName("lock", "m")); - fieldLockOffset = unsafe.objectFieldOffset(fieldLock); + fieldLock.setAccessible(true); } else { // in paper, the used methods are synchronized properly fieldLock = null; - fieldLockOffset = -1; } fieldGameEventDispatcherSections = LevelChunk.class.getDeclaredField(Refraction.pickName( @@ -152,13 +145,6 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "p")); fieldRemove.setAccessible(true); - - CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class); - int scale = unsafe.arrayIndexScale(LevelChunkSection[].class); - if ((scale & (scale - 1)) != 0) { - throw new Error("data type scale not a power of two"); - } - CHUNKSECTION_SHIFT = 31 - Integer.numberOfLeadingZeros(scale); } catch (RuntimeException e) { throw e; } catch (Throwable rethrow) { @@ -173,9 +159,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { LevelChunkSection value, int layer ) { - long offset = ((long) layer << CHUNKSECTION_SHIFT) + CHUNKSECTION_BASE; if (layer >= 0 && layer < sections.length) { - return ReflectionUtils.getUnsafe().compareAndSwapObject(sections, offset, expected, value); + return ReflectionUtils.compareAndSet(sections, expected, value, layer); } return false; } @@ -190,14 +175,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } try { synchronized (section) { - Unsafe unsafe = ReflectionUtils.getUnsafe(); PalettedContainer blocks = section.getStates(); - Semaphore currentLock = (Semaphore) unsafe.getObject(blocks, fieldLockOffset); + Semaphore currentLock = (Semaphore) fieldLock.get(blocks); if (currentLock instanceof DelegateSemaphore delegateSemaphore) { return delegateSemaphore; } DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock); - unsafe.putObject(blocks, fieldLockOffset, newLock); + fieldLock.set(blocks, newLock); return newLock; } } catch (Throwable e) { diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/regen/PaperweightRegen.java index 4d7a749de..63d93156b 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/regen/PaperweightRegen.java @@ -329,7 +329,7 @@ public class PaperweightRegen extends Regenerator= 0 && layer < sections.length) { - return ReflectionUtils.getUnsafe().compareAndSwapObject(sections, offset, expected, value); + return ReflectionUtils.compareAndSet(sections, expected, value, layer); } return false; } @@ -207,19 +188,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } try { synchronized (section) { - Unsafe unsafe = ReflectionUtils.getUnsafe(); PalettedContainer blocks = section.getStates(); - ThreadingDetector currentThreadingDetector = (ThreadingDetector) unsafe.getObject( - blocks, - fieldThreadingDetectorOffset - ); + ThreadingDetector currentThreadingDetector = (ThreadingDetector) fieldThreadingDetector.get(blocks); synchronized (currentThreadingDetector) { - Semaphore currentLock = (Semaphore) unsafe.getObject(currentThreadingDetector, fieldLockOffset); + Semaphore currentLock = (Semaphore) fieldLock.get(currentThreadingDetector); if (currentLock instanceof DelegateSemaphore delegateSemaphore) { return delegateSemaphore; } DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock); - unsafe.putObject(currentThreadingDetector, fieldLockOffset, newLock); + fieldLock.set(currentThreadingDetector, newLock); return newLock; } } diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/regen/PaperweightRegen.java index 34e18f748..8403d531d 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/regen/PaperweightRegen.java @@ -342,7 +342,7 @@ public class PaperweightRegen extends Regenerator= 0 && layer < sections.length) { - return ReflectionUtils.getUnsafe().compareAndSwapObject(sections, offset, expected, value); + return ReflectionUtils.compareAndSet(sections, expected, value, layer); } return false; } @@ -243,19 +225,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } try { synchronized (section) { - Unsafe unsafe = ReflectionUtils.getUnsafe(); PalettedContainer blocks = section.getStates(); - ThreadingDetector currentThreadingDetector = (ThreadingDetector) unsafe.getObject( - blocks, - fieldThreadingDetectorOffset - ); + ThreadingDetector currentThreadingDetector = (ThreadingDetector) fieldThreadingDetector.get(blocks); synchronized (currentThreadingDetector) { - Semaphore currentLock = (Semaphore) unsafe.getObject(currentThreadingDetector, fieldLockOffset); + Semaphore currentLock = (Semaphore) fieldLock.get(currentThreadingDetector); if (currentLock instanceof DelegateSemaphore delegateSemaphore) { return delegateSemaphore; } DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock); - unsafe.putObject(currentThreadingDetector, fieldLockOffset, newLock); + fieldLock.set(currentThreadingDetector, newLock); return newLock; } } diff --git a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/regen/PaperweightRegen.java index 22bae54fe..7e2f3eaee 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/regen/PaperweightRegen.java @@ -369,7 +369,7 @@ public class PaperweightRegen extends Regenerator= 0 && layer < sections.length) { - return ReflectionUtils.getUnsafe().compareAndSwapObject(sections, offset, expected, value); + return ReflectionUtils.compareAndSet(sections, expected, value, layer); } return false; } @@ -266,19 +248,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } try { synchronized (section) { - Unsafe unsafe = ReflectionUtils.getUnsafe(); PalettedContainer blocks = section.getStates(); - ThreadingDetector currentThreadingDetector = (ThreadingDetector) unsafe.getObject( - blocks, - fieldThreadingDetectorOffset - ); + ThreadingDetector currentThreadingDetector = (ThreadingDetector) fieldThreadingDetector.get(blocks); synchronized (currentThreadingDetector) { - Semaphore currentLock = (Semaphore) unsafe.getObject(currentThreadingDetector, fieldLockOffset); + Semaphore currentLock = (Semaphore) fieldLock.get(currentThreadingDetector); if (currentLock instanceof DelegateSemaphore delegateSemaphore) { return delegateSemaphore; } DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock); - unsafe.putObject(currentThreadingDetector, fieldLockOffset, newLock); + fieldLock.set(currentThreadingDetector, newLock); return newLock; } } diff --git a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/regen/PaperweightRegen.java index 812cf1d4c..99e001837 100644 --- a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/regen/PaperweightRegen.java @@ -370,7 +370,7 @@ public class PaperweightRegen extends Regenerator the type of elements in the array + * @return true if the value at the index was successfully updated to the new value, false otherwise + * @see VarHandle#compareAndSet(Object...) + */ + public static boolean compareAndSet(T[] array, T expectedValue, T newValue, int index) { + return REFERENCE_ARRAY_HANDLE.compareAndSet(array, index, expectedValue, newValue); + } + public static void setAccessibleNonFinal(Field field) { // let's make the field accessible field.setAccessible(true); From 2f309ff22b7e77629f2c3a94193d9d4b64ffa1ea Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 9 Sep 2023 16:08:54 +0200 Subject: [PATCH 45/60] Update dependency org.checkerframework:checker-qual to v3.38.0 (#2416) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 74858196d..d2a63c6ef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ sparsebitset = "1.2" parallelgzip = "1.0.5" adventure = "4.14.0" adventure-bukkit = "4.3.0" -checkerqual = "3.37.0" +checkerqual = "3.38.0" truezip = "6.8.4" auto-value = "1.10.2" findbugs = "3.0.2" From 0744504fad7d445fd333d5e361933a14b0045184 Mon Sep 17 00:00:00 2001 From: RedstoneFuture Date: Fri, 15 Sep 2023 22:41:28 +0200 Subject: [PATCH 46/60] Adding note about "worldedit.anyblock" permission (#2419) Adding note --- .../java/com/fastasyncworldedit/core/configuration/Settings.java | 1 + 1 file changed, 1 insertion(+) 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 174d59f37..d0aeb5058 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 @@ -400,6 +400,7 @@ public class Settings extends Config { "of a waterlogged fence). For blocking/remapping of all occurrences of a property like waterlogged, see", "remap-properties below.", "To generate a blank list, substitute the default content with a set of square brackets [] instead.", + "The 'worldedit.anyblock' permission is not considered here.", "Example block property blocking:", " - \"minecraft:conduit[waterlogged=true]\"", " - \"minecraft:piston[extended=false,facing=west]\"", From 81ba7d1acdea586fa7e515aa6b4b1bcff43aa29c Mon Sep 17 00:00:00 2001 From: Joo200 Date: Thu, 14 Sep 2023 12:07:11 +0200 Subject: [PATCH 47/60] Add worldedit runtime environment to increase yaml alias limit (#2395) This can be set by -Dworldedit.yaml.aliasLimit=XXX in the startup parameter (cherry picked from commit 94565356fd39bf360b35ee77ea5b3614d48be772) --- .../src/main/java/com/sk89q/util/yaml/YAMLProcessor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/worldedit-core/src/main/java/com/sk89q/util/yaml/YAMLProcessor.java b/worldedit-core/src/main/java/com/sk89q/util/yaml/YAMLProcessor.java index 146428c3e..f50bd991f 100644 --- a/worldedit-core/src/main/java/com/sk89q/util/yaml/YAMLProcessor.java +++ b/worldedit-core/src/main/java/com/sk89q/util/yaml/YAMLProcessor.java @@ -98,6 +98,8 @@ public class YAMLProcessor extends YAMLNode { LoaderOptions loaderOptions = new LoaderOptions(); try { + int yamlAliasLimit = Integer.getInteger("worldedit.yaml.aliasLimit", 50); + loaderOptions.setMaxAliasesForCollections(yamlAliasLimit); // 64 MB default int yamlCodePointLimit = Integer.getInteger("worldedit.yaml.codePointLimit", 64 * 1024 * 1024); loaderOptions.setCodePointLimit(yamlCodePointLimit); @@ -105,7 +107,7 @@ public class YAMLProcessor extends YAMLNode { // pre-1.32 snakeyaml } - yaml = new Yaml(new SafeConstructor(new LoaderOptions()), representer, dumperOptions, loaderOptions); + yaml = new Yaml(new SafeConstructor(loaderOptions), representer, dumperOptions, loaderOptions); this.file = file; } From 319bc0a5512a97ce65ea39b8f670b4eee30faeea Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Sat, 16 Sep 2023 23:44:14 +0200 Subject: [PATCH 48/60] Fix left click on air being ignored and right click on block being handled twice --- .../bukkit/BukkitServerInterface.java | 9 ++ .../worldedit/bukkit/WorldEditListener.java | 85 +++++++++---------- .../extension/platform/AbstractPlatform.java | 5 ++ .../extension/platform/Platform.java | 8 ++ .../internal/event/InteractionDebouncer.java | 69 +++++++++++++++ 5 files changed, 133 insertions(+), 43 deletions(-) create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/internal/event/InteractionDebouncer.java diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java index 8278142b6..d35ea9a28 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java @@ -42,6 +42,7 @@ import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.lifecycle.Lifecycled; import com.sk89q.worldedit.world.DataFixer; import com.sk89q.worldedit.world.registry.Registries; +import io.papermc.lib.PaperLib; import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; import org.bukkit.Server; @@ -258,6 +259,14 @@ public class BukkitServerInterface extends AbstractPlatform implements MultiUser return SUPPORTED_SIDE_EFFECTS; } + @Override + public long getTickCount() { + if (PaperLib.isPaper()) { + return Bukkit.getCurrentTick(); + } + return super.getTickCount(); + } + public void unregisterCommands() { dynamicCommands.unregisterCommands(); } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditListener.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditListener.java index 06f8f81ed..8c9db1c3e 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditListener.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditListener.java @@ -27,6 +27,7 @@ import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.event.platform.SessionIdleEvent; import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.internal.event.InteractionDebouncer; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.World; @@ -55,6 +56,7 @@ import java.util.Optional; public class WorldEditListener implements Listener { private final WorldEditPlugin plugin; + private final InteractionDebouncer debouncer; /** * Construct the object. @@ -63,6 +65,7 @@ public class WorldEditListener implements Listener { */ public WorldEditListener(WorldEditPlugin plugin) { this.plugin = plugin; + debouncer = new InteractionDebouncer(plugin.getInternalPlatform()); } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -128,62 +131,58 @@ public class WorldEditListener implements Listener { */ @EventHandler public void onPlayerInteract(PlayerInteractEvent event) { - if (!plugin.getInternalPlatform().isHookingEvents()) { - return; - } - - if (event.useItemInHand() == Result.DENY) { - return; - } - - if (event.getHand() == EquipmentSlot.OFF_HAND) { + if (!plugin.getInternalPlatform().isHookingEvents() + || event.useItemInHand() == Result.DENY + || event.getHand() == EquipmentSlot.OFF_HAND + || event.getAction() == Action.PHYSICAL) { return; } final Player player = plugin.wrapPlayer(event.getPlayer()); + + if (event.getAction() != Action.LEFT_CLICK_BLOCK) { + Optional previousResult = debouncer.getDuplicateInteractionResult(player); + if (previousResult.isPresent()) { + if (previousResult.get()) { + event.setCancelled(true); + } + return; + } + } + final World world = player.getWorld(); final WorldEdit we = plugin.getWorldEdit(); final Direction direction = BukkitAdapter.adapt(event.getBlockFace()); + final Block clickedBlock = event.getClickedBlock(); + final Location pos = clickedBlock == null ? null : new Location(world, clickedBlock.getX(), clickedBlock.getY(), clickedBlock.getZ()); - Action action = event.getAction(); - if (action == Action.LEFT_CLICK_BLOCK) { - final Block clickedBlock = event.getClickedBlock(); - final Location pos = new Location(world, clickedBlock.getX(), clickedBlock.getY(), clickedBlock.getZ()); - - if (we.handleBlockLeftClick(player, pos, direction)) { - event.setCancelled(true); - } - - if (we.handleArmSwing(player)) { - event.setCancelled(true); - } - - } else if (action == Action.LEFT_CLICK_AIR) { - - if (we.handleArmSwing(player)) { - event.setCancelled(true); - } - - } else if (action == Action.RIGHT_CLICK_BLOCK) { - final Block clickedBlock = event.getClickedBlock(); - final Location pos = new Location(world, clickedBlock.getX(), clickedBlock.getY(), clickedBlock.getZ()); - - if (we.handleBlockRightClick(player, pos, direction)) { - event.setCancelled(true); - } - - if (we.handleRightClick(player)) { - event.setCancelled(true); - } - } else if (action == Action.RIGHT_CLICK_AIR) { - if (we.handleRightClick(player)) { - event.setCancelled(true); - } + boolean result = false; + switch (event.getAction()) { + case LEFT_CLICK_BLOCK: + result = we.handleBlockLeftClick(player, pos, direction) || we.handleArmSwing(player); + break; + case LEFT_CLICK_AIR: + result = we.handleArmSwing(player); + break; + case RIGHT_CLICK_BLOCK: + result = we.handleBlockRightClick(player, pos, direction) || we.handleRightClick(player); + break; + case RIGHT_CLICK_AIR: + result = we.handleRightClick(player); + break; + default: + break; + } + debouncer.setLastInteraction(player, result); + if (result) { + event.setCancelled(true); } } @EventHandler public void onPlayerQuit(PlayerQuitEvent event) { + debouncer.clearInteraction(plugin.wrapPlayer(event.getPlayer())); + plugin.getWorldEdit().getEventBus().post(new SessionIdleEvent(new BukkitPlayer.SessionKeyImpl(event.getPlayer()))); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlatform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlatform.java index 533fe054d..c8f908b4c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlatform.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlatform.java @@ -55,4 +55,9 @@ public abstract class AbstractPlatform implements Platform { return null; } + @Override + public long getTickCount() { + return System.nanoTime() / 50_000_000; + } + } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java index 822e7a3f8..f20f3ce9b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java @@ -216,6 +216,14 @@ public interface Platform extends Keyed { */ Set getSupportedSideEffects(); + /** + * Get the number of ticks since the server started. + * On some platforms this value may be an approximation based on the JVM run time. + * + * @return The number of ticks since the server started. + */ + long getTickCount(); + //FAWE start /** diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/event/InteractionDebouncer.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/event/InteractionDebouncer.java new file mode 100644 index 000000000..b33570cd9 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/event/InteractionDebouncer.java @@ -0,0 +1,69 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.internal.event; + +import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.util.Identifiable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +public class InteractionDebouncer { + private final Platform platform; + private final Map lastInteractions = new HashMap<>(); + + public InteractionDebouncer(Platform platform) { + this.platform = platform; + } + + public void clearInteraction(Identifiable player) { + lastInteractions.remove(player.getUniqueId()); + } + + public void setLastInteraction(Identifiable player, boolean result) { + lastInteractions.put(player.getUniqueId(), new Interaction(platform.getTickCount(), result)); + } + + public Optional getDuplicateInteractionResult(Identifiable player) { + Interaction last = lastInteractions.get(player.getUniqueId()); + if (last == null) { + return Optional.empty(); + } + + long now = platform.getTickCount(); + if (now - last.tick <= 1) { + return Optional.of(last.result); + } + + return Optional.empty(); + } + + private static class Interaction { + public final long tick; + public final boolean result; + + public Interaction(long tick, boolean result) { + this.tick = tick; + this.result = result; + } + } +} From 7b0f1b3c5a64f8aa1096e1d0a9615017bf47e1dd Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Sat, 16 Sep 2023 23:47:19 +0200 Subject: [PATCH 49/60] Update paperweight --- worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts index 2f65af4ce..20bff7359 100644 --- a/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts @@ -12,6 +12,6 @@ repositories { dependencies { // https://repo.papermc.io/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/ - the().paperDevBundle("1.20.1-R0.1-20230818.021330-113") + the().paperDevBundle("1.20.1-R0.1-20230916.212543-167") compileOnly(libs.paperlib) } From 889e9b7185918d16ceb43f19a8a0bdd5ed5bf67a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 12:41:27 +0200 Subject: [PATCH 50/60] Update dependency com.modrinth.minotaur to v2.8.4 (#2429) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d2a63c6ef..b73ec8509 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -50,7 +50,7 @@ mockito = "5.5.0" # Gradle plugins pluginyml = "0.6.0" -minotaur = "2.8.3" +minotaur = "2.8.4" [libraries] # Minecraft expectations From 45b60f4929a3cfb72cbad23934769c596cbb5030 Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Fri, 29 Sep 2023 22:00:58 +0200 Subject: [PATCH 51/60] Add support for 1.20.2 (#2431) * Update to 1.20.2 * Update paperweight --------- Co-authored-by: Pierre Maurice Schwang --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- build.gradle.kts | 4 +- gradle/libs.versions.toml | 2 +- settings.gradle.kts | 2 +- .../adapters/adapter-1_20_2/build.gradle.kts | 17 + .../ext/fawe/v1_20_R2/PaperweightAdapter.java | 1022 ++++++ .../v1_20_R2/PaperweightDataConverters.java | 2801 +++++++++++++++++ .../fawe/v1_20_R2/PaperweightFakePlayer.java | 98 + .../PaperweightWorldNativeAccess.java | 181 ++ .../v1_20_R2/PaperweightBlockMaterial.java | 185 ++ .../fawe/v1_20_R2/PaperweightFaweAdapter.java | 652 ++++ .../PaperweightFaweWorldNativeAccess.java | 287 ++ .../fawe/v1_20_R2/PaperweightGetBlocks.java | 1147 +++++++ .../v1_20_R2/PaperweightGetBlocks_Copy.java | 248 ++ .../v1_20_R2/PaperweightMapChunkUtil.java | 34 + .../v1_20_R2/PaperweightPlatformAdapter.java | 719 +++++ .../v1_20_R2/PaperweightPostProcessor.java | 175 + .../PaperweightStarlightRelighter.java | 205 ++ .../PaperweightStarlightRelighterFactory.java | 28 + .../nbt/PaperweightLazyCompoundTag.java | 161 + .../fawe/v1_20_R2/regen/PaperweightRegen.java | 591 ++++ worldedit-bukkit/build.gradle.kts | 2 +- worldedit-core/build.gradle.kts | 2 +- 23 files changed, 8558 insertions(+), 7 deletions(-) create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/build.gradle.kts create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightDataConverters.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightFakePlayer.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightWorldNativeAccess.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightBlockMaterial.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweWorldNativeAccess.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks_Copy.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightMapChunkUtil.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPlatformAdapter.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPostProcessor.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighter.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighterFactory.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/nbt/PaperweightLazyCompoundTag.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/regen/PaperweightRegen.java diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 8a134b81e..560478e0e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -27,7 +27,7 @@ body: description: Which server version version you using? If your server version is not listed, it is not supported. Update to a supported version first. multiple: false options: - - '1.20.1' + - '1.20.2' - '1.20' - '1.19.4' - '1.18.2' diff --git a/build.gradle.kts b/build.gradle.kts index e441c1336..ed699857f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -83,7 +83,7 @@ allprojects { } applyCommonConfiguration() -val supportedVersions = listOf("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.2") tasks { supportedVersions.forEach { @@ -97,7 +97,7 @@ tasks { } } runServer { - minecraftVersion("1.20.1") + minecraftVersion("1.20.2") pluginJars(*project(":worldedit-bukkit").getTasksByName("shadowJar", false).map { (it as Jar).archiveFile } .toTypedArray()) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b73ec8509..1fe45859b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # Minecraft expectations -paper = "1.20.1-R0.1-SNAPSHOT" +paper = "1.20.2-R0.1-SNAPSHOT" fastutil = "8.5.9" guava = "31.1-jre" log4j = "2.19.0" diff --git a/settings.gradle.kts b/settings.gradle.kts index 05439cd10..4c6995e2e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ rootProject.name = "FastAsyncWorldEdit" include("worldedit-libs") -listOf("legacy", "1_17_1", "1_18_2", "1_19_4", "1_20").forEach { +listOf("legacy", "1_17_1", "1_18_2", "1_19_4", "1_20", "1_20_2").forEach { include("worldedit-bukkit:adapters:adapter-$it") } diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_20_2/build.gradle.kts new file mode 100644 index 000000000..540d25cca --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/build.gradle.kts @@ -0,0 +1,17 @@ +import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension + +plugins { + java +} + +applyPaperweightAdapterConfiguration() + +repositories { + gradlePluginPortal() +} + +dependencies { + // https://repo.papermc.io/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/ + the().paperDevBundle("1.20.2-R0.1-20230929.031919-14") + compileOnly(libs.paperlib) +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java new file mode 100644 index 000000000..38b0701d4 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java @@ -0,0 +1,1022 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.Futures; +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Lifecycle; +import com.sk89q.jnbt.NBTConstants; +import com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2.PaperweightDataConverters; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightFakePlayer; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseItem; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extension.platform.Watchdog; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.registry.state.BooleanProperty; +import com.sk89q.worldedit.registry.state.DirectionalProperty; +import com.sk89q.worldedit.registry.state.EnumProperty; +import com.sk89q.worldedit.registry.state.IntegerProperty; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.util.io.file.SafeFiles; +import com.sk89q.worldedit.util.nbt.BinaryTag; +import com.sk89q.worldedit.util.nbt.ByteArrayBinaryTag; +import com.sk89q.worldedit.util.nbt.ByteBinaryTag; +import com.sk89q.worldedit.util.nbt.CompoundBinaryTag; +import com.sk89q.worldedit.util.nbt.DoubleBinaryTag; +import com.sk89q.worldedit.util.nbt.EndBinaryTag; +import com.sk89q.worldedit.util.nbt.FloatBinaryTag; +import com.sk89q.worldedit.util.nbt.IntArrayBinaryTag; +import com.sk89q.worldedit.util.nbt.IntBinaryTag; +import com.sk89q.worldedit.util.nbt.ListBinaryTag; +import com.sk89q.worldedit.util.nbt.LongArrayBinaryTag; +import com.sk89q.worldedit.util.nbt.LongBinaryTag; +import com.sk89q.worldedit.util.nbt.ShortBinaryTag; +import com.sk89q.worldedit.util.nbt.StringBinaryTag; +import com.sk89q.worldedit.world.DataFixer; +import com.sk89q.worldedit.world.RegenOptions; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.biome.BiomeTypes; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.item.ItemType; +import net.minecraft.Util; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.network.protocol.game.ClientboundEntityEventPacket; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.StringRepresentable; +import net.minecraft.util.thread.BlockableEventLoop; +import net.minecraft.world.Clearable; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelSettings; +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.block.entity.StructureBlockEntity; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.WorldOptions; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World.Environment; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.v1_20_R2.CraftServer; +import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_20_R2.util.CraftMagicNumbers; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import org.bukkit.generator.ChunkGenerator; +import org.spigotmc.SpigotConfig; +import org.spigotmc.WatchdogThread; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +public final class PaperweightAdapter implements BukkitImplAdapter { + + private final Logger logger = Logger.getLogger(getClass().getCanonicalName()); + + private final Field serverWorldsField; + private final Method getChunkFutureMethod; + private final Field chunkProviderExecutorField; + private final Watchdog watchdog; + + // ------------------------------------------------------------------------ + // Code that may break between versions of Minecraft + // ------------------------------------------------------------------------ + + public PaperweightAdapter() throws NoSuchFieldException, NoSuchMethodException { + // A simple test + CraftServer.class.cast(Bukkit.getServer()); + + int dataVersion = CraftMagicNumbers.INSTANCE.getDataVersion(); + if (dataVersion != 3578) { + throw new UnsupportedClassVersionError("Not 1.20.2!"); + } + + serverWorldsField = CraftServer.class.getDeclaredField("worlds"); + serverWorldsField.setAccessible(true); + + getChunkFutureMethod = ServerChunkCache.class.getDeclaredMethod( + Refraction.pickName("getChunkFutureMainThread", "c"), + int.class, int.class, ChunkStatus.class, boolean.class + ); + getChunkFutureMethod.setAccessible(true); + + chunkProviderExecutorField = ServerChunkCache.class.getDeclaredField( + Refraction.pickName("mainThreadProcessor", "g") + ); + chunkProviderExecutorField.setAccessible(true); + + new PaperweightDataConverters(CraftMagicNumbers.INSTANCE.getDataVersion(), this).buildUnoptimized(); + + Watchdog watchdog; + try { + Class.forName("org.spigotmc.WatchdogThread"); + watchdog = new SpigotWatchdog(); + } catch (ClassNotFoundException | NoSuchFieldException e) { + try { + watchdog = new MojangWatchdog(((CraftServer) Bukkit.getServer()).getServer()); + } catch (NoSuchFieldException ex) { + watchdog = null; + } + } + this.watchdog = watchdog; + + try { + Class.forName("org.spigotmc.SpigotConfig"); + SpigotConfig.config.set("world-settings.faweregentempworld.verbose", false); + } catch (ClassNotFoundException ignored) { + } + } + + @Override + public DataFixer getDataFixer() { + return PaperweightDataConverters.INSTANCE; + } + + /** + * Read the given NBT data into the given tile entity. + * + * @param tileEntity the tile entity + * @param tag the tag + */ + static void readTagIntoTileEntity(net.minecraft.nbt.CompoundTag tag, BlockEntity tileEntity) { + tileEntity.load(tag); + tileEntity.setChanged(); + } + + /** + * Get the ID string of the given entity. + * + * @param entity the entity + * @return the entity ID + */ + private static String getEntityId(Entity entity) { + return EntityType.getKey(entity.getType()).toString(); + } + + /** + * Create an entity using the given entity ID. + * + * @param id the entity ID + * @param world the world + * @return an entity or null + */ + @Nullable + private static Entity createEntityFromId(String id, net.minecraft.world.level.Level world) { + return EntityType.byString(id).map(t -> t.create(world)).orElse(null); + } + + /** + * Write the given NBT data into the given entity. + * + * @param entity the entity + * @param tag the tag + */ + private static void readTagIntoEntity(net.minecraft.nbt.CompoundTag tag, Entity entity) { + entity.load(tag); + } + + /** + * Write the entity's NBT data to the given tag. + * + * @param entity the entity + * @param tag the tag + */ + private static void readEntityIntoTag(Entity entity, net.minecraft.nbt.CompoundTag tag) { + entity.save(tag); + } + + private static Block getBlockFromType(BlockType blockType) { + + return DedicatedServer.getServer().registryAccess().registryOrThrow(Registries.BLOCK).get(ResourceLocation.tryParse(blockType.getId())); + } + + private static Item getItemFromType(ItemType itemType) { + return DedicatedServer.getServer().registryAccess().registryOrThrow(Registries.ITEM).get(ResourceLocation.tryParse(itemType.getId())); + } + + @Override + public OptionalInt getInternalBlockStateId(BlockData data) { + net.minecraft.world.level.block.state.BlockState state = ((CraftBlockData) data).getState(); + int combinedId = Block.getId(state); + return combinedId == 0 && state.getBlock() != Blocks.AIR ? OptionalInt.empty() : OptionalInt.of(combinedId); + } + + @Override + public OptionalInt getInternalBlockStateId(BlockState state) { + Block mcBlock = getBlockFromType(state.getBlockType()); + net.minecraft.world.level.block.state.BlockState newState = mcBlock.defaultBlockState(); + Map, Object> states = state.getStates(); + newState = applyProperties(mcBlock.getStateDefinition(), newState, states); + final int combinedId = Block.getId(newState); + return combinedId == 0 && state.getBlockType() != BlockTypes.AIR ? OptionalInt.empty() : OptionalInt.of(combinedId); + } + + @Override + public BlockState getBlock(Location location) { + checkNotNull(location); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + final ServerLevel handle = craftWorld.getHandle(); + LevelChunk chunk = handle.getChunk(x >> 4, z >> 4); + final BlockPos blockPos = new BlockPos(x, y, z); + final net.minecraft.world.level.block.state.BlockState blockData = chunk.getBlockState(blockPos); + int internalId = Block.getId(blockData); + BlockState state = BlockStateIdAccess.getBlockStateById(internalId); + if (state == null) { + org.bukkit.block.Block bukkitBlock = location.getBlock(); + state = BukkitAdapter.adapt(bukkitBlock.getBlockData()); + } + + return state; + } + + @Override + public BaseBlock getFullBlock(Location location) { + BlockState state = getBlock(location); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + final ServerLevel handle = craftWorld.getHandle(); + LevelChunk chunk = handle.getChunk(x >> 4, z >> 4); + final BlockPos blockPos = new BlockPos(x, y, z); + + // Read the NBT data + BlockEntity te = chunk.getBlockEntity(blockPos); + if (te != null) { + net.minecraft.nbt.CompoundTag tag = te.saveWithId(); + return state.toBaseBlock((CompoundBinaryTag) toNativeBinary(tag)); + } + + return state.toBaseBlock(); + } + + private static final HashMap> biomeTypeToNMSCache = new HashMap<>(); + private static final HashMap, BiomeType> biomeTypeFromNMSCache = new HashMap<>(); + + @Override + public WorldNativeAccess createWorldNativeAccess(org.bukkit.World world) { + return new com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightWorldNativeAccess(this, + new WeakReference<>(((CraftWorld) world).getHandle())); + } + + private static net.minecraft.core.Direction adapt(Direction face) { + switch (face) { + case NORTH: + return net.minecraft.core.Direction.NORTH; + case SOUTH: + return net.minecraft.core.Direction.SOUTH; + case WEST: + return net.minecraft.core.Direction.WEST; + case EAST: + return net.minecraft.core.Direction.EAST; + case DOWN: + return net.minecraft.core.Direction.DOWN; + case UP: + default: + return net.minecraft.core.Direction.UP; + } + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private net.minecraft.world.level.block.state.BlockState applyProperties( + StateDefinition stateContainer, + net.minecraft.world.level.block.state.BlockState newState, + Map, Object> states + ) { + for (Map.Entry, Object> state : states.entrySet()) { + net.minecraft.world.level.block.state.properties.Property property = + stateContainer.getProperty(state.getKey().getName()); + Comparable value = (Comparable) state.getValue(); + // we may need to adapt this value, depending on the source prop + if (property instanceof DirectionProperty) { + Direction dir = (Direction) value; + value = adapt(dir); + } else if (property instanceof net.minecraft.world.level.block.state.properties.EnumProperty) { + String enumName = (String) value; + value = ((net.minecraft.world.level.block.state.properties.EnumProperty) property) + .getValue(enumName).orElseThrow(() -> + new IllegalStateException( + "Enum property " + property.getName() + " does not contain " + enumName + ) + ); + } + + newState = newState.setValue( + (net.minecraft.world.level.block.state.properties.Property) property, + (Comparable) value + ); + } + return newState; + } + + @Override + public BaseEntity getEntity(org.bukkit.entity.Entity entity) { + checkNotNull(entity); + + CraftEntity craftEntity = ((CraftEntity) entity); + Entity mcEntity = craftEntity.getHandle(); + + // Do not allow creating of passenger entity snapshots, passengers are included in the vehicle entity + if (mcEntity.isPassenger()) { + return null; + } + + String id = getEntityId(mcEntity); + + net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); + readEntityIntoTag(mcEntity, tag); + return new BaseEntity( + com.sk89q.worldedit.world.entity.EntityTypes.get(id), + LazyReference.from(() -> (CompoundBinaryTag) toNativeBinary(tag)) + ); + } + + @Nullable + @Override + public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state) { + checkNotNull(location); + checkNotNull(state); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + ServerLevel worldServer = craftWorld.getHandle(); + + Entity createdEntity = createEntityFromId(state.getType().getId(), craftWorld.getHandle()); + + if (createdEntity != null) { + CompoundBinaryTag nativeTag = state.getNbt(); + if (nativeTag != null) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) fromNativeBinary(nativeTag); + for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); + } + readTagIntoEntity(tag, createdEntity); + } + + createdEntity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + + worldServer.addFreshEntity(createdEntity, SpawnReason.CUSTOM); + return createdEntity.getBukkitEntity(); + } else { + return null; + } + } + + // This removes all unwanted tags from the main entity and all its passengers + private void removeUnwantedEntityTagsRecursively(net.minecraft.nbt.CompoundTag tag) { + for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); + } + + // Adapted from net.minecraft.world.entity.EntityType#loadEntityRecursive + if (tag.contains("Passengers", NBTConstants.TYPE_LIST)) { + net.minecraft.nbt.ListTag nbttaglist = tag.getList("Passengers", NBTConstants.TYPE_COMPOUND); + + for (int i = 0; i < nbttaglist.size(); ++i) { + removeUnwantedEntityTagsRecursively(nbttaglist.getCompound(i)); + } + } + } + + @Override + public Component getRichBlockName(BlockType blockType) { + return TranslatableComponent.of(getBlockFromType(blockType).getDescriptionId()); + } + + @Override + public Component getRichItemName(ItemType itemType) { + return TranslatableComponent.of(getItemFromType(itemType).getDescriptionId()); + } + + @Override + public Component getRichItemName(BaseItemStack itemStack) { + return TranslatableComponent.of(CraftItemStack.asNMSCopy(BukkitAdapter.adapt(itemStack)).getDescriptionId()); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static final LoadingCache> PROPERTY_CACHE = CacheBuilder.newBuilder().build(new CacheLoader>() { + @Override + public Property load(net.minecraft.world.level.block.state.properties.Property state) throws Exception { + if (state instanceof net.minecraft.world.level.block.state.properties.BooleanProperty) { + return new BooleanProperty(state.getName(), ImmutableList.copyOf(state.getPossibleValues())); + } else if (state instanceof DirectionProperty) { + return new DirectionalProperty(state.getName(), + (List) state.getPossibleValues().stream().map(e -> Direction.valueOf(((StringRepresentable) e).getSerializedName().toUpperCase(Locale.ROOT))).collect(Collectors.toList())); + } else if (state instanceof net.minecraft.world.level.block.state.properties.EnumProperty) { + return new EnumProperty(state.getName(), + (List) state.getPossibleValues().stream().map(e -> ((StringRepresentable) e).getSerializedName()).collect(Collectors.toList())); + } else if (state instanceof net.minecraft.world.level.block.state.properties.IntegerProperty) { + return new IntegerProperty(state.getName(), ImmutableList.copyOf(state.getPossibleValues())); + } else { + throw new IllegalArgumentException("WorldEdit needs an update to support " + state.getClass().getSimpleName()); + } + } + }); + + @SuppressWarnings({ "rawtypes" }) + @Override + public Map> getProperties(BlockType blockType) { + Map> properties = new TreeMap<>(); + Block block = getBlockFromType(blockType); + StateDefinition blockStateList = + block.getStateDefinition(); + for (net.minecraft.world.level.block.state.properties.Property state : blockStateList.getProperties()) { + Property property = PROPERTY_CACHE.getUnchecked(state); + properties.put(property.getName(), property); + } + return properties; + } + + @Override + public void sendFakeNBT(Player player, BlockVector3 pos, CompoundBinaryTag nbtData) { + ((CraftPlayer) player).getHandle().connection.send(ClientboundBlockEntityDataPacket.create( + new StructureBlockEntity( + new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()), + Blocks.STRUCTURE_BLOCK.defaultBlockState() + ), + __ -> (net.minecraft.nbt.CompoundTag) fromNativeBinary(nbtData) + )); + } + + @Override + public void sendFakeOP(Player player) { + ((CraftPlayer) player).getHandle().connection.send(new ClientboundEntityEventPacket( + ((CraftPlayer) player).getHandle(), (byte) 28 + )); + } + + @Override + public org.bukkit.inventory.ItemStack adapt(BaseItemStack item) { + ItemStack stack = new ItemStack( + DedicatedServer.getServer().registryAccess().registryOrThrow(Registries.ITEM).get(ResourceLocation.tryParse(item.getType().getId())), + item.getAmount() + ); + stack.setTag(((net.minecraft.nbt.CompoundTag) fromNative(item.getNbtData()))); + return CraftItemStack.asCraftMirror(stack); + } + + @Override + public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { + final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack); + final BaseItemStack weStack = new BaseItemStack(BukkitAdapter.asItemType(itemStack.getType()), itemStack.getAmount()); + weStack.setNbt(((CompoundBinaryTag) toNativeBinary(nmsStack.getTag()))); + return weStack; + } + + private final LoadingCache fakePlayers + = CacheBuilder.newBuilder().weakKeys().softValues().build(CacheLoader.from(PaperweightFakePlayer::new)); + + @Override + public boolean simulateItemUse(org.bukkit.World world, BlockVector3 position, BaseItem item, Direction face) { + CraftWorld craftWorld = (CraftWorld) world; + ServerLevel worldServer = craftWorld.getHandle(); + ItemStack stack = CraftItemStack.asNMSCopy(BukkitAdapter.adapt(item instanceof BaseItemStack + ? ((BaseItemStack) item) : new BaseItemStack(item.getType(), item.getNbtData(), 1))); + stack.setTag((net.minecraft.nbt.CompoundTag) fromNative(item.getNbtData())); + + PaperweightFakePlayer fakePlayer; + try { + fakePlayer = fakePlayers.get(worldServer); + } catch (ExecutionException ignored) { + return false; + } + fakePlayer.setItemInHand(InteractionHand.MAIN_HAND, stack); + fakePlayer.absMoveTo(position.getBlockX(), position.getBlockY(), position.getBlockZ(), + (float) face.toVector().toYaw(), (float) face.toVector().toPitch()); + + final BlockPos blockPos = new BlockPos(position.getBlockX(), position.getBlockY(), position.getBlockZ()); + final Vec3 blockVec = Vec3.atLowerCornerOf(blockPos); + final net.minecraft.core.Direction enumFacing = adapt(face); + BlockHitResult rayTrace = new BlockHitResult(blockVec, enumFacing, blockPos, false); + UseOnContext context = new UseOnContext(fakePlayer, InteractionHand.MAIN_HAND, rayTrace); + InteractionResult result = stack.useOn(context); + if (result != InteractionResult.SUCCESS) { + if (worldServer.getBlockState(blockPos).use(worldServer, fakePlayer, InteractionHand.MAIN_HAND, rayTrace).consumesAction()) { + result = InteractionResult.SUCCESS; + } else { + result = stack.getItem().use(worldServer, fakePlayer, InteractionHand.MAIN_HAND).getResult(); + } + } + + return result == InteractionResult.SUCCESS; + } + + @Override + public boolean canPlaceAt(org.bukkit.World world, BlockVector3 position, BlockState blockState) { + int internalId = BlockStateIdAccess.getBlockStateId(blockState); + net.minecraft.world.level.block.state.BlockState blockData = Block.stateById(internalId); + return blockData.canSurvive(((CraftWorld) world).getHandle(), new BlockPos(position.getX(), position.getY(), position.getZ())); + } + + @Override + public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent extent, RegenOptions options) { + try { + doRegen(bukkitWorld, region, extent, options); + } catch (Exception e) { + throw new IllegalStateException("Regen failed.", e); + } + + return true; + } + + private void doRegen(org.bukkit.World bukkitWorld, Region region, Extent extent, RegenOptions options) throws Exception { + Environment env = bukkitWorld.getEnvironment(); + ChunkGenerator gen = bukkitWorld.getGenerator(); + + Path tempDir = Files.createTempDirectory("WorldEditWorldGen"); + LevelStorageSource levelStorage = LevelStorageSource.createDefault(tempDir); + ResourceKey worldDimKey = getWorldDimKey(env); + try (LevelStorageSource.LevelStorageAccess session = levelStorage.createAccess("faweregentempworld", worldDimKey)) { + ServerLevel originalWorld = ((CraftWorld) bukkitWorld).getHandle(); + PrimaryLevelData levelProperties = (PrimaryLevelData) originalWorld.getServer() + .getWorldData().overworldData(); + WorldOptions originalOpts = levelProperties.worldGenOptions(); + + long seed = options.getSeed().orElse(originalWorld.getSeed()); + WorldOptions newOpts = options.getSeed().isPresent() + ? originalOpts.withSeed(OptionalLong.of(seed)) + : originalOpts; + + LevelSettings newWorldSettings = new LevelSettings( + "faweregentempworld", + levelProperties.settings.gameType(), + levelProperties.settings.hardcore(), + levelProperties.settings.difficulty(), + levelProperties.settings.allowCommands(), + levelProperties.settings.gameRules(), + levelProperties.settings.getDataConfiguration() + ); + + PrimaryLevelData.SpecialWorldProperty specialWorldProperty = + levelProperties.isFlatWorld() + ? PrimaryLevelData.SpecialWorldProperty.FLAT + : levelProperties.isDebugWorld() + ? PrimaryLevelData.SpecialWorldProperty.DEBUG + : PrimaryLevelData.SpecialWorldProperty.NONE; + + PrimaryLevelData newWorldData = new PrimaryLevelData(newWorldSettings, newOpts, specialWorldProperty, Lifecycle.stable()); + + ServerLevel freshWorld = new ServerLevel( + originalWorld.getServer(), + originalWorld.getServer().executor, + session, newWorldData, + originalWorld.dimension(), + new LevelStem( + originalWorld.dimensionTypeRegistration(), + originalWorld.getChunkSource().getGenerator() + ), + new NoOpWorldLoadListener(), + originalWorld.isDebug(), + seed, + ImmutableList.of(), + false, + originalWorld.getRandomSequences(), + env, + gen, + bukkitWorld.getBiomeProvider() + ); + try { + regenForWorld(region, extent, freshWorld, options); + } finally { + freshWorld.getChunkSource().close(false); + } + } finally { + try { + @SuppressWarnings("unchecked") + Map map = (Map) serverWorldsField.get(Bukkit.getServer()); + map.remove("faweregentempworld"); + } catch (IllegalAccessException ignored) { + } + SafeFiles.tryHardToDeleteDir(tempDir); + } + } + + private BiomeType adapt(ServerLevel serverWorld, Biome origBiome) { + ResourceLocation key = serverWorld.registryAccess().registryOrThrow(Registries.BIOME).getKey(origBiome); + if (key == null) { + return null; + } + return BiomeTypes.get(key.toString()); + } + + @SuppressWarnings("unchecked") + private void regenForWorld(Region region, Extent extent, ServerLevel serverWorld, RegenOptions options) throws WorldEditException { + List> chunkLoadings = submitChunkLoadTasks(region, serverWorld); + BlockableEventLoop executor; + try { + executor = (BlockableEventLoop) chunkProviderExecutorField.get(serverWorld.getChunkSource()); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Couldn't get executor for chunk loading.", e); + } + executor.managedBlock(() -> { + // bail out early if a future fails + if (chunkLoadings.stream().anyMatch(ftr -> + ftr.isDone() && Futures.getUnchecked(ftr) == null + )) { + return false; + } + return chunkLoadings.stream().allMatch(CompletableFuture::isDone); + }); + Map chunks = new HashMap<>(); + for (CompletableFuture future : chunkLoadings) { + @Nullable + ChunkAccess chunk = future.getNow(null); + checkState(chunk != null, "Failed to generate a chunk, regen failed."); + chunks.put(chunk.getPos(), chunk); + } + + for (BlockVector3 vec : region) { + BlockPos pos = new BlockPos(vec.getBlockX(), vec.getBlockY(), vec.getBlockZ()); + ChunkAccess chunk = chunks.get(new ChunkPos(pos)); + final net.minecraft.world.level.block.state.BlockState blockData = chunk.getBlockState(pos); + int internalId = Block.getId(blockData); + BlockStateHolder state = BlockStateIdAccess.getBlockStateById(internalId); + Objects.requireNonNull(state); + BlockEntity blockEntity = chunk.getBlockEntity(pos); + if (blockEntity != null) { + net.minecraft.nbt.CompoundTag tag = blockEntity.saveWithId(); + state = state.toBaseBlock(((CompoundBinaryTag) toNativeBinary(tag))); + } + extent.setBlock(vec, state.toBaseBlock()); + if (options.shouldRegenBiomes()) { + Biome origBiome = chunk.getNoiseBiome(vec.getX(), vec.getY(), vec.getZ()).value(); + BiomeType adaptedBiome = adapt(serverWorld, origBiome); + if (adaptedBiome != null) { + extent.setBiome(vec, adaptedBiome); + } + } + } + } + + @SuppressWarnings("unchecked") + private List> submitChunkLoadTasks(Region region, ServerLevel serverWorld) { + ServerChunkCache chunkManager = serverWorld.getChunkSource(); + List> chunkLoadings = new ArrayList<>(); + // Pre-gen all the chunks + for (BlockVector2 chunk : region.getChunks()) { + try { + //noinspection unchecked + chunkLoadings.add( + ((CompletableFuture>) + getChunkFutureMethod.invoke(chunkManager, chunk.getX(), chunk.getZ(), ChunkStatus.FEATURES, true)) + .thenApply(either -> either.left().orElse(null)) + ); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException("Couldn't load chunk for regen.", e); + } + } + return chunkLoadings; + } + + private ResourceKey getWorldDimKey(Environment env) { + switch (env) { + case NETHER: + return LevelStem.NETHER; + case THE_END: + return LevelStem.END; + case NORMAL: + default: + return LevelStem.OVERWORLD; + } + } + + private static final Set SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( + SideEffect.NEIGHBORS, + SideEffect.LIGHTING, + SideEffect.VALIDATION, + SideEffect.ENTITY_AI, + SideEffect.EVENTS, + SideEffect.UPDATE + ); + + @Override + public Set getSupportedSideEffects() { + return SUPPORTED_SIDE_EFFECTS; + } + + @Override + public boolean clearContainerBlockContents(org.bukkit.World world, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + + BlockEntity entity = originalWorld.getBlockEntity(new BlockPos(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ())); + if (entity instanceof Clearable) { + ((Clearable) entity).clearContent(); + return true; + } + return false; + } + + // ------------------------------------------------------------------------ + // Code that is less likely to break + // ------------------------------------------------------------------------ + + /** + * Converts from a non-native NMS NBT structure to a native WorldEdit NBT + * structure. + * + * @param foreign non-native NMS NBT structure + * @return native WorldEdit NBT structure + */ + @Override + public BinaryTag toNativeBinary(net.minecraft.nbt.Tag foreign) { + if (foreign == null) { + return null; + } + if (foreign instanceof net.minecraft.nbt.CompoundTag) { + Map values = new HashMap<>(); + Set foreignKeys = ((net.minecraft.nbt.CompoundTag) foreign).getAllKeys(); + + for (String str : foreignKeys) { + net.minecraft.nbt.Tag base = ((net.minecraft.nbt.CompoundTag) foreign).get(str); + values.put(str, toNativeBinary(base)); + } + return CompoundBinaryTag.from(values); + } else if (foreign instanceof net.minecraft.nbt.ByteTag) { + return ByteBinaryTag.of(((net.minecraft.nbt.ByteTag) foreign).getAsByte()); + } else if (foreign instanceof net.minecraft.nbt.ByteArrayTag) { + return ByteArrayBinaryTag.of(((net.minecraft.nbt.ByteArrayTag) foreign).getAsByteArray()); + } else if (foreign instanceof net.minecraft.nbt.DoubleTag) { + return DoubleBinaryTag.of(((net.minecraft.nbt.DoubleTag) foreign).getAsDouble()); + } else if (foreign instanceof net.minecraft.nbt.FloatTag) { + return FloatBinaryTag.of(((net.minecraft.nbt.FloatTag) foreign).getAsFloat()); + } else if (foreign instanceof net.minecraft.nbt.IntTag) { + return IntBinaryTag.of(((net.minecraft.nbt.IntTag) foreign).getAsInt()); + } else if (foreign instanceof net.minecraft.nbt.IntArrayTag) { + return IntArrayBinaryTag.of(((net.minecraft.nbt.IntArrayTag) foreign).getAsIntArray()); + } else if (foreign instanceof net.minecraft.nbt.LongArrayTag) { + return LongArrayBinaryTag.of(((net.minecraft.nbt.LongArrayTag) foreign).getAsLongArray()); + } else if (foreign instanceof net.minecraft.nbt.ListTag) { + try { + return toNativeList((net.minecraft.nbt.ListTag) foreign); + } catch (Throwable e) { + logger.log(Level.WARNING, "Failed to convert net.minecraft.nbt.ListTag", e); + return ListBinaryTag.empty(); + } + } else if (foreign instanceof net.minecraft.nbt.LongTag) { + return LongBinaryTag.of(((net.minecraft.nbt.LongTag) foreign).getAsLong()); + } else if (foreign instanceof net.minecraft.nbt.ShortTag) { + return ShortBinaryTag.of(((net.minecraft.nbt.ShortTag) foreign).getAsShort()); + } else if (foreign instanceof net.minecraft.nbt.StringTag) { + return StringBinaryTag.of(foreign.getAsString()); + } else if (foreign instanceof net.minecraft.nbt.EndTag) { + return EndBinaryTag.get(); + } else { + throw new IllegalArgumentException("Don't know how to make native " + foreign.getClass().getCanonicalName()); + } + } + + /** + * Convert a foreign NBT list tag into a native WorldEdit one. + * + * @param foreign the foreign tag + * @return the converted tag + * @throws SecurityException on error + * @throws IllegalArgumentException on error + */ + private ListBinaryTag toNativeList(net.minecraft.nbt.ListTag foreign) throws SecurityException, IllegalArgumentException { + ListBinaryTag.Builder values = ListBinaryTag.builder(); + + for (net.minecraft.nbt.Tag tag : foreign) { + values.add(toNativeBinary(tag)); + } + + return values.build(); + } + + /** + * Converts a WorldEdit-native NBT structure to a NMS structure. + * + * @param foreign structure to convert + * @return non-native structure + */ + @Override + public net.minecraft.nbt.Tag fromNativeBinary(BinaryTag foreign) { + if (foreign == null) { + return null; + } + if (foreign instanceof CompoundBinaryTag) { + net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); + for (String key : ((CompoundBinaryTag) foreign).keySet()) { + tag.put(key, fromNativeBinary(((CompoundBinaryTag) foreign).get(key))); + } + return tag; + } else if (foreign instanceof ByteBinaryTag) { + return net.minecraft.nbt.ByteTag.valueOf(((ByteBinaryTag) foreign).value()); + } else if (foreign instanceof ByteArrayBinaryTag) { + return new net.minecraft.nbt.ByteArrayTag(((ByteArrayBinaryTag) foreign).value()); + } else if (foreign instanceof DoubleBinaryTag) { + return net.minecraft.nbt.DoubleTag.valueOf(((DoubleBinaryTag) foreign).value()); + } else if (foreign instanceof FloatBinaryTag) { + return net.minecraft.nbt.FloatTag.valueOf(((FloatBinaryTag) foreign).value()); + } else if (foreign instanceof IntBinaryTag) { + return net.minecraft.nbt.IntTag.valueOf(((IntBinaryTag) foreign).value()); + } else if (foreign instanceof IntArrayBinaryTag) { + return new net.minecraft.nbt.IntArrayTag(((IntArrayBinaryTag) foreign).value()); + } else if (foreign instanceof LongArrayBinaryTag) { + return new net.minecraft.nbt.LongArrayTag(((LongArrayBinaryTag) foreign).value()); + } else if (foreign instanceof ListBinaryTag) { + net.minecraft.nbt.ListTag tag = new net.minecraft.nbt.ListTag(); + ListBinaryTag foreignList = (ListBinaryTag) foreign; + for (BinaryTag t : foreignList) { + tag.add(fromNativeBinary(t)); + } + return tag; + } else if (foreign instanceof LongBinaryTag) { + return net.minecraft.nbt.LongTag.valueOf(((LongBinaryTag) foreign).value()); + } else if (foreign instanceof ShortBinaryTag) { + return net.minecraft.nbt.ShortTag.valueOf(((ShortBinaryTag) foreign).value()); + } else if (foreign instanceof StringBinaryTag) { + return net.minecraft.nbt.StringTag.valueOf(((StringBinaryTag) foreign).value()); + } else if (foreign instanceof EndBinaryTag) { + return net.minecraft.nbt.EndTag.INSTANCE; + } else { + throw new IllegalArgumentException("Don't know how to make NMS " + foreign.getClass().getCanonicalName()); + } + } + + @Override + public boolean supportsWatchdog() { + return watchdog != null; + } + + @Override + public void tickWatchdog() { + watchdog.tick(); + } + + private class SpigotWatchdog implements Watchdog { + private final Field instanceField; + private final Field lastTickField; + + SpigotWatchdog() throws NoSuchFieldException { + Field instanceField = WatchdogThread.class.getDeclaredField("instance"); + instanceField.setAccessible(true); + this.instanceField = instanceField; + + Field lastTickField = WatchdogThread.class.getDeclaredField("lastTick"); + lastTickField.setAccessible(true); + this.lastTickField = lastTickField; + } + + @Override + public void tick() { + try { + WatchdogThread instance = (WatchdogThread) this.instanceField.get(null); + if ((long) lastTickField.get(instance) != 0) { + WatchdogThread.tick(); + } + } catch (IllegalAccessException e) { + logger.log(Level.WARNING, "Failed to tick watchdog", e); + } + } + } + + private static class MojangWatchdog implements Watchdog { + private final DedicatedServer server; + private final Field tickField; + + MojangWatchdog(DedicatedServer server) throws NoSuchFieldException { + this.server = server; + Field tickField = MinecraftServer.class.getDeclaredField( + Refraction.pickName("nextTickTime", "ah") + ); + if (tickField.getType() != long.class) { + throw new IllegalStateException("nextTickTime is not a long field, mapping is likely incorrect"); + } + tickField.setAccessible(true); + this.tickField = tickField; + } + + @Override + public void tick() { + try { + tickField.set(server, Util.getMillis()); + } catch (IllegalAccessException ignored) { + } + } + } + + private static class NoOpWorldLoadListener implements ChunkProgressListener { + @Override + public void updateSpawnPos(ChunkPos spawnPos) { + } + + @Override + public void onStatusChange(ChunkPos pos, @org.jetbrains.annotations.Nullable ChunkStatus status) { + } + + @Override + public void start() { + } + + @Override + public void stop() { + } + + @Override + public void setChunkRadius(int radius) { + } + } +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightDataConverters.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightDataConverters.java new file mode 100644 index 000000000..af49e2809 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightDataConverters.java @@ -0,0 +1,2801 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.mojang.datafixers.DSL; +import com.mojang.datafixers.DSL.TypeReference; +import com.mojang.datafixers.DataFixer; +import com.mojang.datafixers.DataFixerBuilder; +import com.mojang.datafixers.schemas.Schema; +import com.mojang.serialization.Dynamic; +import com.sk89q.worldedit.util.nbt.CompoundBinaryTag; +import net.minecraft.core.Direction; +import net.minecraft.nbt.NbtOps; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import net.minecraft.util.StringUtil; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; +import net.minecraft.world.item.DyeColor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; +import javax.annotation.Nullable; + +/** + * Handles converting all Pre 1.13.2 data using the Legacy DataFix System (ported to 1.13.2) + * + * We register a DFU Fixer per Legacy Data Version and apply the fixes using legacy strategy + * which is safer, faster and cleaner code. + * + * The pre DFU code did not fail when the Source version was unknown. + * + * This class also provides util methods for converting compounds to wrap the update call to + * receive the source version in the compound + */ +@SuppressWarnings({ "rawtypes", "unchecked" }) +public class PaperweightDataConverters extends DataFixerBuilder implements com.sk89q.worldedit.world.DataFixer { + + //FAWE start - BinaryTag + @SuppressWarnings("unchecked") + @Override + public T fixUp(FixType type, T original, int srcVer) { + if (type == FixTypes.CHUNK) { + return (T) fixChunk((CompoundBinaryTag) original, srcVer); + } else if (type == FixTypes.BLOCK_ENTITY) { + return (T) fixBlockEntity((CompoundBinaryTag) original, srcVer); + } else if (type == FixTypes.ENTITY) { + return (T) fixEntity((CompoundBinaryTag) original, srcVer); + } else if (type == FixTypes.BLOCK_STATE) { + return (T) fixBlockState((String) original, srcVer); + } else if (type == FixTypes.ITEM_TYPE) { + return (T) fixItemType((String) original, srcVer); + } else if (type == FixTypes.BIOME) { + return (T) fixBiome((String) original, srcVer); + } + return original; + } + + private CompoundBinaryTag fixChunk(CompoundBinaryTag originalChunk, int srcVer) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) adapter.fromNativeBinary(originalChunk); + net.minecraft.nbt.CompoundTag fixed = convert(LegacyType.CHUNK, tag, srcVer); + return (CompoundBinaryTag) adapter.toNativeBinary(fixed); + } + + private CompoundBinaryTag fixBlockEntity(CompoundBinaryTag origTileEnt, int srcVer) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) adapter.fromNativeBinary(origTileEnt); + net.minecraft.nbt.CompoundTag fixed = convert(LegacyType.BLOCK_ENTITY, tag, srcVer); + return (CompoundBinaryTag) adapter.toNativeBinary(fixed); + } + + private CompoundBinaryTag fixEntity(CompoundBinaryTag origEnt, int srcVer) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) adapter.fromNativeBinary(origEnt); + net.minecraft.nbt.CompoundTag fixed = convert(LegacyType.ENTITY, tag, srcVer); + return (CompoundBinaryTag) adapter.toNativeBinary(fixed); + } + //FAWE end + + private String fixBlockState(String blockState, int srcVer) { + net.minecraft.nbt.CompoundTag stateNBT = stateToNBT(blockState); + Dynamic dynamic = new Dynamic<>(OPS_NBT, stateNBT); + net.minecraft.nbt.CompoundTag fixed = (net.minecraft.nbt.CompoundTag) INSTANCE.fixer.update(References.BLOCK_STATE, dynamic, srcVer, DATA_VERSION).getValue(); + return nbtToState(fixed); + } + + private String nbtToState(net.minecraft.nbt.CompoundTag tagCompound) { + StringBuilder sb = new StringBuilder(); + sb.append(tagCompound.getString("Name")); + if (tagCompound.contains("Properties", 10)) { + sb.append('['); + net.minecraft.nbt.CompoundTag props = tagCompound.getCompound("Properties"); + sb.append(props.getAllKeys().stream().map(k -> k + "=" + props.getString(k).replace("\"", "")).collect(Collectors.joining(","))); + sb.append(']'); + } + return sb.toString(); + } + + private static net.minecraft.nbt.CompoundTag stateToNBT(String blockState) { + int propIdx = blockState.indexOf('['); + net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); + if (propIdx < 0) { + tag.putString("Name", blockState); + } else { + tag.putString("Name", blockState.substring(0, propIdx)); + net.minecraft.nbt.CompoundTag propTag = new net.minecraft.nbt.CompoundTag(); + String props = blockState.substring(propIdx + 1, blockState.length() - 1); + String[] propArr = props.split(","); + for (String pair : propArr) { + final String[] split = pair.split("="); + propTag.putString(split[0], split[1]); + } + tag.put("Properties", propTag); + } + return tag; + } + + private String fixBiome(String key, int srcVer) { + return fixName(key, srcVer, References.BIOME); + } + + private String fixItemType(String key, int srcVer) { + return fixName(key, srcVer, References.ITEM_NAME); + } + + private static String fixName(String key, int srcVer, TypeReference type) { + return INSTANCE.fixer.update(type, new Dynamic<>(OPS_NBT, net.minecraft.nbt.StringTag.valueOf(key)), srcVer, DATA_VERSION) + .getValue().getAsString(); + } + + private final com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter adapter; + + private static final NbtOps OPS_NBT = NbtOps.INSTANCE; + private static final int LEGACY_VERSION = 1343; + private static int DATA_VERSION; + public static PaperweightDataConverters INSTANCE; + + private final Map> converters = new EnumMap<>(LegacyType.class); + private final Map> inspectors = new EnumMap<>(LegacyType.class); + + // Set on build + private DataFixer fixer; + private static final Map DFU_TO_LEGACY = new HashMap<>(); + + public enum LegacyType { + LEVEL(References.LEVEL), + PLAYER(References.PLAYER), + CHUNK(References.CHUNK), + BLOCK_ENTITY(References.BLOCK_ENTITY), + ENTITY(References.ENTITY), + ITEM_INSTANCE(References.ITEM_STACK), + OPTIONS(References.OPTIONS), + STRUCTURE(References.STRUCTURE); + + private final TypeReference type; + + LegacyType(TypeReference type) { + this.type = type; + DFU_TO_LEGACY.put(type.typeName(), this); + } + + public TypeReference getDFUType() { + return type; + } + } + + public PaperweightDataConverters(int dataVersion, + com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter adapter) { + super(dataVersion); + DATA_VERSION = dataVersion; + INSTANCE = this; + this.adapter = adapter; + registerConverters(); + registerInspectors(); + } + + + // Called after fixers are built and ready for FIXING + @Override + public DataFixer buildUnoptimized() { + return this.fixer = new WrappedDataFixer(DataFixers.getDataFixer()); + } + + @Override + public DataFixer buildOptimized(final Set requiredTypes, Executor executor) { + return buildUnoptimized(); + } + + @SuppressWarnings("unchecked") + private class WrappedDataFixer implements DataFixer { + private final DataFixer realFixer; + + WrappedDataFixer(DataFixer realFixer) { + this.realFixer = realFixer; + } + + @Override + public Dynamic update(TypeReference type, Dynamic dynamic, int sourceVer, int targetVer) { + LegacyType legacyType = DFU_TO_LEGACY.get(type.typeName()); + if (sourceVer < LEGACY_VERSION && legacyType != null) { + net.minecraft.nbt.CompoundTag cmp = (net.minecraft.nbt.CompoundTag) dynamic.getValue(); + int desiredVersion = Math.min(targetVer, LEGACY_VERSION); + + cmp = convert(legacyType, cmp, sourceVer, desiredVersion); + sourceVer = desiredVersion; + dynamic = new Dynamic(OPS_NBT, cmp); + } + return realFixer.update(type, dynamic, sourceVer, targetVer); + } + + private net.minecraft.nbt.CompoundTag convert(LegacyType type, net.minecraft.nbt.CompoundTag cmp, int sourceVer, int desiredVersion) { + List converters = PaperweightDataConverters.this.converters.get(type); + if (converters != null && !converters.isEmpty()) { + for (DataConverter converter : converters) { + int dataVersion = converter.getDataVersion(); + if (dataVersion > sourceVer && dataVersion <= desiredVersion) { + cmp = converter.convert(cmp); + } + } + } + + List inspectors = PaperweightDataConverters.this.inspectors.get(type); + if (inspectors != null && !inspectors.isEmpty()) { + for (DataInspector inspector : inspectors) { + cmp = inspector.inspect(cmp, sourceVer, desiredVersion); + } + } + + return cmp; + } + + @Override + public Schema getSchema(int i) { + return realFixer.getSchema(i); + } + } + + public static net.minecraft.nbt.CompoundTag convert(LegacyType type, net.minecraft.nbt.CompoundTag cmp) { + return convert(type.getDFUType(), cmp); + } + + public static net.minecraft.nbt.CompoundTag convert(LegacyType type, net.minecraft.nbt.CompoundTag cmp, int sourceVer) { + return convert(type.getDFUType(), cmp, sourceVer); + } + + public static net.minecraft.nbt.CompoundTag convert(LegacyType type, net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + return convert(type.getDFUType(), cmp, sourceVer, targetVer); + } + + public static net.minecraft.nbt.CompoundTag convert(TypeReference type, net.minecraft.nbt.CompoundTag cmp) { + int i = cmp.contains("DataVersion", 99) ? cmp.getInt("DataVersion") : -1; + return convert(type, cmp, i); + } + + public static net.minecraft.nbt.CompoundTag convert(TypeReference type, net.minecraft.nbt.CompoundTag cmp, int sourceVer) { + return convert(type, cmp, sourceVer, DATA_VERSION); + } + + public static net.minecraft.nbt.CompoundTag convert(TypeReference type, net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (sourceVer >= targetVer) { + return cmp; + } + return (net.minecraft.nbt.CompoundTag) INSTANCE.fixer.update(type, new Dynamic<>(OPS_NBT, cmp), sourceVer, targetVer).getValue(); + } + + + public interface DataInspector { + net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer); + } + + public interface DataConverter { + + int getDataVersion(); + + net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp); + } + + + private void registerInspector(LegacyType type, DataInspector inspector) { + this.inspectors.computeIfAbsent(type, k -> new ArrayList<>()).add(inspector); + } + + private void registerConverter(LegacyType type, DataConverter converter) { + int version = converter.getDataVersion(); + + List list = this.converters.computeIfAbsent(type, k -> new ArrayList<>()); + if (!list.isEmpty() && list.get(list.size() - 1).getDataVersion() > version) { + for (int j = 0; j < list.size(); ++j) { + if (list.get(j).getDataVersion() > version) { + list.add(j, converter); + break; + } + } + } else { + list.add(converter); + } + } + + private void registerInspectors() { + registerEntityItemList("EntityHorseDonkey", "SaddleItem", "Items"); + registerEntityItemList("EntityHorseMule", "Items"); + registerEntityItemList("EntityMinecartChest", "Items"); + registerEntityItemList("EntityMinecartHopper", "Items"); + registerEntityItemList("EntityVillager", "Inventory"); + registerEntityItemListEquipment("EntityArmorStand"); + registerEntityItemListEquipment("EntityBat"); + registerEntityItemListEquipment("EntityBlaze"); + registerEntityItemListEquipment("EntityCaveSpider"); + registerEntityItemListEquipment("EntityChicken"); + registerEntityItemListEquipment("EntityCow"); + registerEntityItemListEquipment("EntityCreeper"); + registerEntityItemListEquipment("EntityEnderDragon"); + registerEntityItemListEquipment("EntityEnderman"); + registerEntityItemListEquipment("EntityEndermite"); + registerEntityItemListEquipment("EntityEvoker"); + registerEntityItemListEquipment("EntityGhast"); + registerEntityItemListEquipment("EntityGiantZombie"); + registerEntityItemListEquipment("EntityGuardian"); + registerEntityItemListEquipment("EntityGuardianElder"); + registerEntityItemListEquipment("EntityHorse"); + registerEntityItemListEquipment("EntityHorseDonkey"); + registerEntityItemListEquipment("EntityHorseMule"); + registerEntityItemListEquipment("EntityHorseSkeleton"); + registerEntityItemListEquipment("EntityHorseZombie"); + registerEntityItemListEquipment("EntityIronGolem"); + registerEntityItemListEquipment("EntityMagmaCube"); + registerEntityItemListEquipment("EntityMushroomCow"); + registerEntityItemListEquipment("EntityOcelot"); + registerEntityItemListEquipment("EntityPig"); + registerEntityItemListEquipment("EntityPigZombie"); + registerEntityItemListEquipment("EntityRabbit"); + registerEntityItemListEquipment("EntitySheep"); + registerEntityItemListEquipment("EntityShulker"); + registerEntityItemListEquipment("EntitySilverfish"); + registerEntityItemListEquipment("EntitySkeleton"); + registerEntityItemListEquipment("EntitySkeletonStray"); + registerEntityItemListEquipment("EntitySkeletonWither"); + registerEntityItemListEquipment("EntitySlime"); + registerEntityItemListEquipment("EntitySnowman"); + registerEntityItemListEquipment("EntitySpider"); + registerEntityItemListEquipment("EntitySquid"); + registerEntityItemListEquipment("EntityVex"); + registerEntityItemListEquipment("EntityVillager"); + registerEntityItemListEquipment("EntityVindicator"); + registerEntityItemListEquipment("EntityWitch"); + registerEntityItemListEquipment("EntityWither"); + registerEntityItemListEquipment("EntityWolf"); + registerEntityItemListEquipment("EntityZombie"); + registerEntityItemListEquipment("EntityZombieHusk"); + registerEntityItemListEquipment("EntityZombieVillager"); + registerEntityItemSingle("EntityFireworks", "FireworksItem"); + registerEntityItemSingle("EntityHorse", "ArmorItem"); + registerEntityItemSingle("EntityHorse", "SaddleItem"); + registerEntityItemSingle("EntityHorseMule", "SaddleItem"); + registerEntityItemSingle("EntityHorseSkeleton", "SaddleItem"); + registerEntityItemSingle("EntityHorseZombie", "SaddleItem"); + registerEntityItemSingle("EntityItem", "Item"); + registerEntityItemSingle("EntityItemFrame", "Item"); + registerEntityItemSingle("EntityPotion", "Potion"); + + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItem("TileEntityRecordPlayer", "RecordItem")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityBrewingStand", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityChest", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityDispenser", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityDropper", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityFurnace", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityHopper", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityShulkerBox", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorMobSpawnerMobs()); + registerInspector(LegacyType.CHUNK, new DataInspectorChunks()); + registerInspector(LegacyType.ENTITY, new DataInspectorCommandBlock()); + registerInspector(LegacyType.ENTITY, new DataInspectorEntityPassengers()); + registerInspector(LegacyType.ENTITY, new DataInspectorMobSpawnerMinecart()); + registerInspector(LegacyType.ENTITY, new DataInspectorVillagers()); + registerInspector(LegacyType.ITEM_INSTANCE, new DataInspectorBlockEntity()); + registerInspector(LegacyType.ITEM_INSTANCE, new DataInspectorEntity()); + registerInspector(LegacyType.LEVEL, new DataInspectorLevelPlayer()); + registerInspector(LegacyType.PLAYER, new DataInspectorPlayer()); + registerInspector(LegacyType.PLAYER, new DataInspectorPlayerVehicle()); + registerInspector(LegacyType.STRUCTURE, new DataInspectorStructure()); + } + + private void registerConverters() { + registerConverter(LegacyType.ENTITY, new DataConverterEquipment()); + registerConverter(LegacyType.BLOCK_ENTITY, new DataConverterSignText()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterMaterialId()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterPotionId()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterSpawnEgg()); + registerConverter(LegacyType.ENTITY, new DataConverterMinecart()); + registerConverter(LegacyType.BLOCK_ENTITY, new DataConverterMobSpawner()); + registerConverter(LegacyType.ENTITY, new DataConverterUUID()); + registerConverter(LegacyType.ENTITY, new DataConverterHealth()); + registerConverter(LegacyType.ENTITY, new DataConverterSaddle()); + registerConverter(LegacyType.ENTITY, new DataConverterHanging()); + registerConverter(LegacyType.ENTITY, new DataConverterDropChances()); + registerConverter(LegacyType.ENTITY, new DataConverterRiding()); + registerConverter(LegacyType.ENTITY, new DataConverterArmorStand()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterBook()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterCookedFish()); + registerConverter(LegacyType.ENTITY, new DataConverterZombie()); + registerConverter(LegacyType.OPTIONS, new DataConverterVBO()); + registerConverter(LegacyType.ENTITY, new DataConverterGuardian()); + registerConverter(LegacyType.ENTITY, new DataConverterSkeleton()); + registerConverter(LegacyType.ENTITY, new DataConverterZombieType()); + registerConverter(LegacyType.ENTITY, new DataConverterHorse()); + registerConverter(LegacyType.BLOCK_ENTITY, new DataConverterTileEntity()); + registerConverter(LegacyType.ENTITY, new DataConverterEntity()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterBanner()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterPotionWater()); + registerConverter(LegacyType.ENTITY, new DataConverterShulker()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterShulkerBoxItem()); + registerConverter(LegacyType.BLOCK_ENTITY, new DataConverterShulkerBoxBlock()); + registerConverter(LegacyType.OPTIONS, new DataConverterLang()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterTotem()); + registerConverter(LegacyType.CHUNK, new DataConverterBedBlock()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterBedItem()); + } + + private void registerEntityItemList(String type, String... keys) { + registerInspector(LegacyType.ENTITY, new DataInspectorItemList(type, keys)); + } + + private void registerEntityItemSingle(String type, String key) { + registerInspector(LegacyType.ENTITY, new DataInspectorItem(type, key)); + } + + private void registerEntityItemListEquipment(String type) { + registerEntityItemList(type, "ArmorItems", "HandItems"); + } + + private static final Map OLD_ID_TO_KEY_MAP = new HashMap<>(); + + static { + final Map map = OLD_ID_TO_KEY_MAP; + map.put("EntityItem", new ResourceLocation("item")); + map.put("EntityExperienceOrb", new ResourceLocation("xp_orb")); + map.put("EntityAreaEffectCloud", new ResourceLocation("area_effect_cloud")); + map.put("EntityGuardianElder", new ResourceLocation("elder_guardian")); + map.put("EntitySkeletonWither", new ResourceLocation("wither_skeleton")); + map.put("EntitySkeletonStray", new ResourceLocation("stray")); + map.put("EntityEgg", new ResourceLocation("egg")); + map.put("EntityLeash", new ResourceLocation("leash_knot")); + map.put("EntityPainting", new ResourceLocation("painting")); + map.put("EntityTippedArrow", new ResourceLocation("arrow")); + map.put("EntitySnowball", new ResourceLocation("snowball")); + map.put("EntityLargeFireball", new ResourceLocation("fireball")); + map.put("EntitySmallFireball", new ResourceLocation("small_fireball")); + map.put("EntityEnderPearl", new ResourceLocation("ender_pearl")); + map.put("EntityEnderSignal", new ResourceLocation("eye_of_ender_signal")); + map.put("EntityPotion", new ResourceLocation("potion")); + map.put("EntityThrownExpBottle", new ResourceLocation("xp_bottle")); + map.put("EntityItemFrame", new ResourceLocation("item_frame")); + map.put("EntityWitherSkull", new ResourceLocation("wither_skull")); + map.put("EntityTNTPrimed", new ResourceLocation("tnt")); + map.put("EntityFallingBlock", new ResourceLocation("falling_block")); + map.put("EntityFireworks", new ResourceLocation("fireworks_rocket")); + map.put("EntityZombieHusk", new ResourceLocation("husk")); + map.put("EntitySpectralArrow", new ResourceLocation("spectral_arrow")); + map.put("EntityShulkerBullet", new ResourceLocation("shulker_bullet")); + map.put("EntityDragonFireball", new ResourceLocation("dragon_fireball")); + map.put("EntityZombieVillager", new ResourceLocation("zombie_villager")); + map.put("EntityHorseSkeleton", new ResourceLocation("skeleton_horse")); + map.put("EntityHorseZombie", new ResourceLocation("zombie_horse")); + map.put("EntityArmorStand", new ResourceLocation("armor_stand")); + map.put("EntityHorseDonkey", new ResourceLocation("donkey")); + map.put("EntityHorseMule", new ResourceLocation("mule")); + map.put("EntityEvokerFangs", new ResourceLocation("evocation_fangs")); + map.put("EntityEvoker", new ResourceLocation("evocation_illager")); + map.put("EntityVex", new ResourceLocation("vex")); + map.put("EntityVindicator", new ResourceLocation("vindication_illager")); + map.put("EntityIllagerIllusioner", new ResourceLocation("illusion_illager")); + map.put("EntityMinecartCommandBlock", new ResourceLocation("commandblock_minecart")); + map.put("EntityBoat", new ResourceLocation("boat")); + map.put("EntityMinecartRideable", new ResourceLocation("minecart")); + map.put("EntityMinecartChest", new ResourceLocation("chest_minecart")); + map.put("EntityMinecartFurnace", new ResourceLocation("furnace_minecart")); + map.put("EntityMinecartTNT", new ResourceLocation("tnt_minecart")); + map.put("EntityMinecartHopper", new ResourceLocation("hopper_minecart")); + map.put("EntityMinecartMobSpawner", new ResourceLocation("spawner_minecart")); + map.put("EntityCreeper", new ResourceLocation("creeper")); + map.put("EntitySkeleton", new ResourceLocation("skeleton")); + map.put("EntitySpider", new ResourceLocation("spider")); + map.put("EntityGiantZombie", new ResourceLocation("giant")); + map.put("EntityZombie", new ResourceLocation("zombie")); + map.put("EntitySlime", new ResourceLocation("slime")); + map.put("EntityGhast", new ResourceLocation("ghast")); + map.put("EntityPigZombie", new ResourceLocation("zombie_pigman")); + map.put("EntityEnderman", new ResourceLocation("enderman")); + map.put("EntityCaveSpider", new ResourceLocation("cave_spider")); + map.put("EntitySilverfish", new ResourceLocation("silverfish")); + map.put("EntityBlaze", new ResourceLocation("blaze")); + map.put("EntityMagmaCube", new ResourceLocation("magma_cube")); + map.put("EntityEnderDragon", new ResourceLocation("ender_dragon")); + map.put("EntityWither", new ResourceLocation("wither")); + map.put("EntityBat", new ResourceLocation("bat")); + map.put("EntityWitch", new ResourceLocation("witch")); + map.put("EntityEndermite", new ResourceLocation("endermite")); + map.put("EntityGuardian", new ResourceLocation("guardian")); + map.put("EntityShulker", new ResourceLocation("shulker")); + map.put("EntityPig", new ResourceLocation("pig")); + map.put("EntitySheep", new ResourceLocation("sheep")); + map.put("EntityCow", new ResourceLocation("cow")); + map.put("EntityChicken", new ResourceLocation("chicken")); + map.put("EntitySquid", new ResourceLocation("squid")); + map.put("EntityWolf", new ResourceLocation("wolf")); + map.put("EntityMushroomCow", new ResourceLocation("mooshroom")); + map.put("EntitySnowman", new ResourceLocation("snowman")); + map.put("EntityOcelot", new ResourceLocation("ocelot")); + map.put("EntityIronGolem", new ResourceLocation("villager_golem")); + map.put("EntityHorse", new ResourceLocation("horse")); + map.put("EntityRabbit", new ResourceLocation("rabbit")); + map.put("EntityPolarBear", new ResourceLocation("polar_bear")); + map.put("EntityLlama", new ResourceLocation("llama")); + map.put("EntityLlamaSpit", new ResourceLocation("llama_spit")); + map.put("EntityParrot", new ResourceLocation("parrot")); + map.put("EntityVillager", new ResourceLocation("villager")); + map.put("EntityEnderCrystal", new ResourceLocation("ender_crystal")); + map.put("TileEntityFurnace", new ResourceLocation("furnace")); + map.put("TileEntityChest", new ResourceLocation("chest")); + map.put("TileEntityEnderChest", new ResourceLocation("ender_chest")); + map.put("TileEntityRecordPlayer", new ResourceLocation("jukebox")); + map.put("TileEntityDispenser", new ResourceLocation("dispenser")); + map.put("TileEntityDropper", new ResourceLocation("dropper")); + map.put("TileEntitySign", new ResourceLocation("sign")); + map.put("TileEntityMobSpawner", new ResourceLocation("mob_spawner")); + map.put("TileEntityNote", new ResourceLocation("noteblock")); + map.put("TileEntityPiston", new ResourceLocation("piston")); + map.put("TileEntityBrewingStand", new ResourceLocation("brewing_stand")); + map.put("TileEntityEnchantTable", new ResourceLocation("enchanting_table")); + map.put("TileEntityEnderPortal", new ResourceLocation("end_portal")); + map.put("TileEntityBeacon", new ResourceLocation("beacon")); + map.put("TileEntitySkull", new ResourceLocation("skull")); + map.put("TileEntityLightDetector", new ResourceLocation("daylight_detector")); + map.put("TileEntityHopper", new ResourceLocation("hopper")); + map.put("TileEntityComparator", new ResourceLocation("comparator")); + map.put("TileEntityFlowerPot", new ResourceLocation("flower_pot")); + map.put("TileEntityBanner", new ResourceLocation("banner")); + map.put("TileEntityStructure", new ResourceLocation("structure_block")); + map.put("TileEntityEndGateway", new ResourceLocation("end_gateway")); + map.put("TileEntityCommand", new ResourceLocation("command_block")); + map.put("TileEntityShulkerBox", new ResourceLocation("shulker_box")); + map.put("TileEntityBed", new ResourceLocation("bed")); + } + + private static ResourceLocation getKey(String type) { + final ResourceLocation key = OLD_ID_TO_KEY_MAP.get(type); + if (key == null) { + throw new IllegalArgumentException("Unknown mapping for " + type); + } + return key; + } + + private static void convertCompound(LegacyType type, net.minecraft.nbt.CompoundTag cmp, String key, int sourceVer, int targetVer) { + cmp.put(key, convert(type, cmp.getCompound(key), sourceVer, targetVer)); + } + + private static void convertItem(net.minecraft.nbt.CompoundTag nbttagcompound, String key, int sourceVer, int targetVer) { + if (nbttagcompound.contains(key, 10)) { + convertCompound(LegacyType.ITEM_INSTANCE, nbttagcompound, key, sourceVer, targetVer); + } + } + + private static void convertItems(net.minecraft.nbt.CompoundTag nbttagcompound, String key, int sourceVer, int targetVer) { + if (nbttagcompound.contains(key, 9)) { + net.minecraft.nbt.ListTag nbttaglist = nbttagcompound.getList(key, 10); + + for (int j = 0; j < nbttaglist.size(); ++j) { + nbttaglist.set(j, convert(LegacyType.ITEM_INSTANCE, nbttaglist.getCompound(j), sourceVer, targetVer)); + } + } + + } + + private static class DataConverterEquipment implements DataConverter { + + DataConverterEquipment() { + } + + public int getDataVersion() { + return 100; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + net.minecraft.nbt.ListTag nbttaglist = cmp.getList("Equipment", 10); + net.minecraft.nbt.ListTag nbttaglist1; + + if (!nbttaglist.isEmpty() && !cmp.contains("HandItems", 10)) { + nbttaglist1 = new net.minecraft.nbt.ListTag(); + nbttaglist1.add(nbttaglist.get(0)); + nbttaglist1.add(new net.minecraft.nbt.CompoundTag()); + cmp.put("HandItems", nbttaglist1); + } + + if (nbttaglist.size() > 1 && !cmp.contains("ArmorItem", 10)) { + nbttaglist1 = new net.minecraft.nbt.ListTag(); + nbttaglist1.add(nbttaglist.get(1)); + nbttaglist1.add(nbttaglist.get(2)); + nbttaglist1.add(nbttaglist.get(3)); + nbttaglist1.add(nbttaglist.get(4)); + cmp.put("ArmorItems", nbttaglist1); + } + + cmp.remove("Equipment"); + if (cmp.contains("DropChances", 9)) { + nbttaglist1 = cmp.getList("DropChances", 5); + net.minecraft.nbt.ListTag nbttaglist2; + + if (!cmp.contains("HandDropChances", 10)) { + nbttaglist2 = new net.minecraft.nbt.ListTag(); + nbttaglist2.add(net.minecraft.nbt.FloatTag.valueOf(nbttaglist1.getFloat(0))); + nbttaglist2.add(net.minecraft.nbt.FloatTag.valueOf(0.0F)); + cmp.put("HandDropChances", nbttaglist2); + } + + if (!cmp.contains("ArmorDropChances", 10)) { + nbttaglist2 = new net.minecraft.nbt.ListTag(); + nbttaglist2.add(net.minecraft.nbt.FloatTag.valueOf(nbttaglist1.getFloat(1))); + nbttaglist2.add(net.minecraft.nbt.FloatTag.valueOf(nbttaglist1.getFloat(2))); + nbttaglist2.add(net.minecraft.nbt.FloatTag.valueOf(nbttaglist1.getFloat(3))); + nbttaglist2.add(net.minecraft.nbt.FloatTag.valueOf(nbttaglist1.getFloat(4))); + cmp.put("ArmorDropChances", nbttaglist2); + } + + cmp.remove("DropChances"); + } + + return cmp; + } + } + + private static class DataInspectorBlockEntity implements DataInspector { + + private static final Map b = Maps.newHashMap(); + private static final Map c = Maps.newHashMap(); + + DataInspectorBlockEntity() { + } + + @Nullable + private static String convertEntityId(int i, String s) { + String key = new ResourceLocation(s).toString(); + if (i < 515 && DataInspectorBlockEntity.b.containsKey(key)) { + return DataInspectorBlockEntity.b.get(key); + } else { + return DataInspectorBlockEntity.c.get(key); + } + } + + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (!cmp.contains("tag", 10)) { + return cmp; + } else { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + + if (nbttagcompound1.contains("BlockEntityTag", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("BlockEntityTag"); + String s = cmp.getString("id"); + String s1 = convertEntityId(sourceVer, s); + boolean flag; + + if (s1 == null) { + // CraftBukkit - Remove unnecessary warning (occurs when deserializing a Shulker Box item) + // DataInspectorBlockEntity.a.warn("Unable to resolve BlockEntity for ItemInstance: {}", s); + flag = false; + } else { + flag = !nbttagcompound2.contains("id"); + nbttagcompound2.putString("id", s1); + } + + convert(LegacyType.BLOCK_ENTITY, nbttagcompound2, sourceVer, targetVer); + if (flag) { + nbttagcompound2.remove("id"); + } + } + + return cmp; + } + } + + static { + Map map = DataInspectorBlockEntity.b; + + map.put("minecraft:furnace", "Furnace"); + map.put("minecraft:lit_furnace", "Furnace"); + map.put("minecraft:chest", "Chest"); + map.put("minecraft:trapped_chest", "Chest"); + map.put("minecraft:ender_chest", "EnderChest"); + map.put("minecraft:jukebox", "RecordPlayer"); + map.put("minecraft:dispenser", "Trap"); + map.put("minecraft:dropper", "Dropper"); + map.put("minecraft:sign", "Sign"); + map.put("minecraft:mob_spawner", "MobSpawner"); + map.put("minecraft:noteblock", "Music"); + map.put("minecraft:brewing_stand", "Cauldron"); + map.put("minecraft:enhanting_table", "EnchantTable"); + map.put("minecraft:command_block", "CommandBlock"); + map.put("minecraft:beacon", "Beacon"); + map.put("minecraft:skull", "Skull"); + map.put("minecraft:daylight_detector", "DLDetector"); + map.put("minecraft:hopper", "Hopper"); + map.put("minecraft:banner", "Banner"); + map.put("minecraft:flower_pot", "FlowerPot"); + map.put("minecraft:repeating_command_block", "CommandBlock"); + map.put("minecraft:chain_command_block", "CommandBlock"); + map.put("minecraft:standing_sign", "Sign"); + map.put("minecraft:wall_sign", "Sign"); + map.put("minecraft:piston_head", "Piston"); + map.put("minecraft:daylight_detector_inverted", "DLDetector"); + map.put("minecraft:unpowered_comparator", "Comparator"); + map.put("minecraft:powered_comparator", "Comparator"); + map.put("minecraft:wall_banner", "Banner"); + map.put("minecraft:standing_banner", "Banner"); + map.put("minecraft:structure_block", "Structure"); + map.put("minecraft:end_portal", "Airportal"); + map.put("minecraft:end_gateway", "EndGateway"); + map.put("minecraft:shield", "Shield"); + map = DataInspectorBlockEntity.c; + map.put("minecraft:furnace", "minecraft:furnace"); + map.put("minecraft:lit_furnace", "minecraft:furnace"); + map.put("minecraft:chest", "minecraft:chest"); + map.put("minecraft:trapped_chest", "minecraft:chest"); + map.put("minecraft:ender_chest", "minecraft:enderchest"); + map.put("minecraft:jukebox", "minecraft:jukebox"); + map.put("minecraft:dispenser", "minecraft:dispenser"); + map.put("minecraft:dropper", "minecraft:dropper"); + map.put("minecraft:sign", "minecraft:sign"); + map.put("minecraft:mob_spawner", "minecraft:mob_spawner"); + map.put("minecraft:noteblock", "minecraft:noteblock"); + map.put("minecraft:brewing_stand", "minecraft:brewing_stand"); + map.put("minecraft:enhanting_table", "minecraft:enchanting_table"); + map.put("minecraft:command_block", "minecraft:command_block"); + map.put("minecraft:beacon", "minecraft:beacon"); + map.put("minecraft:skull", "minecraft:skull"); + map.put("minecraft:daylight_detector", "minecraft:daylight_detector"); + map.put("minecraft:hopper", "minecraft:hopper"); + map.put("minecraft:banner", "minecraft:banner"); + map.put("minecraft:flower_pot", "minecraft:flower_pot"); + map.put("minecraft:repeating_command_block", "minecraft:command_block"); + map.put("minecraft:chain_command_block", "minecraft:command_block"); + map.put("minecraft:shulker_box", "minecraft:shulker_box"); + map.put("minecraft:white_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:orange_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:magenta_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:light_blue_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:yellow_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:lime_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:pink_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:gray_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:silver_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:cyan_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:purple_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:blue_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:brown_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:green_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:red_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:black_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:bed", "minecraft:bed"); + map.put("minecraft:standing_sign", "minecraft:sign"); + map.put("minecraft:wall_sign", "minecraft:sign"); + map.put("minecraft:piston_head", "minecraft:piston"); + map.put("minecraft:daylight_detector_inverted", "minecraft:daylight_detector"); + map.put("minecraft:unpowered_comparator", "minecraft:comparator"); + map.put("minecraft:powered_comparator", "minecraft:comparator"); + map.put("minecraft:wall_banner", "minecraft:banner"); + map.put("minecraft:standing_banner", "minecraft:banner"); + map.put("minecraft:structure_block", "minecraft:structure_block"); + map.put("minecraft:end_portal", "minecraft:end_portal"); + map.put("minecraft:end_gateway", "minecraft:end_gateway"); + map.put("minecraft:shield", "minecraft:shield"); + } + } + + private static class DataInspectorEntity implements DataInspector { + + DataInspectorEntity() { + } + + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + + if (nbttagcompound1.contains("EntityTag", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("EntityTag"); + String s = cmp.getString("id"); + String s1; + + if ("minecraft:armor_stand".equals(s)) { + s1 = sourceVer < 515 ? "ArmorStand" : "minecraft:armor_stand"; + } else { + if (!"minecraft:spawn_egg".equals(s)) { + return cmp; + } + + s1 = nbttagcompound2.getString("id"); + } + + boolean flag; + + flag = !nbttagcompound2.contains("id", 8); + nbttagcompound2.putString("id", s1); + + convert(LegacyType.ENTITY, nbttagcompound2, sourceVer, targetVer); + if (flag) { + nbttagcompound2.remove("id"); + } + } + + return cmp; + } + } + + + private abstract static class DataInspectorTagged implements DataInspector { + + private final ResourceLocation key; + + DataInspectorTagged(String type) { + this.key = getKey(type); + } + + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (this.key.equals(new ResourceLocation(cmp.getString("id")))) { + cmp = this.inspectChecked(cmp, sourceVer, targetVer); + } + + return cmp; + } + + abstract net.minecraft.nbt.CompoundTag inspectChecked(net.minecraft.nbt.CompoundTag nbttagcompound, int sourceVer, int targetVer); + } + + private static class DataInspectorItemList extends DataInspectorTagged { + + private final String[] keys; + + DataInspectorItemList(String oclass, String... astring) { + super(oclass); + this.keys = astring; + } + + net.minecraft.nbt.CompoundTag inspectChecked(net.minecraft.nbt.CompoundTag nbttagcompound, int sourceVer, int targetVer) { + for (String s : this.keys) { + PaperweightDataConverters.convertItems(nbttagcompound, s, sourceVer, targetVer); + } + + return nbttagcompound; + } + } + + private static class DataInspectorItem extends DataInspectorTagged { + + private final String[] keys; + + DataInspectorItem(String oclass, String... astring) { + super(oclass); + this.keys = astring; + } + + net.minecraft.nbt.CompoundTag inspectChecked(net.minecraft.nbt.CompoundTag nbttagcompound, int sourceVer, int targetVer) { + for (String key : this.keys) { + PaperweightDataConverters.convertItem(nbttagcompound, key, sourceVer, targetVer); + } + + return nbttagcompound; + } + } + + private static class DataConverterMaterialId implements DataConverter { + + private static final String[] materials = new String[2268]; + + DataConverterMaterialId() { + } + + public int getDataVersion() { + return 102; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if (cmp.contains("id", 99)) { + short short0 = cmp.getShort("id"); + + if (short0 > 0 && short0 < materials.length && materials[short0] != null) { + cmp.putString("id", materials[short0]); + } + } + + return cmp; + } + + static { + materials[1] = "minecraft:stone"; + materials[2] = "minecraft:grass"; + materials[3] = "minecraft:dirt"; + materials[4] = "minecraft:cobblestone"; + materials[5] = "minecraft:planks"; + materials[6] = "minecraft:sapling"; + materials[7] = "minecraft:bedrock"; + materials[8] = "minecraft:flowing_water"; + materials[9] = "minecraft:water"; + materials[10] = "minecraft:flowing_lava"; + materials[11] = "minecraft:lava"; + materials[12] = "minecraft:sand"; + materials[13] = "minecraft:gravel"; + materials[14] = "minecraft:gold_ore"; + materials[15] = "minecraft:iron_ore"; + materials[16] = "minecraft:coal_ore"; + materials[17] = "minecraft:log"; + materials[18] = "minecraft:leaves"; + materials[19] = "minecraft:sponge"; + materials[20] = "minecraft:glass"; + materials[21] = "minecraft:lapis_ore"; + materials[22] = "minecraft:lapis_block"; + materials[23] = "minecraft:dispenser"; + materials[24] = "minecraft:sandstone"; + materials[25] = "minecraft:noteblock"; + materials[27] = "minecraft:golden_rail"; + materials[28] = "minecraft:detector_rail"; + materials[29] = "minecraft:sticky_piston"; + materials[30] = "minecraft:web"; + materials[31] = "minecraft:tallgrass"; + materials[32] = "minecraft:deadbush"; + materials[33] = "minecraft:piston"; + materials[35] = "minecraft:wool"; + materials[37] = "minecraft:yellow_flower"; + materials[38] = "minecraft:red_flower"; + materials[39] = "minecraft:brown_mushroom"; + materials[40] = "minecraft:red_mushroom"; + materials[41] = "minecraft:gold_block"; + materials[42] = "minecraft:iron_block"; + materials[43] = "minecraft:double_stone_slab"; + materials[44] = "minecraft:stone_slab"; + materials[45] = "minecraft:brick_block"; + materials[46] = "minecraft:tnt"; + materials[47] = "minecraft:bookshelf"; + materials[48] = "minecraft:mossy_cobblestone"; + materials[49] = "minecraft:obsidian"; + materials[50] = "minecraft:torch"; + materials[51] = "minecraft:fire"; + materials[52] = "minecraft:mob_spawner"; + materials[53] = "minecraft:oak_stairs"; + materials[54] = "minecraft:chest"; + materials[56] = "minecraft:diamond_ore"; + materials[57] = "minecraft:diamond_block"; + materials[58] = "minecraft:crafting_table"; + materials[60] = "minecraft:farmland"; + materials[61] = "minecraft:furnace"; + materials[62] = "minecraft:lit_furnace"; + materials[65] = "minecraft:ladder"; + materials[66] = "minecraft:rail"; + materials[67] = "minecraft:stone_stairs"; + materials[69] = "minecraft:lever"; + materials[70] = "minecraft:stone_pressure_plate"; + materials[72] = "minecraft:wooden_pressure_plate"; + materials[73] = "minecraft:redstone_ore"; + materials[76] = "minecraft:redstone_torch"; + materials[77] = "minecraft:stone_button"; + materials[78] = "minecraft:snow_layer"; + materials[79] = "minecraft:ice"; + materials[80] = "minecraft:snow"; + materials[81] = "minecraft:cactus"; + materials[82] = "minecraft:clay"; + materials[84] = "minecraft:jukebox"; + materials[85] = "minecraft:fence"; + materials[86] = "minecraft:pumpkin"; + materials[87] = "minecraft:netherrack"; + materials[88] = "minecraft:soul_sand"; + materials[89] = "minecraft:glowstone"; + materials[90] = "minecraft:portal"; + materials[91] = "minecraft:lit_pumpkin"; + materials[95] = "minecraft:stained_glass"; + materials[96] = "minecraft:trapdoor"; + materials[97] = "minecraft:monster_egg"; + materials[98] = "minecraft:stonebrick"; + materials[99] = "minecraft:brown_mushroom_block"; + materials[100] = "minecraft:red_mushroom_block"; + materials[101] = "minecraft:iron_bars"; + materials[102] = "minecraft:glass_pane"; + materials[103] = "minecraft:melon_block"; + materials[106] = "minecraft:vine"; + materials[107] = "minecraft:fence_gate"; + materials[108] = "minecraft:brick_stairs"; + materials[109] = "minecraft:stone_brick_stairs"; + materials[110] = "minecraft:mycelium"; + materials[111] = "minecraft:waterlily"; + materials[112] = "minecraft:nether_brick"; + materials[113] = "minecraft:nether_brick_fence"; + materials[114] = "minecraft:nether_brick_stairs"; + materials[116] = "minecraft:enchanting_table"; + materials[119] = "minecraft:end_portal"; + materials[120] = "minecraft:end_portal_frame"; + materials[121] = "minecraft:end_stone"; + materials[122] = "minecraft:dragon_egg"; + materials[123] = "minecraft:redstone_lamp"; + materials[125] = "minecraft:double_wooden_slab"; + materials[126] = "minecraft:wooden_slab"; + materials[127] = "minecraft:cocoa"; + materials[128] = "minecraft:sandstone_stairs"; + materials[129] = "minecraft:emerald_ore"; + materials[130] = "minecraft:ender_chest"; + materials[131] = "minecraft:tripwire_hook"; + materials[133] = "minecraft:emerald_block"; + materials[134] = "minecraft:spruce_stairs"; + materials[135] = "minecraft:birch_stairs"; + materials[136] = "minecraft:jungle_stairs"; + materials[137] = "minecraft:command_block"; + materials[138] = "minecraft:beacon"; + materials[139] = "minecraft:cobblestone_wall"; + materials[141] = "minecraft:carrots"; + materials[142] = "minecraft:potatoes"; + materials[143] = "minecraft:wooden_button"; + materials[145] = "minecraft:anvil"; + materials[146] = "minecraft:trapped_chest"; + materials[147] = "minecraft:light_weighted_pressure_plate"; + materials[148] = "minecraft:heavy_weighted_pressure_plate"; + materials[151] = "minecraft:daylight_detector"; + materials[152] = "minecraft:redstone_block"; + materials[153] = "minecraft:quartz_ore"; + materials[154] = "minecraft:hopper"; + materials[155] = "minecraft:quartz_block"; + materials[156] = "minecraft:quartz_stairs"; + materials[157] = "minecraft:activator_rail"; + materials[158] = "minecraft:dropper"; + materials[159] = "minecraft:stained_hardened_clay"; + materials[160] = "minecraft:stained_glass_pane"; + materials[161] = "minecraft:leaves2"; + materials[162] = "minecraft:log2"; + materials[163] = "minecraft:acacia_stairs"; + materials[164] = "minecraft:dark_oak_stairs"; + materials[170] = "minecraft:hay_block"; + materials[171] = "minecraft:carpet"; + materials[172] = "minecraft:hardened_clay"; + materials[173] = "minecraft:coal_block"; + materials[174] = "minecraft:packed_ice"; + materials[175] = "minecraft:double_plant"; + materials[256] = "minecraft:iron_shovel"; + materials[257] = "minecraft:iron_pickaxe"; + materials[258] = "minecraft:iron_axe"; + materials[259] = "minecraft:flint_and_steel"; + materials[260] = "minecraft:apple"; + materials[261] = "minecraft:bow"; + materials[262] = "minecraft:arrow"; + materials[263] = "minecraft:coal"; + materials[264] = "minecraft:diamond"; + materials[265] = "minecraft:iron_ingot"; + materials[266] = "minecraft:gold_ingot"; + materials[267] = "minecraft:iron_sword"; + materials[268] = "minecraft:wooden_sword"; + materials[269] = "minecraft:wooden_shovel"; + materials[270] = "minecraft:wooden_pickaxe"; + materials[271] = "minecraft:wooden_axe"; + materials[272] = "minecraft:stone_sword"; + materials[273] = "minecraft:stone_shovel"; + materials[274] = "minecraft:stone_pickaxe"; + materials[275] = "minecraft:stone_axe"; + materials[276] = "minecraft:diamond_sword"; + materials[277] = "minecraft:diamond_shovel"; + materials[278] = "minecraft:diamond_pickaxe"; + materials[279] = "minecraft:diamond_axe"; + materials[280] = "minecraft:stick"; + materials[281] = "minecraft:bowl"; + materials[282] = "minecraft:mushroom_stew"; + materials[283] = "minecraft:golden_sword"; + materials[284] = "minecraft:golden_shovel"; + materials[285] = "minecraft:golden_pickaxe"; + materials[286] = "minecraft:golden_axe"; + materials[287] = "minecraft:string"; + materials[288] = "minecraft:feather"; + materials[289] = "minecraft:gunpowder"; + materials[290] = "minecraft:wooden_hoe"; + materials[291] = "minecraft:stone_hoe"; + materials[292] = "minecraft:iron_hoe"; + materials[293] = "minecraft:diamond_hoe"; + materials[294] = "minecraft:golden_hoe"; + materials[295] = "minecraft:wheat_seeds"; + materials[296] = "minecraft:wheat"; + materials[297] = "minecraft:bread"; + materials[298] = "minecraft:leather_helmet"; + materials[299] = "minecraft:leather_chestplate"; + materials[300] = "minecraft:leather_leggings"; + materials[301] = "minecraft:leather_boots"; + materials[302] = "minecraft:chainmail_helmet"; + materials[303] = "minecraft:chainmail_chestplate"; + materials[304] = "minecraft:chainmail_leggings"; + materials[305] = "minecraft:chainmail_boots"; + materials[306] = "minecraft:iron_helmet"; + materials[307] = "minecraft:iron_chestplate"; + materials[308] = "minecraft:iron_leggings"; + materials[309] = "minecraft:iron_boots"; + materials[310] = "minecraft:diamond_helmet"; + materials[311] = "minecraft:diamond_chestplate"; + materials[312] = "minecraft:diamond_leggings"; + materials[313] = "minecraft:diamond_boots"; + materials[314] = "minecraft:golden_helmet"; + materials[315] = "minecraft:golden_chestplate"; + materials[316] = "minecraft:golden_leggings"; + materials[317] = "minecraft:golden_boots"; + materials[318] = "minecraft:flint"; + materials[319] = "minecraft:porkchop"; + materials[320] = "minecraft:cooked_porkchop"; + materials[321] = "minecraft:painting"; + materials[322] = "minecraft:golden_apple"; + materials[323] = "minecraft:sign"; + materials[324] = "minecraft:wooden_door"; + materials[325] = "minecraft:bucket"; + materials[326] = "minecraft:water_bucket"; + materials[327] = "minecraft:lava_bucket"; + materials[328] = "minecraft:minecart"; + materials[329] = "minecraft:saddle"; + materials[330] = "minecraft:iron_door"; + materials[331] = "minecraft:redstone"; + materials[332] = "minecraft:snowball"; + materials[333] = "minecraft:boat"; + materials[334] = "minecraft:leather"; + materials[335] = "minecraft:milk_bucket"; + materials[336] = "minecraft:brick"; + materials[337] = "minecraft:clay_ball"; + materials[338] = "minecraft:reeds"; + materials[339] = "minecraft:paper"; + materials[340] = "minecraft:book"; + materials[341] = "minecraft:slime_ball"; + materials[342] = "minecraft:chest_minecart"; + materials[343] = "minecraft:furnace_minecart"; + materials[344] = "minecraft:egg"; + materials[345] = "minecraft:compass"; + materials[346] = "minecraft:fishing_rod"; + materials[347] = "minecraft:clock"; + materials[348] = "minecraft:glowstone_dust"; + materials[349] = "minecraft:fish"; + materials[350] = "minecraft:cooked_fish"; // Paper - cooked_fished -> cooked_fish + materials[351] = "minecraft:dye"; + materials[352] = "minecraft:bone"; + materials[353] = "minecraft:sugar"; + materials[354] = "minecraft:cake"; + materials[355] = "minecraft:bed"; + materials[356] = "minecraft:repeater"; + materials[357] = "minecraft:cookie"; + materials[358] = "minecraft:filled_map"; + materials[359] = "minecraft:shears"; + materials[360] = "minecraft:melon"; + materials[361] = "minecraft:pumpkin_seeds"; + materials[362] = "minecraft:melon_seeds"; + materials[363] = "minecraft:beef"; + materials[364] = "minecraft:cooked_beef"; + materials[365] = "minecraft:chicken"; + materials[366] = "minecraft:cooked_chicken"; + materials[367] = "minecraft:rotten_flesh"; + materials[368] = "minecraft:ender_pearl"; + materials[369] = "minecraft:blaze_rod"; + materials[370] = "minecraft:ghast_tear"; + materials[371] = "minecraft:gold_nugget"; + materials[372] = "minecraft:nether_wart"; + materials[373] = "minecraft:potion"; + materials[374] = "minecraft:glass_bottle"; + materials[375] = "minecraft:spider_eye"; + materials[376] = "minecraft:fermented_spider_eye"; + materials[377] = "minecraft:blaze_powder"; + materials[378] = "minecraft:magma_cream"; + materials[379] = "minecraft:brewing_stand"; + materials[380] = "minecraft:cauldron"; + materials[381] = "minecraft:ender_eye"; + materials[382] = "minecraft:speckled_melon"; + materials[383] = "minecraft:spawn_egg"; + materials[384] = "minecraft:experience_bottle"; + materials[385] = "minecraft:fire_charge"; + materials[386] = "minecraft:writable_book"; + materials[387] = "minecraft:written_book"; + materials[388] = "minecraft:emerald"; + materials[389] = "minecraft:item_frame"; + materials[390] = "minecraft:flower_pot"; + materials[391] = "minecraft:carrot"; + materials[392] = "minecraft:potato"; + materials[393] = "minecraft:baked_potato"; + materials[394] = "minecraft:poisonous_potato"; + materials[395] = "minecraft:map"; + materials[396] = "minecraft:golden_carrot"; + materials[397] = "minecraft:skull"; + materials[398] = "minecraft:carrot_on_a_stick"; + materials[399] = "minecraft:nether_star"; + materials[400] = "minecraft:pumpkin_pie"; + materials[401] = "minecraft:fireworks"; + materials[402] = "minecraft:firework_charge"; + materials[403] = "minecraft:enchanted_book"; + materials[404] = "minecraft:comparator"; + materials[405] = "minecraft:netherbrick"; + materials[406] = "minecraft:quartz"; + materials[407] = "minecraft:tnt_minecart"; + materials[408] = "minecraft:hopper_minecart"; + materials[417] = "minecraft:iron_horse_armor"; + materials[418] = "minecraft:golden_horse_armor"; + materials[419] = "minecraft:diamond_horse_armor"; + materials[420] = "minecraft:lead"; + materials[421] = "minecraft:name_tag"; + materials[422] = "minecraft:command_block_minecart"; + materials[2256] = "minecraft:record_13"; + materials[2257] = "minecraft:record_cat"; + materials[2258] = "minecraft:record_blocks"; + materials[2259] = "minecraft:record_chirp"; + materials[2260] = "minecraft:record_far"; + materials[2261] = "minecraft:record_mall"; + materials[2262] = "minecraft:record_mellohi"; + materials[2263] = "minecraft:record_stal"; + materials[2264] = "minecraft:record_strad"; + materials[2265] = "minecraft:record_ward"; + materials[2266] = "minecraft:record_11"; + materials[2267] = "minecraft:record_wait"; + // Paper start + materials[409] = "minecraft:prismarine_shard"; + materials[410] = "minecraft:prismarine_crystals"; + materials[411] = "minecraft:rabbit"; + materials[412] = "minecraft:cooked_rabbit"; + materials[413] = "minecraft:rabbit_stew"; + materials[414] = "minecraft:rabbit_foot"; + materials[415] = "minecraft:rabbit_hide"; + materials[416] = "minecraft:armor_stand"; + materials[423] = "minecraft:mutton"; + materials[424] = "minecraft:cooked_mutton"; + materials[425] = "minecraft:banner"; + materials[426] = "minecraft:end_crystal"; + materials[427] = "minecraft:spruce_door"; + materials[428] = "minecraft:birch_door"; + materials[429] = "minecraft:jungle_door"; + materials[430] = "minecraft:acacia_door"; + materials[431] = "minecraft:dark_oak_door"; + materials[432] = "minecraft:chorus_fruit"; + materials[433] = "minecraft:chorus_fruit_popped"; + materials[434] = "minecraft:beetroot"; + materials[435] = "minecraft:beetroot_seeds"; + materials[436] = "minecraft:beetroot_soup"; + materials[437] = "minecraft:dragon_breath"; + materials[438] = "minecraft:splash_potion"; + materials[439] = "minecraft:spectral_arrow"; + materials[440] = "minecraft:tipped_arrow"; + materials[441] = "minecraft:lingering_potion"; + materials[442] = "minecraft:shield"; + materials[443] = "minecraft:elytra"; + materials[444] = "minecraft:spruce_boat"; + materials[445] = "minecraft:birch_boat"; + materials[446] = "minecraft:jungle_boat"; + materials[447] = "minecraft:acacia_boat"; + materials[448] = "minecraft:dark_oak_boat"; + materials[449] = "minecraft:totem_of_undying"; + materials[450] = "minecraft:shulker_shell"; + materials[452] = "minecraft:iron_nugget"; + materials[453] = "minecraft:knowledge_book"; + // Paper end + } + } + + private static class DataConverterArmorStand implements DataConverter { + + DataConverterArmorStand() { + } + + public int getDataVersion() { + return 147; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("ArmorStand".equals(cmp.getString("id")) && cmp.getBoolean("Silent") && !cmp.getBoolean("Marker")) { + cmp.remove("Silent"); + } + + return cmp; + } + } + + private static class DataConverterBanner implements DataConverter { + + DataConverterBanner() { + } + + public int getDataVersion() { + return 804; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:banner".equals(cmp.getString("id")) && cmp.contains("tag", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + + if (nbttagcompound1.contains("BlockEntityTag", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("BlockEntityTag"); + + if (nbttagcompound2.contains("Base", 99)) { + cmp.putShort("Damage", (short) (nbttagcompound2.getShort("Base") & 15)); + if (nbttagcompound1.contains("display", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound3 = nbttagcompound1.getCompound("display"); + + if (nbttagcompound3.contains("Lore", 9)) { + net.minecraft.nbt.ListTag nbttaglist = nbttagcompound3.getList("Lore", 8); + + if (nbttaglist.size() == 1 && "(+NBT)".equals(nbttaglist.getString(0))) { + return cmp; + } + } + } + + nbttagcompound2.remove("Base"); + if (nbttagcompound2.isEmpty()) { + nbttagcompound1.remove("BlockEntityTag"); + } + + if (nbttagcompound1.isEmpty()) { + cmp.remove("tag"); + } + } + } + } + + return cmp; + } + } + + private static class DataConverterPotionId implements DataConverter { + + private static final String[] potions = new String[128]; + + DataConverterPotionId() { + } + + public int getDataVersion() { + return 102; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:potion".equals(cmp.getString("id"))) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + short short0 = cmp.getShort("Damage"); + + if (!nbttagcompound1.contains("Potion", 8)) { + String s = DataConverterPotionId.potions[short0 & 127]; + + nbttagcompound1.putString("Potion", s == null ? "minecraft:water" : s); + cmp.put("tag", nbttagcompound1); + if ((short0 & 16384) == 16384) { + cmp.putString("id", "minecraft:splash_potion"); + } + } + + if (short0 != 0) { + cmp.putShort("Damage", (short) 0); + } + } + + return cmp; + } + + static { + DataConverterPotionId.potions[0] = "minecraft:water"; + DataConverterPotionId.potions[1] = "minecraft:regeneration"; + DataConverterPotionId.potions[2] = "minecraft:swiftness"; + DataConverterPotionId.potions[3] = "minecraft:fire_resistance"; + DataConverterPotionId.potions[4] = "minecraft:poison"; + DataConverterPotionId.potions[5] = "minecraft:healing"; + DataConverterPotionId.potions[6] = "minecraft:night_vision"; + DataConverterPotionId.potions[7] = null; + DataConverterPotionId.potions[8] = "minecraft:weakness"; + DataConverterPotionId.potions[9] = "minecraft:strength"; + DataConverterPotionId.potions[10] = "minecraft:slowness"; + DataConverterPotionId.potions[11] = "minecraft:leaping"; + DataConverterPotionId.potions[12] = "minecraft:harming"; + DataConverterPotionId.potions[13] = "minecraft:water_breathing"; + DataConverterPotionId.potions[14] = "minecraft:invisibility"; + DataConverterPotionId.potions[15] = null; + DataConverterPotionId.potions[16] = "minecraft:awkward"; + DataConverterPotionId.potions[17] = "minecraft:regeneration"; + DataConverterPotionId.potions[18] = "minecraft:swiftness"; + DataConverterPotionId.potions[19] = "minecraft:fire_resistance"; + DataConverterPotionId.potions[20] = "minecraft:poison"; + DataConverterPotionId.potions[21] = "minecraft:healing"; + DataConverterPotionId.potions[22] = "minecraft:night_vision"; + DataConverterPotionId.potions[23] = null; + DataConverterPotionId.potions[24] = "minecraft:weakness"; + DataConverterPotionId.potions[25] = "minecraft:strength"; + DataConverterPotionId.potions[26] = "minecraft:slowness"; + DataConverterPotionId.potions[27] = "minecraft:leaping"; + DataConverterPotionId.potions[28] = "minecraft:harming"; + DataConverterPotionId.potions[29] = "minecraft:water_breathing"; + DataConverterPotionId.potions[30] = "minecraft:invisibility"; + DataConverterPotionId.potions[31] = null; + DataConverterPotionId.potions[32] = "minecraft:thick"; + DataConverterPotionId.potions[33] = "minecraft:strong_regeneration"; + DataConverterPotionId.potions[34] = "minecraft:strong_swiftness"; + DataConverterPotionId.potions[35] = "minecraft:fire_resistance"; + DataConverterPotionId.potions[36] = "minecraft:strong_poison"; + DataConverterPotionId.potions[37] = "minecraft:strong_healing"; + DataConverterPotionId.potions[38] = "minecraft:night_vision"; + DataConverterPotionId.potions[39] = null; + DataConverterPotionId.potions[40] = "minecraft:weakness"; + DataConverterPotionId.potions[41] = "minecraft:strong_strength"; + DataConverterPotionId.potions[42] = "minecraft:slowness"; + DataConverterPotionId.potions[43] = "minecraft:strong_leaping"; + DataConverterPotionId.potions[44] = "minecraft:strong_harming"; + DataConverterPotionId.potions[45] = "minecraft:water_breathing"; + DataConverterPotionId.potions[46] = "minecraft:invisibility"; + DataConverterPotionId.potions[47] = null; + DataConverterPotionId.potions[48] = null; + DataConverterPotionId.potions[49] = "minecraft:strong_regeneration"; + DataConverterPotionId.potions[50] = "minecraft:strong_swiftness"; + DataConverterPotionId.potions[51] = "minecraft:fire_resistance"; + DataConverterPotionId.potions[52] = "minecraft:strong_poison"; + DataConverterPotionId.potions[53] = "minecraft:strong_healing"; + DataConverterPotionId.potions[54] = "minecraft:night_vision"; + DataConverterPotionId.potions[55] = null; + DataConverterPotionId.potions[56] = "minecraft:weakness"; + DataConverterPotionId.potions[57] = "minecraft:strong_strength"; + DataConverterPotionId.potions[58] = "minecraft:slowness"; + DataConverterPotionId.potions[59] = "minecraft:strong_leaping"; + DataConverterPotionId.potions[60] = "minecraft:strong_harming"; + DataConverterPotionId.potions[61] = "minecraft:water_breathing"; + DataConverterPotionId.potions[62] = "minecraft:invisibility"; + DataConverterPotionId.potions[63] = null; + DataConverterPotionId.potions[64] = "minecraft:mundane"; + DataConverterPotionId.potions[65] = "minecraft:long_regeneration"; + DataConverterPotionId.potions[66] = "minecraft:long_swiftness"; + DataConverterPotionId.potions[67] = "minecraft:long_fire_resistance"; + DataConverterPotionId.potions[68] = "minecraft:long_poison"; + DataConverterPotionId.potions[69] = "minecraft:healing"; + DataConverterPotionId.potions[70] = "minecraft:long_night_vision"; + DataConverterPotionId.potions[71] = null; + DataConverterPotionId.potions[72] = "minecraft:long_weakness"; + DataConverterPotionId.potions[73] = "minecraft:long_strength"; + DataConverterPotionId.potions[74] = "minecraft:long_slowness"; + DataConverterPotionId.potions[75] = "minecraft:long_leaping"; + DataConverterPotionId.potions[76] = "minecraft:harming"; + DataConverterPotionId.potions[77] = "minecraft:long_water_breathing"; + DataConverterPotionId.potions[78] = "minecraft:long_invisibility"; + DataConverterPotionId.potions[79] = null; + DataConverterPotionId.potions[80] = "minecraft:awkward"; + DataConverterPotionId.potions[81] = "minecraft:long_regeneration"; + DataConverterPotionId.potions[82] = "minecraft:long_swiftness"; + DataConverterPotionId.potions[83] = "minecraft:long_fire_resistance"; + DataConverterPotionId.potions[84] = "minecraft:long_poison"; + DataConverterPotionId.potions[85] = "minecraft:healing"; + DataConverterPotionId.potions[86] = "minecraft:long_night_vision"; + DataConverterPotionId.potions[87] = null; + DataConverterPotionId.potions[88] = "minecraft:long_weakness"; + DataConverterPotionId.potions[89] = "minecraft:long_strength"; + DataConverterPotionId.potions[90] = "minecraft:long_slowness"; + DataConverterPotionId.potions[91] = "minecraft:long_leaping"; + DataConverterPotionId.potions[92] = "minecraft:harming"; + DataConverterPotionId.potions[93] = "minecraft:long_water_breathing"; + DataConverterPotionId.potions[94] = "minecraft:long_invisibility"; + DataConverterPotionId.potions[95] = null; + DataConverterPotionId.potions[96] = "minecraft:thick"; + DataConverterPotionId.potions[97] = "minecraft:regeneration"; + DataConverterPotionId.potions[98] = "minecraft:swiftness"; + DataConverterPotionId.potions[99] = "minecraft:long_fire_resistance"; + DataConverterPotionId.potions[100] = "minecraft:poison"; + DataConverterPotionId.potions[101] = "minecraft:strong_healing"; + DataConverterPotionId.potions[102] = "minecraft:long_night_vision"; + DataConverterPotionId.potions[103] = null; + DataConverterPotionId.potions[104] = "minecraft:long_weakness"; + DataConverterPotionId.potions[105] = "minecraft:strength"; + DataConverterPotionId.potions[106] = "minecraft:long_slowness"; + DataConverterPotionId.potions[107] = "minecraft:leaping"; + DataConverterPotionId.potions[108] = "minecraft:strong_harming"; + DataConverterPotionId.potions[109] = "minecraft:long_water_breathing"; + DataConverterPotionId.potions[110] = "minecraft:long_invisibility"; + DataConverterPotionId.potions[111] = null; + DataConverterPotionId.potions[112] = null; + DataConverterPotionId.potions[113] = "minecraft:regeneration"; + DataConverterPotionId.potions[114] = "minecraft:swiftness"; + DataConverterPotionId.potions[115] = "minecraft:long_fire_resistance"; + DataConverterPotionId.potions[116] = "minecraft:poison"; + DataConverterPotionId.potions[117] = "minecraft:strong_healing"; + DataConverterPotionId.potions[118] = "minecraft:long_night_vision"; + DataConverterPotionId.potions[119] = null; + DataConverterPotionId.potions[120] = "minecraft:long_weakness"; + DataConverterPotionId.potions[121] = "minecraft:strength"; + DataConverterPotionId.potions[122] = "minecraft:long_slowness"; + DataConverterPotionId.potions[123] = "minecraft:leaping"; + DataConverterPotionId.potions[124] = "minecraft:strong_harming"; + DataConverterPotionId.potions[125] = "minecraft:long_water_breathing"; + DataConverterPotionId.potions[126] = "minecraft:long_invisibility"; + DataConverterPotionId.potions[127] = null; + } + } + + private static class DataConverterSpawnEgg implements DataConverter { + + private static final String[] eggs = new String[256]; + + DataConverterSpawnEgg() { + } + + public int getDataVersion() { + return 105; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:spawn_egg".equals(cmp.getString("id"))) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("EntityTag"); + short short0 = cmp.getShort("Damage"); + + if (!nbttagcompound2.contains("id", 8)) { + String s = DataConverterSpawnEgg.eggs[short0 & 255]; + + if (s != null) { + nbttagcompound2.putString("id", s); + nbttagcompound1.put("EntityTag", nbttagcompound2); + cmp.put("tag", nbttagcompound1); + } + } + + if (short0 != 0) { + cmp.putShort("Damage", (short) 0); + } + } + + return cmp; + } + + static { + + DataConverterSpawnEgg.eggs[1] = "Item"; + DataConverterSpawnEgg.eggs[2] = "XPOrb"; + DataConverterSpawnEgg.eggs[7] = "ThrownEgg"; + DataConverterSpawnEgg.eggs[8] = "LeashKnot"; + DataConverterSpawnEgg.eggs[9] = "Painting"; + DataConverterSpawnEgg.eggs[10] = "Arrow"; + DataConverterSpawnEgg.eggs[11] = "Snowball"; + DataConverterSpawnEgg.eggs[12] = "Fireball"; + DataConverterSpawnEgg.eggs[13] = "SmallFireball"; + DataConverterSpawnEgg.eggs[14] = "ThrownEnderpearl"; + DataConverterSpawnEgg.eggs[15] = "EyeOfEnderSignal"; + DataConverterSpawnEgg.eggs[16] = "ThrownPotion"; + DataConverterSpawnEgg.eggs[17] = "ThrownExpBottle"; + DataConverterSpawnEgg.eggs[18] = "ItemFrame"; + DataConverterSpawnEgg.eggs[19] = "WitherSkull"; + DataConverterSpawnEgg.eggs[20] = "PrimedTnt"; + DataConverterSpawnEgg.eggs[21] = "FallingSand"; + DataConverterSpawnEgg.eggs[22] = "FireworksRocketEntity"; + DataConverterSpawnEgg.eggs[23] = "TippedArrow"; + DataConverterSpawnEgg.eggs[24] = "SpectralArrow"; + DataConverterSpawnEgg.eggs[25] = "ShulkerBullet"; + DataConverterSpawnEgg.eggs[26] = "DragonFireball"; + DataConverterSpawnEgg.eggs[30] = "ArmorStand"; + DataConverterSpawnEgg.eggs[41] = "Boat"; + DataConverterSpawnEgg.eggs[42] = "MinecartRideable"; + DataConverterSpawnEgg.eggs[43] = "MinecartChest"; + DataConverterSpawnEgg.eggs[44] = "MinecartFurnace"; + DataConverterSpawnEgg.eggs[45] = "MinecartTNT"; + DataConverterSpawnEgg.eggs[46] = "MinecartHopper"; + DataConverterSpawnEgg.eggs[47] = "MinecartSpawner"; + DataConverterSpawnEgg.eggs[40] = "MinecartCommandBlock"; + DataConverterSpawnEgg.eggs[48] = "Mob"; + DataConverterSpawnEgg.eggs[49] = "Monster"; + DataConverterSpawnEgg.eggs[50] = "Creeper"; + DataConverterSpawnEgg.eggs[51] = "Skeleton"; + DataConverterSpawnEgg.eggs[52] = "Spider"; + DataConverterSpawnEgg.eggs[53] = "Giant"; + DataConverterSpawnEgg.eggs[54] = "Zombie"; + DataConverterSpawnEgg.eggs[55] = "Slime"; + DataConverterSpawnEgg.eggs[56] = "Ghast"; + DataConverterSpawnEgg.eggs[57] = "PigZombie"; + DataConverterSpawnEgg.eggs[58] = "Enderman"; + DataConverterSpawnEgg.eggs[59] = "CaveSpider"; + DataConverterSpawnEgg.eggs[60] = "Silverfish"; + DataConverterSpawnEgg.eggs[61] = "Blaze"; + DataConverterSpawnEgg.eggs[62] = "LavaSlime"; + DataConverterSpawnEgg.eggs[63] = "EnderDragon"; + DataConverterSpawnEgg.eggs[64] = "WitherBoss"; + DataConverterSpawnEgg.eggs[65] = "Bat"; + DataConverterSpawnEgg.eggs[66] = "Witch"; + DataConverterSpawnEgg.eggs[67] = "Endermite"; + DataConverterSpawnEgg.eggs[68] = "Guardian"; + DataConverterSpawnEgg.eggs[69] = "Shulker"; + DataConverterSpawnEgg.eggs[90] = "Pig"; + DataConverterSpawnEgg.eggs[91] = "Sheep"; + DataConverterSpawnEgg.eggs[92] = "Cow"; + DataConverterSpawnEgg.eggs[93] = "Chicken"; + DataConverterSpawnEgg.eggs[94] = "Squid"; + DataConverterSpawnEgg.eggs[95] = "Wolf"; + DataConverterSpawnEgg.eggs[96] = "MushroomCow"; + DataConverterSpawnEgg.eggs[97] = "SnowMan"; + DataConverterSpawnEgg.eggs[98] = "Ozelot"; + DataConverterSpawnEgg.eggs[99] = "VillagerGolem"; + DataConverterSpawnEgg.eggs[100] = "EntityHorse"; + DataConverterSpawnEgg.eggs[101] = "Rabbit"; + DataConverterSpawnEgg.eggs[120] = "Villager"; + DataConverterSpawnEgg.eggs[200] = "EnderCrystal"; + } + } + + private static class DataConverterMinecart implements DataConverter { + + private static final List a = Lists.newArrayList("MinecartRideable", "MinecartChest", "MinecartFurnace", "MinecartTNT", "MinecartSpawner", "MinecartHopper", "MinecartCommandBlock"); + + DataConverterMinecart() { + } + + public int getDataVersion() { + return 106; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Minecart".equals(cmp.getString("id"))) { + String s = "MinecartRideable"; + int i = cmp.getInt("Type"); + + if (i > 0 && i < DataConverterMinecart.a.size()) { + s = DataConverterMinecart.a.get(i); + } + + cmp.putString("id", s); + cmp.remove("Type"); + } + + return cmp; + } + } + + private static class DataConverterMobSpawner implements DataConverter { + + DataConverterMobSpawner() { + } + + public int getDataVersion() { + return 107; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if (!"MobSpawner".equals(cmp.getString("id"))) { + return cmp; + } else { + if (cmp.contains("EntityId", 8)) { + String s = cmp.getString("EntityId"); + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("SpawnData"); + + nbttagcompound1.putString("id", s.isEmpty() ? "Pig" : s); + cmp.put("SpawnData", nbttagcompound1); + cmp.remove("EntityId"); + } + + if (cmp.contains("SpawnPotentials", 9)) { + net.minecraft.nbt.ListTag nbttaglist = cmp.getList("SpawnPotentials", 10); + + for (int i = 0; i < nbttaglist.size(); ++i) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttaglist.getCompound(i); + + if (nbttagcompound2.contains("Type", 8)) { + net.minecraft.nbt.CompoundTag nbttagcompound3 = nbttagcompound2.getCompound("Properties"); + + nbttagcompound3.putString("id", nbttagcompound2.getString("Type")); + nbttagcompound2.put("Entity", nbttagcompound3); + nbttagcompound2.remove("Type"); + nbttagcompound2.remove("Properties"); + } + } + } + + return cmp; + } + } + } + + private static class DataConverterUUID implements DataConverter { + + DataConverterUUID() { + } + + public int getDataVersion() { + return 108; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if (cmp.contains("UUID", 8)) { + cmp.putUUID("UUID", UUID.fromString(cmp.getString("UUID"))); + } + + return cmp; + } + } + + private static class DataConverterHealth implements DataConverter { + + private static final Set a = Sets.newHashSet("ArmorStand", "Bat", "Blaze", "CaveSpider", "Chicken", "Cow", "Creeper", "EnderDragon", "Enderman", "Endermite", "EntityHorse", "Ghast", "Giant", "Guardian", "LavaSlime", "MushroomCow", "Ozelot", "Pig", "PigZombie", "Rabbit", "Sheep", "Shulker", "Silverfish", "Skeleton", "Slime", "SnowMan", "Spider", "Squid", "Villager", "VillagerGolem", "Witch", "WitherBoss", "Wolf", "Zombie"); + + DataConverterHealth() { + } + + public int getDataVersion() { + return 109; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if (DataConverterHealth.a.contains(cmp.getString("id"))) { + float f; + + if (cmp.contains("HealF", 99)) { + f = cmp.getFloat("HealF"); + cmp.remove("HealF"); + } else { + if (!cmp.contains("Health", 99)) { + return cmp; + } + + f = cmp.getFloat("Health"); + } + + cmp.putFloat("Health", f); + } + + return cmp; + } + } + + private static class DataConverterSaddle implements DataConverter { + + DataConverterSaddle() { + } + + public int getDataVersion() { + return 110; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("EntityHorse".equals(cmp.getString("id")) && !cmp.contains("SaddleItem", 10) && cmp.getBoolean("Saddle")) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = new net.minecraft.nbt.CompoundTag(); + + nbttagcompound1.putString("id", "minecraft:saddle"); + nbttagcompound1.putByte("Count", (byte) 1); + nbttagcompound1.putShort("Damage", (short) 0); + cmp.put("SaddleItem", nbttagcompound1); + cmp.remove("Saddle"); + } + + return cmp; + } + } + + private static class DataConverterHanging implements DataConverter { + + DataConverterHanging() { + } + + public int getDataVersion() { + return 111; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = cmp.getString("id"); + boolean flag = "Painting".equals(s); + boolean flag1 = "ItemFrame".equals(s); + + if ((flag || flag1) && !cmp.contains("Facing", 99)) { + Direction enumdirection; + + if (cmp.contains("Direction", 99)) { + enumdirection = Direction.from2DDataValue(cmp.getByte("Direction")); + cmp.putInt("TileX", cmp.getInt("TileX") + enumdirection.getStepX()); + cmp.putInt("TileY", cmp.getInt("TileY") + enumdirection.getStepY()); + cmp.putInt("TileZ", cmp.getInt("TileZ") + enumdirection.getStepZ()); + cmp.remove("Direction"); + if (flag1 && cmp.contains("ItemRotation", 99)) { + cmp.putByte("ItemRotation", (byte) (cmp.getByte("ItemRotation") * 2)); + } + } else { + enumdirection = Direction.from2DDataValue(cmp.getByte("Dir")); + cmp.remove("Dir"); + } + + cmp.putByte("Facing", (byte) enumdirection.get2DDataValue()); + } + + return cmp; + } + } + + private static class DataConverterDropChances implements DataConverter { + + DataConverterDropChances() { + } + + public int getDataVersion() { + return 113; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + net.minecraft.nbt.ListTag nbttaglist; + + if (cmp.contains("HandDropChances", 9)) { + nbttaglist = cmp.getList("HandDropChances", 5); + if (nbttaglist.size() == 2 && nbttaglist.getFloat(0) == 0.0F && nbttaglist.getFloat(1) == 0.0F) { + cmp.remove("HandDropChances"); + } + } + + if (cmp.contains("ArmorDropChances", 9)) { + nbttaglist = cmp.getList("ArmorDropChances", 5); + if (nbttaglist.size() == 4 && nbttaglist.getFloat(0) == 0.0F && nbttaglist.getFloat(1) == 0.0F && nbttaglist.getFloat(2) == 0.0F && nbttaglist.getFloat(3) == 0.0F) { + cmp.remove("ArmorDropChances"); + } + } + + return cmp; + } + } + + private static class DataConverterRiding implements DataConverter { + + DataConverterRiding() { + } + + public int getDataVersion() { + return 135; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + while (cmp.contains("Riding", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = this.b(cmp); + + this.convert(cmp, nbttagcompound1); + cmp = nbttagcompound1; + } + + return cmp; + } + + protected void convert(net.minecraft.nbt.CompoundTag nbttagcompound, net.minecraft.nbt.CompoundTag nbttagcompound1) { + net.minecraft.nbt.ListTag nbttaglist = new net.minecraft.nbt.ListTag(); + + nbttaglist.add(nbttagcompound); + nbttagcompound1.put("Passengers", nbttaglist); + } + + protected net.minecraft.nbt.CompoundTag b(net.minecraft.nbt.CompoundTag nbttagcompound) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = nbttagcompound.getCompound("Riding"); + + nbttagcompound.remove("Riding"); + return nbttagcompound1; + } + } + + private static class DataConverterBook implements DataConverter { + + DataConverterBook() { + } + + public int getDataVersion() { + return 165; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:written_book".equals(cmp.getString("id"))) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + + if (nbttagcompound1.contains("pages", 9)) { + net.minecraft.nbt.ListTag nbttaglist = nbttagcompound1.getList("pages", 8); + + for (int i = 0; i < nbttaglist.size(); ++i) { + String s = nbttaglist.getString(i); + Component object = null; + + if (!"null".equals(s) && !StringUtil.isNullOrEmpty(s)) { + if ((s.charAt(0) != 34 || s.charAt(s.length() - 1) != 34) && (s.charAt(0) != 123 || s.charAt(s.length() - 1) != 125)) { + object = Component.literal(s); + } else { + try { + object = GsonHelper.fromJson(DataConverterSignText.a, s, Component.class, true); + if (object == null) { + object = Component.literal(""); + } + } catch (JsonParseException jsonparseexception) { + ; + } + + if (object == null) { + try { + object = Component.Serializer.fromJson(s); + } catch (JsonParseException jsonparseexception1) { + ; + } + } + + if (object == null) { + try { + object = Component.Serializer.fromJsonLenient(s); + } catch (JsonParseException jsonparseexception2) { + ; + } + } + + if (object == null) { + object = Component.literal(s); + } + } + } else { + object = Component.literal(""); + } + + nbttaglist.set(i, net.minecraft.nbt.StringTag.valueOf(Component.Serializer.toJson(object))); + } + + nbttagcompound1.put("pages", nbttaglist); + } + } + + return cmp; + } + } + + private static class DataConverterCookedFish implements DataConverter { + + private static final ResourceLocation a = new ResourceLocation("cooked_fished"); + + DataConverterCookedFish() { + } + + public int getDataVersion() { + return 502; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if (cmp.contains("id", 8) && DataConverterCookedFish.a.equals(new ResourceLocation(cmp.getString("id")))) { + cmp.putString("id", "minecraft:cooked_fish"); + } + + return cmp; + } + } + + private static class DataConverterZombie implements DataConverter { + + private static final Random a = new Random(); + + DataConverterZombie() { + } + + public int getDataVersion() { + return 502; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Zombie".equals(cmp.getString("id")) && cmp.getBoolean("IsVillager")) { + if (!cmp.contains("ZombieType", 99)) { + int i = -1; + + if (cmp.contains("VillagerProfession", 99)) { + try { + i = this.convert(cmp.getInt("VillagerProfession")); + } catch (RuntimeException runtimeexception) { + ; + } + } + + if (i == -1) { + i = this.convert(DataConverterZombie.a.nextInt(6)); + } + + cmp.putInt("ZombieType", i); + } + + cmp.remove("IsVillager"); + } + + return cmp; + } + + private int convert(int i) { + return i >= 0 && i < 6 ? i : -1; + } + } + + private static class DataConverterVBO implements DataConverter { + + DataConverterVBO() { + } + + public int getDataVersion() { + return 505; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + cmp.putString("useVbo", "true"); + return cmp; + } + } + + private static class DataConverterGuardian implements DataConverter { + + DataConverterGuardian() { + } + + public int getDataVersion() { + return 700; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Guardian".equals(cmp.getString("id"))) { + if (cmp.getBoolean("Elder")) { + cmp.putString("id", "ElderGuardian"); + } + + cmp.remove("Elder"); + } + + return cmp; + } + } + + private static class DataConverterSkeleton implements DataConverter { + + DataConverterSkeleton() { + } + + public int getDataVersion() { + return 701; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = cmp.getString("id"); + + if ("Skeleton".equals(s)) { + int i = cmp.getInt("SkeletonType"); + + if (i == 1) { + cmp.putString("id", "WitherSkeleton"); + } else if (i == 2) { + cmp.putString("id", "Stray"); + } + + cmp.remove("SkeletonType"); + } + + return cmp; + } + } + + private static class DataConverterZombieType implements DataConverter { + + DataConverterZombieType() { + } + + public int getDataVersion() { + return 702; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Zombie".equals(cmp.getString("id"))) { + int i = cmp.getInt("ZombieType"); + + switch (i) { + case 0: + default: + break; + + case 1: + case 2: + case 3: + case 4: + case 5: + cmp.putString("id", "ZombieVillager"); + cmp.putInt("Profession", i - 1); + break; + + case 6: + cmp.putString("id", "Husk"); + } + + cmp.remove("ZombieType"); + } + + return cmp; + } + } + + private static class DataConverterHorse implements DataConverter { + + DataConverterHorse() { + } + + public int getDataVersion() { + return 703; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("EntityHorse".equals(cmp.getString("id"))) { + int i = cmp.getInt("Type"); + + switch (i) { + case 0: + default: + cmp.putString("id", "Horse"); + break; + + case 1: + cmp.putString("id", "Donkey"); + break; + + case 2: + cmp.putString("id", "Mule"); + break; + + case 3: + cmp.putString("id", "ZombieHorse"); + break; + + case 4: + cmp.putString("id", "SkeletonHorse"); + } + + cmp.remove("Type"); + } + + return cmp; + } + } + + private static class DataConverterTileEntity implements DataConverter { + + private static final Map a = Maps.newHashMap(); + + DataConverterTileEntity() { + } + + public int getDataVersion() { + return 704; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = DataConverterTileEntity.a.get(cmp.getString("id")); + + if (s != null) { + cmp.putString("id", s); + } + + return cmp; + } + + static { + DataConverterTileEntity.a.put("Airportal", "minecraft:end_portal"); + DataConverterTileEntity.a.put("Banner", "minecraft:banner"); + DataConverterTileEntity.a.put("Beacon", "minecraft:beacon"); + DataConverterTileEntity.a.put("Cauldron", "minecraft:brewing_stand"); + DataConverterTileEntity.a.put("Chest", "minecraft:chest"); + DataConverterTileEntity.a.put("Comparator", "minecraft:comparator"); + DataConverterTileEntity.a.put("Control", "minecraft:command_block"); + DataConverterTileEntity.a.put("DLDetector", "minecraft:daylight_detector"); + DataConverterTileEntity.a.put("Dropper", "minecraft:dropper"); + DataConverterTileEntity.a.put("EnchantTable", "minecraft:enchanting_table"); + DataConverterTileEntity.a.put("EndGateway", "minecraft:end_gateway"); + DataConverterTileEntity.a.put("EnderChest", "minecraft:ender_chest"); + DataConverterTileEntity.a.put("FlowerPot", "minecraft:flower_pot"); + DataConverterTileEntity.a.put("Furnace", "minecraft:furnace"); + DataConverterTileEntity.a.put("Hopper", "minecraft:hopper"); + DataConverterTileEntity.a.put("MobSpawner", "minecraft:mob_spawner"); + DataConverterTileEntity.a.put("Music", "minecraft:noteblock"); + DataConverterTileEntity.a.put("Piston", "minecraft:piston"); + DataConverterTileEntity.a.put("RecordPlayer", "minecraft:jukebox"); + DataConverterTileEntity.a.put("Sign", "minecraft:sign"); + DataConverterTileEntity.a.put("Skull", "minecraft:skull"); + DataConverterTileEntity.a.put("Structure", "minecraft:structure_block"); + DataConverterTileEntity.a.put("Trap", "minecraft:dispenser"); + } + } + + private static class DataConverterEntity implements DataConverter { + + private static final Map a = Maps.newHashMap(); + + DataConverterEntity() { + } + + public int getDataVersion() { + return 704; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = DataConverterEntity.a.get(cmp.getString("id")); + + if (s != null) { + cmp.putString("id", s); + } + + return cmp; + } + + static { + DataConverterEntity.a.put("AreaEffectCloud", "minecraft:area_effect_cloud"); + DataConverterEntity.a.put("ArmorStand", "minecraft:armor_stand"); + DataConverterEntity.a.put("Arrow", "minecraft:arrow"); + DataConverterEntity.a.put("Bat", "minecraft:bat"); + DataConverterEntity.a.put("Blaze", "minecraft:blaze"); + DataConverterEntity.a.put("Boat", "minecraft:boat"); + DataConverterEntity.a.put("CaveSpider", "minecraft:cave_spider"); + DataConverterEntity.a.put("Chicken", "minecraft:chicken"); + DataConverterEntity.a.put("Cow", "minecraft:cow"); + DataConverterEntity.a.put("Creeper", "minecraft:creeper"); + DataConverterEntity.a.put("Donkey", "minecraft:donkey"); + DataConverterEntity.a.put("DragonFireball", "minecraft:dragon_fireball"); + DataConverterEntity.a.put("ElderGuardian", "minecraft:elder_guardian"); + DataConverterEntity.a.put("EnderCrystal", "minecraft:ender_crystal"); + DataConverterEntity.a.put("EnderDragon", "minecraft:ender_dragon"); + DataConverterEntity.a.put("Enderman", "minecraft:enderman"); + DataConverterEntity.a.put("Endermite", "minecraft:endermite"); + DataConverterEntity.a.put("EyeOfEnderSignal", "minecraft:eye_of_ender_signal"); + DataConverterEntity.a.put("FallingSand", "minecraft:falling_block"); + DataConverterEntity.a.put("Fireball", "minecraft:fireball"); + DataConverterEntity.a.put("FireworksRocketEntity", "minecraft:fireworks_rocket"); + DataConverterEntity.a.put("Ghast", "minecraft:ghast"); + DataConverterEntity.a.put("Giant", "minecraft:giant"); + DataConverterEntity.a.put("Guardian", "minecraft:guardian"); + DataConverterEntity.a.put("Horse", "minecraft:horse"); + DataConverterEntity.a.put("Husk", "minecraft:husk"); + DataConverterEntity.a.put("Item", "minecraft:item"); + DataConverterEntity.a.put("ItemFrame", "minecraft:item_frame"); + DataConverterEntity.a.put("LavaSlime", "minecraft:magma_cube"); + DataConverterEntity.a.put("LeashKnot", "minecraft:leash_knot"); + DataConverterEntity.a.put("MinecartChest", "minecraft:chest_minecart"); + DataConverterEntity.a.put("MinecartCommandBlock", "minecraft:commandblock_minecart"); + DataConverterEntity.a.put("MinecartFurnace", "minecraft:furnace_minecart"); + DataConverterEntity.a.put("MinecartHopper", "minecraft:hopper_minecart"); + DataConverterEntity.a.put("MinecartRideable", "minecraft:minecart"); + DataConverterEntity.a.put("MinecartSpawner", "minecraft:spawner_minecart"); + DataConverterEntity.a.put("MinecartTNT", "minecraft:tnt_minecart"); + DataConverterEntity.a.put("Mule", "minecraft:mule"); + DataConverterEntity.a.put("MushroomCow", "minecraft:mooshroom"); + DataConverterEntity.a.put("Ozelot", "minecraft:ocelot"); + DataConverterEntity.a.put("Painting", "minecraft:painting"); + DataConverterEntity.a.put("Pig", "minecraft:pig"); + DataConverterEntity.a.put("PigZombie", "minecraft:zombie_pigman"); + DataConverterEntity.a.put("PolarBear", "minecraft:polar_bear"); + DataConverterEntity.a.put("PrimedTnt", "minecraft:tnt"); + DataConverterEntity.a.put("Rabbit", "minecraft:rabbit"); + DataConverterEntity.a.put("Sheep", "minecraft:sheep"); + DataConverterEntity.a.put("Shulker", "minecraft:shulker"); + DataConverterEntity.a.put("ShulkerBullet", "minecraft:shulker_bullet"); + DataConverterEntity.a.put("Silverfish", "minecraft:silverfish"); + DataConverterEntity.a.put("Skeleton", "minecraft:skeleton"); + DataConverterEntity.a.put("SkeletonHorse", "minecraft:skeleton_horse"); + DataConverterEntity.a.put("Slime", "minecraft:slime"); + DataConverterEntity.a.put("SmallFireball", "minecraft:small_fireball"); + DataConverterEntity.a.put("SnowMan", "minecraft:snowman"); + DataConverterEntity.a.put("Snowball", "minecraft:snowball"); + DataConverterEntity.a.put("SpectralArrow", "minecraft:spectral_arrow"); + DataConverterEntity.a.put("Spider", "minecraft:spider"); + DataConverterEntity.a.put("Squid", "minecraft:squid"); + DataConverterEntity.a.put("Stray", "minecraft:stray"); + DataConverterEntity.a.put("ThrownEgg", "minecraft:egg"); + DataConverterEntity.a.put("ThrownEnderpearl", "minecraft:ender_pearl"); + DataConverterEntity.a.put("ThrownExpBottle", "minecraft:xp_bottle"); + DataConverterEntity.a.put("ThrownPotion", "minecraft:potion"); + DataConverterEntity.a.put("Villager", "minecraft:villager"); + DataConverterEntity.a.put("VillagerGolem", "minecraft:villager_golem"); + DataConverterEntity.a.put("Witch", "minecraft:witch"); + DataConverterEntity.a.put("WitherBoss", "minecraft:wither"); + DataConverterEntity.a.put("WitherSkeleton", "minecraft:wither_skeleton"); + DataConverterEntity.a.put("WitherSkull", "minecraft:wither_skull"); + DataConverterEntity.a.put("Wolf", "minecraft:wolf"); + DataConverterEntity.a.put("XPOrb", "minecraft:xp_orb"); + DataConverterEntity.a.put("Zombie", "minecraft:zombie"); + DataConverterEntity.a.put("ZombieHorse", "minecraft:zombie_horse"); + DataConverterEntity.a.put("ZombieVillager", "minecraft:zombie_villager"); + } + } + + private static class DataConverterPotionWater implements DataConverter { + + DataConverterPotionWater() { + } + + public int getDataVersion() { + return 806; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = cmp.getString("id"); + + if ("minecraft:potion".equals(s) || "minecraft:splash_potion".equals(s) || "minecraft:lingering_potion".equals(s) || "minecraft:tipped_arrow".equals(s)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + + if (!nbttagcompound1.contains("Potion", 8)) { + nbttagcompound1.putString("Potion", "minecraft:water"); + } + + if (!cmp.contains("tag", 10)) { + cmp.put("tag", nbttagcompound1); + } + } + + return cmp; + } + } + + private static class DataConverterShulker implements DataConverter { + + DataConverterShulker() { + } + + public int getDataVersion() { + return 808; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:shulker".equals(cmp.getString("id")) && !cmp.contains("Color", 99)) { + cmp.putByte("Color", (byte) 10); + } + + return cmp; + } + } + + private static class DataConverterShulkerBoxItem implements DataConverter { + + public static final String[] a = new String[] { "minecraft:white_shulker_box", "minecraft:orange_shulker_box", "minecraft:magenta_shulker_box", "minecraft:light_blue_shulker_box", "minecraft:yellow_shulker_box", "minecraft:lime_shulker_box", "minecraft:pink_shulker_box", "minecraft:gray_shulker_box", "minecraft:silver_shulker_box", "minecraft:cyan_shulker_box", "minecraft:purple_shulker_box", "minecraft:blue_shulker_box", "minecraft:brown_shulker_box", "minecraft:green_shulker_box", "minecraft:red_shulker_box", "minecraft:black_shulker_box" }; + + DataConverterShulkerBoxItem() { + } + + public int getDataVersion() { + return 813; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:shulker_box".equals(cmp.getString("id")) && cmp.contains("tag", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + + if (nbttagcompound1.contains("BlockEntityTag", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("BlockEntityTag"); + + if (nbttagcompound2.getList("Items", 10).isEmpty()) { + nbttagcompound2.remove("Items"); + } + + int i = nbttagcompound2.getInt("Color"); + + nbttagcompound2.remove("Color"); + if (nbttagcompound2.isEmpty()) { + nbttagcompound1.remove("BlockEntityTag"); + } + + if (nbttagcompound1.isEmpty()) { + cmp.remove("tag"); + } + + cmp.putString("id", DataConverterShulkerBoxItem.a[i % 16]); + } + } + + return cmp; + } + } + + private static class DataConverterShulkerBoxBlock implements DataConverter { + + DataConverterShulkerBoxBlock() { + } + + public int getDataVersion() { + return 813; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:shulker".equals(cmp.getString("id"))) { + cmp.remove("Color"); + } + + return cmp; + } + } + + private static class DataConverterLang implements DataConverter { + + DataConverterLang() { + } + + public int getDataVersion() { + return 816; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if (cmp.contains("lang", 8)) { + cmp.putString("lang", cmp.getString("lang").toLowerCase(Locale.ROOT)); + } + + return cmp; + } + } + + private static class DataConverterTotem implements DataConverter { + + DataConverterTotem() { + } + + public int getDataVersion() { + return 820; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:totem".equals(cmp.getString("id"))) { + cmp.putString("id", "minecraft:totem_of_undying"); + } + + return cmp; + } + } + + private static class DataConverterBedBlock implements DataConverter { + + private static final Logger a = LogManager.getLogger(PaperweightDataConverters.class); + + DataConverterBedBlock() { + } + + public int getDataVersion() { + return 1125; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + try { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("Level"); + int i = nbttagcompound1.getInt("xPos"); + int j = nbttagcompound1.getInt("zPos"); + net.minecraft.nbt.ListTag nbttaglist = nbttagcompound1.getList("TileEntities", 10); + net.minecraft.nbt.ListTag nbttaglist1 = nbttagcompound1.getList("Sections", 10); + + for (int k = 0; k < nbttaglist1.size(); ++k) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttaglist1.getCompound(k); + byte b0 = nbttagcompound2.getByte("Y"); + byte[] abyte = nbttagcompound2.getByteArray("Blocks"); + + for (int l = 0; l < abyte.length; ++l) { + if (416 == (abyte[l] & 255) << 4) { + int i1 = l & 15; + int j1 = l >> 8 & 15; + int k1 = l >> 4 & 15; + net.minecraft.nbt.CompoundTag nbttagcompound3 = new net.minecraft.nbt.CompoundTag(); + + nbttagcompound3.putString("id", "bed"); + nbttagcompound3.putInt("x", i1 + (i << 4)); + nbttagcompound3.putInt("y", j1 + (b0 << 4)); + nbttagcompound3.putInt("z", k1 + (j << 4)); + nbttaglist.add(nbttagcompound3); + } + } + } + } catch (Exception exception) { + DataConverterBedBlock.a.warn("Unable to datafix Bed blocks, level format may be missing tags."); + } + + return cmp; + } + } + + private static class DataConverterBedItem implements DataConverter { + + DataConverterBedItem() { + } + + public int getDataVersion() { + return 1125; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:bed".equals(cmp.getString("id")) && cmp.getShort("Damage") == 0) { + cmp.putShort("Damage", (short) DyeColor.RED.getId()); + } + + return cmp; + } + } + + private static class DataConverterSignText implements DataConverter { + + public static final Gson a = new GsonBuilder().registerTypeAdapter(Component.class, new JsonDeserializer() { + MutableComponent a(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { + if (jsonelement.isJsonPrimitive()) { + return Component.literal(jsonelement.getAsString()); + } else if (jsonelement.isJsonArray()) { + JsonArray jsonarray = jsonelement.getAsJsonArray(); + MutableComponent ichatbasecomponent = null; + Iterator iterator = jsonarray.iterator(); + + while (iterator.hasNext()) { + JsonElement jsonelement1 = (JsonElement) iterator.next(); + MutableComponent ichatbasecomponent1 = this.a(jsonelement1, jsonelement1.getClass(), jsondeserializationcontext); + + if (ichatbasecomponent == null) { + ichatbasecomponent = ichatbasecomponent1; + } else { + ichatbasecomponent.append(ichatbasecomponent1); + } + } + + return ichatbasecomponent; + } else { + throw new JsonParseException("Don't know how to turn " + jsonelement + " into a Component"); + } + } + + public Object deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { + return this.a(jsonelement, type, jsondeserializationcontext); + } + }).create(); + + DataConverterSignText() { + } + + public int getDataVersion() { + return 101; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Sign".equals(cmp.getString("id"))) { + this.convert(cmp, "Text1"); + this.convert(cmp, "Text2"); + this.convert(cmp, "Text3"); + this.convert(cmp, "Text4"); + } + + return cmp; + } + + private void convert(net.minecraft.nbt.CompoundTag nbttagcompound, String s) { + String s1 = nbttagcompound.getString(s); + Component object = null; + + if (!"null".equals(s1) && !StringUtil.isNullOrEmpty(s1)) { + if ((s1.charAt(0) != 34 || s1.charAt(s1.length() - 1) != 34) && (s1.charAt(0) != 123 || s1.charAt(s1.length() - 1) != 125)) { + object = Component.literal(s1); + } else { + try { + object = GsonHelper.fromJson(DataConverterSignText.a, s1, Component.class, true); + if (object == null) { + object = Component.literal(""); + } + } catch (JsonParseException jsonparseexception) { + ; + } + + if (object == null) { + try { + object = Component.Serializer.fromJson(s1); + } catch (JsonParseException jsonparseexception1) { + ; + } + } + + if (object == null) { + try { + object = Component.Serializer.fromJsonLenient(s1); + } catch (JsonParseException jsonparseexception2) { + ; + } + } + + if (object == null) { + object = Component.literal(s1); + } + } + } else { + object = Component.literal(""); + } + + nbttagcompound.putString(s, Component.Serializer.toJson(object)); + } + } + + private static class DataInspectorPlayerVehicle implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (cmp.contains("RootVehicle", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("RootVehicle"); + + if (nbttagcompound1.contains("Entity", 10)) { + convertCompound(LegacyType.ENTITY, nbttagcompound1, "Entity", sourceVer, targetVer); + } + } + + return cmp; + } + } + + private static class DataInspectorLevelPlayer implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (cmp.contains("Player", 10)) { + convertCompound(LegacyType.PLAYER, cmp, "Player", sourceVer, targetVer); + } + + return cmp; + } + } + + private static class DataInspectorStructure implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + net.minecraft.nbt.ListTag nbttaglist; + int j; + net.minecraft.nbt.CompoundTag nbttagcompound1; + + if (cmp.contains("entities", 9)) { + nbttaglist = cmp.getList("entities", 10); + + for (j = 0; j < nbttaglist.size(); ++j) { + nbttagcompound1 = (net.minecraft.nbt.CompoundTag) nbttaglist.get(j); + if (nbttagcompound1.contains("nbt", 10)) { + convertCompound(LegacyType.ENTITY, nbttagcompound1, "nbt", sourceVer, targetVer); + } + } + } + + if (cmp.contains("blocks", 9)) { + nbttaglist = cmp.getList("blocks", 10); + + for (j = 0; j < nbttaglist.size(); ++j) { + nbttagcompound1 = (net.minecraft.nbt.CompoundTag) nbttaglist.get(j); + if (nbttagcompound1.contains("nbt", 10)) { + convertCompound(LegacyType.BLOCK_ENTITY, nbttagcompound1, "nbt", sourceVer, targetVer); + } + } + } + + return cmp; + } + } + + private static class DataInspectorChunks implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (cmp.contains("Level", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("Level"); + net.minecraft.nbt.ListTag nbttaglist; + int j; + + if (nbttagcompound1.contains("Entities", 9)) { + nbttaglist = nbttagcompound1.getList("Entities", 10); + + for (j = 0; j < nbttaglist.size(); ++j) { + nbttaglist.set(j, convert(LegacyType.ENTITY, (net.minecraft.nbt.CompoundTag) nbttaglist.get(j), sourceVer, targetVer)); + } + } + + if (nbttagcompound1.contains("TileEntities", 9)) { + nbttaglist = nbttagcompound1.getList("TileEntities", 10); + + for (j = 0; j < nbttaglist.size(); ++j) { + nbttaglist.set(j, convert(LegacyType.BLOCK_ENTITY, (net.minecraft.nbt.CompoundTag) nbttaglist.get(j), sourceVer, targetVer)); + } + } + } + + return cmp; + } + } + + private static class DataInspectorEntityPassengers implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (cmp.contains("Passengers", 9)) { + net.minecraft.nbt.ListTag nbttaglist = cmp.getList("Passengers", 10); + + for (int j = 0; j < nbttaglist.size(); ++j) { + nbttaglist.set(j, convert(LegacyType.ENTITY, nbttaglist.getCompound(j), sourceVer, targetVer)); + } + } + + return cmp; + } + } + + private static class DataInspectorPlayer implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + convertItems(cmp, "Inventory", sourceVer, targetVer); + convertItems(cmp, "EnderItems", sourceVer, targetVer); + if (cmp.contains("ShoulderEntityLeft", 10)) { + convertCompound(LegacyType.ENTITY, cmp, "ShoulderEntityLeft", sourceVer, targetVer); + } + + if (cmp.contains("ShoulderEntityRight", 10)) { + convertCompound(LegacyType.ENTITY, cmp, "ShoulderEntityRight", sourceVer, targetVer); + } + + return cmp; + } + } + + private static class DataInspectorVillagers implements DataInspector { + ResourceLocation entityVillager = getKey("EntityVillager"); + + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (entityVillager.equals(new ResourceLocation(cmp.getString("id"))) && cmp.contains("Offers", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("Offers"); + + if (nbttagcompound1.contains("Recipes", 9)) { + net.minecraft.nbt.ListTag nbttaglist = nbttagcompound1.getList("Recipes", 10); + + for (int j = 0; j < nbttaglist.size(); ++j) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttaglist.getCompound(j); + + convertItem(nbttagcompound2, "buy", sourceVer, targetVer); + convertItem(nbttagcompound2, "buyB", sourceVer, targetVer); + convertItem(nbttagcompound2, "sell", sourceVer, targetVer); + nbttaglist.set(j, nbttagcompound2); + } + } + } + + return cmp; + } + } + + private static class DataInspectorMobSpawnerMinecart implements DataInspector { + ResourceLocation entityMinecartMobSpawner = getKey("EntityMinecartMobSpawner"); + ResourceLocation tileEntityMobSpawner = getKey("TileEntityMobSpawner"); + + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + String s = cmp.getString("id"); + if (entityMinecartMobSpawner.equals(new ResourceLocation(s))) { + cmp.putString("id", tileEntityMobSpawner.toString()); + convert(LegacyType.BLOCK_ENTITY, cmp, sourceVer, targetVer); + cmp.putString("id", s); + } + + return cmp; + } + } + + private static class DataInspectorMobSpawnerMobs implements DataInspector { + ResourceLocation tileEntityMobSpawner = getKey("TileEntityMobSpawner"); + + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (tileEntityMobSpawner.equals(new ResourceLocation(cmp.getString("id")))) { + if (cmp.contains("SpawnPotentials", 9)) { + net.minecraft.nbt.ListTag nbttaglist = cmp.getList("SpawnPotentials", 10); + + for (int j = 0; j < nbttaglist.size(); ++j) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = nbttaglist.getCompound(j); + + convertCompound(LegacyType.ENTITY, nbttagcompound1, "Entity", sourceVer, targetVer); + } + } + + convertCompound(LegacyType.ENTITY, cmp, "SpawnData", sourceVer, targetVer); + } + + return cmp; + } + } + + private static class DataInspectorCommandBlock implements DataInspector { + ResourceLocation tileEntityCommand = getKey("TileEntityCommand"); + + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (tileEntityCommand.equals(new ResourceLocation(cmp.getString("id")))) { + cmp.putString("id", "Control"); + convert(LegacyType.BLOCK_ENTITY, cmp, sourceVer, targetVer); + cmp.putString("id", "MinecartCommandBlock"); + } + + return cmp; + } + } +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightFakePlayer.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightFakePlayer.java new file mode 100644 index 000000000..f05eebe0c --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightFakePlayer.java @@ -0,0 +1,98 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2; + +import com.mojang.authlib.GameProfile; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.stats.Stat; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.HumanoidArm; +import net.minecraft.world.entity.player.ChatVisiblity; +import net.minecraft.world.level.block.entity.SignBlockEntity; +import net.minecraft.world.phys.Vec3; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; + +import java.util.OptionalInt; +import java.util.UUID; + +class PaperweightFakePlayer extends ServerPlayer { + private static final GameProfile FAKE_WORLDEDIT_PROFILE = new GameProfile(UUID.nameUUIDFromBytes("worldedit".getBytes()), "[WorldEdit]"); + private static final Vec3 ORIGIN = new Vec3(0.0D, 0.0D, 0.0D); + private static final ClientInformation FAKE_CLIENT_INFO = new ClientInformation( + "en_US", 16, ChatVisiblity.FULL, true, 0, HumanoidArm.LEFT, false, false + ); + + PaperweightFakePlayer(ServerLevel world) { + super(world.getServer(), world, FAKE_WORLDEDIT_PROFILE, FAKE_CLIENT_INFO); + } + + @Override + public Vec3 position() { + return ORIGIN; + } + + @Override + public void tick() { + } + + @Override + public void die(DamageSource damagesource) { + } + + @Override + public Entity changeDimension(ServerLevel worldserver, TeleportCause cause) { + return this; + } + + @Override + public OptionalInt openMenu(MenuProvider factory) { + return OptionalInt.empty(); + } + + @Override + public void updateOptions(ClientInformation clientOptions) { + } + + @Override + public void displayClientMessage(Component message, boolean actionBar) { + } + + @Override + public void awardStat(Stat stat, int amount) { + } + + @Override + public void awardStat(Stat stat) { + } + + @Override + public boolean isInvulnerableTo(DamageSource damageSource) { + return true; + } + + @Override + public void openTextEdit(SignBlockEntity sign, boolean front) { + } +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightWorldNativeAccess.java new file mode 100644 index 000000000..ac5baa35f --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightWorldNativeAccess.java @@ -0,0 +1,181 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.nbt.CompoundBinaryTag; +import com.sk89q.worldedit.world.block.BlockState; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.chunk.LevelChunk; +import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData; +import org.bukkit.event.block.BlockPhysicsEvent; + +import java.lang.ref.WeakReference; +import java.util.Objects; +import javax.annotation.Nullable; + +public class PaperweightWorldNativeAccess implements WorldNativeAccess { + private static final int UPDATE = 1; + private static final int NOTIFY = 2; + + private final com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter adapter; + private final WeakReference world; + private SideEffectSet sideEffectSet; + + public PaperweightWorldNativeAccess(com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter adapter, WeakReference world) { + this.adapter = adapter; + this.world = world; + } + + private ServerLevel getWorld() { + return Objects.requireNonNull(world.get(), "The reference to the world was lost"); + } + + @Override + public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { + this.sideEffectSet = sideEffectSet; + } + + @Override + public LevelChunk getChunk(int x, int z) { + return getWorld().getChunk(x, z); + } + + @Override + public net.minecraft.world.level.block.state.BlockState toNative(BlockState state) { + int stateId = BlockStateIdAccess.getBlockStateId(state); + return BlockStateIdAccess.isValidInternalId(stateId) + ? Block.stateById(stateId) + : ((CraftBlockData) BukkitAdapter.adapt(state)).getState(); + } + + @Override + public net.minecraft.world.level.block.state.BlockState getBlockState(LevelChunk chunk, BlockPos position) { + return chunk.getBlockState(position); + } + + @Nullable + @Override + public net.minecraft.world.level.block.state.BlockState setBlockState(LevelChunk chunk, BlockPos position, net.minecraft.world.level.block.state.BlockState state) { + return chunk.setBlockState(position, state, false, this.sideEffectSet.shouldApply(SideEffect.UPDATE)); + } + + @Override + public net.minecraft.world.level.block.state.BlockState getValidBlockForPosition(net.minecraft.world.level.block.state.BlockState block, BlockPos position) { + return Block.updateFromNeighbourShapes(block, getWorld(), position); + } + + @Override + public BlockPos getPosition(int x, int y, int z) { + return new BlockPos(x, y, z); + } + + @Override + public void updateLightingForBlock(BlockPos position) { + getWorld().getChunkSource().getLightEngine().checkBlock(position); + } + + @Override + public boolean updateTileEntity(final BlockPos position, final CompoundBinaryTag tag) { + return false; + } + + @Override + public void notifyBlockUpdate(LevelChunk chunk, BlockPos position, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { + if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { + getWorld().sendBlockUpdated(position, oldState, newState, UPDATE | NOTIFY); + } + } + + @Override + public boolean isChunkTicking(LevelChunk chunk) { + return chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); + } + + @Override + public void markBlockChanged(LevelChunk chunk, BlockPos position) { + if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { + getWorld().getChunkSource().blockChanged(position); + } + } + + @Override + public void notifyNeighbors(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { + ServerLevel world = getWorld(); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + world.updateNeighborsAt(pos, oldState.getBlock()); + } else { + // When we don't want events, manually run the physics without them. + Block block = oldState.getBlock(); + fireNeighborChanged(pos, world, block, pos.west()); + fireNeighborChanged(pos, world, block, pos.east()); + fireNeighborChanged(pos, world, block, pos.below()); + fireNeighborChanged(pos, world, block, pos.above()); + fireNeighborChanged(pos, world, block, pos.north()); + fireNeighborChanged(pos, world, block, pos.south()); + } + if (newState.hasAnalogOutputSignal()) { + world.updateNeighbourForOutputSignal(pos, newState.getBlock()); + } + } + + // Not sure why neighborChanged is deprecated + @SuppressWarnings("deprecation") + private void fireNeighborChanged(BlockPos pos, ServerLevel world, Block block, BlockPos neighborPos) { + world.getBlockState(neighborPos).neighborChanged(world, neighborPos, block, pos, false); + } + + @Override + public void updateNeighbors(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState, int recursionLimit) { + ServerLevel world = getWorld(); + // a == updateNeighbors + // b == updateDiagonalNeighbors + oldState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + CraftWorld craftWorld = world.getWorld(); + BlockPhysicsEvent event = new BlockPhysicsEvent(craftWorld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), CraftBlockData.fromData(newState)); + world.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + } + newState.updateNeighbourShapes(world, pos, NOTIFY, recursionLimit); + newState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); + } + + @Override + public void onBlockStateChange(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { + getWorld().onBlockStateChange(pos, oldState, newState); + } + + @Override + public void flush() { + + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightBlockMaterial.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightBlockMaterial.java new file mode 100644 index 000000000..2e1dd8279 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightBlockMaterial.java @@ -0,0 +1,185 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; + +import com.google.common.base.Suppliers; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.util.ReflectionUtil; +import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag; +import com.sk89q.worldedit.world.registry.BlockMaterial; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.EmptyBlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.PushReaction; +import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData; + +public class PaperweightBlockMaterial implements BlockMaterial { + + private final Block block; + private final BlockState blockState; + private final boolean isTranslucent; + private final CraftBlockData craftBlockData; + private final org.bukkit.Material craftMaterial; + private final int opacity; + private final CompoundTag tile; + + public PaperweightBlockMaterial(Block block) { + this(block, block.defaultBlockState()); + } + + public PaperweightBlockMaterial(Block block, BlockState blockState) { + this.block = block; + this.blockState = blockState; + this.craftBlockData = CraftBlockData.fromData(blockState); + this.craftMaterial = craftBlockData.getMaterial(); + BlockBehaviour.Properties blockInfo = ReflectionUtil.getField(BlockBehaviour.class, block, + Refraction.pickName("properties", "aN")); + this.isTranslucent = !(boolean) ReflectionUtil.getField(BlockBehaviour.Properties.class, blockInfo, + Refraction.pickName("canOcclude", "m") + ); + opacity = blockState.getLightBlock(EmptyBlockGetter.INSTANCE, BlockPos.ZERO); + BlockEntity tileEntity = !(block instanceof EntityBlock) ? null : ((EntityBlock) block).newBlockEntity( + BlockPos.ZERO, + blockState + ); + tile = tileEntity == null + ? null + : new PaperweightLazyCompoundTag(Suppliers.memoize(tileEntity::saveWithId)); + } + + public Block getBlock() { + return block; + } + + public BlockState getState() { + return blockState; + } + + public CraftBlockData getCraftBlockData() { + return craftBlockData; + } + + @Override + public boolean isAir() { + return blockState.isAir(); + } + + @Override + public boolean isFullCube() { + return craftMaterial.isOccluding(); + } + + @Override + public boolean isOpaque() { + return blockState.isOpaque(); + } + + @Override + public boolean isPowerSource() { + return blockState.isSignalSource(); + } + + @Override + public boolean isLiquid() { + // TODO: Better check ? + return block instanceof LiquidBlock; + } + + @Override + public boolean isSolid() { + // TODO: Replace + return blockState.isSolid(); + } + + @Override + public float getHardness() { + return craftBlockData.getState().destroySpeed; + } + + @Override + public float getResistance() { + return block.getExplosionResistance(); + } + + @Override + public float getSlipperiness() { + return block.getFriction(); + } + + @Override + public int getLightValue() { + return blockState.getLightEmission(); + } + + @Override + public int getLightOpacity() { + return opacity; + } + + @Override + public boolean isFragileWhenPushed() { + return blockState.getPistonPushReaction() == PushReaction.DESTROY; + } + + @Override + public boolean isUnpushable() { + return blockState.getPistonPushReaction() == PushReaction.BLOCK; + } + + @Override + public boolean isTicksRandomly() { + return block.isRandomlyTicking(blockState); + } + + @Override + public boolean isMovementBlocker() { + return craftMaterial.isSolid(); + } + + @Override + public boolean isBurnable() { + return craftMaterial.isBurnable(); + } + + @Override + public boolean isToolRequired() { + // Removed in 1.16.1, this is not present in higher versions + return false; + } + + @Override + public boolean isReplacedDuringPlacement() { + return blockState.canBeReplaced(); + } + + @Override + public boolean isTranslucent() { + return isTranslucent; + } + + @Override + public boolean hasContainer() { + return block instanceof EntityBlock; + } + + @Override + public boolean isTile() { + return block instanceof EntityBlock; + } + + @Override + public CompoundTag getDefaultTile() { + return tile; + } + + @Override + public int getMapColor() { + // rgb field + return block.defaultMapColor().col; + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java new file mode 100644 index 000000000..a9e657b2e --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java @@ -0,0 +1,652 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; + +import com.fastasyncworldedit.bukkit.adapter.CachedBukkitAdapter; +import com.fastasyncworldedit.bukkit.adapter.IDelegateBukkitImplAdapter; +import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory; +import com.fastasyncworldedit.core.FaweCache; +import com.fastasyncworldedit.core.entity.LazyBaseEntity; +import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; +import com.fastasyncworldedit.core.queue.IBatchProcessor; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; +import com.fastasyncworldedit.core.util.NbtUtils; +import com.fastasyncworldedit.core.util.TaskManager; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regen.PaperweightRegen; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.registry.state.BooleanProperty; +import com.sk89q.worldedit.registry.state.DirectionalProperty; +import com.sk89q.worldedit.registry.state.EnumProperty; +import com.sk89q.worldedit.registry.state.IntegerProperty; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.TreeGenerator; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.nbt.BinaryTag; +import com.sk89q.worldedit.util.nbt.CompoundBinaryTag; +import com.sk89q.worldedit.util.nbt.StringBinaryTag; +import com.sk89q.worldedit.world.RegenOptions; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.item.ItemType; +import com.sk89q.worldedit.world.registry.BlockMaterial; +import io.papermc.lib.PaperLib; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.core.WritableRegistry; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.level.chunk.LevelChunk; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.TreeType; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.v1_20_R2.CraftServer; +import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlockState; +import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_20_R2.util.CraftNamespacedKey; +import org.bukkit.entity.Player; + +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.OptionalInt; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static net.minecraft.core.registries.Registries.BIOME; + +public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements + IDelegateBukkitImplAdapter { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + private static Method CHUNK_HOLDER_WAS_ACCESSIBLE_SINCE_LAST_SAVE; + + static { + try { + CHUNK_HOLDER_WAS_ACCESSIBLE_SINCE_LAST_SAVE = ChunkHolder.class.getDeclaredMethod("wasAccessibleSinceLastSave"); + } catch (NoSuchMethodException ignored) { // may not be present in newer paper versions + } + } + + private final PaperweightAdapter parent; + // ------------------------------------------------------------------------ + // Code that may break between versions of Minecraft + // ------------------------------------------------------------------------ + private final PaperweightMapChunkUtil mapUtil = new PaperweightMapChunkUtil(); + private char[] ibdToStateOrdinal = null; + private int[] ordinalToIbdID = null; + private boolean initialised = false; + private Map>> allBlockProperties = null; + + public PaperweightFaweAdapter() throws NoSuchFieldException, NoSuchMethodException { + this.parent = new PaperweightAdapter(); + } + + @Nullable + private static String getEntityId(Entity entity) { + ResourceLocation resourceLocation = net.minecraft.world.entity.EntityType.getKey(entity.getType()); + return resourceLocation == null ? null : resourceLocation.toString(); + } + + private static void readEntityIntoTag(Entity entity, net.minecraft.nbt.CompoundTag compoundTag) { + entity.save(compoundTag); + } + + @Override + public BukkitImplAdapter getParent() { + return parent; + } + + private synchronized boolean init() { + if (ibdToStateOrdinal != null && ibdToStateOrdinal[1] != 0) { + return false; + } + ibdToStateOrdinal = new char[BlockTypesCache.states.length]; // size + ordinalToIbdID = new int[ibdToStateOrdinal.length]; // size + for (int i = 0; i < ibdToStateOrdinal.length; i++) { + BlockState blockState = BlockTypesCache.states[i]; + PaperweightBlockMaterial material = (PaperweightBlockMaterial) blockState.getMaterial(); + int id = Block.BLOCK_STATE_REGISTRY.getId(material.getState()); + char ordinal = blockState.getOrdinalChar(); + ibdToStateOrdinal[id] = ordinal; + ordinalToIbdID[ordinal] = id; + } + Map>> properties = new HashMap<>(); + try { + for (Field field : BlockStateProperties.class.getDeclaredFields()) { + Object obj = field.get(null); + if (!(obj instanceof net.minecraft.world.level.block.state.properties.Property state)) { + continue; + } + Property property; + if (state instanceof net.minecraft.world.level.block.state.properties.BooleanProperty) { + property = new BooleanProperty( + state.getName(), + (List) ImmutableList.copyOf(state.getPossibleValues()) + ); + } else if (state instanceof DirectionProperty) { + property = new DirectionalProperty( + state.getName(), + state + .getPossibleValues() + .stream() + .map(e -> Direction.valueOf(((StringRepresentable) e).getSerializedName().toUpperCase())) + .collect(Collectors.toList()) + ); + } else if (state instanceof net.minecraft.world.level.block.state.properties.EnumProperty) { + property = new EnumProperty( + state.getName(), + state + .getPossibleValues() + .stream() + .map(e -> ((StringRepresentable) e).getSerializedName()) + .collect(Collectors.toList()) + ); + } else if (state instanceof net.minecraft.world.level.block.state.properties.IntegerProperty) { + property = new IntegerProperty( + state.getName(), + (List) ImmutableList.copyOf(state.getPossibleValues()) + ); + } else { + throw new IllegalArgumentException("FastAsyncWorldEdit needs an update to support " + state + .getClass() + .getSimpleName()); + } + properties.compute(property.getName().toLowerCase(Locale.ROOT), (k, v) -> { + if (v == null) { + v = new ArrayList<>(Collections.singletonList(property)); + } else { + v.add(property); + } + return v; + }); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } finally { + allBlockProperties = ImmutableMap.copyOf(properties); + } + initialised = true; + return true; + } + + @Override + public BlockMaterial getMaterial(BlockType blockType) { + Block block = getBlock(blockType); + return new PaperweightBlockMaterial(block); + } + + @Override + public synchronized BlockMaterial getMaterial(BlockState state) { + net.minecraft.world.level.block.state.BlockState blockState = ((CraftBlockData) Bukkit.createBlockData(state.getAsString())).getState(); + return new PaperweightBlockMaterial(blockState.getBlock(), blockState); + } + + public Block getBlock(BlockType blockType) { + return DedicatedServer.getServer().registryAccess().registryOrThrow(Registries.BLOCK) + .get(new ResourceLocation(blockType.getNamespace(), blockType.getResource())); + } + + @Deprecated + @Override + public BlockState getBlock(Location location) { + Preconditions.checkNotNull(location); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + final ServerLevel handle = craftWorld.getHandle(); + LevelChunk chunk = handle.getChunk(x >> 4, z >> 4); + final BlockPos blockPos = new BlockPos(x, y, z); + final net.minecraft.world.level.block.state.BlockState blockData = chunk.getBlockState(blockPos); + BlockState state = adapt(blockData); + if (state == null) { + org.bukkit.block.Block bukkitBlock = location.getBlock(); + state = BukkitAdapter.adapt(bukkitBlock.getBlockData()); + } + return state; + } + + @Override + public BaseBlock getFullBlock(final Location location) { + Preconditions.checkNotNull(location); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + final ServerLevel handle = craftWorld.getHandle(); + LevelChunk chunk = handle.getChunk(x >> 4, z >> 4); + final BlockPos blockPos = new BlockPos(x, y, z); + final net.minecraft.world.level.block.state.BlockState blockData = chunk.getBlockState(blockPos); + BlockState state = adapt(blockData); + if (state == null) { + org.bukkit.block.Block bukkitBlock = location.getBlock(); + state = BukkitAdapter.adapt(bukkitBlock.getBlockData()); + } + if (state.getBlockType().getMaterial().hasContainer()) { + + // Read the NBT data + BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK); + if (blockEntity != null) { + net.minecraft.nbt.CompoundTag tag = blockEntity.saveWithId(); + return state.toBaseBlock((CompoundBinaryTag) toNativeBinary(tag)); + } + } + + return state.toBaseBlock(); + } + + @Override + public Set getSupportedSideEffects() { + return SideEffectSet.defaults().getSideEffectsToApply(); + } + + @Override + public WorldNativeAccess createWorldNativeAccess(org.bukkit.World world) { + return new PaperweightFaweWorldNativeAccess( + this, + new WeakReference<>(((CraftWorld) world).getHandle()) + ); + } + + @Override + public BaseEntity getEntity(org.bukkit.entity.Entity entity) { + Preconditions.checkNotNull(entity); + + CraftEntity craftEntity = ((CraftEntity) entity); + Entity mcEntity = craftEntity.getHandle(); + + String id = getEntityId(mcEntity); + + if (id != null) { + EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); + Supplier saveTag = () -> { + final net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); + readEntityIntoTag(mcEntity, minecraftTag); + //add Id for AbstractChangeSet to work + final CompoundBinaryTag tag = (CompoundBinaryTag) toNativeBinary(minecraftTag); + final Map tags = NbtUtils.getCompoundBinaryTagValues(tag); + tags.put("Id", StringBinaryTag.of(id)); + return CompoundBinaryTag.from(tags); + }; + return new LazyBaseEntity(type, saveTag); + } else { + return null; + } + } + + @Override + public Component getRichBlockName(BlockType blockType) { + return parent.getRichBlockName(blockType); + } + + @Override + public Component getRichItemName(ItemType itemType) { + return parent.getRichItemName(itemType); + } + + @Override + public Component getRichItemName(BaseItemStack itemStack) { + return parent.getRichItemName(itemStack); + } + + @Override + public OptionalInt getInternalBlockStateId(BlockState state) { + PaperweightBlockMaterial material = (PaperweightBlockMaterial) state.getMaterial(); + net.minecraft.world.level.block.state.BlockState mcState = material.getCraftBlockData().getState(); + return OptionalInt.of(Block.BLOCK_STATE_REGISTRY.getId(mcState)); + } + + @Override + public BlockState adapt(BlockData blockData) { + CraftBlockData cbd = ((CraftBlockData) blockData); + net.minecraft.world.level.block.state.BlockState ibd = cbd.getState(); + return adapt(ibd); + } + + public BlockState adapt(net.minecraft.world.level.block.state.BlockState blockState) { + return BlockTypesCache.states[adaptToChar(blockState)]; + } + + public char adaptToChar(net.minecraft.world.level.block.state.BlockState blockState) { + int id = Block.BLOCK_STATE_REGISTRY.getId(blockState); + if (initialised) { + return ibdToStateOrdinal[id]; + } + synchronized (this) { + if (initialised) { + return ibdToStateOrdinal[id]; + } + try { + init(); + return ibdToStateOrdinal[id]; + } catch (ArrayIndexOutOfBoundsException e1) { + LOGGER.error("Attempted to convert {} with ID {} to char. ibdToStateOrdinal length: {}. Defaulting to air!", + blockState.getBlock(), Block.BLOCK_STATE_REGISTRY.getId(blockState), ibdToStateOrdinal.length, e1 + ); + return BlockTypesCache.ReservedIDs.AIR; + } + } + } + + public char ibdIDToOrdinal(int id) { + if (initialised) { + return ibdToStateOrdinal[id]; + } + synchronized (this) { + if (initialised) { + return ibdToStateOrdinal[id]; + } + init(); + return ibdToStateOrdinal[id]; + } + } + + @Override + public char[] getIbdToStateOrdinal() { + if (initialised) { + return ibdToStateOrdinal; + } + synchronized (this) { + if (initialised) { + return ibdToStateOrdinal; + } + init(); + return ibdToStateOrdinal; + } + } + + public int ordinalToIbdID(char ordinal) { + if (initialised) { + return ordinalToIbdID[ordinal]; + } + synchronized (this) { + if (initialised) { + return ordinalToIbdID[ordinal]; + } + init(); + return ordinalToIbdID[ordinal]; + } + } + + @Override + public int[] getOrdinalToIbdID() { + if (initialised) { + return ordinalToIbdID; + } + synchronized (this) { + if (initialised) { + return ordinalToIbdID; + } + init(); + return ordinalToIbdID; + } + } + + @Override + public > BlockData adapt(B state) { + PaperweightBlockMaterial material = (PaperweightBlockMaterial) state.getMaterial(); + return material.getCraftBlockData(); + } + + @Override + public void sendFakeChunk(org.bukkit.World world, Player player, ChunkPacket chunkPacket) { + ServerLevel nmsWorld = ((CraftWorld) world).getHandle(); + ChunkHolder map = PaperweightPlatformAdapter.getPlayerChunk(nmsWorld, chunkPacket.getChunkX(), chunkPacket.getChunkZ()); + if (map != null && wasAccessibleSinceLastSave(map)) { + boolean flag = false; + // PlayerChunk.d players = map.players; + Stream stream = /*players.a(new ChunkCoordIntPair(packet.getChunkX(), packet.getChunkZ()), flag) + */ Stream.empty(); + + ServerPlayer checkPlayer = player == null ? null : ((CraftPlayer) player).getHandle(); + stream.filter(entityPlayer -> checkPlayer == null || entityPlayer == checkPlayer) + .forEach(entityPlayer -> { + synchronized (chunkPacket) { + ClientboundLevelChunkWithLightPacket nmsPacket = (ClientboundLevelChunkWithLightPacket) chunkPacket.getNativePacket(); + if (nmsPacket == null) { + nmsPacket = mapUtil.create(this, chunkPacket); + chunkPacket.setNativePacket(nmsPacket); + } + try { + FaweCache.INSTANCE.CHUNK_FLAG.get().set(true); + entityPlayer.connection.send(nmsPacket); + } finally { + FaweCache.INSTANCE.CHUNK_FLAG.get().set(false); + } + } + }); + } + } + + @Override + public Map> getProperties(BlockType blockType) { + return getParent().getProperties(blockType); + } + + @Override + public boolean canPlaceAt(org.bukkit.World world, BlockVector3 blockVector3, BlockState blockState) { + int internalId = BlockStateIdAccess.getBlockStateId(blockState); + net.minecraft.world.level.block.state.BlockState blockState1 = Block.stateById(internalId); + return blockState1.hasPostProcess( + ((CraftWorld) world).getHandle(), + new BlockPos(blockVector3.getX(), blockVector3.getY(), blockVector3.getZ()) + ); + } + + @Override + public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { + ItemStack stack = new ItemStack( + DedicatedServer.getServer().registryAccess().registryOrThrow(Registries.ITEM) + .get(ResourceLocation.tryParse(baseItemStack.getType().getId())), + baseItemStack.getAmount() + ); + stack.setTag(((net.minecraft.nbt.CompoundTag) fromNative(baseItemStack.getNbtData()))); + return CraftItemStack.asCraftMirror(stack); + } + + @Override + public boolean generateTree( + TreeGenerator.TreeType treeType, EditSession editSession, BlockVector3 blockVector3, + org.bukkit.World bukkitWorld + ) { + TreeType bukkitType = BukkitWorld.toBukkitTreeType(treeType); + if (bukkitType == TreeType.CHORUS_PLANT) { + blockVector3 = blockVector3.add( + 0, + 1, + 0 + ); // bukkit skips the feature gen which does this offset normally, so we have to add it back + } + ServerLevel serverLevel = ((CraftWorld) bukkitWorld).getHandle(); + final BlockVector3 finalBlockVector = blockVector3; + // Sync to main thread to ensure no clashes occur + Map placed = TaskManager.taskManager().sync(() -> { + serverLevel.captureTreeGeneration = true; + serverLevel.captureBlockStates = true; + try { + if (!bukkitWorld.generateTree(BukkitAdapter.adapt(bukkitWorld, finalBlockVector), bukkitType)) { + return null; + } + return ImmutableMap.copyOf(serverLevel.capturedBlockStates); + } finally { + serverLevel.captureBlockStates = false; + serverLevel.captureTreeGeneration = false; + serverLevel.capturedBlockStates.clear(); + } + }); + if (placed == null || placed.isEmpty()) { + return false; + } + for (CraftBlockState craftBlockState : placed.values()) { + if (craftBlockState == null || craftBlockState.getType() == Material.AIR) { + continue; + } + editSession.setBlock(craftBlockState.getX(), craftBlockState.getY(), craftBlockState.getZ(), + BukkitAdapter.adapt(((org.bukkit.block.BlockState) craftBlockState).getBlockData()) + ); + } + return true; + } + + @Override + public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { + final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack); + final BaseItemStack weStack = new BaseItemStack(BukkitAdapter.asItemType(itemStack.getType()), itemStack.getAmount()); + weStack.setNbt(((CompoundBinaryTag) toNativeBinary(nmsStack.getTag()))); + return weStack; + } + + @Override + public Tag toNative(net.minecraft.nbt.Tag foreign) { + return parent.toNative(foreign); + } + + @Override + public net.minecraft.nbt.Tag fromNative(Tag foreign) { + if (foreign instanceof PaperweightLazyCompoundTag) { + return ((PaperweightLazyCompoundTag) foreign).get(); + } + return parent.fromNative(foreign); + } + + @Override + public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception { + return new PaperweightRegen(bukkitWorld, region, target, options).regenerate(); + } + + @Override + public IChunkGet get(org.bukkit.World world, int chunkX, int chunkZ) { + return new PaperweightGetBlocks(world, chunkX, chunkZ); + } + + @Override + public int getInternalBiomeId(BiomeType biomeType) { + final Registry registry = MinecraftServer + .getServer() + .registryAccess() + .registryOrThrow(BIOME); + ResourceLocation resourceLocation = ResourceLocation.tryParse(biomeType.getId()); + Biome biome = registry.get(resourceLocation); + return registry.getId(biome); + } + + @Override + public Iterable getRegisteredBiomes() { + WritableRegistry biomeRegistry = (WritableRegistry) ((CraftServer) Bukkit.getServer()) + .getServer() + .registryAccess() + .registryOrThrow(BIOME); + List keys = biomeRegistry.stream() + .map(biomeRegistry::getKey).filter(Objects::nonNull).toList(); + List namespacedKeys = new ArrayList<>(); + for (ResourceLocation key : keys) { + try { + namespacedKeys.add(CraftNamespacedKey.fromMinecraft(key)); + } catch (IllegalArgumentException e) { + LOGGER.error("Error converting biome key {}", key.toString(), e); + } + } + return namespacedKeys; + } + + @Override + public RelighterFactory getRelighterFactory() { + if (PaperLib.isPaper()) { + return new PaperweightStarlightRelighterFactory(); + } else { + return new NMSRelighterFactory(); + } + } + + @Override + public Map>> getAllProperties() { + if (initialised) { + return allBlockProperties; + } + synchronized (this) { + if (initialised) { + return allBlockProperties; + } + init(); + return allBlockProperties; + } + } + + @Override + public IBatchProcessor getTickingPostProcessor() { + return new PaperweightPostProcessor(); + } + + private boolean wasAccessibleSinceLastSave(ChunkHolder holder) { + if (!PaperLib.isPaper() || !PaperweightPlatformAdapter.POST_CHUNK_REWRITE) { + try { + return (boolean) CHUNK_HOLDER_WAS_ACCESSIBLE_SINCE_LAST_SAVE.invoke(holder); + } catch (IllegalAccessException | InvocationTargetException ignored) { + // fall-through + } + } + // Papers new chunk system has no related replacement - therefor we assume true. + return true; + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweWorldNativeAccess.java new file mode 100644 index 000000000..b452ec4cd --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweWorldNativeAccess.java @@ -0,0 +1,287 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; + +import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.util.TaskManager; +import com.fastasyncworldedit.core.util.task.RunnableVal; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.nbt.CompoundBinaryTag; +import com.sk89q.worldedit.world.block.BlockState; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData; +import org.bukkit.event.block.BlockPhysicsEvent; + +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess { + + private static final int UPDATE = 1; + private static final int NOTIFY = 2; + private static final Direction[] NEIGHBOUR_ORDER = { + Direction.EAST, + Direction.WEST, + Direction.DOWN, + Direction.UP, + Direction.NORTH, + Direction.SOUTH + }; + private final PaperweightFaweAdapter paperweightFaweAdapter; + private final WeakReference level; + private final AtomicInteger lastTick; + private final Set cachedChanges = new HashSet<>(); + private final Set cachedChunksToSend = new HashSet<>(); + private SideEffectSet sideEffectSet; + + public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAdapter, WeakReference level) { + this.paperweightFaweAdapter = paperweightFaweAdapter; + this.level = level; + // Use the actual tick as minecraft-defined so we don't try to force blocks into the world when the server's already lagging. + // - With the caveat that we don't want to have too many cached changed (1024) so we'd flush those at 1024 anyway. + this.lastTick = new AtomicInteger(MinecraftServer.currentTick); + } + + private Level getLevel() { + return Objects.requireNonNull(level.get(), "The reference to the world was lost"); + } + + @Override + public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { + this.sideEffectSet = sideEffectSet; + } + + @Override + public LevelChunk getChunk(int x, int z) { + return getLevel().getChunk(x, z); + } + + @Override + public net.minecraft.world.level.block.state.BlockState toNative(BlockState blockState) { + int stateId = paperweightFaweAdapter.ordinalToIbdID(blockState.getOrdinalChar()); + return BlockStateIdAccess.isValidInternalId(stateId) + ? Block.stateById(stateId) + : ((CraftBlockData) BukkitAdapter.adapt(blockState)).getState(); + } + + @Override + public net.minecraft.world.level.block.state.BlockState getBlockState(LevelChunk levelChunk, BlockPos blockPos) { + return levelChunk.getBlockState(blockPos); + } + + @Nullable + @Override + public synchronized net.minecraft.world.level.block.state.BlockState setBlockState( + LevelChunk levelChunk, BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState blockState + ) { + int currentTick = MinecraftServer.currentTick; + if (Fawe.isMainThread()) { + return levelChunk.setBlockState(blockPos, blockState, + this.sideEffectSet != null && this.sideEffectSet.shouldApply(SideEffect.UPDATE) + ); + } + // Since FAWE is.. Async we need to do it on the main thread (wooooo.. :( ) + cachedChanges.add(new CachedChange(levelChunk, blockPos, blockState)); + cachedChunksToSend.add(new IntPair(levelChunk.locX, levelChunk.locZ)); + boolean nextTick = lastTick.get() > currentTick; + if (nextTick || cachedChanges.size() >= 1024) { + if (nextTick) { + lastTick.set(currentTick); + } + flushAsync(nextTick); + } + return blockState; + } + + @Override + public net.minecraft.world.level.block.state.BlockState getValidBlockForPosition( + net.minecraft.world.level.block.state.BlockState blockState, + BlockPos blockPos + ) { + return Block.updateFromNeighbourShapes(blockState, getLevel(), blockPos); + } + + @Override + public BlockPos getPosition(int x, int y, int z) { + return new BlockPos(x, y, z); + } + + @Override + public void updateLightingForBlock(BlockPos blockPos) { + getLevel().getChunkSource().getLightEngine().checkBlock(blockPos); + } + + @Override + public boolean updateTileEntity(BlockPos blockPos, CompoundBinaryTag tag) { + // We will assume that the tile entity was created for us, + // though we do not do this on the other versions + BlockEntity blockEntity = getLevel().getBlockEntity(blockPos); + if (blockEntity == null) { + return false; + } + net.minecraft.nbt.Tag nativeTag = paperweightFaweAdapter.fromNativeBinary(tag); + blockEntity.load((CompoundTag) nativeTag); + return true; + } + + @Override + public void notifyBlockUpdate( + LevelChunk levelChunk, BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState oldState, + net.minecraft.world.level.block.state.BlockState newState + ) { + if (levelChunk.getSections()[level.get().getSectionIndex(blockPos.getY())] != null) { + getLevel().sendBlockUpdated(blockPos, oldState, newState, UPDATE | NOTIFY); + } + } + + @Override + public boolean isChunkTicking(LevelChunk levelChunk) { + return levelChunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); + } + + @Override + public void markBlockChanged(LevelChunk levelChunk, BlockPos blockPos) { + if (levelChunk.getSections()[level.get().getSectionIndex(blockPos.getY())] != null) { + ((ServerChunkCache) getLevel().getChunkSource()).blockChanged(blockPos); + } + } + + @Override + public void notifyNeighbors( + BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState oldState, + net.minecraft.world.level.block.state.BlockState newState + ) { + Level level = getLevel(); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + level.blockUpdated(blockPos, oldState.getBlock()); + } else { + // When we don't want events, manually run the physics without them. + // Un-nest neighbour updating + for (Direction direction : NEIGHBOUR_ORDER) { + BlockPos shifted = blockPos.relative(direction); + level.getBlockState(shifted).neighborChanged(level, shifted, oldState.getBlock(), blockPos, false); + } + } + if (newState.hasAnalogOutputSignal()) { + level.updateNeighbourForOutputSignal(blockPos, newState.getBlock()); + } + } + + @Override + public void updateNeighbors( + BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState oldState, + net.minecraft.world.level.block.state.BlockState newState, + int recursionLimit + ) { + Level level = getLevel(); + // a == updateNeighbors + // b == updateDiagonalNeighbors + oldState.updateIndirectNeighbourShapes(level, blockPos, NOTIFY, recursionLimit); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + CraftWorld craftWorld = level.getWorld(); + if (craftWorld != null) { + BlockPhysicsEvent event = new BlockPhysicsEvent( + craftWorld.getBlockAt(blockPos.getX(), blockPos.getY(), blockPos.getZ()), + CraftBlockData.fromData(newState) + ); + level.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + } + } + newState.triggerEvent(level, blockPos, NOTIFY, recursionLimit); + newState.updateIndirectNeighbourShapes(level, blockPos, NOTIFY, recursionLimit); + } + + @Override + public void onBlockStateChange( + BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState oldState, + net.minecraft.world.level.block.state.BlockState newState + ) { + getLevel().onBlockStateChange(blockPos, oldState, newState); + } + + private synchronized void flushAsync(final boolean sendChunks) { + final Set changes = Set.copyOf(cachedChanges); + cachedChanges.clear(); + final Set toSend; + if (sendChunks) { + toSend = Set.copyOf(cachedChunksToSend); + cachedChunksToSend.clear(); + } else { + toSend = Collections.emptySet(); + } + RunnableVal runnableVal = new RunnableVal<>() { + @Override + public void run(Object value) { + changes.forEach(cc -> cc.levelChunk.setBlockState(cc.blockPos, cc.blockState, + sideEffectSet != null && sideEffectSet.shouldApply(SideEffect.UPDATE) + )); + if (!sendChunks) { + return; + } + for (IntPair chunk : toSend) { + PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x(), chunk.z(), false); + } + } + }; + TaskManager.taskManager().async(() -> TaskManager.taskManager().sync(runnableVal)); + } + + @Override + public synchronized void flush() { + RunnableVal runnableVal = new RunnableVal<>() { + @Override + public void run(Object value) { + cachedChanges.forEach(cc -> cc.levelChunk.setBlockState(cc.blockPos, cc.blockState, + sideEffectSet != null && sideEffectSet.shouldApply(SideEffect.UPDATE) + )); + for (IntPair chunk : cachedChunksToSend) { + PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x(), chunk.z(), false); + } + } + }; + if (Fawe.isMainThread()) { + runnableVal.run(); + } else { + TaskManager.taskManager().sync(runnableVal); + } + cachedChanges.clear(); + cachedChunksToSend.clear(); + } + + private record CachedChange( + LevelChunk levelChunk, + BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState blockState + ) { + + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks.java new file mode 100644 index 000000000..d21510ff6 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks.java @@ -0,0 +1,1147 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; + +import com.fastasyncworldedit.bukkit.adapter.BukkitGetBlocks; +import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore; +import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.FaweCache; +import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; +import com.fastasyncworldedit.core.math.BitArrayUnstretched; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.queue.implementation.QueueHandler; +import com.fastasyncworldedit.core.queue.implementation.blocks.CharGetBlocks; +import com.fastasyncworldedit.core.util.MathMan; +import com.fastasyncworldedit.core.util.collection.AdaptedMap; +import com.google.common.base.Suppliers; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag; +import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.biome.BiomeTypes; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import io.papermc.lib.PaperLib; +import io.papermc.paper.event.block.BeaconDeactivatedEvent; +import net.minecraft.core.*; +import net.minecraft.nbt.IntTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.util.BitStorage; +import net.minecraft.util.ZeroBitStorage; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.entity.BeaconBlockEntity; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.*; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.lighting.LevelLightEngine; +import org.apache.logging.log4j.Logger; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlock; +import org.bukkit.event.entity.CreatureSpawnEvent; + +import javax.annotation.Nonnull; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static net.minecraft.core.registries.Registries.BIOME; + +public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBlocks { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + private static final Function posNms2We = v -> BlockVector3.at(v.getX(), v.getY(), v.getZ()); + private static final Function nmsTile2We = + tileEntity -> new PaperweightLazyCompoundTag(Suppliers.memoize(tileEntity::saveWithId)); + private final PaperweightFaweAdapter adapter = ((PaperweightFaweAdapter) WorldEditPlugin + .getInstance() + .getBukkitImplAdapter()); + private final ReadWriteLock sectionLock = new ReentrantReadWriteLock(); + private final ServerLevel serverLevel; + private final int chunkX; + private final int chunkZ; + private final int minHeight; + private final int maxHeight; + private final int minSectionPosition; + private final int maxSectionPosition; + private final Registry biomeRegistry; + private final IdMap> biomeHolderIdMap; + private LevelChunkSection[] sections; + private LevelChunk levelChunk; + private DataLayer[] blockLight; + private DataLayer[] skyLight; + private boolean createCopy = false; + private PaperweightGetBlocks_Copy copy = null; + private boolean forceLoadSections = true; + private boolean lightUpdate = false; + + public PaperweightGetBlocks(World world, int chunkX, int chunkZ) { + this(((CraftWorld) world).getHandle(), chunkX, chunkZ); + } + + public PaperweightGetBlocks(ServerLevel serverLevel, int chunkX, int chunkZ) { + super(serverLevel.getMinBuildHeight() >> 4, (serverLevel.getMaxBuildHeight() - 1) >> 4); + this.serverLevel = serverLevel; + this.chunkX = chunkX; + this.chunkZ = chunkZ; + this.minHeight = serverLevel.getMinBuildHeight(); + this.maxHeight = serverLevel.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive. + this.minSectionPosition = minHeight >> 4; + this.maxSectionPosition = maxHeight >> 4; + this.skyLight = new DataLayer[getSectionCount()]; + this.blockLight = new DataLayer[getSectionCount()]; + this.biomeRegistry = serverLevel.registryAccess().registryOrThrow(BIOME); + this.biomeHolderIdMap = biomeRegistry.asHolderIdMap(); + } + + public int getChunkX() { + return chunkX; + } + + public int getChunkZ() { + return chunkZ; + } + + @Override + public boolean isCreateCopy() { + return createCopy; + } + + @Override + public void setCreateCopy(boolean createCopy) { + this.createCopy = createCopy; + } + + @Override + public IChunkGet getCopy() { + return copy; + } + + @Override + public void setLightingToGet(char[][] light, int minSectionPosition, int maxSectionPosition) { + if (light != null) { + lightUpdate = true; + try { + fillLightNibble(light, LightLayer.BLOCK, minSectionPosition, maxSectionPosition); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + @Override + public void setSkyLightingToGet(char[][] light, int minSectionPosition, int maxSectionPosition) { + if (light != null) { + lightUpdate = true; + try { + fillLightNibble(light, LightLayer.SKY, minSectionPosition, maxSectionPosition); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + @Override + public void setHeightmapToGet(HeightMapType type, int[] data) { + // height + 1 to match server internal + BitArrayUnstretched bitArray = new BitArrayUnstretched(MathMan.log2nlz(getChunk().getHeight() + 1), 256); + bitArray.fromRaw(data); + Heightmap.Types nativeType = Heightmap.Types.valueOf(type.name()); + Heightmap heightMap = getChunk().heightmaps.get(nativeType); + heightMap.setRawData(getChunk(), nativeType, bitArray.getData()); + } + + @Override + public int getMaxY() { + return maxHeight; + } + + @Override + public int getMinY() { + return minHeight; + } + + @Override + public BiomeType getBiomeType(int x, int y, int z) { + LevelChunkSection section = getSections(false)[(y >> 4) - getMinSectionPosition()]; + Holder biomes = section.getNoiseBiome(x >> 2, (y & 15) >> 2, z >> 2); + return PaperweightPlatformAdapter.adapt(biomes, serverLevel); + } + + @Override + public void removeSectionLighting(int layer, boolean sky) { + SectionPos sectionPos = SectionPos.of(getChunk().getPos(), layer); + DataLayer dataLayer = serverLevel.getChunkSource().getLightEngine().getLayerListener(LightLayer.BLOCK).getDataLayerData( + sectionPos); + if (dataLayer != null) { + lightUpdate = true; + synchronized (dataLayer) { + byte[] bytes = dataLayer.getData(); + Arrays.fill(bytes, (byte) 0); + } + } + if (sky) { + SectionPos sectionPos1 = SectionPos.of(getChunk().getPos(), layer); + DataLayer dataLayer1 = serverLevel + .getChunkSource() + .getLightEngine() + .getLayerListener(LightLayer.SKY) + .getDataLayerData(sectionPos1); + if (dataLayer1 != null) { + lightUpdate = true; + synchronized (dataLayer1) { + byte[] bytes = dataLayer1.getData(); + Arrays.fill(bytes, (byte) 0); + } + } + } + } + + @Override + public CompoundTag getTile(int x, int y, int z) { + BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( + chunkX << 4), y, (z & 15) + ( + chunkZ << 4))); + if (blockEntity == null) { + return null; + } + return new PaperweightLazyCompoundTag(Suppliers.memoize(blockEntity::saveWithId)); + } + + @Override + public Map getTiles() { + Map nmsTiles = getChunk().getBlockEntities(); + if (nmsTiles.isEmpty()) { + return Collections.emptyMap(); + } + return AdaptedMap.immutable(nmsTiles, posNms2We, nmsTile2We); + } + + @Override + public int getSkyLight(int x, int y, int z) { + int layer = y >> 4; + int alayer = layer - getMinSectionPosition(); + if (skyLight[alayer] == null) { + SectionPos sectionPos = SectionPos.of(getChunk().getPos(), layer); + DataLayer dataLayer = + serverLevel.getChunkSource().getLightEngine().getLayerListener(LightLayer.SKY).getDataLayerData(sectionPos); + // If the server hasn't generated the section's NibbleArray yet, it will be null + if (dataLayer == null) { + byte[] LAYER_COUNT = new byte[2048]; + // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway. + Arrays.fill(LAYER_COUNT, (byte) 15); + dataLayer = new DataLayer(LAYER_COUNT); + ((LevelLightEngine) serverLevel.getChunkSource().getLightEngine()).queueSectionData( + LightLayer.BLOCK, + sectionPos, + dataLayer + ); + } + skyLight[alayer] = dataLayer; + } + return skyLight[alayer].get(x & 15, y & 15, z & 15); + } + + @Override + public int getEmittedLight(int x, int y, int z) { + int layer = y >> 4; + int alayer = layer - getMinSectionPosition(); + if (blockLight[alayer] == null) { + serverLevel.getRawBrightness(new BlockPos(1, 1, 1), 5); + SectionPos sectionPos = SectionPos.of(getChunk().getPos(), layer); + DataLayer dataLayer = serverLevel + .getChunkSource() + .getLightEngine() + .getLayerListener(LightLayer.BLOCK) + .getDataLayerData(sectionPos); + // If the server hasn't generated the section's DataLayer yet, it will be null + if (dataLayer == null) { + byte[] LAYER_COUNT = new byte[2048]; + // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway. + Arrays.fill(LAYER_COUNT, (byte) 15); + dataLayer = new DataLayer(LAYER_COUNT); + ((LevelLightEngine) serverLevel.getChunkSource().getLightEngine()).queueSectionData(LightLayer.BLOCK, sectionPos, + dataLayer + ); + } + blockLight[alayer] = dataLayer; + } + return blockLight[alayer].get(x & 15, y & 15, z & 15); + } + + @Override + public int[] getHeightMap(HeightMapType type) { + long[] longArray = getChunk().heightmaps.get(Heightmap.Types.valueOf(type.name())).getRawData(); + BitArrayUnstretched bitArray = new BitArrayUnstretched(9, 256, longArray); + return bitArray.toRaw(new int[256]); + } + + @Override + public CompoundTag getEntity(UUID uuid) { + Entity entity = serverLevel.getEntity(uuid); + if (entity != null) { + org.bukkit.entity.Entity bukkitEnt = entity.getBukkitEntity(); + return BukkitAdapter.adapt(bukkitEnt).getState().getNbtData(); + } + for (CompoundTag tag : getEntities()) { + if (uuid.equals(tag.getUUID())) { + return tag; + } + } + return null; + } + + @Override + public Set getEntities() { + ensureLoaded(serverLevel, chunkX, chunkZ); + List entities = PaperweightPlatformAdapter.getEntities(getChunk()); + if (entities.isEmpty()) { + return Collections.emptySet(); + } + int size = entities.size(); + return new AbstractSet<>() { + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean contains(Object get) { + if (!(get instanceof CompoundTag getTag)) { + return false; + } + UUID getUUID = getTag.getUUID(); + for (Entity entity : entities) { + UUID uuid = entity.getUUID(); + if (uuid.equals(getUUID)) { + return true; + } + } + return false; + } + + @Nonnull + @Override + public Iterator iterator() { + Iterable result = entities.stream().map(input -> { + net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); + input.save(tag); + return (CompoundTag) adapter.toNative(tag); + }).collect(Collectors.toList()); + return result.iterator(); + } + }; + } + + private void removeEntity(Entity entity) { + entity.discard(); + } + + public LevelChunk ensureLoaded(ServerLevel nmsWorld, int chunkX, int chunkZ) { + return PaperweightPlatformAdapter.ensureLoaded(nmsWorld, chunkX, chunkZ); + } + + @Override + @SuppressWarnings("rawtypes") + public synchronized > T call(IChunkSet set, Runnable finalizer) { + forceLoadSections = false; + copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null; + try { + ServerLevel nmsWorld = serverLevel; + LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ); + + // Remove existing tiles. Create a copy so that we can remove blocks + Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); + List beacons = null; + if (!chunkTiles.isEmpty()) { + for (Map.Entry entry : chunkTiles.entrySet()) { + final BlockPos pos = entry.getKey(); + final int lx = pos.getX() & 15; + final int ly = pos.getY(); + final int lz = pos.getZ() & 15; + final int layer = ly >> 4; + if (!set.hasSection(layer)) { + continue; + } + + int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); + if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { + BlockEntity tile = entry.getValue(); + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (beacons == null) { + beacons = new ArrayList<>(); + } + beacons.add(tile); + PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + continue; + } + nmsChunk.removeBlockEntity(tile.getBlockPos()); + if (createCopy) { + copy.storeTile(tile); + } + } + } + } + final BiomeType[][] biomes = set.getBiomes(); + + int bitMask = 0; + synchronized (nmsChunk) { + LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); + + for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { + + int getSectionIndex = layerNo - getMinSectionPosition(); + int setSectionIndex = layerNo - set.getMinSectionPosition(); + + if (!set.hasSection(layerNo)) { + // No blocks, but might be biomes present. Handle this lazily. + if (biomes == null) { + continue; + } + if (layerNo < set.getMinSectionPosition() || layerNo > set.getMaxSectionPosition()) { + continue; + } + if (biomes[setSectionIndex] != null) { + synchronized (super.sectionLocks[getSectionIndex]) { + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + if (createCopy && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); + } + + if (existingSection == null) { + PalettedContainer> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( + biomes[setSectionIndex], + biomeHolderIdMap + ); + LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + new char[4096], + adapter, + biomeRegistry, + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, new char[4096], getSectionIndex); + continue; + } else { + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { + LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, + getSectionIndex + ); + continue; + } + } + } else { + setBiomesToPalettedContainer(biomes, setSectionIndex, existingSection.getBiomes()); + } + } + } + continue; + } + + bitMask |= 1 << getSectionIndex; + + // 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. + synchronized (super.sectionLocks[getSectionIndex]) { + + LevelChunkSection newSection; + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + // Don't attempt to tick section whilst we're editing + if (existingSection != null) { + PaperweightPlatformAdapter.clearCounts(existingSection); + if (PaperLib.isPaper()) { + existingSection.tickingList.clear(); + } + } + + if (createCopy) { + char[] tmpLoad = loadPrivately(layerNo); + char[] copyArr = new char[4096]; + System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); + copy.storeSection(getSectionIndex, copyArr); + if (biomes != null && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); + } + } + + if (existingSection == null) { + PalettedContainer> biomeData = biomes == null ? new PalettedContainer<>( + biomeHolderIdMap, + biomeHolderIdMap.byIdOrThrow(WorldEditPlugin + .getInstance() + .getBukkitImplAdapter() + .getInternalBiomeId( + BiomeTypes.PLAINS)), + PalettedContainer.Strategy.SECTION_BIOMES + ) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap); + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + setArr, + adapter, + biomeRegistry, + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + continue; + } else { + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { + LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, + getSectionIndex + ); + continue; + } + } + } + + //ensure that the server doesn't try to tick the chunksection while we're editing it. (Again) + PaperweightPlatformAdapter.clearCounts(existingSection); + if (PaperLib.isPaper()) { + existingSection.tickingList.clear(); + } + DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); + + // Synchronize to prevent further acquisitions + synchronized (lock) { + lock.acquire(); // Wait until we have the lock + lock.release(); + try { + sectionLock.writeLock().lock(); + if (this.getChunk() != nmsChunk) { + this.levelChunk = nmsChunk; + this.sections = null; + this.reset(); + } else if (existingSection != getSections(false)[getSectionIndex]) { + this.sections[getSectionIndex] = existingSection; + this.reset(); + } else if (!Arrays.equals( + update(getSectionIndex, new char[4096], true), + loadPrivately(layerNo) + )) { + this.reset(layerNo); + /*} else if (lock.isModified()) { + this.reset(layerNo);*/ + } + } finally { + sectionLock.writeLock().unlock(); + } + + PalettedContainer> biomeData = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); + + newSection = + PaperweightPlatformAdapter.newChunkSection( + layerNo, + this::loadPrivately, + setArr, + adapter, + biomeRegistry, + biomeData + ); + if (!PaperweightPlatformAdapter.setSectionAtomic( + levelChunkSections, + existingSection, + newSection, + getSectionIndex + )) { + LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, + getSectionIndex + ); + } else { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + } + } + } + } + + Map heightMaps = set.getHeightMaps(); + for (Map.Entry entry : heightMaps.entrySet()) { + PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); + } + PaperweightGetBlocks.this.setLightingToGet( + set.getLight(), + set.getMinSectionPosition(), + set.getMaxSectionPosition() + ); + PaperweightGetBlocks.this.setSkyLightingToGet( + set.getSkyLight(), + set.getMinSectionPosition(), + set.getMaxSectionPosition() + ); + + Runnable[] syncTasks = null; + + int bx = chunkX << 4; + int bz = chunkZ << 4; + + // Call beacon deactivate events here synchronously + // list will be null on spigot, so this is an implicit isPaper check + if (beacons != null && !beacons.isEmpty()) { + final List finalBeacons = beacons; + + syncTasks = new Runnable[4]; + + syncTasks[3] = () -> { + for (BlockEntity beacon : finalBeacons) { + BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); + new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).callEvent(); + } + }; + } + + Set entityRemoves = set.getEntityRemoves(); + if (entityRemoves != null && !entityRemoves.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[3]; + } + + syncTasks[2] = () -> { + Set entitiesRemoved = new HashSet<>(); + final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + + for (Entity entity : entities) { + UUID uuid = entity.getUUID(); + if (entityRemoves.contains(uuid)) { + if (createCopy) { + copy.storeEntity(entity); + } + removeEntity(entity); + entitiesRemoved.add(uuid); + entityRemoves.remove(uuid); + } + } + if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { + for (UUID uuid : entityRemoves) { + Entity entity = nmsWorld.getEntities().get(uuid); + if (entity != null) { + removeEntity(entity); + } + } + } + // Only save entities that were actually removed to history + set.getEntityRemoves().clear(); + set.getEntityRemoves().addAll(entitiesRemoved); + }; + } + + Set entities = set.getEntities(); + if (entities != null && !entities.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[2]; + } + + syncTasks[1] = () -> { + Iterator iterator = entities.iterator(); + while (iterator.hasNext()) { + final CompoundTag nativeTag = iterator.next(); + final Map entityTagMap = nativeTag.getValue(); + final StringTag idTag = (StringTag) entityTagMap.get("Id"); + final ListTag posTag = (ListTag) entityTagMap.get("Pos"); + final ListTag rotTag = (ListTag) entityTagMap.get("Rotation"); + if (idTag == null || posTag == null || rotTag == null) { + LOGGER.error("Unknown entity tag: {}", nativeTag); + continue; + } + final double x = posTag.getDouble(0); + final double y = posTag.getDouble(1); + final double z = posTag.getDouble(2); + final float yaw = rotTag.getFloat(0); + final float pitch = rotTag.getFloat(1); + final String id = idTag.getValue(); + + EntityType type = EntityType.byString(id).orElse(null); + if (type != null) { + Entity entity = type.create(nmsWorld); + if (entity != null) { + final net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) adapter.fromNative( + nativeTag); + for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); + } + entity.load(tag); + entity.absMoveTo(x, y, z, yaw, pitch); + entity.setUUID(nativeTag.getUUID()); + if (!nmsWorld.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { + LOGGER.warn( + "Error creating entity of type `{}` in world `{}` at location `{},{},{}`", + id, + nmsWorld.getWorld().getName(), + x, + y, + z + ); + // Unsuccessful create should not be saved to history + iterator.remove(); + } + } + } + } + }; + } + + // set tiles + Map tiles = set.getTiles(); + if (tiles != null && !tiles.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[1]; + } + + syncTasks[0] = () -> { + for (final Map.Entry entry : tiles.entrySet()) { + final CompoundTag nativeTag = entry.getValue(); + final BlockVector3 blockHash = entry.getKey(); + final int x = blockHash.getX() + bx; + final int y = blockHash.getY(); + final int z = blockHash.getZ() + bz; + final BlockPos pos = new BlockPos(x, y, z); + + synchronized (nmsWorld) { + BlockEntity tileEntity = nmsWorld.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsWorld.removeBlockEntity(pos); + tileEntity = nmsWorld.getBlockEntity(pos); + } + if (tileEntity != null) { + final net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) adapter.fromNative( + nativeTag); + tag.put("x", IntTag.valueOf(x)); + tag.put("y", IntTag.valueOf(y)); + tag.put("z", IntTag.valueOf(z)); + tileEntity.load(tag); + } + } + } + }; + } + + Runnable callback; + if (bitMask == 0 && biomes == null && !lightUpdate) { + callback = null; + } else { + int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; + boolean finalLightUpdate = lightUpdate; + callback = () -> { + // Set Modified + nmsChunk.setLightCorrect(true); // Set Modified + nmsChunk.mustNotSave = false; + nmsChunk.setUnsaved(true); + // send to player + if (Settings.settings().LIGHTING.MODE == 0 || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING) { + this.send(finalMask, finalLightUpdate); + } + if (finalizer != null) { + finalizer.run(); + } + }; + } + if (syncTasks != null) { + QueueHandler queueHandler = Fawe.instance().getQueueHandler(); + Runnable[] finalSyncTasks = syncTasks; + + // Chain the sync tasks and the callback + Callable chain = () -> { + try { + // Run the sync tasks + for (Runnable task : finalSyncTasks) { + if (task != null) { + task.run(); + } + } + if (callback == null) { + if (finalizer != null) { + finalizer.run(); + } + return null; + } else { + return queueHandler.async(callback, null); + } + } catch (Throwable e) { + e.printStackTrace(); + throw e; + } + }; + //noinspection unchecked - required at compile time + return (T) (Future) queueHandler.sync(chain); + } else { + if (callback == null) { + if (finalizer != null) { + finalizer.run(); + } + } else { + callback.run(); + } + } + } + return null; + } catch (Throwable e) { + e.printStackTrace(); + return null; + } finally { + forceLoadSections = true; + } + } + + private void updateGet( + LevelChunk nmsChunk, + LevelChunkSection[] chunkSections, + LevelChunkSection section, + char[] arr, + int layer + ) { + try { + sectionLock.writeLock().lock(); + if (this.getChunk() != nmsChunk) { + this.levelChunk = nmsChunk; + this.sections = new LevelChunkSection[chunkSections.length]; + System.arraycopy(chunkSections, 0, this.sections, 0, chunkSections.length); + this.reset(); + } + if (this.sections == null) { + this.sections = new LevelChunkSection[chunkSections.length]; + System.arraycopy(chunkSections, 0, this.sections, 0, chunkSections.length); + } + if (this.sections[layer] != section) { + // Not sure why it's funky, but it's what I did in commit fda7d00747abe97d7891b80ed8bb88d97e1c70d1 and I don't want to touch it >dords + this.sections[layer] = new LevelChunkSection[]{section}.clone()[0]; + } + } finally { + sectionLock.writeLock().unlock(); + } + this.blocks[layer] = arr; + } + + private char[] loadPrivately(int layer) { + layer -= getMinSectionPosition(); + if (super.sections[layer] != null) { + synchronized (super.sectionLocks[layer]) { + if (super.sections[layer].isFull() && super.blocks[layer] != null) { + char[] blocks = new char[4096]; + System.arraycopy(super.blocks[layer], 0, blocks, 0, 4096); + return blocks; + } + } + } + return PaperweightGetBlocks.this.update(layer, null, true); + } + + @Override + public synchronized void send(int mask, boolean lighting) { + PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting); + } + + /** + * Update a given (nullable) data array to the current data stored in the server's chunk, associated with this + * {@link PaperweightPlatformAdapter} instance. Not synchronised to the {@link PaperweightPlatformAdapter} instance as synchronisation + * is handled where necessary in the method, and should otherwise be handled correctly by this method's caller. + * + * @param layer layer index (0 may denote a negative layer in the world, e.g. at y=-32) + * @param data array to be updated/filled with data or null + * @param aggressive if the cached section array should be re-acquired. + * @return the given array to be filled with data, or a new array if null is given. + */ + @Override + @SuppressWarnings("unchecked") + public char[] update(int layer, char[] data, boolean aggressive) { + LevelChunkSection section = getSections(aggressive)[layer]; + // Section is null, return empty array + if (section == null) { + data = new char[4096]; + Arrays.fill(data, (char) BlockTypesCache.ReservedIDs.AIR); + return data; + } + if (data != null && data.length != 4096) { + data = new char[4096]; + Arrays.fill(data, (char) BlockTypesCache.ReservedIDs.AIR); + } + if (data == null || data == FaweCache.INSTANCE.EMPTY_CHAR_4096) { + data = new char[4096]; + Arrays.fill(data, (char) BlockTypesCache.ReservedIDs.AIR); + } + Semaphore lock = PaperweightPlatformAdapter.applyLock(section); + synchronized (lock) { + // Efficiently convert ChunkSection to raw data + try { + lock.acquire(); + + final PalettedContainer blocks = section.getStates(); + final Object dataObject = PaperweightPlatformAdapter.fieldData.get(blocks); + final BitStorage bits = (BitStorage) PaperweightPlatformAdapter.fieldStorage.get(dataObject); + + if (bits instanceof ZeroBitStorage) { + Arrays.fill(data, adapter.adaptToChar(blocks.get(0, 0, 0))); // get(int) is only public on paper + return data; + } + + final Palette palette = (Palette) PaperweightPlatformAdapter.fieldPalette.get(dataObject); + + final int bitsPerEntry = bits.getBits(); + final long[] blockStates = bits.getRaw(); + + new BitArrayUnstretched(bitsPerEntry, 4096, blockStates).toRaw(data); + + int num_palette; + if (palette instanceof LinearPalette || palette instanceof HashMapPalette) { + num_palette = palette.getSize(); + } else { + // The section's palette is the global block palette. + for (int i = 0; i < 4096; i++) { + char paletteVal = data[i]; + char ordinal = adapter.ibdIDToOrdinal(paletteVal); + data[i] = ordinal; + } + return data; + } + + char[] paletteToOrdinal = FaweCache.INSTANCE.PALETTE_TO_BLOCK_CHAR.get(); + try { + if (num_palette != 1) { + for (int i = 0; i < num_palette; i++) { + char ordinal = ordinal(palette.valueFor(i), adapter); + paletteToOrdinal[i] = ordinal; + } + for (int i = 0; i < 4096; i++) { + char paletteVal = data[i]; + char val = paletteToOrdinal[paletteVal]; + if (val == Character.MAX_VALUE) { + val = ordinal(palette.valueFor(i), adapter); + paletteToOrdinal[i] = val; + } + data[i] = val; + } + } else { + char ordinal = ordinal(palette.valueFor(0), adapter); + Arrays.fill(data, ordinal); + } + } finally { + for (int i = 0; i < num_palette; i++) { + paletteToOrdinal[i] = Character.MAX_VALUE; + } + } + return data; + } catch (IllegalAccessException | InterruptedException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } finally { + lock.release(); + } + } + } + + private char ordinal(BlockState ibd, PaperweightFaweAdapter adapter) { + if (ibd == null) { + return BlockTypesCache.ReservedIDs.AIR; + } else { + return adapter.adaptToChar(ibd); + } + } + + public LevelChunkSection[] getSections(boolean force) { + force &= forceLoadSections; + sectionLock.readLock().lock(); + LevelChunkSection[] tmp = sections; + sectionLock.readLock().unlock(); + if (tmp == null || force) { + try { + sectionLock.writeLock().lock(); + tmp = sections; + if (tmp == null || force) { + LevelChunkSection[] chunkSections = getChunk().getSections(); + tmp = new LevelChunkSection[chunkSections.length]; + System.arraycopy(chunkSections, 0, tmp, 0, chunkSections.length); + sections = tmp; + } + } finally { + sectionLock.writeLock().unlock(); + } + } + return tmp; + } + + public LevelChunk getChunk() { + LevelChunk levelChunk = this.levelChunk; + if (levelChunk == null) { + synchronized (this) { + levelChunk = this.levelChunk; + if (levelChunk == null) { + this.levelChunk = levelChunk = ensureLoaded(this.serverLevel, chunkX, chunkZ); + } + } + } + return levelChunk; + } + + private void fillLightNibble(char[][] light, LightLayer lightLayer, int minSectionPosition, int maxSectionPosition) { + for (int Y = 0; Y <= maxSectionPosition - minSectionPosition; Y++) { + if (light[Y] == null) { + continue; + } + SectionPos sectionPos = SectionPos.of(levelChunk.getPos(), Y + minSectionPosition); + DataLayer dataLayer = serverLevel.getChunkSource().getLightEngine().getLayerListener(lightLayer).getDataLayerData( + sectionPos); + if (dataLayer == null) { + byte[] LAYER_COUNT = new byte[2048]; + Arrays.fill(LAYER_COUNT, lightLayer == LightLayer.SKY ? (byte) 15 : (byte) 0); + dataLayer = new DataLayer(LAYER_COUNT); + ((LevelLightEngine) serverLevel.getChunkSource().getLightEngine()).queueSectionData( + lightLayer, + sectionPos, + dataLayer + ); + } + synchronized (dataLayer) { + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + int i = y << 8 | z << 4 | x; + if (light[Y][i] < 16) { + dataLayer.set(x, y, z, light[Y][i]); + } + } + } + } + } + } + } + + private PalettedContainer> setBiomesToPalettedContainer( + final BiomeType[][] biomes, + final int sectionIndex, + final PalettedContainerRO> data + ) { + PalettedContainer> biomeData; + if (data instanceof PalettedContainer> palettedContainer) { + biomeData = palettedContainer; + } else { + LOGGER.warn( + "Cannot correctly set biomes to world, existing biomes may be lost. Expected class " + + "type {} but got {}", + PalettedContainer.class.getSimpleName(), + data.getClass().getSimpleName() + ); + biomeData = data.recreate(); + } + BiomeType[] sectionBiomes; + if (biomes == null || (sectionBiomes = biomes[sectionIndex]) == null) { + return biomeData; + } + for (int y = 0, index = 0; y < 4; y++) { + for (int z = 0; z < 4; z++) { + for (int x = 0; x < 4; x++, index++) { + BiomeType biomeType = sectionBiomes[index]; + if (biomeType == null) { + continue; + } + biomeData.set( + x, + y, + z, + biomeHolderIdMap.byIdOrThrow(WorldEditPlugin + .getInstance() + .getBukkitImplAdapter() + .getInternalBiomeId(biomeType)) + ); + } + } + } + return biomeData; + } + + @Override + public boolean hasSection(int layer) { + layer -= getMinSectionPosition(); + return getSections(false)[layer] != null; + } + + @Override + @SuppressWarnings("unchecked") + public synchronized boolean trim(boolean aggressive) { + skyLight = new DataLayer[getSectionCount()]; + blockLight = new DataLayer[getSectionCount()]; + if (aggressive) { + sectionLock.writeLock().lock(); + sections = null; + levelChunk = null; + sectionLock.writeLock().unlock(); + return super.trim(true); + } else if (sections == null) { + // don't bother trimming if there are no sections stored. + return true; + } else { + for (int i = getMinSectionPosition(); i <= getMaxSectionPosition(); i++) { + int layer = i - getMinSectionPosition(); + if (!hasSection(i) || !super.sections[layer].isFull()) { + continue; + } + LevelChunkSection existing = getSections(true)[layer]; + try { + final PalettedContainer blocksExisting = existing.getStates(); + + final Object dataObject = PaperweightPlatformAdapter.fieldData.get(blocksExisting); + final Palette palette = (Palette) PaperweightPlatformAdapter.fieldPalette.get( + dataObject); + int paletteSize; + + if (palette instanceof LinearPalette || palette instanceof HashMapPalette) { + paletteSize = palette.getSize(); + } else { + super.trim(false, i); + continue; + } + if (paletteSize == 1) { + //If the cached palette size is 1 then no blocks can have been changed i.e. do not need to update these chunks. + continue; + } + super.trim(false, i); + } catch (IllegalAccessException ignored) { + super.trim(false, i); + } + } + return true; + } + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks_Copy.java new file mode 100644 index 000000000..428161a48 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks_Copy.java @@ -0,0 +1,248 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; + +import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; +import com.fastasyncworldedit.core.queue.IBlocks; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.queue.IChunkSet; +import com.google.common.base.Suppliers; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import net.minecraft.core.Holder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.PalettedContainerRO; +import org.apache.logging.log4j.Logger; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Future; + +public class PaperweightGetBlocks_Copy implements IChunkGet { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + private final Map tiles = new HashMap<>(); + private final Set entities = new HashSet<>(); + private final char[][] blocks; + private final int minHeight; + private final int maxHeight; + final ServerLevel serverLevel; + final LevelChunk levelChunk; + private PalettedContainer>[] biomes = null; + + protected PaperweightGetBlocks_Copy(LevelChunk levelChunk) { + this.levelChunk = levelChunk; + this.serverLevel = levelChunk.level; + this.minHeight = serverLevel.getMinBuildHeight(); + this.maxHeight = serverLevel.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive. + this.blocks = new char[getSectionCount()][]; + } + + protected void storeTile(BlockEntity blockEntity) { + tiles.put( + BlockVector3.at( + blockEntity.getBlockPos().getX(), + blockEntity.getBlockPos().getY(), + blockEntity.getBlockPos().getZ() + ), + new PaperweightLazyCompoundTag(Suppliers.memoize(blockEntity::saveWithId)) + ); + } + + @Override + public Map getTiles() { + return tiles; + } + + @Override + @Nullable + public CompoundTag getTile(int x, int y, int z) { + return tiles.get(BlockVector3.at(x, y, z)); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + protected void storeEntity(Entity entity) { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + net.minecraft.nbt.CompoundTag compoundTag = new net.minecraft.nbt.CompoundTag(); + entity.save(compoundTag); + entities.add((CompoundTag) adapter.toNative(compoundTag)); + } + + @Override + public Set getEntities() { + return this.entities; + } + + @Override + public CompoundTag getEntity(UUID uuid) { + for (CompoundTag tag : entities) { + if (uuid.equals(tag.getUUID())) { + return tag; + } + } + return null; + } + + @Override + public boolean isCreateCopy() { + return false; + } + + @Override + public void setCreateCopy(boolean createCopy) { + } + + @Override + public void setLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) { + } + + @Override + public void setSkyLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) { + } + + @Override + public void setHeightmapToGet(HeightMapType type, int[] data) { + } + + @Override + public int getMaxY() { + return maxHeight; + } + + @Override + public int getMinY() { + return minHeight; + } + + @Override + public int getMaxSectionPosition() { + return maxHeight >> 4; + } + + @Override + public int getMinSectionPosition() { + return minHeight >> 4; + } + + @Override + public BiomeType getBiomeType(int x, int y, int z) { + Holder biome = biomes[(y >> 4) - getMinSectionPosition()].get(x >> 2, (y & 15) >> 2, z >> 2); + return PaperweightPlatformAdapter.adapt(biome, serverLevel); + } + + @Override + public void removeSectionLighting(int layer, boolean sky) { + } + + @Override + public boolean trim(boolean aggressive, int layer) { + return false; + } + + @Override + public IBlocks reset() { + return null; + } + + @Override + public int getSectionCount() { + return serverLevel.getSectionsCount(); + } + + protected void storeSection(int layer, char[] data) { + blocks[layer] = data; + } + + protected void storeBiomes(int layer, PalettedContainerRO> biomeData) { + if (biomes == null) { + biomes = new PalettedContainer[getSectionCount()]; + } + if (biomeData instanceof PalettedContainer> palettedContainer) { + biomes[layer] = palettedContainer.copy(); + } else { + LOGGER.error( + "Cannot correctly save biomes to history. Expected class type {} but got {}", + PalettedContainer.class.getSimpleName(), + biomeData.getClass().getSimpleName() + ); + } + } + + @Override + public BaseBlock getFullBlock(int x, int y, int z) { + BlockState state = BlockTypesCache.states[get(x, y, z)]; + return state.toBaseBlock(this, x, y, z); + } + + @Override + public boolean hasSection(int layer) { + layer -= getMinSectionPosition(); + return blocks[layer] != null; + } + + @Override + public char[] load(int layer) { + layer -= getMinSectionPosition(); + return blocks[layer]; + } + + @Override + public char[] loadIfPresent(int layer) { + layer -= getMinSectionPosition(); + return blocks[layer]; + } + + @Override + public BlockState getBlock(int x, int y, int z) { + return BlockTypesCache.states[get(x, y, z)]; + } + + @Override + public int getSkyLight(int x, int y, int z) { + return 0; + } + + @Override + public int getEmittedLight(int x, int y, int z) { + return 0; + } + + @Override + public int[] getHeightMap(HeightMapType type) { + return new int[0]; + } + + @Override + public > T call(IChunkSet set, Runnable finalize) { + return null; + } + + public char get(int x, int y, int z) { + final int layer = (y >> 4) - getMinSectionPosition(); + final int index = (y & 15) << 8 | z << 4 | x; + return blocks[layer][index]; + } + + + @Override + public boolean trim(boolean aggressive) { + return false; + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightMapChunkUtil.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightMapChunkUtil.java new file mode 100644 index 000000000..e6501086e --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightMapChunkUtil.java @@ -0,0 +1,34 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; + +import com.fastasyncworldedit.bukkit.adapter.MapChunkUtil; +import com.sk89q.worldedit.bukkit.adapter.Refraction; +import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; + +//TODO un-very-break-this +public class PaperweightMapChunkUtil extends MapChunkUtil { + + public PaperweightMapChunkUtil() throws NoSuchFieldException { + fieldX = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("TWO_MEGABYTES", "a")); + fieldZ = ClientboundLevelChunkWithLightPacket.class.getDeclaredField(Refraction.pickName("x", "a")); + fieldBitMask = ClientboundLevelChunkWithLightPacket.class.getDeclaredField(Refraction.pickName("z", "b")); + fieldHeightMap = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("heightmaps", "b")); + fieldChunkData = ClientboundLevelChunkWithLightPacket.class.getDeclaredField(Refraction.pickName("chunkData", "c")); + fieldBlockEntities = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("buffer", "c")); + fieldFull = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("blockEntitiesData", "d")); + fieldX.setAccessible(true); + fieldZ.setAccessible(true); + fieldBitMask.setAccessible(true); + fieldHeightMap.setAccessible(true); + fieldChunkData.setAccessible(true); + fieldBlockEntities.setAccessible(true); + fieldFull.setAccessible(true); + } + + @Override + public ClientboundLevelChunkWithLightPacket createPacket() { + // TODO ??? return new ClientboundLevelChunkPacket(); + throw new UnsupportedOperationException(); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPlatformAdapter.java new file mode 100644 index 000000000..9ab6b0602 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPlatformAdapter.java @@ -0,0 +1,719 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; + +import com.destroystokyo.paper.util.maplist.EntityList; +import com.fastasyncworldedit.bukkit.adapter.CachedBukkitAdapter; +import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore; +import com.fastasyncworldedit.bukkit.adapter.NMSAdapter; +import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.FaweCache; +import com.fastasyncworldedit.core.math.BitArrayUnstretched; +import com.fastasyncworldedit.core.util.MathMan; +import com.fastasyncworldedit.core.util.ReflectionUtils; +import com.fastasyncworldedit.core.util.TaskManager; +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 io.papermc.paper.world.ChunkEntitySlices; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.IdMap; +import net.minecraft.core.Registry; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.TicketType; +import net.minecraft.util.BitStorage; +import net.minecraft.util.ExceptionCollector; +import net.minecraft.util.SimpleBitStorage; +import net.minecraft.util.ThreadingDetector; +import net.minecraft.util.Unit; +import net.minecraft.util.ZeroBitStorage; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; +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.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; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +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.entity.PersistentEntitySectionManager; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R2.CraftChunk; +import sun.misc.Unsafe; + +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; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +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 { + + public static final Field fieldData; + + public static final Constructor dataConstructor; + + public static final Field fieldStorage; + public static final Field fieldPalette; + + private static final Field fieldTickingFluidCount; + private static final Field fieldTickingBlockCount; + private static final Field fieldNonEmptyBlockCount; + + private static final MethodHandle methodGetVisibleChunk; + + private static final Field fieldThreadingDetector; + private static final Field fieldLock; + + private static final MethodHandle methodRemoveGameEventListener; + private static final MethodHandle methodremoveTickingBlockEntity; + + /* + * This is a workaround for the changes from https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/commits/1fddefce1cdce44010927b888432bf70c0e88cde#src/main/java/org/bukkit/craftbukkit/CraftChunk.java + * and is only needed to support 1.19.4 versions before *and* after this change. + */ + private static final MethodHandle CRAFT_CHUNK_GET_HANDLE; + + 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; + private static Field SERVER_LEVEL_ENTITY_MANAGER; + + static { + final MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + fieldData = PalettedContainer.class.getDeclaredField(Refraction.pickName("data", "d")); + fieldData.setAccessible(true); + + Class dataClazz = fieldData.getType(); + dataConstructor = dataClazz.getDeclaredConstructors()[0]; + dataConstructor.setAccessible(true); + + fieldStorage = dataClazz.getDeclaredField(Refraction.pickName("storage", "b")); + fieldStorage.setAccessible(true); + fieldPalette = dataClazz.getDeclaredField(Refraction.pickName("palette", "c")); + fieldPalette.setAccessible(true); + + fieldTickingFluidCount = LevelChunkSection.class.getDeclaredField(Refraction.pickName("tickingFluidCount", "g")); + fieldTickingFluidCount.setAccessible(true); + fieldTickingBlockCount = LevelChunkSection.class.getDeclaredField(Refraction.pickName("tickingBlockCount", "f")); + fieldTickingBlockCount.setAccessible(true); + fieldNonEmptyBlockCount = LevelChunkSection.class.getDeclaredField(Refraction.pickName("nonEmptyBlockCount", "e")); + fieldNonEmptyBlockCount.setAccessible(true); + + Method getVisibleChunkIfPresent = ChunkMap.class.getDeclaredMethod(Refraction.pickName( + "getVisibleChunkIfPresent", + "b" + ), long.class); + getVisibleChunkIfPresent.setAccessible(true); + methodGetVisibleChunk = lookup.unreflect(getVisibleChunkIfPresent); + + if (!PaperLib.isPaper()) { + fieldThreadingDetector = PalettedContainer.class.getDeclaredField(Refraction.pickName("threadingDetector", "f")); + fieldThreadingDetector.setAccessible(true); + fieldLock = ThreadingDetector.class.getDeclaredField(Refraction.pickName("lock", "c")); + fieldLock.setAccessible(true); + } else { + // in paper, the used methods are synchronized properly + fieldThreadingDetector = null; + fieldLock = null; + } + + Method removeGameEventListener = LevelChunk.class.getDeclaredMethod( + Refraction.pickName("removeGameEventListener", "a"), + BlockEntity.class, + ServerLevel.class + ); + removeGameEventListener.setAccessible(true); + methodRemoveGameEventListener = lookup.unreflect(removeGameEventListener); + + Method removeBlockEntityTicker = LevelChunk.class.getDeclaredMethod( + Refraction.pickName( + "removeBlockEntityTicker", + "l" + ), BlockPos.class + ); + removeBlockEntityTicker.setAccessible(true); + methodremoveTickingBlockEntity = lookup.unreflect(removeBlockEntityTicker); + + fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "q")); + fieldRemove.setAccessible(true); + + boolean chunkRewrite; + try { + ServerLevel.class.getDeclaredMethod("getEntityLookup"); + chunkRewrite = true; + PAPER_CHUNK_GEN_ALL_ENTITIES = ChunkEntitySlices.class.getDeclaredMethod("getAllEntities"); + PAPER_CHUNK_GEN_ALL_ENTITIES.setAccessible(true); + } catch (NoSuchMethodException ignored) { + chunkRewrite = false; + } + try { + // Paper - Pre-Chunk-Update + LEVEL_CHUNK_ENTITIES = LevelChunk.class.getDeclaredField("entities"); + LEVEL_CHUNK_ENTITIES.setAccessible(true); + } catch (NoSuchFieldException ignored) { + } + try { + // Non-Paper + SERVER_LEVEL_ENTITY_MANAGER = ServerLevel.class.getDeclaredField(Refraction.pickName("entityManager", "M")); + SERVER_LEVEL_ENTITY_MANAGER.setAccessible(true); + } catch (NoSuchFieldException ignored) { + } + POST_CHUNK_REWRITE = chunkRewrite; + } catch (RuntimeException | Error e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + MethodHandle craftChunkGetHandle; + final MethodType type = methodType(LevelChunk.class); + try { + craftChunkGetHandle = lookup.findVirtual(CraftChunk.class, "getHandle", type); + } catch (NoSuchMethodException | IllegalAccessException e) { + try { + final MethodType newType = methodType(ChunkAccess.class, ChunkStatus.class); + craftChunkGetHandle = lookup.findVirtual(CraftChunk.class, "getHandle", newType); + craftChunkGetHandle = MethodHandles.insertArguments(craftChunkGetHandle, 1, ChunkStatus.FULL); + } catch (NoSuchMethodException | IllegalAccessException ex) { + throw new RuntimeException(ex); + } + } + CRAFT_CHUNK_GET_HANDLE = craftChunkGetHandle; + } + + static boolean setSectionAtomic( + LevelChunkSection[] sections, + LevelChunkSection expected, + LevelChunkSection value, + int layer + ) { + if (layer >= 0 && layer < sections.length) { + return ReflectionUtils.compareAndSet(sections, expected, value, layer); + } + return false; + } + + // There is no point in having a functional semaphore for paper servers. + private static final ThreadLocal SEMAPHORE_THREAD_LOCAL = + ThreadLocal.withInitial(() -> new DelegateSemaphore(1, null)); + + static DelegateSemaphore applyLock(LevelChunkSection section) { + if (PaperLib.isPaper()) { + return SEMAPHORE_THREAD_LOCAL.get(); + } + try { + synchronized (section) { + PalettedContainer blocks = section.getStates(); + ThreadingDetector currentThreadingDetector = (ThreadingDetector) fieldThreadingDetector.get(blocks); + synchronized (currentThreadingDetector) { + Semaphore currentLock = (Semaphore) fieldLock.get(currentThreadingDetector); + if (currentLock instanceof DelegateSemaphore delegateSemaphore) { + return delegateSemaphore; + } + DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock); + fieldLock.set(currentThreadingDetector, newLock); + return newLock; + } + } + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + if (!PaperLib.isPaper()) { + LevelChunk nmsChunk = serverLevel.getChunkSource().getChunk(chunkX, chunkZ, false); + if (nmsChunk != null) { + return nmsChunk; + } + if (Fawe.isMainThread()) { + return serverLevel.getChunk(chunkX, chunkZ); + } + } else { + LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); + if (nmsChunk != null) { + addTicket(serverLevel, chunkX, chunkZ); + return nmsChunk; + } + nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); + if (nmsChunk != null) { + addTicket(serverLevel, chunkX, chunkZ); + return nmsChunk; + } + // Avoid "async" methods from the main thread. + if (Fawe.isMainThread()) { + return serverLevel.getChunk(chunkX, chunkZ); + } + CompletableFuture future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true); + try { + 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) { + e.printStackTrace(); + } + } + return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); + } + + private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { + // Ensure chunk is definitely loaded before applying a ticket + io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel + .getChunkSource() + .addRegionTicket(TicketType.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE)); + } + + public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { + ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap; + try { + return (ChunkHolder) methodGetVisibleChunk.invoke(chunkMap, ChunkPos.asLong(chunkX, chunkZ)); + } catch (Throwable thr) { + throw new RuntimeException(thr); + } + } + + @SuppressWarnings("deprecation") + public static void sendChunk(ServerLevel nmsWorld, int chunkX, int chunkZ, boolean lighting) { + ChunkHolder chunkHolder = getPlayerChunk(nmsWorld, chunkX, chunkZ); + if (chunkHolder == null) { + return; + } + ChunkPos coordIntPair = new ChunkPos(chunkX, chunkZ); + LevelChunk levelChunk; + if (PaperLib.isPaper()) { + // getChunkAtIfLoadedImmediately is paper only + levelChunk = nmsWorld + .getChunkSource() + .getChunkAtIfLoadedImmediately(chunkX, chunkZ); + } else { + levelChunk = ((Optional) ((Either) chunkHolder + .getTickingChunkFuture() // method is not present with new paper chunk system + .getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left()) + .orElse(null); + } + if (levelChunk == null) { + return; + } + TaskManager.taskManager().task(() -> { + ClientboundLevelChunkWithLightPacket packet; + if (PaperLib.isPaper()) { + packet = new ClientboundLevelChunkWithLightPacket( + levelChunk, + nmsWorld.getChunkSource().getLightEngine(), + null, + null + // last false is to not bother with x-ray + ); + } else { + // deprecated on paper - deprecation suppressed + packet = new ClientboundLevelChunkWithLightPacket( + levelChunk, + nmsWorld.getChunkSource().getLightEngine(), + null, + null + ); + } + nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet)); + }); + } + + private static List nearbyPlayers(ServerLevel serverLevel, ChunkPos coordIntPair) { + return serverLevel.getChunkSource().chunkMap.getPlayers(coordIntPair, false); + } + + /* + NMS conversion + */ + public static LevelChunkSection newChunkSection( + final int layer, + final char[] blocks, + CachedBukkitAdapter adapter, + Registry biomeRegistry, + @Nullable PalettedContainer> biomes + ) { + return newChunkSection(layer, null, blocks, adapter, biomeRegistry, biomes); + } + + public static LevelChunkSection newChunkSection( + final int layer, + final Function get, + char[] set, + CachedBukkitAdapter adapter, + Registry biomeRegistry, + @Nullable PalettedContainer> biomes + ) { + if (set == null) { + return newChunkSection(layer, biomeRegistry, biomes); + } + final int[] blockToPalette = FaweCache.INSTANCE.BLOCK_TO_PALETTE.get(); + final int[] paletteToBlock = FaweCache.INSTANCE.PALETTE_TO_BLOCK.get(); + final long[] blockStates = FaweCache.INSTANCE.BLOCK_STATES.get(); + final int[] blocksCopy = FaweCache.INSTANCE.SECTION_BLOCKS.get(); + try { + int num_palette; + if (get == null) { + num_palette = createPalette(blockToPalette, paletteToBlock, blocksCopy, set, adapter, null); + } else { + num_palette = createPalette(layer, blockToPalette, paletteToBlock, blocksCopy, get, set, adapter, null); + } + + int bitsPerEntry = MathMan.log2nlz(num_palette - 1); + if (bitsPerEntry > 0 && bitsPerEntry < 5) { + bitsPerEntry = 4; + } else if (bitsPerEntry > 8) { + bitsPerEntry = MathMan.log2nlz(Block.BLOCK_STATE_REGISTRY.size() - 1); + } + + int bitsPerEntryNonZero = Math.max(bitsPerEntry, 1); // We do want to use zero sometimes + final int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntryNonZero); + final int blockBitArrayEnd = MathMan.ceilZero((float) 4096 / blocksPerLong); + + if (num_palette == 1) { + for (int i = 0; i < blockBitArrayEnd; i++) { + blockStates[i] = 0; + } + } else { + final BitArrayUnstretched bitArray = new BitArrayUnstretched(bitsPerEntryNonZero, 4096, blockStates); + bitArray.fromRaw(blocksCopy); + } + + final long[] bits = Arrays.copyOfRange(blockStates, 0, blockBitArrayEnd); + final BitStorage nmsBits; + if (bitsPerEntry == 0) { + nmsBits = new ZeroBitStorage(4096); + } else { + nmsBits = new SimpleBitStorage(bitsPerEntry, 4096, bits); + } + List palette; + if (bitsPerEntry < 9) { + palette = new ArrayList<>(); + for (int i = 0; i < num_palette; i++) { + int ordinal = paletteToBlock[i]; + blockToPalette[ordinal] = Integer.MAX_VALUE; + final BlockState state = BlockTypesCache.states[ordinal]; + palette.add(((PaperweightBlockMaterial) state.getMaterial()).getState()); + } + } else { + palette = List.of(); + } + + // Create palette with data + @SuppressWarnings("deprecation") // constructor is deprecated on paper, but needed to keep compatibility with spigot + final PalettedContainer blockStatePalettedContainer = + new PalettedContainer<>( + Block.BLOCK_STATE_REGISTRY, + PalettedContainer.Strategy.SECTION_STATES, + PalettedContainer.Strategy.SECTION_STATES.getConfiguration(Block.BLOCK_STATE_REGISTRY, bitsPerEntry), + nmsBits, + palette + ); + if (biomes == null) { + IdMap> biomeHolderIdMap = biomeRegistry.asHolderIdMap(); + biomes = new PalettedContainer<>( + biomeHolderIdMap, + biomeHolderIdMap.byIdOrThrow(WorldEditPlugin + .getInstance() + .getBukkitImplAdapter() + .getInternalBiomeId( + BiomeTypes.PLAINS)), + PalettedContainer.Strategy.SECTION_BIOMES + ); + } + + return new LevelChunkSection(blockStatePalettedContainer, biomes); + } catch (final Throwable e) { + throw e; + } finally { + Arrays.fill(blockToPalette, Integer.MAX_VALUE); + Arrays.fill(paletteToBlock, Integer.MAX_VALUE); + Arrays.fill(blockStates, 0); + Arrays.fill(blocksCopy, 0); + } + } + + @SuppressWarnings("deprecation") // Only deprecated in paper + private static LevelChunkSection newChunkSection( + int layer, + Registry biomeRegistry, + @Nullable PalettedContainer> biomes + ) { + if (biomes == null) { + return new LevelChunkSection(biomeRegistry); + } + PalettedContainer dataPaletteBlocks = new PalettedContainer<>( + Block.BLOCK_STATE_REGISTRY, + Blocks.AIR.defaultBlockState(), + PalettedContainer.Strategy.SECTION_STATES + ); + return new LevelChunkSection(dataPaletteBlocks, biomes); + } + + /** + * Create a new {@link PalettedContainer}. Should only be used if no biome container existed beforehand. + */ + public static PalettedContainer> getBiomePalettedContainer( + BiomeType[] biomes, + IdMap> biomeRegistry + ) { + if (biomes == null) { + return null; + } + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + // Don't stream this as typically will see 1-4 biomes; stream overhead is large for the small length + Map> palette = new HashMap<>(); + for (BiomeType biomeType : new LinkedList<>(Arrays.asList(biomes))) { + Holder biome; + if (biomeType == null) { + biome = biomeRegistry.byId(adapter.getInternalBiomeId(BiomeTypes.PLAINS)); + } else { + biome = biomeRegistry.byId(adapter.getInternalBiomeId(biomeType)); + } + palette.put(biomeType, biome); + } + int biomeCount = palette.size(); + int bitsPerEntry = MathMan.log2nlz(biomeCount - 1); + Object configuration = PalettedContainer.Strategy.SECTION_STATES.getConfiguration( + new FakeIdMapBiome(biomeCount), + bitsPerEntry + ); + if (bitsPerEntry > 3) { + bitsPerEntry = MathMan.log2nlz(biomeRegistry.size() - 1); + } + PalettedContainer> biomePalettedContainer = new PalettedContainer<>( + biomeRegistry, + biomeRegistry.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), + PalettedContainer.Strategy.SECTION_BIOMES + ); + + final Palette> biomePalette; + if (bitsPerEntry == 0) { + biomePalette = new SingleValuePalette<>( + biomePalettedContainer.registry, + biomePalettedContainer, + new ArrayList<>(palette.values()) // Must be modifiable + ); + } else if (bitsPerEntry == 4) { + biomePalette = LinearPalette.create( + 4, + biomePalettedContainer.registry, + biomePalettedContainer, + new ArrayList<>(palette.values()) // Must be modifiable + ); + } else if (bitsPerEntry < 9) { + biomePalette = HashMapPalette.create( + bitsPerEntry, + biomePalettedContainer.registry, + biomePalettedContainer, + new ArrayList<>(palette.values()) // Must be modifiable + ); + } else { + biomePalette = GlobalPalette.create( + bitsPerEntry, + biomePalettedContainer.registry, + biomePalettedContainer, + null // unused + ); + } + + int bitsPerEntryNonZero = Math.max(bitsPerEntry, 1); // We do want to use zero sometimes + final int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntryNonZero); + final int arrayLength = MathMan.ceilZero(64f / blocksPerLong); + + + BitStorage bitStorage = bitsPerEntry == 0 ? new ZeroBitStorage(64) : new SimpleBitStorage( + bitsPerEntry, + 64, + new long[arrayLength] + ); + + try { + Object data = dataConstructor.newInstance(configuration, bitStorage, biomePalette); + fieldData.set(biomePalettedContainer, data); + int index = 0; + for (int y = 0; y < 4; y++) { + for (int z = 0; z < 4; z++) { + for (int x = 0; x < 4; x++, index++) { + BiomeType biomeType = biomes[index]; + if (biomeType == null) { + continue; + } + Holder biome = biomeRegistry.byId(WorldEditPlugin + .getInstance() + .getBukkitImplAdapter() + .getInternalBiomeId(biomeType)); + if (biome == null) { + continue; + } + biomePalettedContainer.set(x, y, z, biome); + } + } + } + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + return biomePalettedContainer; + } + + public static void clearCounts(final LevelChunkSection section) throws IllegalAccessException { + fieldTickingFluidCount.setShort(section, (short) 0); + fieldTickingBlockCount.setShort(section, (short) 0); + } + + public static BiomeType adapt(Holder biome, LevelAccessor levelAccessor) { + final Registry biomeRegistry = levelAccessor.registryAccess().registryOrThrow(BIOME); + if (biomeRegistry.getKey(biome.value()) == null) { + return biomeRegistry.asHolderIdMap().getId(biome) == -1 ? BiomeTypes.OCEAN + : null; + } + return BiomeTypes.get(biome.unwrapKey().orElseThrow().location().toString()); + } + + static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { + try { + if (levelChunk.loaded || levelChunk.level.isClientSide()) { + BlockEntity blockEntity = levelChunk.blockEntities.remove(beacon.getBlockPos()); + if (blockEntity != null) { + if (!levelChunk.level.isClientSide) { + methodRemoveGameEventListener.invoke(levelChunk, beacon, levelChunk.level); + } + fieldRemove.set(beacon, true); + } + } + methodremoveTickingBlockEntity.invoke(levelChunk, beacon.getBlockPos()); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + } + + static List getEntities(LevelChunk chunk) { + ExceptionCollector collector = new ExceptionCollector<>(); + if (PaperLib.isPaper()) { + if (POST_CHUNK_REWRITE) { + try { + //noinspection unchecked + return (List) PAPER_CHUNK_GEN_ALL_ENTITIES.invoke(chunk.level.getEntityLookup().getChunk(chunk.locX, chunk.locZ)); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Failed to lookup entities [POST_CHUNK_REWRITE=true]", e); + } + } + try { + EntityList entityList = (EntityList) LEVEL_CHUNK_ENTITIES.get(chunk); + return List.of(entityList.getRawData()); + } catch (IllegalAccessException e) { + collector.add(new RuntimeException("Failed to lookup entities [POST_CHUNK_REWRITE=false]", e)); + // fall through + } + } + try { + //noinspection unchecked + return ((PersistentEntitySectionManager) (SERVER_LEVEL_ENTITY_MANAGER.get(chunk.level))).getEntities(chunk.getPos()); + } catch (IllegalAccessException e) { + collector.add(new RuntimeException("Failed to lookup entities [PAPER=false]", e)); + } + collector.throwIfPresent(); + return List.of(); + } + + record FakeIdMapBlock(int size) implements IdMap { + + @Override + public int getId(final net.minecraft.world.level.block.state.BlockState entry) { + return 0; + } + + @Nullable + @Override + public net.minecraft.world.level.block.state.BlockState byId(final int index) { + return null; + } + + @Nonnull + @Override + public Iterator iterator() { + return Collections.emptyIterator(); + } + + } + + record FakeIdMapBiome(int size) implements IdMap { + + @Override + public int getId(final Biome entry) { + return 0; + } + + @Nullable + @Override + public Biome byId(final int index) { + return null; + } + + @Nonnull + @Override + public Iterator iterator() { + return Collections.emptyIterator(); + } + + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPostProcessor.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPostProcessor.java new file mode 100644 index 000000000..5fafdc439 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPostProcessor.java @@ -0,0 +1,175 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; + +import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.extent.processor.ProcessorScope; +import com.fastasyncworldedit.core.queue.IBatchProcessor; +import com.fastasyncworldedit.core.queue.IChunk; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.registry.state.PropertyKey; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.Fluids; + +import javax.annotation.Nullable; + +public class PaperweightPostProcessor implements IBatchProcessor { + + @Override + public IChunkSet processSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) { + return set; + } + + @SuppressWarnings("deprecation") + @Override + public void postProcess(final IChunk chunk, final IChunkGet iChunkGet, final IChunkSet iChunkSet) { + boolean tickFluid = Settings.settings().EXPERIMENTAL.ALLOW_TICK_FLUIDS; + // The PostProcessor shouldn't be added, but just in case + if (!tickFluid) { + return; + } + PaperweightGetBlocks_Copy getBlocks = (PaperweightGetBlocks_Copy) iChunkGet; + layer: + for (int layer = iChunkSet.getMinSectionPosition(); layer <= iChunkSet.getMaxSectionPosition(); layer++) { + char[] set = iChunkSet.loadIfPresent(layer); + if (set == null) { + // No edit means no need to process + continue; + } + char[] get = null; + for (int i = 0; i < 4096; i++) { + char ordinal = set[i]; + char replacedOrdinal = BlockTypesCache.ReservedIDs.__RESERVED__; + boolean fromGet = false; // Used for liquids + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { + if (get == null) { + get = getBlocks.load(layer); + } + // If this is null, then it's because we're loading a layer in the range of 0->15, but blocks aren't + // actually being set + if (get == null) { + continue layer; + } + fromGet = true; + ordinal = replacedOrdinal = get[i]; + } + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { + continue; + } else if (!fromGet) { // if fromGet, don't do the same again + if (get == null) { + get = getBlocks.load(layer); + } + replacedOrdinal = get[i]; + } + boolean ticking = BlockTypesCache.ticking[ordinal]; + boolean replacedWasTicking = BlockTypesCache.ticking[replacedOrdinal]; + boolean replacedWasLiquid = false; + BlockState replacedState = null; + if (!ticking) { + // If the block being replaced was not ticking, it cannot be a liquid + if (!replacedWasTicking) { + continue; + } + // If the block being replaced is not fluid, we do not need to worry + if (!(replacedWasLiquid = + (replacedState = BlockState.getFromOrdinal(replacedOrdinal)).getMaterial().isLiquid())) { + continue; + } + } + BlockState state = BlockState.getFromOrdinal(ordinal); + boolean liquid = state.getMaterial().isLiquid(); + int x = i & 15; + int y = (i >> 8) & 15; + int z = (i >> 4) & 15; + BlockPos position = new BlockPos((chunk.getX() << 4) + x, (layer << 4) + y, (chunk.getZ() << 4) + z); + if (liquid || replacedWasLiquid) { + if (liquid) { + addFluid(getBlocks.serverLevel, state, position); + continue; + } + // If the replaced fluid (is?) adjacent to water. Do not bother to check adjacent chunks(sections) as this + // may be time consuming. Chances are any fluid blocks in adjacent chunks are being replaced or will end up + // being ticked anyway. We only need it to be "hit" once. + if (!wasAdjacentToWater(get, set, i, x, y, z)) { + continue; + } + addFluid(getBlocks.serverLevel, replacedState, position); + } + } + } + } + + @Nullable + @Override + public Extent construct(final Extent child) { + throw new UnsupportedOperationException("Processing only"); + } + + @Override + public ProcessorScope getScope() { + return ProcessorScope.READING_SET_BLOCKS; + } + + private boolean wasAdjacentToWater(char[] get, char[] set, int i, int x, int y, int z) { + if (set == null || get == null) { + return false; + } + char ordinal; + char reserved = BlockTypesCache.ReservedIDs.__RESERVED__; + if (x > 0 && set[i - 1] != reserved) { + if (BlockTypesCache.ticking[(ordinal = get[i - 1])] && isFluid(ordinal)) { + return true; + } + } + if (x < 15 && set[i + 1] != reserved) { + if (BlockTypesCache.ticking[(ordinal = get[i + 1])] && isFluid(ordinal)) { + return true; + } + } + if (z > 0 && set[i - 16] != reserved) { + if (BlockTypesCache.ticking[(ordinal = get[i - 16])] && isFluid(ordinal)) { + return true; + } + } + if (z < 15 && set[i + 16] != reserved) { + if (BlockTypesCache.ticking[(ordinal = get[i + 16])] && isFluid(ordinal)) { + return true; + } + } + if (y > 0 && set[i - 256] != reserved) { + if (BlockTypesCache.ticking[(ordinal = get[i - 256])] && isFluid(ordinal)) { + return true; + } + } + if (y < 15 && set[i + 256] != reserved) { + return BlockTypesCache.ticking[(ordinal = get[i + 256])] && isFluid(ordinal); + } + return false; + } + + @SuppressWarnings("deprecation") + private boolean isFluid(char ordinal) { + return BlockState.getFromOrdinal(ordinal).getMaterial().isLiquid(); + } + + @SuppressWarnings("deprecation") + private void addFluid(final ServerLevel serverLevel, final BlockState replacedState, final BlockPos position) { + Fluid type; + if (replacedState.getBlockType() == BlockTypes.LAVA) { + type = (int) replacedState.getState(PropertyKey.LEVEL) == 0 ? Fluids.LAVA : Fluids.FLOWING_LAVA; + } else { + type = (int) replacedState.getState(PropertyKey.LEVEL) == 0 ? Fluids.WATER : Fluids.FLOWING_WATER; + } + serverLevel.scheduleTick( + position, + type, + type.getTickDelay(serverLevel) + ); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighter.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighter.java new file mode 100644 index 000000000..6c6527c02 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighter.java @@ -0,0 +1,205 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; + +import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter; +import com.fastasyncworldedit.core.extent.processor.lighting.Relighter; +import com.fastasyncworldedit.core.queue.IQueueChunk; +import com.fastasyncworldedit.core.queue.IQueueExtent; +import com.fastasyncworldedit.core.util.MathMan; +import com.fastasyncworldedit.core.util.TaskManager; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArraySet; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongSet; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.TicketType; +import net.minecraft.util.Unit; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkStatus; +import org.apache.logging.log4j.Logger; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import java.util.function.IntConsumer; + +public class PaperweightStarlightRelighter implements Relighter { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32 + private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting + + private static final TicketType FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0); + private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkStatus.getDistance(ChunkStatus.LIGHT); + + + private final ServerLevel serverLevel; + private final ReentrantLock lock = new ReentrantLock(); + private final Long2ObjectLinkedOpenHashMap regions = new Long2ObjectLinkedOpenHashMap<>(); + private final ReentrantLock areaLock = new ReentrantLock(); + private final NMSRelighter delegate; + + @SuppressWarnings("rawtypes") + public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent queue) { + this.serverLevel = serverLevel; + this.delegate = new NMSRelighter(queue); + } + + @Override + public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) { + areaLock.lock(); + try { + long key = MathMan.pairInt(cx >> CHUNKS_PER_BATCH_SQRT_LOG2, cz >> CHUNKS_PER_BATCH_SQRT_LOG2); + // TODO probably submit here already if chunks.size == CHUNKS_PER_BATCH? + LongSet chunks = this.regions.computeIfAbsent(key, k -> new LongArraySet(CHUNKS_PER_BATCH >> 2)); + chunks.add(ChunkPos.asLong(cx, cz)); + } finally { + areaLock.unlock(); + } + return true; + } + + @Override + public void addLightUpdate(int x, int y, int z) { + delegate.addLightUpdate(x, y, z); + } + + /* + * This method is called "recursively", iterating and removing elements + * from the regions linked map. This way, chunks are loaded in batches to avoid + * OOMEs. + */ + @Override + public void fixLightingSafe(boolean sky) { + this.areaLock.lock(); + try { + if (regions.isEmpty()) { + return; + } + LongSet first = regions.removeFirst(); + fixLighting(first, () -> fixLightingSafe(true)); + } finally { + this.areaLock.unlock(); + } + } + + /* + * Processes a set of chunks and runs an action afterwards. + * The action is run async, the chunks are partly processed on the main thread + * (as required by the server). + */ + private void fixLighting(LongSet chunks, Runnable andThen) { + // convert from long keys to ChunkPos + Set coords = new HashSet<>(); + LongIterator iterator = chunks.iterator(); + while (iterator.hasNext()) { + coords.add(new ChunkPos(iterator.nextLong())); + } + TaskManager.taskManager().task(() -> { + // trigger chunk load and apply ticket on main thread + List> futures = new ArrayList<>(); + for (ChunkPos pos : coords) { + futures.add(serverLevel.getWorld().getChunkAtAsync(pos.x, pos.z) + .thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel( + FAWE_TICKET, + pos, + LIGHT_LEVEL, + Unit.INSTANCE + )) + ); + } + // collect futures and trigger relight once all chunks are loaded + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(v -> + invokeRelight( + coords, + c -> { + }, // no callback for single chunks required + i -> { + if (i != coords.size()) { + LOGGER.warn("Processed {} chunks instead of {}", i, coords.size()); + } + // post process chunks on main thread + TaskManager.taskManager().task(() -> postProcessChunks(coords)); + // call callback on our own threads + TaskManager.taskManager().async(andThen); + } + ) + ); + }); + } + + private void invokeRelight( + Set coords, + Consumer chunkCallback, + IntConsumer processCallback + ) { + try { + serverLevel.getChunkSource().getLightEngine().relight(coords, chunkCallback, processCallback); + } catch (Exception e) { + LOGGER.error("Error occurred on relighting", e); + } + } + + /* + * Allow the server to unload the chunks again. + * Also, if chunk packets are sent delayed, we need to do that here + */ + private void postProcessChunks(Set coords) { + boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING; + for (ChunkPos pos : coords) { + int x = pos.x; + int z = pos.z; + if (delay) { // we still need to send the block changes of that chunk + PaperweightPlatformAdapter.sendChunk(serverLevel, x, z, false); + } + serverLevel.getChunkSource().removeTicketAtLevel(FAWE_TICKET, pos, LIGHT_LEVEL, Unit.INSTANCE); + } + } + + @Override + public void clear() { + + } + + @Override + public void removeLighting() { + this.delegate.removeLighting(); + } + + @Override + public void fixBlockLighting() { + fixLightingSafe(true); + } + + @Override + public void fixSkyLighting() { + fixLightingSafe(true); + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public ReentrantLock getLock() { + return this.lock; + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public void close() throws Exception { + fixLightingSafe(true); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighterFactory.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighterFactory.java new file mode 100644 index 000000000..4e5b9b7ff --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighterFactory.java @@ -0,0 +1,28 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; + +import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter; +import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode; +import com.fastasyncworldedit.core.extent.processor.lighting.Relighter; +import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; +import com.fastasyncworldedit.core.queue.IQueueChunk; +import com.fastasyncworldedit.core.queue.IQueueExtent; +import com.sk89q.worldedit.world.World; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; + +import javax.annotation.Nonnull; + +public class PaperweightStarlightRelighterFactory implements RelighterFactory { + + @Override + public @Nonnull + @SuppressWarnings("rawtypes") + Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent queue) { + org.bukkit.World w = Bukkit.getWorld(world.getName()); + if (w == null) { + return NullRelighter.INSTANCE; + } + return new PaperweightStarlightRelighter(((CraftWorld) w).getHandle(), queue); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/nbt/PaperweightLazyCompoundTag.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/nbt/PaperweightLazyCompoundTag.java new file mode 100644 index 000000000..911da046a --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/nbt/PaperweightLazyCompoundTag.java @@ -0,0 +1,161 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt; + +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.LazyCompoundTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.util.nbt.CompoundBinaryTag; +import net.minecraft.nbt.NumericTag; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +public class PaperweightLazyCompoundTag extends LazyCompoundTag { + + private final Supplier compoundTagSupplier; + private CompoundTag compoundTag; + + public PaperweightLazyCompoundTag(Supplier compoundTagSupplier) { + super(new HashMap<>()); + this.compoundTagSupplier = compoundTagSupplier; + } + + public PaperweightLazyCompoundTag(net.minecraft.nbt.CompoundTag compoundTag) { + this(() -> compoundTag); + } + + public net.minecraft.nbt.CompoundTag get() { + return compoundTagSupplier.get(); + } + + @Override + @SuppressWarnings("unchecked") + public Map getValue() { + if (compoundTag == null) { + compoundTag = (CompoundTag) WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(compoundTagSupplier.get()); + } + return compoundTag.getValue(); + } + + @Override + public CompoundBinaryTag asBinaryTag() { + getValue(); + return compoundTag.asBinaryTag(); + } + + public boolean containsKey(String key) { + return compoundTagSupplier.get().contains(key); + } + + public byte[] getByteArray(String key) { + return compoundTagSupplier.get().getByteArray(key); + } + + public byte getByte(String key) { + return compoundTagSupplier.get().getByte(key); + } + + public double getDouble(String key) { + return compoundTagSupplier.get().getDouble(key); + } + + public double asDouble(String key) { + net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key); + if (tag instanceof NumericTag numTag) { + return numTag.getAsDouble(); + } + return 0; + } + + public float getFloat(String key) { + return compoundTagSupplier.get().getFloat(key); + } + + public int[] getIntArray(String key) { + return compoundTagSupplier.get().getIntArray(key); + } + + public int getInt(String key) { + return compoundTagSupplier.get().getInt(key); + } + + public int asInt(String key) { + net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key); + if (tag instanceof NumericTag numTag) { + return numTag.getAsInt(); + } + return 0; + } + + @SuppressWarnings("unchecked") + public List getList(String key) { + net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key); + if (tag instanceof net.minecraft.nbt.ListTag nbtList) { + ArrayList list = new ArrayList<>(); + for (net.minecraft.nbt.Tag elem : nbtList) { + if (elem instanceof net.minecraft.nbt.CompoundTag compoundTag) { + list.add(new PaperweightLazyCompoundTag(compoundTag)); + } else { + list.add(WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(elem)); + } + } + return list; + } + return Collections.emptyList(); + } + + @SuppressWarnings("unchecked") + public ListTag getListTag(String key) { + net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key); + if (tag instanceof net.minecraft.nbt.ListTag) { + return (ListTag) WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(tag); + } + return new ListTag(StringTag.class, Collections.emptyList()); + } + + @SuppressWarnings("unchecked") + public List getList(String key, Class listType) { + ListTag listTag = getListTag(key); + if (listTag.getType().equals(listType)) { + return (List) listTag.getValue(); + } else { + return Collections.emptyList(); + } + } + + public long[] getLongArray(String key) { + return compoundTagSupplier.get().getLongArray(key); + } + + public long getLong(String key) { + return compoundTagSupplier.get().getLong(key); + } + + public long asLong(String key) { + net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key); + if (tag instanceof NumericTag numTag) { + return numTag.getAsLong(); + } + return 0; + } + + public short getShort(String key) { + return compoundTagSupplier.get().getShort(key); + } + + public String getString(String key) { + return compoundTagSupplier.get().getString(key); + } + + @Override + public String toString() { + return compoundTagSupplier.get().toString(); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/regen/PaperweightRegen.java new file mode 100644 index 000000000..2ec8e6e41 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/regen/PaperweightRegen.java @@ -0,0 +1,591 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regen; + +import com.fastasyncworldedit.bukkit.adapter.Regenerator; +import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.queue.IChunkCache; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.util.ReflectionUtils; +import com.fastasyncworldedit.core.util.TaskManager; +import com.google.common.collect.ImmutableList; +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Lifecycle; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.PaperweightGetBlocks; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.io.file.SafeFiles; +import com.sk89q.worldedit.world.RegenOptions; +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ChunkTaskPriorityQueueSorter.Message; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ThreadedLevelLightEngine; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.thread.ProcessorHandle; +import net.minecraft.util.thread.ProcessorMailbox; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.LevelSettings; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.biome.FixedBiomeSource; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.FlatLevelSource; +import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; +import net.minecraft.world.level.levelgen.WorldOptions; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; +import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.craftbukkit.v1_20_R2.CraftServer; +import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R2.generator.CustomChunkGenerator; +import org.bukkit.generator.BiomeProvider; +import org.bukkit.generator.BlockPopulator; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.OptionalLong; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; + +import static net.minecraft.core.registries.Registries.BIOME; + +public class PaperweightRegen extends Regenerator { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + private static final Field serverWorldsField; + private static final Field paperConfigField; + private static final Field flatBedrockField; + private static final Field generatorSettingFlatField; + private static final Field generatorSettingBaseSupplierField; + private static final Field delegateField; + private static final Field chunkSourceField; + private static final Field generatorStructureStateField; + private static final Field ringPositionsField; + private static final Field hasGeneratedPositionsField; + + //list of chunk stati in correct order without FULL + private static final Map chunkStati = new LinkedHashMap<>(); + + static { + chunkStati.put(ChunkStatus.EMPTY, Concurrency.FULL); // empty: radius -1, does nothing + chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Concurrency.NONE); // structure starts: uses unsynchronized maps + chunkStati.put( + ChunkStatus.STRUCTURE_REFERENCES, + Concurrency.FULL + ); // structure refs: radius 8, but only writes to current chunk + chunkStati.put(ChunkStatus.BIOMES, Concurrency.FULL); // biomes: radius 0 + chunkStati.put(ChunkStatus.NOISE, Concurrency.RADIUS); // noise: radius 8 + chunkStati.put(ChunkStatus.SURFACE, Concurrency.NONE); // surface: radius 0, requires NONE + chunkStati.put(ChunkStatus.CARVERS, Concurrency.NONE); // carvers: radius 0, but RADIUS and FULL change results + /*chunkStati.put( + ChunkStatus.LIQUID_CARVERS, + Concurrency.NONE + ); // liquid carvers: radius 0, but RADIUS and FULL change results*/ + chunkStati.put(ChunkStatus.FEATURES, Concurrency.NONE); // features: uses unsynchronized maps + chunkStati.put( + ChunkStatus.LIGHT, + Concurrency.FULL + ); // light: radius 1, but no writes to other chunks, only current chunk + chunkStati.put(ChunkStatus.SPAWN, Concurrency.FULL); // spawn: radius 0 + // chunkStati.put(ChunkStatus.HEIGHTMAPS, Concurrency.FULL); // heightmaps: radius 0 + + try { + serverWorldsField = CraftServer.class.getDeclaredField("worlds"); + serverWorldsField.setAccessible(true); + + Field tmpPaperConfigField; + Field tmpFlatBedrockField; + try { //only present on paper + tmpPaperConfigField = Level.class.getDeclaredField("paperConfig"); + tmpPaperConfigField.setAccessible(true); + + tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock"); + tmpFlatBedrockField.setAccessible(true); + } catch (Exception e) { + tmpPaperConfigField = null; + tmpFlatBedrockField = null; + } + paperConfigField = tmpPaperConfigField; + flatBedrockField = tmpFlatBedrockField; + + generatorSettingBaseSupplierField = NoiseBasedChunkGenerator.class.getDeclaredField(Refraction.pickName( + "settings", "e")); + generatorSettingBaseSupplierField.setAccessible(true); + + generatorSettingFlatField = FlatLevelSource.class.getDeclaredField(Refraction.pickName("settings", "d")); + generatorSettingFlatField.setAccessible(true); + + delegateField = CustomChunkGenerator.class.getDeclaredField("delegate"); + delegateField.setAccessible(true); + + chunkSourceField = ServerLevel.class.getDeclaredField(Refraction.pickName("chunkSource", "I")); + chunkSourceField.setAccessible(true); + + generatorStructureStateField = ChunkMap.class.getDeclaredField(Refraction.pickName("chunkGeneratorState", "v")); + generatorStructureStateField.setAccessible(true); + + ringPositionsField = ChunkGeneratorStructureState.class.getDeclaredField(Refraction.pickName("ringPositions", "g")); + ringPositionsField.setAccessible(true); + + hasGeneratedPositionsField = ChunkGeneratorStructureState.class.getDeclaredField( + Refraction.pickName("hasGeneratedPositions", "h") + ); + hasGeneratedPositionsField.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + //runtime + private ServerLevel originalServerWorld; + private ServerChunkCache originalChunkProvider; + private ServerLevel freshWorld; + private ServerChunkCache freshChunkProvider; + private LevelStorageSource.LevelStorageAccess session; + private StructureTemplateManager structureTemplateManager; + private ThreadedLevelLightEngine threadedLevelLightEngine; + private ChunkGenerator chunkGenerator; + + private Path tempDir; + + private boolean generateFlatBedrock = false; + + public PaperweightRegen(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) { + super(originalBukkitWorld, region, target, options); + } + + @Override + protected boolean prepare() { + this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle(); + originalChunkProvider = originalServerWorld.getChunkSource(); + + //flat bedrock? (only on paper) + if (paperConfigField != null) { + try { + generateFlatBedrock = flatBedrockField.getBoolean(paperConfigField.get(originalServerWorld)); + } catch (Exception ignored) { + } + } + + seed = options.getSeed().orElse(originalServerWorld.getSeed()); + chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c)); + + return true; + } + + @Override + @SuppressWarnings("unchecked") + protected boolean initNewWorld() throws Exception { + //world folder + tempDir = java.nio.file.Files.createTempDirectory("FastAsyncWorldEditWorldGen"); + + //prepare for world init (see upstream implementation for reference) + org.bukkit.World.Environment environment = originalBukkitWorld.getEnvironment(); + org.bukkit.generator.ChunkGenerator generator = originalBukkitWorld.getGenerator(); + LevelStorageSource levelStorageSource = LevelStorageSource.createDefault(tempDir); + ResourceKey levelStemResourceKey = getWorldDimKey(environment); + session = levelStorageSource.createAccess("faweregentempworld", levelStemResourceKey); + PrimaryLevelData originalWorldData = originalServerWorld.serverLevelData; + + MinecraftServer server = originalServerWorld.getCraftServer().getServer(); + WorldOptions originalOpts = originalWorldData.worldGenOptions(); + WorldOptions newOpts = options.getSeed().isPresent() + ? originalOpts.withSeed(OptionalLong.of(seed)) + : originalOpts; + LevelSettings newWorldSettings = new LevelSettings( + "faweregentempworld", + originalWorldData.settings.gameType(), + originalWorldData.settings.hardcore(), + originalWorldData.settings.difficulty(), + originalWorldData.settings.allowCommands(), + originalWorldData.settings.gameRules(), + originalWorldData.settings.getDataConfiguration() + ); + + PrimaryLevelData.SpecialWorldProperty specialWorldProperty = + originalWorldData.isFlatWorld() + ? PrimaryLevelData.SpecialWorldProperty.FLAT + : originalWorldData.isDebugWorld() + ? PrimaryLevelData.SpecialWorldProperty.DEBUG + : PrimaryLevelData.SpecialWorldProperty.NONE; + PrimaryLevelData newWorldData = new PrimaryLevelData(newWorldSettings, newOpts, specialWorldProperty, Lifecycle.stable()); + + BiomeProvider biomeProvider = getBiomeProvider(); + + + //init world + freshWorld = Fawe.instance().getQueueHandler().sync((Supplier) () -> new ServerLevel( + server, + server.executor, + session, + newWorldData, + originalServerWorld.dimension(), + DedicatedServer.getServer().registryAccess().registry(Registries.LEVEL_STEM).orElseThrow() + .getOrThrow(levelStemResourceKey), + new RegenNoOpWorldLoadListener(), + originalServerWorld.isDebug(), + seed, + ImmutableList.of(), + false, + originalServerWorld.getRandomSequences(), + environment, + generator, + biomeProvider + ) { + + private final Holder singleBiome = options.hasBiomeType() ? DedicatedServer.getServer().registryAccess() + .registryOrThrow(BIOME).asHolderIdMap().byIdOrThrow( + WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType()) + ) : null; + + @Override + public void tick(BooleanSupplier shouldKeepTicking) { //no ticking + } + + @Override + public Holder getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) { + if (options.hasBiomeType()) { + return singleBiome; + } + return PaperweightRegen.this.chunkGenerator.getBiomeSource().getNoiseBiome( + biomeX, biomeY, biomeZ, getChunkSource().randomState().sampler() + ); + } + }).get(); + freshWorld.noSave = true; + removeWorldFromWorldsMap(); + newWorldData.checkName(originalServerWorld.serverLevelData.getLevelName()); //rename to original world name + if (paperConfigField != null) { + paperConfigField.set(freshWorld, originalServerWorld.paperConfig()); + } + + ChunkGenerator originalGenerator = originalChunkProvider.getGenerator(); + if (originalGenerator instanceof FlatLevelSource flatLevelSource) { + FlatLevelGeneratorSettings generatorSettingFlat = flatLevelSource.settings(); + chunkGenerator = new FlatLevelSource(generatorSettingFlat); + } else if (originalGenerator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) { + Holder generatorSettingBaseSupplier = (Holder) generatorSettingBaseSupplierField.get( + originalGenerator); + BiomeSource biomeSource; + if (options.hasBiomeType()) { + + biomeSource = new FixedBiomeSource( + DedicatedServer.getServer().registryAccess() + .registryOrThrow(BIOME).asHolderIdMap().byIdOrThrow( + WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType()) + ) + ); + } else { + biomeSource = originalGenerator.getBiomeSource(); + } + chunkGenerator = new NoiseBasedChunkGenerator( + biomeSource, + generatorSettingBaseSupplier + ); + } else if (originalGenerator instanceof CustomChunkGenerator customChunkGenerator) { + chunkGenerator = customChunkGenerator.getDelegate(); + } else { + LOGGER.error("Unsupported generator type {}", originalGenerator.getClass().getName()); + return false; + } + if (generator != null) { + chunkGenerator = new CustomChunkGenerator(freshWorld, chunkGenerator, generator); + generateConcurrent = generator.isParallelCapable(); + } +// chunkGenerator.conf = freshWorld.spigotConfig; - Does not exist anymore, may need to be re-addressed + + freshChunkProvider = new ServerChunkCache( + freshWorld, + session, + server.getFixerUpper(), + server.getStructureManager(), + server.executor, + chunkGenerator, + freshWorld.spigotConfig.viewDistance, + freshWorld.spigotConfig.simulationDistance, + server.forceSynchronousWrites(), + new RegenNoOpWorldLoadListener(), + (chunkCoordIntPair, state) -> { + }, + () -> server.overworld().getDataStorage() + ) { + // redirect to LevelChunks created in #createChunks + @Override + public ChunkAccess getChunk(int x, int z, ChunkStatus chunkstatus, boolean create) { + ChunkAccess chunkAccess = getChunkAt(x, z); + if (chunkAccess == null && create) { + chunkAccess = createChunk(getProtoChunkAt(x, z)); + } + return chunkAccess; + } + }; + + if (seed == originalOpts.seed() && !options.hasBiomeType()) { + // Optimisation for needless ring position calculation when the seed and biome is the same. + ChunkGeneratorStructureState state = (ChunkGeneratorStructureState) generatorStructureStateField.get(originalChunkProvider.chunkMap); + boolean hasGeneratedPositions = hasGeneratedPositionsField.getBoolean(state); + if (hasGeneratedPositions) { + Map>> origPositions = + (Map>>) ringPositionsField.get(state); + Map>> copy = new Object2ObjectArrayMap<>( + origPositions); + ChunkGeneratorStructureState newState = (ChunkGeneratorStructureState) generatorStructureStateField.get(freshChunkProvider.chunkMap); + ringPositionsField.set(newState, copy); + hasGeneratedPositionsField.setBoolean(newState, true); + } + } + + + chunkSourceField.set(freshWorld, freshChunkProvider); + //let's start then + structureTemplateManager = server.getStructureManager(); + threadedLevelLightEngine = new NoOpLightEngine(freshChunkProvider); + + return true; + } + + @Override + protected void cleanup() { + try { + session.close(); + } catch (Exception ignored) { + } + + //shutdown chunk provider + try { + Fawe.instance().getQueueHandler().sync(() -> { + try { + freshChunkProvider.close(false); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } catch (Exception ignored) { + } + + //remove world from server + try { + Fawe.instance().getQueueHandler().sync(this::removeWorldFromWorldsMap); + } catch (Exception ignored) { + } + + //delete directory + try { + SafeFiles.tryHardToDeleteDir(tempDir); + } catch (Exception ignored) { + } + } + + @Override + protected ProtoChunk createProtoChunk(int x, int z) { + return new FastProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, freshWorld, + this.freshWorld.registryAccess().registryOrThrow(BIOME), null + ); + } + + @Override + protected LevelChunk createChunk(ProtoChunk protoChunk) { + return new LevelChunk( + freshWorld, + protoChunk, + null // we don't want to add entities + ); + } + + @Override + protected ChunkStatusWrap getFullChunkStatus() { + return new ChunkStatusWrap(ChunkStatus.FULL); + } + + @Override + protected List getBlockPopulators() { + return originalServerWorld.getWorld().getPopulators(); + } + + @Override + protected void populate(LevelChunk levelChunk, Random random, BlockPopulator blockPopulator) { + // BlockPopulator#populate has to be called synchronously for TileEntity access + TaskManager.taskManager().task(() -> { + final CraftWorld world = freshWorld.getWorld(); + final Chunk chunk = world.getChunkAt(levelChunk.locX, levelChunk.locZ); + blockPopulator.populate(world, random, chunk); + }); + } + + @Override + protected IChunkCache initSourceQueueCache() { + return (chunkX, chunkZ) -> new PaperweightGetBlocks(freshWorld, chunkX, chunkZ) { + @Override + public LevelChunk ensureLoaded(ServerLevel nmsWorld, int x, int z) { + return getChunkAt(x, z); + } + }; + } + + //util + @SuppressWarnings("unchecked") + private void removeWorldFromWorldsMap() { + Fawe.instance().getQueueHandler().sync(() -> { + try { + Map map = (Map) serverWorldsField.get(Bukkit.getServer()); + map.remove("faweregentempworld"); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + } + + private ResourceKey getWorldDimKey(org.bukkit.World.Environment env) { + return switch (env) { + case NETHER -> LevelStem.NETHER; + case THE_END -> LevelStem.END; + default -> LevelStem.OVERWORLD; + }; + } + + private static class RegenNoOpWorldLoadListener implements ChunkProgressListener { + + private RegenNoOpWorldLoadListener() { + } + + @Override + public void updateSpawnPos(ChunkPos spawnPos) { + } + + @Override + public void onStatusChange(ChunkPos pos, @Nullable ChunkStatus status) { + } + + @Override + public void start() { + + } + + @Override + public void stop() { + } + + // TODO Paper only(?) @Override + public void setChunkRadius(int radius) { + } + + } + + private class FastProtoChunk extends ProtoChunk { + + public FastProtoChunk( + final ChunkPos pos, + final UpgradeData upgradeData, + final LevelHeightAccessor world, + final Registry biomeRegistry, + @Nullable final BlendingData blendingData + ) { + super(pos, upgradeData, world, biomeRegistry, blendingData); + } + + // avoid warning on paper + + // compatibility with spigot + + public boolean generateFlatBedrock() { + return generateFlatBedrock; + } + + // no one will ever see the entities! + @Override + public List getEntities() { + return Collections.emptyList(); + } + + } + + protected class ChunkStatusWrap extends ChunkStatusWrapper { + + private final ChunkStatus chunkStatus; + + public ChunkStatusWrap(ChunkStatus chunkStatus) { + this.chunkStatus = chunkStatus; + } + + @Override + public int requiredNeighborChunkRadius() { + return chunkStatus.getRange(); + } + + @Override + public String name() { + return chunkStatus.toString(); + } + + @Override + public CompletableFuture processChunk(List accessibleChunks) { + return chunkStatus.generate( + Runnable::run, // TODO revisit, we might profit from this somehow? + freshWorld, + chunkGenerator, + structureTemplateManager, + threadedLevelLightEngine, + c -> CompletableFuture.completedFuture(Either.left(c)), + accessibleChunks + ); + } + + } + + /** + * A light engine that does nothing. As light is calculated after pasting anyway, we can avoid + * work this way. + */ + static class NoOpLightEngine extends ThreadedLevelLightEngine { + + private static final ProcessorMailbox MAILBOX = ProcessorMailbox.create(task -> { + }, "fawe-no-op"); + private static final ProcessorHandle> HANDLE = ProcessorHandle.of("fawe-no-op", m -> { + }); + + public NoOpLightEngine(final ServerChunkCache chunkProvider) { + super(chunkProvider, chunkProvider.chunkMap, false, MAILBOX, HANDLE); + } + + @Override + public CompletableFuture lightChunk(final ChunkAccess chunk, final boolean excludeBlocks) { + return CompletableFuture.completedFuture(chunk); + } + + } + +} diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index 4f7211db5..710c68212 100644 --- a/worldedit-bukkit/build.gradle.kts +++ b/worldedit-bukkit/build.gradle.kts @@ -206,7 +206,7 @@ tasks { versionNumber.set("${project.version}") versionType.set("release") uploadFile.set(file("build/libs/${rootProject.name}-Bukkit-${project.version}.jar")) - gameVersions.addAll(listOf("1.20.1", "1.20", "1.19.4", "1.18.2", "1.17.1", "1.16.5")) + gameVersions.addAll(listOf("1.20.2", "1.20.1", "1.20", "1.19.4", "1.18.2", "1.17.1", "1.16.5")) loaders.addAll(listOf("paper", "spigot")) changelog.set("The changelog is available on GitHub: https://github.com/IntellectualSites/" + "FastAsyncWorldEdit/releases/tag/${project.version}") diff --git a/worldedit-core/build.gradle.kts b/worldedit-core/build.gradle.kts index a58230b2c..d9794d9f6 100644 --- a/worldedit-core/build.gradle.kts +++ b/worldedit-core/build.gradle.kts @@ -12,7 +12,7 @@ applyPlatformAndCoreConfiguration() dependencies { constraints { implementation(libs.snakeyaml) { - version { strictly("2.0") } + version { strictly("2.2") } because("Bukkit provides SnakeYaml") } } From 828308b71dff7601ddcd40f578125cdc2bbbeb12 Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Fri, 29 Sep 2023 22:51:11 +0200 Subject: [PATCH 52/60] Release 2.8.0 --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ed699857f..ee21f4b08 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,7 +34,7 @@ logger.lifecycle(""" ******************************************* """) -var rootVersion by extra("2.7.2") +var rootVersion by extra("2.8.0") var snapshot by extra("SNAPSHOT") var revision: String by extra("") var buildNumber by extra("") @@ -52,7 +52,7 @@ ext { } } -version = String.format("%s-%s", rootVersion, buildNumber) +version = String.format("%s", rootVersion) if (!project.hasProperty("gitCommitHash")) { apply(plugin = "org.ajoberstar.grgit") From a7f1d94b6f2f35088509bd435a1e42820ca789fb Mon Sep 17 00:00:00 2001 From: Alexander Brandes Date: Fri, 29 Sep 2023 23:17:00 +0200 Subject: [PATCH 53/60] Back to snapshot for development --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ee21f4b08..9cc6dee0f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,7 +34,7 @@ logger.lifecycle(""" ******************************************* """) -var rootVersion by extra("2.8.0") +var rootVersion by extra("2.8.1") var snapshot by extra("SNAPSHOT") var revision: String by extra("") var buildNumber by extra("") @@ -52,7 +52,7 @@ ext { } } -version = String.format("%s", rootVersion) +version = String.format("%s-%s", rootVersion, buildNumber) if (!project.hasProperty("gitCommitHash")) { apply(plugin = "org.ajoberstar.grgit") From 8bfb4f31acf18509cb911ceaa1b7ed6143664743 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 10:06:25 +0200 Subject: [PATCH 54/60] Update plugin xyz.jpenilla.run-paper to v2.2.0 (#2441) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9cc6dee0f..44fa18649 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ import xyz.jpenilla.runpaper.task.RunServer plugins { id("io.github.gradle-nexus.publish-plugin") version "1.3.0" - id("xyz.jpenilla.run-paper") version "2.1.0" + id("xyz.jpenilla.run-paper") version "2.2.0" } if (!File("$rootDir/.git").exists()) { From c8069bbdb4dbea72563ce8a3ac743fe5307957aa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 10:18:32 +0200 Subject: [PATCH 55/60] Update dependency com.zaxxer:SparseBitSet to v1.3 (#2439) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1fe45859b..ffb24f7e9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ plotsquared = "7.0.0" # Third party bstats = "3.0.2" -sparsebitset = "1.2" +sparsebitset = "1.3" parallelgzip = "1.0.5" adventure = "4.14.0" adventure-bukkit = "4.3.0" From fca775ee14e282900fcf51cbc7c93a8e1edae4f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 10:18:43 +0200 Subject: [PATCH 56/60] Update dependency net.kyori:adventure-platform-bukkit to v4.3.1 (#2438) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ffb24f7e9..589d2827b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ bstats = "3.0.2" sparsebitset = "1.3" parallelgzip = "1.0.5" adventure = "4.14.0" -adventure-bukkit = "4.3.0" +adventure-bukkit = "4.3.1" checkerqual = "3.38.0" truezip = "6.8.4" auto-value = "1.10.2" From f2ca272fb4ae38699a03afc9b4ac143919b71a2f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 11:26:45 +0200 Subject: [PATCH 57/60] Update antlr4 to v4.13.1 (#2436) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 589d2827b..3774e0d6a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ auto-value = "1.10.2" findbugs = "3.0.2" rhino-runtime = "1.7.14" zstd-jni = "1.4.8-1" # Not latest as it can be difficult to obtain latest ZSTD libs -antlr4 = "4.13.0" +antlr4 = "4.13.1" json-simple = "1.1.1" jlibnoise = "1.0.0" jchronic = "0.2.4a" From a88b4c7e32bb456cd9537df396f580fcb44c2f24 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 11:26:58 +0200 Subject: [PATCH 58/60] Update dependency com.palmergames.bukkit.towny:towny to v0.99.5.20 (#2437) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3774e0d6a..13066707a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ mapmanager = "1.8.0-SNAPSHOT" griefprevention = "16.18.1" griefdefender = "2.1.0-SNAPSHOT" residence = "4.5._13.1" -towny = "0.99.5.16" +towny = "0.99.5.20" plotsquared = "7.0.0" # Third party From c7465cc156d10a2ade20dd3eb9eae2927a6fa3fa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 12:00:08 +0200 Subject: [PATCH 59/60] Update actions/checkout action to v4 (#2442) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-pr.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/upload-release-assets.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 11f0d1d5d..98319f7d2 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -9,7 +9,7 @@ jobs: os: [ubuntu-latest, windows-latest, macos-latest] steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 - name: Setup Java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 65555631f..6cda7331f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 - name: Setup Java diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f4287607f..a0cd830bd 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -19,7 +19,7 @@ jobs: language: ['java'] steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v3 with: diff --git a/.github/workflows/upload-release-assets.yml b/.github/workflows/upload-release-assets.yml index e93fc35c5..cb1d0a9d1 100644 --- a/.github/workflows/upload-release-assets.yml +++ b/.github/workflows/upload-release-assets.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 - name: Setup Java From 66c2dc3edaaa71cf0c9f7e71e6f3f6ff06eb628f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 12:00:16 +0200 Subject: [PATCH 60/60] Update dependency com.zaxxer:SparseBitSet to v1.3 (#2440) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- worldedit-bukkit/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index 710c68212..ead334bf4 100644 --- a/worldedit-bukkit/build.gradle.kts +++ b/worldedit-bukkit/build.gradle.kts @@ -186,7 +186,7 @@ tasks.named("shadowJar") { include(dependency("net.kyori:adventure-nbt:4.14.0")) } relocate("com.zaxxer", "com.fastasyncworldedit.core.math") { - include(dependency("com.zaxxer:SparseBitSet:1.2")) + include(dependency("com.zaxxer:SparseBitSet:1.3")) } relocate("org.anarres", "com.fastasyncworldedit.core.internal.io") { include(dependency("org.anarres:parallelgzip:1.0.5"))