mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-29 20:09:13 +00:00
Merge branch 'Xiao-MoMi:dev' into dev
This commit is contained in:
@@ -237,8 +237,8 @@ gui:
|
|||||||
performance:
|
performance:
|
||||||
# Maximum chain update depth when fixing client visuals
|
# Maximum chain update depth when fixing client visuals
|
||||||
max-block-chain-update-limit: 64
|
max-block-chain-update-limit: 64
|
||||||
# Maximum emoji parsed each time
|
# Maximum number of emojis to parse per operation
|
||||||
max-emoji-parsed-limit: 100
|
max-emojis-per-parse: 16
|
||||||
|
|
||||||
light-system:
|
light-system:
|
||||||
enable: true
|
enable: true
|
||||||
@@ -249,7 +249,7 @@ chunk-system:
|
|||||||
# 1 = NONE | Compression Speed | Decompress Speed | Compression Ratio | Memory Usage |
|
# 1 = NONE | Compression Speed | Decompress Speed | Compression Ratio | Memory Usage |
|
||||||
# 2 = DEFLATE | Medium-Slow Medium Moderate Low |
|
# 2 = DEFLATE | Medium-Slow Medium Moderate Low |
|
||||||
# 3 = GZIP | Medium-Slow Medium Moderate Low |
|
# 3 = GZIP | Medium-Slow Medium Moderate Low |
|
||||||
# 4 = LAZ4 | Blazing-Fast Blazing-Fast Low Low |
|
# 4 = LZ4 | Blazing-Fast Blazing-Fast Low Low |
|
||||||
# 5 = ZSTD | Medium-Fast Fast High Medium |
|
# 5 = ZSTD | Medium-Fast Fast High Medium |
|
||||||
compression-method: 4
|
compression-method: 4
|
||||||
# Disabling this option prevents the plugin from converting custom blocks to vanilla states when chunks are unloaded.
|
# Disabling this option prevents the plugin from converting custom blocks to vanilla states when chunks are unloaded.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package net.momirealms.craftengine.bukkit.font;
|
package net.momirealms.craftengine.bukkit.font;
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonPrimitive;
|
import com.google.gson.JsonObject;
|
||||||
import io.papermc.paper.event.player.AsyncChatCommandDecorateEvent;
|
import io.papermc.paper.event.player.AsyncChatCommandDecorateEvent;
|
||||||
import io.papermc.paper.event.player.AsyncChatDecorateEvent;
|
import io.papermc.paper.event.player.AsyncChatDecorateEvent;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
@@ -36,10 +36,10 @@ import org.bukkit.event.player.PlayerJoinEvent;
|
|||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.inventory.meta.BookMeta;
|
import org.bukkit.inventory.meta.BookMeta;
|
||||||
import org.bukkit.inventory.view.AnvilView;
|
import org.bukkit.inventory.view.AnvilView;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class BukkitFontManager extends AbstractFontManager implements Listener {
|
public class BukkitFontManager extends AbstractFontManager implements Listener {
|
||||||
private final BukkitCraftEngine plugin;
|
private final BukkitCraftEngine plugin;
|
||||||
@@ -144,111 +144,67 @@ public class BukkitFontManager extends AbstractFontManager implements Listener {
|
|||||||
if (renameText == null || renameText.isEmpty()) return;
|
if (renameText == null || renameText.isEmpty()) return;
|
||||||
|
|
||||||
Component itemName = Component.text(renameText);
|
Component itemName = Component.text(renameText);
|
||||||
MutableInt parsedCount = new MutableInt(0);
|
EmojiReplaceProcessResult replaceProcessResult = processEmojiNoCopy(player, itemName, renameText);
|
||||||
processComponent(itemName, player, parsedCount, (text) -> {
|
if (replaceProcessResult.changed()) {
|
||||||
if (parsedCount.value >= Config.maxEmojiParsed()) return;
|
|
||||||
Item<ItemStack> wrapped = this.plugin.itemManager().wrap(result);
|
Item<ItemStack> wrapped = this.plugin.itemManager().wrap(result);
|
||||||
wrapped.customName(AdventureHelper.componentToJson(text));
|
wrapped.customName(AdventureHelper.componentToJson(replaceProcessResult.newText()));
|
||||||
event.setResult(wrapped.loadCopy());
|
event.setResult(wrapped.load());
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||||
|
public void onSignChange(SignChangeEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
List<Component> lines = event.lines();
|
||||||
|
for (int i = 0; i < lines.size(); i++) {
|
||||||
|
JsonElement json = ComponentUtils.paperAdventureToJsonElement(lines.get(i));
|
||||||
|
if (json == null) continue;
|
||||||
|
Component line = AdventureHelper.jsonElementToComponent(json);
|
||||||
|
EmojiReplaceProcessResult result = processEmojiNoCopy(player, line);
|
||||||
|
if (result.changed()) {
|
||||||
|
try {
|
||||||
|
Reflections.method$SignChangeEvent$line.invoke(event, i, ComponentUtils.jsonElementToPaperAdventure(AdventureHelper.componentToJsonElement(result.newText())));
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
plugin.logger().warn("Failed to set sign line", e);
|
||||||
|
}
|
||||||
|
} else if (AdventureHelper.isPureTextComponent(line)) {
|
||||||
|
String plainText = AdventureHelper.plainTextContent(line);
|
||||||
|
try {
|
||||||
|
JsonObject jo = new JsonObject();
|
||||||
|
jo.addProperty("text", plainText);
|
||||||
|
Reflections.method$SignChangeEvent$line.invoke(event, i, ComponentUtils.jsonElementToPaperAdventure(jo));
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
plugin.logger().warn("Failed to reset sign line", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||||
public void onPlayerEditBook(PlayerEditBookEvent event) {
|
public void onPlayerEditBook(PlayerEditBookEvent event) {
|
||||||
if (!event.isSigning()) return;
|
if (!event.isSigning()) return;
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
BookMeta newBookMeta = event.getNewBookMeta();
|
BookMeta newBookMeta = event.getNewBookMeta();
|
||||||
List<?> pages = newBookMeta.pages();
|
List<?> pages = newBookMeta.pages();
|
||||||
MutableBoolean replacedBookMeta = new MutableBoolean(false);
|
boolean changed = false;
|
||||||
MutableInt parsedCount = new MutableInt(0);
|
|
||||||
for (int i = 0; i < pages.size(); i++) {
|
for (int i = 0; i < pages.size(); i++) {
|
||||||
int finalIndex = i;
|
|
||||||
JsonElement json = ComponentUtils.paperAdventureToJsonElement(pages.get(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);
|
Component page = AdventureHelper.jsonElementToComponent(json);
|
||||||
processComponent(page, player, parsedCount, (text) -> {
|
EmojiReplaceProcessResult result = processEmojiNoCopy(player, page);
|
||||||
|
if (result.changed()) {
|
||||||
|
changed = true;
|
||||||
try {
|
try {
|
||||||
replacedBookMeta.value = true;
|
Reflections.method$BookMeta$page.invoke(newBookMeta, i + 1, ComponentUtils.jsonElementToPaperAdventure(AdventureHelper.componentToJsonElement(result.newText())));
|
||||||
Reflections.method$BookMeta$page.invoke(
|
|
||||||
newBookMeta, finalIndex + 1,
|
|
||||||
ComponentUtils.jsonElementToPaperAdventure(AdventureHelper.componentToJsonElement(text))
|
|
||||||
);
|
|
||||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
this.plugin.logger().warn("Failed to set book page", e);
|
this.plugin.logger().warn("Failed to set book page", e);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
if (parsedCount.value > Config.maxEmojiParsed()) break;
|
|
||||||
}
|
}
|
||||||
if (replacedBookMeta.value) {
|
if (changed) {
|
||||||
event.setNewBookMeta(newBookMeta);
|
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;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
));
|
|
||||||
});
|
|
||||||
consumer.accept(textReplaced);
|
|
||||||
processedKeywords.add(keyword);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
private void processChatEvent(AsyncChatDecorateEvent event) {
|
private void processChatEvent(AsyncChatDecorateEvent event) {
|
||||||
Player player = event.player();
|
Player player = event.player();
|
||||||
@@ -276,6 +232,43 @@ 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) {
|
private IllegalCharacterProcessResult processIllegalCharacters(String raw) {
|
||||||
boolean hasIllegal = false;
|
boolean hasIllegal = false;
|
||||||
// replace illegal image usage
|
// replace illegal image usage
|
||||||
@@ -322,4 +315,15 @@ public class BukkitFontManager extends AbstractFontManager implements Listener {
|
|||||||
return new IllegalCharacterProcessResult(false, null);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import net.momirealms.craftengine.core.util.context.PlayerContext;
|
|||||||
import org.ahocorasick.trie.Token;
|
import org.ahocorasick.trie.Token;
|
||||||
import org.ahocorasick.trie.Trie;
|
import org.ahocorasick.trie.Trie;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -30,11 +31,11 @@ public abstract class AbstractFontManager implements FontManager {
|
|||||||
private final Set<Integer> illegalChars = new HashSet<>();
|
private final Set<Integer> illegalChars = new HashSet<>();
|
||||||
private final ImageParser imageParser;
|
private final ImageParser imageParser;
|
||||||
private final EmojiParser emojiParser;
|
private final EmojiParser emojiParser;
|
||||||
|
|
||||||
private OffsetFont offsetFont;
|
private OffsetFont offsetFont;
|
||||||
private Trie imageTagTrie;
|
|
||||||
|
protected Trie imageTagTrie;
|
||||||
protected Trie emojiKeywordTrie;
|
protected Trie emojiKeywordTrie;
|
||||||
private Map<String, Component> tagMapper;
|
protected Map<String, Component> tagMapper;
|
||||||
protected Map<String, Emoji> emojiMapper;
|
protected Map<String, Emoji> emojiMapper;
|
||||||
// tab补全
|
// tab补全
|
||||||
protected final Map<UUID, String> cachedEmojiSuggestions = new HashMap<>();
|
protected final Map<UUID, String> cachedEmojiSuggestions = new HashMap<>();
|
||||||
@@ -279,8 +280,12 @@ public abstract class AbstractFontManager implements FontManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<String> keywords = MiscUtils.getAsStringList(keywordsRaw);
|
List<String> keywords = MiscUtils.getAsStringList(keywordsRaw);
|
||||||
UUID uuid = UUID.randomUUID();
|
if (keywords.isEmpty()) {
|
||||||
|
TranslationManager.instance().log("warning.config.emoji.lack_keywords", path.toString(), id.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
String keyword = keywords.get(0);
|
String keyword = keywords.get(0);
|
||||||
|
UUID uuid = UUID.nameUUIDFromBytes(keyword.getBytes(StandardCharsets.UTF_8));
|
||||||
cachedEmojiSuggestions.put(uuid, keyword);
|
cachedEmojiSuggestions.put(uuid, keyword);
|
||||||
String content = section.getOrDefault("content", "<arg:emoji>").toString();
|
String content = section.getOrDefault("content", "<arg:emoji>").toString();
|
||||||
String image = null;
|
String image = null;
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public class Config {
|
|||||||
protected UUID resource_pack$external_host$uuid;
|
protected UUID resource_pack$external_host$uuid;
|
||||||
|
|
||||||
protected int performance$max_block_chain_update_limit;
|
protected int performance$max_block_chain_update_limit;
|
||||||
protected int performance$max_emoji_parsed_limit;
|
protected int performance$max_emojis_per_parse;
|
||||||
|
|
||||||
protected boolean light_system$force_update_light;
|
protected boolean light_system$force_update_light;
|
||||||
protected boolean light_system$enable;
|
protected boolean light_system$enable;
|
||||||
@@ -260,7 +260,7 @@ public class Config {
|
|||||||
|
|
||||||
// performance
|
// performance
|
||||||
performance$max_block_chain_update_limit = config.getInt("performance.max-block-chain-update-limit", 64);
|
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);
|
performance$max_emojis_per_parse = config.getInt("performance.max-emojis-per-parse", 32);
|
||||||
|
|
||||||
// light
|
// light
|
||||||
light_system$force_update_light = config.getBoolean("light-system.force-update-light", false);
|
light_system$force_update_light = config.getBoolean("light-system.force-update-light", false);
|
||||||
@@ -354,8 +354,8 @@ public class Config {
|
|||||||
return instance.performance$max_block_chain_update_limit;
|
return instance.performance$max_block_chain_update_limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int maxEmojiParsed() {
|
public static int maxEmojisPerParse() {
|
||||||
return instance.performance$max_emoji_parsed_limit;
|
return instance.performance$max_emojis_per_parse;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean removeInvalidFurniture() {
|
public static boolean removeInvalidFurniture() {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import net.kyori.adventure.audience.Audience;
|
|||||||
import net.kyori.adventure.key.Key;
|
import net.kyori.adventure.key.Key;
|
||||||
import net.kyori.adventure.sound.Sound;
|
import net.kyori.adventure.sound.Sound;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.TextComponent;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
@@ -260,4 +261,27 @@ public class AdventureHelper {
|
|||||||
}
|
}
|
||||||
return stringBuilder.toString();
|
return stringBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String plainTextContent(Component component) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (component instanceof TextComponent textComponent) {
|
||||||
|
sb.append(textComponent.content());
|
||||||
|
}
|
||||||
|
for (Component child : component.children()) {
|
||||||
|
sb.append(plainTextContent(child));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPureTextComponent(Component component) {
|
||||||
|
if (!(component instanceof TextComponent textComponent)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (Component child : textComponent.children()) {
|
||||||
|
if (!isPureTextComponent(child)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user