9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-26 02:19:23 +00:00

feat(emoji): 增加 emoji 解析支持

This commit is contained in:
jhqwqmc
2025-04-10 15:07:48 +08:00
parent ea096b08a2
commit 4d947fe6b8
6 changed files with 156 additions and 35 deletions

View File

@@ -237,6 +237,8 @@ gui:
performance:
# Maximum chain update depth when fixing client visuals
max-block-chain-update-limit: 64
# Maximum emoji parsed each time
max-emoji-parsed-limit: 100
light-system:
enable: true

View File

@@ -1,5 +1,7 @@
package net.momirealms.craftengine.bukkit.font;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import io.papermc.paper.event.player.AsyncChatCommandDecorateEvent;
import io.papermc.paper.event.player.AsyncChatDecorateEvent;
import net.kyori.adventure.text.Component;
@@ -25,14 +27,17 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.event.inventory.PrepareAnvilEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerEditBookEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.view.AnvilView;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.function.Consumer;
public class BukkitFontManager extends AbstractFontManager implements Listener {
private final BukkitCraftEngine plugin;
@@ -105,31 +110,104 @@ public class BukkitFontManager extends AbstractFontManager implements Listener {
if (renameText == null || renameText.isEmpty()) return;
Component itemName = Component.text(renameText);
boolean replaced = false;
for (Token token : super.emojiKeywordTrie.tokenize(renameText)) {
final int[] parsedCount = {0};
processComponent(itemName, player, parsedCount[0], (text) -> {
if (parsedCount[0]++ >= Config.maxEmojiParsed()) return;
Item<ItemStack> wrapped = this.plugin.itemManager().wrap(result);
wrapped.customName(AdventureHelper.componentToJson(text));
event.setResult(wrapped.loadCopy());
}, (count) -> parsedCount[0]++);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerEditBook(PlayerEditBookEvent event) {
if (!event.isSigning()) return;
Player player = event.getPlayer();
BookMeta newBookMeta = event.getNewBookMeta();
List<?> pages = newBookMeta.pages();
final boolean[] replacedBookMeta = {false};
final int[] parsedCount = {0};
for (int i = 0; i < pages.size(); i++) {
int finalIndex = i;
JsonElement json = ComponentUtils.paperAdventureToJsonElement(pages.get(i));
if (json instanceof JsonPrimitive primitive) {
if (primitive.isString() && primitive.getAsString().isEmpty()) continue;
}
Component page = AdventureHelper.jsonElementToComponent(json);
processComponent(page, player, parsedCount[0], (text) -> {
try {
replacedBookMeta[0] = true;
Reflections.method$BookMeta$page.invoke(
newBookMeta, finalIndex + 1,
ComponentUtils.jsonElementToPaperAdventure(AdventureHelper.componentToJsonElement(text))
);
} catch (IllegalAccessException | InvocationTargetException e) {
this.plugin.logger().warn("Failed to set book page", e);
}
}, (count) -> parsedCount[0]++);
if (parsedCount[0] > Config.maxEmojiParsed()) break;
}
if (replacedBookMeta[0]) {
event.setNewBookMeta(newBookMeta);
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onSignChange(SignChangeEvent event) {
Player player = event.getPlayer();
List<Component> lines = event.lines();
final int[] parsedCount = {0};
for (int i = 0; i < lines.size(); i++) {
int finalIndex = i;
JsonElement json = ComponentUtils.paperAdventureToJsonElement(lines.get(i));
if (json.toString().isEmpty()) continue;
Component line = AdventureHelper.jsonElementToComponent(json);
processComponent(line, player, parsedCount[0], (text) -> {
try {
Reflections.method$SignChangeEvent$line.invoke(
event, finalIndex,
ComponentUtils.jsonElementToPaperAdventure(AdventureHelper.componentToJsonElement(text))
);
} catch (IllegalAccessException | InvocationTargetException e) {
plugin.logger().warn("Failed to set sign line", e);
}
}, (count) -> parsedCount[0]++);
if (parsedCount[0] > Config.maxEmojiParsed()) break;
}
}
private void processComponent(Component text, Player player, int parsedCount, Consumer<Component> consumer, Consumer<Integer> parsedCountConsumer) {
if (parsedCount > Config.maxEmojiParsed()) return;
Component textReplaced = text;
Set<String> processedKeywords = new HashSet<>();
for (Token token : super.emojiKeywordTrie.tokenize(AdventureHelper.componentToJson(text))) {
if (!token.isMatch()) continue;
Emoji emoji = super.emojiMapper.get(token.getFragment());
if (emoji == null) continue;
if (emoji.permission() != null && !player.hasPermission(Objects.requireNonNull(emoji.permission()))) {
String keyword = token.getFragment();
parsedCountConsumer.accept(parsedCount++);
if (parsedCount > Config.maxEmojiParsed()) return;
if (processedKeywords.contains(keyword)) continue;
Emoji emoji = super.emojiMapper.get(keyword);
if (emoji == null) {
parsedCountConsumer.accept(parsedCount--);
continue;
}
itemName = itemName.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()
)) ;
if (emoji.permission() != null && !player.hasPermission(Objects.requireNonNull(emoji.permission()))) {
parsedCountConsumer.accept(parsedCount--);
continue;
}
textReplaced = textReplaced.replaceText(builder -> {
builder.matchLiteral(keyword)
.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;
consumer.accept(textReplaced);
processedKeywords.add(keyword);
}
if (!replaced) return;
Item<ItemStack> wrapped = this.plugin.itemManager().wrap(result);
wrapped.customName(AdventureHelper.componentToJson(itemName));
event.setResult(wrapped.loadCopy());
}
@SuppressWarnings("UnstableApiUsage")

View File

@@ -3,7 +3,6 @@ package net.momirealms.craftengine.bukkit.util;
import com.google.gson.JsonElement;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.AdventureHelper;
public class ComponentUtils {
@@ -27,19 +26,18 @@ public class ComponentUtils {
}
public static String paperAdventureToJson(Object component) {
try {
return (String) Reflections.method$ComponentSerializer$serialize.invoke(Reflections.instance$GsonComponentSerializer, component);
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to serialize paper adventure component " + component, e);
return AdventureHelper.EMPTY_COMPONENT;
}
return Reflections.instance$GsonComponentSerializer$Gson.toJson(component);
}
public static Object jsonToPaperAdventure(String json) {
try {
return Reflections.method$ComponentSerializer$deserialize.invoke(Reflections.instance$GsonComponentSerializer, json);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to deserialize paper component from json", e);
}
return Reflections.instance$GsonComponentSerializer$Gson.fromJson(json, Reflections.clazz$AdventureComponent);
}
public static JsonElement paperAdventureToJsonElement(Object component) {
return Reflections.instance$GsonComponentSerializer$Gson.toJsonTree(component);
}
public static Object jsonElementToPaperAdventure(JsonElement json) {
return Reflections.instance$GsonComponentSerializer$Gson.fromJson(json, Reflections.clazz$AdventureComponent);
}
}

View File

@@ -1,6 +1,7 @@
package net.momirealms.craftengine.bukkit.util;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
@@ -6314,4 +6315,40 @@ public class Reflections {
BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketPlayOutScoreboardObjective")
)
);
public static final Class<?> clazz$SignChangeEvent = requireNonNull(
ReflectionUtils.getClazz(
"org.bukkit.event.block.SignChangeEvent"
)
);
public static final Method method$SignChangeEvent$line = requireNonNull(
ReflectionUtils.getMethod(clazz$SignChangeEvent, void.class, int.class, clazz$AdventureComponent)
);
public static final Class<?> clazz$BookMeta = requireNonNull(
ReflectionUtils.getClazz(
"org.bukkit.inventory.meta.BookMeta"
)
);
public static final Method method$BookMeta$page = requireNonNull(
ReflectionUtils.getMethod(clazz$BookMeta, void.class, int.class, clazz$AdventureComponent)
);
public static final Method method$GsonComponentSerializer$serializer = requireNonNull(
ReflectionUtils.getMethod(
clazz$GsonComponentSerializer, Gson.class
)
);
public static final Gson instance$GsonComponentSerializer$Gson;
static {
try {
instance$GsonComponentSerializer$Gson = (Gson) Reflections.method$GsonComponentSerializer$serializer.invoke(instance$GsonComponentSerializer);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -96,6 +96,7 @@ public class Config {
protected UUID resource_pack$external_host$uuid;
protected int performance$max_block_chain_update_limit;
protected int performance$max_emoji_parsed_limit;
protected boolean light_system$force_update_light;
protected boolean light_system$enable;
@@ -259,6 +260,7 @@ public class Config {
// performance
performance$max_block_chain_update_limit = config.getInt("performance.max-block-chain-update-limit", 64);
performance$max_emoji_parsed_limit = config.getInt("performance.max-emoji-parsed-limit", 100);
// light
light_system$force_update_light = config.getBoolean("light-system.force-update-light", false);
@@ -352,6 +354,10 @@ public class Config {
return instance.performance$max_block_chain_update_limit;
}
public static int maxEmojiParsed() {
return instance.performance$max_emoji_parsed_limit;
}
public static boolean removeInvalidFurniture() {
return instance.furniture$remove_invalid_furniture_on_chunk_load$enable;
}

View File

@@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G
# Project settings
# Rule: [major update].[feature update].[bug fix]
project_version=0.0.46.3
config_version=23
config_version=24
lang_version=4
project_group=net.momirealms
latest_supported_version=1.21.5