diff --git a/build.gradle b/build.gradle index 0129c16..f4a1304 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,7 @@ repositories { maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } maven { url 'https://maven.nucleoid.xyz' } maven { url 'https://maven.gegy.dev' } + maven { url "https://api.modrinth.com/maven" } //mavenLocal() } @@ -49,6 +50,7 @@ dependencies { modCompileOnly("fr.catcore:server-translations-api:1.4.17+1.19.2") + modCompileOnly("maven.modrinth:vanish:1.1.0") //modLocalRuntime("fr.catcore:server-translations-api:1.4.17+1.19.2") //modRuntime "supercoder79:databreaker:0.2.7" diff --git a/gradle.properties b/gradle.properties index 61db063..7e37e8b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,6 +11,7 @@ loader_version=0.14.10 fabric_version=0.66.1+1.19.3 # Mod Properties + mod_version = 2.1.0-development+1.19.3 maven_group = eu.pb4 archives_base_name = styled-chat diff --git a/src/main/java/eu/pb4/styledchat/StyledChatUtils.java b/src/main/java/eu/pb4/styledchat/StyledChatUtils.java index 99a76e8..0d08148 100644 --- a/src/main/java/eu/pb4/styledchat/StyledChatUtils.java +++ b/src/main/java/eu/pb4/styledchat/StyledChatUtils.java @@ -23,6 +23,8 @@ import eu.pb4.styledchat.config.data.VersionedChatStyleData; import eu.pb4.styledchat.ducks.ExtPlayNetworkHandler; import eu.pb4.styledchat.ducks.ExtSignedMessage; +import eu.pb4.styledchat.parser.LinkParser; +import eu.pb4.styledchat.parser.MentionParser; import eu.pb4.styledchat.parser.SpoilerNode; import me.lucko.fabric.api.permissions.v0.Permissions; import net.minecraft.command.EntitySelector; @@ -75,11 +77,24 @@ public static TextNode parseText(String input) { } public static NodeParser createParser(ServerCommandSource source) { + return createParser(PlaceholderContext.of(source)); + } + + public static NodeParser createParser(PlaceholderContext context) { var config = ConfigManager.getConfig(); var list = new ArrayList(); - var base = createTextParserV1(source); + var base = createTextParserV1(context.source()); + list.add(base); + if (config.configData.formatting.parseLinksInChat) { + list.add(new LinkParser(ConfigManager.getConfig().getLinkStyle(context))); + } + + if (config.configData.formatting.parseMentionsInChat) { + list.add(new MentionParser(ConfigManager.getConfig().getMentionStyle(context), context)); + } + if (config.configData.formatting.markdown) { var form = new ArrayList(); @@ -127,7 +142,7 @@ public static NodeParser createParser(ServerCommandSource source) { } - public static TextParserV1 createTextParserV1(ServerCommandSource source) { + public static TextParserV1 createTextParserV1(ServerCommandSource source) { var parser = new TextParserV1(); Config config = ConfigManager.getConfig(); @@ -157,7 +172,7 @@ public static Map getEmotes(PlaceholderContext context) { } public static Text formatFor(PlaceholderContext context, String input) { - var parser = createParser(context.hasPlayer() ? context.player().getCommandSource() : context.server().getCommandSource()); + var parser = createParser(context); var config = ConfigManager.getConfig(); if (StyledChatMod.USE_FABRIC_API) { input = StyledChatEvents.PRE_MESSAGE_CONTENT.invoker().onPreMessage(input, context); @@ -165,7 +180,7 @@ public static Text formatFor(PlaceholderContext context, String input) { var emotes = getEmotes(context); - var value = additionalParsing(new ParentNode(parser.parseNodes(new LiteralNode(input))), context); + var value = TextNode.asSingle(parser.parseNodes(new LiteralNode(input))); if (StyledChatMod.USE_FABRIC_API) { value = StyledChatEvents.MESSAGE_CONTENT.invoker().onMessage(value, context); @@ -287,59 +302,19 @@ public static MessageDecorator getCommandDecorator(String context, ServerCom }); }; } + + @Deprecated public static TextNode additionalParsing(TextNode node, PlaceholderContext context) { - var config = ConfigManager.getConfig(); - if (config.configData.formatting.parseLinksInChat) { + + if (ConfigManager.getConfig().configData.formatting.parseLinksInChat) { node = parseLinks(node, context); } return node; } + @Deprecated public static TextNode parseLinks(TextNode node, PlaceholderContext context) { - if (node instanceof LiteralNode literalNode) { - var style = ConfigManager.getConfig().getLinkStyle(context); - var input = literalNode.value(); - var list = new ArrayList(); - - Matcher matcher = URL_REGEX.matcher(input); - int currentPos = 0; - int currentEnd = input.length(); - - while (matcher.find()) { - if (currentEnd <= matcher.start()) { - break; - } - - String betweenText = input.substring(currentPos, matcher.start()); - - if (betweenText.length() != 0) { - list.add(new LiteralNode(betweenText)); - } - - list.add(new ClickActionNode(Placeholders.parseNodes(style, Placeholders.PREDEFINED_PLACEHOLDER_PATTERN, Map.of("link", Text.literal(matcher.group()))).getChildren(), ClickEvent.Action.OPEN_URL, new LiteralNode(matcher.group()))); - - currentPos = matcher.end(); - } - - if (currentPos < currentEnd) { - String restOfText = input.substring(currentPos, currentEnd); - if (restOfText.length() != 0) { - list.add(new LiteralNode(restOfText)); - } - } - - return list.size() == 1 ? list.get(0) : new ParentNode(list.toArray(new TextNode[0])); - } else if (node instanceof ParentTextNode parentTextNode) { - var list = new ArrayList(); - - for (var child : parentTextNode.getChildren()) { - list.add(parseLinks(child, context)); - } - - return parentTextNode.copyWith(list.toArray(new TextNode[0])); - } - - return node; + return TextNode.asSingle(LinkParser.parse(node, context)); } public static boolean isHandledByMod(RegistryKey typeKey) { diff --git a/src/main/java/eu/pb4/styledchat/config/ChatStyle.java b/src/main/java/eu/pb4/styledchat/config/ChatStyle.java index fb24dd3..7956e74 100644 --- a/src/main/java/eu/pb4/styledchat/config/ChatStyle.java +++ b/src/main/java/eu/pb4/styledchat/config/ChatStyle.java @@ -49,6 +49,7 @@ public class ChatStyle { public final TextNode spoilerStyle; public final String spoilerSymbol; public final TextNode linkStyle; + public final TextNode mentionStyle; public final Map emoticons = new HashMap<>(); public final Object2BooleanMap formatting = new Object2BooleanOpenHashMap<>(); @@ -78,6 +79,7 @@ public ChatStyle(ChatStyleData data, ChatStyle defaultStyle) { this.spoilerStyle = data.spoilerStyle != null ? StyledChatUtils.parseText(data.spoilerStyle) : defaultStyle.spoilerStyle; this.spoilerSymbol = data.spoilerSymbol != null ? data.spoilerSymbol : defaultStyle.spoilerSymbol; this.linkStyle = data.linkStyle != null ? StyledChatUtils.parseText(data.linkStyle) : defaultStyle.linkStyle; + this.mentionStyle = data.mentionStyle != null ? StyledChatUtils.parseText(data.mentionStyle) : defaultStyle.mentionStyle; for (var emoticon : data.emoticons.entrySet()) { this.emoticons.put(emoticon.getKey(), StyledChatUtils.parseText(emoticon.getValue())); @@ -112,6 +114,7 @@ public ChatStyle(ChatStyleData data) { this.spoilerStyle = data.spoilerStyle != null ? StyledChatUtils.parseText(data.spoilerStyle) : null; this.spoilerSymbol = data.spoilerSymbol != null ? data.spoilerSymbol : null; this.linkStyle = data.linkStyle != null ? StyledChatUtils.parseText(data.linkStyle) : null; + this.mentionStyle = data.mentionStyle != null ? StyledChatUtils.parseText(data.mentionStyle) : null; for (var emoticon : data.emoticons.entrySet()) { this.emoticons.put(emoticon.getKey(), StyledChatUtils.parseText(emoticon.getValue())); @@ -421,6 +424,11 @@ public TextNode getLink() { return this.linkStyle; } + @Nullable + public TextNode getMention() { + return this.mentionStyle; + } + @Nullable public TextNode getSpoilerStyle() { return this.spoilerStyle; diff --git a/src/main/java/eu/pb4/styledchat/config/Config.java b/src/main/java/eu/pb4/styledchat/config/Config.java index 333a59b..4674688 100644 --- a/src/main/java/eu/pb4/styledchat/config/Config.java +++ b/src/main/java/eu/pb4/styledchat/config/Config.java @@ -309,6 +309,19 @@ public TextNode getLinkStyle(PlaceholderContext ctx) { return this.defaultStyle.getLink(); } + public TextNode getMentionStyle(PlaceholderContext ctx) { + var context2 = PredicateContext.of(ctx.source()); + for (var entry : this.permissionStyle) { + if (entry.require.test(context2).success()) { + var text = entry.getMention(); + if (text != null) { + return text; + } + } + } + return this.defaultStyle.getMention(); + } + public Text getPetDeath(TameableEntity entity, Text vanillaMessage) { var context2 = PredicateContext.of(entity); for (var entry : this.permissionStyle) { diff --git a/src/main/java/eu/pb4/styledchat/config/data/ChatStyleData.java b/src/main/java/eu/pb4/styledchat/config/data/ChatStyleData.java index 2521d0e..07a4aa6 100644 --- a/src/main/java/eu/pb4/styledchat/config/data/ChatStyleData.java +++ b/src/main/java/eu/pb4/styledchat/config/data/ChatStyleData.java @@ -26,6 +26,8 @@ public class ChatStyleData implements Cloneable { @SerializedName("link_style") public String linkStyle; + @SerializedName("mention_style") + public String mentionStyle; @SerializedName("spoiler_style") public String spoilerStyle; @SerializedName("spoiler_symbol") @@ -130,6 +132,7 @@ public static ChatStyleData createDefault() { data.messages.petDeathMessage = "${default_message}"; data.linkStyle = "${link}"; + data.mentionStyle = "%player:displayname%"; data.spoilerStyle = "${spoiler}"; data.spoilerSymbol = "▌"; diff --git a/src/main/java/eu/pb4/styledchat/config/data/ConfigData.java b/src/main/java/eu/pb4/styledchat/config/data/ConfigData.java index 5e89c6b..f17df56 100644 --- a/src/main/java/eu/pb4/styledchat/config/data/ConfigData.java +++ b/src/main/java/eu/pb4/styledchat/config/data/ConfigData.java @@ -20,6 +20,8 @@ public static class Formatting { public boolean legacyChatFormatting = false; @SerializedName("parse_links") public boolean parseLinksInChat = true; + @SerializedName("parse_mentions") + public boolean parseMentionsInChat = true; @SerializedName("markdown") public boolean markdown = true; @SerializedName("formatting_from_other_mods") diff --git a/src/main/java/eu/pb4/styledchat/parser/LinkParser.java b/src/main/java/eu/pb4/styledchat/parser/LinkParser.java new file mode 100644 index 0000000..6907e3c --- /dev/null +++ b/src/main/java/eu/pb4/styledchat/parser/LinkParser.java @@ -0,0 +1,73 @@ +package eu.pb4.styledchat.parser; + +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.Placeholders; +import eu.pb4.placeholders.api.node.LiteralNode; +import eu.pb4.placeholders.api.node.TextNode; +import eu.pb4.placeholders.api.node.parent.ClickActionNode; +import eu.pb4.placeholders.api.node.parent.ParentTextNode; +import eu.pb4.placeholders.api.parsers.NodeParser; +import eu.pb4.styledchat.config.ConfigManager; +import net.minecraft.text.ClickEvent; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; + +import static eu.pb4.styledchat.StyledChatUtils.URL_REGEX; + +public record LinkParser(TextNode style) implements NodeParser { + + @Override + public TextNode[] parseNodes(TextNode node) { + if (node instanceof LiteralNode literalNode) { + var input = literalNode.value(); + var list = new ArrayList(); + + Matcher matcher = URL_REGEX.matcher(input); + int currentPos = 0; + int currentEnd = input.length(); + + while (matcher.find()) { + if (currentEnd <= matcher.start()) { + break; + } + + String betweenText = input.substring(currentPos, matcher.start()); + + if (betweenText.length() != 0) { + list.add(new LiteralNode(betweenText)); + } + + list.add(new ClickActionNode(Placeholders.parseNodes(style, Placeholders.PREDEFINED_PLACEHOLDER_PATTERN, Map.of("link", Text.literal(matcher.group()))).getChildren(), ClickEvent.Action.OPEN_URL, new LiteralNode(matcher.group()))); + + currentPos = matcher.end(); + } + + if (currentPos < currentEnd) { + String restOfText = input.substring(currentPos, currentEnd); + if (restOfText.length() != 0) { + list.add(new LiteralNode(restOfText)); + } + } + + return list.toArray(new TextNode[0]); + } else if (node instanceof ParentTextNode parentTextNode) { + var list = new ArrayList(); + + for (var child : parentTextNode.getChildren()) { + list.addAll(List.of(this.parseNodes(child))); + } + + return new TextNode[] { parentTextNode.copyWith(list.toArray(new TextNode[0])) }; + } + + return new TextNode[] { node }; + } + + public static TextNode[] parse(TextNode node, PlaceholderContext context) { + return new LinkParser(ConfigManager.getConfig().getLinkStyle(context)).parseNodes(node); + } +} diff --git a/src/main/java/eu/pb4/styledchat/parser/MentionParser.java b/src/main/java/eu/pb4/styledchat/parser/MentionParser.java new file mode 100644 index 0000000..9dfa07d --- /dev/null +++ b/src/main/java/eu/pb4/styledchat/parser/MentionParser.java @@ -0,0 +1,53 @@ +package eu.pb4.styledchat.parser; + +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.Placeholders; +import eu.pb4.placeholders.api.node.LiteralNode; +import eu.pb4.placeholders.api.node.TextNode; +import eu.pb4.placeholders.api.node.parent.ParentTextNode; +import eu.pb4.placeholders.api.parsers.NodeParser; +import me.drex.vanish.api.VanishAPI; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.server.network.ServerPlayerEntity; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +public record MentionParser(TextNode style, PlaceholderContext context) implements NodeParser { + + public static final boolean VANISH = FabricLoader.getInstance().isModLoaded("melius-vanish"); + + @Override + public TextNode[] parseNodes(TextNode node) { + if (node instanceof LiteralNode literalNode) { + return parseInput(literalNode.value()); + } else if (node instanceof ParentTextNode parentTextNode) { + var list = new ArrayList(); + + for (var child : parentTextNode.getChildren()) { + list.addAll(List.of(this.parseNodes(child))); + } + + return new TextNode[]{parentTextNode.copyWith(list.toArray(new TextNode[]{}))}; + } + + return new TextNode[]{node}; + } + + public TextNode[] parseInput(String input) { + if (input.isEmpty()) return new TextNode[]{}; + for (ServerPlayerEntity player : context.server().getPlayerManager().getPlayerList()) { + if (VANISH && VanishAPI.isVanished(player)) continue; + int startPos = input.indexOf(player.getEntityName()); + if (startPos != -1) { + int endPos = startPos + player.getEntityName().length(); + TextNode[] before = parseInput(input.substring(0, startPos)); + TextNode mention = TextNode.convert(Placeholders.parseText(style, PlaceholderContext.of(player))); + TextNode[] after = parseInput(input.substring(Math.min(endPos, input.length()))); + return Stream.of(before, new TextNode[]{mention}, after).flatMap(Stream::of).toArray(TextNode[]::new); + } + } + return new TextNode[]{new LiteralNode(input)}; + } +}