WIP UndoRedo overhaul.

Fixed concurrent modification issue in ServerBlockPlacer.
This commit is contained in:
Christian Knaapen
2023-02-04 00:30:30 +01:00
parent bda0908731
commit 23b8c0c9fc
13 changed files with 227 additions and 166 deletions

View File

@@ -1,7 +1,6 @@
package nl.requios.effortlessbuilding; package nl.requios.effortlessbuilding;
import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.common.ForgeConfigSpec;
import nl.requios.effortlessbuilding.create.foundation.render.SuperByteBufferCache;
import static net.minecraftforge.common.ForgeConfigSpec.*; import static net.minecraftforge.common.ForgeConfigSpec.*;

View File

@@ -103,7 +103,7 @@ public class CommonEvents {
return; return;
} }
UndoRedo.clear(player); EffortlessBuilding.UNDO_REDO.clear(player);
} }
@SubscribeEvent @SubscribeEvent
@@ -129,7 +129,7 @@ public class CommonEvents {
} }
//Undo redo has no dimension data, so clear it //Undo redo has no dimension data, so clear it
UndoRedo.clear(player); EffortlessBuilding.UNDO_REDO.clear(player);
//TODO disable build mode and modifiers? //TODO disable build mode and modifiers?
} }

View File

@@ -28,6 +28,7 @@ import nl.requios.effortlessbuilding.proxy.ClientProxy;
import nl.requios.effortlessbuilding.proxy.IProxy; import nl.requios.effortlessbuilding.proxy.IProxy;
import nl.requios.effortlessbuilding.proxy.ServerProxy; import nl.requios.effortlessbuilding.proxy.ServerProxy;
import nl.requios.effortlessbuilding.systems.ServerBlockPlacer; import nl.requios.effortlessbuilding.systems.ServerBlockPlacer;
import nl.requios.effortlessbuilding.systems.UndoRedo;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; 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 IProxy proxy = DistExecutor.unsafeRunForDist(() -> ClientProxy::new, () -> ServerProxy::new);
public static final ServerBlockPlacer SERVER_BLOCK_PLACER = new ServerBlockPlacer(); public static final ServerBlockPlacer SERVER_BLOCK_PLACER = new ServerBlockPlacer();
public static final UndoRedo UNDO_REDO = new UndoRedo();
//Registration //Registration
private static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID); private static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID);

View File

