Skip to content

Commit

Permalink
Merge pull request #5 from mcjunshi/1.20.1
Browse files Browse the repository at this point in the history
enhance chat module
  • Loading branch information
sakurawald authored Feb 26, 2024
2 parents 5ae016c + b0dd27c commit 2347d8f
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;

import io.github.sakurawald.Fuji;
import io.github.sakurawald.config.Configs;
import io.github.sakurawald.module.ModuleManager;
Expand All @@ -20,7 +21,10 @@
import net.kyori.adventure.text.TextReplacementConfig;
import net.kyori.adventure.text.event.ClickCallback;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Formatter;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
Expand All @@ -38,12 +42,16 @@
import java.util.Arrays;
import java.util.Comparator;
import java.util.Queue;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;

public class ChatModule extends ModuleInitializer {

private static final Pattern xaero_waypoint_pattern = Pattern.compile(
"^xaero-waypoint:([^:]+):.+:(-?\\d+):(~?-?\\d*):(-?\\d+).*?-(.*)-waypoints$");
private static final Pattern pos_pattern = Pattern.compile("^xaero-waypoint:|pos");
private final MiniMessage miniMessage = MiniMessage.builder().build();
private final MainStatsModule mainStatsModule = ModuleManager.getInitializer(MainStatsModule.class);
@Getter
Expand Down Expand Up @@ -71,28 +79,103 @@ public void onReload() {
@Override
public void registerCommand(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext registryAccess, Commands.CommandSelection environment) {
dispatcher.register(
Commands.literal("chat")
.then(literal("format")
.then(argument("format", StringArgumentType.greedyString())
.executes(this::$format)
)));
Commands.literal("chat")
.then(Commands.literal("format")
.then(Commands.literal("reset")
.executes(this::resetFormat)
)
.then(Commands.literal("set")
.then(argument("format", StringArgumentType.greedyString())
.executes(this::format)
)
)
)
);
}

private int $format(CommandContext<CommandSourceStack> ctx) {

private int format(CommandContext<CommandSourceStack> ctx) {
return CommandUtil.playerOnlyCommand(ctx, player -> {
String name = player.getGameProfile().getName();
String format = StringArgumentType.getString(ctx, "format");
Configs.chatHandler.model().format.player2format.put(name, format);
Configs.chatHandler.saveToDisk();
format = MessageUtil.ofString(player,"chat.format.set").replace("%s",format);
Component formatComponent = miniMessage.deserialize(format, Formatter.date("date", LocalDateTime.now(ZoneId.systemDefault()))).asComponent();
Component component = formatComponent
.replaceText("%player%",Component.text(name))
.replaceText("%message%",MessageUtil.ofComponent(player, "chat.format.show"));
player.sendMessage(component);
return Command.SINGLE_SUCCESS;
});
}


private int resetFormat(CommandContext<CommandSourceStack> ctx)
{
return CommandUtil.playerOnlyCommand(ctx, player -> {
String name = player.getGameProfile().getName();
Configs.chatHandler.model().format.player2format.remove(name);
Configs.chatHandler.saveToDisk();
MessageUtil.sendMessage(player, "chat.format.reset");
return Command.SINGLE_SUCCESS;
});
}

private Component resolvePositionTag(ServerPlayer player, Component component) {
Component replacement = Component.text("%s (%d %d %d) %s".formatted(player.serverLevel().dimension().location(),
player.getBlockX(), player.getBlockY(), player.getBlockZ(), player.chunkPosition().toString())).color(NamedTextColor.GOLD);
return component.replaceText(TextReplacementConfig.builder().match("(?<=^|\\s)pos(?=\\s|$)").replacement(replacement).build());
String str = PlainTextComponentSerializer.plainText().serialize(component);
Matcher posmatcher = pos_pattern.matcher(str);
if (posmatcher.find()) {
String hoverText;
String click_command;
int x,z;
String y;
String dim_name;
Matcher xaeroMap_matcher = xaero_waypoint_pattern.matcher(str);
if (xaeroMap_matcher.find()) { //小地图路径点
hoverText = xaeroMap_matcher.group(1).replaceAll("^col^",":").replaceAll("^ast^","*"); //xaeroMap使用^col^代替:,^ast^代替*以确保解析正确
x = Integer.parseInt(xaeroMap_matcher.group(2));
y = xaeroMap_matcher.group(3);
z = Integer.parseInt(xaeroMap_matcher.group(4));
dim_name = xaeroMap_matcher.group(5).replaceFirst(".*\\$",""); //在部分自定义纬度,xaeroMap使用类似dim%minecraft$开头
click_command = str
.replaceFirst("xaero-waypoint","/xaero_waypoint_add") //小地图分享格式:xaero-waypoint:name:n:0:~:0:0:false:0:Internal-overworld-waypoints
.replaceAll(":Internal-",":Internal_") //小地图指令格式:/xaero_waypoint:name:n:0:~:0:0:false:0:Internal_overworld_waypoints
.replaceAll("-waypoints$","_waypoints");
} else {
hoverText = MessageUtil.ofString(player,"chat.current_pos");
dim_name = player.serverLevel().dimension().location().toString().replaceFirst("minecraft:","");
x = player.getBlockX();
y = Integer.toString(player.getBlockY());
z = player.getBlockZ();
click_command = MessageUtil.ofString(player,"chat.xaero_waypoint_add.command",x, y, z,dim_name.replaceAll(":","$"));
}
switch (dim_name) {
case "overworld":
hoverText += "\n"+MessageUtil.ofString(player,"the_nether")
+": %d %s %d\n".formatted(x/8, y, z/8);
break;
case "the_nether":
hoverText += "\n"+MessageUtil.ofString(player,"overworld")
+": %d %s %d\n".formatted(x*8, y, z*8);
break;
}
String dim_display_name;
if (MessageUtil.containsKey(player,dim_name)) {
dim_display_name = MessageUtil.ofString(player,dim_name);
} else {
dim_display_name = dim_name;
}
Component replacement = Component.text("[%d %s %d, %s]".formatted(x, y, z, dim_display_name))
.decoration(TextDecoration.ITALIC, true)
.clickEvent(ClickEvent.runCommand(click_command))
.hoverEvent(Component.text(hoverText).append(MessageUtil.ofComponent(player,"chat.xaero_waypoint_add")));
return component.replaceText(TextReplacementConfig.builder()
.match("^xaero-waypoint:.*|pos")
.replacement(replacement)
.build());
}
return component;
}

private Component resolveItemTag(ServerPlayer player, Component component) {
Expand All @@ -101,7 +184,7 @@ private Component resolveItemTag(ServerPlayer player, Component component) {
player.getMainHandItem().getDisplayName().asComponent()
.hoverEvent(MessageUtil.ofComponent(player, "display.click.prompt"))
.clickEvent(displayCallback(displayUUID));
return component.replaceText(TextReplacementConfig.builder().match("(?<=^|\\s)item(?=\\s|$)").replacement(replacement).build());
return component.replaceText(TextReplacementConfig.builder().match("item").replacement(replacement).build());
}

private Component resolveInvTag(ServerPlayer player, Component component) {
Expand All @@ -110,7 +193,7 @@ private Component resolveInvTag(ServerPlayer player, Component component) {
MessageUtil.ofComponent(player, "display.inventory.text")
.hoverEvent(MessageUtil.ofComponent(player, "display.click.prompt"))
.clickEvent(displayCallback(displayUUID));
return component.replaceText(TextReplacementConfig.builder().match("(?<=^|\\s)inv(?=\\s|$)").replacement(replacement).build());
return component.replaceText(TextReplacementConfig.builder().match("inv").replacement(replacement).build());
}

private Component resolveEnderTag(ServerPlayer player, Component component) {
Expand All @@ -119,7 +202,7 @@ private Component resolveEnderTag(ServerPlayer player, Component component) {
MessageUtil.ofComponent(player, "display.ender_chest.text")
.hoverEvent(MessageUtil.ofComponent(player, "display.click.prompt"))
.clickEvent(displayCallback(displayUUID));
return component.replaceText(TextReplacementConfig.builder().match("(?<=^|\\s)ender(?=\\s|$)").replacement(replacement).build());
return component.replaceText(TextReplacementConfig.builder().match("ender").replacement(replacement).build());
}

@NotNull
Expand All @@ -132,50 +215,93 @@ private ClickEvent displayCallback(String displayUUID) {
.uses(Integer.MAX_VALUE).build());
}

@SuppressWarnings("unused")
private String resolveMentionTag(ServerPlayer player, String str) {
private Component resolveMentionTag(ServerPlayer player, Component component) {
/* resolve player tag */
ArrayList<ServerPlayer> mentionedPlayers = new ArrayList<>();

String[] playerNames = Fuji.SERVER.getPlayerNames();
// fix: mention the longest name first
Arrays.sort(playerNames, Comparator.comparingInt(String::length).reversed());

String str = PlainTextComponentSerializer.plainText().serialize(component);
for (String playerName : playerNames) {
// here we must continue so that mentionPlayers will not be added
if (!str.contains(playerName)) continue;
str = str.replace(playerName, "<aqua>%s</aqua>".formatted(playerName));
Pattern pattern = Pattern.compile("(?:(?<=\\s)|^|@)" + Pattern.quote(playerName));
Matcher matcher = pattern.matcher(str);
if (!matcher.find()) continue;
component = component.replaceText(matcher.group(),
Component.text("@",NamedTextColor.GREEN)
.append(Component.text(playerName,NamedTextColor.DARK_GREEN)));
mentionedPlayers.add(Fuji.SERVER.getPlayerList().getPlayerByName(playerName));
}

/* run mention player task */
if (!mentionedPlayers.isEmpty()) {
MentionPlayersJob.scheduleJob(mentionedPlayers);
}
return str;

return component;

}

public Component resolveLinks(ServerPlayer player, Component component) {

String str = PlainTextComponentSerializer.plainText().serialize(component);

//BV号替换
Matcher bvmatcher = Pattern.compile("(?<=[^/]|^)BV\\w{10}",Pattern.CASE_INSENSITIVE).matcher(str);
while (bvmatcher.find()) {
String bvNumber = bvmatcher.group();
Component textBuilder = Component.text("bilibili")
.decoration(TextDecoration.UNDERLINED, true)
.hoverEvent(HoverEvent.showText(Component.text(bvNumber)))
.clickEvent(ClickEvent.openUrl("https://www.bilibili.com/video/"+bvNumber));
component = component.replaceText(bvNumber, textBuilder);
}

//网址替换
Matcher urlmatcher = Pattern.compile("(https?)://[^\\s/$.?#].[^\\s]*").matcher(str);
Pattern displayPattern = Pattern.compile("(?<=https?://)[^/\\s]{0,15}(?=\\.[^./]{0,20}(/|$))");
while (urlmatcher.find()) {
String url = urlmatcher.group();
Matcher displayMatcher = displayPattern.matcher(url);
String displayText = url;
if (displayMatcher.find()) {
displayText = displayMatcher.group().replaceFirst("www.|m.","");
}
Component textBuilder = Component.text(displayText)
.decoration(TextDecoration.UNDERLINED, true)
.hoverEvent(HoverEvent.showText(Component.text(url)))
.clickEvent(ClickEvent.openUrl(url));
component = component.replaceText(url, textBuilder);
}

return component;
}

public void broadcastChatMessage(ServerPlayer player, String message) {
/* resolve format */
message = Configs.chatHandler.model().format.player2format.getOrDefault(player.getGameProfile().getName(), message)
.replace("%message%", message);
message = resolveMentionTag(player, message);
String format = Configs.configHandler.model().modules.chat.format;
format = format.replace("%message%", message);
format = format.replace("%player%", player.getGameProfile().getName());

//输入文本处理
Component msgComponent = Component.text(message);
msgComponent = resolveLinks(player, msgComponent);
msgComponent = resolveMentionTag(player, msgComponent);
msgComponent = resolveItemTag(player, msgComponent);
msgComponent = resolveInvTag(player, msgComponent);
msgComponent = resolveEnderTag(player, msgComponent);
msgComponent = resolvePositionTag(player, msgComponent);

//玩家发言样式
String format = Configs.chatHandler.model().format.player2format.getOrDefault(
player.getGameProfile().getName(), //获取玩家名对应的样式
Configs.configHandler.model().modules.chat.format); //若无,使用默认样式
/* resolve stats */
if (mainStatsModule != null) {
MainStats stats = MainStats.uuid2stats.getOrDefault(player.getUUID().toString(), new MainStats());
format = stats.update(player).resolve(Fuji.SERVER, format);
}
Component formatComponent = miniMessage.deserialize(
format, Formatter.date("date", LocalDateTime.now(ZoneId.systemDefault()))).asComponent();
//合并
Component component = formatComponent
.replaceText("%player%",Component.text(player.getGameProfile().getName()))
.replaceText("%message%",msgComponent);

/* resolve tags */
Component component = miniMessage.deserialize(format, Formatter.date("date", LocalDateTime.now(ZoneId.systemDefault()))).asComponent();
component = resolveItemTag(player, component);
component = resolveInvTag(player, component);
component = resolveEnderTag(player, component);
component = resolvePositionTag(player, component);
chatHistory.add(component);
// info so that it can be seen in the console
Fuji.LOGGER.info(PlainTextComponentSerializer.plainText().serialize(component));
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/io/github/sakurawald/util/MessageUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ else if (audience instanceof CommandSourceStack source && source.getPlayer() !=
return formatString(value, args);
}

public boolean containsKey(Audience audience, String key) {
ServerPlayer player;
if (audience instanceof ServerPlayer) player = (ServerPlayer) audience;
else if (audience instanceof CommandSourceStack source && source.getPlayer() != null)
player = source.getPlayer();
else player = null;

/* get lang */
String lang;
if (player != null) {
lang = player2lang.getOrDefault(player.getGameProfile().getName(), DEFAULT_LANG);
} else {
lang = DEFAULT_LANG;
}
loadLanguageIfAbsent(lang);
JsonObject json;
json = lang2json.get(!lang2json.containsKey(lang) ? DEFAULT_LANG : lang);

return json.has(key);
}
public static String formatString(String string, Object... args) {
if (args.length > 0) {
return String.format(string, args);
Expand Down
13 changes: 11 additions & 2 deletions src/main/resources/assets/fuji/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
"back": "<#FFA1F5>Back",
"empty": "<#FFA1F5>Empty",
"confirm": "<#FFA1F5>Confirm",
"overworld":"overworld",
"the_nether":"the nether",
"the_end":"the end",
"command.player_only": "<red>This command can only be executed by a player",
"level.no_exists": "<red>Level %s doesn't exist",
"input.syntax.error": "<red>Input Syntax Error",
Expand Down Expand Up @@ -162,5 +165,11 @@
"ping.player": "<gold>Ping of %s: %d ms",
"ping.target.no_found": "<red>Ping target no found.",
"bed.not_found": "<red>No bed found.",
"bed.success": "<gold>Teleported to bed."
}
"bed.success": "<gold>Teleported to bed.",
"chat.format.set": "<gold>Chat format has been modified, current style:</gold><newline>%s<reset><newline><red><click:run_command:/chat format reset> [Reset]</click></red>",
"chat.format.reset": "<gold>Chat format has been reset</gold>",
"chat.format.show": "Current style",
"chat.current_pos": "Current Position",
"chat.xaero_waypoint_add": "Click to add a Xaero's minimap waypoint",
"chat.xaero_waypoint_add.command": "/xaero_waypoint_add:Waypoint:·:%d:%s:%d:0:false:0:Internal_%s_waypoints"
}
13 changes: 11 additions & 2 deletions src/main/resources/assets/fuji/lang/zh_cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
"back": "<#FFA1F5>返回",
"empty": "<#FFA1F5>空",
"confirm": "<#FFA1F5>确认",
"overworld":"主世界",
"the_nether":"下界",
"the_end":"末地",
"command.player_only": "<red>该命名只能由玩家执行",
"level.no_exists": "<red>维度 %s 不存在",
"input.syntax.error": "<red>输入的格式错误",
Expand Down Expand Up @@ -162,5 +165,11 @@
"ping.player": "<gold>玩家 %s 的延迟: %d ms",
"ping.target.no_found": "<red>目标玩家未找到",
"bed.not_found": "<red>未找到床",
"bed.success": "<gold>已传送到床"
}
"bed.success": "<gold>已传送到床",
"chat.format.set":"<gold>已修改聊天样式, 当前样式:</gold><newline>%s<reset><newline><red><click:run_command:/chat format reset> [重置]</click></red>",
"chat.format.reset":"<gold>已重置聊天样式</gold>",
"chat.format.show":"当前样式",
"chat.current_pos":"当前位置",
"chat.xaero_waypoint_add":"点击添加小地图路径点",
"chat.xaero_waypoint_add.command":"/xaero_waypoint_add:路径点:·:%d:%s:%d:0:false:0:Internal_%s_waypoints"
}

0 comments on commit 2347d8f

Please sign in to comment.