diff --git a/gradle.properties b/gradle.properties index 95ef88b..92e942a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,15 +6,15 @@ org.gradle.parallel=true version_properties_path=./properties # Mod Properties -mod_version=1.1.1 +mod_version=1.2.1 maven_group=cn.revaria.chatplus archives_base_name=chat-plus # Minecraft -minecraft_version=26.1 +minecraft_version=26.2 # Override Fabric API version for local testing (runClient/runServer) #test_fabric_api_version= # Dependencies -manifold_version=2025.1.9 +manifold_version=2025.1.25 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a9db115..87c980e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,9 +1,6 @@ +#Tue Jul 21 11:35:40 CST 2026 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip -networkTimeout=10000 -retries=0 -retryBackOffMs=500 -validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/properties/26.2.properties b/properties/26.2.properties new file mode 100644 index 0000000..cbfb2c3 --- /dev/null +++ b/properties/26.2.properties @@ -0,0 +1,24 @@ +java_version=25 + +# Fabric Loom +# - For Minecraft versions <26.1: enable remap +# - For Minecraft versions >=26.1: disable remap +fabric_loom_remap=false + +# Fabric Properties +# check these on https://fabricmc.net/develop +fabric_loader_version=0.19.3 +min_fabric_loader_version= +fabric_api_version=0.152.2 +# Fabric API mod ID conventions: +# - For Minecraft versions <1.19.2: use "fabric" +# - For Minecraft versions >=1.19.2: use "fabric-api" +# Ref: https://wiki.fabricmc.net/tutorial:setup +fabric_api_mod_id=fabric-api + +# Mod Compatibility +! Dynmap 3.8 +mod_dynmap_version=4UHHD4t6 +! Styled Chat 2.13.0+26.2 +mod_styled_chat_version=2.13.0+26.2 +mod_styled_chat_placeholder_api_version=3.1.0-beta.1+26.2 diff --git a/src/main/java/cn/revaria/chatplus/ChatPlus.java b/src/main/java/cn/revaria/chatplus/ChatPlus.java index e18f3f2..bad1b5e 100644 --- a/src/main/java/cn/revaria/chatplus/ChatPlus.java +++ b/src/main/java/cn/revaria/chatplus/ChatPlus.java @@ -1,6 +1,8 @@ package cn.revaria.chatplus; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.minecraft.server.MinecraftServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,12 +14,20 @@ public class ChatPlus implements ModInitializer { // That way, it's clear which mod wrote info, warnings, and errors. public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + private static MinecraftServer server; + + public static MinecraftServer getServer() { + return server; + } + @Override public void onInitialize() { // This code runs as soon as Minecraft is in a mod-load-ready state. // However, some things (like resources) may still be uninitialized. // Proceed with mild caution. + ServerLifecycleEvents.SERVER_STARTING.register(server -> ChatPlus.server = server); + LOGGER.info("Chat Plus loaded"); } } diff --git a/src/main/java/cn/revaria/chatplus/format/ChatFormat.java b/src/main/java/cn/revaria/chatplus/format/ChatFormat.java new file mode 100644 index 0000000..6fdc7d2 --- /dev/null +++ b/src/main/java/cn/revaria/chatplus/format/ChatFormat.java @@ -0,0 +1,7 @@ +package cn.revaria.chatplus.format; + +public interface ChatFormat { + int startingIndex(); + ChatFormatType formatType(); + ChatFormat copy(); +} diff --git a/src/main/java/cn/revaria/chatplus/format/ChatFormatType.java b/src/main/java/cn/revaria/chatplus/format/ChatFormatType.java new file mode 100644 index 0000000..be2b14b --- /dev/null +++ b/src/main/java/cn/revaria/chatplus/format/ChatFormatType.java @@ -0,0 +1,8 @@ +package cn.revaria.chatplus.format; + +public enum ChatFormatType { + COLOR, + FONT, + INSERT, + RESET +} diff --git a/src/main/java/cn/revaria/chatplus/format/formats/ChatColorFormat.java b/src/main/java/cn/revaria/chatplus/format/formats/ChatColorFormat.java new file mode 100644 index 0000000..35f0a83 --- /dev/null +++ b/src/main/java/cn/revaria/chatplus/format/formats/ChatColorFormat.java @@ -0,0 +1,78 @@ +package cn.revaria.chatplus.format.formats; + +import cn.revaria.chatplus.format.ChatFormat; +import cn.revaria.chatplus.format.ChatFormatType; + +import java.util.Map; + +public class ChatColorFormat implements ChatFormat { + + private final int R, G, B; + + private final int startingIndex; + private final ChatFormatType formatType = ChatFormatType.COLOR; + + public ChatColorFormat(int startingIndex, int R, int G, int B) { + this.startingIndex = startingIndex; + + this.R = R; + this.G = G; + this.B = B; + } + public ChatColorFormat(int startingIndex, char colorCode) { + this.startingIndex = startingIndex; + + if (VANILLA_COLOR_FORMATS.containsKey(colorCode)) { + this.R = VANILLA_COLOR_FORMATS.get(colorCode).getR(); + this.G = VANILLA_COLOR_FORMATS.get(colorCode).getG(); + this.B = VANILLA_COLOR_FORMATS.get(colorCode).getB(); + } + else { + this.R = 0; + this.G = 0; + this.B = 0; + } + } + + public int getR() { + return R; + } + public int getG() { + return G; + } + public int getB() { + return B; + } + + @Override + public int startingIndex() { + return this.startingIndex; + } + @Override + public ChatFormatType formatType() { + return this.formatType; + } + @Override + public ChatColorFormat copy() { + return new ChatColorFormat(this.startingIndex, this.R, this.G, this.B); + } + + public static final Map VANILLA_COLOR_FORMATS = Map.ofEntries( + Map.entry('0', new ChatColorFormat(0, 0, 0, 0)), + Map.entry('1', new ChatColorFormat(0, 0, 0, 170)), + Map.entry('2', new ChatColorFormat(0, 0, 170, 0)), + Map.entry('3', new ChatColorFormat(0, 0, 170, 170)), + Map.entry('4', new ChatColorFormat(0, 170, 0, 0)), + Map.entry('5', new ChatColorFormat(0, 170, 0, 170)), + Map.entry('6', new ChatColorFormat(0, 255, 170, 0)), + Map.entry('7', new ChatColorFormat(0, 170, 170, 170)), + Map.entry('8', new ChatColorFormat(0, 85, 85, 85)), + Map.entry('9', new ChatColorFormat(0, 85, 85, 255)), + Map.entry('a', new ChatColorFormat(0, 85, 255, 85)), + Map.entry('b', new ChatColorFormat(0, 85, 255, 255)), + Map.entry('c', new ChatColorFormat(0, 255, 85, 85)), + Map.entry('d', new ChatColorFormat(0, 255, 85, 255)), + Map.entry('e', new ChatColorFormat(0, 255, 255, 85)), + Map.entry('f', new ChatColorFormat(0, 255, 255, 255)) + ); +} diff --git a/src/main/java/cn/revaria/chatplus/format/formats/ChatFontFormat.java b/src/main/java/cn/revaria/chatplus/format/formats/ChatFontFormat.java new file mode 100644 index 0000000..05ae252 --- /dev/null +++ b/src/main/java/cn/revaria/chatplus/format/formats/ChatFontFormat.java @@ -0,0 +1,51 @@ +package cn.revaria.chatplus.format.formats; + +import cn.revaria.chatplus.format.ChatFormat; +import cn.revaria.chatplus.format.ChatFormatType; +import net.minecraft.ChatFormatting; + +import java.util.Map; + +public class ChatFontFormat implements ChatFormat { + + private final ChatFormatting fontFormat; + + private final int startingIndex; + private final ChatFormatType formatType = ChatFormatType.FONT; + + public ChatFontFormat(int startingIndex, ChatFormatting fontFormat) { + this.startingIndex = startingIndex; + + this.fontFormat = fontFormat; + } + public ChatFontFormat(int startingIndex, char fontCode) { + this.startingIndex = startingIndex; + + this.fontFormat = VANILLA_FONT_FORMATS.getOrDefault(fontCode, ChatFormatting.ITALIC); + } + + public ChatFormatting getFont() { + return fontFormat; + } + + @Override + public int startingIndex() { + return startingIndex; + } + @Override + public ChatFormatType formatType() { + return formatType; + } + @Override + public ChatFontFormat copy() { + return new ChatFontFormat(this.startingIndex, this.fontFormat); + } + + public static final Map VANILLA_FONT_FORMATS = Map.ofEntries( + Map.entry('k', ChatFormatting.OBFUSCATED), + Map.entry('l', ChatFormatting.BOLD), + Map.entry('m', ChatFormatting.STRIKETHROUGH), + Map.entry('n', ChatFormatting.UNDERLINE), + Map.entry('o', ChatFormatting.ITALIC) + ); +} diff --git a/src/main/java/cn/revaria/chatplus/format/formats/ChatInsertFormat.java b/src/main/java/cn/revaria/chatplus/format/formats/ChatInsertFormat.java new file mode 100644 index 0000000..877dd39 --- /dev/null +++ b/src/main/java/cn/revaria/chatplus/format/formats/ChatInsertFormat.java @@ -0,0 +1,9 @@ +package cn.revaria.chatplus.format.formats; + +import cn.revaria.chatplus.format.ChatFormat; +import net.minecraft.network.chat.MutableComponent; + +public interface ChatInsertFormat extends ChatFormat { + MutableComponent getInsertComponent(); + ChatInsertFormat copy(); +} diff --git a/src/main/java/cn/revaria/chatplus/format/formats/ChatResetFormat.java b/src/main/java/cn/revaria/chatplus/format/formats/ChatResetFormat.java new file mode 100644 index 0000000..8d40574 --- /dev/null +++ b/src/main/java/cn/revaria/chatplus/format/formats/ChatResetFormat.java @@ -0,0 +1,27 @@ +package cn.revaria.chatplus.format.formats; + +import cn.revaria.chatplus.format.ChatFormat; +import cn.revaria.chatplus.format.ChatFormatType; + +public class ChatResetFormat implements ChatFormat { + + private final int startingIndex; + private final ChatFormatType formatType = ChatFormatType.RESET; + + public ChatResetFormat(int startingIndex) { + this.startingIndex = startingIndex; + } + + @Override + public int startingIndex() { + return startingIndex; + } + @Override + public ChatFormatType formatType() { + return formatType; + } + @Override + public ChatResetFormat copy() { + return new ChatResetFormat(this.startingIndex); + } +} diff --git a/src/main/java/cn/revaria/chatplus/format/formats/insertformats/ChatInsertItemFormat.java b/src/main/java/cn/revaria/chatplus/format/formats/insertformats/ChatInsertItemFormat.java new file mode 100644 index 0000000..26720eb --- /dev/null +++ b/src/main/java/cn/revaria/chatplus/format/formats/insertformats/ChatInsertItemFormat.java @@ -0,0 +1,41 @@ +package cn.revaria.chatplus.format.formats.insertformats; + +import cn.revaria.chatplus.format.ChatFormatType; +import cn.revaria.chatplus.format.formats.ChatInsertFormat; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.world.item.ItemStack; + +public class ChatInsertItemFormat implements ChatInsertFormat { + + private final ItemStack item; + + private final int startingIndex; + private final ChatFormatType formatType = ChatFormatType.INSERT; + + public ChatInsertItemFormat(int startingIndex, ItemStack item) { + this.startingIndex = startingIndex; + + this.item = item; + } + + public ItemStack getItem() { + return item; + } + + @Override + public MutableComponent getInsertComponent() { + return item.getDisplayName().copy(); + } + @Override + public int startingIndex() { + return startingIndex; + } + @Override + public ChatFormatType formatType() { + return formatType; + } + @Override + public ChatInsertItemFormat copy() { + return new ChatInsertItemFormat(this.startingIndex, this.item); + } +} diff --git a/src/main/java/cn/revaria/chatplus/format/formats/insertformats/ChatInsertRemindAllFormat.java b/src/main/java/cn/revaria/chatplus/format/formats/insertformats/ChatInsertRemindAllFormat.java new file mode 100644 index 0000000..097d926 --- /dev/null +++ b/src/main/java/cn/revaria/chatplus/format/formats/insertformats/ChatInsertRemindAllFormat.java @@ -0,0 +1,66 @@ +package cn.revaria.chatplus.format.formats.insertformats; + +import cn.revaria.chatplus.ChatPlus; +import cn.revaria.chatplus.format.ChatFormatType; +import cn.revaria.chatplus.format.formats.ChatInsertFormat; +import net.minecraft.ChatFormatting; +import net.minecraft.core.Holder; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.TextColor; +import net.minecraft.network.protocol.game.ClientboundSoundPacket; +import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; + +public class ChatInsertRemindAllFormat implements ChatInsertFormat { + + private final String reminderText; + private boolean soundPlayed = false; + + private final int startingIndex; + private final ChatFormatType formatType = ChatFormatType.INSERT; + + public ChatInsertRemindAllFormat(int startingIndex, String reminderText) { + this.startingIndex = startingIndex; + + this.reminderText = reminderText; + } + + @Override + public MutableComponent getInsertComponent() { + if (!soundPlayed) { + soundPlayed = true; + + for (ServerPlayer player : ChatPlus.getServer().getPlayerList().getPlayers()) { + player.connection.send(new ClientboundSoundPacket( + Holder.direct(SoundEvent.createVariableRangeEvent(Identifier.fromNamespaceAndPath("minecraft", "entity.experience_orb.pickup"))), + SoundSource.MASTER, + player.position().x, + player.position().y, + player.position().z, + 3, + 1, + player.level().getRandom().nextLong() + )); + } + } + + return Component.literal("@").append(reminderText) + .withColor(TextColor.GREEN) + .withStyle(ChatFormatting.BOLD); + } + @Override + public int startingIndex() { + return startingIndex; + } + @Override + public ChatFormatType formatType() { + return formatType; + } + @Override + public ChatInsertFormat copy() { + return new ChatInsertRemindAllFormat(startingIndex, reminderText); + } +} diff --git a/src/main/java/cn/revaria/chatplus/format/formats/insertformats/ChatInsertReminderFormat.java b/src/main/java/cn/revaria/chatplus/format/formats/insertformats/ChatInsertReminderFormat.java new file mode 100644 index 0000000..96542be --- /dev/null +++ b/src/main/java/cn/revaria/chatplus/format/formats/insertformats/ChatInsertReminderFormat.java @@ -0,0 +1,66 @@ +package cn.revaria.chatplus.format.formats.insertformats; + +import cn.revaria.chatplus.format.ChatFormatType; +import cn.revaria.chatplus.format.formats.ChatInsertFormat; +import net.minecraft.ChatFormatting; +import net.minecraft.core.Holder; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.TextColor; +import net.minecraft.network.protocol.game.ClientboundSoundPacket; +import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; + +public class ChatInsertReminderFormat implements ChatInsertFormat { + + private final ServerPlayer aimPlayer; + private boolean soundPlayed = false; + + private final int startingIndex; + private final ChatFormatType formatType = ChatFormatType.INSERT; + + public ChatInsertReminderFormat(int startingIndex, ServerPlayer aim) { + this.startingIndex = startingIndex; + + this.aimPlayer = aim; + } + + public ServerPlayer getAimPlayer() { + return aimPlayer; + } + + @Override + public MutableComponent getInsertComponent() { + if (!soundPlayed) { + soundPlayed = true; + + aimPlayer.connection.send(new ClientboundSoundPacket( + Holder.direct(SoundEvent.createVariableRangeEvent(Identifier.fromNamespaceAndPath("minecraft", "entity.experience_orb.pickup"))), + SoundSource.MASTER, + aimPlayer.position().x, + aimPlayer.position().y, + aimPlayer.position().z, + 3, + 1, + aimPlayer.level().getRandom().nextLong() + )); + } + return Component.literal("@").append(aimPlayer.getDisplayName()) + .withColor(TextColor.GREEN) + .withStyle(ChatFormatting.BOLD); + } + @Override + public int startingIndex() { + return startingIndex; + } + @Override + public ChatFormatType formatType() { + return formatType; + } + @Override + public ChatInsertFormat copy() { + return new ChatInsertReminderFormat(startingIndex, aimPlayer); + } +} diff --git a/src/main/java/cn/revaria/chatplus/mixin/compat/DynmapMixin.java b/src/main/java/cn/revaria/chatplus/mixin/compat/DynmapMixin.java.disabled similarity index 100% rename from src/main/java/cn/revaria/chatplus/mixin/compat/DynmapMixin.java rename to src/main/java/cn/revaria/chatplus/mixin/compat/DynmapMixin.java.disabled diff --git a/src/main/java/cn/revaria/chatplus/util/TextStyleFormatter.java b/src/main/java/cn/revaria/chatplus/util/TextStyleFormatter.java index 465b47d..c39a4b0 100644 --- a/src/main/java/cn/revaria/chatplus/util/TextStyleFormatter.java +++ b/src/main/java/cn/revaria/chatplus/util/TextStyleFormatter.java @@ -2,31 +2,31 @@ #if MC_VER <= MC_1_20 import net.minecraft.network.chat.contents.LiteralContents; -#else -import net.minecraft.network.chat.contents.PlainTextContents; #endif +import cn.revaria.chatplus.ChatPlus; +import cn.revaria.chatplus.format.formats.ChatInsertFormat; +import cn.revaria.chatplus.format.formats.insertformats.ChatInsertReminderFormat; +import cn.revaria.chatplus.format.formats.insertformats.ChatInsertRemindAllFormat; +import cn.revaria.chatplus.format.ChatFormat; +import cn.revaria.chatplus.format.ChatFormatType; +import cn.revaria.chatplus.format.formats.ChatColorFormat; +import cn.revaria.chatplus.format.formats.ChatFontFormat; +import cn.revaria.chatplus.format.formats.insertformats.ChatInsertItemFormat; +import cn.revaria.chatplus.format.formats.ChatResetFormat; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.item.ItemStack; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.List; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class TextStyleFormatter { - public static int MAIN_HAND = -1; // Must be smaller than or equal to 0 /** - * Processes text styling with two key functions: - *

