9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-29 20:09:13 +00:00

实验性改善

This commit is contained in:
XiaoMoMi
2025-04-11 22:47:48 +08:00
parent 182555b70c
commit af7713534c
5 changed files with 168 additions and 97 deletions

View File

@@ -11,17 +11,14 @@ import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.bukkit.util.LegacyInventoryUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.font.AbstractFontManager;
import net.momirealms.craftengine.core.font.Emoji;
import net.momirealms.craftengine.core.font.EmojiParameters;
import net.momirealms.craftengine.core.font.EmojiComponentProcessResult;
import net.momirealms.craftengine.core.font.EmojiTextProcessResult;
import net.momirealms.craftengine.core.font.FontManager;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.CharacterUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.util.context.PlayerContext;
import org.ahocorasick.trie.Token;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -36,10 +33,11 @@ import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.view.AnvilView;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class BukkitFontManager extends AbstractFontManager implements Listener {
private final BukkitCraftEngine plugin;
@@ -127,9 +125,12 @@ public class BukkitFontManager extends AbstractFontManager implements Listener {
}
if (renameText == null || renameText.isEmpty()) return;
IllegalCharacterProcessResult processResult = processIllegalCharacters(renameText);
if (processResult.has()) {
renameText = processResult.newText();
}
Component itemName = Component.text(renameText);
EmojiReplaceProcessResult replaceProcessResult = processEmojiNoCopy(player, itemName, renameText);
EmojiComponentProcessResult replaceProcessResult = replaceComponentEmoji(itemName, plugin.adapt(player), renameText);
if (replaceProcessResult.changed()) {
Item<ItemStack> wrapped = this.plugin.itemManager().wrap(result);
wrapped.customName(AdventureHelper.componentToJson(replaceProcessResult.newText()));
@@ -145,7 +146,7 @@ public class BukkitFontManager extends AbstractFontManager implements Listener {
JsonElement json = ComponentUtils.paperAdventureToJsonElement(lines.get(i));
if (json == null) continue;
Component line = AdventureHelper.jsonElementToComponent(json);
EmojiReplaceProcessResult result = processEmojiNoCopy(player, line);
EmojiComponentProcessResult result = replaceComponentEmoji(line, plugin.adapt(player));
if (result.changed()) {
try {
Reflections.method$SignChangeEvent$line.invoke(event, i, ComponentUtils.jsonElementToPaperAdventure(AdventureHelper.componentToJsonElement(result.newText())));
@@ -175,7 +176,7 @@ public class BukkitFontManager extends AbstractFontManager implements Listener {
for (int i = 0; i < pages.size(); i++) {
JsonElement json = ComponentUtils.paperAdventureToJsonElement(pages.get(i));
Component page = AdventureHelper.jsonElementToComponent(json);
EmojiReplaceProcessResult result = processEmojiNoCopy(player, page);
EmojiComponentProcessResult result = replaceComponentEmoji(page, plugin.adapt(player));
if (result.changed()) {
changed = true;
try {
@@ -197,19 +198,19 @@ public class BukkitFontManager extends AbstractFontManager implements Listener {
try {
Object originalMessage = Reflections.field$AsyncChatDecorateEvent$originalMessage.get(event);
String rawJsonMessage = ComponentUtils.paperAdventureToJson(originalMessage);
String jsonMessage = replaceJsonEmoji(rawJsonMessage, this.plugin.adapt(player));
boolean hasChanged = !rawJsonMessage.equals(jsonMessage);
EmojiTextProcessResult processResult = replaceJsonEmoji(rawJsonMessage, this.plugin.adapt(player));
boolean hasChanged = processResult.replaced();
if (!player.hasPermission(FontManager.BYPASS_CHAT)) {
IllegalCharacterProcessResult result = processIllegalCharacters(jsonMessage);
IllegalCharacterProcessResult result = processIllegalCharacters(processResult.text());
if (result.has()) {
Object component = ComponentUtils.jsonToPaperAdventure(result.newText());
Reflections.method$AsyncChatDecorateEvent$result.invoke(event, component);
} else if (hasChanged) {
Object component = ComponentUtils.jsonToPaperAdventure(jsonMessage);
Object component = ComponentUtils.jsonToPaperAdventure(processResult.text());
Reflections.method$AsyncChatDecorateEvent$result.invoke(event, component);
}
} else if (hasChanged) {
Object component = ComponentUtils.jsonToPaperAdventure(jsonMessage);
Object component = ComponentUtils.jsonToPaperAdventure(processResult.text());
Reflections.method$AsyncChatDecorateEvent$result.invoke(event, component);
}
} catch (IllegalAccessException | InvocationTargetException e) {
@@ -217,43 +218,6 @@ public class BukkitFontManager extends AbstractFontManager implements Listener {
}
}
private EmojiReplaceProcessResult processEmojiNoCopy(Player player, Component text) {
return this.processEmojiNoCopy(player, text, AdventureHelper.plainTextContent(text));
}
private EmojiReplaceProcessResult processEmojiNoCopy(Player player, Component text, @NotNull String raw) {
boolean replaced = false;
Set<String> processed = new HashSet<>();
int times = 0;
for (Token token : super.emojiKeywordTrie.tokenize(raw)) {
if (!token.isMatch())
continue;
String fragment = token.getFragment();
if (processed.contains(fragment))
continue;
Emoji emoji = super.emojiMapper.get(token.getFragment());
if (emoji == null || (emoji.permission() != null && !player.hasPermission(Objects.requireNonNull(emoji.permission()))))
continue;
text = text.replaceText(builder -> builder.matchLiteral(token.getFragment())
.replacement(AdventureHelper.miniMessage().deserialize(
emoji.content(),
PlayerContext.of(plugin.adapt(player),
ContextHolder.builder()
.withOptionalParameter(EmojiParameters.EMOJI, emoji.emojiImage())
.withParameter(EmojiParameters.KEYWORD, emoji.keywords().get(0))
.build()
).tagResolvers()
)));
replaced = true;
processed.add(fragment);
if (++times >= Config.maxEmojisPerParse()) {
break;
}
}
if (!replaced) return EmojiReplaceProcessResult.failed();
return EmojiReplaceProcessResult.success(text);
}
private IllegalCharacterProcessResult processIllegalCharacters(String raw) {
boolean hasIllegal = false;
// replace illegal image usage
@@ -300,15 +264,4 @@ public class BukkitFontManager extends AbstractFontManager implements Listener {
return new IllegalCharacterProcessResult(false, null);
}
}
public record EmojiReplaceProcessResult(boolean changed, Component newText) {
public static EmojiReplaceProcessResult success(Component newText) {
return new EmojiReplaceProcessResult(true, newText);
}
public static EmojiReplaceProcessResult failed() {
return new EmojiReplaceProcessResult(false, null);
}
}
}

View File

@@ -14,11 +14,15 @@ import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.util.context.PlayerContext;
import org.ahocorasick.trie.Token;
import org.ahocorasick.trie.Trie;
import org.jetbrains.annotations.NotNull;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public abstract class AbstractFontManager implements FontManager {
private final CraftEngine plugin;
@@ -104,14 +108,20 @@ public abstract class AbstractFontManager implements FontManager {
}
@Override
public String replaceMiniMessageEmoji(String miniMessage, Player player) {
if (this.emojiKeywordTrie == null) {
return miniMessage;
public EmojiTextProcessResult replaceMiniMessageEmoji(@NotNull String miniMessage, Player player, int maxTimes) {
if (this.emojiKeywordTrie == null || maxTimes <= 0) {
return EmojiTextProcessResult.notReplaced(miniMessage);
}
Map<String, String> replacements = new HashMap<>();
for (Token token : this.emojiKeywordTrie.tokenize(miniMessage)) {
if (!token.isMatch()) continue;
Emoji emoji = this.emojiMapper.get(token.getFragment());
if (emoji == null) continue;
if (!token.isMatch())
continue;
String fragment = token.getFragment();
if (replacements.containsKey(fragment))
continue;
Emoji emoji = this.emojiMapper.get(fragment);
if (emoji == null || (player != null && emoji.permission() != null && !player.hasPermission(emoji.permission())))
continue;
Component content = AdventureHelper.miniMessage().deserialize(
emoji.content(),
PlayerContext.of(player, ContextHolder.builder()
@@ -119,40 +129,96 @@ public abstract class AbstractFontManager implements FontManager {
.withParameter(EmojiParameters.KEYWORD, emoji.keywords().get(0))
.build()).tagResolvers()
);
miniMessage = miniMessage.replace(token.getFragment(), AdventureHelper.componentToMiniMessage(content));
replacements.put(fragment, AdventureHelper.componentToMiniMessage(content));
}
return miniMessage;
if (replacements.isEmpty()) return EmojiTextProcessResult.notReplaced(miniMessage);
String regex = replacements.keySet().stream()
.map(Pattern::quote)
.collect(Collectors.joining("|"));
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(miniMessage);
StringBuilder sb = new StringBuilder();
int count = 0;
while (matcher.find() && count < maxTimes) {
String key = matcher.group();
String replacement = replacements.get(key);
if (replacement != null) {
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
count++;
} else {
// should not reach this
matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group()));
}
}
matcher.appendTail(sb);
return EmojiTextProcessResult.replaced(sb.toString());
}
@Override
public String replaceJsonEmoji(String jsonText, Player player) {
public EmojiTextProcessResult replaceJsonEmoji(@NotNull String jsonText, Player player, int maxTimes) {
if (this.emojiKeywordTrie == null) {
return jsonText;
return EmojiTextProcessResult.notReplaced(jsonText);
}
Map<String, Emoji> emojis = new HashMap<>();
Map<String, Component> emojis = new HashMap<>();
for (Token token : this.emojiKeywordTrie.tokenize(jsonText)) {
if (token.isMatch()) {
emojis.put(token.getFragment(), this.emojiMapper.get(token.getFragment()));
}
}
if (emojis.isEmpty()) return jsonText;
Component component = AdventureHelper.jsonToComponent(jsonText);
for (Map.Entry<String, Emoji> entry : emojis.entrySet()) {
Emoji emoji = entry.getValue();
if (player != null && emoji.permission() != null && !player.hasPermission(emoji.permission())) {
if (!token.isMatch())
continue;
}
component = component.replaceText(builder -> builder.matchLiteral(entry.getKey())
.replacement(
AdventureHelper.miniMessage().deserialize(
emoji.content(),
PlayerContext.of(player, ContextHolder.builder()
.withOptionalParameter(EmojiParameters.EMOJI, emoji.emojiImage())
.withParameter(EmojiParameters.KEYWORD, emoji.keywords().get(0))
.build()).tagResolvers())
));
String fragment = token.getFragment();
if (emojis.containsKey(fragment)) continue;
Emoji emoji = this.emojiMapper.get(fragment);
if (emoji == null || (player != null && emoji.permission() != null && !player.hasPermission(emoji.permission())))
continue;
emojis.put(fragment, AdventureHelper.miniMessage().deserialize(
emoji.content(),
PlayerContext.of(player, ContextHolder.builder()
.withOptionalParameter(EmojiParameters.EMOJI, emoji.emojiImage())
.withParameter(EmojiParameters.KEYWORD, emoji.keywords().get(0))
.build()).tagResolvers())
);
if (emojis.size() >= maxTimes) break;
}
return AdventureHelper.componentToJson(component);
if (emojis.isEmpty()) return EmojiTextProcessResult.notReplaced(jsonText);
Component component = AdventureHelper.jsonToComponent(jsonText);
String patternString = emojis.keySet().stream()
.map(Pattern::quote)
.collect(Collectors.joining("|"));
component = component.replaceText(builder -> builder.times(maxTimes)
.match(Pattern.compile(patternString))
.replacement((result, b) -> emojis.get(result.group())));
return EmojiTextProcessResult.replaced(AdventureHelper.componentToJson(component));
}
@Override
public EmojiComponentProcessResult replaceComponentEmoji(@NotNull Component text, Player player, @NotNull String raw, int maxTimes) {
Map<String, Component> emojis = new HashMap<>();
for (Token token : this.emojiKeywordTrie.tokenize(raw)) {
if (!token.isMatch())
continue;
String fragment = token.getFragment();
if (emojis.containsKey(fragment))
continue;
Emoji emoji = this.emojiMapper.get(token.getFragment());
if (emoji == null || (player != null && emoji.permission() != null && !player.hasPermission(Objects.requireNonNull(emoji.permission()))))
continue;
emojis.put(fragment, AdventureHelper.miniMessage().deserialize(
emoji.content(),
PlayerContext.of(player,
ContextHolder.builder()
.withOptionalParameter(EmojiParameters.EMOJI, emoji.emojiImage())
.withParameter(EmojiParameters.KEYWORD, emoji.keywords().get(0))
.build()
).tagResolvers()
));
if (emojis.size() >= maxTimes) break;
}
if (emojis.isEmpty()) return EmojiComponentProcessResult.failed();
String patternString = emojis.keySet().stream()
.map(Pattern::quote)
.collect(Collectors.joining("|"));
text = text.replaceText(builder -> builder.times(maxTimes)
.match(Pattern.compile(patternString))
.replacement((result, b) -> emojis.get(result.group())));
return EmojiComponentProcessResult.success(text);
}
@Override

View File

@@ -0,0 +1,14 @@
package net.momirealms.craftengine.core.font;
import net.kyori.adventure.text.Component;
public record EmojiComponentProcessResult(boolean changed, Component newText) {
public static EmojiComponentProcessResult success(Component newText) {
return new EmojiComponentProcessResult(true, newText);
}
public static EmojiComponentProcessResult failed() {
return new EmojiComponentProcessResult(false, null);
}
}

View File

@@ -0,0 +1,12 @@
package net.momirealms.craftengine.core.font;
public record EmojiTextProcessResult(boolean replaced, String text) {
public static EmojiTextProcessResult replaced(String text) {
return new EmojiTextProcessResult(true, text);
}
public static EmojiTextProcessResult notReplaced(String text) {
return new EmojiTextProcessResult(false, text);
}
}

View File

@@ -3,10 +3,14 @@ package net.momirealms.craftengine.core.font;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.Manageable;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.CharacterUtils;
import net.momirealms.craftengine.core.util.FormatUtils;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
@@ -23,11 +27,33 @@ public interface FontManager extends Manageable {
String stripTags(String text);
default EmojiComponentProcessResult replaceComponentEmoji(@NotNull Component text, @Nullable Player player) {
return replaceComponentEmoji(text, player, Config.maxEmojisPerParse());
}
default EmojiComponentProcessResult replaceComponentEmoji(@NotNull Component text, @Nullable Player player, int maxTimes) {
return replaceComponentEmoji(text, player, AdventureHelper.plainTextContent(text), maxTimes);
}
default EmojiComponentProcessResult replaceComponentEmoji(@NotNull Component text, @Nullable Player player, String raw) {
return replaceComponentEmoji(text, player, raw, Config.maxEmojisPerParse());
}
EmojiComponentProcessResult replaceComponentEmoji(@NotNull Component text, @Nullable Player player, @NotNull String raw, int maxTimes);
ConfigSectionParser[] parsers();
String replaceMiniMessageEmoji(String miniMessage, Player player);
default EmojiTextProcessResult replaceMiniMessageEmoji(@NotNull String miniMessage, @Nullable Player player) {
return replaceMiniMessageEmoji(miniMessage, player, Config.maxEmojisPerParse());
}
String replaceJsonEmoji(String jsonText, Player player);
EmojiTextProcessResult replaceMiniMessageEmoji(@NotNull String miniMessage, @Nullable Player player, int maxTimes);
default EmojiTextProcessResult replaceJsonEmoji(@NotNull String json, @Nullable Player player) {
return replaceJsonEmoji(json, player, Config.maxEmojisPerParse());
}
EmojiTextProcessResult replaceJsonEmoji(@NotNull String jsonText, @Nullable Player player, int maxTimes);
boolean isDefaultFontInUse();