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.
This commit is contained in:
Christian Knaapen
2023-01-24 02:06:43 +01:00
parent af4ef73375
commit 4ea34fc854
31 changed files with 544 additions and 624 deletions

View File

@@ -10,7 +10,7 @@ public class ClientConfig {
public static class Visuals {
public final ForgeConfigSpec.ConfigValue<Boolean> showBlockPreviews;
public final ForgeConfigSpec.ConfigValue<Boolean> alwaysShowBlockPreview;
public final ForgeConfigSpec.ConfigValue<Boolean> onlyShowBlockPreviewsWhenBuilding;
public final ForgeConfigSpec.ConfigValue<Integer> 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();

View File

@@ -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]));
}
}
}

View File

@@ -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();
}

View File

@@ -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));
}
}
}

View File

@@ -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"),

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<MenuButton> 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;

View File

@@ -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;
}

View File

@@ -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<NetworkEvent.Context> 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<NetworkEvent.Context> 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<BlockPos>() {{
add(message.getCoordinate());
}},
new ArrayList<BlockState>() {{
add(message.getPreviousBlockState());
}},
new ArrayList<BlockState>() {{
add(message.getNewBlockState());
}},
message.getCoordinate(), message.getCoordinate()));
}
}
}

View File

@@ -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<NetworkEvent.Context> 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<NetworkEvent.Context> ctx) {
Player player = EffortlessBuilding.proxy.getPlayerEntityFromContext(ctx);
//Add to undo stack clientside
UndoRedo.clear(player);
}
}
}

View File

@@ -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<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> {
Player player = EffortlessBuilding.proxy.getPlayerEntityFromContext(ctx);
ModeOptions.performAction(player, message.action);
});
ctx.get().setPacketHandled(true);
}
}
}

View File

@@ -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));
}
}

View File

@@ -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<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> {
UndoRedo.redo(ctx.get().getSender());
});
ctx.get().setPacketHandled(true);
}
}
}

View File

@@ -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<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> {
UndoRedo.undo(ctx.get().getSender());
});
ctx.get().setPacketHandled(true);
}
}
}

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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<BlockPos, BlockEntry> implements Iterable<BlockEntry> {
public static boolean logging = true;
@@ -35,20 +37,19 @@ public class BlockSet extends HashMap<BlockPos, BlockEntry> 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<BlockPos, BlockEntry> 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;
}
}
}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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";

View File

@@ -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<BlockPos> coordinates;
private final List<BlockState> previousBlockStates;

View File

@@ -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 <level>"
"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"
}