- * 1. Replaces {@code &} with {@code §}, using {@code &&} to escape literal {@code &}
- * 2. Substitutes {@code [item]} with main-hand item hover text and {@code [item=N]} with Nth slot's hover text - *

- * Recursively processes nested text components. + * * * @param sourceText Original text to process (supports nested text components) * @param sourcePlayer Player used for item stack references @@ -35,46 +35,350 @@ public class TextStyleFormatter { public static MutableComponent applyStyle(Component sourceText, ServerPlayer sourcePlayer) { MutableComponent sourceMutableText = sourceText.copy(); - MutableComponent finalText = Component.empty().setStyle(sourceMutableText.getStyle()); - - if (sourceText.getContents() instanceof #if MC_VER <= MC_1_20 LiteralContents #else PlainTextContents #endif plainTextContent) { - String changedMessage = plainTextContent.text() - .replace('&', '§') - .replace("§§", "&"); - String regex = "\\[item(?:=([1-9]))?\\]"; - String[] messages = changedMessage.split(regex, -1); - Deque itemDeque = new ArrayDeque<>(); - - Matcher matcher = Pattern.compile(regex).matcher(changedMessage); - while (matcher.find()) { - String digit = matcher.group(1); - if (digit == null) { - itemDeque.addLast(MAIN_HAND); - } else { - itemDeque.addLast(Integer.parseInt(digit)); - } - } - - for (String message : messages) { - finalText.append(Component.literal(message)); - if (!itemDeque.isEmpty()) { - ItemStack itemStack; - if (itemDeque.getFirst() == MAIN_HAND) { - itemStack = sourcePlayer.getMainHandItem(); - } else { - itemStack = sourcePlayer.getInventory().getItem(itemDeque.getFirst() - 1); + String sourceRawText = sourceMutableText.toString().substring(8, sourceMutableText.toString().length() - 1); + + // These will be motified in translateInput() + TreeMap> formatsTable = new TreeMap<>(); + + String sourceLiteralText = translateInput(sourceRawText, sourcePlayer, formatsTable); + + /* Debug + sourcePlayer.sendSystemMessage(Component.literal("原始聊天代码:")); + sourcePlayer.sendSystemMessage(Component.literal(sourceMutableText.toString())); + sourcePlayer.sendSystemMessage(Component.literal("原始聊天文本:")); + sourcePlayer.sendSystemMessage(Component.literal(sourceRawText)); + sourcePlayer.sendSystemMessage(Component.literal("提取到的聊天内容:")); + sourcePlayer.sendSystemMessage(Component.literal(sourceLiteralText)); + sourcePlayer.sendSystemMessage(Component.literal("修饰列表:")); + for (int i : formatsTable.keySet()) { + ArrayList formatsList = formatsTable.get(i); + sourcePlayer.sendSystemMessage(Component.literal(i + ".")); + for (ChatFormat format : formatsList) { + if (format instanceof ChatInsertItemFormat) { + sourcePlayer.sendSystemMessage(Component.literal(" 物品插入数据")); + } + if (format instanceof ChatResetFormat) { + sourcePlayer.sendSystemMessage(Component.literal(" 清除")); + } + if (format instanceof ChatColorFormat colorFormat) { + sourcePlayer.sendSystemMessage(Component.literal(" 颜色:" + colorFormat.getR() + " " + colorFormat.getG() + " " + colorFormat.getB())); + } + if (format instanceof ChatFontFormat fontFormat) { + sourcePlayer.sendSystemMessage(Component.literal(" 字体").withStyle(fontFormat.getFont())); + } + } + }*/ + + MutableComponent finalText = Component.literal(""); + ChatColorFormat currentColor = new ChatColorFormat(0, 'f'); + HashSet currentFonts = new HashSet<>(); + int startIndex = 0; + + for (int i : formatsTable.keySet()) { + + ArrayList formatsList = formatsTable.get(i); + + if (formatsList.getFirst() instanceof ChatInsertFormat insertFormat) { + + if (i > 0) { + finalText.append(composeChatComponent( + sourceLiteralText.substring(startIndex, i), + currentColor, + currentFonts + )); + } + + finalText.append(insertFormat.getInsertComponent()); + + startIndex = i; + continue; + } + if (formatsList.getFirst() instanceof ChatResetFormat) { + + if (i > 0) { + finalText.append(composeChatComponent( + sourceLiteralText.substring(startIndex, i), + currentColor, + currentFonts + )); + } + + currentColor = new ChatColorFormat(0, 'f'); + currentFonts.clear(); + + startIndex = i; + continue; + } + + if (i > 0) { + finalText.append(composeChatComponent( + sourceLiteralText.substring(startIndex, i), + currentColor, + currentFonts + )); + } + + for (ChatFormat format : formatsList) { + + if (format instanceof ChatColorFormat colorFormat) { + currentColor = colorFormat; + } + else if (format instanceof ChatFontFormat fontFormat) { + currentFonts.add(fontFormat); + } + } + + startIndex = i; + } + if (startIndex < sourceLiteralText.length()) { + finalText.append(composeChatComponent( + sourceLiteralText.substring(startIndex, sourceLiteralText.length()), + currentColor, + currentFonts + )); + } + + return finalText; + } + + private static MutableComponent composeChatComponent(String chatText, ChatColorFormat currentColor, HashSet currentFonts) { + + if (currentColor == null) { + currentColor = new ChatColorFormat(0, 'f'); + } + + MutableComponent finalChatComponent = Component.literal(chatText); + + finalChatComponent.withColor(currentColor.getR() << 16 | currentColor.getG() << 8 | currentColor.getB()); + for (ChatFontFormat fontFormat : currentFonts) { + finalChatComponent.withStyle(fontFormat.getFont()); + } + + return finalChatComponent; + } + + private static String translateInput(String sourceRawText, ServerPlayer sourcePlayer, TreeMap> formatsTableOutput) { + + StringBuilder textBuilder = new StringBuilder(); + + MinecraftServer server = ChatPlus.getServer(); + String[] playerNames = server.getPlayerList().getPlayerNamesArray(); + + String itemRegex = "\\[item(?:=([1-9]))?\\]"; + Pattern itemPattern = Pattern.compile(itemRegex); + Matcher matcher = itemPattern.matcher(sourceRawText); + + for (int i = 0; i < sourceRawText.length(); ++i) { + + char character = sourceRawText.charAt(i); + int currentIndex = textBuilder.length(); + + if (character == '&' && i < sourceRawText.length() - 1) { + + char colorCode = sourceRawText.charAt(i + 1); + if (ChatColorFormat.VANILLA_COLOR_FORMATS.containsKey(colorCode)) { + + if (!formatsTableOutput.containsKey(currentIndex)) { + formatsTableOutput.put(currentIndex, new ArrayList<>()); + } + formatsTableOutput.get(currentIndex).add(new ChatColorFormat(currentIndex, colorCode)); + + ++i; + continue; + } + } + + if (character == '#' && i < sourceRawText.length() - 6) { + + boolean legalColorCode = true; + for (int j = i + 1; j <= i + 6 && j < sourceRawText.length(); ++j) { + char c = sourceRawText.charAt(j); + + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'))) { + legalColorCode = false; + break; + } + } + + if (legalColorCode) { + + if (!formatsTableOutput.containsKey(currentIndex)) { + formatsTableOutput.put(currentIndex, new ArrayList<>()); } - finalText.append(itemStack.getDisplayName()); - itemDeque.removeFirst(); + + int color = Integer.parseInt(sourceRawText.substring(i + 1, i + 7), 16); + formatsTableOutput.get(currentIndex).add(new ChatColorFormat( + currentIndex, + (color >> 16) & 0xFF, + (color >> 8) & 0xFF, + color & 0xFF) + ); + + i += 6; + continue; + } + } + + if (character == '&' && i < sourceRawText.length() - 1) { + + char fontCode = sourceRawText.charAt(i + 1); + if (ChatFontFormat.VANILLA_FONT_FORMATS.containsKey(fontCode)) { + + if (!formatsTableOutput.containsKey(currentIndex)) { + formatsTableOutput.put(currentIndex, new ArrayList<>()); + } + formatsTableOutput.get(currentIndex).add(new ChatFontFormat(currentIndex, fontCode)); + + ++i; + continue; + } + } + + if (character == '[' && i < sourceRawText.length() - 5) { + + matcher.region(i, Math.min(i + 8, sourceRawText.length())); + if (matcher.lookingAt()) { + + if (!formatsTableOutput.containsKey(currentIndex)) { + formatsTableOutput.put(currentIndex, new ArrayList<>()); + } + + String digit = matcher.group(1); + if (digit == null) { + formatsTableOutput.get(currentIndex).add(new ChatInsertItemFormat( + currentIndex, + sourcePlayer.getMainHandItem() + )); + } + else { + formatsTableOutput.get(currentIndex).add(new ChatInsertItemFormat( + currentIndex, + sourcePlayer.getInventory().getItem(Integer.parseInt(digit) - 1) + )); + } + + while (sourceRawText.charAt(i) != ']' && i < sourceRawText.length()) { + ++i; + } + continue; + } + } + + if (character == '&' && i < sourceRawText.length() - 1) { + + if (sourceRawText.charAt(i + 1) == 'r') { + + if (!formatsTableOutput.containsKey(currentIndex)) { + formatsTableOutput.put(currentIndex, new ArrayList<>()); + } + formatsTableOutput.get(currentIndex).add(new ChatResetFormat(currentIndex)); + + ++i; + continue; + } + } + + if (character == '@') { + + if (sourceRawText.startsWith("All", i + 1) + || sourceRawText.startsWith("ALL", i + 1) + || sourceRawText.startsWith("all", i + 1)) { + + if (!formatsTableOutput.containsKey(currentIndex)) { + formatsTableOutput.put(currentIndex, new ArrayList<>()); + } + formatsTableOutput.get(currentIndex).add(new ChatInsertRemindAllFormat( + currentIndex, + "All" + )); + + i += 3; + continue; + } + if (sourceRawText.startsWith("所有人", i + 1)) { + + if (!formatsTableOutput.containsKey(currentIndex)) { + formatsTableOutput.put(currentIndex, new ArrayList<>()); + } + formatsTableOutput.get(currentIndex).add(new ChatInsertRemindAllFormat( + currentIndex, + "所有人" + )); + + i += 3; + continue; + } + } + + if ((character >= '0' && character <= '9') + || (character >= 'a' && character <= 'z') + || (character >= 'A' && character <= 'Z') + || character == '_') { + + String insertName = matchPlayerName(sourceRawText, playerNames, i); + if (insertName != null) { + + if (!formatsTableOutput.containsKey(currentIndex)) { + formatsTableOutput.put(currentIndex, new ArrayList<>()); + } + formatsTableOutput.get(currentIndex).add(new ChatInsertReminderFormat( + currentIndex, + server.getPlayerList().getPlayer(insertName) + )); + + i += insertName.length() - 1; + continue; } } + + textBuilder.append(character); } - List sourceTexts = sourceMutableText.getSiblings(); - for (Component text : sourceTexts) { - finalText.append(applyStyle(text, sourcePlayer)); + formatsTableOutput.forEach((i, formatsList) -> { + + boolean containsResetFormat = false; + ChatInsertFormat containedInsertFormat = null; + for (ChatFormat format : formatsList) { + + if (format.formatType() == ChatFormatType.RESET) { + containsResetFormat = true; + break; + } + if (format instanceof ChatInsertFormat insertFormat && format.formatType() == ChatFormatType.INSERT) { + containedInsertFormat = insertFormat.copy(); + break; + } + } + + if (containsResetFormat) { + formatsList.clear(); + formatsList.add(new ChatResetFormat(i)); + } + if (containedInsertFormat != null) { + formatsList.clear(); + formatsList.add(containedInsertFormat); + } + }); + + return textBuilder.toString(); + } + + private static String matchPlayerName(String chatText, String[] playerNames, int startingIndex) { + + String aimName = null; + + for (String name : playerNames) { + + if (chatText.startsWith(name, startingIndex)) { + + if (aimName == null || aimName.length() < name.length()) { + aimName = name; + } + } } - return finalText; + return aimName; } } diff --git a/src/main/resources/chat-plus.mixins.json b/src/main/resources/chat-plus.mixins.json index bbb0e0e..9b06cbd 100644 --- a/src/main/resources/chat-plus.mixins.json +++ b/src/main/resources/chat-plus.mixins.json @@ -4,7 +4,6 @@ "plugin": "cn.revaria.chatplus.plugin.MixinConfigPlugin", "mixins": [ "ChatMixin", - "compat.DynmapMixin", "compat.StyledChatMixin" ], "injectors": {