diff --git a/src/main/java/tech/mctown/cma/CMALogger.java b/src/main/java/tech/mctown/cma/CMALogger.java index 88e10d4..16ac888 100644 --- a/src/main/java/tech/mctown/cma/CMALogger.java +++ b/src/main/java/tech/mctown/cma/CMALogger.java @@ -12,6 +12,10 @@ public static void toConsole(String message) { System.out.println(message); } + public static void debug(String message) { + System.out.println("\033[34m[CMA DEBUG]\033[0m" + "\033[33m" + message + "\033[0m"); + } + public static void toGame(String message, ServerPlayerEntity player) { player.sendMessage(Text.of(message), false); } @@ -26,4 +30,9 @@ public static void toGame(String message, MinecraftServer server) { public static void toGame(String message, PlayerEntity player) { player.sendMessage(Text.of(message)); } + + public static void error(String s, Exception e) { + System.out.println("\033[31m[CMA ERROR]\033[0m" + "\033[33m" + s + "\033[0m"); + e.printStackTrace(); + } } diff --git a/src/main/java/tech/mctown/cma/CMAUtils.java b/src/main/java/tech/mctown/cma/CMAUtils.java new file mode 100644 index 0000000..3ff554a --- /dev/null +++ b/src/main/java/tech/mctown/cma/CMAUtils.java @@ -0,0 +1,50 @@ +package tech.mctown.cma; + +import carpet.CarpetSettings; +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.text.TextColor; +import net.minecraft.util.Formatting; + +import java.util.Map; + +public class CMAUtils { + public static void executeCommand(ServerPlayerEntity player, String command) { + if (player == null || command == null || command.trim().isEmpty()) { + throw new IllegalArgumentException("Player or command cannot be null or empty"); + } +// 若带有 / 则去掉 + if (command.startsWith("/")) { + command = command.substring(1); + } + var server = player.getServer(); + if (server == null) { + throw new IllegalStateException("Player is not on a server"); + } + + ServerCommandSource source = player.getCommandSource(); + CommandDispatcher dispatcher = server.getCommandManager().getDispatcher(); + + try { + // 解析和执行命令 + var parseResults = dispatcher.parse(command, source); + dispatcher.execute(parseResults); + } catch (Exception e) { + Text text = Text.literal(getTranslation("carpet.commandWentWrong")) + .setStyle(Style.EMPTY.withColor(TextColor.fromFormatting(Formatting.RED))).append(Text.literal(" /" + command + ": " + e.getMessage()) + .setStyle(Style.EMPTY.withColor(TextColor.fromFormatting(Formatting.RED)).withUnderline(true))); + player.sendMessage(text, false); + CMALogger.debug("Failed to execute command: " + command + e.getMessage()); + } + } + + public static String getTranslation(String key) { + Map lang = CMATranslations.getTranslationFromResourcePath(CarpetSettings.language); + String translationText = lang.get(key); + CMALogger.debug("Translation with key" + key + ": " + translationText); + return lang.get(key); + } +} \ No newline at end of file diff --git a/src/main/java/tech/mctown/cma/mixins/runCommandOnSign/AbstractSignBlockMixin.java b/src/main/java/tech/mctown/cma/mixins/runCommandOnSign/AbstractSignBlockMixin.java new file mode 100644 index 0000000..1f38f23 --- /dev/null +++ b/src/main/java/tech/mctown/cma/mixins/runCommandOnSign/AbstractSignBlockMixin.java @@ -0,0 +1,72 @@ +package tech.mctown.cma.mixins.runCommandOnSign; + +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.block.entity.SignText; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Items; +import net.minecraft.server.command.CommandOutput; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec2f; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import tech.mctown.cma.CMALogger; +import tech.mctown.cma.CMASettings; +import tech.mctown.cma.CMAUtils; + +import java.util.Arrays; +import java.util.Objects; + +@Mixin(net.minecraft.block.AbstractSignBlock.class) // 向告示牌注入代码 +public class AbstractSignBlockMixin { + @Inject(method = "onUse", at = @At("HEAD"), cancellable = true) + public void cancelOpenGUI(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit, CallbackInfoReturnable cir) { + if (CMASettings.runCommandOnSign) { + if (player instanceof ServerPlayerEntity) { + if (world.getBlockEntity(pos) instanceof SignBlockEntity signBlockEntity) { + if (player.getMainHandStack().isOf(Items.AIR) && player.isSneaking()) { + CMALogger.debug("AbstractSignBlockMixin cancelOpenGUI"); + boolean isFront = signBlockEntity.isPlayerFacingFront(player); + SignText texts = signBlockEntity.getText(isFront); + Text[] text = texts.getMessages(false); + CMALogger.debug(Arrays.toString(text)); + // 若告示牌上的文本以 / 开头,则执行命令 + StringBuilder command = new StringBuilder(text[0].getString()); + if (command.toString().startsWith("/")) { + for (int i = 1; i < 4; i++) { + if (Objects.equals(text[i].getString(), "")) { + continue; + } + command.append(text[i].getString()); + } + CMALogger.debug("Command: " + command); + CMAUtils.executeCommand((ServerPlayerEntity) player, command.toString()); +// Objects.requireNonNull(player.getServer()).getCommandManager().executeWithPrefix(createCommandSource(player, world, pos), command.toString()); + } + cir.setReturnValue(ActionResult.SUCCESS); + } + } + } + } + } + + @Unique + private ServerCommandSource createCommandSource(PlayerEntity player, World world, BlockPos pos) { + String string = player == null ? "Sign" : player.getName().getString(); + Text text = (Text) (player == null ? Text.literal("Sign") : player.getDisplayName()); + return new ServerCommandSource(CommandOutput.DUMMY, Vec3d.ofCenter(pos), Vec2f.ZERO, (ServerWorld) world, 2, string, text, world.getServer(), player); + } +} diff --git a/src/main/java/tech/mctown/cma/mixins/runCommandOnSign/SignBlockEntityMixin.java b/src/main/java/tech/mctown/cma/mixins/runCommandOnSign/SignBlockEntityMixin.java index 82be6f3..7666b32 100644 --- a/src/main/java/tech/mctown/cma/mixins/runCommandOnSign/SignBlockEntityMixin.java +++ b/src/main/java/tech/mctown/cma/mixins/runCommandOnSign/SignBlockEntityMixin.java @@ -1,22 +1,63 @@ package tech.mctown.cma.mixins.runCommandOnSign; +import net.minecraft.block.AbstractSignBlock; import net.minecraft.block.SignBlock; import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.block.entity.SignText; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Items; +import net.minecraft.network.message.MessageType; +import net.minecraft.network.packet.s2c.play.GameMessageS2CPacket; import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.filter.FilteredMessage; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import tech.mctown.cma.CMALogger; import tech.mctown.cma.CMASettings; +import tech.mctown.cma.CMAUtils; -@Mixin(SignBlockEntity.class) +import javax.swing.text.Position; +import java.awt.*; +import java.util.List; + + +@Mixin(SignBlockEntity.class) // 向告示牌实体注入代码 public abstract class SignBlockEntityMixin { -// + // + // 如果告示牌上的文本以 / 开头,且玩家手持物品为空,且玩家未潜行,则执行命令 + // 倘若不满足,则执行原版告示牌的编辑功能 + @Inject(method = "tryChangeText", at = @At("HEAD"), cancellable = true) + public void PreventChangeTextWhenEmptyHands(PlayerEntity player, boolean front, List messages, CallbackInfo ci) { + if (CMASettings.runCommandOnSign) { + if (player instanceof ServerPlayerEntity) { + CMALogger.debug("Player is trying to change the text, checking sign text"); + ServerWorld world = (ServerWorld) player.getEntityWorld(); + BlockPos pos = ((SignBlockEntity) (Object) this).getPos(); + if (world.getBlockEntity(pos) instanceof SignBlockEntity signBlockEntity) { + boolean isFront = signBlockEntity.isPlayerFacingFront(player); + SignText texts = signBlockEntity.getText(isFront); + Text[] text = texts.getMessages(false); + CMALogger.debug("Sign text: " + text[0].getString()); + if (text[0].getString().startsWith("/")) { + CMALogger.debug("Player is trying to change the text, but the text starts with /"); + Text message = Text.literal(CMAUtils.getTranslation("carpet.runCommandOnSignTips")); + player.sendMessage(message, false); + } + } + } + } + } +} // @Shadow + // protected abstract Text[] getTexts(boolean filtered); // // @Inject(method = "onActivate", at = @At("HEAD")) @@ -36,4 +77,3 @@ public abstract class SignBlockEntityMixin { // } // } // } -} diff --git a/src/main/resources/assets/cma/lang/en_us.json b/src/main/resources/assets/cma/lang/en_us.json index 7ae557d..e91ae7b 100644 --- a/src/main/resources/assets/cma/lang/en_us.json +++ b/src/main/resources/assets/cma/lang/en_us.json @@ -4,5 +4,7 @@ "carpet.rule.flintAndSteelActivatesObserver.desc": "Observer will be activated when player uses Flint and Steel on it.", "carpet.rule.commandDumpEntity.desc": "Enable the usage of /dumpentity command.", "carpet.rule.fakePlayerGamemode.desc": "Op players can spawn bots in different gamemodes.", - "carpet.rule.fakePlayerColor.desc": "Fake players will have different name colors based on their gamemodes." + "carpet.rule.fakePlayerColor.desc": "Fake players will have different name colors based on their gamemodes.", + "carpet.commandWentWrong": "Command failed, error message as follows:", + "carpet.runCommandOnSignTips": "Tips: If you want to run a command on the sign, right-click it while sneaking and don't holding anything in the main hand." } \ No newline at end of file diff --git a/src/main/resources/assets/cma/lang/zh_cn.json b/src/main/resources/assets/cma/lang/zh_cn.json index 1d579b0..e06b46c 100644 --- a/src/main/resources/assets/cma/lang/zh_cn.json +++ b/src/main/resources/assets/cma/lang/zh_cn.json @@ -3,5 +3,8 @@ "carpet.rule.runCommandOnSign.extra": "命令必须以'/'开头", "carpet.rule.flintAndSteelActivatesObserver.desc": "打火石可以激活侦测器", "carpet.rule.commandDumpEntity.desc": "开启dumpentity命令,打印所有的实体信息", - "carpet.rule.fakePlayerGamemode.desc": "OP可以生成不同游戏模式的假人" + "carpet.rule.fakePlayerGamemode.desc": "OP可以生成不同游戏模式的假人", + "carpet.commandWentWrong": "指令出现错误,错误信息如下:", + "carpet.runCommandOnSignTips": "提示:如果你想运行告示牌上的指令,请空手潜行右键点击告示牌", + "carpet.rule.fakePlayerColor.desc": "Fake players will have different name colors based on their gamemodes." } \ No newline at end of file diff --git a/src/main/resources/cma.mixins.json b/src/main/resources/cma.mixins.json index a093ae5..c36b4c4 100644 --- a/src/main/resources/cma.mixins.json +++ b/src/main/resources/cma.mixins.json @@ -3,9 +3,10 @@ "package": "tech.mctown.cma.mixins", "compatibilityLevel": "JAVA_21", "mixins": [ + "fakePlayerGamemode.PlayerCommandMixin", "flintAndSteelActivatesObserver.FlintAndSteelItemMixin", - "runCommandOnSign.SignBlockEntityMixin", - "fakePlayerGamemode.PlayerCommandMixin" + "runCommandOnSign.AbstractSignBlockMixin", + "runCommandOnSign.SignBlockEntityMixin" ], "server": [], "client": [],