diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java index afc6c36de1..8bffc3a2ed 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java @@ -448,6 +448,17 @@ public BaseBlock getFullBlock(BlockVector3 position) { } } + @Override + public boolean notifyAndLightBlock(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState previousType) throws WorldEditException { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + if (adapter != null) { + adapter.notifyAndLightBlock(BukkitAdapter.adapt(getWorld(), position), previousType); + return true; + } + + return false; + } + @Override public BaseBiome getBiome(BlockVector2 position) { BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java index 3938775819..13631e1d9b 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java @@ -24,6 +24,7 @@ import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; import org.bukkit.Location; @@ -78,6 +79,15 @@ public interface BukkitImplAdapter { */ boolean setBlock(Location location, BlockStateHolder state, boolean notifyAndLight); + /** + * Notifies the simulation that the block at the given location has + * been changed and it must be re-lighted (and issue other events). + * + * @param position position of the block + * @param previousType the type of the previous block that was there + */ + void notifyAndLightBlock(Location position, BlockState previousType); + /** * Get the state for the given entity. * diff --git a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1.class b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1.class index b4c1e73b50..855eaaa082 100644 Binary files a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1.class and b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1.class differ diff --git a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2.class b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2.class index 0c8ead5c3c..a431d05840 100644 Binary files a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2.class and b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2.class differ diff --git a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2_2.class b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2_2.class index 0c0194e0ca..68d629d18c 100644 Binary files a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2_2.class and b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2_2.class differ 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 fabf49f3f6..4d1bde24d6 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -120,6 +120,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -149,6 +150,38 @@ public enum Stage { BEFORE_CHANGE } + /** + * Reorder mode for {@link EditSession#setReorderMode(ReorderMode)}. + * + * MULTI_STAGE = Multi stage reorder, may not be great with mods. + * FAST = Use the fast mode. Good for mods. + * NONE = Place blocks without worrying about placement order. + */ + public enum ReorderMode { + MULTI_STAGE("multi"), + FAST("fast"), + NONE("none"); + + private String displayName; + + ReorderMode(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return this.displayName; + } + + public static Optional getFromDisplayName(String name) { + for (ReorderMode mode : values()) { + if (mode.getDisplayName().equalsIgnoreCase(name)) { + return Optional.of(mode); + } + } + return Optional.empty(); + } + } + @SuppressWarnings("ProtectedField") protected final World world; private final ChangeSet changeSet = new BlockOptimizedHistory(); @@ -170,6 +203,8 @@ public enum Stage { private final Extent bypassHistory; private final Extent bypassNone; + private ReorderMode reorderMode = ReorderMode.MULTI_STAGE; + private Mask oldMask; /** @@ -226,6 +261,8 @@ public enum Stage { this.bypassHistory = extent; this.bypassNone = extent; } + + setReorderMode(this.reorderMode); } private Extent wrapExtent(Extent extent, EventBus eventBus, EditSessionEvent event, Stage stage) { @@ -237,10 +274,13 @@ private Extent wrapExtent(Extent extent, EventBus eventBus, EditSessionEvent eve // pkg private for TracedEditSession only, may later become public API boolean commitRequired() { - if (isQueueEnabled() && reorderExtent.commitRequired()) { + if (reorderExtent != null && reorderExtent.commitRequired()) { + return true; + } + if (chunkBatchingExtent != null && chunkBatchingExtent.commitRequired()) { return true; } - if (isBatchingChunks() && chunkBatchingExtent.commitRequired()) { + if (fastModeExtent != null && fastModeExtent.commitRequired()) { return true; } return false; @@ -248,14 +288,63 @@ boolean commitRequired() { /** * Turns on specific features for a normal WorldEdit session, such as - * {@link #enableQueue() queuing} and {@link #setBatchingChunks(boolean) + * {@link #setBatchingChunks(boolean) * chunk batching}. */ public void enableStandardMode() { - enableQueue(); setBatchingChunks(true); } + /** + * Sets the {@link ReorderMode} of this EditSession, and flushes the session. + * + * @param reorderMode The reorder mode + */ + public void setReorderMode(ReorderMode reorderMode) { + if (reorderMode == ReorderMode.FAST && fastModeExtent == null) { + throw new IllegalArgumentException("An EditSession without a fast mode tried to use it for reordering!"); + } + if (reorderMode == ReorderMode.MULTI_STAGE && reorderExtent == null) { + throw new IllegalArgumentException("An EditSession without a reorder extent tried to use it for reordering!"); + } + if (commitRequired()) { + flushSession(); + } + + this.reorderMode = reorderMode; + switch (reorderMode) { + case MULTI_STAGE: + if (fastModeExtent != null) { + fastModeExtent.setPostEditSimulationEnabled(false); + } + reorderExtent.setEnabled(true); + break; + case FAST: + fastModeExtent.setPostEditSimulationEnabled(true); + if (reorderExtent != null) { + reorderExtent.setEnabled(false); + } + break; + case NONE: + if (fastModeExtent != null) { + fastModeExtent.setPostEditSimulationEnabled(false); + } + if (reorderExtent != null) { + reorderExtent.setEnabled(false); + } + break; + } + } + + /** + * Get the reorder mode. + * + * @return the reorder mode + */ + public ReorderMode getReorderMode() { + return reorderMode; + } + /** * Get the world. * @@ -297,26 +386,35 @@ public void setBlockChangeLimit(int limit) { * Returns queue status. * * @return whether the queue is enabled + * @deprecated Use {@link EditSession#getReorderMode()} with MULTI_STAGE instead. */ + @Deprecated public boolean isQueueEnabled() { - return reorderExtent.isEnabled(); + return reorderMode == ReorderMode.MULTI_STAGE && reorderExtent.isEnabled(); } /** * Queue certain types of block for better reproduction of those blocks. + * + * Uses {@link ReorderMode#MULTI_STAGE} + * @deprecated Use {@link EditSession#setReorderMode(ReorderMode)} with MULTI_STAGE instead. */ + @Deprecated public void enableQueue() { - reorderExtent.setEnabled(true); + setReorderMode(ReorderMode.MULTI_STAGE); } /** * Disable the queue. This will {@linkplain #flushSession() flush the session}. + * + * @deprecated Use {@link EditSession#setReorderMode(ReorderMode)} with another mode instead. */ + @Deprecated public void disableQueue() { if (isQueueEnabled()) { flushSession(); } - reorderExtent.setEnabled(false); + setReorderMode(ReorderMode.NONE); } /** @@ -436,16 +534,15 @@ public void setBatchingChunks(boolean batchingChunks) { /** * Disable all buffering extents. * - * @see #disableQueue() + * @see #setReorderMode(ReorderMode) * @see #setBatchingChunks(boolean) */ public void disableBuffering() { // We optimize here to avoid repeated calls to flushSession. - boolean needsFlush = isQueueEnabled() || isBatchingChunks(); - if (needsFlush) { + if (commitRequired()) { flushSession(); } - reorderExtent.setEnabled(false); + setReorderMode(ReorderMode.NONE); if (chunkBatchingExtent != null) { chunkBatchingExtent.setEnabled(false); } 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 ee1c038fe5..9b117c9183 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java @@ -93,6 +93,7 @@ public class LocalSession { private transient Mask mask; private transient TimeZone timezone = TimeZone.getDefault(); private transient BlockVector3 cuiTemporaryBlock; + private transient EditSession.ReorderMode reorderMode = EditSession.ReorderMode.MULTI_STAGE; // Saved properties private String lastScript; @@ -225,11 +226,13 @@ public EditSession undo(@Nullable BlockBag newBlockBag, Player player) { --historyPointer; if (historyPointer >= 0) { EditSession editSession = history.get(historyPointer); - EditSession newEditSession = WorldEdit.getInstance().getEditSessionFactory() - .getEditSession(editSession.getWorld(), -1, newBlockBag, player); - newEditSession.enableStandardMode(); - newEditSession.setFastMode(fastMode); - editSession.undo(newEditSession); + try (EditSession newEditSession = WorldEdit.getInstance().getEditSessionFactory() + .getEditSession(editSession.getWorld(), -1, newBlockBag, player)) { + newEditSession.enableStandardMode(); + newEditSession.setReorderMode(reorderMode); + newEditSession.setFastMode(fastMode); + editSession.undo(newEditSession); + } return editSession; } else { historyPointer = 0; @@ -248,11 +251,13 @@ public EditSession redo(@Nullable BlockBag newBlockBag, Player player) { checkNotNull(player); if (historyPointer < history.size()) { EditSession editSession = history.get(historyPointer); - EditSession newEditSession = WorldEdit.getInstance().getEditSessionFactory() - .getEditSession(editSession.getWorld(), -1, newBlockBag, player); - newEditSession.enableStandardMode(); - newEditSession.setFastMode(fastMode); - editSession.redo(newEditSession); + try (EditSession newEditSession = WorldEdit.getInstance().getEditSessionFactory() + .getEditSession(editSession.getWorld(), -1, newBlockBag, player)) { + newEditSession.enableStandardMode(); + newEditSession.setReorderMode(reorderMode); + newEditSession.setFastMode(fastMode); + editSession.redo(newEditSession); + } ++historyPointer; return editSession; } @@ -853,6 +858,7 @@ public EditSession createEditSession(Player player) { .getEditSession(player.isPlayer() ? player.getWorld() : null, getBlockChangeLimit(), blockBag, player); editSession.setFastMode(fastMode); + editSession.setReorderMode(reorderMode); Request.request().setEditSession(editSession); editSession.setMask(mask); @@ -877,6 +883,24 @@ public void setFastMode(boolean fastMode) { this.fastMode = fastMode; } + /** + * Gets the reorder mode of the session. + * + * @return The reorder mode + */ + public EditSession.ReorderMode getReorderMode() { + return reorderMode; + } + + /** + * Sets the reorder mode of the session. + * + * @param reorderMode The reorder mode + */ + public void setReorderMode(EditSession.ReorderMode reorderMode) { + this.reorderMode = reorderMode; + } + /** * Get the mask. * diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/Blocks.java b/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/Blocks.java index 09bd50ff22..c71bd6ef66 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/Blocks.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/Blocks.java @@ -19,14 +19,9 @@ package com.sk89q.worldedit.blocks; -import com.sk89q.worldedit.world.block.BlockCategories; import com.sk89q.worldedit.world.block.BlockStateHolder; -import com.sk89q.worldedit.world.block.BlockType; -import com.sk89q.worldedit.world.block.BlockTypes; import java.util.Collection; -import java.util.HashSet; -import java.util.Set; /** * Block-related utility methods. @@ -36,121 +31,6 @@ public final class Blocks { private Blocks() { } - /** - * HashSet for shouldPlaceLast. - */ - private static final Set shouldPlaceLast = new HashSet<>(); - static { - shouldPlaceLast.addAll(BlockCategories.SAPLINGS.getAll()); - shouldPlaceLast.addAll(BlockCategories.FLOWER_POTS.getAll()); - shouldPlaceLast.addAll(BlockCategories.BUTTONS.getAll()); - shouldPlaceLast.addAll(BlockCategories.ANVIL.getAll()); // becomes relevant with asynchronous placement - shouldPlaceLast.addAll(BlockCategories.WOODEN_PRESSURE_PLATES.getAll()); - shouldPlaceLast.addAll(BlockCategories.CARPETS.getAll()); - shouldPlaceLast.addAll(BlockCategories.RAILS.getAll()); - shouldPlaceLast.add(BlockTypes.BLACK_BED); - shouldPlaceLast.add(BlockTypes.BLUE_BED); - shouldPlaceLast.add(BlockTypes.BROWN_BED); - shouldPlaceLast.add(BlockTypes.CYAN_BED); - shouldPlaceLast.add(BlockTypes.GRAY_BED); - shouldPlaceLast.add(BlockTypes.GREEN_BED); - shouldPlaceLast.add(BlockTypes.LIGHT_BLUE_BED); - shouldPlaceLast.add(BlockTypes.LIGHT_GRAY_BED); - shouldPlaceLast.add(BlockTypes.LIME_BED); - shouldPlaceLast.add(BlockTypes.MAGENTA_BED); - shouldPlaceLast.add(BlockTypes.ORANGE_BED); - shouldPlaceLast.add(BlockTypes.PINK_BED); - shouldPlaceLast.add(BlockTypes.PURPLE_BED); - shouldPlaceLast.add(BlockTypes.RED_BED); - shouldPlaceLast.add(BlockTypes.WHITE_BED); - shouldPlaceLast.add(BlockTypes.YELLOW_BED); - shouldPlaceLast.add(BlockTypes.GRASS); - shouldPlaceLast.add(BlockTypes.TALL_GRASS); - shouldPlaceLast.add(BlockTypes.ROSE_BUSH); - shouldPlaceLast.add(BlockTypes.DANDELION); - shouldPlaceLast.add(BlockTypes.BROWN_MUSHROOM); - shouldPlaceLast.add(BlockTypes.RED_MUSHROOM); - shouldPlaceLast.add(BlockTypes.FERN); - shouldPlaceLast.add(BlockTypes.LARGE_FERN); - shouldPlaceLast.add(BlockTypes.OXEYE_DAISY); - shouldPlaceLast.add(BlockTypes.AZURE_BLUET); - shouldPlaceLast.add(BlockTypes.TORCH); - shouldPlaceLast.add(BlockTypes.WALL_TORCH); - shouldPlaceLast.add(BlockTypes.FIRE); - shouldPlaceLast.add(BlockTypes.REDSTONE_WIRE); - shouldPlaceLast.add(BlockTypes.CARROTS); - shouldPlaceLast.add(BlockTypes.POTATOES); - shouldPlaceLast.add(BlockTypes.WHEAT); - shouldPlaceLast.add(BlockTypes.BEETROOTS); - shouldPlaceLast.add(BlockTypes.COCOA); - shouldPlaceLast.add(BlockTypes.LADDER); - shouldPlaceLast.add(BlockTypes.LEVER); - shouldPlaceLast.add(BlockTypes.REDSTONE_TORCH); - shouldPlaceLast.add(BlockTypes.REDSTONE_WALL_TORCH); - shouldPlaceLast.add(BlockTypes.SNOW); - shouldPlaceLast.add(BlockTypes.NETHER_PORTAL); - shouldPlaceLast.add(BlockTypes.END_PORTAL); - shouldPlaceLast.add(BlockTypes.REPEATER); - shouldPlaceLast.add(BlockTypes.VINE); - shouldPlaceLast.add(BlockTypes.LILY_PAD); - shouldPlaceLast.add(BlockTypes.NETHER_WART); - shouldPlaceLast.add(BlockTypes.PISTON); - shouldPlaceLast.add(BlockTypes.STICKY_PISTON); - shouldPlaceLast.add(BlockTypes.TRIPWIRE_HOOK); - shouldPlaceLast.add(BlockTypes.TRIPWIRE); - shouldPlaceLast.add(BlockTypes.STONE_PRESSURE_PLATE); - shouldPlaceLast.add(BlockTypes.HEAVY_WEIGHTED_PRESSURE_PLATE); - shouldPlaceLast.add(BlockTypes.LIGHT_WEIGHTED_PRESSURE_PLATE); - shouldPlaceLast.add(BlockTypes.COMPARATOR); - shouldPlaceLast.add(BlockTypes.IRON_TRAPDOOR); - shouldPlaceLast.add(BlockTypes.ACACIA_TRAPDOOR); - shouldPlaceLast.add(BlockTypes.BIRCH_TRAPDOOR); - shouldPlaceLast.add(BlockTypes.DARK_OAK_TRAPDOOR); - shouldPlaceLast.add(BlockTypes.JUNGLE_TRAPDOOR); - shouldPlaceLast.add(BlockTypes.OAK_TRAPDOOR); - shouldPlaceLast.add(BlockTypes.SPRUCE_TRAPDOOR); - shouldPlaceLast.add(BlockTypes.DAYLIGHT_DETECTOR); - } - - /** - * Checks to see whether a block should be placed last (when reordering - * blocks that are placed). - * - * @param type the block type - * @return true if the block should be placed last - */ - public static boolean shouldPlaceLast(BlockType type) { - return shouldPlaceLast.contains(type); - } - - /** - * HashSet for shouldPlaceLast. - */ - private static final Set shouldPlaceFinal = new HashSet<>(); - static { - shouldPlaceFinal.addAll(BlockCategories.DOORS.getAll()); - shouldPlaceFinal.addAll(BlockCategories.BANNERS.getAll()); - shouldPlaceFinal.add(BlockTypes.SIGN); - shouldPlaceFinal.add(BlockTypes.WALL_SIGN); - shouldPlaceFinal.add(BlockTypes.CACTUS); - shouldPlaceFinal.add(BlockTypes.SUGAR_CANE); - shouldPlaceFinal.add(BlockTypes.CAKE); - shouldPlaceFinal.add(BlockTypes.PISTON_HEAD); - shouldPlaceFinal.add(BlockTypes.MOVING_PISTON); - } - - /** - * Checks to see whether a block should be placed in the final queue. - * - * This applies to blocks that can be attached to other blocks that have an attachment. - * - * @param type the type of the block - * @return whether the block is in the final queue - */ - public static boolean shouldPlaceFinal(BlockType type) { - return shouldPlaceFinal.contains(type); - } - /** * Checks whether a given block is in a list of base blocks. * diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java index 862b4398d8..5e4f608eda 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java @@ -115,6 +115,31 @@ public void fast(Player player, LocalSession session, EditSession editSession, C } } + @Command( + aliases = { "/reorder" }, + usage = "[multi|fast|none]", + desc = "Sets the reorder mode of WorldEdit", + min = 0, + max = 1 + ) + @CommandPermissions("worldedit.reorder") + public void reorderMode(Player player, LocalSession session, EditSession editSession, CommandContext args) throws WorldEditException { + String newState = args.getString(0, null); + if (newState == null) { + player.print("The reorder mode is " + session.getReorderMode().getDisplayName()); + } else { + java.util.Optional reorderModeOptional = EditSession.ReorderMode.getFromDisplayName(newState); + if (!reorderModeOptional.isPresent()) { + player.printError("Unknown reorder mode!"); + return; + } + + EditSession.ReorderMode reorderMode = reorderModeOptional.get(); + session.setReorderMode(reorderMode); + player.print("The reorder mode is now " + session.getReorderMode().getDisplayName()); + } + } + @Command( aliases = { "/drawsel" }, usage = "[on|off]", diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/ChunkBatchingExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/ChunkBatchingExtent.java index 730edefea5..daeb9f0e6a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/ChunkBatchingExtent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/ChunkBatchingExtent.java @@ -73,7 +73,7 @@ public void setEnabled(boolean enabled) { } public boolean commitRequired() { - return batches.size() > 0; + return enabled; } @Override @@ -88,7 +88,7 @@ public boolean setBlock(BlockVector3 location, BlockStateHolder block) throws Wo @Override protected Operation commitBefore() { - if (!enabled) { + if (!commitRequired()) { return null; } return new Operation() { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/MultiStageReorder.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/MultiStageReorder.java index ea6bece5a4..aa16947403 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/MultiStageReorder.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/MultiStageReorder.java @@ -19,9 +19,7 @@ package com.sk89q.worldedit.extent.reorder; -import com.google.common.collect.Iterables; import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.blocks.Blocks; import com.sk89q.worldedit.extent.AbstractDelegateExtent; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.operation.Operation; @@ -35,8 +33,10 @@ import com.sk89q.worldedit.world.block.BlockCategories; 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 java.util.ArrayList; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; @@ -50,29 +50,134 @@ */ public class MultiStageReorder extends AbstractDelegateExtent implements ReorderingExtent { - private LocatedBlockList stage1 = new LocatedBlockList(); - private LocatedBlockList stage2 = new LocatedBlockList(); - private LocatedBlockList stage3 = new LocatedBlockList(); + private static Map priorityMap = new HashMap<>(); + + static { + // Late + priorityMap.put(BlockTypes.WATER, PlacementPriority.LATE); + priorityMap.put(BlockTypes.LAVA, PlacementPriority.LATE); + priorityMap.put(BlockTypes.SAND, PlacementPriority.LATE); + priorityMap.put(BlockTypes.GRAVEL, PlacementPriority.LATE); + + // Late + BlockCategories.SAPLINGS.getAll().forEach(type -> priorityMap.put(type, PlacementPriority.LAST)); + BlockCategories.FLOWER_POTS.getAll().forEach(type -> priorityMap.put(type, PlacementPriority.LAST)); + BlockCategories.BUTTONS.getAll().forEach(type -> priorityMap.put(type, PlacementPriority.LAST)); + BlockCategories.ANVIL.getAll().forEach(type -> priorityMap.put(type, PlacementPriority.LAST)); + BlockCategories.WOODEN_PRESSURE_PLATES.getAll().forEach(type -> priorityMap.put(type, PlacementPriority.LAST)); + BlockCategories.CARPETS.getAll().forEach(type -> priorityMap.put(type, PlacementPriority.LAST)); + BlockCategories.RAILS.getAll().forEach(type -> priorityMap.put(type, PlacementPriority.LAST)); + priorityMap.put(BlockTypes.BLACK_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.BLUE_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.BROWN_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.CYAN_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.GRAY_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.GREEN_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.LIGHT_BLUE_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.LIGHT_GRAY_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.LIME_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.MAGENTA_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.ORANGE_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.PINK_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.PURPLE_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.RED_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.WHITE_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.YELLOW_BED, PlacementPriority.LAST); + priorityMap.put(BlockTypes.GRASS, PlacementPriority.LAST); + priorityMap.put(BlockTypes.TALL_GRASS, PlacementPriority.LAST); + priorityMap.put(BlockTypes.ROSE_BUSH, PlacementPriority.LAST); + priorityMap.put(BlockTypes.DANDELION, PlacementPriority.LAST); + priorityMap.put(BlockTypes.BROWN_MUSHROOM, PlacementPriority.LAST); + priorityMap.put(BlockTypes.RED_MUSHROOM, PlacementPriority.LAST); + priorityMap.put(BlockTypes.FERN, PlacementPriority.LAST); + priorityMap.put(BlockTypes.LARGE_FERN, PlacementPriority.LAST); + priorityMap.put(BlockTypes.OXEYE_DAISY, PlacementPriority.LAST); + priorityMap.put(BlockTypes.AZURE_BLUET, PlacementPriority.LAST); + priorityMap.put(BlockTypes.TORCH, PlacementPriority.LAST); + priorityMap.put(BlockTypes.WALL_TORCH, PlacementPriority.LAST); + priorityMap.put(BlockTypes.FIRE, PlacementPriority.LAST); + priorityMap.put(BlockTypes.REDSTONE_WIRE, PlacementPriority.LAST); + priorityMap.put(BlockTypes.CARROTS, PlacementPriority.LAST); + priorityMap.put(BlockTypes.POTATOES, PlacementPriority.LAST); + priorityMap.put(BlockTypes.WHEAT, PlacementPriority.LAST); + priorityMap.put(BlockTypes.BEETROOTS, PlacementPriority.LAST); + priorityMap.put(BlockTypes.COCOA, PlacementPriority.LAST); + priorityMap.put(BlockTypes.LADDER, PlacementPriority.LAST); + priorityMap.put(BlockTypes.LEVER, PlacementPriority.LAST); + priorityMap.put(BlockTypes.REDSTONE_TORCH, PlacementPriority.LAST); + priorityMap.put(BlockTypes.REDSTONE_WALL_TORCH, PlacementPriority.LAST); + priorityMap.put(BlockTypes.SNOW, PlacementPriority.LAST); + priorityMap.put(BlockTypes.NETHER_PORTAL, PlacementPriority.LAST); + priorityMap.put(BlockTypes.END_PORTAL, PlacementPriority.LAST); + priorityMap.put(BlockTypes.REPEATER, PlacementPriority.LAST); + priorityMap.put(BlockTypes.VINE, PlacementPriority.LAST); + priorityMap.put(BlockTypes.LILY_PAD, PlacementPriority.LAST); + priorityMap.put(BlockTypes.NETHER_WART, PlacementPriority.LAST); + priorityMap.put(BlockTypes.PISTON, PlacementPriority.LAST); + priorityMap.put(BlockTypes.STICKY_PISTON, PlacementPriority.LAST); + priorityMap.put(BlockTypes.TRIPWIRE_HOOK, PlacementPriority.LAST); + priorityMap.put(BlockTypes.TRIPWIRE, PlacementPriority.LAST); + priorityMap.put(BlockTypes.STONE_PRESSURE_PLATE, PlacementPriority.LAST); + priorityMap.put(BlockTypes.HEAVY_WEIGHTED_PRESSURE_PLATE, PlacementPriority.LAST); + priorityMap.put(BlockTypes.LIGHT_WEIGHTED_PRESSURE_PLATE, PlacementPriority.LAST); + priorityMap.put(BlockTypes.COMPARATOR, PlacementPriority.LAST); + priorityMap.put(BlockTypes.IRON_TRAPDOOR, PlacementPriority.LAST); + priorityMap.put(BlockTypes.ACACIA_TRAPDOOR, PlacementPriority.LAST); + priorityMap.put(BlockTypes.BIRCH_TRAPDOOR, PlacementPriority.LAST); + priorityMap.put(BlockTypes.DARK_OAK_TRAPDOOR, PlacementPriority.LAST); + priorityMap.put(BlockTypes.JUNGLE_TRAPDOOR, PlacementPriority.LAST); + priorityMap.put(BlockTypes.OAK_TRAPDOOR, PlacementPriority.LAST); + priorityMap.put(BlockTypes.SPRUCE_TRAPDOOR, PlacementPriority.LAST); + priorityMap.put(BlockTypes.DAYLIGHT_DETECTOR, PlacementPriority.LAST); + + // Final + BlockCategories.DOORS.getAll().forEach(type -> priorityMap.put(type, PlacementPriority.FINAL)); + BlockCategories.BANNERS.getAll().forEach(type -> priorityMap.put(type, PlacementPriority.FINAL)); + priorityMap.put(BlockTypes.SIGN, PlacementPriority.FINAL); + priorityMap.put(BlockTypes.WALL_SIGN, PlacementPriority.FINAL); + priorityMap.put(BlockTypes.CACTUS, PlacementPriority.FINAL); + priorityMap.put(BlockTypes.SUGAR_CANE, PlacementPriority.FINAL); + priorityMap.put(BlockTypes.CAKE, PlacementPriority.FINAL); + priorityMap.put(BlockTypes.PISTON_HEAD, PlacementPriority.FINAL); + priorityMap.put(BlockTypes.MOVING_PISTON, PlacementPriority.FINAL); + } + + private Map stages = new HashMap<>(); + private boolean enabled; + public enum PlacementPriority { + CLEAR_FINAL, + CLEAR_LAST, + CLEAR_LATE, + FIRST, + LATE, + LAST, + FINAL + } + /** - * Create a new instance. + * Create a new instance when the re-ordering is enabled. * * @param extent the extent - * @param enabled true to enable */ - public MultiStageReorder(Extent extent, boolean enabled) { - super(extent); - this.enabled = enabled; + public MultiStageReorder(Extent extent) { + this(extent, true); } /** - * Create a new instance when the re-ordering is enabled. + * Create a new instance. * * @param extent the extent + * @param enabled true to enable */ - public MultiStageReorder(Extent extent) { - this(extent, true); + public MultiStageReorder(Extent extent, boolean enabled) { + super(extent); + this.enabled = enabled; + + for (PlacementPriority priority : PlacementPriority.values()) { + stages.put(priority, new LocatedBlockList()); + } } /** @@ -94,63 +199,87 @@ public void setEnabled(boolean enabled) { } public boolean commitRequired() { - return stage1.size() > 0 || stage2.size() > 0 || stage3.size() > 0; + return enabled; + } + + /** + * Gets the stage priority of the block. + * + * @param block The block + * @return The priority + */ + private PlacementPriority getPlacementPriority(BlockStateHolder block) { + return priorityMap.getOrDefault(block.getBlockType(), PlacementPriority.FIRST); } @Override public boolean setBlock(BlockVector3 location, BlockStateHolder block) throws WorldEditException { - BlockState existing = getBlock(location); - if (!enabled) { return super.setBlock(location, block); } - if (Blocks.shouldPlaceLast(block.getBlockType())) { - // Place torches, etc. last - stage2.add(location, block); - return !existing.equalsFuzzy(block); - } else if (Blocks.shouldPlaceFinal(block.getBlockType())) { - // Place signs, reed, etc even later - stage3.add(location, block); - return !existing.equalsFuzzy(block); - } else if (Blocks.shouldPlaceLast(existing.getBlockType())) { - // Destroy torches, etc. first - super.setBlock(location, BlockTypes.AIR.getDefaultState()); - return super.setBlock(location, block); - } else { - stage1.add(location, block); - return !existing.equalsFuzzy(block); + BlockState existing = getBlock(location); + PlacementPriority priority = getPlacementPriority(block); + PlacementPriority srcPriority = getPlacementPriority(existing); + + if (srcPriority != PlacementPriority.FIRST) { + BlockStateHolder replacement = block.getBlockType().getMaterial().isAir() ? block : BlockTypes.AIR.getDefaultState(); + + switch (srcPriority) { + case FINAL: + stages.get(PlacementPriority.CLEAR_FINAL).add(location, replacement); + break; + case LATE: + stages.get(PlacementPriority.CLEAR_LATE).add(location, replacement); + break; + case LAST: + stages.get(PlacementPriority.CLEAR_LAST).add(location, replacement); + break; + } + + if (block.getBlockType().getMaterial().isAir()) { + return !existing.equalsFuzzy(block); + } } + + stages.get(priority).add(location, block); + return !existing.equalsFuzzy(block); } @Override public Operation commitBefore() { - return new OperationQueue( - new SetLocatedBlocks( - getExtent(), - Iterables.concat(stage1, stage2)), - new Stage3Committer()); + if (!commitRequired()) { + return null; + } + List operations = new ArrayList<>(); + for (PlacementPriority priority : PlacementPriority.values()) { + if (priority != PlacementPriority.FINAL) { + operations.add(new SetLocatedBlocks(getExtent(), stages.get(priority))); + } + } + + operations.add(new FinalStageCommitter()); + return new OperationQueue(operations); } - private class Stage3Committer implements Operation { + private class FinalStageCommitter implements Operation { + private Extent extent = getExtent(); - @Override - public Operation resume(RunContext run) throws WorldEditException { - Extent extent = getExtent(); + private final Set blocks = new HashSet<>(); + private final Map blockTypes = new HashMap<>(); - final Set blocks = new HashSet<>(); - final Map blockTypes = new HashMap<>(); - for (LocatedBlock entry : stage3) { + public FinalStageCommitter() { + for (LocatedBlock entry : stages.get(PlacementPriority.FINAL)) { final BlockVector3 pt = entry.getLocation(); blocks.add(pt); blockTypes.put(pt, entry.getBlock()); } + } + @Override + public Operation resume(RunContext run) throws WorldEditException { while (!blocks.isEmpty()) { BlockVector3 current = blocks.iterator().next(); - if (!blocks.contains(current)) { - continue; - } final Deque walked = new LinkedList<>(); @@ -201,10 +330,9 @@ public Operation resume(RunContext run) throws WorldEditException { } } - stage1.clear(); - stage2.clear(); - stage3.clear(); - + for (LocatedBlockList stage : stages.values()) { + stage.clear(); + } return null; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/world/FastModeExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/world/FastModeExtent.java index b75156d3b1..5b92a8ad3c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/world/FastModeExtent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/world/FastModeExtent.java @@ -29,8 +29,10 @@ import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockTypes; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; @@ -40,8 +42,10 @@ public class FastModeExtent extends AbstractDelegateExtent { private final World world; + private final Set positions = new HashSet<>(); private final Set dirtyChunks = new HashSet<>(); private boolean enabled = true; + private boolean postEditSimulation; /** * Create a new instance with fast mode enabled. @@ -83,24 +87,59 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } + public boolean isPostEditSimulationEnabled() { + return postEditSimulation; + } + + public void setPostEditSimulationEnabled(boolean enabled) { + this.postEditSimulation = enabled; + } + @Override public boolean setBlock(BlockVector3 location, BlockStateHolder block) throws WorldEditException { - if (enabled) { + if (enabled || postEditSimulation) { dirtyChunks.add(BlockVector2.at(location.getBlockX() >> 4, location.getBlockZ() >> 4)); - return world.setBlock(location, block, false); + + if (world.setBlock(location, block, false)) { + if (!enabled && postEditSimulation) { + positions.add(location); + } + return true; + } + + return false; } else { return world.setBlock(location, block, true); } } + public boolean commitRequired() { + return enabled || postEditSimulation; + } + @Override protected Operation commitBefore() { + if (!commitRequired()) { + return null; + } return new Operation() { @Override public Operation resume(RunContext run) throws WorldEditException { if (!dirtyChunks.isEmpty()) { world.fixAfterFastMode(dirtyChunks); } + + if (!enabled && postEditSimulation) { + Iterator positionIterator = positions.iterator(); + while (run.shouldContinue() && positionIterator.hasNext()) { + BlockVector3 position = positionIterator.next(); + world.notifyAndLightBlock(position, BlockTypes.AIR.getDefaultState()); + positionIterator.remove(); + } + + return !positions.isEmpty() ? this : null; + } + return null; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/history/changeset/ArrayListHistory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/history/changeset/ArrayListHistory.java index bc13a57550..d014835d30 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/history/changeset/ArrayListHistory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/history/changeset/ArrayListHistory.java @@ -35,10 +35,24 @@ public class ArrayListHistory implements ChangeSet { private final List changes = new ArrayList<>(); + private boolean recordChanges = true; + @Override public void add(Change change) { checkNotNull(change); - changes.add(change); + if (recordChanges) { + changes.add(change); + } + } + + @Override + public boolean isRecordingChanges() { + return recordChanges; + } + + @Override + public void setRecordChanges(boolean recordChanges) { + this.recordChanges = recordChanges; } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/history/changeset/ChangeSet.java b/worldedit-core/src/main/java/com/sk89q/worldedit/history/changeset/ChangeSet.java index 0b018bcb94..003a007b63 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/history/changeset/ChangeSet.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/history/changeset/ChangeSet.java @@ -36,6 +36,20 @@ public interface ChangeSet { */ void add(Change change); + /** + * Whether or not the ChangeSet is recording changes. + * + * @return whether or not the ChangeSet is set to record changes + */ + boolean isRecordingChanges(); + + /** + * Tell the change set whether to record changes or not. + * + * @param recordChanges whether to record changes or not + */ + void setRecordChanges(boolean recordChanges); + /** * Get a backward directed iterator that can be used for undo. * diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java index 43ee77e394..eec1d47a91 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java @@ -64,6 +64,11 @@ public boolean setBlock(BlockVector3 position, BlockStateHolder block, boolean n return false; } + @Override + public boolean notifyAndLightBlock(BlockVector3 position, BlockState previousType) throws WorldEditException { + return false; + } + @Override public int getBlockLightLevel(BlockVector3 position) { return 0; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java index 3e7bbf88c4..d47723a422 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java @@ -33,10 +33,13 @@ import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.util.TreeGenerator; +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.weather.WeatherType; +import java.util.Vector; + /** * Represents a world (dimension). */ @@ -95,6 +98,16 @@ public interface World extends Extent { */ boolean setBlock(BlockVector3 position, BlockStateHolder block, boolean notifyAndLight) throws WorldEditException; + /** + * Notifies the simulation that the block at the given location has + * been changed and it must be re-lighted (and issue other events). + * + * @param position position of the block + * @param previousType the type of the previous block that was there + * @return true if the block was successfully notified + */ + boolean notifyAndLightBlock(BlockVector3 position, BlockState previousType) throws WorldEditException; + /** * Get the light level at the given block. * diff --git a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeWorld.java b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeWorld.java index 23ea709ff7..d3d84d48aa 100644 --- a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeWorld.java +++ b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeWorld.java @@ -212,6 +212,12 @@ public boolean setBlock(BlockVector3 position, BlockStateHolder block, boolean n return successful; } + @Override + public boolean notifyAndLightBlock(BlockVector3 position, BlockState previousType) throws WorldEditException { + // TODO Implement + return false; + } + // Can't get the "Object" to be right for withProperty w/o this @SuppressWarnings({ "rawtypes", "unchecked" }) private IBlockState applyProperties(BlockStateContainer stateContainer, IBlockState newState, Map, Object> states) { diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java index 74c2a6a3c2..e3401190e1 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java @@ -41,7 +41,6 @@ import com.sk89q.worldedit.world.item.ItemTypes; import com.sk89q.worldedit.world.weather.WeatherType; import com.sk89q.worldedit.world.weather.WeatherTypes; - import org.spongepowered.api.Sponge; import org.spongepowered.api.block.BlockSnapshot; import org.spongepowered.api.block.BlockState; @@ -163,6 +162,12 @@ public boolean setBlock(BlockVector3 position, BlockStateHolder block, boolean n return true; } + @Override + public boolean notifyAndLightBlock(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState previousType) throws WorldEditException { + // TODO Move this to adapter + return false; + } + @Override public boolean regenerate(Region region, EditSession editSession) { return false;