diff --git a/build.gradle b/build.gradle index 5808c1f..979a894 100644 --- a/build.gradle +++ b/build.gradle @@ -2,8 +2,8 @@ import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform import java.nio.file.Paths plugins { - id "net.fabricmc.fabric-loom-remap" version "1.16-SNAPSHOT" apply false - id "net.fabricmc.fabric-loom" version "1.16-SNAPSHOT" apply false + id "net.fabricmc.fabric-loom-remap" version "1.17-SNAPSHOT" apply false + id "net.fabricmc.fabric-loom" version "1.17-SNAPSHOT" apply false id "maven-publish" // Manifold preprocessor diff --git a/gradle.properties b/gradle.properties index 1021c87..0cba7db 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.0 +mod_version=1.2.0 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 c61a118..7e7d24f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME 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/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/ChatInsertItemFormat.java b/src/main/java/cn/revaria/chatplus/format/formats/ChatInsertItemFormat.java new file mode 100644 index 0000000..86c2323 --- /dev/null +++ b/src/main/java/cn/revaria/chatplus/format/formats/ChatInsertItemFormat.java @@ -0,0 +1,36 @@ +package cn.revaria.chatplus.format.formats; + +import cn.revaria.chatplus.format.ChatFormat; +import cn.revaria.chatplus.format.ChatFormatType; +import net.minecraft.world.item.ItemStack; + +public class ChatInsertItemFormat implements ChatFormat { + + 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 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/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/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..f791d50 100644 --- a/src/main/java/cn/revaria/chatplus/util/TextStyleFormatter.java +++ b/src/main/java/cn/revaria/chatplus/util/TextStyleFormatter.java @@ -3,30 +3,26 @@ package cn.revaria.chatplus.util; #if MC_VER <= MC_1_20 import net.minecraft.network.chat.contents.LiteralContents; #else -import net.minecraft.network.chat.contents.PlainTextContents; +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.ChatInsertItemFormat; +import cn.revaria.chatplus.format.formats.ChatResetFormat; #endif import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; 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 +31,271 @@ public class TextStyleFormatter { public static MutableComponent applyStyle(Component sourceText, ServerPlayer sourcePlayer) { MutableComponent sourceMutableText = sourceText.copy(); + 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(sourceLiteralText)); + 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.empty().setStyle(sourceMutableText.getStyle()); + ChatColorFormat currentColor = new ChatColorFormat(0, 'f'); + HashSet currentFonts = new HashSet<>(); + int startIndex = 0; - 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<>(); + for (int i : formatsTable.keySet()) { - 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)); + ArrayList formatsList = formatsTable.get(i); + + if (formatsList.getFirst() instanceof ChatInsertItemFormat insertItem) { + + if (i > 0) { + finalText.append(composeChatComponent( + sourceLiteralText.substring(startIndex, i), + currentColor, + currentFonts + )); + } + + finalText.append(insertItem.getItem().getDisplayName()); + + 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); } } - 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); - } - finalText.append(itemStack.getDisplayName()); - itemDeque.removeFirst(); - } - } + startIndex = i; } - - List sourceTexts = sourceMutableText.getSiblings(); - for (Component text : sourceTexts) { - finalText.append(applyStyle(text, sourcePlayer)); + if (startIndex < sourceLiteralText.length() - 1) { + 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(); + + 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<>()); + } + + 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; + } + } + + textBuilder.append(character); + } + + formatsTableOutput.forEach((i, formatsList) -> { + + boolean containsResetFormat = false; + ChatInsertItemFormat containedInsertItemFormat = null; + for (ChatFormat format : formatsList) { + + if (format.formatType() == ChatFormatType.RESET) { + containsResetFormat = true; + break; + } + if (format instanceof ChatInsertItemFormat insertItemFormat && format.formatType() == ChatFormatType.INSERT) { + containedInsertItemFormat = insertItemFormat.copy(); + break; + } + } + + if (containsResetFormat) { + formatsList.clear(); + formatsList.add(new ChatResetFormat(i)); + } + if (containedInsertItemFormat != null) { + formatsList.clear(); + formatsList.add(containedInsertItemFormat); + } + }); + + return textBuilder.toString(); + } } 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": {