diff --git a/bukkit/compatibility/build.gradle.kts b/bukkit/compatibility/build.gradle.kts index 05d1dc29f..8a7807cc9 100644 --- a/bukkit/compatibility/build.gradle.kts +++ b/bukkit/compatibility/build.gradle.kts @@ -32,6 +32,8 @@ dependencies { // MMOItems compileOnly("net.Indyuce:MMOItems-API:6.10-SNAPSHOT") compileOnly("io.lumine:MythicLib-dist:1.6.2-SNAPSHOT") + // LuckPerms + compileOnly("net.luckperms:api:5.4") } java { diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/permission/LuckPermsEventListeners.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/permission/LuckPermsEventListeners.java new file mode 100644 index 000000000..ecfbb6983 --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/permission/LuckPermsEventListeners.java @@ -0,0 +1,77 @@ +package net.momirealms.craftengine.bukkit.compatibility.permission; + +import net.luckperms.api.LuckPerms; +import net.luckperms.api.event.EventBus; +import net.luckperms.api.event.EventSubscription; +import net.luckperms.api.event.group.GroupDataRecalculateEvent; +import net.luckperms.api.event.user.UserDataRecalculateEvent; +import net.luckperms.api.model.user.User; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import org.bukkit.Bukkit; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.logging.Level; + +public class LuckPermsEventListeners { + private final JavaPlugin plugin; + private final LuckPerms luckPerms; + private final Consumer playerCallback; + private final List> subscriptions = new ArrayList<>(); + + public LuckPermsEventListeners(JavaPlugin plugin, Consumer playerCallback) { + this.plugin = plugin; + this.playerCallback = playerCallback; + RegisteredServiceProvider provider = Bukkit.getServicesManager().getRegistration(LuckPerms.class); + if (provider != null) { + this.luckPerms = provider.getProvider(); + this.registerEventListeners(); + } else { + throw new IllegalStateException("Unable to hook LuckPerms"); + } + } + + private void registerEventListeners() { + EventBus eventBus = this.luckPerms.getEventBus(); + this.subscriptions.add(eventBus.subscribe(this.plugin, UserDataRecalculateEvent.class, this::onUserPermissionChange)); + this.subscriptions.add(eventBus.subscribe(this.plugin, GroupDataRecalculateEvent.class, this::onGroupPermissionChange)); + } + + public void unregisterListeners() { + this.subscriptions.forEach(subscription -> { + try { + subscription.close(); + } catch (Exception e) { + this.plugin.getLogger().log(Level.WARNING, "Failed to close event subscription", e); + } + }); + this.subscriptions.clear(); + } + + private void onUserPermissionChange(UserDataRecalculateEvent event) { + CraftEngine.instance().scheduler().async().execute(() -> { + this.playerCallback.accept(event.getUser().getUniqueId()); + }); + } + + private void onGroupPermissionChange(GroupDataRecalculateEvent event) { + CraftEngine.instance().scheduler().asyncLater(() -> { + String groupName = event.getGroup().getName(); + Bukkit.getOnlinePlayers().forEach(player -> { + UUID uuid = player.getUniqueId(); + User user = luckPerms.getUserManager().getUser(uuid); + if (user == null) return; + boolean inGroup = user.getInheritedGroups(user.getQueryOptions()).stream() + .anyMatch(g -> g.getName().equals(groupName)); + if (inGroup) { + this.playerCallback.accept(uuid); + } + }); + }, 1L, TimeUnit.SECONDS); + } +} diff --git a/bukkit/loader/build.gradle.kts b/bukkit/loader/build.gradle.kts index ab4e6c6c1..7f2a8a975 100644 --- a/bukkit/loader/build.gradle.kts +++ b/bukkit/loader/build.gradle.kts @@ -48,6 +48,7 @@ bukkit { name = "CraftEngine" apiVersion = "1.20" authors = listOf("XiaoMoMi") + contributors = listOf("jhqwqmc", "iqtesterrr") softDepend = listOf("PlaceholderAPI", "WorldEdit", "FastAsyncWorldEdit") foliaSupported = true } diff --git a/bukkit/loader/src/main/resources/config.yml b/bukkit/loader/src/main/resources/config.yml index 78ff3ec52..7ba3c04e5 100644 --- a/bukkit/loader/src/main/resources/config.yml +++ b/bukkit/loader/src/main/resources/config.yml @@ -153,18 +153,20 @@ image: command: true sign: true # By intercepting packets, you are allowed to use in other plugins + # Turning off some unused options would help reduce CPU usage on async threads intercept-packets: system-chat: true - tab-list: true + tab-list: true # Tab list header and footer + player-info: true # User list in tab actionbar: true title: true bossbar: true - container: true - team: true + container: true # GUI + team: true # Team prefix, suffix and display name scoreboard: true entity-name: false - armor-stand: true - text-display: true + armor-stand: true # Legacy Holograms + text-display: true # Modern Holograms emoji: {} @@ -237,6 +239,8 @@ gui: performance: # Maximum chain update depth when fixing client visuals max-block-chain-update-limit: 64 + # Maximum number of emojis to parse per operation + max-emojis-per-parse: 16 light-system: enable: true @@ -247,7 +251,7 @@ chunk-system: # 1 = NONE | Compression Speed | Decompress Speed | Compression Ratio | Memory Usage | # 2 = DEFLATE | 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 | compression-method: 4 # Disabling this option prevents the plugin from converting custom blocks to vanilla states when chunks are unloaded. @@ -261,7 +265,7 @@ chunk-system: restore-custom-blocks-on-chunk-load: true offset-characters: - font: minecraft:default + font: minecraft:offset_chars -1: '\uf800' -2: '\uf801' -3: '\uf802' diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/emoji.yml b/bukkit/loader/src/main/resources/resources/default/configuration/emoji.yml new file mode 100644 index 000000000..755db9585 --- /dev/null +++ b/bukkit/loader/src/main/resources/resources/default/configuration/emoji.yml @@ -0,0 +1,26 @@ +templates: + default:emoji/basic: + content: "'>" + default:emoji/addition_info: + content: "'>{text}" + +emoji: + default:emoji_location: + template: "default:emoji/addition_info" + arguments: + text: "" + overrides: + image: "default:icons:0:0" + permission: emoji.location + keywords: + - ":location:" + - ":pos:" + default:emoji_time: + template: "default:emoji/addition_info" + arguments: + text: "" + overrides: + image: "default:icons:0:1" + permission: emoji.time + keywords: + - ":time:" \ No newline at end of file diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/i18n.yml b/bukkit/loader/src/main/resources/resources/default/configuration/i18n.yml index 51b178e1b..fe6db7c36 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/i18n.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/i18n.yml @@ -39,6 +39,9 @@ i18n: category.topaz: "Topaz" category.furniture: "Furniture" category.misc: "Misc" + emoji.tip: "Use '' to send the '' emoji" + emoji.time: "Current time: " + emoji.location: "Current coordinates: ,," zh_cn: item.chinese_lantern: "灯笼" item.fairy_flower: "仙灵花" @@ -78,4 +81,7 @@ i18n: category.palm_tree: "棕榈树" category.topaz: "黄玉" category.furniture: "家具" - category.misc: "杂项" \ No newline at end of file + category.misc: "杂项" + emoji.tip: "使用''来发送表情''" + emoji.time: "当前时间: " + emoji.location: "当前坐标: ,," \ No newline at end of file diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/icons.yml b/bukkit/loader/src/main/resources/resources/default/configuration/icons.yml index 6b2db4498..d54812865 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/icons.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/icons.yml @@ -2,7 +2,7 @@ images: default:icons: height: 10 ascent: 9 - font: minecraft:icons # Do not use 'minecraft:default' unless other plugins don't support custom font! + font: minecraft:icons file: minecraft:font/image/icons.png chars: - '\ub000\ub001' diff --git a/bukkit/loader/src/main/resources/resources/internal/configuration/offset_chars.yml b/bukkit/loader/src/main/resources/resources/internal/configuration/offset_chars.yml index cf2175bf7..45b1c925a 100644 --- a/bukkit/loader/src/main/resources/resources/internal/configuration/offset_chars.yml +++ b/bukkit/loader/src/main/resources/resources/internal/configuration/offset_chars.yml @@ -2,264 +2,264 @@ images: internal:neg_1: height: -3 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf800' internal:neg_2: height: -4 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf801' internal:neg_3: height: -5 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf802' internal:neg_4: height: -6 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf803' internal:neg_5: height: -7 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf804' internal:neg_6: height: -8 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf805' internal:neg_7: height: -9 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf806' internal:neg_8: height: -10 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf807' internal:neg_9: height: -11 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf808' internal:neg_10: height: -12 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf809' internal:neg_11: height: -13 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf80a' internal:neg_12: height: -14 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf80b' internal:neg_13: height: -15 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf80c' internal:neg_14: height: -16 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf80d' internal:neg_15: height: -17 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf80e' internal:neg_16: height: -18 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf80f' internal:neg_24: height: -26 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf810' internal:neg_32: height: -34 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf811' internal:neg_48: height: -50 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf812' internal:neg_64: height: -66 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf813' internal:neg_128: height: -130 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf814' internal:neg_256: height: -258 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf815' internal:pos_1: height: -1 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf830' internal:pos_2: height: 1 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf831' internal:pos_3: height: 2 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf832' internal:pos_4: height: 3 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf833' internal:pos_5: height: 4 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf834' internal:pos_6: height: 5 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf835' internal:pos_7: height: 6 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf836' internal:pos_8: height: 7 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf837' internal:pos_9: height: 8 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf838' internal:pos_10: height: 9 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf839' internal:pos_11: height: 10 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf83a' internal:pos_12: height: 11 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf83b' internal:pos_13: height: 12 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf83c' internal:pos_14: height: 13 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf83d' internal:pos_15: height: 14 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf83e' internal:pos_16: height: 15 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf83f' internal:pos_24: height: 23 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf840' internal:pos_32: height: 31 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf841' internal:pos_48: height: 47 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf842' internal:pos_64: height: 63 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf843' internal:pos_128: height: 127 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf844' internal:pos_256: height: 255 ascent: -5000 - font: minecraft:default + font: minecraft:offset_chars file: minecraft:font/offset/space_split.png char: '\uf845' \ No newline at end of file diff --git a/bukkit/loader/src/main/resources/translations/en.yml b/bukkit/loader/src/main/resources/translations/en.yml index d2c95dfb1..007676fe9 100644 --- a/bukkit/loader/src/main/resources/translations/en.yml +++ b/bukkit/loader/src/main/resources/translations/en.yml @@ -102,4 +102,7 @@ warning.config.block.state.model.lack_path: "Issue found in file warning.config.block.state.model.invalid_resource_location: "Issue found in file - The block '' has a 'path' argument [] that contains legal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters" warning.config.model.generation.conflict: "Issue found in file - Failed to generate model for '' as two or more configurations attempt to generate different json models with the same path: ''" warning.config.model.generation.texture.invalid_resource_location: "Issue found in file - The config '' has a '' texture argument [] that contains legal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters" -warning.config.model.generation.parent.invalid_resource_location: "Issue found in file - The config '' has a parent argument [] that contains legal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters" \ No newline at end of file +warning.config.model.generation.parent.invalid_resource_location: "Issue found in file - The config '' has a parent argument [] that contains legal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters" +warning.config.emoji.lack_keywords: "Issue found in file - The emoji '' is missing the required 'keywords' argument." +warning.config.emoji.duplicated: "Issue found in file - Duplicated emoji ''." +warning.config.emoji.invalid_image: "Issue found in file - The emoji '' has an invalid 'image' argument ''." \ No newline at end of file diff --git a/bukkit/loader/src/main/resources/translations/zh_cn.yml b/bukkit/loader/src/main/resources/translations/zh_cn.yml index 822f32e54..66acc8f30 100644 --- a/bukkit/loader/src/main/resources/translations/zh_cn.yml +++ b/bukkit/loader/src/main/resources/translations/zh_cn.yml @@ -102,4 +102,7 @@ warning.config.block.state.model.lack_path: "在文件 中发现 warning.config.block.state.model.invalid_resource_location: "在文件 中发现问题 - 方块 '' 的 'path' 路径参数 [] 包含非法字符,请参考资源路径规范:https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6" warning.config.model.generation.conflict: "在文件 中发现问题 - 无法为 '' 生成模型,多个配置尝试用相同路径 '' 生成不同的JSON模型" warning.config.model.generation.texture.invalid_resource_location: "在文件 中发现问题 - 配置项 '' 的 '' 纹理参数 [] 包含非法字符,请参考资源路径规范:https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6" -warning.config.model.generation.parent.invalid_resource_location: "在文件 中发现问题 - 配置项 '' 的父模型参数 [] 包含非法字符,请参考资源路径规范:https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6" \ No newline at end of file +warning.config.model.generation.parent.invalid_resource_location: "在文件 中发现问题 - 配置项 '' 的父模型参数 [] 包含非法字符,请参考资源路径规范:https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6" +warning.config.emoji.lack_keywords: "在文件 中发现问题 - 表情 '' 缺少必要的 'keywords' 配置" +warning.config.emoji.duplicated: "在文件 中发现问题 - 表情 '' 重复定义" +warning.config.emoji.invalid_image: "在文件 中发现问题 - 表情 '' 使用了无效的 'image' 图片参数 ''." \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java index 88c6c4617..f8879f586 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java @@ -70,7 +70,7 @@ public class BlockEventListener implements Listener { try { Object soundType = Reflections.field$BlockBehaviour$soundType.get(ownerBlock); Object placeSound = Reflections.field$SoundType$placeSound.get(soundType); - player.playSound(block.getLocation(), Reflections.field$SoundEvent$location.get(placeSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); + player.playSound(block.getLocation(), FastNMS.INSTANCE.field$SoundEvent$location(placeSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); } catch (ReflectiveOperationException e) { this.plugin.logger().warn("Failed to get sound type", e); } @@ -87,7 +87,7 @@ public class BlockEventListener implements Listener { Object ownerBlock = BlockStateUtils.getBlockOwner(blockState); Object soundType = Reflections.field$BlockBehaviour$soundType.get(ownerBlock); Object placeSound = Reflections.field$SoundType$placeSound.get(soundType); - player.playSound(block.getLocation(), Reflections.field$SoundEvent$location.get(placeSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); + player.playSound(block.getLocation(), FastNMS.INSTANCE.field$SoundEvent$location(placeSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); } catch (ReflectiveOperationException e) { this.plugin.logger().warn("Failed to get sound type", e); } @@ -179,7 +179,7 @@ public class BlockEventListener implements Listener { try { Object soundType = Reflections.field$BlockBehaviour$soundType.get(ownerBlock); Object breakSound = Reflections.field$SoundType$breakSound.get(soundType); - block.getWorld().playSound(block.getLocation(), Reflections.field$SoundEvent$location.get(breakSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); + block.getWorld().playSound(block.getLocation(), FastNMS.INSTANCE.field$SoundEvent$location(breakSound).toString(), SoundCategory.BLOCKS, 1f, 0.8f); } catch (ReflectiveOperationException e) { this.plugin.logger().warn("Failed to get sound type", e); } @@ -251,7 +251,7 @@ public class BlockEventListener implements Listener { try { Object soundType = Reflections.field$BlockBehaviour$soundType.get(ownerBlock); Object stepSound = Reflections.field$SoundType$stepSound.get(soundType); - player.playSound(playerLocation, Reflections.field$SoundEvent$location.get(stepSound).toString(), SoundCategory.BLOCKS, 0.15f, 1f); + player.playSound(playerLocation, FastNMS.INSTANCE.field$SoundEvent$location(stepSound).toString(), SoundCategory.BLOCKS, 0.15f, 1f); } catch (ReflectiveOperationException e) { plugin.logger().warn("Failed to get sound type", e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index 730920314..01b2c7495 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java @@ -8,6 +8,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import dev.dejvokep.boostedyaml.YamlDocument; import net.momirealms.craftengine.bukkit.compatibility.worldedit.WorldEditBlockRegister; +import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector; import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers; @@ -318,13 +319,16 @@ public class BukkitBlockManager extends AbstractBlockManager { } } + affectedBlocks.remove(Reflections.instance$Blocks$FIRE); + affectedBlocks.remove(Reflections.instance$Blocks$SOUL_FIRE); + this.affectedSoundBlocks = ImmutableSet.copyOf(affectedBlocks); ImmutableMap.Builder soundMapperBuilder = ImmutableMap.builder(); for (Object soundType : affectedSounds) { for (Field field : List.of(Reflections.field$SoundType$placeSound, Reflections.field$SoundType$fallSound, Reflections.field$SoundType$hitSound, Reflections.field$SoundType$stepSound, Reflections.field$SoundType$breakSound)) { Object soundEvent = field.get(soundType); - Key previousId = Key.of(Reflections.field$SoundEvent$location.get(soundEvent).toString()); + Key previousId = Key.of(FastNMS.INSTANCE.field$SoundEvent$location(soundEvent).toString()); soundMapperBuilder.put(previousId, Key.of(previousId.namespace(), "replaced." + previousId.value())); } } @@ -829,8 +833,8 @@ public class BukkitBlockManager extends AbstractBlockManager { return counter; } - private Object createResourceLocation(Key key) throws Exception { - return Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, key.namespace(), key.value()); + private Object createResourceLocation(Key key) { + return FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath(key.namespace(), key.value()); } private Object getBlockFromRegistry(Object resourceLocation) throws Exception { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java index 99b543466..5c4ec7e41 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java @@ -117,7 +117,7 @@ public class LeavesBlockBehavior extends WaterLoggedBlockBehavior { if (event.isCancelled()) { return; } - Reflections.method$Level$removeBlock.invoke(level, blockPos, false); + FastNMS.INSTANCE.method$Level$removeBlock(level, blockPos, false); if (isWaterLogged(immutableBlockState)) { bukkitWorld.setBlockData(pos.x(), pos.y(), pos.z(), Material.WATER.createBlockData()); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SugarCaneBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SugarCaneBlockBehavior.java index c11a5d511..f819125c8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SugarCaneBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SugarCaneBlockBehavior.java @@ -59,7 +59,7 @@ public class SugarCaneBlockBehavior extends BushBlockBehavior { ImmutableBlockState currentState = BukkitBlockManager.instance().getImmutableBlockState(stateId); if (currentState != null && !currentState.isEmpty()) { // break the sugar cane - Reflections.method$Level$removeBlock.invoke(level, blockPos, false); + FastNMS.INSTANCE.method$Level$removeBlock(level, blockPos, false); Vec3d vec3d = Vec3d.atCenterOf(LocationUtils.fromBlockPos(blockPos)); net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); ContextHolder.Builder builder = ContextHolder.builder() diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java index 1d32645ec..40bd35005 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java @@ -1,26 +1,41 @@ package net.momirealms.craftengine.bukkit.font; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; 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.compatibility.permission.LuckPermsEventListeners; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; 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.FontManager; +import net.momirealms.craftengine.core.font.*; +import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.config.Config; -import net.momirealms.craftengine.core.util.CharacterUtils; +import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.Bukkit; import org.bukkit.entity.Player; 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.util.function.Consumer; +import java.lang.reflect.InvocationTargetException; +import java.util.*; public class BukkitFontManager extends AbstractFontManager implements Listener { private final BukkitCraftEngine plugin; + private LuckPermsEventListeners luckPermsEventListeners; public BukkitFontManager(BukkitCraftEngine plugin) { super(plugin); @@ -29,6 +44,9 @@ public class BukkitFontManager extends AbstractFontManager implements Listener { @Override public void delayedInit() { + if (this.plugin.isPluginEnabled("LuckPerms")) { + luckPermsEventListeners = new LuckPermsEventListeners(plugin.bootstrap(), this::refreshEmojiSuggestions); + } Bukkit.getPluginManager().registerEvents(this, plugin.bootstrap()); } @@ -36,6 +54,53 @@ public class BukkitFontManager extends AbstractFontManager implements Listener { public void disable() { super.disable(); HandlerList.unregisterAll(this); + if (luckPermsEventListeners != null && this.plugin.isPluginEnabled("LuckPerms")) { + luckPermsEventListeners.unregisterListeners(); + } + } + + @Override + public void delayedLoad() { + Collection players = Bukkit.getOnlinePlayers(); + for (Player player : players) { + removeEmojiSuggestions(player); + } + super.delayedLoad(); + for (Player player : players) { + this.addEmojiSuggestions(player, getEmojiSuggestion(player)); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerJoin(PlayerJoinEvent event) { + plugin.scheduler().async().execute(() -> this.addEmojiSuggestions(event.getPlayer(), getEmojiSuggestion(event.getPlayer()))); + } + + private void refreshEmojiSuggestions(UUID uuid) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) return; + removeEmojiSuggestions(player); + addEmojiSuggestions(player, getEmojiSuggestion(player)); + } + + private List getEmojiSuggestion(Player player) { + List suggestions = new ArrayList<>(); + for (Emoji emoji : super.emojiList) { + if (emoji.permission() == null || player.hasPermission(Objects.requireNonNull(emoji.permission()))) { + suggestions.addAll(emoji.keywords()); + } + } + return suggestions; + } + + private void addEmojiSuggestions(Player player, List suggestions) { + player.addCustomChatCompletions(suggestions); + } + + private void removeEmojiSuggestions(Player player) { + if (super.allEmojiSuggestions != null) { + player.removeCustomChatCompletions(super.allEmojiSuggestions); + } } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) @@ -55,53 +120,125 @@ public class BukkitFontManager extends AbstractFontManager implements Listener { @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onCommand(PlayerCommandPreprocessEvent event) { if (!Config.filterCommand()) return; - if (!this.isDefaultFontInUse()) return; - if (event.getPlayer().hasPermission(FontManager.BYPASS_COMMAND)) { + if (!event.getPlayer().hasPermission(FontManager.BYPASS_COMMAND)) { + IllegalCharacterProcessResult result = processIllegalCharacters(event.getMessage()); + if (result.has()) { + event.setMessage(result.text()); + } + } + } + + @SuppressWarnings("UnstableApiUsage") + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onAnvilRename(PrepareAnvilEvent event) { + if (super.emojiKeywordTrie == null) { return; } - runIfContainsIllegalCharacter(event.getMessage(), event::setMessage); + ItemStack result = event.getResult(); + if (result == null) return; + Player player; + try { + player = (Player) Reflections.method$InventoryView$getPlayer.invoke(VersionHelper.isVersionNewerThan1_21() ? event.getView() : LegacyInventoryUtils.getView(event)); + } catch (ReflectiveOperationException e) { + this.plugin.logger().warn("Failed to get inventory viewer", e); + return; + } + + String renameText; + if (VersionHelper.isVersionNewerThan1_21_2()) { + AnvilView anvilView = event.getView(); + renameText = anvilView.getRenameText(); + } else { + renameText = LegacyInventoryUtils.getRenameText(event.getInventory()); + } + + if (renameText == null || renameText.isEmpty()) return; + Component itemName = Component.text(renameText); + EmojiComponentProcessResult replaceProcessResult = replaceComponentEmoji(itemName, plugin.adapt(player), renameText); + if (replaceProcessResult.changed()) { + Item wrapped = this.plugin.itemManager().wrap(result); + wrapped.customName(AdventureHelper.componentToJson(replaceProcessResult.newText())); + event.setResult(wrapped.load()); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onSignChange(SignChangeEvent event) { + Player player = event.getPlayer(); + List 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); + EmojiComponentProcessResult result = replaceComponentEmoji(line, plugin.adapt(player)); + 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) { + if (!event.isSigning()) return; + Player player = event.getPlayer(); + BookMeta newBookMeta = event.getNewBookMeta(); + List pages = newBookMeta.pages(); + boolean changed = false; + for (int i = 0; i < pages.size(); i++) { + JsonElement json = ComponentUtils.paperAdventureToJsonElement(pages.get(i)); + Component page = AdventureHelper.jsonElementToComponent(json); + EmojiComponentProcessResult result = replaceComponentEmoji(page, plugin.adapt(player)); + if (result.changed()) { + changed = true; + try { + Reflections.method$BookMeta$page.invoke(newBookMeta, i + 1, ComponentUtils.jsonElementToPaperAdventure(AdventureHelper.componentToJsonElement(result.newText()))); + } catch (IllegalAccessException | InvocationTargetException e) { + this.plugin.logger().warn("Failed to set book page", e); + } + } + } + if (changed) { + event.setNewBookMeta(newBookMeta); + } } @SuppressWarnings("UnstableApiUsage") private void processChatEvent(AsyncChatDecorateEvent event) { Player player = event.player(); if (player == null) return; - if (!this.isDefaultFontInUse()) return; - if (player.hasPermission(FontManager.BYPASS_CHAT)) { - return; - } try { Object originalMessage = Reflections.field$AsyncChatDecorateEvent$originalMessage.get(event); - runIfContainsIllegalCharacter(ComponentUtils.paperAdventureToJson(originalMessage), (json) -> { - Object component = ComponentUtils.jsonToPaperAdventure(json); - try { + String rawJsonMessage = ComponentUtils.paperAdventureToJson(originalMessage); + EmojiTextProcessResult processResult = replaceJsonEmoji(rawJsonMessage, this.plugin.adapt(player)); + boolean hasChanged = processResult.replaced(); + if (!player.hasPermission(FontManager.BYPASS_CHAT)) { + IllegalCharacterProcessResult result = processIllegalCharacters(processResult.text()); + if (result.has()) { + Object component = ComponentUtils.jsonToPaperAdventure(result.text()); + Reflections.method$AsyncChatDecorateEvent$result.invoke(event, component); + } else if (hasChanged) { + Object component = ComponentUtils.jsonToPaperAdventure(processResult.text()); Reflections.method$AsyncChatDecorateEvent$result.invoke(event, component); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); } - }); - } catch (IllegalAccessException e) { + } else if (hasChanged) { + Object component = ComponentUtils.jsonToPaperAdventure(processResult.text()); + Reflections.method$AsyncChatDecorateEvent$result.invoke(event, component); + } + } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } - - private void runIfContainsIllegalCharacter(String string, Consumer callback) { - //noinspection DuplicatedCode - char[] chars = string.toCharArray(); - int[] codepoints = CharacterUtils.charsToCodePoints(chars); - int[] newCodepoints = new int[codepoints.length]; - boolean hasIllegal = false; - for (int i = 0; i < codepoints.length; i++) { - int codepoint = codepoints[i]; - if (!isIllegalCodepoint(codepoint)) { - newCodepoints[i] = codepoint; - } else { - newCodepoints[i] = '*'; - hasIllegal = true; - } - } - if (hasIllegal) { - callback.accept(new String(newCodepoints, 0, newCodepoints.length)); - } - } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java index bd548bb67..663ec5371 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java @@ -655,7 +655,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager { jsonObject.add("components", result.components()); Object nmsStack = ItemObject.newItem(TagCompound.newTag(jsonObject.toString())); try { - itemStack = (ItemStack) Reflections.method$CraftItemStack$asCraftMirror.invoke(null, nmsStack); + itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsStack); } catch (Exception e) { this.plugin.logger().warn("Failed to create ItemStack mirror", e); return new ItemStack(Material.STICK); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java index f67a36256..f0eb1042f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java @@ -343,8 +343,7 @@ public class RecipeEventListener implements Listener { ItemStack itemStack = event.getItem(); if (ItemUtils.isEmpty(itemStack)) return; try { - @SuppressWarnings("unchecked") - Optional optionalMCRecipe = (Optional) Reflections.method$RecipeManager$getRecipeFor1.invoke( + Optional optionalMCRecipe = FastNMS.INSTANCE.method$RecipeManager$getRecipeFor( BukkitRecipeManager.nmsRecipeManager(), Reflections.instance$RecipeType$CAMPFIRE_COOKING, Reflections.constructor$SingleRecipeInput.newInstance(FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack)), diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java index 641947e72..2386ea39b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java @@ -406,7 +406,7 @@ public class BukkitInjector { InjectedCacheCheck injectedCacheCheck = (InjectedCacheCheck) thisObj; Object type = injectedCacheCheck.recipeType(); Object lastRecipe = injectedCacheCheck.lastRecipe(); - Optional> optionalRecipe = (Optional>) Reflections.method$RecipeManager$getRecipeFor0.invoke(mcRecipeManager, type, args[0], args[1], lastRecipe); + Optional> optionalRecipe = FastNMS.INSTANCE.method$RecipeManager$getRecipeFor(mcRecipeManager, type, args[0], args[1], lastRecipe); if (optionalRecipe.isPresent()) { Pair pair = optionalRecipe.get(); Object resourceLocation = pair.getFirst(); @@ -474,7 +474,7 @@ public class BukkitInjector { InjectedCacheCheck injectedCacheCheck = (InjectedCacheCheck) thisObj; Object type = injectedCacheCheck.recipeType(); Object lastRecipe = injectedCacheCheck.lastRecipe(); - Optional optionalRecipe = (Optional) Reflections.method$RecipeManager$getRecipeFor0.invoke(mcRecipeManager, type, args[0], args[1], lastRecipe); + Optional optionalRecipe = (Optional) FastNMS.INSTANCE.method$RecipeManager$getRecipeFor(mcRecipeManager, type, args[0], args[1], lastRecipe); if (optionalRecipe.isPresent()) { Object holder = optionalRecipe.get(); Object id = FastNMS.INSTANCE.field$RecipeHolder$id(holder); @@ -542,7 +542,7 @@ public class BukkitInjector { InjectedCacheCheck injectedCacheCheck = (InjectedCacheCheck) thisObj; Object type = injectedCacheCheck.recipeType(); Object lastRecipe = injectedCacheCheck.lastRecipe(); - Optional optionalRecipe = (Optional) Reflections.method$RecipeManager$getRecipeFor0.invoke(mcRecipeManager, type, args[0], args[1], lastRecipe); + Optional optionalRecipe = (Optional) FastNMS.INSTANCE.method$RecipeManager$getRecipeFor(mcRecipeManager, type, args[0], args[1], lastRecipe); if (optionalRecipe.isPresent()) { Object holder = optionalRecipe.get(); Object id = FastNMS.INSTANCE.field$RecipeHolder$id(holder); @@ -602,7 +602,7 @@ public class BukkitInjector { InjectedCacheCheck injectedCacheCheck = (InjectedCacheCheck) thisObj; Object type = injectedCacheCheck.recipeType(); Object lastRecipe = injectedCacheCheck.lastRecipe(); - Optional optionalRecipe = (Optional) Reflections.method$RecipeManager$getRecipeFor1.invoke(mcRecipeManager, type, args[0], args[1], lastRecipe); + Optional optionalRecipe = (Optional) FastNMS.INSTANCE.method$RecipeManager$getRecipeFor(mcRecipeManager, type, args[0], args[1], lastRecipe); if (optionalRecipe.isPresent()) { Object holder = optionalRecipe.get(); Object id = FastNMS.INSTANCE.field$RecipeHolder$id(holder); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 4816cd91f..0afcc7e55 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -8,8 +8,8 @@ import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.bukkit.plugin.network.impl.PacketIds1_20; -import net.momirealms.craftengine.bukkit.plugin.network.impl.PacketIds1_20_5; +import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20; +import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20_5; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.plugin.CraftEngine; @@ -127,6 +127,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes private void registerPacketHandlers() { registerNMSPacketConsumer(PacketConsumers.LEVEL_CHUNK_WITH_LIGHT, Reflections.clazz$ClientboundLevelChunkWithLightPacket); + registerNMSPacketConsumer(PacketConsumers.PLAYER_INFO_UPDATE, Reflections.clazz$ClientboundPlayerInfoUpdatePacket); registerNMSPacketConsumer(PacketConsumers.PLAYER_ACTION, Reflections.clazz$ServerboundPlayerActionPacket); registerNMSPacketConsumer(PacketConsumers.SWING_HAND, Reflections.clazz$ServerboundSwingPacket); registerNMSPacketConsumer(PacketConsumers.USE_ITEM_ON, Reflections.clazz$ServerboundUseItemOnPacket); @@ -442,7 +443,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes NMSPacketEvent event = new NMSPacketEvent(packet); onNMSPacketSend(player, event, packet); if (event.isCancelled()) return; - super.write(context, packet, channelPromise); + if (event.isUsingNewPacket()) { + super.write(context, event.optionalNewPacket(), channelPromise); + } else { + super.write(context, packet, channelPromise); + } channelPromise.addListener((p) -> { for (Runnable task : event.getDelayedTasks()) { task.run(); @@ -459,7 +464,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes NMSPacketEvent event = new NMSPacketEvent(packet); onNMSPacketReceive(player, event, packet); if (event.isCancelled()) return; - super.channelRead(context, packet); + if (event.isUsingNewPacket()) { + super.channelRead(context, event.optionalNewPacket()); + } else { + super.channelRead(context, packet); + } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/NMSPacketEvent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/NMSPacketEvent.java index bce61806f..d753f4815 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/NMSPacketEvent.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/NMSPacketEvent.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.bukkit.plugin.network; import net.momirealms.craftengine.core.util.Cancellable; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collections; @@ -8,9 +9,10 @@ import java.util.List; import java.util.Optional; public class NMSPacketEvent implements Cancellable { + private final Object packet; private boolean cancelled; private List delayedTasks = null; - private final Object packet; + private Object newPacket = null; public NMSPacketEvent(Object packet) { this.packet = packet; @@ -20,6 +22,18 @@ public class NMSPacketEvent implements Cancellable { return packet; } + public void replacePacket(@NotNull Object newPacket) { + this.newPacket = newPacket; + } + + public boolean isUsingNewPacket() { + return newPacket != null; + } + + public Object optionalNewPacket() { + return newPacket; + } + public void addDelayedTask(Runnable task) { if (delayedTasks == null) { delayedTasks = new ArrayList<>(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index 2fce786b1..0d9f63c9a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -20,6 +20,7 @@ import net.momirealms.craftengine.bukkit.util.*; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.font.FontManager; +import net.momirealms.craftengine.core.font.IllegalCharacterProcessResult; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.network.ConnectionState; @@ -42,7 +43,6 @@ import java.lang.reflect.InvocationTargetException; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.function.BiConsumer; -import java.util.function.Consumer; public class PacketConsumers { private static int[] mappings; @@ -321,6 +321,54 @@ public class PacketConsumers { } }; + public static final TriConsumer PLAYER_INFO_UPDATE = (user, event, packet) -> { + try { + if (!user.isOnline()) return; + if (!Config.interceptPlayerInfo()) return; + List entries = FastNMS.INSTANCE.field$ClientboundPlayerInfoUpdatePacket$entries(packet); + if (entries instanceof MarkedArrayList) { + return; + } + EnumSet> enums = FastNMS.INSTANCE.field$ClientboundPlayerInfoUpdatePacket$actions(packet); + outer: { + for (Object entry : enums) { + if (entry == Reflections.instance$ClientboundPlayerInfoUpdatePacket$Action$UPDATE_DISPLAY_NAME) { + break outer; + } + } + return; + } + + boolean isChanged = false; + List newEntries = new MarkedArrayList<>(); + for (Object entry : entries) { + Object mcComponent = FastNMS.INSTANCE.field$ClientboundPlayerInfoUpdatePacket$Entry$displayName(entry); + if (mcComponent == null) { + newEntries.add(entry); + continue; + } + String json = ComponentUtils.minecraftToJson(mcComponent); + Map tokens = CraftEngine.instance().imageManager().matchTags(json); + if (tokens.isEmpty()) { + newEntries.add(entry); + continue; + } + Component component = AdventureHelper.jsonToComponent(json); + for (Map.Entry token : tokens.entrySet()) { + component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); + } + Object newEntry = FastNMS.INSTANCE.constructor$ClientboundPlayerInfoUpdatePacket$Entry(entry, ComponentUtils.adventureToMinecraft(component)); + newEntries.add(newEntry); + isChanged = true; + } + if (isChanged) { + event.replacePacket(FastNMS.INSTANCE.constructor$ClientboundPlayerInfoUpdatePacket(enums, newEntries)); + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle ClientboundPlayerInfoUpdatePacket", e); + } + }; + public static final BiConsumer TEAM_1_20 = (user, event) -> { if (!Config.interceptTeam()) return; try { @@ -1117,7 +1165,7 @@ public class PacketConsumers { if (!user.isOnline()) return; BukkitServerPlayer player = (BukkitServerPlayer) user; if (!player.isMiningBlock()) return; - Object hand = Reflections.field$ServerboundSwingPacket$hand.get(packet); + Object hand = FastNMS.INSTANCE.field$ServerboundSwingPacket$hand(packet); if (hand == Reflections.instance$InteractionHand$MAIN_HAND) { player.onSwingHand(); } @@ -1149,7 +1197,7 @@ public class PacketConsumers { Object commonInfo = Reflections.field$ClientboundRespawnPacket$commonPlayerSpawnInfo.get(packet); dimensionKey = Reflections.field$CommonPlayerSpawnInfo$dimension.get(commonInfo); } - Object location = Reflections.field$ResourceKey$location.get(dimensionKey); + Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey); World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString()))); if (world != null) { int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16; @@ -1174,7 +1222,7 @@ public class PacketConsumers { Object commonInfo = Reflections.field$ClientboundLoginPacket$commonPlayerSpawnInfo.get(packet); dimensionKey = Reflections.field$CommonPlayerSpawnInfo$dimension.get(commonInfo); } - Object location = Reflections.field$ResourceKey$location.get(dimensionKey); + Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey); World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString()))); if (world != null) { int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16; @@ -1218,7 +1266,7 @@ public class PacketConsumers { if (bukkitPlayer.getGameMode() != GameMode.CREATIVE) return; int slot = VersionHelper.isVersionNewerThan1_20_5() ? Reflections.field$ServerboundSetCreativeModeSlotPacket$slotNum.getShort(packet) : Reflections.field$ServerboundSetCreativeModeSlotPacket$slotNum.getInt(packet); if (slot < 36 || slot > 44) return; - ItemStack item = (ItemStack) Reflections.method$CraftItemStack$asCraftMirror.invoke(null, Reflections.field$ServerboundSetCreativeModeSlotPacket$itemStack.get(packet)); + ItemStack item = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(Reflections.field$ServerboundSetCreativeModeSlotPacket$itemStack.get(packet)); if (ItemUtils.isEmpty(item)) return; if (slot - 36 != bukkitPlayer.getInventory().getHeldItemSlot()) { return; @@ -1620,9 +1668,7 @@ public class PacketConsumers { buf.writeVarInt(event.packetID()); buf.writeVarInt(0); Object newId = KeyUtils.toResourceLocation(mapped); - Object newSoundEvent = VersionHelper.isVersionNewerThan1_21_2() ? - Reflections.constructor$SoundEvent.newInstance(newId, Reflections.field$SoundEvent$fixedRange.get(soundEvent)) : - Reflections.constructor$SoundEvent.newInstance(newId, Reflections.field$SoundEvent$range.get(soundEvent), Reflections.field$SoundEvent$newSystem.get(soundEvent)); + Object newSoundEvent = FastNMS.INSTANCE.constructor$SoundEvent(newId, FastNMS.INSTANCE.method$SoundEvent$fixedRange(soundEvent)); FastNMS.INSTANCE.method$SoundEvent$directEncode(buf, newSoundEvent); buf.writeVarInt(source); buf.writeInt(x); @@ -1642,21 +1688,21 @@ public class PacketConsumers { public static final TriConsumer RENAME_ITEM = (user, event, packet) -> { try { if (!Config.filterAnvil()) return; + if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_ANVIL)) { + return; + } String message = (String) Reflections.field$ServerboundRenameItemPacket$name.get(packet); if (message != null && !message.isEmpty()) { - FontManager manager = CraftEngine.instance().imageManager(); - if (!manager.isDefaultFontInUse()) return; // check bypass - if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_ANVIL)) { - return; - } - runIfContainsIllegalCharacter(message, manager, (s) -> { + FontManager manager = CraftEngine.instance().imageManager(); + IllegalCharacterProcessResult result = manager.processIllegalCharacters(message); + if (result.has()) { try { - Reflections.field$ServerboundRenameItemPacket$name.set(packet, s); + Reflections.field$ServerboundRenameItemPacket$name.set(packet, result.text()); } catch (ReflectiveOperationException e) { CraftEngine.instance().logger().warn("Failed to replace chat", e); } - }); + } } } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ServerboundRenameItemPacket", e); @@ -1667,20 +1713,19 @@ public class PacketConsumers { public static final TriConsumer SIGN_UPDATE = (user, event, packet) -> { try { if (!Config.filterSign()) return; - String[] lines = (String[]) Reflections.field$ServerboundSignUpdatePacket$lines.get(packet); - FontManager manager = CraftEngine.instance().imageManager(); - if (!manager.isDefaultFontInUse()) return; // check bypass if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_SIGN)) { return; } + String[] lines = (String[]) Reflections.field$ServerboundSignUpdatePacket$lines.get(packet); + FontManager manager = CraftEngine.instance().imageManager(); + if (!manager.isDefaultFontInUse()) return; for (int i = 0; i < lines.length; i++) { String line = lines[i]; if (line != null && !line.isEmpty()) { - try { - int lineIndex = i; - runIfContainsIllegalCharacter(line, manager, (s) -> lines[lineIndex] = s); - } catch (Exception ignore) { + IllegalCharacterProcessResult result = manager.processIllegalCharacters(line); + if (result.has()) { + lines[i] = result.text(); } } } @@ -1728,18 +1773,12 @@ public class PacketConsumers { } if (changed) { - if (VersionHelper.isVersionNewerThan1_20_5()) { - event.setCancelled(true); - Object newPacket = Reflections.constructor$ServerboundEditBookPacket.newInstance( - Reflections.field$ServerboundEditBookPacket$slot.get(packet), - newPages, - newTitle - ); - user.receivePacket(newPacket); - } else { - Reflections.field$ServerboundEditBookPacket$pages.set(packet, newPages); - Reflections.field$ServerboundEditBookPacket$title.set(packet, newTitle); - } + Object newPacket = Reflections.constructor$ServerboundEditBookPacket.newInstance( + Reflections.field$ServerboundEditBookPacket$slot.get(packet), + newPages, + newTitle + ); + event.replacePacket(newPacket); } } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ServerboundEditBookPacket", e); @@ -1765,25 +1804,6 @@ public class PacketConsumers { return hasIllegal ? Pair.of(true, new String(newCodepoints, 0, newCodepoints.length)) : Pair.of(false, original); } - private static void runIfContainsIllegalCharacter(String string, FontManager manager, Consumer callback) { - if (string.isEmpty()) return; - int[] codepoints = CharacterUtils.charsToCodePoints(string.toCharArray()); - int[] newCodepoints = new int[codepoints.length]; - boolean hasIllegal = false; - for (int i = 0; i < codepoints.length; i++) { - int codepoint = codepoints[i]; - if (!manager.isIllegalCodepoint(codepoint)) { - newCodepoints[i] = codepoint; - } else { - newCodepoints[i] = '*'; - hasIllegal = true; - } - } - if (hasIllegal) { - callback.accept(new String(newCodepoints, 0, newCodepoints.length)); - } - } - public static final TriConsumer CUSTOM_PAYLOAD = (user, event, packet) -> { try { if (!VersionHelper.isVersionNewerThan1_20_5()) return; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java index 060d79728..978507531 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java @@ -37,4 +37,6 @@ public interface PacketIds { int clientboundSetObjectivePacket(); int clientboundLevelChunkWithLightPacket(); + + int clientboundPlayerInfoUpdatePacket(); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/impl/PacketIdFinder.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIdFinder.java similarity index 97% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/impl/PacketIdFinder.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIdFinder.java index 737534fcc..489ea5d01 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/impl/PacketIdFinder.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIdFinder.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.bukkit.plugin.network.impl; +package net.momirealms.craftengine.bukkit.plugin.network.id; import com.google.gson.JsonElement; import net.momirealms.craftengine.bukkit.nms.FastNMS; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/impl/PacketIds1_20.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java similarity index 93% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/impl/PacketIds1_20.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java index bbf6bfbaf..a42831d0b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/impl/PacketIds1_20.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.bukkit.plugin.network.impl; +package net.momirealms.craftengine.bukkit.plugin.network.id; import net.momirealms.craftengine.bukkit.plugin.network.PacketIds; import net.momirealms.craftengine.bukkit.util.Reflections; @@ -94,4 +94,9 @@ public class PacketIds1_20 implements PacketIds { public int clientboundLevelChunkWithLightPacket() { return PacketIdFinder.clientboundByClazz(Reflections.clazz$ClientboundLevelChunkWithLightPacket); } + + @Override + public int clientboundPlayerInfoUpdatePacket() { + return PacketIdFinder.clientboundByClazz(Reflections.clazz$ClientboundPlayerInfoUpdatePacket); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/impl/PacketIds1_20_5.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java similarity index 92% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/impl/PacketIds1_20_5.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java index 8acbd3d52..290f16df7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/impl/PacketIds1_20_5.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.bukkit.plugin.network.impl; +package net.momirealms.craftengine.bukkit.plugin.network.id; import net.momirealms.craftengine.bukkit.plugin.network.PacketIds; @@ -93,4 +93,9 @@ public class PacketIds1_20_5 implements PacketIds { public int clientboundLevelChunkWithLightPacket() { return PacketIdFinder.clientboundByName("minecraft:level_chunk_with_light"); } + + @Override + public int clientboundPlayerInfoUpdatePacket() { + return PacketIdFinder.clientboundByName("minecraft:player_info_update"); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 721f4b1c8..398b5528b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -424,8 +424,8 @@ public class BukkitServerPlayer extends Player { Object blockOwner = Reflections.field$StateHolder$owner.get(this.destroyedState); Object soundType = Reflections.field$BlockBehaviour$soundType.get(blockOwner); Object soundEvent = Reflections.field$SoundType$hitSound.get(soundType); - Object soundId = Reflections.field$SoundEvent$location.get(soundEvent); - level().playBlockSound(new Vec3d(this.destroyPos.x(), this.destroyPos.y(), this.destroyPos.z()), Key.of(soundId.toString()), 0.5F, 0.5F); + Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); + player.playSound(location, soundId.toString(), SoundCategory.BLOCKS, 0.5F, 0.5F); this.lastHitBlockTime = currentTick; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java index 0869b5437..bbce5ef50 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java @@ -128,7 +128,7 @@ public class BlockStateUtils { public static int physicsEventToId(BlockPhysicsEvent event) throws ReflectiveOperationException { Object blockData = Reflections.field$BlockPhysicsEvent$changed.get(event); Object blockState = Reflections.field$CraftBlockData$data.get(blockData); - return (int) Reflections.method$IdMapper$getId.invoke(Reflections.instance$BLOCK_STATE_REGISTRY, blockState); + return FastNMS.INSTANCE.method$IdMapper$getId(Reflections.instance$BLOCK_STATE_REGISTRY, blockState); } public static Object physicsEventToState(BlockPhysicsEvent event) throws ReflectiveOperationException { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ComponentUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ComponentUtils.java index 426e456ff..b9622ed40 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ComponentUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ComponentUtils.java @@ -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); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java index 7c67dbc05..a163b570d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java @@ -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; @@ -10,6 +11,7 @@ import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.MessageToByteEncoder; import io.papermc.paper.event.player.AsyncChatDecorateEvent; import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.core.util.ReflectionUtils; import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.Location; @@ -404,18 +406,21 @@ public class Reflections { BukkitReflectionUtils.assembleMCClass("core.HolderLookup$b") ); + @Deprecated public static final Method method$Component$Serializer$fromJson0 = ReflectionUtils.getMethod( clazz$Component$Serializer, new String[] { "fromJson" }, String.class, clazz$HolderLookup$Provider ); + @Deprecated public static final Method method$Component$Serializer$fromJson1 = ReflectionUtils.getMethod( clazz$Component$Serializer, new String[] { "fromJson" }, JsonElement.class, clazz$HolderLookup$Provider ); + @Deprecated public static final Method method$Component$Serializer$toJson = ReflectionUtils.getMethod( clazz$Component$Serializer, new String[] { "toJson" }, @@ -487,6 +492,7 @@ public class Reflections { ReflectionUtils.getInstanceDeclaredField(clazz$ServerPlayer, clazz$ServerGamePacketListenerImpl, 0) ); + @Deprecated public static final Method method$ServerGamePacketListenerImpl$sendPacket = requireNonNull( ReflectionUtils.getMethods(clazz$ServerGamePacketListenerImpl, void.class, clazz$Packet).get(0) ); @@ -546,12 +552,14 @@ public class Reflections { ) ); + @Deprecated public static final Field field$ClientboundAddEntityPacket$data = requireNonNull( ReflectionUtils.getDeclaredField( clazz$ClientboundAddEntityPacket, int.class, 4 ) ); + @Deprecated public static final Field field$ClientboundAddEntityPacket$type = requireNonNull( ReflectionUtils.getDeclaredField( clazz$ClientboundAddEntityPacket, clazz$EntityType, 0 @@ -576,6 +584,7 @@ public class Reflections { // ) // ); + @Deprecated public static final Field field$ClientboundAddEntityPacket$entityId = requireNonNull( ReflectionUtils.getDeclaredField( clazz$ClientboundAddEntityPacket, int.class, 0 @@ -594,18 +603,21 @@ public class Reflections { ) ); + @Deprecated public static final Field field$Vec3$x = requireNonNull( ReflectionUtils.getInstanceDeclaredField( clazz$Vec3, double.class, 0 ) ); + @Deprecated public static final Field field$Vec3$y = requireNonNull( ReflectionUtils.getInstanceDeclaredField( clazz$Vec3, double.class, 1 ) ); + @Deprecated public static final Field field$Vec3$z = requireNonNull( ReflectionUtils.getInstanceDeclaredField( clazz$Vec3, double.class, 2 @@ -1085,6 +1097,7 @@ public class Reflections { ) ); + @Deprecated public static final Field field$ResourceKey$location = requireNonNull( ReflectionUtils.getDeclaredField( clazz$ResourceKey, clazz$ResourceLocation, 1 @@ -1453,10 +1466,12 @@ public class Reflections { ReflectionUtils.getMethod(clazz$IdMapper, int.class) ); + @Deprecated public static final Method method$IdMapper$getId = requireNonNull( ReflectionUtils.getMethod(clazz$IdMapper, int.class, Object.class) ); + @Deprecated public static final Method method$IdMapper$byId = requireNonNull( ReflectionUtils.getMethod(clazz$IdMapper, Object.class, int.class) ); @@ -1642,6 +1657,7 @@ public class Reflections { BukkitReflectionUtils.assembleMCClass("network.RegistryFriendlyByteBuf") ); + @Deprecated public static final Constructor constructor$RegistryFriendlyByteBuf = Optional.ofNullable(clazz$RegistryFriendlyByteBuf) .map(it -> ReflectionUtils.getConstructor(it, 0)) .orElse(null); @@ -1744,6 +1760,43 @@ public class Reflections { ) ); + public static final Class clazz$ClientboundPlayerInfoUpdatePacket = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket") + ) + ); + + public static final Field field$ClientboundPlayerInfoUpdatePacket$entries = requireNonNull( + ReflectionUtils.getDeclaredField( + clazz$ClientboundPlayerInfoUpdatePacket, List.class, 0 + ) + ); + + public static final Class clazz$ClientboundPlayerInfoUpdatePacket$Action = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action"), + BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket$a") + ) + ); + + public static final Method method$ClientboundPlayerInfoUpdatePacket$Action$values = requireNonNull( + ReflectionUtils.getStaticMethod( + clazz$ClientboundPlayerInfoUpdatePacket$Action, clazz$ClientboundPlayerInfoUpdatePacket$Action.arrayType() + ) + ); + + public static final Object instance$ClientboundPlayerInfoUpdatePacket$Action$UPDATE_DISPLAY_NAME; + + static { + try { + Object[] values = (Object[]) method$ClientboundPlayerInfoUpdatePacket$Action$values.invoke(null); + instance$ClientboundPlayerInfoUpdatePacket$Action$UPDATE_DISPLAY_NAME = values[5]; + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Deprecated public static final Field field$ClientboundLevelChunkWithLightPacket$chunkData = requireNonNull( ReflectionUtils.getDeclaredField( clazz$ClientboundLevelChunkWithLightPacket, clazz$ClientboundLevelChunkPacketData, 0 @@ -1780,6 +1833,7 @@ public class Reflections { ) ); + @Deprecated public static final Field field$CraftChunk$worldServer = requireNonNull( ReflectionUtils.getDeclaredField( clazz$CraftChunk, clazz$ServerLevel, 0 @@ -2158,6 +2212,7 @@ public class Reflections { } } + @Deprecated public static final Method method$ResourceLocation$fromNamespaceAndPath = requireNonNull( ReflectionUtils.getStaticMethod( clazz$ResourceLocation, clazz$ResourceLocation, String.class, String.class @@ -2436,6 +2491,7 @@ public class Reflections { ) ); + @Deprecated public static final Constructor constructor$AABB = requireNonNull( ReflectionUtils.getConstructor( clazz$AABB, double.class, double.class, double.class, double.class, double.class, double.class @@ -2555,12 +2611,14 @@ public class Reflections { ) ); + @Deprecated public static final Field field$ClientboundLevelParticlesPacket$particle = requireNonNull( ReflectionUtils.getDeclaredField( clazz$ClientboundLevelParticlesPacket, clazz$ParticleOptions, 0 ) ); + @Deprecated public static final Field field$BlockParticleOption$blockState = requireNonNull( ReflectionUtils.getDeclaredField( clazz$BlockParticleOption, clazz$BlockState, 0 @@ -2627,6 +2685,7 @@ public class Reflections { ) ); + @Deprecated public static final Field field$SoundEvent$location = requireNonNull( ReflectionUtils.getInstanceDeclaredField( clazz$SoundEvent, clazz$ResourceLocation, 0 @@ -2666,6 +2725,7 @@ public class Reflections { ) ); + @Deprecated public static final Constructor constructor$ClientboundLightUpdatePacket = requireNonNull( ReflectionUtils.getConstructor( clazz$ClientboundLightUpdatePacket, clazz$ChunkPos, clazz$LevelLightEngine, BitSet.class, BitSet.class @@ -2706,6 +2766,7 @@ public class Reflections { ); // 1.20 ~ 1.21.4 moonrise + @Deprecated public static final Method method$ChunkHolder$getPlayers = ReflectionUtils.getMethod( clazz$ChunkHolder, List.class, boolean.class @@ -2785,12 +2846,14 @@ public class Reflections { ) ); + @Deprecated public static final Field field$ServerboundPlayerActionPacket$pos = requireNonNull( ReflectionUtils.getDeclaredField( clazz$ServerboundPlayerActionPacket, clazz$BlockPos, 0 ) ); + @Deprecated public static final Field field$ServerboundPlayerActionPacket$action = requireNonNull( ReflectionUtils.getDeclaredField( clazz$ServerboundPlayerActionPacket, clazz$ServerboundPlayerActionPacket$Action, 0 @@ -2825,17 +2888,17 @@ public class Reflections { static { try { if (VersionHelper.isVersionNewerThan1_20_5()) { - Object block_break_speed = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", VersionHelper.isVersionNewerThan1_21_2() ? "block_break_speed" : "player.block_break_speed"); + Object block_break_speed = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", VersionHelper.isVersionNewerThan1_21_2() ? "block_break_speed" : "player.block_break_speed"); @SuppressWarnings("unchecked") Optional breakSpeedHolder = (Optional) method$Registry$getHolder0.invoke(instance$BuiltInRegistries$ATTRIBUTE, block_break_speed); instance$Holder$Attribute$block_break_speed = breakSpeedHolder.orElse(null); - Object block_interaction_range = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", VersionHelper.isVersionNewerThan1_21_2() ? "block_interaction_range" : "player.block_interaction_range"); + Object block_interaction_range = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", VersionHelper.isVersionNewerThan1_21_2() ? "block_interaction_range" : "player.block_interaction_range"); @SuppressWarnings("unchecked") Optional blockInteractionRangeHolder = (Optional) method$Registry$getHolder0.invoke(instance$BuiltInRegistries$ATTRIBUTE, block_interaction_range); instance$Holder$Attribute$block_interaction_range = blockInteractionRangeHolder.orElse(null); - Object scale = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", VersionHelper.isVersionNewerThan1_21_2() ? "scale" : "generic.scale"); + Object scale = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", VersionHelper.isVersionNewerThan1_21_2() ? "scale" : "generic.scale"); @SuppressWarnings("unchecked") Optional scaleHolder = (Optional) method$Registry$getHolder0.invoke(instance$BuiltInRegistries$ATTRIBUTE, scale); instance$Holder$Attribute$scale = scaleHolder.orElse(null); @@ -2979,6 +3042,7 @@ public class Reflections { ) ); + @Deprecated public static final Field field$ServerboundSwingPacket$hand = requireNonNull( ReflectionUtils.getDeclaredField( clazz$ServerboundSwingPacket, clazz$InteractionHand, 0 @@ -3191,11 +3255,11 @@ public class Reflections { // for 1.20.1-1.20.4 static { try { - Object mining_fatigue = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "mining_fatigue"); + Object mining_fatigue = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "mining_fatigue"); instance$MobEffecr$mining_fatigue = method$Registry$get.invoke(instance$BuiltInRegistries$MOB_EFFECT, mining_fatigue); - Object haste = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "haste"); + Object haste = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "haste"); instance$MobEffecr$haste = method$Registry$get.invoke(instance$BuiltInRegistries$MOB_EFFECT, haste); - Object invisibility = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "invisibility"); + Object invisibility = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "invisibility"); instance$MobEffecr$invisibility = method$Registry$get.invoke(instance$BuiltInRegistries$MOB_EFFECT, invisibility); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); @@ -3282,7 +3346,7 @@ public class Reflections { static { try { - Object key = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "intentionally_empty"); + Object key = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "intentionally_empty"); instance$SoundEvent$EMPTY = method$Registry$get.invoke(instance$BuiltInRegistries$SOUND_EVENT, key); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); @@ -3354,6 +3418,7 @@ public class Reflections { ) ); + @Deprecated public static final Method method$CraftItemStack$asCraftMirror = requireNonNull( ReflectionUtils.getStaticMethod( clazz$CraftItemStack, clazz$CraftItemStack, new String[]{"asCraftMirror"}, clazz$ItemStack @@ -3519,19 +3584,22 @@ public class Reflections { public static final Object instance$Blocks$STONE; public static final Object instance$Blocks$STONE$defaultState; public static final Object instance$Blocks$FIRE; + public static final Object instance$Blocks$SOUL_FIRE; public static final Object instance$Blocks$ICE; static { try { - Object air = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "air"); + Object air = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "air"); instance$Blocks$AIR = method$Registry$get.invoke(instance$BuiltInRegistries$BLOCK, air); instance$Blocks$AIR$defaultState = method$Block$defaultBlockState.invoke(instance$Blocks$AIR); - Object fire = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "fire"); + Object fire = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "fire"); instance$Blocks$FIRE = method$Registry$get.invoke(instance$BuiltInRegistries$BLOCK, fire); - Object stone = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "stone"); + Object soulFire = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "soul_fire"); + instance$Blocks$SOUL_FIRE = method$Registry$get.invoke(instance$BuiltInRegistries$BLOCK, soulFire); + Object stone = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "stone"); instance$Blocks$STONE = method$Registry$get.invoke(instance$BuiltInRegistries$BLOCK, stone); instance$Blocks$STONE$defaultState = method$Block$defaultBlockState.invoke(instance$Blocks$STONE); - Object ice = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "ice"); + Object ice = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "ice"); instance$Blocks$ICE = method$Registry$get.invoke(instance$BuiltInRegistries$BLOCK, ice); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); @@ -3544,6 +3612,7 @@ public class Reflections { ) ); + @Deprecated public static final Method method$Level$removeBlock = requireNonNull( ReflectionUtils.getMethod( clazz$Level, boolean.class, clazz$BlockPos, boolean.class @@ -3713,19 +3782,19 @@ public class Reflections { static { try { - Object textDisplay = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "text_display"); + Object textDisplay = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "text_display"); instance$EntityType$TEXT_DISPLAY = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, textDisplay); - Object itemDisplay = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "item_display"); + Object itemDisplay = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "item_display"); instance$EntityType$ITEM_DISPLAY = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, itemDisplay); - Object blockDisplay = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "block_display"); + Object blockDisplay = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "block_display"); instance$EntityType$BLOCK_DISPLAY = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, blockDisplay); - Object fallingBlock = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "falling_block"); + Object fallingBlock = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "falling_block"); instance$EntityType$FALLING_BLOCK = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, fallingBlock); - Object interaction = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "interaction"); + Object interaction = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "interaction"); instance$EntityType$INTERACTION = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, interaction); - Object shulker = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "shulker"); + Object shulker = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "shulker"); instance$EntityType$SHULKER = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, shulker); - Object armorStand = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "armor_stand"); + Object armorStand = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "armor_stand"); instance$EntityType$ARMOR_STAND = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, armorStand); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); @@ -3742,13 +3811,13 @@ public class Reflections { static { try { - instance$RecipeType$CRAFTING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "crafting")); - instance$RecipeType$SMELTING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "smelting")); - instance$RecipeType$BLASTING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "blasting")); - instance$RecipeType$SMOKING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "smoking")); - instance$RecipeType$CAMPFIRE_COOKING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "campfire_cooking")); - instance$RecipeType$STONECUTTING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "stonecutting")); - instance$RecipeType$SMITHING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "smithing")); + instance$RecipeType$CRAFTING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "crafting")); + instance$RecipeType$SMELTING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "smelting")); + instance$RecipeType$BLASTING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "blasting")); + instance$RecipeType$SMOKING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "smoking")); + instance$RecipeType$CAMPFIRE_COOKING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "campfire_cooking")); + instance$RecipeType$STONECUTTING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "stonecutting")); + instance$RecipeType$SMITHING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "smithing")); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } @@ -3803,6 +3872,7 @@ public class Reflections { ) ); + @Deprecated public static final Method method$BlockStateBase$onPlace = requireNonNull( ReflectionUtils.getMethod( clazz$BlockStateBase, void.class, clazz$Level, clazz$BlockPos, clazz$BlockState, boolean.class @@ -3848,10 +3918,10 @@ public class Reflections { static { try { - Object air = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "air"); + Object air = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "air"); instance$Items$AIR = method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ITEM, air); instance$ItemStack$Air = constructor$ItemStack.newInstance(instance$Items$AIR); - Object waterBucket = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "water_bucket"); + Object waterBucket = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "water_bucket"); instance$Items$WATER_BUCKET = method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ITEM, waterBucket); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); @@ -4078,15 +4148,15 @@ public class Reflections { static { try { - Object waterId = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "water"); + Object waterId = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "water"); instance$Fluids$WATER = method$Registry$get.invoke(instance$BuiltInRegistries$FLUID, waterId); - Object flowingWaterId = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "flowing_water"); + Object flowingWaterId = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "flowing_water"); instance$Fluids$FLOWING_WATER = method$Registry$get.invoke(instance$BuiltInRegistries$FLUID, flowingWaterId); - Object lavaId = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "lava"); + Object lavaId = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "lava"); instance$Fluids$LAVA = method$Registry$get.invoke(instance$BuiltInRegistries$FLUID, lavaId); - Object flowingLavaId = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "flowing_lava"); + Object flowingLavaId = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "flowing_lava"); instance$Fluids$FLOWING_LAVA = method$Registry$get.invoke(instance$BuiltInRegistries$FLUID, flowingLavaId); - Object emptyId = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "empty"); + Object emptyId = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "empty"); instance$Fluids$EMPTY = method$Registry$get.invoke(instance$BuiltInRegistries$FLUID, emptyId); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); @@ -4499,6 +4569,7 @@ public class Reflections { .map(it -> ReflectionUtils.getDeclaredField(it, 1)) .orElse(null); + @Deprecated public static final Field field$RecipeHolder$id = Optional.ofNullable(clazz$RecipeHolder) .map(it -> ReflectionUtils.getDeclaredField(it, 0)) .orElse(null); @@ -4770,17 +4841,23 @@ public class Reflections { .map(it -> ReflectionUtils.getConstructor(it, clazz$ItemStack)) .orElse(null); - // 1.20.1-1.21.1 - public static final Method method$RecipeManager$getRecipeFor0 = - ReflectionUtils.getMethod( - clazz$RecipeManager, Optional.class, clazz$RecipeType, clazz$Container, clazz$Level, clazz$ResourceLocation - ); - - // 1.21.2+ - public static final Method method$RecipeManager$getRecipeFor1 = - ReflectionUtils.getMethod( - clazz$RecipeManager, Optional.class, clazz$RecipeType, clazz$RecipeInput, clazz$Level, clazz$ResourceKey - ); +// // 1.20.1-1.20.6 +// public static final Method method$RecipeManager$getRecipeFor0 = +// ReflectionUtils.getMethod( +// clazz$RecipeManager, Optional.class, clazz$RecipeType, clazz$Container, clazz$Level, clazz$ResourceLocation +// ); +// +// // 1.21.1 +// public static final Method method$RecipeManager$getRecipeFor2 = +// ReflectionUtils.getMethod( +// clazz$RecipeManager, Optional.class, clazz$RecipeType, clazz$RecipeInput, clazz$Level, clazz$ResourceLocation +// ); +// +// // 1.21.2+ +// public static final Method method$RecipeManager$getRecipeFor1 = +// ReflectionUtils.getMethod( +// clazz$RecipeManager, Optional.class, clazz$RecipeType, clazz$RecipeInput, clazz$Level, clazz$ResourceKey +// ); // 1.21+ public static final Field field$SingleRecipeInput$item = Optional.ofNullable(clazz$SingleRecipeInput) @@ -5957,6 +6034,7 @@ public class Reflections { ) ); + @Deprecated public static final Method method$Entity$getId = requireNonNull( VersionHelper.isVersionNewerThan1_20_5() ? ReflectionUtils.getMethod(clazz$Entity, int.class, new String[]{"getId"}) @@ -6311,4 +6389,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); + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index 77d1e3c6c..2ccb72aa8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -1,6 +1,7 @@ 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.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.ResourceLocation; @@ -8,12 +9,18 @@ import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser; import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.util.*; +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.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; @@ -26,10 +33,15 @@ public abstract class AbstractFontManager implements FontManager { private final Set illegalChars = new HashSet<>(); private final ImageParser imageParser; private final EmojiParser emojiParser; - private OffsetFont offsetFont; - private Trie trie; - private Map tagMapper; + + protected Trie imageTagTrie; + protected Trie emojiKeywordTrie; + protected Map tagMapper; + protected Map emojiMapper; + + protected List emojiList; + protected List allEmojiSuggestions; public AbstractFontManager(CraftEngine plugin) { this.plugin = plugin; @@ -49,20 +61,12 @@ public abstract class AbstractFontManager implements FontManager { this.fonts.clear(); this.images.clear(); this.illegalChars.clear(); + this.emojis.clear(); } @Override - public Map matchTags(String json) { - if (this.trie == null) { - return Collections.emptyMap(); - } - Map tags = new HashMap<>(); - for (Token token : this.trie.tokenize(json)) { - if (token.isMatch()) { - tags.put(token.getFragment(), this.tagMapper.get(token.getFragment())); - } - } - return tags; + public void disable() { + this.unload(); } @Override @@ -73,10 +77,193 @@ public abstract class AbstractFontManager implements FontManager { @Override public void delayedLoad() { Optional.ofNullable(this.fonts.get(DEFAULT_FONT)).ifPresent(font -> this.illegalChars.addAll(font.codepointsInUse())); - this.buildTrie(); + this.buildImageTagTrie(); + this.buildEmojiKeywordsTrie(); + this.emojiList = new ArrayList<>(this.emojis.values()); + this.allEmojiSuggestions = this.emojis.values().stream() + .flatMap(emoji -> emoji.keywords().stream()) + .collect(Collectors.toList()); } - private void buildTrie() { + @Override + public Map matchTags(String json) { + if (this.imageTagTrie == null) { + return Collections.emptyMap(); + } + Map tags = new HashMap<>(); + for (Token token : this.imageTagTrie.tokenize(json)) { + if (token.isMatch()) { + tags.put(token.getFragment(), this.tagMapper.get(token.getFragment())); + } + } + return tags; + } + + @Override + public EmojiTextProcessResult replaceMiniMessageEmoji(@NotNull String miniMessage, Player player, int maxTimes) { + if (this.emojiKeywordTrie == null || maxTimes <= 0) { + return EmojiTextProcessResult.notReplaced(miniMessage); + } + Map replacements = new HashMap<>(); + for (Token token : this.emojiKeywordTrie.tokenize(miniMessage)) { + 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() + .withOptionalParameter(EmojiParameters.EMOJI, emoji.emojiImage()) + .withParameter(EmojiParameters.KEYWORD, emoji.keywords().get(0)) + .build()).tagResolvers() + ); + replacements.put(fragment, AdventureHelper.componentToMiniMessage(content)); + } + 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 EmojiTextProcessResult replaceJsonEmoji(@NotNull String jsonText, Player player, int maxTimes) { + if (this.emojiKeywordTrie == null) { + return EmojiTextProcessResult.notReplaced(jsonText); + } + Map emojis = new HashMap<>(); + for (Token token : this.emojiKeywordTrie.tokenize(jsonText)) { + if (!token.isMatch()) + continue; + 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; + } + 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 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 + public IllegalCharacterProcessResult processIllegalCharacters(String raw, char replacement) { + boolean hasIllegal = false; + // replace illegal image usage + Map tokens = matchTags(raw); + if (!tokens.isEmpty()) { + for (Map.Entry entry : tokens.entrySet()) { + raw = raw.replace(entry.getKey(), String.valueOf(replacement)); + hasIllegal = true; + } + } + + if (this.isDefaultFontInUse()) { + // replace illegal codepoint + char[] chars = raw.toCharArray(); + int[] codepoints = CharacterUtils.charsToCodePoints(chars); + int[] newCodepoints = new int[codepoints.length]; + + for (int i = 0; i < codepoints.length; i++) { + int codepoint = codepoints[i]; + if (!isIllegalCodepoint(codepoint)) { + newCodepoints[i] = codepoint; + } else { + newCodepoints[i] = replacement; + hasIllegal = true; + } + } + + if (hasIllegal) { + return IllegalCharacterProcessResult.has(new String(newCodepoints, 0, newCodepoints.length)); + } + } else if (hasIllegal) { + return IllegalCharacterProcessResult.has(raw); + } + return IllegalCharacterProcessResult.not(); + } + + private void buildEmojiKeywordsTrie() { + this.emojiMapper = new HashMap<>(); + for (Emoji emoji : this.emojis.values()) { + for (String keyword : emoji.keywords()) { + this.emojiMapper.put(keyword, emoji); + } + } + this.emojiKeywordTrie = Trie.builder() + .ignoreOverlaps() + .addKeywords(this.emojiMapper.keySet()) + .build(); + } + + private void buildImageTagTrie() { this.tagMapper = new HashMap<>(); for (BitmapImage image : this.images.values()) { String id = image.id().toString(); @@ -93,7 +280,7 @@ public abstract class AbstractFontManager implements FontManager { this.tagMapper.put("", AdventureHelper.miniMessage().deserialize(this.offsetFont.createOffset(i, FormatUtils::miniMessageFont))); this.tagMapper.put("\\", Component.text("")); } - this.trie = Trie.builder() + this.imageTagTrie = Trie.builder() .ignoreOverlaps() .addKeywords(this.tagMapper.keySet()) .build(); @@ -164,7 +351,51 @@ public abstract class AbstractFontManager implements FontManager { @Override public void parseSection(Pack pack, Path path, Key id, Map section) { - + if (emojis.containsKey(id)) { + TranslationManager.instance().log("warning.config.emoji.duplicated", path.toString(), id.toString()); + return; + } + String permission = (String) section.get("permission"); + Object keywordsRaw = section.get("keywords"); + if (keywordsRaw == null) { + TranslationManager.instance().log("warning.config.emoji.lack_keywords", path.toString(), id.toString()); + return; + } + List keywords = MiscUtils.getAsStringList(keywordsRaw); + if (keywords.isEmpty()) { + TranslationManager.instance().log("warning.config.emoji.lack_keywords", path.toString(), id.toString()); + return; + } + String content = section.getOrDefault("content", "").toString(); + String image = null; + if (section.containsKey("image")) { + String rawImage = section.get("image").toString(); + String[] split = rawImage.split(":"); + if (split.length == 2) { + Key imageId = new Key(split[0], split[1]); + Optional bitmapImage = bitmapImageByImageId(imageId); + if (bitmapImage.isPresent()) { + image = bitmapImage.get().miniMessage(0, 0); + } else { + TranslationManager.instance().log("warning.config.emoji.invalid_image", path.toString(), id.toString(), rawImage); + return; + } + } else if (split.length == 4) { + Key imageId = new Key(split[0], split[1]); + Optional bitmapImage = bitmapImageByImageId(imageId); + if (bitmapImage.isPresent()) { + image = bitmapImage.get().miniMessage(Integer.parseInt(split[2]), Integer.parseInt(split[3])); + } else { + TranslationManager.instance().log("warning.config.emoji.invalid_image", path.toString(), id.toString(), rawImage); + return; + } + } else { + TranslationManager.instance().log("warning.config.emoji.invalid_image", path.toString(), id.toString(), rawImage); + return; + } + } + Emoji emoji = new Emoji(content, permission, image, keywords); + emojis.put(id, emoji); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/Emoji.java b/core/src/main/java/net/momirealms/craftengine/core/font/Emoji.java index 3b76f27f7..81a51429b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/Emoji.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/Emoji.java @@ -1,25 +1,27 @@ package net.momirealms.craftengine.core.font; -import net.momirealms.craftengine.core.util.Key; - import javax.annotation.Nullable; +import java.util.List; public class Emoji { - private final Key font; - private final String image; + private final String content; private final String permission; + private final String image; + private final List keywords; - public Emoji(Key font, String image, String permission) { - this.font = font; + public Emoji(String content, String permission, String image, List keywords) { + this.content = content; this.image = image; this.permission = permission; + this.keywords = keywords; } - public Key font() { - return font; + public String content() { + return content; } - public String image() { + @Nullable + public String emojiImage() { return image; } @@ -27,4 +29,8 @@ public class Emoji { public String permission() { return permission; } + + public List keywords() { + return keywords; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/EmojiComponentProcessResult.java b/core/src/main/java/net/momirealms/craftengine/core/font/EmojiComponentProcessResult.java new file mode 100644 index 000000000..dbffb0998 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/font/EmojiComponentProcessResult.java @@ -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); + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/EmojiParameters.java b/core/src/main/java/net/momirealms/craftengine/core/font/EmojiParameters.java new file mode 100644 index 000000000..83c91536d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/font/EmojiParameters.java @@ -0,0 +1,9 @@ +package net.momirealms.craftengine.core.font; + +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.context.ContextKey; + +public class EmojiParameters { + public static final ContextKey KEYWORD = new ContextKey<>(Key.of("keyword")); + public static final ContextKey EMOJI = new ContextKey<>(Key.of("emoji")); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/EmojiTextProcessResult.java b/core/src/main/java/net/momirealms/craftengine/core/font/EmojiTextProcessResult.java new file mode 100644 index 000000000..8813cbdf0 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/font/EmojiTextProcessResult.java @@ -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); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java index 143bd2858..59869b59b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java @@ -1,11 +1,16 @@ 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; @@ -19,8 +24,40 @@ public interface FontManager extends Manageable { String BYPASS_COMMAND = "craftengine.filter.bypass.command"; String BYPASS_ANVIL = "craftengine.filter.bypass.anvil"; + 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); + + default IllegalCharacterProcessResult processIllegalCharacters(String raw) { + return processIllegalCharacters(raw, '*'); + } + + IllegalCharacterProcessResult processIllegalCharacters(String raw, char replacement); + ConfigSectionParser[] parsers(); + default EmojiTextProcessResult replaceMiniMessageEmoji(@NotNull String miniMessage, @Nullable Player player) { + return replaceMiniMessageEmoji(miniMessage, player, Config.maxEmojisPerParse()); + } + + 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(); boolean isIllegalCodepoint(int codepoint); diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/IllegalCharacterProcessResult.java b/core/src/main/java/net/momirealms/craftengine/core/font/IllegalCharacterProcessResult.java new file mode 100644 index 000000000..f2568de6e --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/font/IllegalCharacterProcessResult.java @@ -0,0 +1,12 @@ +package net.momirealms.craftengine.core.font; + +public record IllegalCharacterProcessResult(boolean has, String text) { + + public static IllegalCharacterProcessResult has(String text) { + return new IllegalCharacterProcessResult(true, text); + } + + public static IllegalCharacterProcessResult not() { + return new IllegalCharacterProcessResult(false, ""); + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java index 2c77dcbd9..5f7ccfd0a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemBuildContext.java @@ -1,43 +1,20 @@ package net.momirealms.craftengine.core.item; -import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.momirealms.craftengine.core.entity.player.Player; -import net.momirealms.craftengine.core.plugin.text.minimessage.*; import net.momirealms.craftengine.core.util.context.ContextHolder; +import net.momirealms.craftengine.core.util.context.PlayerContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class ItemBuildContext implements MiniMessageTextContext { +public class ItemBuildContext extends PlayerContext { public static final ItemBuildContext EMPTY = new ItemBuildContext(null, ContextHolder.EMPTY); - private final Player player; - private final ContextHolder contexts; - private TagResolver[] tagResolvers; public ItemBuildContext(@Nullable Player player, @NotNull ContextHolder contexts) { - this.player = player; - this.contexts = contexts; + super(player, contexts); } @NotNull public static ItemBuildContext of(@Nullable Player player, @NotNull ContextHolder contexts) { return new ItemBuildContext(player, contexts); } - - @Nullable - public Player player() { - return this.player; - } - - @NotNull - public ContextHolder contexts() { - return this.contexts; - } - - @NotNull - public TagResolver[] tagResolvers() { - if (this.tagResolvers == null) { - this.tagResolvers = new TagResolver[]{ShiftTag.INSTANCE, ImageTag.INSTANCE, new PlaceholderTag(this.player), new I18NTag(this), new NamedArgumentTag(this)}; - } - return this.tagResolvers; - } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index c99a83be3..a94426c90 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -300,6 +300,8 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/pack.png"); // templates plugin.saveResource("resources/default/configuration/templates.yml"); + // emoji + plugin.saveResource("resources/default/configuration/emoji.yml"); // i18n plugin.saveResource("resources/default/configuration/i18n.yml"); // block_name diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index fd197c315..8e976ca03 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -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_emojis_per_parse; protected boolean light_system$force_update_light; protected boolean light_system$enable; @@ -133,6 +134,7 @@ public class Config { protected boolean image$intercept_packets$entity_name; protected boolean image$intercept_packets$text_display; protected boolean image$intercept_packets$armor_stand; + protected boolean image$intercept_packets$player_info; public Config(CraftEngine plugin) { this.plugin = plugin; @@ -259,6 +261,7 @@ public class Config { // performance performance$max_block_chain_update_limit = config.getInt("performance.max-block-chain-update-limit", 64); + performance$max_emojis_per_parse = config.getInt("performance.max-emojis-per-parse", 32); // light light_system$force_update_light = config.getBoolean("light-system.force-update-light", false); @@ -300,6 +303,7 @@ public class Config { image$intercept_packets$entity_name = config.getBoolean("image.intercept-packets.entity-name", false); image$intercept_packets$text_display = config.getBoolean("image.intercept-packets.text-display", true); image$intercept_packets$armor_stand = config.getBoolean("image.intercept-packets.armor-stand", true); + image$intercept_packets$player_info = config.getBoolean("image.intercept-packets.player-info", true); Class modClazz = ReflectionUtils.getClazz(CraftEngine.MOD_CLASS); if (modClazz != null) { @@ -352,6 +356,10 @@ public class Config { return instance.performance$max_block_chain_update_limit; } + public static int maxEmojisPerParse() { + return instance.performance$max_emojis_per_parse; + } + public static boolean removeInvalidFurniture() { return instance.furniture$remove_invalid_furniture_on_chunk_load$enable; } @@ -644,6 +652,10 @@ public class Config { return instance.image$intercept_packets$armor_stand; } + public static boolean interceptPlayerInfo() { + return instance.image$intercept_packets$player_info; + } + public YamlDocument loadOrCreateYamlData(String fileName) { File file = new File(this.plugin.dataFolderFile(), fileName); if (!file.exists()) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java index 81460d345..aacfc4f47 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java @@ -16,6 +16,7 @@ import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.context.ContextHolder; +import net.momirealms.craftengine.core.util.context.PlayerContext; import java.nio.file.Path; import java.util.*; @@ -175,7 +176,7 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { } }) .build() - .title(AdventureHelper.miniMessage().deserialize(Constants.BROWSER_TITLE, ItemBuildContext.of(player, ContextHolder.EMPTY).tagResolvers())) + .title(AdventureHelper.miniMessage().deserialize(Constants.BROWSER_TITLE, PlayerContext.of(player, ContextHolder.EMPTY).tagResolvers())) .refresh() .open(player); } @@ -290,7 +291,7 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { } }) .build() - .title(AdventureHelper.miniMessage().deserialize(Constants.CATEGORY_TITLE, ItemBuildContext.of(player, ContextHolder.EMPTY).tagResolvers())) + .title(AdventureHelper.miniMessage().deserialize(Constants.CATEGORY_TITLE, PlayerContext.of(player, ContextHolder.EMPTY).tagResolvers())) .refresh() .open(player); } @@ -351,7 +352,7 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { } }) .build() - .title(AdventureHelper.miniMessage().deserialize(Constants.RECIPE_NONE_TITLE, ItemBuildContext.of(player, ContextHolder.EMPTY).tagResolvers())) + .title(AdventureHelper.miniMessage().deserialize(Constants.RECIPE_NONE_TITLE, PlayerContext.of(player, ContextHolder.EMPTY).tagResolvers())) .refresh() .open(player); } @@ -576,7 +577,7 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { } }) .build() - .title(AdventureHelper.miniMessage().deserialize(Constants.RECIPE_SMITHING_TRANSFORM_TITLE, ItemBuildContext.of(player, ContextHolder.EMPTY).tagResolvers())) + .title(AdventureHelper.miniMessage().deserialize(Constants.RECIPE_SMITHING_TRANSFORM_TITLE, PlayerContext.of(player, ContextHolder.EMPTY).tagResolvers())) .refresh() .open(player); } @@ -709,7 +710,7 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { } }) .build() - .title(AdventureHelper.miniMessage().deserialize(Constants.RECIPE_STONECUTTING_TITLE, ItemBuildContext.of(player, ContextHolder.EMPTY).tagResolvers())) + .title(AdventureHelper.miniMessage().deserialize(Constants.RECIPE_STONECUTTING_TITLE, PlayerContext.of(player, ContextHolder.EMPTY).tagResolvers())) .refresh() .open(player); } @@ -859,7 +860,7 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { } }) .build() - .title(AdventureHelper.miniMessage().deserialize(title, ItemBuildContext.of(player, ContextHolder.EMPTY).tagResolvers())) + .title(AdventureHelper.miniMessage().deserialize(title, PlayerContext.of(player, ContextHolder.EMPTY).tagResolvers())) .refresh() .open(player); } @@ -1054,7 +1055,7 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { } }) .build() - .title(AdventureHelper.miniMessage().deserialize(Constants.RECIPE_CRAFTING_TITLE, ItemBuildContext.of(player, ContextHolder.EMPTY).tagResolvers())) + .title(AdventureHelper.miniMessage().deserialize(Constants.RECIPE_CRAFTING_TITLE, PlayerContext.of(player, ContextHolder.EMPTY).tagResolvers())) .refresh() .open(player); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/NamedArgumentTag.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/NamedArgumentTag.java index e63f82acd..67f2b335f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/NamedArgumentTag.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/NamedArgumentTag.java @@ -28,15 +28,12 @@ public class NamedArgumentTag implements TagResolver { if (!has(name)) { return null; } - String argumentKey = arguments.popOr("No argument key provided").toString(); - ContextKey key = ContextKey.of(Key.of(argumentKey)); if (!this.context.contexts().has(key)) { throw ctx.newException("Invalid argument key", arguments); } - - return Tag.inserting(AdventureHelper.miniMessage().deserialize(this.context.contexts().getOrThrow(key), this.context.tagResolvers())); + return Tag.selfClosingInserting(AdventureHelper.miniMessage().deserialize(this.context.contexts().getOrThrow(key), this.context.tagResolvers())); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java index 2a365d14d..7d32c3fb6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java @@ -5,7 +5,9 @@ import net.kyori.adventure.audience.Audience; import net.kyori.adventure.key.Key; import net.kyori.adventure.sound.Sound; 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.tag.resolver.TagResolver; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.json.JSONOptions; import net.kyori.adventure.text.serializer.json.legacyimpl.NBTLegacyHoverEventSerializer; @@ -19,12 +21,14 @@ public class AdventureHelper { public static final String EMPTY_COMPONENT = componentToJson(Component.empty()); private final MiniMessage miniMessage; private final MiniMessage miniMessageStrict; + private final MiniMessage miniMessageCustom; private final GsonComponentSerializer gsonComponentSerializer; private final NBTComponentSerializer nbtComponentSerializer; private AdventureHelper() { this.miniMessage = MiniMessage.builder().build(); this.miniMessageStrict = MiniMessage.builder().strict(true).build(); + this.miniMessageCustom = MiniMessage.builder().tags(TagResolver.empty()).build(); GsonComponentSerializer.Builder builder = GsonComponentSerializer.builder(); if (!VersionHelper.isVersionNewerThan1_20_5()) { builder.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()); @@ -56,6 +60,14 @@ public class AdventureHelper { return getInstance().miniMessage; } + public static MiniMessage customMiniMessage() { + return getInstance().miniMessageCustom; + } + + public static MiniMessage strictMiniMessage() { + return getInstance().miniMessageStrict; + } + /** * Retrieves the GsonComponentSerializer instance. * @@ -126,6 +138,10 @@ public class AdventureHelper { return getInstance().miniMessageStrict.serialize(getInstance().gsonComponentSerializer.deserialize(json)); } + public static String componentToMiniMessage(Component component) { + return getInstance().miniMessageStrict.serialize(component); + } + /** * Converts a JSON string to a Component. * @@ -245,4 +261,27 @@ public class AdventureHelper { } 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; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MarkedArrayList.java b/core/src/main/java/net/momirealms/craftengine/core/util/MarkedArrayList.java new file mode 100644 index 000000000..1d1e57d24 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MarkedArrayList.java @@ -0,0 +1,16 @@ +package net.momirealms.craftengine.core.util; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; + +public class MarkedArrayList extends ArrayList { + + public MarkedArrayList() { + } + + public MarkedArrayList(@NotNull Collection c) { + super(c); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/context/PlayerContext.java b/core/src/main/java/net/momirealms/craftengine/core/util/context/PlayerContext.java new file mode 100644 index 000000000..1104abb65 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/context/PlayerContext.java @@ -0,0 +1,42 @@ +package net.momirealms.craftengine.core.util.context; + +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.text.minimessage.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class PlayerContext implements MiniMessageTextContext { + public static final PlayerContext EMPTY = new PlayerContext(null, ContextHolder.EMPTY); + private final Player player; + private final ContextHolder contexts; + private TagResolver[] tagResolvers; + + public PlayerContext(@Nullable Player player, @NotNull ContextHolder contexts) { + this.player = player; + this.contexts = contexts; + } + + @NotNull + public static PlayerContext of(@Nullable Player player, @NotNull ContextHolder contexts) { + return new PlayerContext(player, contexts); + } + + @Nullable + public Player player() { + return this.player; + } + + @NotNull + public ContextHolder contexts() { + return this.contexts; + } + + @NotNull + public TagResolver[] tagResolvers() { + if (this.tagResolvers == null) { + this.tagResolvers = new TagResolver[]{ShiftTag.INSTANCE, ImageTag.INSTANCE, new PlaceholderTag(this.player), new I18NTag(this), new NamedArgumentTag(this)}; + } + return this.tagResolvers; + } +} diff --git a/gradle.properties b/gradle.properties index 47baf8538..617b4f248 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,8 +2,8 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.46.2 -config_version=23 +project_version=0.0.46.3 +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.52 +nms_helper_version=0.56.2 # Ignite Dependencies mixinextras_version=0.4.1 mixin_version=0.15.2+mixin.0.8.7