From 99906ad51f57336864cce9a0f36ff5079cec944a Mon Sep 17 00:00:00 2001 From: 70CentsApple Date: Sat, 9 Sep 2023 14:17:16 +0800 Subject: [PATCH] feat: chat responser - move sendPlayerChat method from MacroChat.java to ChatTools.java --- gradle.properties | 2 +- .../net/apple70cents/chattools/ChatTools.java | 35 +++++ .../chattools/config/ModClothConfig.java | 51 ++++++- .../chattools/config/ModConfigFallback.java | 5 + .../features/chatresponser/ChatResponser.java | 129 ++++++++++++++++++ .../features/quickchat/MacroChat.java | 22 +-- .../features/quickchat/QuickRepeat.java | 2 +- .../chattools/mixin/ChatHudMixin.java | 4 + .../assets/chattools/lang/en_us.json | 19 ++- .../assets/chattools/lang/zh_cn.json | 17 ++- 10 files changed, 257 insertions(+), 29 deletions(-) create mode 100644 src/main/java/net/apple70cents/chattools/features/chatresponser/ChatResponser.java diff --git a/gradle.properties b/gradle.properties index 5e1c4db..15e91be 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,6 +11,6 @@ loader_version=0.14.21 fabric_version=0.83.0+1.20 # Mod Properties -mod_version = 1.3.4 +mod_version = 1.4.0 maven_group = net.apple70cents archives_base_name = chattools diff --git a/src/main/java/net/apple70cents/chattools/ChatTools.java b/src/main/java/net/apple70cents/chattools/ChatTools.java index 72fcc42..f650d0e 100644 --- a/src/main/java/net/apple70cents/chattools/ChatTools.java +++ b/src/main/java/net/apple70cents/chattools/ChatTools.java @@ -9,6 +9,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.apple70cents.chattools.config.ModClothConfig; import net.apple70cents.chattools.config.ModConfigFallback; +import net.apple70cents.chattools.features.chatnotifier.ChatNotifier; import net.apple70cents.chattools.features.chatnotifier.SystemToast; import net.apple70cents.chattools.features.quickchat.MacroChat; import net.apple70cents.chattools.features.quickchat.QuickRepeat; @@ -18,12 +19,15 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.util.InputUtil; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Pair; +import net.minecraft.util.StringHelper; import net.minecraft.util.Util; +import org.apache.commons.lang3.StringUtils; import org.lwjgl.glfw.GLFW; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -259,6 +263,37 @@ public static String wash_message(String str) { return Pattern.compile("§.").matcher(str).replaceAll(""); } + public static void sendPlayerChat(String chatText, boolean forceDisableInjector) { + boolean oldStatus = config.injectorEnabled; + // 临时关闭 + if (forceDisableInjector) { + config.injectorEnabled = false; + } + ChatNotifier.setJustSentMessage(true); + chatText = StringHelper.truncateChat(StringUtils.normalizeSpace(chatText.trim())); + if (!chatText.isEmpty()) { + MinecraftClient.getInstance().inGameHud.getChatHud().addToMessageHistory(chatText); + ClientPlayerEntity player = MinecraftClient.getInstance().player; + if (chatText.startsWith("/")) { + if (player != null) { + player.networkHandler.sendChatCommand(chatText.substring(1)); + } + } else { + if (player != null) { + player.networkHandler.sendChatMessage(chatText); + } + } + } + // 重新开启 + if (forceDisableInjector) { + config.injectorEnabled = oldStatus; + } + } + + public static void sendPlayerChat(String chatText) { + sendPlayerChat(chatText, false); + } + /** * 打开 GUI * diff --git a/src/main/java/net/apple70cents/chattools/config/ModClothConfig.java b/src/main/java/net/apple70cents/chattools/config/ModClothConfig.java index edc110b..0753b5d 100644 --- a/src/main/java/net/apple70cents/chattools/config/ModClothConfig.java +++ b/src/main/java/net/apple70cents/chattools/config/ModClothConfig.java @@ -10,6 +10,7 @@ import me.shedaniel.clothconfig2.impl.builders.SubCategoryBuilder; import net.apple70cents.chattools.ChatTools; import net.apple70cents.chattools.features.chatbubbles.BubbleRenderer; +import net.apple70cents.chattools.features.chatresponser.ChatResponser; import net.apple70cents.chattools.features.quickchat.MacroChat; import net.minecraft.client.MinecraftClient; import net.minecraft.client.util.InputUtil; @@ -140,6 +141,10 @@ public enum CustomModifier { add(new BubbleRenderer.BubbleRuleUnit(".*\\.hypixel\\.net", "(?\\S+): (?.*)", false)); add(new BubbleRenderer.BubbleRuleUnit()); }}; + public boolean chatResponserEnabled = false; + public List responserRuleList = new ArrayList<>() {{ + add(new ChatResponser.ResponserRuleUnit()); + }}; /** * 保存配置 @@ -205,7 +210,7 @@ public static ConfigBuilder getConfigBuilder() { builder.setSavingRunnable(ModClothConfig::save); ConfigEntryBuilder eb = builder.entryBuilder(); final ModClothConfig config = ModClothConfig.get(); - Function> REGEX_COMPILE_ERROR_SUPPLIER = (v) -> { + final Function> REGEX_COMPILE_ERROR_SUPPLIER = (v) -> { try { Pattern.compile(v); return Optional.empty(); @@ -213,7 +218,7 @@ public static ConfigBuilder getConfigBuilder() { return Optional.of(Text.of(e.getDescription())); } }; - Function> REGEX_COMPILE_ERROR_SUPPLIER_ALLOW_STAR = (v) -> { + final Function> REGEX_COMPILE_ERROR_SUPPLIER_ALLOW_STAR = (v) -> { if ("*".equals(v)) return Optional.empty(); try { Pattern.compile(v); @@ -222,7 +227,7 @@ public static ConfigBuilder getConfigBuilder() { return Optional.of(Text.of(e.getDescription())); } }; - Function> REGEX_COMPILE_ERROR_SUPPLIER_REQUIRE_GROUPS = (v) -> { + final Function> REGEX_COMPILE_ERROR_SUPPLIER_REQUIRE_GROUPS = (v) -> { try { Pattern.compile(v); if (v.contains("") && v.contains("")) { @@ -448,6 +453,46 @@ public static ConfigBuilder getConfigBuilder() { } })); + // ========== Chat Responser Category ========== + ConfigCategory chatResponserCategory = builder.getOrCreateCategory(Text.translatable("key.chattools.category.responser")); + // 启用聊天回应 + chatResponserCategory.addEntry(eb.startBooleanToggle(Text.translatable("text.config.chattools.option.responserEnabled"), config.chatResponserEnabled).setDefaultValue(new ModConfigFallback().chatResponserEnabled).setSaveConsumer(v -> config.chatResponserEnabled = v).build()); + // 回应规则 + if (MinecraftClient.getInstance().getCurrentServerEntry() == null) { + label = Text.translatable("text.config.chattools.option.responserRulesList", "§f-"); + } else { + label = Text.translatable("text.config.chattools.option.responserRulesList", "§f" + MinecraftClient.getInstance().getCurrentServerEntry().address); + } + chatResponserCategory.addEntry(new NestedListListEntry>(label, config.responserRuleList, true, // 启用默认展开 + () -> Optional.of(new net.minecraft.text.MutableText[]{Text.translatable("text.config.chattools.option.responserRulesList.@Tooltip")}), // Tooltip + v -> config.responserRuleList = v, // Save Consumer + () -> new ModConfigFallback().responserRuleList, // 默认值 + eb.getResetButtonKey(), // 重置按钮键值 + true, // 启用删除键 + true, // 在列表前面插入新内容 + (responserRuleUnit, __) -> { + AtomicReference responserUnitRef = new AtomicReference<>(new ChatResponser.ResponserRuleUnit()); + if (responserRuleUnit == null) { // 新建 + Text displayText = Text.translatable("text.config.chattools.option.responserNew"); + ChatResponser.ResponserRuleUnit defaultRule = new ChatResponser.ResponserRuleUnit(); + responserUnitRef.set(defaultRule); // 设置初始值 + return new MultiElementListEntry<>(displayText, defaultRule, new ArrayList<>() {{ + add(eb.startStrField(Text.translatable("text.config.chattools.option.responserAddress"), defaultRule.getAddress()).setTooltip(Text.translatable("text.config.chattools.option.responserAddress.@Tooltip")).setDefaultValue(defaultRule.getAddress()).setSaveConsumer(responserUnitRef.get()::setAddress).setErrorSupplier(REGEX_COMPILE_ERROR_SUPPLIER_ALLOW_STAR).build()); + add(eb.startStrField(Text.translatable("text.config.chattools.option.responserMatchPattern"), defaultRule.getPattern()).setTooltip(Text.translatable("text.config.chattools.option.responserMatchPattern.@Tooltip")).setDefaultValue(defaultRule.getPattern()).setSaveConsumer(responserUnitRef.get()::setPattern).setErrorSupplier(REGEX_COMPILE_ERROR_SUPPLIER).build()); + add(eb.startStrField(Text.translatable("text.config.chattools.option.responserString"), defaultRule.getMessage()).setTooltip(Text.translatable("text.config.chattools.option.responserString.@Tooltip")).setDefaultValue(defaultRule.getMessage()).setSaveConsumer(responserUnitRef.get()::setMessage).build()); + add(eb.startBooleanToggle(Text.translatable("text.config.chattools.option.responserForceDisableInjector"), defaultRule.isForceDisableInjector()).setTooltip(Text.translatable("text.config.chattools.option.responserForceDisableInjector.@Tooltip")).setDefaultValue(defaultRule.isForceDisableInjector()).setSaveConsumer(responserUnitRef.get()::setForceDisableInjector).build()); + }}, false); + } else { // 现有 + String colorPrefix = ("*".equals(responserRuleUnit.getAddress()) || (MinecraftClient.getInstance().getCurrentServerEntry() != null && Pattern.compile(responserRuleUnit.getAddress()).matcher(MinecraftClient.getInstance().getCurrentServerEntry().address).matches())) ? "§a" : "§6"; + Text displayText = Text.translatable("text.config.chattools.option.responserDisplay", colorPrefix + responserRuleUnit.getAddress(), responserRuleUnit.isForceDisableInjector() ? "§a✔" : "§c✘", responserRuleUnit.getPattern(), responserRuleUnit.getMessage()); + return new MultiElementListEntry<>(displayText, responserRuleUnit, new ArrayList<>() {{ + add(eb.startStrField(Text.translatable("text.config.chattools.option.responserAddress"), responserRuleUnit.getAddress()).setTooltip(Text.translatable("text.config.chattools.option.responserAddress.@Tooltip")).setDefaultValue(new ChatResponser.ResponserRuleUnit().getAddress()).setSaveConsumer(responserRuleUnit::setAddress).setErrorSupplier(REGEX_COMPILE_ERROR_SUPPLIER_ALLOW_STAR).build()); + add(eb.startStrField(Text.translatable("text.config.chattools.option.responserMatchPattern"), responserRuleUnit.getPattern()).setTooltip(Text.translatable("text.config.chattools.option.responserMatchPattern.@Tooltip")).setDefaultValue(new ChatResponser.ResponserRuleUnit().getPattern()).setSaveConsumer(responserRuleUnit::setPattern).setErrorSupplier(REGEX_COMPILE_ERROR_SUPPLIER).build()); + add(eb.startStrField(Text.translatable("text.config.chattools.option.responserString"), responserRuleUnit.getMessage()).setTooltip(Text.translatable("text.config.chattools.option.responserString.@Tooltip")).setDefaultValue(new ChatResponser.ResponserRuleUnit().getMessage()).setSaveConsumer(responserRuleUnit::setMessage).build()); + add(eb.startBooleanToggle(Text.translatable("text.config.chattools.option.responserForceDisableInjector"), responserRuleUnit.isForceDisableInjector()).setTooltip(Text.translatable("text.config.chattools.option.responserString.@Tooltip")).setDefaultValue(new ChatResponser.ResponserRuleUnit().isForceDisableInjector()).setSaveConsumer(responserRuleUnit::setForceDisableInjector).build()); + }}, false); + } + })); return builder; } } diff --git a/src/main/java/net/apple70cents/chattools/config/ModConfigFallback.java b/src/main/java/net/apple70cents/chattools/config/ModConfigFallback.java index 782a8a8..d88508a 100644 --- a/src/main/java/net/apple70cents/chattools/config/ModConfigFallback.java +++ b/src/main/java/net/apple70cents/chattools/config/ModConfigFallback.java @@ -1,6 +1,7 @@ package net.apple70cents.chattools.config; import net.apple70cents.chattools.features.chatbubbles.BubbleRenderer; +import net.apple70cents.chattools.features.chatresponser.ChatResponser; import net.apple70cents.chattools.features.quickchat.MacroChat; import net.minecraft.client.util.InputUtil; @@ -76,4 +77,8 @@ public static class ToastNotifySettings { add(new BubbleRenderer.BubbleRuleUnit(".*\\.hypixel\\.net","(?\\S+): (?.*)",false)); add(new BubbleRenderer.BubbleRuleUnit()); }}; + public boolean chatResponserEnabled = false; + public List responserRuleList = new ArrayList<>() {{ + add(new ChatResponser.ResponserRuleUnit()); + }}; } \ No newline at end of file diff --git a/src/main/java/net/apple70cents/chattools/features/chatresponser/ChatResponser.java b/src/main/java/net/apple70cents/chattools/features/chatresponser/ChatResponser.java new file mode 100644 index 0000000..88dbfa4 --- /dev/null +++ b/src/main/java/net/apple70cents/chattools/features/chatresponser/ChatResponser.java @@ -0,0 +1,129 @@ +package net.apple70cents.chattools.features.chatresponser; + +import net.apple70cents.chattools.ChatTools; +import net.apple70cents.chattools.config.ModClothConfig; +import net.minecraft.client.MinecraftClient; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ChatResponser { + public static class ResponserRuleUnit { + private String address; + private String pattern; + + private String message; + private boolean forceDisableInjector; + + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getPattern() { + return pattern; + } + public void setPattern(String pattern) { + this.pattern = pattern; + } + + public String getMessage() { + return message; + } + + public void setMessage(String string) { + this.message = string; + } + + public boolean isForceDisableInjector() { + return forceDisableInjector; + } + + public void setForceDisableInjector(boolean forceDisableInjector) { + this.forceDisableInjector = forceDisableInjector; + } + + public ResponserRuleUnit() { + this.address = "*"; + this.pattern = "Repeat my words:(?.*)"; + this.message = "You said {word}."; + this.forceDisableInjector = false; + } + + public ResponserRuleUnit(String address, String pattern, String message, boolean forceDisableInjector) { + this.address = address; + this.pattern = pattern; + this.message = message; + this.forceDisableInjector = forceDisableInjector; + } + } + + static ModClothConfig config = ModClothConfig.get(); + static MinecraftClient mc = MinecraftClient.getInstance(); + + /** + * 把`message`中的{GROUP}格式的组名替换成`rawMessageReceived`中的对应分组 + * + * @param rawMessageReceived 收到的原始信息 + * @param rawPattern 原始模式串 + * @param message 待发消息 + * @return 处理后的待发消息 + */ + static String replaceAllGroupNames(String rawMessageReceived, String rawPattern, String message) { + // 这个正则表达式能取出花括号中的组名。即能取出`{name}`中的`name`;`{\}\{}`中的`\}\{`;但不能取出`{name\}`或者`\{name}`。 + final String groupNamePattern = "(?.*?)(?§r to match player name.\nUse group §o§r to match chat context.", "text.config.chattools.option.chatBubblesFallback": "Fallback", - "text.config.chattools.option.chatBubblesFallback.@Tooltip": "If the message contains a player name,\nrender the message anyway above the player,\neven if the messages doesn't match the rule.\n(Note that in this case the message will not be modified.)" + "text.config.chattools.option.chatBubblesFallback.@Tooltip": "If the message contains a player name,\nrender the message anyway above the player,\neven if the messages doesn't match the rule.\n(Note that in this case the message will not be modified.)", + "text.config.chattools.option.responserEnabled": "Enable Chat Responser", + "text.config.chattools.option.responserEnabled.@Tooltip": "Chat Responser allows you to reponse to specific chat messages.", + "text.config.chattools.option.responserRulesList": "Responser Rules §6(Current Server Address: %s §6)§r", + "text.config.chattools.option.responserRulesList.@Tooltip": "Apply different responser rules on different servers.\nThe more preceding content has higher priority.", + "text.config.chattools.option.responserNew": "New Rule", + "text.config.chattools.option.responserDisplay": "§e[ %s§e ] [ Override: %s§e ]§r %s §e→§r %s", + "text.config.chattools.option.responserAddress": "Address", + "text.config.chattools.option.responserAddress.@Tooltip": "Response when the current\nserver address matches this field.\nRegEx is supported.\nYou can use §o*§r for arbitrary content.", + "text.config.chattools.option.responserMatchPattern": "Pattern", + "text.config.chattools.option.responserMatchPattern.@Tooltip": "Response when the pattern is matched.\nRegEx is supported.\nUsing RegEx groups is suggested.", + "text.config.chattools.option.responserString": "Responser", + "text.config.chattools.option.responserString.@Tooltip": "§o{$GROUP}§r will be replaced as the context of the group `$GROUP`.\n§o{pos}§r refers to your position.", + "text.config.chattools.option.responserForceDisableInjector": "Disable Formatter (Override)", + "text.config.chattools.option.responserForceDisableInjector.@Tooltip": "Disable Chat Formatter on respond." } \ No newline at end of file diff --git a/src/main/resources/assets/chattools/lang/zh_cn.json b/src/main/resources/assets/chattools/lang/zh_cn.json index 62975a1..80e1451 100644 --- a/src/main/resources/assets/chattools/lang/zh_cn.json +++ b/src/main/resources/assets/chattools/lang/zh_cn.json @@ -10,6 +10,7 @@ "key.chattools.category.injector": "注入聊天", "key.chattools.category.quickchat": "快捷发言", "key.chattools.category.bubble": "聊天气泡", + "key.chattools.category.responser": "聊天回应", "key.chattools.match": "§a§l聊天栏中有您关注的信息", "key.chattools.addonToastNotReady": "§6§l弹窗提醒Addon模式没准备好!请使用`/chattools download`来下载依赖文件。", "key.chattools.requireRestart": "请在编辑后重启 Minecraft 以应用", @@ -95,5 +96,19 @@ "text.config.chattools.option.chatBubblesPattern": "规则", "text.config.chattools.option.chatBubblesPattern.@Tooltip": "内容遵循 §a正则表达式(RegEx)§r 语法\n使用组表示匹配的玩家名称\n使用组表示匹配的消息内容", "text.config.chattools.option.chatBubblesFallback": "回退", - "text.config.chattools.option.chatBubblesFallback.@Tooltip": "即使消息不被匹配,但如果消息里包含任意玩家昵称,\n也在此玩家头上渲染气泡\n(注意:这种情况下消息将会原封不动地渲染出来)" + "text.config.chattools.option.chatBubblesFallback.@Tooltip": "即使消息不被匹配,但如果消息里包含任意玩家昵称,\n也在此玩家头上渲染气泡\n(注意:这种情况下消息将会原封不动地渲染出来)", + "text.config.chattools.option.responserEnabled": "启用聊天回应", + "text.config.chattools.option.responserEnabled.@Tooltip": "聊天回应功能允许对于特定的聊天内容自动回复特定的信息", + "text.config.chattools.option.responserRulesList": "聊天回应规则列表 §6(当前服务器地址: %s§6 )§r", + "text.config.chattools.option.responserRulesList.@Tooltip": "在不同的服务器应用不同的聊天回应规则。\n更靠前的内容优先级更高。", + "text.config.chattools.option.responserNew": "新建规则", + "text.config.chattools.option.responserDisplay": "§e[ %s§e ] [关闭注入:%s§e]§r %s §e→§r %s", + "text.config.chattools.option.responserAddress": "服务器地址", + "text.config.chattools.option.responserAddress.@Tooltip": "当目前服务器地址匹配此字段时按照对应规则进行聊天回应\n内容遵循 §a正则表达式(RegEx)§r 语法\n可以使用 §o*§r 表示任意内容", + "text.config.chattools.option.responserMatchPattern": "模式串", + "text.config.chattools.option.responserMatchPattern.@Tooltip": "当模式串被匹配,发送回应内容\n内容遵循 §a正则表达式(RegEx)§r 语法\n建议使用正则表达式组功能", + "text.config.chattools.option.responserString": "回应内容", + "text.config.chattools.option.responserString.@Tooltip": "§o{$GROUP}§r 将会被替换成匹配到的`$GROUP`组的内容\n§o{pos}§r 将被替换成你的坐标。", + "text.config.chattools.option.responserForceDisableInjector": "关闭注入", + "text.config.chattools.option.responserForceDisableInjector.@Tooltip": "发送回应内容时将不会尝试注入聊天。" } \ No newline at end of file