From 4ea34fc854ed22107c6ce981a4321db714363e9d Mon Sep 17 00:00:00 2001 From: Christian Knaapen Date: Tue, 24 Jan 2023 02:06:43 +0100 Subject: [PATCH] Added buttons in radial menu for replace modes. Implemented/fixed replace modes. Made ModeOptions clientside and UndoRedo serverside. New config option: only show previews when building, default true. Overhauled drawOutlinesIfNoBlockInHand, now drawOutlineAtBreakPosition. Fixed not being able to break blocks. --- .../effortlessbuilding/ClientConfig.java | 12 +- .../effortlessbuilding/ClientEvents.java | 47 ++--- .../effortlessbuilding/CommonConfig.java | 2 +- .../effortlessbuilding/CommonEvents.java | 29 +-- .../buildmode/ModeOptions.java | 106 ++++------ .../buildmode/ThreeClicksBuildMode.java | 9 +- .../buildmode/TwoClicksBuildMode.java | 8 +- .../buildmodifier/BuildModifiers.java | 3 +- .../gui/buildmode/RadialMenu.java | 96 ++++++--- .../buildmodifier/ModifierSettingsGui.java | 2 +- .../network/AddUndoMessage.java | 99 --------- .../network/ClearUndoMessage.java | 53 ----- .../network/ModeActionMessage.java | 45 ---- .../network/PacketHandler.java | 10 +- .../network/PerformRedoPacket.java | 27 +++ .../network/PerformUndoPacket.java | 27 +++ .../render/BlockPreviews.java | 91 ++++---- .../render/RenderHandler.java | 8 +- .../systems/BuildSettings.java | 57 +++-- .../systems/BuilderChain.java | 199 ++++++++++++------ .../systems/BuilderFilter.java | 18 +- .../systems/DelayedBlockPlacer.java | 3 +- .../systems/ServerBlockPlacer.java | 4 +- .../systems/ServerBuildState.java | 5 + .../{buildmodifier => systems}/UndoRedo.java | 4 +- .../utilities/BlockSet.java | 38 ++-- .../utilities/BlockUtilities.java | 5 +- .../utilities/PlaceChecker.java | 5 +- .../utilities/ReachHelper.java | 1 + .../UndoRedoBlockSet.java | 5 +- .../assets/effortlessbuilding/lang/en_us.json | 150 ++++++------- 31 files changed, 544 insertions(+), 624 deletions(-) delete mode 100644 src/main/java/nl/requios/effortlessbuilding/network/AddUndoMessage.java delete mode 100644 src/main/java/nl/requios/effortlessbuilding/network/ClearUndoMessage.java delete mode 100644 src/main/java/nl/requios/effortlessbuilding/network/ModeActionMessage.java create mode 100644 src/main/java/nl/requios/effortlessbuilding/network/PerformRedoPacket.java create mode 100644 src/main/java/nl/requios/effortlessbuilding/network/PerformUndoPacket.java rename src/main/java/nl/requios/effortlessbuilding/{buildmodifier => systems}/UndoRedo.java (98%) rename src/main/java/nl/requios/effortlessbuilding/{buildmodifier => utilities}/UndoRedoBlockSet.java (93%) diff --git a/src/main/java/nl/requios/effortlessbuilding/ClientConfig.java b/src/main/java/nl/requios/effortlessbuilding/ClientConfig.java index e2021d2..1dacdd6 100644 --- a/src/main/java/nl/requios/effortlessbuilding/ClientConfig.java +++ b/src/main/java/nl/requios/effortlessbuilding/ClientConfig.java @@ -10,7 +10,7 @@ public class ClientConfig { public static class Visuals { public final ForgeConfigSpec.ConfigValue showBlockPreviews; - public final ForgeConfigSpec.ConfigValue alwaysShowBlockPreview; + public final ForgeConfigSpec.ConfigValue onlyShowBlockPreviewsWhenBuilding; public final ForgeConfigSpec.ConfigValue maxBlockPreviews; public Visuals(ForgeConfigSpec.Builder builder) { @@ -18,16 +18,16 @@ public class ClientConfig { showBlockPreviews = builder .comment("Show previews of the blocks while placing them") - .define("useShaders", true); + .define("showBlockPreviews", true); - alwaysShowBlockPreview = builder - .comment("Show a block preview if you have a block in hand even in the 'Disabled' build mode") - .define("alwaysShowBlockPreview", false); + onlyShowBlockPreviewsWhenBuilding = builder + .comment("Show block previews only when actively using a build mode") + .define("onlyShowBlockPreviewsWhenBuilding", true); maxBlockPreviews = builder .comment("Don't show block previews when placing more than this many blocks. " + "The outline will always be rendered.") - .define("shaderTreshold", 500); + .define("maxBlockPreviews", 500); builder.pop(); diff --git a/src/main/java/nl/requios/effortlessbuilding/ClientEvents.java b/src/main/java/nl/requios/effortlessbuilding/ClientEvents.java index 87bf8bb..3a40119 100644 --- a/src/main/java/nl/requios/effortlessbuilding/ClientEvents.java +++ b/src/main/java/nl/requios/effortlessbuilding/ClientEvents.java @@ -52,15 +52,14 @@ public class ClientEvents { EffortlessBuilding.log("Registering KeyMappings!"); // register key bindings - keyBindings = new KeyMapping[6]; + keyBindings = new KeyMapping[5]; // instantiate the key bindings - keyBindings[0] = new KeyMapping("key.effortlessbuilding.hud.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_KP_ADD, 0), "key.effortlessbuilding.category"); - keyBindings[1] = new KeyMapping("key.effortlessbuilding.replace.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_KP_SUBTRACT, 0), "key.effortlessbuilding.category"); - keyBindings[2] = new KeyMapping("key.effortlessbuilding.mode.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_LEFT_ALT, 0), "key.effortlessbuilding.category"); - keyBindings[3] = new KeyMapping("key.effortlessbuilding.undo.desc", KeyConflictContext.IN_GAME, KeyModifier.CONTROL, InputConstants.getKey(GLFW.GLFW_KEY_Z, 0), "key.effortlessbuilding.category"); - keyBindings[4] = new KeyMapping("key.effortlessbuilding.redo.desc", KeyConflictContext.IN_GAME, KeyModifier.CONTROL, InputConstants.getKey(GLFW.GLFW_KEY_Y, 0), "key.effortlessbuilding.category"); - keyBindings[5] = new KeyMapping("key.effortlessbuilding.altplacement.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_LEFT_CONTROL, 0), "key.effortlessbuilding.category"); + keyBindings[0] = new KeyMapping("key.effortlessbuilding.mode.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_LEFT_ALT, 0), "key.effortlessbuilding.category"); + keyBindings[1] = new KeyMapping("key.effortlessbuilding.hud.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_KP_ADD, 0), "key.effortlessbuilding.category"); + keyBindings[2] = new KeyMapping("key.effortlessbuilding.undo.desc", KeyConflictContext.IN_GAME, KeyModifier.CONTROL, InputConstants.getKey(GLFW.GLFW_KEY_Z, 0), "key.effortlessbuilding.category"); + keyBindings[3] = new KeyMapping("key.effortlessbuilding.redo.desc", KeyConflictContext.IN_GAME, KeyModifier.CONTROL, InputConstants.getKey(GLFW.GLFW_KEY_Y, 0), "key.effortlessbuilding.category"); + keyBindings[4] = new KeyMapping("key.effortlessbuilding.altplacement.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_LEFT_CONTROL, 0), "key.effortlessbuilding.category"); for (KeyMapping keyBinding : keyBindings) { event.register(keyBinding); @@ -148,19 +147,8 @@ public class ClientEvents { if (player == null) return; - //Remember to send packet to server if necessary - //Show Modifier Settings GUI - if (keyBindings[0].consumeClick()) { - openModifierSettings(); - } - - //QuickReplace toggle - if (keyBindings[1].consumeClick()) { - EffortlessBuildingClient.BUILD_SETTINGS.toggleQuickReplace(); - } - //Radial menu - if (keyBindings[2].isDown()) { + if (keyBindings[0].isDown()) { if (ReachHelper.getMaxReach(player) > 0) { if (!RadialMenu.instance.isVisible()) { Minecraft.getInstance().setScreen(RadialMenu.instance); @@ -170,22 +158,23 @@ public class ClientEvents { } } + //Show Modifier Settings GUI + if (keyBindings[1].consumeClick()) { + openModifierSettings(); + } + //Undo (Ctrl+Z) - if (keyBindings[3].consumeClick()) { - ModeOptions.ActionEnum action = ModeOptions.ActionEnum.UNDO; - ModeOptions.performAction(player, action); - PacketHandler.INSTANCE.sendToServer(new ModeActionMessage(action)); + if (keyBindings[2].consumeClick()) { + ModeOptions.performAction(player, ModeOptions.ActionEnum.UNDO); } //Redo (Ctrl+Y) - if (keyBindings[4].consumeClick()) { - ModeOptions.ActionEnum action = ModeOptions.ActionEnum.REDO; - ModeOptions.performAction(player, action); - PacketHandler.INSTANCE.sendToServer(new ModeActionMessage(action)); + if (keyBindings[3].consumeClick()) { + ModeOptions.performAction(player, ModeOptions.ActionEnum.REDO); } //Change placement mode - if (keyBindings[5].consumeClick()) { + if (keyBindings[4].isDown()) { //Toggle between first two actions of the first option of the current build mode BuildModeEnum currentBuildMode = EffortlessBuildingClient.BUILD_MODES.getBuildMode(); if (currentBuildMode.options.length > 0) { @@ -193,10 +182,8 @@ public class ClientEvents { if (option.actions.length >= 2) { if (ModeOptions.getOptionSetting(option) == option.actions[0]) { ModeOptions.performAction(player, option.actions[1]); - PacketHandler.INSTANCE.sendToServer(new ModeActionMessage(option.actions[1])); } else { ModeOptions.performAction(player, option.actions[0]); - PacketHandler.INSTANCE.sendToServer(new ModeActionMessage(option.actions[0])); } } } diff --git a/src/main/java/nl/requios/effortlessbuilding/CommonConfig.java b/src/main/java/nl/requios/effortlessbuilding/CommonConfig.java index 9db7c50..6bde5f4 100644 --- a/src/main/java/nl/requios/effortlessbuilding/CommonConfig.java +++ b/src/main/java/nl/requios/effortlessbuilding/CommonConfig.java @@ -74,7 +74,7 @@ public class CommonConfig { undoStackSize = builder .comment("How many placements are remembered for the undo functionality.") .worldRestart() - .define("undoStackSize", 10); + .define("undoStackSize", 50); builder.pop(); } diff --git a/src/main/java/nl/requios/effortlessbuilding/CommonEvents.java b/src/main/java/nl/requios/effortlessbuilding/CommonEvents.java index 04ae015..349ab52 100644 --- a/src/main/java/nl/requios/effortlessbuilding/CommonEvents.java +++ b/src/main/java/nl/requios/effortlessbuilding/CommonEvents.java @@ -18,13 +18,11 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod.EventBusSubscriber; import net.minecraftforge.network.PacketDistributor; import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; -import nl.requios.effortlessbuilding.buildmodifier.UndoRedo; +import nl.requios.effortlessbuilding.systems.UndoRedo; import nl.requios.effortlessbuilding.capability.ModifierCapabilityManager; import nl.requios.effortlessbuilding.compatibility.CompatHelper; import nl.requios.effortlessbuilding.systems.ServerBuildState; import nl.requios.effortlessbuilding.utilities.ReachHelper; -import nl.requios.effortlessbuilding.network.AddUndoMessage; -import nl.requios.effortlessbuilding.network.ClearUndoMessage; import nl.requios.effortlessbuilding.network.PacketHandler; @EventBusSubscriber @@ -72,12 +70,6 @@ public class CommonEvents { if (isPlayerHoldingBlock(player)) { event.setCanceled(true); } - - } else { - //NORMAL mode, let vanilla handle block placing - - //TODO move UndoRedo to serverside only - PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new AddUndoMessage(event.getPos(), event.getBlockSnapshot().getReplacedBlock(), event.getState())); } } @@ -93,16 +85,6 @@ public class CommonEvents { if (!ServerBuildState.isLikeVanilla(player) && ReachHelper.canBreakFar(player)) { event.setCanceled(true); - } else { - //NORMAL mode, let vanilla handle block breaking - - //Add to undo stack in client - //TODO move UndoRedo to serverside only - if (player instanceof ServerPlayer && event.getState() != null && event.getPos() != null) { - PacketDistributor.PacketTarget packetTarget = PacketDistributor.PLAYER.with(() -> (ServerPlayer) player); - if (packetTarget != null) - PacketHandler.INSTANCE.send(packetTarget, new AddUndoMessage(event.getPos(), event.getState(), Blocks.AIR.defaultBlockState())); - } } } @@ -116,6 +98,7 @@ public class CommonEvents { public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { if (event.getEntity() instanceof FakePlayer) return; Player player = event.getEntity(); + ServerBuildState.handleNewPlayer(player); ModifierSettingsManager.handleNewPlayer(player); } @@ -126,13 +109,13 @@ public class CommonEvents { if (player.getCommandSenderWorld().isClientSide) return; UndoRedo.clear(player); - PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new ClearUndoMessage()); } @SubscribeEvent public static void onPlayerRespawn(PlayerEvent.PlayerRespawnEvent event) { if (event.getEntity() instanceof FakePlayer) return; Player player = event.getEntity(); + ServerBuildState.handleNewPlayer(player); ModifierSettingsManager.handleNewPlayer(player); } @@ -149,10 +132,10 @@ public class CommonEvents { modifierSettings.getArraySettings().enabled = false; ModifierSettingsManager.setModifierSettings(player, modifierSettings); + ServerBuildState.handleNewPlayer(player); ModifierSettingsManager.handleNewPlayer(player); UndoRedo.clear(player); - PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new ClearUndoMessage()); } @SubscribeEvent @@ -165,6 +148,4 @@ public class CommonEvents { Player newPlayer = event.getEntity(); ModifierSettingsManager.setModifierSettings(newPlayer, ModifierSettingsManager.getModifierSettings(oldPlayer)); } - - -} +} \ No newline at end of file diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeOptions.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeOptions.java index 98c190b..1142b86 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeOptions.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeOptions.java @@ -1,11 +1,17 @@ package nl.requios.effortlessbuilding.buildmode; import net.minecraft.world.entity.player.Player; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; import nl.requios.effortlessbuilding.ClientEvents; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.EffortlessBuildingClient; -import nl.requios.effortlessbuilding.buildmodifier.UndoRedo; +import nl.requios.effortlessbuilding.network.PacketHandler; +import nl.requios.effortlessbuilding.network.PerformRedoPacket; +import nl.requios.effortlessbuilding.network.PerformUndoPacket; +import nl.requios.effortlessbuilding.systems.BuildSettings; +@OnlyIn(Dist.CLIENT) public class ModeOptions { private static ActionEnum buildSpeed = ActionEnum.NORMAL_SPEED; @@ -58,76 +64,43 @@ public class ModeOptions { return circleStart; } - //Called on both client and server public static void performAction(Player player, ActionEnum action) { if (action == null) return; switch (action) { - case UNDO: - UndoRedo.undo(player); - break; - case REDO: - UndoRedo.redo(player); - break; - case REPLACE: - if (player.level.isClientSide) - EffortlessBuildingClient.BUILD_SETTINGS.toggleQuickReplace(); - break; - case OPEN_MODIFIER_SETTINGS: - if (player.level.isClientSide) - ClientEvents.openModifierSettings(); - break; - case OPEN_PLAYER_SETTINGS: - if (player.level.isClientSide) - ClientEvents.openPlayerSettings(); - break; + case UNDO -> PacketHandler.INSTANCE.sendToServer(new PerformUndoPacket()); + case REDO -> PacketHandler.INSTANCE.sendToServer(new PerformRedoPacket()); + case OPEN_MODIFIER_SETTINGS -> ClientEvents.openModifierSettings(); + case OPEN_PLAYER_SETTINGS -> ClientEvents.openPlayerSettings(); - case NORMAL_SPEED: - buildSpeed = ActionEnum.NORMAL_SPEED; - break; - case FAST_SPEED: - buildSpeed = ActionEnum.FAST_SPEED; - break; - case FULL: - fill = ActionEnum.FULL; - break; - case HOLLOW: - fill = ActionEnum.HOLLOW; - break; - case CUBE_FULL: - cubeFill = ActionEnum.CUBE_FULL; - break; - case CUBE_HOLLOW: - cubeFill = ActionEnum.CUBE_HOLLOW; - break; - case CUBE_SKELETON: - cubeFill = ActionEnum.CUBE_SKELETON; - break; - case SHORT_EDGE: - raisedEdge = ActionEnum.SHORT_EDGE; - break; - case LONG_EDGE: - raisedEdge = ActionEnum.LONG_EDGE; - break; - case THICKNESS_1: - lineThickness = ActionEnum.THICKNESS_1; - break; - case THICKNESS_3: - lineThickness = ActionEnum.THICKNESS_3; - break; - case THICKNESS_5: - lineThickness = ActionEnum.THICKNESS_5; - break; - case CIRCLE_START_CENTER: - circleStart = ActionEnum.CIRCLE_START_CENTER; - break; - case CIRCLE_START_CORNER: - circleStart = ActionEnum.CIRCLE_START_CORNER; - break; + case REPLACE_ONLY_AIR -> EffortlessBuildingClient.BUILD_SETTINGS.setReplaceMode(BuildSettings.ReplaceMode.ONLY_AIR); + case REPLACE_BLOCKS_AND_AIR -> EffortlessBuildingClient.BUILD_SETTINGS.setReplaceMode(BuildSettings.ReplaceMode.BLOCKS_AND_AIR); + case REPLACE_ONLY_BLOCKS -> EffortlessBuildingClient.BUILD_SETTINGS.setReplaceMode(BuildSettings.ReplaceMode.ONLY_BLOCKS); + case REPLACE_FILTERED_BY_OFFHAND -> EffortlessBuildingClient.BUILD_SETTINGS.setReplaceMode(BuildSettings.ReplaceMode.FILTERED_BY_OFFHAND); + case TOGGLE_PROTECT_TILE_ENTITIES -> EffortlessBuildingClient.BUILD_SETTINGS.toggleProtectTileEntities(); + + case NORMAL_SPEED -> buildSpeed = ActionEnum.NORMAL_SPEED; + case FAST_SPEED -> buildSpeed = ActionEnum.FAST_SPEED; + + case FULL -> fill = ActionEnum.FULL; + case HOLLOW -> fill = ActionEnum.HOLLOW; + + case CUBE_FULL -> cubeFill = ActionEnum.CUBE_FULL; + case CUBE_HOLLOW -> cubeFill = ActionEnum.CUBE_HOLLOW; + case CUBE_SKELETON -> cubeFill = ActionEnum.CUBE_SKELETON; + + case SHORT_EDGE -> raisedEdge = ActionEnum.SHORT_EDGE; + case LONG_EDGE -> raisedEdge = ActionEnum.LONG_EDGE; + + case THICKNESS_1 -> lineThickness = ActionEnum.THICKNESS_1; + case THICKNESS_3 -> lineThickness = ActionEnum.THICKNESS_3; + case THICKNESS_5 -> lineThickness = ActionEnum.THICKNESS_5; + + case CIRCLE_START_CENTER -> circleStart = ActionEnum.CIRCLE_START_CENTER; + case CIRCLE_START_CORNER -> circleStart = ActionEnum.CIRCLE_START_CORNER; } if (player.level.isClientSide && - action != ActionEnum.REPLACE && action != ActionEnum.OPEN_MODIFIER_SETTINGS && action != ActionEnum.OPEN_PLAYER_SETTINGS) { @@ -138,10 +111,15 @@ public class ModeOptions { public enum ActionEnum { UNDO("effortlessbuilding.action.undo"), REDO("effortlessbuilding.action.redo"), - REPLACE("effortlessbuilding.action.replace"), OPEN_MODIFIER_SETTINGS("effortlessbuilding.action.open_modifier_settings"), OPEN_PLAYER_SETTINGS("effortlessbuilding.action.open_player_settings"), + REPLACE_ONLY_AIR("effortlessbuilding.action.replace_only_air"), + REPLACE_BLOCKS_AND_AIR("effortlessbuilding.action.replace_blocks_and_air"), + REPLACE_ONLY_BLOCKS("effortlessbuilding.action.replace_only_blocks"), + REPLACE_FILTERED_BY_OFFHAND("effortlessbuilding.action.replace_filtered_by_offhand"), + TOGGLE_PROTECT_TILE_ENTITIES("effortlessbuilding.action.toggle_protect_tile_entities"), + NORMAL_SPEED("effortlessbuilding.action.normal_speed"), FAST_SPEED("effortlessbuilding.action.fast_speed"), diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/ThreeClicksBuildMode.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/ThreeClicksBuildMode.java index 9d0db46..a7ab600 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/ThreeClicksBuildMode.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/ThreeClicksBuildMode.java @@ -4,6 +4,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; import net.minecraft.world.entity.player.Player; import net.minecraft.world.phys.Vec3; +import nl.requios.effortlessbuilding.EffortlessBuildingClient; import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockSet; import nl.requios.effortlessbuilding.utilities.ReachHelper; @@ -29,18 +30,14 @@ public abstract class ThreeClicksBuildMode extends BaseBuildMode { if (clicks == 1) { //First click, remember starting position + firstBlockEntry = EffortlessBuildingClient.BUILDER_CHAIN.getStartPos(); //If clicking in air, reset and try again - if (blocks.size() == 0) { - clicks = 0; - return false; - } + if (firstBlockEntry == null) clicks = 0; - firstBlockEntry = blocks.getFirstBlockEntry(); } else if (clicks == 2) { //Second click, find second position - //If clicking in air, reset and try again if (blocks.size() == 0) { clicks = 0; return false; diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/TwoClicksBuildMode.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/TwoClicksBuildMode.java index 239966d..171c512 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/TwoClicksBuildMode.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/TwoClicksBuildMode.java @@ -3,6 +3,7 @@ package nl.requios.effortlessbuilding.buildmode; import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; import net.minecraft.world.entity.player.Player; +import nl.requios.effortlessbuilding.EffortlessBuildingClient; import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockSet; import nl.requios.effortlessbuilding.utilities.ReachHelper; @@ -25,14 +26,11 @@ public abstract class TwoClicksBuildMode extends BaseBuildMode { if (clicks == 1) { //First click, remember starting position + firstBlockEntry = EffortlessBuildingClient.BUILDER_CHAIN.getStartPos(); //If clicking in air, reset and try again - if (blocks.size() == 0) { - clicks = 0; - return false; - } + if (firstBlockEntry == null) clicks = 0; - firstBlockEntry = blocks.getFirstBlockEntry(); } else { //Second click, place blocks clicks = 0; diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmodifier/BuildModifiers.java b/src/main/java/nl/requios/effortlessbuilding/buildmodifier/BuildModifiers.java index 6b942b0..1d26754 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmodifier/BuildModifiers.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmodifier/BuildModifiers.java @@ -19,10 +19,11 @@ import nl.requios.effortlessbuilding.CommonConfig; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.compatibility.CompatHelper; import nl.requios.effortlessbuilding.systems.DelayedBlockPlacer; -import nl.requios.effortlessbuilding.utilities.BlockEntry; +import nl.requios.effortlessbuilding.systems.UndoRedo; import nl.requios.effortlessbuilding.utilities.BlockSet; import nl.requios.effortlessbuilding.utilities.SurvivalHelper; import nl.requios.effortlessbuilding.item.AbstractRandomizerBagItem; +import nl.requios.effortlessbuilding.utilities.UndoRedoBlockSet; import java.util.ArrayList; import java.util.Collections; diff --git a/src/main/java/nl/requios/effortlessbuilding/gui/buildmode/RadialMenu.java b/src/main/java/nl/requios/effortlessbuilding/gui/buildmode/RadialMenu.java index 01262ab..b793e18 100644 --- a/src/main/java/nl/requios/effortlessbuilding/gui/buildmode/RadialMenu.java +++ b/src/main/java/nl/requios/effortlessbuilding/gui/buildmode/RadialMenu.java @@ -14,6 +14,7 @@ import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.core.Direction; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.FormattedText; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; @@ -22,8 +23,6 @@ import nl.requios.effortlessbuilding.ClientEvents; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.EffortlessBuildingClient; import nl.requios.effortlessbuilding.buildmode.ModeOptions; -import nl.requios.effortlessbuilding.network.ModeActionMessage; -import nl.requios.effortlessbuilding.network.PacketHandler; import org.apache.commons.lang3.text.WordUtils; import org.lwjgl.opengl.GL11; @@ -64,7 +63,8 @@ public class RadialMenu extends Screen { private final double textDistance = 75; private final double buttonDistance = 105; private final float fadeSpeed = 0.3f; - private final int descriptionHeight = 100; + private final int buildModeDescriptionHeight = 100; + private final int actionDescriptionWidth = 200; public BuildModeEnum switchTo = null; public ActionEnum doAction = null; @@ -91,7 +91,7 @@ public class RadialMenu extends Screen { public void tick() { super.tick(); - if (!ClientEvents.isKeybindDown(2)) { + if (!ClientEvents.isKeybindDown(0)) { onClose(); } } @@ -146,18 +146,23 @@ public class RadialMenu extends Screen { } //Add actions - buttons.add(new MenuButton(ActionEnum.UNDO.name, ActionEnum.UNDO, -buttonDistance - 26, -13, Direction.UP)); - buttons.add(new MenuButton(ActionEnum.REDO.name, ActionEnum.REDO, -buttonDistance, -13, Direction.UP)); -// buttons.add(new MenuButton(ActionEnum.OPEN_PLAYER_SETTINGS.name, ActionEnum.OPEN_PLAYER_SETTINGS, -buttonDistance - 26 - 13, 13, Direction.DOWN)); - buttons.add(new MenuButton(ActionEnum.OPEN_MODIFIER_SETTINGS.name, ActionEnum.OPEN_MODIFIER_SETTINGS, -buttonDistance - 26, 13, Direction.DOWN)); - buttons.add(new MenuButton(ActionEnum.REPLACE.name, ActionEnum.REPLACE, -buttonDistance, 13, Direction.DOWN)); +// buttons.add(new MenuButton(ActionEnum.OPEN_PLAYER_SETTINGS, -buttonDistance - 65, -13, Direction.UP)); + buttons.add(new MenuButton(ActionEnum.TOGGLE_PROTECT_TILE_ENTITIES, -buttonDistance - 78, -13, Direction.UP)); + buttons.add(new MenuButton(ActionEnum.OPEN_MODIFIER_SETTINGS, -buttonDistance - 52, -13, Direction.UP)); + buttons.add(new MenuButton(ActionEnum.UNDO, -buttonDistance - 26, -13, Direction.UP)); + buttons.add(new MenuButton(ActionEnum.REDO, -buttonDistance, -13, Direction.UP)); + + buttons.add(new MenuButton(ActionEnum.REPLACE_ONLY_AIR, -buttonDistance - 78, 13, Direction.DOWN)); + buttons.add(new MenuButton(ActionEnum.REPLACE_BLOCKS_AND_AIR, -buttonDistance - 52, 13, Direction.DOWN)); + buttons.add(new MenuButton(ActionEnum.REPLACE_ONLY_BLOCKS, -buttonDistance - 26, 13, Direction.DOWN)); + buttons.add(new MenuButton(ActionEnum.REPLACE_FILTERED_BY_OFFHAND, -buttonDistance, 13, Direction.DOWN)); //Add buildmode dependent options OptionEnum[] options = currentBuildMode.options; for (int i = 0; i < options.length; i++) { for (int j = 0; j < options[i].actions.length; j++) { ActionEnum action = options[i].actions[j]; - buttons.add(new MenuButton(action.name, action, buttonDistance + j * 26, -13 + i * 39, Direction.DOWN)); + buttons.add(new MenuButton(action, buttonDistance + j * 26, -13 + i * 39, Direction.DOWN)); } } @@ -250,15 +255,17 @@ public class RadialMenu extends Screen { private void drawSideButtonBackgrounds(BufferBuilder buffer, double middleX, double middleY, double mouseXCenter, double mouseYCenter, ArrayList buttons) { for (final MenuButton btn : buttons) { - final boolean isSelected = + final boolean isHighlighted = btn.x1 <= mouseXCenter && btn.x2 >= mouseXCenter && btn.y1 <= mouseYCenter && btn.y2 >= mouseYCenter; + + boolean isSelected = btn.action == getBuildSpeed() || btn.action == getFill() || btn.action == getCubeFill() || btn.action == getRaisedEdge() || btn.action == getLineThickness() || - btn.action == getCircleStart(); - - final boolean isHighlighted = btn.x1 <= mouseXCenter && btn.x2 >= mouseXCenter && btn.y1 <= mouseYCenter && btn.y2 >= mouseYCenter; + btn.action == getCircleStart() || + btn.action == EffortlessBuildingClient.BUILD_SETTINGS.getReplaceModeActionEnum() || + btn.action == ActionEnum.TOGGLE_PROTECT_TILE_ENTITIES && EffortlessBuildingClient.BUILD_SETTINGS.shouldProtectTileEntities(); Vector4f color = sideButtonColor; if (isSelected) color = selectedColor; @@ -340,20 +347,21 @@ public class RadialMenu extends Screen { //Draw description text = I18n.get(menuRegion.mode.getDescriptionKey()); - font.drawShadow(ms, text, (int) middleX - font.width(text) / 2f, (int) middleY + descriptionHeight, descriptionTextColor); + font.drawShadow(ms, text, (int) middleX - font.width(text) / 2f, (int) middleY + buildModeDescriptionHeight, descriptionTextColor); } } //Draw action text for (final MenuButton button : buttons) { if (button.highlighted) { + String text = ChatFormatting.AQUA + button.name; + String description = ChatFormatting.WHITE + button.description; //Add keybind in brackets String keybind = findKeybind(button, currentBuildMode); - String keybindFormatted = ""; - if (!keybind.isEmpty()) - keybindFormatted = ChatFormatting.GRAY + "(" + WordUtils.capitalizeFully(keybind) + ")"; + boolean hasKeybind = !keybind.isEmpty(); + keybind = ChatFormatting.GRAY + "(" + WordUtils.capitalizeFully(keybind) + ")"; if (button.textSide == Direction.WEST) { @@ -367,19 +375,31 @@ public class RadialMenu extends Screen { } else if (button.textSide == Direction.UP || button.textSide == Direction.NORTH) { - font.draw(ms, keybindFormatted, (int) (middleX + (button.x1 + button.x2) * 0.5 - font.width(keybindFormatted) * 0.5), - (int) (middleY + button.y1 - 26), whiteTextColor); + int y = (int) (middleY + button.y1 - 14); + font.draw(ms, text, (int) (middleX + (button.x1 + button.x2) * 0.5 - font.width(text) * 0.5), y, whiteTextColor); - font.draw(ms, text, (int) (middleX + (button.x1 + button.x2) * 0.5 - font.width(text) * 0.5), - (int) (middleY + button.y1 - 14), whiteTextColor); + y -= 12; + if (hasKeybind) { + font.draw(ms, keybind, (int) (middleX + (button.x1 + button.x2) * 0.5 - font.width(keybind) * 0.5), y, whiteTextColor); + y -= 12; + } + + if (!description.isEmpty()) + font.drawWordWrap(FormattedText.of(description), (int) (middleX + (button.x1 + button.x2) * 0.5 - actionDescriptionWidth * 0.5), y, actionDescriptionWidth, whiteTextColor); } else if (button.textSide == Direction.DOWN || button.textSide == Direction.SOUTH) { - font.draw(ms, text, (int) (middleX + (button.x1 + button.x2) * 0.5 - font.width(text) * 0.5), - (int) (middleY + button.y1 + 26), whiteTextColor); + int y = (int) (middleY + button.y1 + 26); + font.draw(ms, text, (int) (middleX + (button.x1 + button.x2) * 0.5 - font.width(text) * 0.5), y, whiteTextColor); - font.draw(ms, keybindFormatted, (int) (middleX + (button.x1 + button.x2) * 0.5 - font.width(keybindFormatted) * 0.5), - (int) (middleY + button.y1 + 38), whiteTextColor); + y += 12; + if (hasKeybind) { + font.draw(ms, keybind, (int) (middleX + (button.x1 + button.x2) * 0.5 - font.width(keybind) * 0.5), y, whiteTextColor); + y += 12; + } + + if (!description.isEmpty()) + font.drawWordWrap(FormattedText.of(description), (int) (middleX + (button.x1 + button.x2) * 0.5 - actionDescriptionWidth * 0.5), y, actionDescriptionWidth, whiteTextColor); } @@ -390,15 +410,14 @@ public class RadialMenu extends Screen { private String findKeybind(MenuButton button, BuildModeEnum currentBuildMode){ String result = ""; int keybindingIndex = -1; - if (button.action == ActionEnum.UNDO) keybindingIndex = 3; - if (button.action == ActionEnum.REDO) keybindingIndex = 4; - if (button.action == ActionEnum.REPLACE) keybindingIndex = 1; - if (button.action == ActionEnum.OPEN_MODIFIER_SETTINGS) keybindingIndex = 0; + if (button.action == ActionEnum.OPEN_MODIFIER_SETTINGS) keybindingIndex = 1; + if (button.action == ActionEnum.UNDO) keybindingIndex = 2; + if (button.action == ActionEnum.REDO) keybindingIndex = 3; if (keybindingIndex != -1) { KeyMapping keyMap = ClientEvents.keyBindings[keybindingIndex]; - if (!keyMap.getKeyModifier().name().equals("none")) { + if (!keyMap.getKeyModifier().name().equals("None")) { result = keyMap.getKeyModifier().name() + " "; } result += I18n.get(keyMap.getKey().getName()); @@ -408,10 +427,12 @@ public class RadialMenu extends Screen { //Add (ctrl) to first two actions of first option if (button.action == currentBuildMode.options[0].actions[0] || button.action == currentBuildMode.options[0].actions[1]) { - result = I18n.get(ClientEvents.keyBindings[5].getKey().getName()); + result = I18n.get(ClientEvents.keyBindings[4].getKey().getDisplayName().getString()); if (result.equals("Left Control")) result = "Ctrl"; } } + + result = result.replace("Key.keyboard.", ""); return result; } @@ -465,7 +486,6 @@ public class RadialMenu extends Screen { playRadialMenuSound(); ModeOptions.performAction(player, action); - PacketHandler.INSTANCE.sendToServer(new ModeActionMessage(action)); if (fromMouseClick) performedActionUsingMouse = true; } @@ -487,11 +507,17 @@ public class RadialMenu extends Screen { public double y1, y2; public boolean highlighted; public String name; + public String description = ""; public Direction textSide; - public MenuButton(final String name, final ActionEnum action, final double x, final double y, + public MenuButton(final ActionEnum action, final double x, final double y, final Direction textSide) { - this.name = I18n.get(name); + this.name = I18n.get(action.name); + + if (I18n.exists(action.name + ".description")) { + this.description = I18n.get(action.name + ".description"); + } + this.action = action; x1 = x - 10; x2 = x + 10; diff --git a/src/main/java/nl/requios/effortlessbuilding/gui/buildmodifier/ModifierSettingsGui.java b/src/main/java/nl/requios/effortlessbuilding/gui/buildmodifier/ModifierSettingsGui.java index a0ae3cf..2495e59 100644 --- a/src/main/java/nl/requios/effortlessbuilding/gui/buildmodifier/ModifierSettingsGui.java +++ b/src/main/java/nl/requios/effortlessbuilding/gui/buildmodifier/ModifierSettingsGui.java @@ -88,7 +88,7 @@ public class ModifierSettingsGui extends Screen { @Override public boolean keyPressed(int keyCode, int p_96553_, int p_96554_) { - if (keyCode == ClientEvents.keyBindings[0].getKey().getValue()) { + if (keyCode == ClientEvents.keyBindings[1].getKey().getValue()) { minecraft.player.closeContainer(); return true; } diff --git a/src/main/java/nl/requios/effortlessbuilding/network/AddUndoMessage.java b/src/main/java/nl/requios/effortlessbuilding/network/AddUndoMessage.java deleted file mode 100644 index 209aa9d..0000000 --- a/src/main/java/nl/requios/effortlessbuilding/network/AddUndoMessage.java +++ /dev/null @@ -1,99 +0,0 @@ -package nl.requios.effortlessbuilding.network; - -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.entity.player.Player; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.core.BlockPos; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.fml.DistExecutor; -import net.minecraftforge.fml.LogicalSide; -import net.minecraftforge.network.NetworkEvent; -import nl.requios.effortlessbuilding.EffortlessBuilding; -import nl.requios.effortlessbuilding.buildmodifier.UndoRedoBlockSet; -import nl.requios.effortlessbuilding.buildmodifier.UndoRedo; - -import java.util.ArrayList; -import java.util.function.Supplier; - -/*** - * Sends a message to the client asking to add a block to the undo stack. - */ -@Deprecated -public class AddUndoMessage { - private final BlockPos coordinate; - private final BlockState previousBlockState; - private final BlockState newBlockState; - - public AddUndoMessage() { - coordinate = BlockPos.ZERO; - previousBlockState = null; - newBlockState = null; - } - - public AddUndoMessage(BlockPos coordinate, BlockState previousBlockState, BlockState newBlockState) { - this.coordinate = coordinate; - this.previousBlockState = previousBlockState; - this.newBlockState = newBlockState; - } - - public static void encode(AddUndoMessage message, FriendlyByteBuf buf) { - buf.writeInt(message.coordinate.getX()); - buf.writeInt(message.coordinate.getY()); - buf.writeInt(message.coordinate.getZ()); - buf.writeInt(Block.getId(message.previousBlockState)); - buf.writeInt(Block.getId(message.newBlockState)); - } - - public static AddUndoMessage decode(FriendlyByteBuf buf) { - BlockPos coordinate = new BlockPos(buf.readInt(), buf.readInt(), buf.readInt()); - BlockState previousBlockState = Block.stateById(buf.readInt()); - BlockState newBlockState = Block.stateById(buf.readInt()); - return new AddUndoMessage(coordinate, previousBlockState, newBlockState); - } - - public BlockPos getCoordinate() { - return coordinate; - } - - public BlockState getPreviousBlockState() { - return previousBlockState; - } - - public BlockState getNewBlockState() { - return newBlockState; - } - - public static class Handler { - public static void handle(AddUndoMessage message, Supplier ctx) { - ctx.get().enqueueWork(() -> { - if (ctx.get().getDirection().getReceptionSide() == LogicalSide.CLIENT) { - //Received clientside - DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> ClientHandler.handle(message, ctx)); - } - }); - ctx.get().setPacketHandled(true); - } - } - - @OnlyIn(Dist.CLIENT) - public static class ClientHandler { - public static void handle(AddUndoMessage message, Supplier ctx) { - Player player = EffortlessBuilding.proxy.getPlayerEntityFromContext(ctx); - //Add to undo stack clientside - //Only the appropriate player that needs to add this to the undo stack gets this message - UndoRedo.addUndo(player, new UndoRedoBlockSet( - new ArrayList() {{ - add(message.getCoordinate()); - }}, - new ArrayList() {{ - add(message.getPreviousBlockState()); - }}, - new ArrayList() {{ - add(message.getNewBlockState()); - }}, - message.getCoordinate(), message.getCoordinate())); - } - } -} diff --git a/src/main/java/nl/requios/effortlessbuilding/network/ClearUndoMessage.java b/src/main/java/nl/requios/effortlessbuilding/network/ClearUndoMessage.java deleted file mode 100644 index c24a00a..0000000 --- a/src/main/java/nl/requios/effortlessbuilding/network/ClearUndoMessage.java +++ /dev/null @@ -1,53 +0,0 @@ -package nl.requios.effortlessbuilding.network; - -import net.minecraft.world.entity.player.Player; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.fml.DistExecutor; -import net.minecraftforge.fml.LogicalSide; -import net.minecraftforge.network.NetworkEvent; -import nl.requios.effortlessbuilding.EffortlessBuilding; -import nl.requios.effortlessbuilding.buildmodifier.UndoRedo; - -import java.util.function.Supplier; - -/*** - * Sends a message to the client asking to clear the undo and redo stacks. - */ -@Deprecated -public class ClearUndoMessage { - - public ClearUndoMessage() { - } - - public static void encode(ClearUndoMessage message, FriendlyByteBuf buf) { - - } - - public static ClearUndoMessage decode(FriendlyByteBuf buf) { - return new ClearUndoMessage(); - } - - public static class Handler { - public static void handle(ClearUndoMessage message, Supplier ctx) { - ctx.get().enqueueWork(() -> { - if (ctx.get().getDirection().getReceptionSide() == LogicalSide.CLIENT) { - //Received clientside - DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> ClientHandler.handle(message, ctx)); - } - }); - ctx.get().setPacketHandled(true); - } - } - - @OnlyIn(Dist.CLIENT) - public static class ClientHandler { - public static void handle(ClearUndoMessage message, Supplier ctx) { - Player player = EffortlessBuilding.proxy.getPlayerEntityFromContext(ctx); - - //Add to undo stack clientside - UndoRedo.clear(player); - } - } -} diff --git a/src/main/java/nl/requios/effortlessbuilding/network/ModeActionMessage.java b/src/main/java/nl/requios/effortlessbuilding/network/ModeActionMessage.java deleted file mode 100644 index 181ff30..0000000 --- a/src/main/java/nl/requios/effortlessbuilding/network/ModeActionMessage.java +++ /dev/null @@ -1,45 +0,0 @@ -package nl.requios.effortlessbuilding.network; - -import net.minecraft.world.entity.player.Player; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraftforge.network.NetworkEvent; -import nl.requios.effortlessbuilding.EffortlessBuilding; -import nl.requios.effortlessbuilding.buildmode.ModeOptions; - -import java.util.function.Supplier; - -/** - * Shares mode settings (see ModeSettingsManager) between server and client - */ -@Deprecated -public class ModeActionMessage { - - private ModeOptions.ActionEnum action; - - public ModeActionMessage() { - } - - public ModeActionMessage(ModeOptions.ActionEnum action) { - this.action = action; - } - - public static void encode(ModeActionMessage message, FriendlyByteBuf buf) { - buf.writeInt(message.action.ordinal()); - } - - public static ModeActionMessage decode(FriendlyByteBuf buf) { - ModeOptions.ActionEnum action = ModeOptions.ActionEnum.values()[buf.readInt()]; - return new ModeActionMessage(action); - } - - public static class Handler { - public static void handle(ModeActionMessage message, Supplier ctx) { - ctx.get().enqueueWork(() -> { - Player player = EffortlessBuilding.proxy.getPlayerEntityFromContext(ctx); - - ModeOptions.performAction(player, message.action); - }); - ctx.get().setPacketHandled(true); - } - } -} diff --git a/src/main/java/nl/requios/effortlessbuilding/network/PacketHandler.java b/src/main/java/nl/requios/effortlessbuilding/network/PacketHandler.java index 9729862..d4813ee 100644 --- a/src/main/java/nl/requios/effortlessbuilding/network/PacketHandler.java +++ b/src/main/java/nl/requios/effortlessbuilding/network/PacketHandler.java @@ -26,16 +26,14 @@ public class PacketHandler { IsQuickReplacingPacket.Handler::handle, Optional.of(NetworkDirection.PLAY_TO_SERVER)); INSTANCE.registerMessage(id++, ModifierSettingsMessage.class, ModifierSettingsMessage::encode, ModifierSettingsMessage::decode, ModifierSettingsMessage.Handler::handle); - INSTANCE.registerMessage(id++, ModeActionMessage.class, ModeActionMessage::encode, ModeActionMessage::decode, - ModeActionMessage.Handler::handle); INSTANCE.registerMessage(id++, ServerPlaceBlocksPacket.class, ServerPlaceBlocksPacket::encode, ServerPlaceBlocksPacket::decode, ServerPlaceBlocksPacket.Handler::handle, Optional.of(NetworkDirection.PLAY_TO_SERVER)); INSTANCE.registerMessage(id++, ServerBreakBlocksPacket.class, ServerBreakBlocksPacket::encode, ServerBreakBlocksPacket::decode, ServerBreakBlocksPacket.Handler::handle, Optional.of(NetworkDirection.PLAY_TO_SERVER)); - INSTANCE.registerMessage(id++, AddUndoMessage.class, AddUndoMessage::encode, AddUndoMessage::decode, - AddUndoMessage.Handler::handle); - INSTANCE.registerMessage(id++, ClearUndoMessage.class, ClearUndoMessage::encode, ClearUndoMessage::decode, - ClearUndoMessage.Handler::handle); + INSTANCE.registerMessage(id++, PerformUndoPacket.class, PerformUndoPacket::encode, PerformUndoPacket::decode, + PerformUndoPacket.Handler::handle, Optional.of(NetworkDirection.PLAY_TO_SERVER)); + INSTANCE.registerMessage(id++, PerformRedoPacket.class, PerformRedoPacket::encode, PerformRedoPacket::decode, + PerformRedoPacket.Handler::handle, Optional.of(NetworkDirection.PLAY_TO_SERVER)); } } diff --git a/src/main/java/nl/requios/effortlessbuilding/network/PerformRedoPacket.java b/src/main/java/nl/requios/effortlessbuilding/network/PerformRedoPacket.java new file mode 100644 index 0000000..e5f5d87 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/network/PerformRedoPacket.java @@ -0,0 +1,27 @@ +package nl.requios.effortlessbuilding.network; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; +import nl.requios.effortlessbuilding.systems.UndoRedo; + +import java.util.function.Supplier; + +public class PerformRedoPacket { + + public PerformRedoPacket() {} + + public static void encode(PerformRedoPacket message, FriendlyByteBuf buf) {} + + public static PerformRedoPacket decode(FriendlyByteBuf buf) { + return new PerformRedoPacket(); + } + + public static class Handler { + public static void handle(PerformRedoPacket message, Supplier ctx) { + ctx.get().enqueueWork(() -> { + UndoRedo.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 new file mode 100644 index 0000000..51e1eff --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/network/PerformUndoPacket.java @@ -0,0 +1,27 @@ +package nl.requios.effortlessbuilding.network; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; +import nl.requios.effortlessbuilding.systems.UndoRedo; + +import java.util.function.Supplier; + +public class PerformUndoPacket { + + public PerformUndoPacket() {} + + public static void encode(PerformUndoPacket message, FriendlyByteBuf buf) {} + + public static PerformUndoPacket decode(FriendlyByteBuf buf) { + return new PerformUndoPacket(); + } + + public static class Handler { + public static void handle(PerformUndoPacket message, Supplier ctx) { + ctx.get().enqueueWork(() -> { + UndoRedo.undo(ctx.get().getSender()); + }); + ctx.get().setPacketHandled(true); + } + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviews.java b/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviews.java index e25ad8b..4dda587 100644 --- a/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviews.java +++ b/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviews.java @@ -4,6 +4,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.world.entity.player.Player; import net.minecraft.core.BlockPos; import net.minecraft.util.Mth; +import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; import net.minecraftforge.api.distmarker.Dist; @@ -29,12 +30,12 @@ public class BlockPreviews { public void onTick() { var player = Minecraft.getInstance().player; - drawPlacedBlocks(player); + drawPlacedBlocks(); drawLookAtPreview(player); - drawOutlinesIfNoBlockInHand(player); + drawOutlineAtBreakPosition(player); } - public void drawPlacedBlocks(Player player) { + public void drawPlacedBlocks() { //Render placed blocks with appear animation if (ClientConfig.visuals.showBlockPreviews.get()) { for (PlacedBlocksEntry placed : placedBlocksList) { @@ -55,21 +56,22 @@ public class BlockPreviews { } public void drawLookAtPreview(Player player) { - if (EffortlessBuildingClient.BUILD_MODES.getBuildMode() == BuildModeEnum.DISABLED && - !ClientConfig.visuals.alwaysShowBlockPreview.get()) return; + if (EffortlessBuildingClient.BUILD_MODES.getBuildMode() == BuildModeEnum.DISABLED) return; + if (EffortlessBuildingClient.BUILDER_CHAIN.getBuildingState() == BuilderChain.BuildingState.IDLE && + ClientConfig.visuals.onlyShowBlockPreviewsWhenBuilding.get()) return; var blocks = EffortlessBuildingClient.BUILDER_CHAIN.getBlocks(); if (blocks.size() == 0) return; var coordinates = blocks.getCoordinates(); - var state = EffortlessBuildingClient.BUILDER_CHAIN.getState(); + var state = EffortlessBuildingClient.BUILDER_CHAIN.getPretendBuildingState(); //Dont fade out the outline if we are still determining where to place //Every outline with same ID will not fade out (because it gets replaced) Object outlineID = "single"; if (blocks.size() > 1) outlineID = blocks.firstPos; - if (state != BuilderChain.State.BREAKING) { + if (state != BuilderChain.BuildingState.BREAKING) { //Use fancy shader if config allows, otherwise outlines if (ClientConfig.visuals.showBlockPreviews.get() && blocks.size() < ClientConfig.visuals.maxBlockPreviews.get()) { renderBlockPreviews(blocks, false, 0f); @@ -98,7 +100,7 @@ public class BlockPreviews { } //Display block count and dimensions in actionbar - if (state != BuilderChain.State.IDLE) { + if (state != BuilderChain.BuildingState.IDLE) { //Find min and max values (not simply firstPos and secondPos because that doesn't work with circles) int minX = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE; @@ -126,31 +128,35 @@ public class BlockPreviews { } } - public void drawOutlinesIfNoBlockInHand(Player player) { - var builderChain = EffortlessBuildingClient.BUILDER_CHAIN; - if (builderChain.isBlockInHand()) return; - if (builderChain.getState() != BuilderChain.State.IDLE) return; + public void drawOutlineAtBreakPosition(Player player) { + if (EffortlessBuildingClient.BUILD_MODES.getBuildMode() == BuildModeEnum.DISABLED) return; - var blocks = new ArrayList<>(builderChain.getBlocks().values()); - if (blocks.size() == 0) return; + BuilderChain builderChain = EffortlessBuildingClient.BUILDER_CHAIN; + BlockPos pos = builderChain.getStartPosForBreaking(); + if (pos == null) return; - //Only render first outline if further than normal reach - var lookingAtNear = Minecraft.getInstance().hitResult; - if (lookingAtNear != null && lookingAtNear.getType() == HitResult.Type.BLOCK) - blocks.remove(0); - - //Only render outlines if there is a block, like vanilla - blocks.removeIf(blockEntry -> blockEntry.existingBlockState == null || - blockEntry.existingBlockState.isAir() || - blockEntry.existingBlockState.getMaterial().isLiquid()); - - if (!blocks.isEmpty()) { - var coordinates = blocks.stream().map(blockEntry -> blockEntry.blockPos).collect(Collectors.toList()); - CreateClient.OUTLINER.showCluster("break", coordinates) - .disableNormals() - .lineWidth(1 / 64f) - .colored(0x222222); + var abilitiesState = builderChain.getAbilitiesState(); + if (ClientConfig.visuals.onlyShowBlockPreviewsWhenBuilding.get()) { + if (abilitiesState == BuilderChain.AbilitiesState.NONE) return; + } else { + if (abilitiesState != BuilderChain.AbilitiesState.CAN_BREAK) return; } + + //Only render if further than normal reach + if (EffortlessBuildingClient.BUILDER_CHAIN.getLookingAtNear() != null) return; + + AABB aabb = new AABB(pos); + if (player.level.isLoaded(pos)) { + var blockState = player.level.getBlockState(pos); + if (!blockState.isAir()) { + aabb = blockState.getShape(player.level, pos).bounds().move(pos); + } + } + + CreateClient.OUTLINER.showAABB("break", aabb) + .disableNormals() + .lineWidth(1 / 64f) + .colored(0x222222); } protected void renderBlockPreviews(BlockSet blocks, boolean breaking, float dissolve) { @@ -186,8 +192,8 @@ public class BlockPreviews { t = Mth.clamp(t, 0, 1); //Now we got a usable t value for this block - t = (float) Mth.smoothstep(t); -// t = (float) bezier(t, .58,-0.08,.23,1.33); +// t = (float) Mth.smoothstep(t); + t = gain(t, 0.5f); if (!breaking) { scale = 0.5f + (t * 0.3f); @@ -206,21 +212,12 @@ public class BlockPreviews { .scale(scale); } - //A bezier easing function where implicit first and last control points are (0,0) and (1,1). - public double bezier(double t, double x1, double y1, double x2, double y2) { - double t2 = t * t; - double t3 = t2 * t; - - double cx = 3.0 * x1; - double bx = 3.0 * (x2 - x1) - cx; - double ax = 1.0 - cx -bx; - - double cy = 3.0 * y1; - double by = 3.0 * (y2 - y1) - cy; - double ay = 1.0 - cy - by; - - // Calculate the curve point at parameter value t - return (ay * t3) + (by * t2) + (cy * t) + 0; + //k=1 is the identity curve, k<1 produces the classic gain() shape, and k>1 produces "s" shaped curves. The curves are symmetric (and inverse) for k=a and k=1/a. + //https://iquilezles.org/articles/functions/ + private float gain(float x, float k) + { + float a = (float) (0.5 * Math.pow(2.0 * ((x < 0.5) ? x : 1.0 - x), k)); + return (x < 0.5) ? a : (1.0f - a); } public void onBlocksPlaced(BlockSet blocks) { diff --git a/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java b/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java index 4406095..a5d9ac2 100644 --- a/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java +++ b/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java @@ -55,7 +55,7 @@ public class RenderHandler { renderSubText(event.getPoseStack()); } - private static final ChatFormatting highlightColor = ChatFormatting.BLUE; + private static final ChatFormatting highlightColor = ChatFormatting.DARK_AQUA; private static final ChatFormatting normalColor = ChatFormatting.WHITE; private static final Component placingText = Component.literal( normalColor + "Left-click to " + highlightColor + "cancel, " + @@ -66,10 +66,10 @@ public class RenderHandler { normalColor + "Right-click to " + highlightColor + "cancel"); private static void renderSubText(PoseStack ms) { - var state = EffortlessBuildingClient.BUILDER_CHAIN.getState(); - if (state == BuilderChain.State.IDLE) return; + var state = EffortlessBuildingClient.BUILDER_CHAIN.getBuildingState(); + if (state == BuilderChain.BuildingState.IDLE) return; - var text = state == BuilderChain.State.PLACING ? placingText : breakingText; + var text = state == BuilderChain.BuildingState.PLACING ? placingText : breakingText; int screenWidth = Minecraft.getInstance().getWindow().getGuiScaledWidth(); int screenHeight = Minecraft.getInstance().getWindow().getGuiScaledHeight(); diff --git a/src/main/java/nl/requios/effortlessbuilding/systems/BuildSettings.java b/src/main/java/nl/requios/effortlessbuilding/systems/BuildSettings.java index 08547a1..eee2918 100644 --- a/src/main/java/nl/requios/effortlessbuilding/systems/BuildSettings.java +++ b/src/main/java/nl/requios/effortlessbuilding/systems/BuildSettings.java @@ -1,10 +1,8 @@ package nl.requios.effortlessbuilding.systems; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; -import nl.requios.effortlessbuilding.EffortlessBuilding; +import nl.requios.effortlessbuilding.buildmode.ModeOptions; import nl.requios.effortlessbuilding.network.IsQuickReplacingPacket; import nl.requios.effortlessbuilding.network.PacketHandler; @@ -12,56 +10,57 @@ import nl.requios.effortlessbuilding.network.PacketHandler; public class BuildSettings { public enum ReplaceMode { ONLY_AIR, - SOLID_AND_AIR, - SOLID_ONLY, + BLOCKS_AND_AIR, + ONLY_BLOCKS, FILTERED_BY_OFFHAND } - private boolean quickReplace = false; - public ReplaceMode replaceMode = ReplaceMode.ONLY_AIR; - private boolean replaceTileEntities; + private ReplaceMode replaceMode = ReplaceMode.ONLY_AIR; + private boolean protectTileEntities = true; public boolean isQuickReplacing() { - return quickReplace; - } - - public void toggleQuickReplace() { - setQuickReplace(!quickReplace); - } - - public void setQuickReplace(boolean quickReplace) { - this.quickReplace = quickReplace; - - EffortlessBuilding.log(Minecraft.getInstance().player, "Set " + ChatFormatting.GOLD + "Quick Replace " + - ChatFormatting.RESET + (this.quickReplace ? "on" : "off")); - PacketHandler.INSTANCE.sendToServer(new IsQuickReplacingPacket(this.quickReplace)); + return replaceMode != ReplaceMode.ONLY_AIR; } public void setReplaceMode(ReplaceMode replaceMode) { this.replaceMode = replaceMode; + PacketHandler.INSTANCE.sendToServer(new IsQuickReplacingPacket(isQuickReplacing())); } - public void setReplaceTileEntities(boolean replaceTileEntities) { - this.replaceTileEntities = replaceTileEntities; + public ReplaceMode getReplaceMode() { + return replaceMode; + } + + public ModeOptions.ActionEnum getReplaceModeActionEnum() { + return switch (replaceMode) { + case ONLY_AIR -> ModeOptions.ActionEnum.REPLACE_ONLY_AIR; + case BLOCKS_AND_AIR -> ModeOptions.ActionEnum.REPLACE_BLOCKS_AND_AIR; + case ONLY_BLOCKS -> ModeOptions.ActionEnum.REPLACE_ONLY_BLOCKS; + case FILTERED_BY_OFFHAND -> ModeOptions.ActionEnum.REPLACE_FILTERED_BY_OFFHAND; + }; + } + + public void toggleProtectTileEntities() { + protectTileEntities = !protectTileEntities; } public boolean shouldReplaceAir() { - return replaceMode == ReplaceMode.ONLY_AIR || replaceMode == ReplaceMode.SOLID_AND_AIR; + return replaceMode == ReplaceMode.ONLY_AIR || replaceMode == ReplaceMode.BLOCKS_AND_AIR; } - public boolean shouldReplaceSolid() { - return replaceMode == ReplaceMode.SOLID_ONLY || replaceMode == ReplaceMode.SOLID_AND_AIR; + public boolean shouldReplaceBlocks() { + return replaceMode == ReplaceMode.ONLY_BLOCKS || replaceMode == ReplaceMode.BLOCKS_AND_AIR; } public boolean shouldReplaceFiltered() { return replaceMode == ReplaceMode.FILTERED_BY_OFFHAND; } - public boolean shouldReplaceTileEntities() { - return replaceTileEntities; + public boolean shouldProtectTileEntities() { + return protectTileEntities; } public boolean shouldOffsetStartPosition() { - return shouldReplaceSolid() || shouldReplaceFiltered(); + return replaceMode != ReplaceMode.ONLY_AIR; } } diff --git a/src/main/java/nl/requios/effortlessbuilding/systems/BuilderChain.java b/src/main/java/nl/requios/effortlessbuilding/systems/BuilderChain.java index 359ee1e..a2c216e 100644 --- a/src/main/java/nl/requios/effortlessbuilding/systems/BuilderChain.java +++ b/src/main/java/nl/requios/effortlessbuilding/systems/BuilderChain.java @@ -19,6 +19,7 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import nl.requios.effortlessbuilding.ClientEvents; import nl.requios.effortlessbuilding.EffortlessBuildingClient; +import nl.requios.effortlessbuilding.buildmode.BuildModeEnum; import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; import nl.requios.effortlessbuilding.compatibility.CompatHelper; import nl.requios.effortlessbuilding.item.AbstractRandomizerBagItem; @@ -27,9 +28,7 @@ import nl.requios.effortlessbuilding.network.ServerBreakBlocksPacket; import nl.requios.effortlessbuilding.network.ServerPlaceBlocksPacket; import nl.requios.effortlessbuilding.utilities.*; -import java.util.ArrayList; import java.util.HashSet; -import java.util.List; // Receives block placed events, then finds additional blocks we want to place through various systems, // and then sends them to the server to be placed @@ -38,42 +37,46 @@ import java.util.List; public class BuilderChain { private final BlockSet blocks = new BlockSet(); - private boolean blockInHand; - private boolean lookingAtInteractiveObject; private Item previousHeldItem; private int soundTime = 0; + private BlockEntry startPosForPlacing; + private BlockPos startPosForBreaking; + private BlockHitResult lookingAtNear; - public enum State { + public enum BuildingState { IDLE, PLACING, BREAKING } - private State state = State.IDLE; + //What we are currently doing + private BuildingState buildingState = BuildingState.IDLE; + + public enum AbilitiesState { + CAN_PLACE_AND_BREAK, + CAN_BREAK, + NONE + } + + //Whether we can place or break blocks, determined by what we are looking at and what we are holding + private AbilitiesState abilitiesState = AbilitiesState.CAN_PLACE_AND_BREAK; public void onRightClick() { - if (lookingAtInteractiveObject) return; - var player = Minecraft.getInstance().player; - - if (state == State.BREAKING) { + if (abilitiesState != AbilitiesState.CAN_PLACE_AND_BREAK || buildingState == BuildingState.BREAKING) { cancel(); return; } - if (!blockInHand) { - if (state == State.PLACING) cancel(); - return; - } - - if (state == State.IDLE) { - state = State.PLACING; + if (buildingState == BuildingState.IDLE) { + buildingState = BuildingState.PLACING; } + var player = Minecraft.getInstance().player; var buildMode = EffortlessBuildingClient.BUILD_MODES.getBuildMode(); //Find out if we should place blocks now if (buildMode.instance.onClick(blocks)) { - state = State.IDLE; + buildingState = BuildingState.IDLE; if (!blocks.isEmpty()) { EffortlessBuildingClient.BLOCK_PREVIEWS.onBlocksPlaced(blocks); @@ -85,28 +88,29 @@ public class BuilderChain { } public void onLeftClick() { - if (lookingAtInteractiveObject) return; - var player = Minecraft.getInstance().player; - - if (state == State.PLACING) { + if (abilitiesState == AbilitiesState.NONE || buildingState == BuildingState.PLACING) { cancel(); return; } + var player = Minecraft.getInstance().player; if (!ReachHelper.canBreakFar(player)) return; - if (state == State.IDLE){ - state = State.BREAKING; + if (buildingState == BuildingState.IDLE){ + buildingState = BuildingState.BREAKING; - //Recalculate block positions, because start position has changed - onTick(); + //Use new start position for breaking, because we assumed the player was gonna place + blocks.setStartPos(new BlockEntry(startPosForBreaking)); + BuilderFilter.filterOnCoordinates(blocks, player); + findExistingBlockStates(player.level); + BuilderFilter.filterOnExistingBlockStates(blocks, player); } var buildMode = EffortlessBuildingClient.BUILD_MODES.getBuildMode(); //Find out if we should break blocks now if (buildMode.instance.onClick(blocks)) { - state = State.IDLE; + buildingState = BuildingState.IDLE; if (!blocks.isEmpty()) { EffortlessBuildingClient.BLOCK_PREVIEWS.onBlocksBroken(blocks); @@ -120,40 +124,45 @@ public class BuilderChain { public void onTick() { var previousCoordinates = new HashSet<>(blocks.getCoordinates()); blocks.clear(); + startPosForPlacing = null; + startPosForBreaking = null; + lookingAtNear = null; var mc = Minecraft.getInstance(); var player = mc.player; var world = mc.level; - //Check if we have a BlockItem in hand - var itemStack = player.getItemInHand(InteractionHand.MAIN_HAND); - blockInHand = CompatHelper.isItemBlockProxy(itemStack); - - lookingAtInteractiveObject = BlockUtilities.determineIfLookingAtInteractiveObject(mc, world); - if (lookingAtInteractiveObject) return; + abilitiesState = determineAbilities(mc, player, world); + if (abilitiesState == AbilitiesState.NONE) return; var buildMode = EffortlessBuildingClient.BUILD_MODES.getBuildMode(); var modifierSettings = ModifierSettingsManager.getModifierSettings(player); - if (state == State.IDLE) { + if (buildingState == BuildingState.IDLE) { //Find start position - BlockHitResult lookingAt = ClientEvents.getLookingAtFar(player); - BlockEntry startEntry = findStartPosition(player, lookingAt); + BlockEntry startEntry = findStartPosition(player, buildMode); if (startEntry != null) { - blocks.add(startEntry); - blocks.firstPos = startEntry.blockPos; - blocks.lastPos = startEntry.blockPos; + blocks.setStartPos(startEntry); + } else { + //We aren't placing or breaking blocks, and we have no start position + abilitiesState = AbilitiesState.NONE; + return; } } EffortlessBuildingClient.BUILD_MODES.findCoordinates(blocks, player, buildMode); EffortlessBuildingClient.BUILD_MODIFIERS.findCoordinates(blocks, player, modifierSettings); - BuilderFilter.filterOnCoordinates(blocks, player); + if (buildMode == BuildModeEnum.DISABLED && blocks.size() <= 1) { + abilitiesState = AbilitiesState.NONE; + return; + } + findExistingBlockStates(world); BuilderFilter.filterOnExistingBlockStates(blocks, player); + var itemStack = player.getItemInHand(InteractionHand.MAIN_HAND); findNewBlockStates(player, itemStack); BuilderFilter.filterOnNewBlockStates(blocks, player); @@ -165,6 +174,28 @@ public class BuilderChain { previousHeldItem = itemStack.getItem(); } + //Whether we can place or break blocks, determined by what we are looking at and what we are holding + private AbilitiesState determineAbilities(Minecraft mc, Player player, Level world) { + + var hitResult = Minecraft.getInstance().hitResult; + if (hitResult != null && hitResult.getType() == HitResult.Type.BLOCK) { + lookingAtNear = (BlockHitResult) hitResult; + } + + var itemStack = player.getItemInHand(InteractionHand.MAIN_HAND); + boolean blockInHand = CompatHelper.isItemBlockProxy(itemStack); + boolean lookingAtInteractiveObject = BlockUtilities.determineIfLookingAtInteractiveObject(mc, world); + boolean isShiftKeyDown = player.isShiftKeyDown(); + + if (lookingAtInteractiveObject && !isShiftKeyDown) + return AbilitiesState.NONE; + + if (!blockInHand) + return AbilitiesState.CAN_BREAK; + + return AbilitiesState.CAN_PLACE_AND_BREAK; + } + private void onBlocksChanged(Player player) { //Renew randomness of randomizer bag @@ -177,64 +208,77 @@ public class BuilderChain { if (blocks.getLastBlockEntry() != null && blocks.getLastBlockEntry().newBlockState != null) { var lastBlockState = blocks.getLastBlockEntry().newBlockState; SoundType soundType = lastBlockState.getBlock().getSoundType(lastBlockState, player.level, blocks.lastPos, player); - SoundEvent soundEvent = state == BuilderChain.State.BREAKING ? soundType.getBreakSound() : soundType.getPlaceSound(); + SoundEvent soundEvent = buildingState == BuildingState.BREAKING ? soundType.getBreakSound() : soundType.getPlaceSound(); player.level.playSound(player, player.blockPosition(), soundEvent, SoundSource.BLOCKS, 0.3f, 0.8f); } } } public void cancel() { - if (state == State.IDLE) return; - state = State.IDLE; + if (buildingState == BuildingState.IDLE) return; + buildingState = BuildingState.IDLE; EffortlessBuildingClient.BUILD_MODES.onCancel(); Minecraft.getInstance().player.playSound(SoundEvents.UI_TOAST_OUT, 4, 1); } - private BlockEntry findStartPosition(Player player, BlockHitResult lookingAtFar) { - if (lookingAtFar == null || lookingAtFar.getType() == HitResult.Type.MISS) return null; + private BlockEntry findStartPosition(Player player, BuildModeEnum buildMode) { - var startPos = lookingAtFar.getBlockPos(); + //Determine if we should look far or nearby + boolean shouldLookAtNear = buildMode == BuildModeEnum.DISABLED; + BlockHitResult lookingAt; + if (shouldLookAtNear) { + lookingAt = lookingAtNear; + } else { + lookingAt = ClientEvents.getLookingAtFar(player); + } + if (lookingAt == null || lookingAt.getType() == HitResult.Type.MISS) return null; + + var startPos = lookingAt.getBlockPos(); //Check if out of reach int maxReach = ReachHelper.getMaxReach(player); if (player.blockPosition().distSqr(startPos) > maxReach * maxReach) return null; - //TODO we are always at IDLE state here, find another way to check if we are breaking - if (state != State.BREAKING) { + startPosForBreaking = startPos; + if (!shouldLookAtNear && !ReachHelper.canBreakFar(player)) { + startPosForBreaking = null; + } + + if (abilitiesState == AbilitiesState.CAN_PLACE_AND_BREAK) { + //Calculate start position for placing + //Offset in direction of sidehit if not quickreplace and not replaceable boolean shouldOffsetStartPosition = EffortlessBuildingClient.BUILD_SETTINGS.shouldOffsetStartPosition(); boolean replaceable = player.level.getBlockState(startPos).getMaterial().isReplaceable(); boolean becomesDoubleSlab = SurvivalHelper.doesBecomeDoubleSlab(player, startPos); if (!shouldOffsetStartPosition && !replaceable && !becomesDoubleSlab) { - startPos = startPos.relative(lookingAtFar.getDirection()); + startPos = startPos.relative(lookingAt.getDirection()); } //Get under tall grass and other replaceable blocks if (shouldOffsetStartPosition && replaceable) { startPos = startPos.below(); } + } else { + //We can only break + //Do not break far if we are not allowed to - if (!ReachHelper.canBreakFar(player)) { - boolean startPosIsNear = false; - var lookingAtNear = Minecraft.getInstance().hitResult; - if (lookingAtNear != null && lookingAtNear.getType() == HitResult.Type.BLOCK) { - startPosIsNear = ((BlockHitResult) lookingAtNear).getBlockPos().equals(startPos); - } - if (!startPosIsNear) return null; - } + if (startPosForBreaking == null) return null; } var blockEntry = new BlockEntry(startPos); //Place upside-down stairs if we aim high at block - var hitVec = lookingAtFar.getLocation(); + var hitVec = lookingAt.getLocation(); //Format hitvec to 0.x hitVec = new Vec3(Math.abs(hitVec.x - ((int) hitVec.x)), Math.abs(hitVec.y - ((int) hitVec.y)), Math.abs(hitVec.z - ((int) hitVec.z))); if (hitVec.y > 0.5) { blockEntry.mirrorY = true; } + startPosForPlacing = blockEntry; + return blockEntry; } @@ -245,7 +289,7 @@ public class BuilderChain { } private void findNewBlockStates(Player player, ItemStack itemStack) { - if (state == State.BREAKING) return; + if (buildingState == BuildingState.BREAKING) return; if (itemStack.getItem() instanceof BlockItem) { @@ -264,20 +308,39 @@ public class BuilderChain { } } - - public State getState() { - return state; - } - public BlockSet getBlocks() { return blocks; } - public boolean isBlockInHand() { - return blockInHand; + public BuildingState getBuildingState() { + return buildingState; } - public boolean isLookingAtInteractiveObject() { - return lookingAtInteractiveObject; + public AbilitiesState getAbilitiesState() { + return abilitiesState; + } + + public BuildingState getPretendBuildingState() { + if (buildingState != BuildingState.IDLE) return buildingState; + if (abilitiesState == AbilitiesState.CAN_PLACE_AND_BREAK) return BuildingState.PLACING; + if (abilitiesState == AbilitiesState.CAN_BREAK) return BuildingState.BREAKING; + return BuildingState.IDLE; + } + + public BlockEntry getStartPosForPlacing() { + return startPosForPlacing; + } + + public BlockPos getStartPosForBreaking() { + return startPosForBreaking; + } + + public BlockEntry getStartPos() { + if (getPretendBuildingState() == BuildingState.BREAKING) return new BlockEntry(getStartPosForBreaking()); + return getStartPosForPlacing(); + } + + public BlockHitResult getLookingAtNear() { + return lookingAtNear; } } diff --git a/src/main/java/nl/requios/effortlessbuilding/systems/BuilderFilter.java b/src/main/java/nl/requios/effortlessbuilding/systems/BuilderFilter.java index e752ceb..bcb65ac 100644 --- a/src/main/java/nl/requios/effortlessbuilding/systems/BuilderFilter.java +++ b/src/main/java/nl/requios/effortlessbuilding/systems/BuilderFilter.java @@ -5,7 +5,6 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import nl.requios.effortlessbuilding.EffortlessBuildingClient; import nl.requios.effortlessbuilding.compatibility.CompatHelper; -import nl.requios.effortlessbuilding.utilities.BlockEntry; import nl.requios.effortlessbuilding.utilities.BlockSet; import nl.requios.effortlessbuilding.utilities.PlaceChecker; @@ -17,7 +16,8 @@ public class BuilderFilter { public static void filterOnExistingBlockStates(BlockSet blocks, Player player) { var buildSettings = EffortlessBuildingClient.BUILD_SETTINGS; - boolean placing = EffortlessBuildingClient.BUILDER_CHAIN.getState() == BuilderChain.State.PLACING; + var buildingState = EffortlessBuildingClient.BUILDER_CHAIN.getPretendBuildingState(); + boolean placing = buildingState == BuilderChain.BuildingState.PLACING; var iter = blocks.entrySet().iterator(); while (iter.hasNext()) { @@ -25,12 +25,13 @@ public class BuilderFilter { var blockState = blockEntry.existingBlockState; boolean remove = false; - if (!buildSettings.shouldReplaceTileEntities() && blockState.hasBlockEntity()) remove = true; + if (buildSettings.shouldProtectTileEntities() && blockState.hasBlockEntity()) remove = true; - if (placing) { + if (placing && !buildSettings.shouldReplaceFiltered()) { if (!buildSettings.shouldReplaceAir() && blockState.isAir()) remove = true; - boolean isSolid = blockState.isRedstoneConductor(player.level, blockEntry.blockPos); - if (!buildSettings.shouldReplaceSolid() && isSolid) remove = true; + boolean isReplaceable = blockState.getMaterial().isReplaceable(); +// boolean isSolid = blockState.isRedstoneConductor(player.level, blockEntry.blockPos); + if (!buildSettings.shouldReplaceBlocks() && !isReplaceable) remove = true; } if (buildSettings.shouldReplaceFiltered()) { @@ -43,13 +44,16 @@ public class BuilderFilter { } public static void filterOnNewBlockStates(BlockSet blocks, Player player) { + var buildSettings = EffortlessBuildingClient.BUILD_SETTINGS; + var buildingState = EffortlessBuildingClient.BUILDER_CHAIN.getPretendBuildingState(); + boolean placing = buildingState == BuilderChain.BuildingState.PLACING; var iter = blocks.entrySet().iterator(); while (iter.hasNext()) { var blockEntry = iter.next().getValue(); boolean remove = false; - if (!PlaceChecker.shouldPlaceBlock(player.level, blockEntry)) remove = true; + if (placing && !PlaceChecker.shouldPlaceBlock(player.level, blockEntry)) remove = true; if (remove) iter.remove(); } diff --git a/src/main/java/nl/requios/effortlessbuilding/systems/DelayedBlockPlacer.java b/src/main/java/nl/requios/effortlessbuilding/systems/DelayedBlockPlacer.java index 337a369..c40f9b8 100644 --- a/src/main/java/nl/requios/effortlessbuilding/systems/DelayedBlockPlacer.java +++ b/src/main/java/nl/requios/effortlessbuilding/systems/DelayedBlockPlacer.java @@ -6,8 +6,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; -import nl.requios.effortlessbuilding.buildmodifier.UndoRedoBlockSet; -import nl.requios.effortlessbuilding.buildmodifier.UndoRedo; +import nl.requios.effortlessbuilding.utilities.UndoRedoBlockSet; import nl.requios.effortlessbuilding.utilities.InventoryHelper; import nl.requios.effortlessbuilding.utilities.SurvivalHelper; diff --git a/src/main/java/nl/requios/effortlessbuilding/systems/ServerBlockPlacer.java b/src/main/java/nl/requios/effortlessbuilding/systems/ServerBlockPlacer.java index 51fb6e1..85d3796 100644 --- a/src/main/java/nl/requios/effortlessbuilding/systems/ServerBlockPlacer.java +++ b/src/main/java/nl/requios/effortlessbuilding/systems/ServerBlockPlacer.java @@ -30,7 +30,7 @@ public class ServerBlockPlacer { private boolean isPlacingOrBreakingBlocks = false; public void placeBlocks(Player player, BlockSet blocks) { - EffortlessBuilding.log(player, "Placing " + blocks.size() + " blocks"); +// EffortlessBuilding.log(player, "Placing " + blocks.size() + " blocks"); for (BlockEntry block : blocks) { placeBlock(player, block); @@ -47,7 +47,7 @@ public class ServerBlockPlacer { } public void breakBlocks(Player player, BlockSet blocks) { - EffortlessBuilding.log(player, "Breaking " + blocks.size() + " blocks"); +// EffortlessBuilding.log(player, "Breaking " + blocks.size() + " blocks"); for (BlockEntry block : blocks) { breakBlock(player, block); diff --git a/src/main/java/nl/requios/effortlessbuilding/systems/ServerBuildState.java b/src/main/java/nl/requios/effortlessbuilding/systems/ServerBuildState.java index f0fb590..1bd2b88 100644 --- a/src/main/java/nl/requios/effortlessbuilding/systems/ServerBuildState.java +++ b/src/main/java/nl/requios/effortlessbuilding/systems/ServerBuildState.java @@ -7,6 +7,11 @@ public class ServerBuildState { private static final String IS_USING_BUILD_MODE_KEY = EffortlessBuilding.MODID + ":isUsingBuildMode"; private static final String IS_QUICK_REPLACING_KEY = EffortlessBuilding.MODID + ":isQuickReplacing"; + public static void handleNewPlayer(Player player) { + setIsUsingBuildMode(player, false); + setIsQuickReplacing(player, false); + } + public static boolean isUsingBuildMode(Player player) { return player.getPersistentData().contains(IS_USING_BUILD_MODE_KEY); } diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmodifier/UndoRedo.java b/src/main/java/nl/requios/effortlessbuilding/systems/UndoRedo.java similarity index 98% rename from src/main/java/nl/requios/effortlessbuilding/buildmodifier/UndoRedo.java rename to src/main/java/nl/requios/effortlessbuilding/systems/UndoRedo.java index 3f7f863..593cd5f 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmodifier/UndoRedo.java +++ b/src/main/java/nl/requios/effortlessbuilding/systems/UndoRedo.java @@ -1,4 +1,4 @@ -package nl.requios.effortlessbuilding.buildmodifier; +package nl.requios.effortlessbuilding.systems; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; @@ -10,12 +10,14 @@ 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.utilities.UndoRedoBlockSet; import nl.requios.effortlessbuilding.utilities.FixedStack; import nl.requios.effortlessbuilding.utilities.InventoryHelper; import nl.requios.effortlessbuilding.utilities.SurvivalHelper; import java.util.*; +//Server only public class UndoRedo { //Undo and redo stacks per player diff --git a/src/main/java/nl/requios/effortlessbuilding/utilities/BlockSet.java b/src/main/java/nl/requios/effortlessbuilding/utilities/BlockSet.java index 1c5ccd4..9a379a6 100644 --- a/src/main/java/nl/requios/effortlessbuilding/utilities/BlockSet.java +++ b/src/main/java/nl/requios/effortlessbuilding/utilities/BlockSet.java @@ -1,9 +1,10 @@ package nl.requios.effortlessbuilding.utilities; -import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; -import net.minecraft.nbt.NbtUtils; import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.fml.DistExecutor; import nl.requios.effortlessbuilding.EffortlessBuilding; import org.jetbrains.annotations.NotNull; @@ -12,6 +13,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +//Common public class BlockSet extends HashMap implements Iterable { public static boolean logging = true; @@ -35,20 +37,19 @@ public class BlockSet extends HashMap implements Iterable< } } + public void setStartPos(BlockEntry startPos) { + clear(); + add(startPos); + firstPos = startPos.blockPos; + lastPos = startPos.blockPos; + } + public void add(BlockEntry blockEntry) { if (!containsKey(blockEntry.blockPos)) { - - //Limit number of blocks you can place - int limit = ReachHelper.getMaxBlocksPlacedAtOnce(Minecraft.getInstance().player); - if (size() >= limit) { - if (logging) EffortlessBuilding.log("BlockSet limit reached, not adding block at " + blockEntry.blockPos); - return; + if (!DistExecutor.unsafeCallWhenOn(Dist.CLIENT, () -> () -> ClientSide.isFull(this))) { + put(blockEntry.blockPos, blockEntry); } - - put(blockEntry.blockPos, blockEntry); - } else { - if (logging) EffortlessBuilding.log("BlockSet already contains block at " + blockEntry.blockPos); } } @@ -78,4 +79,17 @@ public class BlockSet extends HashMap implements Iterable< public static BlockSet decode(FriendlyByteBuf buf) { return new BlockSet(buf.readList(BlockEntry::decode)); } + + @OnlyIn(Dist.CLIENT) + public static class ClientSide { + public static boolean isFull(BlockSet blockSet) { + //Limit number of blocks you can place + int limit = ReachHelper.getMaxBlocksPlacedAtOnce(net.minecraft.client.Minecraft.getInstance().player); + if (blockSet.size() >= limit) { + if (logging) EffortlessBuilding.log("BlockSet limit reached, not adding block."); + return true; + } + return false; + } + } } diff --git a/src/main/java/nl/requios/effortlessbuilding/utilities/BlockUtilities.java b/src/main/java/nl/requios/effortlessbuilding/utilities/BlockUtilities.java index 9d63b11..7197f45 100644 --- a/src/main/java/nl/requios/effortlessbuilding/utilities/BlockUtilities.java +++ b/src/main/java/nl/requios/effortlessbuilding/utilities/BlockUtilities.java @@ -1,7 +1,6 @@ package nl.requios.effortlessbuilding.utilities; import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.core.Direction; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundSource; @@ -9,6 +8,7 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.SoundType; import net.minecraft.world.level.block.state.BlockState; @@ -16,6 +16,7 @@ import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; +//Common public class BlockUtilities { public static BlockState getBlockState(Player player, InteractionHand hand, ItemStack blockItemStack, BlockEntry blockEntry) { @@ -25,7 +26,7 @@ public class BlockUtilities { return block.getStateForPlacement(new BlockPlaceContext(player, hand, blockItemStack, blockHitResult)); } - public static boolean determineIfLookingAtInteractiveObject(Minecraft mc, ClientLevel level) { + public static boolean determineIfLookingAtInteractiveObject(Minecraft mc, Level level) { //Check if we are looking at an interactive object var result = false; if (mc.hitResult != null) { diff --git a/src/main/java/nl/requios/effortlessbuilding/utilities/PlaceChecker.java b/src/main/java/nl/requios/effortlessbuilding/utilities/PlaceChecker.java index 4061a08..975fe93 100644 --- a/src/main/java/nl/requios/effortlessbuilding/utilities/PlaceChecker.java +++ b/src/main/java/nl/requios/effortlessbuilding/utilities/PlaceChecker.java @@ -17,13 +17,16 @@ public class PlaceChecker { //SchematicPrinter::shouldPlaceBlock public static boolean shouldPlaceBlock(Level world, BlockEntry blockEntry) { - if (world == null) + if (world == null || blockEntry == null) return false; var pos = blockEntry.blockPos; var state = blockEntry.newBlockState; BlockEntity tileEntity = null; + if (state == null) + return false; + BlockState toReplace = world.getBlockState(pos); BlockEntity toReplaceTE = world.getBlockEntity(pos); BlockState toReplaceOther = null; diff --git a/src/main/java/nl/requios/effortlessbuilding/utilities/ReachHelper.java b/src/main/java/nl/requios/effortlessbuilding/utilities/ReachHelper.java index a39608a..fbf2e6e 100644 --- a/src/main/java/nl/requios/effortlessbuilding/utilities/ReachHelper.java +++ b/src/main/java/nl/requios/effortlessbuilding/utilities/ReachHelper.java @@ -6,6 +6,7 @@ import nl.requios.effortlessbuilding.CommonConfig; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; +//Common public class ReachHelper { private static final String REACH_UPGRADE_KEY = EffortlessBuilding.MODID + ":reachUpgrade"; diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmodifier/UndoRedoBlockSet.java b/src/main/java/nl/requios/effortlessbuilding/utilities/UndoRedoBlockSet.java similarity index 93% rename from src/main/java/nl/requios/effortlessbuilding/buildmodifier/UndoRedoBlockSet.java rename to src/main/java/nl/requios/effortlessbuilding/utilities/UndoRedoBlockSet.java index 3fac572..7706faf 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmodifier/UndoRedoBlockSet.java +++ b/src/main/java/nl/requios/effortlessbuilding/utilities/UndoRedoBlockSet.java @@ -1,11 +1,12 @@ -package nl.requios.effortlessbuilding.buildmodifier; +package nl.requios.effortlessbuilding.utilities; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.core.BlockPos; import java.util.List; -//Used only for Undo +//Server only +@Deprecated public class UndoRedoBlockSet { private final List coordinates; private final List previousBlockStates; diff --git a/src/main/resources/assets/effortlessbuilding/lang/en_us.json b/src/main/resources/assets/effortlessbuilding/lang/en_us.json index e5da310..389201c 100644 --- a/src/main/resources/assets/effortlessbuilding/lang/en_us.json +++ b/src/main/resources/assets/effortlessbuilding/lang/en_us.json @@ -1,79 +1,87 @@ { - "effortlessbuilding.screen.modifier_settings": "Modifier Settings", - "effortlessbuilding.screen.radial_menu": "Build Modes", - "effortlessbuilding.screen.player_settings": "Player Settings", + "effortlessbuilding.screen.modifier_settings": "Modifier Settings", + "effortlessbuilding.screen.radial_menu": "Build Modes", + "effortlessbuilding.screen.player_settings": "Player Settings", - "key.effortlessbuilding.category": "Effortless Building", - "key.effortlessbuilding.hud.desc": "Modifier Menu", - "key.effortlessbuilding.replace.desc": "Toggle QuickReplace", - "key.effortlessbuilding.mode.desc": "Radial Menu", - "key.effortlessbuilding.undo.desc": "Undo", - "key.effortlessbuilding.redo.desc": "Redo", - "key.effortlessbuilding.altplacement.desc": "Alternative placement", + "key.effortlessbuilding.category": "Effortless Building", + "key.effortlessbuilding.hud.desc": "Modifier Menu", + "key.effortlessbuilding.replace.desc": "Toggle QuickReplace", + "key.effortlessbuilding.mode.desc": "Radial Menu", + "key.effortlessbuilding.undo.desc": "Undo", + "key.effortlessbuilding.redo.desc": "Redo", + "key.effortlessbuilding.altplacement.desc": "Alternative placement", - "item.effortlessbuilding.randomizer_bag": "Leather Randomizer Bag", - "item.effortlessbuilding.golden_randomizer_bag": "Golden Randomizer Bag", - "item.effortlessbuilding.diamond_randomizer_bag": "Diamond Randomizer Bag", - "item.effortlessbuilding.reach_upgrade1": "Reach Upgrade 1", - "item.effortlessbuilding.reach_upgrade2": "Reach Upgrade 2", - "item.effortlessbuilding.reach_upgrade3": "Reach Upgrade 3", + "item.effortlessbuilding.randomizer_bag": "Leather Randomizer Bag", + "item.effortlessbuilding.golden_randomizer_bag": "Golden Randomizer Bag", + "item.effortlessbuilding.diamond_randomizer_bag": "Diamond Randomizer Bag", + "item.effortlessbuilding.reach_upgrade1": "Reach Upgrade 1", + "item.effortlessbuilding.reach_upgrade2": "Reach Upgrade 2", + "item.effortlessbuilding.reach_upgrade3": "Reach Upgrade 3", - "effortlessbuilding.mode.normal": "Disable", - "effortlessbuilding.mode.normal_plus": "Single", - "effortlessbuilding.mode.line": "Line", - "effortlessbuilding.mode.wall": "Wall", - "effortlessbuilding.mode.floor": "Floor", - "effortlessbuilding.mode.diagonal_line": "Diagonal Line", - "effortlessbuilding.mode.diagonal_wall": "Diagonal Wall", - "effortlessbuilding.mode.slope_floor": "Slope Floor", - "effortlessbuilding.mode.cube": "Cube", - "effortlessbuilding.mode.circle": "Circle", - "effortlessbuilding.mode.cylinder": "Cylinder", - "effortlessbuilding.mode.sphere": "Sphere", - "effortlessbuilding.mode.pyramid": "Pyramid", - "effortlessbuilding.mode.cone": "Cone", - "effortlessbuilding.mode.dome": "Dome", - - "effortlessbuilding.modedescription.normal": "Disable mod and use vanilla placement rules", - "effortlessbuilding.modedescription.normal_plus": "Like vanilla, but with increased reach and placement preview", - "effortlessbuilding.modedescription.line": "", - "effortlessbuilding.modedescription.wall": "", - "effortlessbuilding.modedescription.floor": "", - "effortlessbuilding.modedescription.diagonal_line": "", - "effortlessbuilding.modedescription.diagonal_wall": "", - "effortlessbuilding.modedescription.slope_floor": "", - "effortlessbuilding.modedescription.cube": "", - "effortlessbuilding.modedescription.circle": "", - "effortlessbuilding.modedescription.cylinder": "", - "effortlessbuilding.modedescription.sphere": "", - "effortlessbuilding.modedescription.pyramid": "", - "effortlessbuilding.modedescription.cone": "", - "effortlessbuilding.modedescription.dome": "", + "effortlessbuilding.mode.normal": "Disable", + "effortlessbuilding.mode.normal_plus": "Single", + "effortlessbuilding.mode.line": "Line", + "effortlessbuilding.mode.wall": "Wall", + "effortlessbuilding.mode.floor": "Floor", + "effortlessbuilding.mode.diagonal_line": "Diagonal Line", + "effortlessbuilding.mode.diagonal_wall": "Diagonal Wall", + "effortlessbuilding.mode.slope_floor": "Slope Floor", + "effortlessbuilding.mode.cube": "Cube", + "effortlessbuilding.mode.circle": "Circle", + "effortlessbuilding.mode.cylinder": "Cylinder", + "effortlessbuilding.mode.sphere": "Sphere", + "effortlessbuilding.mode.pyramid": "Pyramid", + "effortlessbuilding.mode.cone": "Cone", + "effortlessbuilding.mode.dome": "Dome", - "effortlessbuilding.action.undo": "Undo", - "effortlessbuilding.action.redo": "Redo", - "effortlessbuilding.action.replace": "Replace", - "effortlessbuilding.action.open_modifier_settings": "Open Modifier Settings", - "effortlessbuilding.action.open_player_settings": "Open Settings", - - "effortlessbuilding.action.build_speed": "Build Speed", - "effortlessbuilding.action.filling": "Filling", - "effortlessbuilding.action.raised_edge": "Raised Edge", - "effortlessbuilding.action.thickness": "Line Thickness", - "effortlessbuilding.action.circle_start": "Start Point", + "effortlessbuilding.modedescription.normal": "Disable mod and use vanilla placement rules", + "effortlessbuilding.modedescription.normal_plus": "Like vanilla, but with increased reach and placement preview", + "effortlessbuilding.modedescription.line": "", + "effortlessbuilding.modedescription.wall": "", + "effortlessbuilding.modedescription.floor": "", + "effortlessbuilding.modedescription.diagonal_line": "", + "effortlessbuilding.modedescription.diagonal_wall": "", + "effortlessbuilding.modedescription.slope_floor": "", + "effortlessbuilding.modedescription.cube": "", + "effortlessbuilding.modedescription.circle": "", + "effortlessbuilding.modedescription.cylinder": "", + "effortlessbuilding.modedescription.sphere": "", + "effortlessbuilding.modedescription.pyramid": "", + "effortlessbuilding.modedescription.cone": "", + "effortlessbuilding.modedescription.dome": "", - "effortlessbuilding.action.normal_speed": "Normal", - "effortlessbuilding.action.fast_speed": "Fast", - "effortlessbuilding.action.full": "Filled", - "effortlessbuilding.action.hollow": "Hollow", - "effortlessbuilding.action.skeleton": "Skeleton", - "effortlessbuilding.action.short_edge": "Short Edge", - "effortlessbuilding.action.long_edge": "Long Edge", - "effortlessbuilding.action.thickness_1": "1 Block Thick", - "effortlessbuilding.action.thickness_3": "3 Blocks Thick", - "effortlessbuilding.action.thickness_5": "5 Blocks Thick", - "effortlessbuilding.action.start_center": "Middle", - "effortlessbuilding.action.start_corner": "Corner", + "effortlessbuilding.action.undo": "Undo", + "effortlessbuilding.action.redo": "Redo", + "effortlessbuilding.action.open_modifier_settings": "Open Modifier Settings", + "effortlessbuilding.action.open_player_settings": "Open Build Settings", - "commands.reach.usage": "/reach " + "effortlessbuilding.action.replace_only_air": "Don't Replace Blocks", + "effortlessbuilding.action.replace_only_air.description": "You will only place blocks where there is no block already. Replaceables such as grass will still be replaced.", + "effortlessbuilding.action.replace_blocks_and_air": "Replace Blocks And Air", + "effortlessbuilding.action.replace_blocks_and_air.description": "You will replace blocks and air.", + "effortlessbuilding.action.replace_only_blocks": "Replace Only Blocks", + "effortlessbuilding.action.replace_only_blocks.description": "You will only replace blocks, not air.", + "effortlessbuilding.action.replace_filtered_by_offhand": "Filter By Offhand", + "effortlessbuilding.action.replace_filtered_by_offhand.description": "You will only replace blocks that match the block in your offhand. If you don't have a block in your offhand, you will only replace air. If you have a randomizer bag in your offhand, you will only replace blocks that are contained in the bag.", + "effortlessbuilding.action.toggle_protect_tile_entities": "Protect Tile Entities", + "effortlessbuilding.action.toggle_protect_tile_entities.description": "Blocks that hold data such as chests, furnaces, and hoppers will not be replaced.", + + "effortlessbuilding.action.build_speed": "Build Speed", + "effortlessbuilding.action.filling": "Filling", + "effortlessbuilding.action.raised_edge": "Raised Edge", + "effortlessbuilding.action.thickness": "Line Thickness", + "effortlessbuilding.action.circle_start": "Start Point", + + "effortlessbuilding.action.normal_speed": "Normal", + "effortlessbuilding.action.fast_speed": "Fast", + "effortlessbuilding.action.full": "Filled", + "effortlessbuilding.action.hollow": "Hollow", + "effortlessbuilding.action.skeleton": "Skeleton", + "effortlessbuilding.action.short_edge": "Short Edge", + "effortlessbuilding.action.long_edge": "Long Edge", + "effortlessbuilding.action.thickness_1": "1 Block Thick", + "effortlessbuilding.action.thickness_3": "3 Blocks Thick", + "effortlessbuilding.action.thickness_5": "5 Blocks Thick", + "effortlessbuilding.action.start_center": "Middle", + "effortlessbuilding.action.start_corner": "Corner" } \ No newline at end of file