diff --git a/common/src/main/java/org/samo_lego/taterzens/commands/edit/PathCommand.java b/common/src/main/java/org/samo_lego/taterzens/commands/edit/PathCommand.java index c563888c4..4ef15df61 100644 --- a/common/src/main/java/org/samo_lego/taterzens/commands/edit/PathCommand.java +++ b/common/src/main/java/org/samo_lego/taterzens/commands/edit/PathCommand.java @@ -1,40 +1,242 @@ package org.samo_lego.taterzens.commands.edit; +import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.tree.LiteralCommandNode; import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.commands.arguments.coordinates.BlockPosArgument; +import net.minecraft.core.BlockPos; import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.HoverEvent; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.TextComponent; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; import org.samo_lego.taterzens.Taterzens; import org.samo_lego.taterzens.commands.NpcCommand; import org.samo_lego.taterzens.interfaces.ITaterzenEditor; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static net.minecraft.commands.Commands.argument; import static net.minecraft.commands.Commands.literal; import static org.samo_lego.taterzens.Taterzens.config; -import static org.samo_lego.taterzens.util.TextUtil.joinText; -import static org.samo_lego.taterzens.util.TextUtil.successText; -import static org.samo_lego.taterzens.util.TextUtil.translate; +import static org.samo_lego.taterzens.util.TextUtil.*; public class PathCommand { public static void registerNode(LiteralCommandNode editNode) { LiteralCommandNode pathNode = literal("path") - .then(literal("clear") - .requires(src -> Taterzens.getInstance().getPlatform().checkPermission(src, "taterzens.npc.edit.path.clear", config.perms.npcCommandPermissionLevel)) - .executes(PathCommand::clearTaterzenPath) + .then(literal("clear") + .requires(src -> Taterzens.getInstance().getPlatform().checkPermission(src, "taterzens.npc.edit.path.clear", config.perms.npcCommandPermissionLevel)) + .executes(PathCommand::clearTaterzenPath) + ) + .then(literal("list") + .requires(src -> Taterzens.getInstance().getPlatform().checkPermission(src, "taterzens.npc.edit.path.list", config.perms.npcCommandPermissionLevel)) + .executes(PathCommand::listPathNodes) + ) + .requires(src -> Taterzens.getInstance().getPlatform().checkPermission(src, "taterzens.npc.edit.path", config.perms.npcCommandPermissionLevel)) + .then(literal("add") + .then(argument("pos", BlockPosArgument.blockPos()) + .suggests((context, builder) -> BlockPosArgument.blockPos().listSuggestions(context, builder)) + .executes(PathCommand::addPathNode) + ) + ) + .then(literal("remove") + .then(literal("index") + .then(argument("index", IntegerArgumentType.integer(1)) + .suggests((context, builder) -> SharedSuggestionProvider.suggest(getAvailablePathNodeIndices(context), builder)) + .executes(PathCommand::removePathNodeByIndex) + ) ) - .requires(src -> Taterzens.getInstance().getPlatform().checkPermission(src, "taterzens.npc.edit.path", config.perms.npcCommandPermissionLevel)) - .executes(PathCommand::editTaterzenPath) - .build(); + .executes(PathCommand::removeRecentPathNode) + ) + .executes(PathCommand::editTaterzenPath) + .build(); editNode.addChild(pathNode); } + private static String[] getAvailablePathNodeIndices(CommandContext context) throws CommandSyntaxException + { + CommandSourceStack source = context.getSource(); + ServerPlayer player = source.getPlayerOrException(); + + AtomicReference> pathNodes = new AtomicReference<>(); + + int result = NpcCommand.selectedTaterzenExecutor(player, + taterzen -> pathNodes.set(taterzen.getPathTargets())); + + if (result == 1) + { + String[] availableIndices = new String[pathNodes.get().size()]; + for (int i = 0; i < pathNodes.get().size(); i++) { + availableIndices[i] = Integer.toString(i + 1); + } + return availableIndices; + } + else + { + return new String[0]; + } + } + private static int listPathNodes(CommandContext context) throws CommandSyntaxException + { + CommandSourceStack source = context.getSource(); + ServerPlayer player = source.getPlayerOrException(); + + MutableComponent response = translate("taterzens.command.path_editor.list").withStyle(ChatFormatting.AQUA); + + return NpcCommand.selectedTaterzenExecutor(player, taterzen -> { + + ArrayList pathNodes = taterzen.getPathTargets(); + if (!pathNodes.isEmpty()) { + for (int i = 0; i < pathNodes.size(); i++) + { + int idx = i + 1; + + response.append( + new TextComponent("\n" + idx + ": (" + pathNodes.get(i).toShortString() + ")") + .withStyle(i % 2 == 0 ? ChatFormatting.GREEN : ChatFormatting.BLUE) + ); + } + } + else + { + response.append( + new TextComponent(" " + translate("taterzens.command.path_editor.empty").getString()) + .withStyle(ChatFormatting.YELLOW) + ); + } + + source.sendSuccess(response, false); + }); + } + + private static int addPathNode(CommandContext context) throws CommandSyntaxException + { + CommandSourceStack source = context.getSource(); + ServerPlayer player = source.getPlayerOrException(); + BlockPos pos = BlockPosArgument.getLoadedBlockPos(context, "pos"); + + int result = NpcCommand.selectedTaterzenExecutor(player, taterzen -> { + + taterzen.addPathTarget(pos); + + // Replace block in world with redstone block in case player is in editor mode + if(((ITaterzenEditor) player).getEditorMode() == ITaterzenEditor.EditorMode.PATH) { + player.connection.send(new ClientboundBlockUpdatePacket(pos, Blocks.REDSTONE_BLOCK.defaultBlockState())); + } + }); + + if (result == 1) + { + source.sendSuccess(successText("taterzens.command.path_editor.add.success", "(" + pos.toShortString() + ")"), false); + return 1; + } + else + { + source.sendFailure(errorText("taterzens.command.path_editor.add.failure", "(" + pos.toShortString() + ")")); + return 0; + } + } + + private static int removePathNodeByIndex(CommandContext context) throws CommandSyntaxException + { + CommandSourceStack source = context.getSource(); + ServerPlayer player = source.getPlayerOrException(); + int idx = IntegerArgumentType.getInteger(context, "index") - 1; + AtomicReference pathNode = new AtomicReference<>(); + + AtomicBoolean success = new AtomicBoolean(false); + NpcCommand.selectedTaterzenExecutor(player, taterzen -> { + try + { + if (!taterzen.getPathTargets().isEmpty()) + { + pathNode.set(taterzen.getPathTargets().get(idx)); + taterzen.removePathTarget(pathNode.get()); + + // Revert blocks from redstone to before in case player is in editor mode + if(((ITaterzenEditor) player).getEditorMode() == ITaterzenEditor.EditorMode.PATH) { + player.connection.send(new ClientboundBlockUpdatePacket(pathNode.get(), player.getLevel().getBlockState(pathNode.get()))); + } + + source.sendSuccess(successText("taterzens.command.path_editor.remove.success", "(" + pathNode.get().toShortString() + ")"), false); + } + else + { + source.sendSuccess(successText("taterzens.command.path_editor.empty"), false); + } + + success.set(true); + } + catch (IndexOutOfBoundsException err) + { + source.sendFailure(errorText("taterzens.command.path_editor.remove.outofbounds", + Integer.toString(idx + 1), + "1", + Integer.toString(taterzen.getPathTargets().size())) + ); + success.set(false); + } + }); + + if (success.get()) + { + return 1; + } + else + { + source.sendFailure(errorText("taterzens.command.path_editor.remove.failure.index", Integer.toString(idx + 1))); + return 0; + } + } + + private static int removeRecentPathNode(CommandContext context) throws CommandSyntaxException + { + CommandSourceStack source = context.getSource(); + ServerPlayer player = source.getPlayerOrException(); + + int result = NpcCommand.selectedTaterzenExecutor(player, taterzen -> { + ArrayList pathNodes = taterzen.getPathTargets(); + if (!pathNodes.isEmpty()) + { + int lastIndex = pathNodes.size() - 1; + BlockPos lastPos = pathNodes.get(lastIndex); + taterzen.removePathTargetByIndex(lastIndex); + + // Revert blocks from redstone to before in case player is in editor mode + if(((ITaterzenEditor) player).getEditorMode() == ITaterzenEditor.EditorMode.PATH) { + player.connection.send(new ClientboundBlockUpdatePacket(lastPos, player.getLevel().getBlockState(lastPos))); + } + + source.sendSuccess(successText("taterzens.command.path_editor.remove.success", "(" + lastPos.toShortString() + ")"), false); + } + else + { + source.sendSuccess(successText("taterzens.command.path_editor.empty"), false); + } + }); + + if (result == 1) + { + return 1; + } + else + { + source.sendFailure(errorText("taterzens.command.path_editor.remove.failure")); + return 0; + } + } + private static int clearTaterzenPath(CommandContext context) throws CommandSyntaxException { CommandSourceStack source = context.getSource(); Entity entity = source.getEntityOrException(); diff --git a/common/src/main/java/org/samo_lego/taterzens/event/BlockEvent.java b/common/src/main/java/org/samo_lego/taterzens/event/BlockEvent.java index 5e97fd656..d49998b8a 100644 --- a/common/src/main/java/org/samo_lego/taterzens/event/BlockEvent.java +++ b/common/src/main/java/org/samo_lego/taterzens/event/BlockEvent.java @@ -1,6 +1,7 @@ package org.samo_lego.taterzens.event; import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.ChatType; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionResult; @@ -8,26 +9,46 @@ import net.minecraft.world.level.Level; import org.samo_lego.taterzens.interfaces.ITaterzenEditor; +import java.util.ArrayList; + +import static org.samo_lego.taterzens.util.TextUtil.errorText; +import static org.samo_lego.taterzens.util.TextUtil.successText; + public class BlockEvent { /** * Used if player is in path edit mode. Interacted blocks are removed from the path * of selected {@link org.samo_lego.taterzens.npc.TaterzenNPC}. * - * @param Player player breaking the block. + * @param player player breaking the block. * @param world world where block is being broken. * @param blockPos position of block interaction. * * @return FAIL if player has selected NPC and is in path edit mode, otherwise PASS. */ - public static InteractionResult onBlockInteract(Player Player, Level world, BlockPos blockPos) { - if(Player instanceof ServerPlayer) { // Prevents crash on client - ITaterzenEditor player = (ITaterzenEditor) Player; - if(player.getNpc() != null && ((ITaterzenEditor) Player).getEditorMode() == ITaterzenEditor.EditorMode.PATH) { - player.getNpc().removePathTarget(blockPos); - ((ServerPlayer) player).connection.send(new ClientboundBlockUpdatePacket(blockPos, world.getBlockState(blockPos))); - return InteractionResult.FAIL; + public static InteractionResult onBlockInteract(Player player, Level world, BlockPos blockPos) + { + ITaterzenEditor editorPlayer = (ITaterzenEditor) player; + if(editorPlayer.getNpc() != null && ((ITaterzenEditor) player).getEditorMode() == ITaterzenEditor.EditorMode.PATH) { + + ServerPlayer serverPlayer = ((ServerPlayer) editorPlayer); + ArrayList pathNodes = editorPlayer.getNpc().getPathTargets(); + + if (!pathNodes.isEmpty()) { + int idx = pathNodes.indexOf(blockPos); + if (idx >= 0) { + editorPlayer.getNpc().removePathTargetByIndex(idx); + serverPlayer.connection.send(new ClientboundBlockUpdatePacket(blockPos, world.getBlockState(blockPos))); + serverPlayer.sendMessage(successText("taterzens.command.path_editor.remove.success", "(" + blockPos.toShortString() + ")"), ChatType.SYSTEM, serverPlayer.getUUID()); + } + else { + serverPlayer.sendMessage(errorText("taterzens.command.path_editor.remove.404", "(" + blockPos.toShortString() + ")"), ChatType.SYSTEM, serverPlayer.getUUID()); + } + } + else { + serverPlayer.sendMessage(successText("taterzens.command.path_editor.empty", "(" + blockPos.toShortString() + ")"), ChatType.SYSTEM, ((ServerPlayer) editorPlayer).getUUID()); } + return InteractionResult.FAIL; } return InteractionResult.PASS; diff --git a/common/src/main/java/org/samo_lego/taterzens/mixin/ServerPlayInteractionManagerMixin.java b/common/src/main/java/org/samo_lego/taterzens/mixin/ServerPlayInteractionManagerMixin.java index b1be9e875..3b31677b9 100644 --- a/common/src/main/java/org/samo_lego/taterzens/mixin/ServerPlayInteractionManagerMixin.java +++ b/common/src/main/java/org/samo_lego/taterzens/mixin/ServerPlayInteractionManagerMixin.java @@ -2,6 +2,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.network.chat.ChatType; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; import net.minecraft.server.level.ServerPlayer; @@ -15,6 +16,8 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import static org.samo_lego.taterzens.util.TextUtil.successText; + @Mixin(ServerPlayerGameMode.class) public class ServerPlayInteractionManagerMixin { @@ -39,10 +42,12 @@ public class ServerPlayInteractionManagerMixin { ) private void onAttackBlock(BlockPos blockPos, ServerboundPlayerActionPacket.Action playerAction, Direction direction, int worldHeight, CallbackInfo ci) { if (playerAction == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) { - ITaterzenEditor player = (ITaterzenEditor) this.player; - if(player.getNpc() != null && ((ITaterzenEditor) this.player).getEditorMode() == ITaterzenEditor.EditorMode.PATH) { - player.getNpc().addPathTarget(blockPos); - ((ServerPlayer) player).connection.send(new ClientboundBlockUpdatePacket(blockPos, Blocks.REDSTONE_BLOCK.defaultBlockState())); + ITaterzenEditor editorPlayer = (ITaterzenEditor) this.player; + if(editorPlayer.getNpc() != null && ((ITaterzenEditor) this.player).getEditorMode() == ITaterzenEditor.EditorMode.PATH) { + editorPlayer.getNpc().addPathTarget(blockPos); + ServerPlayer serverPlayer = ((ServerPlayer) editorPlayer); + serverPlayer.connection.send(new ClientboundBlockUpdatePacket(blockPos, Blocks.REDSTONE_BLOCK.defaultBlockState())); + serverPlayer.sendMessage(successText("taterzens.command.path_editor.add.success", "(" + blockPos.toShortString() + ")"), ChatType.SYSTEM, serverPlayer.getUUID()); ci.cancel(); } } diff --git a/common/src/main/java/org/samo_lego/taterzens/npc/TaterzenNPC.java b/common/src/main/java/org/samo_lego/taterzens/npc/TaterzenNPC.java index e5bab878e..928eccbd6 100644 --- a/common/src/main/java/org/samo_lego/taterzens/npc/TaterzenNPC.java +++ b/common/src/main/java/org/samo_lego/taterzens/npc/TaterzenNPC.java @@ -455,6 +455,13 @@ public void removePathTarget(BlockPos blockPos) { this.npcData.pathTargets.remove(blockPos); } + /** Removes node from path targets by index. + * @param index Index of the entry in the ArrayList. + * */ + public void removePathTargetByIndex(int index) { + this.npcData.pathTargets.remove(index); + } + /** * Gets the path nodes / targets. * @return array list of block positions. diff --git a/common/src/main/resources/data/taterzens/lang/en_us.json b/common/src/main/resources/data/taterzens/lang/en_us.json index 92052621b..f11dd7569 100644 --- a/common/src/main/resources/data/taterzens/lang/en_us.json +++ b/common/src/main/resources/data/taterzens/lang/en_us.json @@ -45,6 +45,15 @@ "taterzens.command.path_editor.desc.1": "Left click the blocks to add them to the path.", "taterzens.command.path_editor.desc.2": "Right click the blocks to remove them to the path.", "taterzens.command.path_editor.clear": "Path for %s was cleared successfully.", + "taterzens.command.path_editor.list": "Currently set path nodes:", + "taterzens.command.path_editor.empty": "No path nodes set.", + "taterzens.command.path_editor.add.success": "Successfully added node %s to path.", + "taterzens.command.path_editor.add.failure": "Could not add node %s to path.", + "taterzens.command.path_editor.remove.success": "Successfully removed node %s from path.", + "taterzens.command.path_editor.remove.failure.index": "Could not remove node number %s from path.", + "taterzens.command.path_editor.remove.failure": "Could not remove the last node from path.", + "taterzens.command.path_editor.remove.404": "Block position %s is not part of the path.", + "taterzens.command.path_editor.remove.outofbounds": "Could not remove node with index %s from path. Minimum index is %s, maximum is %s.", "taterzens.command.profession.add": "Profession %s has been added successfully.", "taterzens.command.profession.remove": "Profession %s has been removed successfully.", "taterzens.command.profession.error.404": "No professions with id %s were found.", diff --git a/common/src/main/resources/data/taterzens/perms/permissions.toml b/common/src/main/resources/data/taterzens/perms/permissions.toml index ecc189154..c553bcbd8 100644 --- a/common/src/main/resources/data/taterzens/perms/permissions.toml +++ b/common/src/main/resources/data/taterzens/perms/permissions.toml @@ -73,6 +73,8 @@ permissions = [ "taterzens.npc.edit.path", # Clears Taterzen's path. "taterzens.npc.edit.path.clear", + # Lists all path entries of a Taterzen + "taterzens.npc.edit.path.list", # Editing Taterzen's messages # Enters message adding mode of Taterzen. diff --git a/docs/getting_started/path.md b/docs/getting_started/path.md index 80e55efed..0235f987a 100644 --- a/docs/getting_started/path.md +++ b/docs/getting_started/path.md @@ -15,24 +15,115 @@ title: Path ## Setting path nodes -Path nodes are targets where Taterzen will move. -To edit those, you need to enter the path editor by running +Path nodes are targets where Taterzen will move to. +To edit those, you have two possibilities for that: interactive or command based. It is also possible to mix those +editing methods. + +### Interactive method + +If you would like to edit the nodes in an interactive manner, i.e. with visual feedback in the world and by clicking on +blocks in your environment to set or remove nodes, you need to enter the path editor by running: ``` /npc edit path ``` -*(To exit the editor, run the same command again.)* +To exit the editor, run the same command again. + + +#### 1. Adding nodes + +To add a block as a node to the Taterzen's path, simply left click on it. It will temporarily be switched to a redstone +block to aid you during the path building process. But don't worry, this will be reversed as soon as you leave the +editor. + +#### 2. Removing nodes + +To remove a node from the path, just right click on it. + +### Command based method + +You can add or remove blocks using commands. If you would like to benefit from the visual feedback provided in the +interactive method you can just enter the interactive editor as described in section [Interactive method](#interactive-method) +and run your commands. (Or click on blocks if you prefer that from time to time.) + +#### 1. Listing currently set path nodes + +If you want to retrieve an ordered list of path nodes (which will be the coordinates you set), use the following command: +``` +/npc edit path list +``` + +You will get an output looking something like this _(in case you have already set a path for the selected Taterzen)_: +``` +Currently set path nodes: +1: (24, 72, -11) +2: (29, 71, -7) +3: (27, 73, -1) +4: (31, 72, -9) +5: (28, 72, -15) +6: (24, 72, -4) +``` +The numbers within the smooth brackets, e.g. `(24, 72, -11)`, are the x, y and z coordinates of the respective path +node. (See also: [Minecraft Wiki on Coordinates](https://minecraft.fandom.com/wiki/Coordinates)) + +The numbers in front of the coordinates are the _index numbers_ of the corresponding coordinate-list-entry. You will +refer to those index numbers in case you want to remove nodes via commands. + +#### 2. Adding nodes + +Adding nodes via a command is simple. You just run the command: + +``` +/npc edit path add +``` + +and provide a valid block position for ``. A valid block position means: The block is currently loaded and in the +same dimension as you are. + +The position `` just consists of three coordinate values for x, y, and z. You can utilize +[relative](https://minecraft.fandom.com/wiki/Coordinates#Relative_world_coordinates) and +[local](https://minecraft.fandom.com/wiki/Coordinates#Local_coordinates) coordinate descriptions from vanilla minecraft. +(I.e. `~ ~ ~` or `^ ^ ^`). + +For example let's say you want to add a node at position `x = 29, y = 71, z = -7`. This can be done like this: +``` +/npc edit path add 29 71 -7 +``` +Another example: if you want to add the block you are standing on, do: +``` +/npc edit path add ~ ~-1 ~ +``` -### 1. Adding blocks +#### 3. Removing nodes -To add a block to path, simply left click on it. It will temporarily be switched to redstone block. +In order to remove nodes via commands you can either remove the most recently set node (i.e. the last node in the list), +or you can remove specific nodes via their list entry index number. -### 2. Removing blocks +To remove the most recent node just run: +``` +/npc edit path remove +``` -To remove a block from path, right click on it. +If you want to get rid of a specific node in your list instead, the command has this structure: +``` +/npc edit path remove index +``` + +For example, let's say you want to remove node number 3 from your path: +``` +/npc edit path remove index 3 +``` +_Note:_ This node will be removed and all other nodes after node number 3, i.e. nodes 4, 5, 6, etc., move "one position +up", which means that their list index will be lowered by 1. Consequently, node 4 will be the new node number 3, node 5 +will be the new number 4 and so on. Keep that in mind when removing nodes via indices! +You can also use tab-completion suggestions, where currently available list indices are provided. +Don't worry if you make a mistake and are going to remove an entry which does not exist, i.e. if the index you pass to +the command is lower than 1 or greater than the last available list index. You will just get an error message in that +case, helping you to prevent doing that mistake again. If the list is empty anyway you will get a confirmation message +accordingly. -## Clear whole path +#### 4. Clearing the whole path To remove all nodes, run ``` /npc edit path clear diff --git a/fabric/src/main/java/org/samo_lego/taterzens/fabric/event/BlockInteractEventImpl.java b/fabric/src/main/java/org/samo_lego/taterzens/fabric/event/BlockInteractEventImpl.java index 9fd335164..63856afd0 100644 --- a/fabric/src/main/java/org/samo_lego/taterzens/fabric/event/BlockInteractEventImpl.java +++ b/fabric/src/main/java/org/samo_lego/taterzens/fabric/event/BlockInteractEventImpl.java @@ -25,6 +25,11 @@ public BlockInteractEventImpl() { */ @Override public InteractionResult interact(Player player, Level world, InteractionHand hand, BlockHitResult blockHitResult) { - return BlockEvent.onBlockInteract(player, world, blockHitResult.getBlockPos()); + // check in if clause prevents crash (check for server side) on clients + // and prevents executing event handler twice (check for main hand) + if (!player.getLevel().isClientSide() && hand == InteractionHand.MAIN_HAND) { + return BlockEvent.onBlockInteract(player, world, blockHitResult.getBlockPos()); + } + return InteractionResult.PASS; } } diff --git a/forge/src/main/java/org/samo_lego/taterzens/forge/event/EventHandler.java b/forge/src/main/java/org/samo_lego/taterzens/forge/event/EventHandler.java index cc6456bc9..6adc89747 100644 --- a/forge/src/main/java/org/samo_lego/taterzens/forge/event/EventHandler.java +++ b/forge/src/main/java/org/samo_lego/taterzens/forge/event/EventHandler.java @@ -2,6 +2,7 @@ import com.mojang.brigadier.CommandDispatcher; import net.minecraft.commands.CommandSourceStack; +import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; import net.minecraftforge.event.RegisterCommandsEvent; @@ -20,9 +21,13 @@ public EventHandler() { @SubscribeEvent public static void onBlockInteract(PlayerInteractEvent.RightClickBlock event) { - Player player = event.getPlayer(); - if(BlockEvent.onBlockInteract(player, player.getCommandSenderWorld(), event.getPos()) == InteractionResult.FAIL) { - event.setCanceled(true); + // check in if clause prevents crash (check for server side) on clients + // and prevents executing event handler twice (check for main hand) + if (event.getSide().isServer() && event.getHand() == InteractionHand.MAIN_HAND) { + Player player = event.getPlayer(); + if (BlockEvent.onBlockInteract(player, player.getCommandSenderWorld(), event.getPos()) == InteractionResult.FAIL) { + event.setCanceled(true); + } } }