From 23b8c0c9fcb29327e20c25897581a09211c52f17 Mon Sep 17 00:00:00 2001 From: Christian Knaapen Date: Sat, 4 Feb 2023 00:30:30 +0100 Subject: [PATCH] WIP UndoRedo overhaul. Fixed concurrent modification issue in ServerBlockPlacer. --- .../effortlessbuilding/CommonConfig.java | 1 - .../effortlessbuilding/CommonEvents.java | 4 +- .../EffortlessBuilding.java | 2 + .../effortlessbuilding/ServerConfig.java | 3 +- .../buildmode/ThreeClicksBuildMode.java | 2 + .../buildmode/TwoClicksBuildMode.java | 1 + .../network/PerformRedoPacket.java | 3 +- .../network/PerformUndoPacket.java | 3 +- .../render/ModifierRenderer.java | 1 - .../render/RenderHandler.java | 4 +- .../systems/BuilderChain.java | 6 - .../systems/ServerBlockPlacer.java | 36 +- .../effortlessbuilding/systems/UndoRedo.java | 327 ++++++++++-------- 13 files changed, 227 insertions(+), 166 deletions(-) diff --git a/src/main/java/nl/requios/effortlessbuilding/CommonConfig.java b/src/main/java/nl/requios/effortlessbuilding/CommonConfig.java index 0285ed9..f641391 100644 --- a/src/main/java/nl/requios/effortlessbuilding/CommonConfig.java +++ b/src/main/java/nl/requios/effortlessbuilding/CommonConfig.java @@ -1,7 +1,6 @@ package nl.requios.effortlessbuilding; import net.minecraftforge.common.ForgeConfigSpec; -import nl.requios.effortlessbuilding.create.foundation.render.SuperByteBufferCache; import static net.minecraftforge.common.ForgeConfigSpec.*; diff --git a/src/main/java/nl/requios/effortlessbuilding/CommonEvents.java b/src/main/java/nl/requios/effortlessbuilding/CommonEvents.java index 8c2aacd..5e32073 100644 --- a/src/main/java/nl/requios/effortlessbuilding/CommonEvents.java +++ b/src/main/java/nl/requios/effortlessbuilding/CommonEvents.java @@ -103,7 +103,7 @@ public class CommonEvents { return; } - UndoRedo.clear(player); + EffortlessBuilding.UNDO_REDO.clear(player); } @SubscribeEvent @@ -129,7 +129,7 @@ public class CommonEvents { } //Undo redo has no dimension data, so clear it - UndoRedo.clear(player); + EffortlessBuilding.UNDO_REDO.clear(player); //TODO disable build mode and modifiers? } diff --git a/src/main/java/nl/requios/effortlessbuilding/EffortlessBuilding.java b/src/main/java/nl/requios/effortlessbuilding/EffortlessBuilding.java index e509e94..c3a7c85 100644 --- a/src/main/java/nl/requios/effortlessbuilding/EffortlessBuilding.java +++ b/src/main/java/nl/requios/effortlessbuilding/EffortlessBuilding.java @@ -28,6 +28,7 @@ import nl.requios.effortlessbuilding.proxy.ClientProxy; import nl.requios.effortlessbuilding.proxy.IProxy; import nl.requios.effortlessbuilding.proxy.ServerProxy; import nl.requios.effortlessbuilding.systems.ServerBlockPlacer; +import nl.requios.effortlessbuilding.systems.UndoRedo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -41,6 +42,7 @@ public class EffortlessBuilding { public static IProxy proxy = DistExecutor.unsafeRunForDist(() -> ClientProxy::new, () -> ServerProxy::new); public static final ServerBlockPlacer SERVER_BLOCK_PLACER = new ServerBlockPlacer(); + public static final UndoRedo UNDO_REDO = new UndoRedo(); //Registration private static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID); diff --git a/src/main/java/nl/requios/effortlessbuilding/ServerConfig.java b/src/main/java/nl/requios/effortlessbuilding/ServerConfig.java index 80eee5e..5dcc2f1 100644 --- a/src/main/java/nl/requios/effortlessbuilding/ServerConfig.java +++ b/src/main/java/nl/requios/effortlessbuilding/ServerConfig.java @@ -2,7 +2,6 @@ package nl.requios.effortlessbuilding; import net.minecraftforge.common.ForgeConfigSpec; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -45,7 +44,7 @@ public class ServerConfig { builder.push("Memory"); undoStackSize = builder - .comment("How many placements are remembered for the undo functionality.") + .comment("How many sets of blocks are remembered for the undo functionality, per player.") .worldRestart() .defineInRange("undoStackSize", 50, 10, 200); diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/ThreeClicksBuildMode.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/ThreeClicksBuildMode.java index a7ab600..c953d8d 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/ThreeClicksBuildMode.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/ThreeClicksBuildMode.java @@ -81,6 +81,7 @@ public abstract class ThreeClicksBuildMode extends BaseBuildMode { blocks.clear(); for (BlockPos pos : getIntermediateBlocks(player, x1, y1, z1, x2, y2, z2)) { + if (blocks.containsKey(pos)) continue; blocks.add(new BlockEntry(pos)); } blocks.firstPos = firstPos; @@ -116,6 +117,7 @@ public abstract class ThreeClicksBuildMode extends BaseBuildMode { blocks.clear(); for (BlockPos pos : getFinalBlocks(player, x1, y1, z1, x2, y2, z2, x3, y3, z3)) { + if (blocks.containsKey(pos)) continue; blocks.add(new BlockEntry(pos)); } blocks.firstPos = firstPos; diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/TwoClicksBuildMode.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/TwoClicksBuildMode.java index 171c512..5e7eb3c 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/TwoClicksBuildMode.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/TwoClicksBuildMode.java @@ -65,6 +65,7 @@ public abstract class TwoClicksBuildMode extends BaseBuildMode { blocks.clear(); for (BlockPos pos : getAllBlocks(player, x1, y1, z1, x2, y2, z2)) { + if (blocks.containsKey(pos)) continue; blocks.add(new BlockEntry(pos)); } blocks.firstPos = firstPos; diff --git a/src/main/java/nl/requios/effortlessbuilding/network/PerformRedoPacket.java b/src/main/java/nl/requios/effortlessbuilding/network/PerformRedoPacket.java index e5f5d87..f0a4b91 100644 --- a/src/main/java/nl/requios/effortlessbuilding/network/PerformRedoPacket.java +++ b/src/main/java/nl/requios/effortlessbuilding/network/PerformRedoPacket.java @@ -2,6 +2,7 @@ package nl.requios.effortlessbuilding.network; import net.minecraft.network.FriendlyByteBuf; import net.minecraftforge.network.NetworkEvent; +import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.systems.UndoRedo; import java.util.function.Supplier; @@ -19,7 +20,7 @@ public class PerformRedoPacket { public static class Handler { public static void handle(PerformRedoPacket message, Supplier ctx) { ctx.get().enqueueWork(() -> { - UndoRedo.redo(ctx.get().getSender()); + EffortlessBuilding.UNDO_REDO.redo(ctx.get().getSender()); }); ctx.get().setPacketHandled(true); } diff --git a/src/main/java/nl/requios/effortlessbuilding/network/PerformUndoPacket.java b/src/main/java/nl/requios/effortlessbuilding/network/PerformUndoPacket.java index 51e1eff..9d62f51 100644 --- a/src/main/java/nl/requios/effortlessbuilding/network/PerformUndoPacket.java +++ b/src/main/java/nl/requios/effortlessbuilding/network/PerformUndoPacket.java @@ -2,6 +2,7 @@ package nl.requios.effortlessbuilding.network; import net.minecraft.network.FriendlyByteBuf; import net.minecraftforge.network.NetworkEvent; +import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.systems.UndoRedo; import java.util.function.Supplier; @@ -19,7 +20,7 @@ public class PerformUndoPacket { public static class Handler { public static void handle(PerformUndoPacket message, Supplier ctx) { ctx.get().enqueueWork(() -> { - UndoRedo.undo(ctx.get().getSender()); + EffortlessBuilding.UNDO_REDO.undo(ctx.get().getSender()); }); ctx.get().setPacketHandled(true); } diff --git a/src/main/java/nl/requios/effortlessbuilding/render/ModifierRenderer.java b/src/main/java/nl/requios/effortlessbuilding/render/ModifierRenderer.java index 4e2643f..684d1d4 100644 --- a/src/main/java/nl/requios/effortlessbuilding/render/ModifierRenderer.java +++ b/src/main/java/nl/requios/effortlessbuilding/render/ModifierRenderer.java @@ -2,7 +2,6 @@ package nl.requios.effortlessbuilding.render; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; -import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.MultiBufferSource.BufferSource; import com.mojang.math.Matrix4f; import net.minecraft.world.phys.Vec3; diff --git a/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java b/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java index 828648a..1ec09db 100644 --- a/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java +++ b/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java @@ -9,7 +9,6 @@ import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.network.chat.Component; -import net.minecraft.world.entity.player.Player; import net.minecraft.world.phys.Vec3; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.RenderGuiEvent; @@ -28,7 +27,6 @@ public class RenderHandler { @SubscribeEvent public static void onRender(RenderLevelStageEvent event) { if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_TRANSLUCENT_BLOCKS) return; - Vec3 cameraPos = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition(); PoseStack ms = event.getPoseStack(); @@ -36,7 +34,7 @@ public class RenderHandler { MultiBufferSource.BufferSource buffer = MultiBufferSource.immediate(bufferBuilder); ms.pushPose(); - ms.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z); + ms.translate(-cameraPos.x(), -cameraPos.y(), -cameraPos.z()); //Mirror and radial mirror lines and areas ModifierRenderer.render(ms, buffer); diff --git a/src/main/java/nl/requios/effortlessbuilding/systems/BuilderChain.java b/src/main/java/nl/requios/effortlessbuilding/systems/BuilderChain.java index 002fea1..47d5067 100644 --- a/src/main/java/nl/requios/effortlessbuilding/systems/BuilderChain.java +++ b/src/main/java/nl/requios/effortlessbuilding/systems/BuilderChain.java @@ -19,7 +19,6 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import nl.requios.effortlessbuilding.ClientConfig; import nl.requios.effortlessbuilding.ClientEvents; -import nl.requios.effortlessbuilding.CommonConfig; import nl.requios.effortlessbuilding.EffortlessBuildingClient; import nl.requios.effortlessbuilding.buildmode.BuildModeEnum; import nl.requios.effortlessbuilding.compatibility.CompatHelper; @@ -238,11 +237,6 @@ public class BuilderChain { startPos = startPos.relative(lookingAt.getDirection()); } - //Get under tall grass and other replaceable blocks - if (shouldOffsetStartPosition && replaceable) { - startPos = startPos.below(); - } - } else { //We can only break diff --git a/src/main/java/nl/requios/effortlessbuilding/systems/ServerBlockPlacer.java b/src/main/java/nl/requios/effortlessbuilding/systems/ServerBlockPlacer.java index 5481ede..0888c1d 100644 --- a/src/main/java/nl/requios/effortlessbuilding/systems/ServerBlockPlacer.java +++ b/src/main/java/nl/requios/effortlessbuilding/systems/ServerBlockPlacer.java @@ -28,11 +28,13 @@ public class ServerBlockPlacer { } public void tick() { - for (DelayedEntry entry : delayedEntries) { + //Iterator to prevent concurrent modification exception + for (var iterator = delayedEntries.iterator(); iterator.hasNext(); ) { + DelayedEntry entry = iterator.next(); long gameTime = entry.player.level.getGameTime(); if (gameTime >= entry.placeTime) { placeBlocks(entry.player, entry.blocks); - delayedEntries.remove(entry); + iterator.remove(); } } } @@ -40,35 +42,44 @@ public class ServerBlockPlacer { public void placeBlocks(Player player, BlockSet blocks) { if (!checkAndNotifyAllowedToUseMod(player)) return; // EffortlessBuilding.log(player, "Placing " + blocks.size() + " blocks"); - + + var undoSet = new BlockSet(); for (BlockEntry block : blocks) { if (blocks.skipFirst && block.blockPos == blocks.firstPos) continue; - placeBlock(player, block); + if (placeBlock(player, block)) { + undoSet.add(block); + } } + EffortlessBuilding.UNDO_REDO.addUndo(player, undoSet); } - private void placeBlock(Player player, BlockEntry block) { + private boolean placeBlock(Player player, BlockEntry block) { Level world = player.level; - if (!world.isLoaded(block.blockPos)) return; + if (!world.isLoaded(block.blockPos)) return false; isPlacingOrBreakingBlocks = true; boolean placedBlock = BlockUtilities.placeBlockEntry(player, block) == InteractionResult.SUCCESS; isPlacingOrBreakingBlocks = false; + return placedBlock; } public void breakBlocks(Player player, BlockSet blocks) { if (!checkAndNotifyAllowedToUseMod(player)) return; // EffortlessBuilding.log(player, "Breaking " + blocks.size() + " blocks"); + var undoSet = new BlockSet(); for (BlockEntry block : blocks) { if (blocks.skipFirst && block.blockPos == blocks.firstPos) continue; - breakBlock(player, block); + if (breakBlock(player, block)) { + undoSet.add(block); + } } + EffortlessBuilding.UNDO_REDO.addUndo(player, undoSet); } - private void breakBlock(Player player, BlockEntry block) { + private boolean breakBlock(Player player, BlockEntry block) { ServerLevel world = (ServerLevel) player.level; - if (!world.isLoaded(block.blockPos) || world.isEmptyBlock(block.blockPos)) return; + if (!world.isLoaded(block.blockPos) || world.isEmptyBlock(block.blockPos)) return false; isPlacingOrBreakingBlocks = true; boolean brokeBlock = BlockHelper.destroyBlockAs(world, block.blockPos, player, player.getMainHandItem(), 0f, stack -> { @@ -77,9 +88,16 @@ public class ServerBlockPlacer { } }); isPlacingOrBreakingBlocks = false; + return brokeBlock; } public boolean checkAndNotifyAllowedToUseMod(Player player) { + //TODO TEMP + if (!player.isCreative()) { + EffortlessBuilding.log(player, ChatFormatting.RED + "Effortless Building is not yet supported in survival mode."); + return false; + } + if (!isAllowedToUseMod(player)) { EffortlessBuilding.log(player, ChatFormatting.RED + "You are not allowed to use Effortless Building."); return false; diff --git a/src/main/java/nl/requios/effortlessbuilding/systems/UndoRedo.java b/src/main/java/nl/requios/effortlessbuilding/systems/UndoRedo.java index baee0e8..e23c367 100644 --- a/src/main/java/nl/requios/effortlessbuilding/systems/UndoRedo.java +++ b/src/main/java/nl/requios/effortlessbuilding/systems/UndoRedo.java @@ -8,187 +8,234 @@ import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.ItemStack; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; -import nl.requios.effortlessbuilding.CommonConfig; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.ServerConfig; -import nl.requios.effortlessbuilding.utilities.UndoRedoBlockSet; -import nl.requios.effortlessbuilding.utilities.FixedStack; -import nl.requios.effortlessbuilding.utilities.InventoryHelper; -import nl.requios.effortlessbuilding.utilities.SurvivalHelper; +import nl.requios.effortlessbuilding.utilities.*; import java.util.*; //Server only public class UndoRedo { - //Undo and redo stacks per player - //Gets added to twice in singleplayer (server and client) if not careful. So separate stacks. - private static final Map> undoStacksClient = new HashMap<>(); - private static final Map> undoStacksServer = new HashMap<>(); - private static final Map> redoStacksClient = new HashMap<>(); - private static final Map> redoStacksServer = new HashMap<>(); + public final Map> undoStacks = new HashMap<>(); + public final Map> redoStacks = new HashMap<>(); - //add to undo stack - public static void addUndo(Player player, UndoRedoBlockSet blockSet) { - Map> undoStacks = player.level.isClientSide ? undoStacksClient : undoStacksServer; - - //Assert coordinates is as long as previous and new blockstate lists - if (blockSet.getCoordinates().size() != blockSet.getPreviousBlockStates().size() || - blockSet.getCoordinates().size() != blockSet.getNewBlockStates().size()) { - EffortlessBuilding.logger.error("Coordinates and blockstate lists are not equal length. Coordinates: {}. Previous blockstates: {}. New blockstates: {}.", - blockSet.getCoordinates().size(), blockSet.getPreviousBlockStates().size(), blockSet.getNewBlockStates().size()); - } - - //Warn if previous and new blockstate are equal - //Can happen in a lot of valid cases -// for (int i = 0; i < blockSet.getCoordinates().size(); i++) { -// if (blockSet.getPreviousBlockStates().get(i).equals(blockSet.getNewBlockStates().get(i))) { -// EffortlessBuilding.logger.warn("Previous and new blockstates are equal at index {}. Blockstate: {}.", -// i, blockSet.getPreviousBlockStates().get(i)); -// } -// } + public void addUndo(Player player, BlockSet blockSet) { + if (blockSet.isEmpty()) return; //If no stack exists, make one if (!undoStacks.containsKey(player.getUUID())) { - undoStacks.put(player.getUUID(), new FixedStack<>(new UndoRedoBlockSet[ServerConfig.memory.undoStackSize.get()])); + undoStacks.put(player.getUUID(), new FixedStack<>(new BlockSet[ServerConfig.memory.undoStackSize.get()])); } undoStacks.get(player.getUUID()).push(blockSet); } - private static void addRedo(Player player, UndoRedoBlockSet blockSet) { - Map> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer; - - //(No asserts necessary, it's private) + private void addRedo(Player player, BlockSet blockSet) { + if (blockSet.isEmpty()) return; //If no stack exists, make one if (!redoStacks.containsKey(player.getUUID())) { - redoStacks.put(player.getUUID(), new FixedStack<>(new UndoRedoBlockSet[ServerConfig.memory.undoStackSize.get()])); + redoStacks.put(player.getUUID(), new FixedStack<>(new BlockSet[ServerConfig.memory.undoStackSize.get()])); } redoStacks.get(player.getUUID()).push(blockSet); } - public static boolean undo(Player player) { - Map> undoStacks = player.level.isClientSide ? undoStacksClient : undoStacksServer; - + public boolean undo(Player player) { if (!undoStacks.containsKey(player.getUUID())) return false; - FixedStack undoStack = undoStacks.get(player.getUUID()); - + FixedStack undoStack = undoStacks.get(player.getUUID()); if (undoStack.isEmpty()) return false; - UndoRedoBlockSet blockSet = undoStack.pop(); - List coordinates = blockSet.getCoordinates(); - List previousBlockStates = blockSet.getPreviousBlockStates(); - List newBlockStates = blockSet.getNewBlockStates(); + BlockSet blockSet = undoStack.pop(); +// blockSet.undo(player.level); - //Find up to date itemstacks in player inventory - List itemStacks = findItemStacksInInventory(player, previousBlockStates); - - if (player.level.isClientSide) { -// BlockPreviews.onBlocksBroken(coordinates, itemStacks, newBlockStates, blockSet.getSecondPos(), blockSet.getFirstPos()); - } else { - //break all those blocks, reset to what they were - for (int i = 0; i < coordinates.size(); i++) { - BlockPos coordinate = coordinates.get(i); - ItemStack itemStack = itemStacks.get(i); - - if (previousBlockStates.get(i).equals(newBlockStates.get(i))) continue; - - //get blockstate from itemstack - BlockState previousBlockState = previousBlockStates.get(i); - if (itemStack.getItem() instanceof BlockItem) { - previousBlockState = ((BlockItem) itemStack.getItem()).getBlock().defaultBlockState(); - } - - if (player.level.isLoaded(coordinate)) { - //check itemstack empty - if (itemStack.isEmpty() && !player.isCreative()) { - itemStack = findItemStackInInventory(player, previousBlockStates.get(i)); - //get blockstate from new itemstack - if (!itemStack.isEmpty() && itemStack.getItem() instanceof BlockItem) { - previousBlockState = ((BlockItem) itemStack.getItem()).getBlock().defaultBlockState(); - } else { - if (previousBlockStates.get(i).getBlock() != Blocks.AIR) - EffortlessBuilding.logTranslate(player, "", previousBlockStates.get(i).getBlock().getDescriptionId(), " not found in inventory", true); - previousBlockState = Blocks.AIR.defaultBlockState(); - } - } - if (itemStack.isEmpty()) SurvivalHelper.breakBlock(player.level, player, coordinate, true); - //if previousBlockState is air, placeBlock will set it to air - SurvivalHelper.placeBlock(player.level, player, coordinate, previousBlockState, itemStack, true, false, false); - } - } - } - - //add to redo - addRedo(player, blockSet); + BlockSet redoSet = new BlockSet(); + addRedo(player, redoSet); return true; } - public static boolean redo(Player player) { - Map> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer; - + public boolean redo(Player player) { if (!redoStacks.containsKey(player.getUUID())) return false; - FixedStack redoStack = redoStacks.get(player.getUUID()); - + FixedStack redoStack = redoStacks.get(player.getUUID()); if (redoStack.isEmpty()) return false; - UndoRedoBlockSet blockSet = redoStack.pop(); - List coordinates = blockSet.getCoordinates(); - List previousBlockStates = blockSet.getPreviousBlockStates(); - List newBlockStates = blockSet.getNewBlockStates(); - - //Find up to date itemstacks in player inventory - List itemStacks = findItemStacksInInventory(player, newBlockStates); - - if (player.level.isClientSide) { -// BlockPreviews.onBlocksPlaced(coordinates, itemStacks, newBlockStates, blockSet.getFirstPos(), blockSet.getSecondPos()); - } else { - //place blocks - for (int i = 0; i < coordinates.size(); i++) { - BlockPos coordinate = coordinates.get(i); - ItemStack itemStack = itemStacks.get(i); - - if (previousBlockStates.get(i).equals(newBlockStates.get(i))) continue; - - //get blockstate from itemstack - BlockState newBlockState = newBlockStates.get(i); - if (itemStack.getItem() instanceof BlockItem) { - newBlockState = ((BlockItem) itemStack.getItem()).getBlock().defaultBlockState(); - } - - if (player.level.isLoaded(coordinate)) { - //check itemstack empty - if (itemStack.isEmpty() && !player.isCreative()) { - itemStack = findItemStackInInventory(player, newBlockStates.get(i)); - //get blockstate from new itemstack - if (!itemStack.isEmpty() && itemStack.getItem() instanceof BlockItem) { - newBlockState = ((BlockItem) itemStack.getItem()).getBlock().defaultBlockState(); - } else { - if (newBlockStates.get(i).getBlock() != Blocks.AIR) - EffortlessBuilding.logTranslate(player, "", newBlockStates.get(i).getBlock().getDescriptionId(), " not found in inventory", true); - newBlockState = Blocks.AIR.defaultBlockState(); - } - } - if (itemStack.isEmpty()) SurvivalHelper.breakBlock(player.level, player, coordinate, true); - SurvivalHelper.placeBlock(player.level, player, coordinate, newBlockState, itemStack, true, false, false); - } - } - } - - //add to undo + BlockSet blockSet = redoStack.pop(); +// blockSet.redo(player.level); addUndo(player, blockSet); return true; } - public static void clear(Player player) { - Map> undoStacks = player.level.isClientSide ? undoStacksClient : undoStacksServer; - Map> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer; + //Undo and redo stacks per player + //Gets added to twice in singleplayer (server and client) if not careful. So separate stacks. +// private static final Map> undoStacksClient = new HashMap<>(); +// private static final Map> undoStacksServer = new HashMap<>(); +// private static final Map> redoStacksClient = new HashMap<>(); +// private static final Map> redoStacksServer = new HashMap<>(); +// +// //add to undo stack +// public static void addUndo(Player player, UndoRedoBlockSet blockSet) { +// Map> undoStacks = player.level.isClientSide ? undoStacksClient : undoStacksServer; +// +// //Assert coordinates is as long as previous and new blockstate lists +// if (blockSet.getCoordinates().size() != blockSet.getPreviousBlockStates().size() || +// blockSet.getCoordinates().size() != blockSet.getNewBlockStates().size()) { +// EffortlessBuilding.logger.error("Coordinates and blockstate lists are not equal length. Coordinates: {}. Previous blockstates: {}. New blockstates: {}.", +// blockSet.getCoordinates().size(), blockSet.getPreviousBlockStates().size(), blockSet.getNewBlockStates().size()); +// } +// +// //Warn if previous and new blockstate are equal +// //Can happen in a lot of valid cases +//// for (int i = 0; i < blockSet.getCoordinates().size(); i++) { +//// if (blockSet.getPreviousBlockStates().get(i).equals(blockSet.getNewBlockStates().get(i))) { +//// EffortlessBuilding.logger.warn("Previous and new blockstates are equal at index {}. Blockstate: {}.", +//// i, blockSet.getPreviousBlockStates().get(i)); +//// } +//// } +// +// //If no stack exists, make one +// if (!undoStacks.containsKey(player.getUUID())) { +// undoStacks.put(player.getUUID(), new FixedStack<>(new UndoRedoBlockSet[ServerConfig.memory.undoStackSize.get()])); +// } +// +// undoStacks.get(player.getUUID()).push(blockSet); +// } +// +// private static void addRedo(Player player, UndoRedoBlockSet blockSet) { +// Map> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer; +// +// //(No asserts necessary, it's private) +// +// //If no stack exists, make one +// if (!redoStacks.containsKey(player.getUUID())) { +// redoStacks.put(player.getUUID(), new FixedStack<>(new UndoRedoBlockSet[ServerConfig.memory.undoStackSize.get()])); +// } +// +// redoStacks.get(player.getUUID()).push(blockSet); +// } +// +// public static boolean undo(Player player) { +// Map> undoStacks = player.level.isClientSide ? undoStacksClient : undoStacksServer; +// +// if (!undoStacks.containsKey(player.getUUID())) return false; +// +// FixedStack undoStack = undoStacks.get(player.getUUID()); +// +// if (undoStack.isEmpty()) return false; +// +// UndoRedoBlockSet blockSet = undoStack.pop(); +// List coordinates = blockSet.getCoordinates(); +// List previousBlockStates = blockSet.getPreviousBlockStates(); +// List newBlockStates = blockSet.getNewBlockStates(); +// +// //Find up to date itemstacks in player inventory +// List itemStacks = findItemStacksInInventory(player, previousBlockStates); +// +// if (player.level.isClientSide) { +//// BlockPreviews.onBlocksBroken(coordinates, itemStacks, newBlockStates, blockSet.getSecondPos(), blockSet.getFirstPos()); +// } else { +// //break all those blocks, reset to what they were +// for (int i = 0; i < coordinates.size(); i++) { +// BlockPos coordinate = coordinates.get(i); +// ItemStack itemStack = itemStacks.get(i); +// +// if (previousBlockStates.get(i).equals(newBlockStates.get(i))) continue; +// +// //get blockstate from itemstack +// BlockState previousBlockState = previousBlockStates.get(i); +// if (itemStack.getItem() instanceof BlockItem) { +// previousBlockState = ((BlockItem) itemStack.getItem()).getBlock().defaultBlockState(); +// } +// +// if (player.level.isLoaded(coordinate)) { +// //check itemstack empty +// if (itemStack.isEmpty() && !player.isCreative()) { +// itemStack = findItemStackInInventory(player, previousBlockStates.get(i)); +// //get blockstate from new itemstack +// if (!itemStack.isEmpty() && itemStack.getItem() instanceof BlockItem) { +// previousBlockState = ((BlockItem) itemStack.getItem()).getBlock().defaultBlockState(); +// } else { +// if (previousBlockStates.get(i).getBlock() != Blocks.AIR) +// EffortlessBuilding.logTranslate(player, "", previousBlockStates.get(i).getBlock().getDescriptionId(), " not found in inventory", true); +// previousBlockState = Blocks.AIR.defaultBlockState(); +// } +// } +// if (itemStack.isEmpty()) SurvivalHelper.breakBlock(player.level, player, coordinate, true); +// //if previousBlockState is air, placeBlock will set it to air +// SurvivalHelper.placeBlock(player.level, player, coordinate, previousBlockState, itemStack, true, false, false); +// } +// } +// } +// +// //add to redo +// addRedo(player, blockSet); +// +// return true; +// } +// +// public static boolean redo(Player player) { +// Map> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer; +// +// if (!redoStacks.containsKey(player.getUUID())) return false; +// +// FixedStack redoStack = redoStacks.get(player.getUUID()); +// +// if (redoStack.isEmpty()) return false; +// +// UndoRedoBlockSet blockSet = redoStack.pop(); +// List coordinates = blockSet.getCoordinates(); +// List previousBlockStates = blockSet.getPreviousBlockStates(); +// List newBlockStates = blockSet.getNewBlockStates(); +// +// //Find up to date itemstacks in player inventory +// List itemStacks = findItemStacksInInventory(player, newBlockStates); +// +// if (player.level.isClientSide) { +//// BlockPreviews.onBlocksPlaced(coordinates, itemStacks, newBlockStates, blockSet.getFirstPos(), blockSet.getSecondPos()); +// } else { +// //place blocks +// for (int i = 0; i < coordinates.size(); i++) { +// BlockPos coordinate = coordinates.get(i); +// ItemStack itemStack = itemStacks.get(i); +// +// if (previousBlockStates.get(i).equals(newBlockStates.get(i))) continue; +// +// //get blockstate from itemstack +// BlockState newBlockState = newBlockStates.get(i); +// if (itemStack.getItem() instanceof BlockItem) { +// newBlockState = ((BlockItem) itemStack.getItem()).getBlock().defaultBlockState(); +// } +// +// if (player.level.isLoaded(coordinate)) { +// //check itemstack empty +// if (itemStack.isEmpty() && !player.isCreative()) { +// itemStack = findItemStackInInventory(player, newBlockStates.get(i)); +// //get blockstate from new itemstack +// if (!itemStack.isEmpty() && itemStack.getItem() instanceof BlockItem) { +// newBlockState = ((BlockItem) itemStack.getItem()).getBlock().defaultBlockState(); +// } else { +// if (newBlockStates.get(i).getBlock() != Blocks.AIR) +// EffortlessBuilding.logTranslate(player, "", newBlockStates.get(i).getBlock().getDescriptionId(), " not found in inventory", true); +// newBlockState = Blocks.AIR.defaultBlockState(); +// } +// } +// if (itemStack.isEmpty()) SurvivalHelper.breakBlock(player.level, player, coordinate, true); +// SurvivalHelper.placeBlock(player.level, player, coordinate, newBlockState, itemStack, true, false, false); +// } +// } +// } +// +// //add to undo +// addUndo(player, blockSet); +// +// return true; +// } + + public void clear(Player player) { if (undoStacks.containsKey(player.getUUID())) { undoStacks.get(player.getUUID()).clear(); } @@ -197,7 +244,7 @@ public class UndoRedo { } } - private static List findItemStacksInInventory(Player player, List blockStates) { + private List findItemStacksInInventory(Player player, List blockStates) { List itemStacks = new ArrayList<>(blockStates.size()); for (BlockState blockState : blockStates) { itemStacks.add(findItemStackInInventory(player, blockState)); @@ -205,7 +252,7 @@ public class UndoRedo { return itemStacks; } - private static ItemStack findItemStackInInventory(Player player, BlockState blockState) { + private ItemStack findItemStackInInventory(Player player, BlockState blockState) { ItemStack itemStack = ItemStack.EMPTY; if (blockState == null) return itemStack;