@@ -2,7 +2,6 @@ package nl.requios.effortlessbuilding;
import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.common.ForgeConfigSpec;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -45,7 +44,7 @@ public class ServerConfig {
builder.push("Memory"); builder.push("Memory");
undoStackSize = builder 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() .worldRestart()
.defineInRange("undoStackSize", 50, 10, 200); .defineInRange("undoStackSize", 50, 10, 200);

View File

@@ -81,6 +81,7 @@ public abstract class ThreeClicksBuildMode extends BaseBuildMode {
blocks.clear(); blocks.clear();
for (BlockPos pos : getIntermediateBlocks(player, x1, y1, z1, x2, y2, z2)) { for (BlockPos pos : getIntermediateBlocks(player, x1, y1, z1, x2, y2, z2)) {
if (blocks.containsKey(pos)) continue;
blocks.add(new BlockEntry(pos)); blocks.add(new BlockEntry(pos));
} }
blocks.firstPos = firstPos; blocks.firstPos = firstPos;
@@ -116,6 +117,7 @@ public abstract class ThreeClicksBuildMode extends BaseBuildMode {
blocks.clear(); blocks.clear();
for (BlockPos pos : getFinalBlocks(player, x1, y1, z1, x2, y2, z2, x3, y3, z3)) { 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.add(new BlockEntry(pos));
} }
blocks.firstPos = firstPos; blocks.firstPos = firstPos;

View File

@@ -65,6 +65,7 @@ public abstract class TwoClicksBuildMode extends BaseBuildMode {
blocks.clear(); blocks.clear();
for (BlockPos pos : getAllBlocks(player, x1, y1, z1, x2, y2, z2)) { for (BlockPos pos : getAllBlocks(player, x1, y1, z1, x2, y2, z2)) {
if (blocks.containsKey(pos)) continue;
blocks.add(new BlockEntry(pos)); blocks.add(new BlockEntry(pos));
} }
blocks.firstPos = firstPos; blocks.firstPos = firstPos;

View File

@@ -2,6 +2,7 @@ package nl.requios.effortlessbuilding.network;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.systems.UndoRedo; import nl.requios.effortlessbuilding.systems.UndoRedo;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -19,7 +20,7 @@ public class PerformRedoPacket {
public static class Handler { public static class Handler {
public static void handle(PerformRedoPacket message, Supplier<NetworkEvent.Context> ctx) { public static void handle(PerformRedoPacket message, Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> { ctx.get().enqueueWork(() -> {
UndoRedo.redo(ctx.get().getSender()); EffortlessBuilding.UNDO_REDO.redo(ctx.get().getSender());
}); });
ctx.get().setPacketHandled(true); ctx.get().setPacketHandled(true);
} }

View File

@@ -2,6 +2,7 @@ package nl.requios.effortlessbuilding.network;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.systems.UndoRedo; import nl.requios.effortlessbuilding.systems.UndoRedo;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -19,7 +20,7 @@ public class PerformUndoPacket {
public static class Handler { public static class Handler {
public static void handle(PerformUndoPacket message, Supplier<NetworkEvent.Context> ctx) { public static void handle(PerformUndoPacket message, Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> { ctx.get().enqueueWork(() -> {
UndoRedo.undo(ctx.get().getSender()); EffortlessBuilding.UNDO_REDO.undo(ctx.get().getSender());
}); });
ctx.get().setPacketHandled(true); ctx.get().setPacketHandled(true);
} }

View File

@@ -2,7 +2,6 @@ package nl.requios.effortlessbuilding.render;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource.BufferSource; import net.minecraft.client.renderer.MultiBufferSource.BufferSource;
import com.mojang.math.Matrix4f; import com.mojang.math.Matrix4f;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;

View File

@@ -9,7 +9,6 @@ import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.RenderGuiEvent; import net.minecraftforge.client.event.RenderGuiEvent;
@@ -28,7 +27,6 @@ public class RenderHandler {
@SubscribeEvent @SubscribeEvent
public static void onRender(RenderLevelStageEvent event) { public static void onRender(RenderLevelStageEvent event) {
if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_TRANSLUCENT_BLOCKS) return; if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_TRANSLUCENT_BLOCKS) return;
Vec3 cameraPos = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition(); Vec3 cameraPos = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition();
PoseStack ms = event.getPoseStack(); PoseStack ms = event.getPoseStack();
@@ -36,7 +34,7 @@ public class RenderHandler {
MultiBufferSource.BufferSource buffer = MultiBufferSource.immediate(bufferBuilder); MultiBufferSource.BufferSource buffer = MultiBufferSource.immediate(bufferBuilder);
ms.pushPose(); 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 //Mirror and radial mirror lines and areas
ModifierRenderer.render(ms, buffer); ModifierRenderer.render(ms, buffer);

View File

@@ -19,7 +19,6 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import nl.requios.effortlessbuilding.ClientConfig; import nl.requios.effortlessbuilding.ClientConfig;
import nl.requios.effortlessbuilding.ClientEvents; import nl.requios.effortlessbuilding.ClientEvents;
import nl.requios.effortlessbuilding.CommonConfig;
import nl.requios.effortlessbuilding.EffortlessBuildingClient; import nl.requios.effortlessbuilding.EffortlessBuildingClient;
import nl.requios.effortlessbuilding.buildmode.BuildModeEnum; import nl.requios.effortlessbuilding.buildmode.BuildModeEnum;
import nl.requios.effortlessbuilding.compatibility.CompatHelper; import nl.requios.effortlessbuilding.compatibility.CompatHelper;
@@ -238,11 +237,6 @@ public class BuilderChain {
startPos = startPos.relative(lookingAt.getDirection()); startPos = startPos.relative(lookingAt.getDirection());
} }
//Get under tall grass and other replaceable blocks
if (shouldOffsetStartPosition && replaceable) {
startPos = startPos.below();
}
} else { } else {
//We can only break //We can only break

View File

@@ -28,11 +28,13 @@ public class ServerBlockPlacer {
} }
public void tick() { 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(); long gameTime = entry.player.level.getGameTime();
if (gameTime >= entry.placeTime) { if (gameTime >= entry.placeTime) {
placeBlocks(entry.player, entry.blocks); placeBlocks(entry.player, entry.blocks);
delayedEntries.remove(entry); iterator.remove();
} }
} }
} }
@@ -40,35 +42,44 @@ public class ServerBlockPlacer {
public void placeBlocks(Player player, BlockSet blocks) { public void placeBlocks(Player player, BlockSet blocks) {
if (!checkAndNotifyAllowedToUseMod(player)) return; if (!checkAndNotifyAllowedToUseMod(player)) return;
// EffortlessBuilding.log(player, "Placing " + blocks.size() + " blocks"); // EffortlessBuilding.log(player, "Placing " + blocks.size() + " blocks");
var undoSet = new BlockSet();
for (BlockEntry block : blocks) { for (BlockEntry block : blocks) {
if (blocks.skipFirst && block.blockPos == blocks.firstPos) continue; 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; Level world = player.level;
if (!world.isLoaded(block.blockPos)) return; if (!world.isLoaded(block.blockPos)) return false;
isPlacingOrBreakingBlocks = true; isPlacingOrBreakingBlocks = true;
boolean placedBlock = BlockUtilities.placeBlockEntry(player, block) == InteractionResult.SUCCESS; boolean placedBlock = BlockUtilities.placeBlockEntry(player, block) == InteractionResult.SUCCESS;
isPlacingOrBreakingBlocks = false; isPlacingOrBreakingBlocks = false;
return placedBlock;
} }
public void breakBlocks(Player player, BlockSet blocks) { public void breakBlocks(Player player, BlockSet blocks) {
if (!checkAndNotifyAllowedToUseMod(player)) return; if (!checkAndNotifyAllowedToUseMod(player)) return;
// EffortlessBuilding.log(player, "Breaking " + blocks.size() + " blocks"); // EffortlessBuilding.log(player, "Breaking " + blocks.size() + " blocks");
var undoSet = new BlockSet();
for (BlockEntry block : blocks) { for (BlockEntry block : blocks) {
if (blocks.skipFirst && block.blockPos == blocks.firstPos) continue; 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; 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; isPlacingOrBreakingBlocks = true;
boolean brokeBlock = BlockHelper.destroyBlockAs(world, block.blockPos, player, player.getMainHandItem(), 0f, stack -> { boolean brokeBlock = BlockHelper.destroyBlockAs(world, block.blockPos, player, player.getMainHandItem(), 0f, stack -> {
@@ -77,9 +88,16 @@ public class ServerBlockPlacer {
} }
}); });
isPlacingOrBreakingBlocks = false; isPlacingOrBreakingBlocks = false;
return brokeBlock;
} }
public boolean checkAndNotifyAllowedToUseMod(Player player) { 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)) { if (!isAllowedToUseMod(player)) {
EffortlessBuilding.log(player, ChatFormatting.RED + "You are not allowed to use Effortless Building."); EffortlessBuilding.log(player, ChatFormatting.RED + "You are not allowed to use Effortless Building.");
return false; return false;

View File

@@ -8,187 +8,234 @@ import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import nl.requios.effortlessbuilding.CommonConfig;
import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.EffortlessBuilding;
import nl.requios.effortlessbuilding.ServerConfig; import nl.requios.effortlessbuilding.ServerConfig;
import nl.requios.effortlessbuilding.utilities.UndoRedoBlockSet; import nl.requios.effortlessbuilding.utilities.*;
import nl.requios.effortlessbuilding.utilities.FixedStack;
import nl.requios.effortlessbuilding.utilities.InventoryHelper;
import nl.requios.effortlessbuilding.utilities.SurvivalHelper;
import java.util.*; import java.util.*;
//Server only //Server only
public class UndoRedo { public class UndoRedo {
//Undo and redo stacks per player public final Map<UUID, FixedStack<BlockSet>> undoStacks = new HashMap<>();
//Gets added to twice in singleplayer (server and client) if not careful. So separate stacks. public final Map<UUID, FixedStack<BlockSet>> redoStacks = new HashMap<>();
private static final Map<UUID, FixedStack<UndoRedoBlockSet>> undoStacksClient = new HashMap<>();
private static final Map<UUID, FixedStack<UndoRedoBlockSet>> undoStacksServer = new HashMap<>();
private static final Map<UUID, FixedStack<UndoRedoBlockSet>> redoStacksClient = new HashMap<>();
private static final Map<UUID, FixedStack<UndoRedoBlockSet>> redoStacksServer = new HashMap<>();
//add to undo stack public void addUndo(Player player, BlockSet blockSet) {
public static void addUndo(Player player, UndoRedoBlockSet blockSet) { if (blockSet.isEmpty()) return;
Map<UUID, FixedStack<UndoRedoBlockSet>> 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 no stack exists, make one
if (!undoStacks.containsKey(player.getUUID())) { 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); undoStacks.get(player.getUUID()).push(blockSet);
} }
private static void addRedo(Player player, UndoRedoBlockSet blockSet) { private void addRedo(Player player, BlockSet blockSet) {
Map<UUID, FixedStack<UndoRedoBlockSet>> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer; if (blockSet.isEmpty()) return;
//(No asserts necessary, it's private)
//If no stack exists, make one //If no stack exists, make one
if (!redoStacks.containsKey(player.getUUID())) { 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); redoStacks.get(player.getUUID()).push(blockSet);
} }
public static boolean undo(Player player) { public boolean undo(Player player) {
Map<UUID, FixedStack<UndoRedoBlockSet>> undoStacks = player.level.isClientSide ? undoStacksClient : undoStacksServer;
if (!undoStacks.containsKey(player.getUUID())) return false; if (!undoStacks.containsKey(player.getUUID())) return false;
FixedStack<UndoRedoBlockSet> undoStack = undoStacks.get(player.getUUID()); FixedStack<BlockSet> undoStack = undoStacks.get(player.getUUID());
if (undoStack.isEmpty()) return false; if (undoStack.isEmpty()) return false;
UndoRedoBlockSet blockSet = undoStack.pop(); BlockSet blockSet = undoStack.pop();
List<BlockPos> coordinates = blockSet.getCoordinates(); // blockSet.undo(player.level);
List<BlockState> previousBlockStates = blockSet.getPreviousBlockStates();
List<BlockState> newBlockStates = blockSet.getNewBlockStates();
//Find up to date itemstacks in player inventory BlockSet redoSet = new BlockSet();
List<ItemStack> itemStacks = findItemStacksInInventory(player, previousBlockStates); addRedo(player, redoSet);
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; return true;
} }
public static boolean redo(Player player) { public boolean redo(Player player) {
Map<UUID, FixedStack<UndoRedoBlockSet>> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer;
if (!redoStacks.containsKey(player.getUUID())) return false; if (!redoStacks.containsKey(player.getUUID())) return false;
FixedStack<UndoRedoBlockSet> redoStack = redoStacks.get(player.getUUID()); FixedStack<BlockSet> redoStack = redoStacks.get(player.getUUID());
if (redoStack.isEmpty()) return false; if (redoStack.isEmpty()) return false;
UndoRedoBlockSet blockSet = redoStack.pop(); BlockSet blockSet = redoStack.pop();
List<BlockPos> coordinates = blockSet.getCoordinates(); // blockSet.redo(player.level);
List<BlockState> previousBlockStates = blockSet.getPreviousBlockStates();
List<BlockState> newBlockStates = blockSet.getNewBlockStates();
//Find up to date itemstacks in player inventory
List<ItemStack> 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); addUndo(player, blockSet);
return true; return true;
} }
public static void clear(Player player) { //Undo and redo stacks per player
Map<UUID, FixedStack<UndoRedoBlockSet>> undoStacks = player.level.isClientSide ? undoStacksClient : undoStacksServer; //Gets added to twice in singleplayer (server and client) if not careful. So separate stacks.
Map<UUID, FixedStack<UndoRedoBlockSet>> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer; // private static final Map<UUID, FixedStack<UndoRedoBlockSet>> undoStacksClient = new HashMap<>();
// private static final Map<UUID, FixedStack<UndoRedoBlockSet>> undoStacksServer = new HashMap<>();
// private static final Map<UUID, FixedStack<UndoRedoBlockSet>> redoStacksClient = new HashMap<>();
// private static final Map<UUID, FixedStack<UndoRedoBlockSet>> redoStacksServer = new HashMap<>();
//
// //add to undo stack
// public static void addUndo(Player player, UndoRedoBlockSet blockSet) {
// Map<UUID, FixedStack<UndoRedoBlockSet>> 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<UUID, FixedStack<UndoRedoBlockSet>> 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<UUID, FixedStack<UndoRedoBlockSet>> undoStacks = player.level.isClientSide ? undoStacksClient : undoStacksServer;
//
// if (!undoStacks.containsKey(player.getUUID())) return false;
//
// FixedStack<UndoRedoBlockSet> undoStack = undoStacks.get(player.getUUID());
//
// if (undoStack.isEmpty()) return false;
//
// UndoRedoBlockSet blockSet = undoStack.pop();
// List<BlockPos> coordinates = blockSet.getCoordinates();
// List<BlockState> previousBlockStates = blockSet.getPreviousBlockStates();
// List<BlockState> newBlockStates = blockSet.getNewBlockStates();
//
// //Find up to date itemstacks in player inventory
// List<ItemStack> 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<UUID, FixedStack<UndoRedoBlockSet>> redoStacks = player.level.isClientSide ? redoStacksClient : redoStacksServer;
//
// if (!redoStacks.containsKey(player.getUUID())) return false;
//
// FixedStack<UndoRedoBlockSet> redoStack = redoStacks.get(player.getUUID());
//
// if (redoStack.isEmpty()) return false;
//
// UndoRedoBlockSet blockSet = redoStack.pop();
// List<BlockPos> coordinates = blockSet.getCoordinates();
// List<BlockState> previousBlockStates = blockSet.getPreviousBlockStates();
// List<BlockState> newBlockStates = blockSet.getNewBlockStates();
//
// //Find up to date itemstacks in player inventory
// List<ItemStack> 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())) { if (undoStacks.containsKey(player.getUUID())) {
undoStacks.get(player.getUUID()).clear(); undoStacks.get(player.getUUID()).clear();
} }
@@ -197,7 +244,7 @@ public class UndoRedo {
} }
} }
private static List<ItemStack> findItemStacksInInventory(Player player, List<BlockState> blockStates) { private List<ItemStack> findItemStacksInInventory(Player player, List<BlockState> blockStates) {
List<ItemStack> itemStacks = new ArrayList<>(blockStates.size()); List<ItemStack> itemStacks = new ArrayList<>(blockStates.size());
for (BlockState blockState : blockStates) { for (BlockState blockState : blockStates) {
itemStacks.add(findItemStackInInventory(player, blockState)); itemStacks.add(findItemStackInInventory(player, blockState));
@@ -205,7 +252,7 @@ public class UndoRedo {
return itemStacks; return itemStacks;
} }
private static ItemStack findItemStackInInventory(Player player, BlockState blockState) { private ItemStack findItemStackInInventory(Player player, BlockState blockState) {
ItemStack itemStack = ItemStack.EMPTY; ItemStack itemStack = ItemStack.EMPTY;
if (blockState == null) return itemStack; if (blockState == null) return itemStack;