mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-25 01:49:30 +00:00
@@ -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
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
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;
|
||||
import net.momirealms.craftengine.bukkit.nms.FastNMS;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.LegacyInventoryUtils;
|
||||
@@ -25,14 +28,18 @@ 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.event.player.PlayerJoinEvent;
|
||||
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;
|
||||
@@ -53,6 +60,23 @@ public class BukkitFontManager extends AbstractFontManager implements Listener {
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delayedLoad() {
|
||||
Map<UUID, String> oldCachedEmojiSuggestions = this.oldCachedEmojiSuggestions();
|
||||
super.delayedLoad();
|
||||
this.oldCachedEmojiSuggestions.putAll(this.cachedEmojiSuggestions());
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
FastNMS.INSTANCE.method$ChatSuggestions$remove(oldCachedEmojiSuggestions.keySet(), player);
|
||||
FastNMS.INSTANCE.method$ChatSuggestions$add(this.cachedEmojiSuggestions(), player);
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
FastNMS.INSTANCE.method$ChatSuggestions$add(this.cachedEmojiSuggestions(), player);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public void onChat(AsyncChatDecorateEvent event) {
|
||||
@@ -105,31 +129,109 @@ 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)) {
|
||||
MutableInt parsedCount = new MutableInt(0);
|
||||
processComponent(itemName, player, parsedCount, (text) -> {
|
||||
if (parsedCount.value >= Config.maxEmojiParsed()) return;
|
||||
Item<ItemStack> wrapped = this.plugin.itemManager().wrap(result);
|
||||
wrapped.customName(AdventureHelper.componentToJson(text));
|
||||
event.setResult(wrapped.loadCopy());
|
||||
});
|
||||
}
|
||||
|
||||
@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();
|
||||
MutableBoolean replacedBookMeta = new MutableBoolean(false);
|
||||
MutableInt parsedCount = new MutableInt(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, (text) -> {
|
||||
try {
|
||||
replacedBookMeta.value = 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);
|
||||
}
|
||||
});
|
||||
if (parsedCount.value > Config.maxEmojiParsed()) break;
|
||||
}
|
||||
if (replacedBookMeta.value) {
|
||||
event.setNewBookMeta(newBookMeta);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onSignChange(SignChangeEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
List<Component> lines = event.lines();
|
||||
MutableInt parsedCount = new MutableInt(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, (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);
|
||||
}
|
||||
});
|
||||
if (parsedCount.value > Config.maxEmojiParsed()) break;
|
||||
}
|
||||
}
|
||||
|
||||
private void processComponent(Component text, Player player, MutableInt parsedCount, Consumer<Component> consumer) {
|
||||
if (parsedCount.value > 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 (parsedCount.value++ > Config.maxEmojiParsed()) return;
|
||||
String keyword = token.getFragment();
|
||||
if (processedKeywords.contains(keyword)) continue;
|
||||
Emoji emoji = super.emojiMapper.get(keyword);
|
||||
if (emoji == null) continue;
|
||||
if (emoji.permission() != null && !player.hasPermission(Objects.requireNonNull(emoji.permission()))) {
|
||||
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()
|
||||
)) ;
|
||||
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());
|
||||
private static final class MutableInt {
|
||||
int value;
|
||||
MutableInt(int value) { this.value = value; }
|
||||
}
|
||||
|
||||
private static final class MutableBoolean {
|
||||
boolean value;
|
||||
MutableBoolean(boolean value) { this.value = value; }
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.momirealms.craftengine.core.font;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.pack.LoadingSequence;
|
||||
@@ -35,6 +36,9 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
protected Trie emojiKeywordTrie;
|
||||
private Map<String, Component> tagMapper;
|
||||
protected Map<String, Emoji> emojiMapper;
|
||||
// tab补全
|
||||
protected final Map<UUID, String> cachedEmojiSuggestions = new HashMap<>();
|
||||
protected final Map<UUID, String> oldCachedEmojiSuggestions = new HashMap<>();
|
||||
|
||||
public AbstractFontManager(CraftEngine plugin) {
|
||||
this.plugin = plugin;
|
||||
@@ -55,6 +59,17 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
this.images.clear();
|
||||
this.illegalChars.clear();
|
||||
this.emojis.clear();
|
||||
this.cachedEmojiSuggestions.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<UUID, String> cachedEmojiSuggestions() {
|
||||
return ImmutableMap.copyOf(this.cachedEmojiSuggestions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<UUID, String> oldCachedEmojiSuggestions() {
|
||||
return ImmutableMap.copyOf(this.oldCachedEmojiSuggestions);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -146,6 +161,7 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
|
||||
@Override
|
||||
public void delayedLoad() {
|
||||
this.oldCachedEmojiSuggestions.clear();
|
||||
Optional.ofNullable(this.fonts.get(DEFAULT_FONT)).ifPresent(font -> this.illegalChars.addAll(font.codepointsInUse()));
|
||||
this.buildImageTagTrie();
|
||||
this.buildEmojiKeywordsTrie();
|
||||
@@ -263,6 +279,9 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
return;
|
||||
}
|
||||
List<String> keywords = MiscUtils.getAsStringList(keywordsRaw);
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String keyword = keywords.get(0);
|
||||
cachedEmojiSuggestions.put(uuid, keyword);
|
||||
String content = section.getOrDefault("content", "<arg:emoji>").toString();
|
||||
String image = null;
|
||||
if (section.containsKey("image")) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import net.momirealms.craftengine.core.util.Key;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface FontManager extends Manageable {
|
||||
Key DEFAULT_FONT = Key.of("minecraft:default");
|
||||
@@ -73,4 +74,8 @@ public interface FontManager extends Manageable {
|
||||
}
|
||||
|
||||
Map<String, Component> matchTags(String json);
|
||||
|
||||
Map<UUID, String> cachedEmojiSuggestions();
|
||||
|
||||
Map<UUID, String> oldCachedEmojiSuggestions();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -41,7 +41,7 @@ geantyref_version=1.3.16
|
||||
zstd_version=1.5.7-2
|
||||
commons_io_version=2.18.0
|
||||
sparrow_nbt_version=0.6.2
|
||||
sparrow_util_version=0.37
|
||||
sparrow_util_version=0.38
|
||||
fastutil_version=8.5.15
|
||||
netty_version=4.1.119.Final
|
||||
joml_version=1.10.8
|
||||
@@ -51,7 +51,7 @@ byte_buddy_version=1.17.5
|
||||
ahocorasick_version=0.6.3
|
||||
snake_yaml_version=2.4
|
||||
anti_grief_version=0.13
|
||||
nms_helper_version=0.53
|
||||
nms_helper_version=0.54
|
||||
# Ignite Dependencies
|
||||
mixinextras_version=0.4.1
|
||||
mixin_version=0.15.2+mixin.0.8.7
|
||||
|
||||
Reference in New Issue
Block a user