diff --git a/CHANGELOG.md b/CHANGELOG.md index e68fc88..f63833d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,20 @@ # 更新日志 -## 1.3.0 - 2026-08-02 +## 1.3.0 - 2026-08-05 ### 新增 - 按 HuskHomes 的单服逻辑完整实现 TPA 请求、接受、拒绝、忽略、预热、冷却、位置快照、音效与相关权限。 - 新增维护模式,包括 MOTD、BossBar、登录拦截、白名单、管理员通知和 LuckPerms 异步权限检查。 - 新增 SkinBridge,可识别 InfstarMC、LittleSkin 与自定义 Blessing Skin Provider,并通过 MineSkin 与 Paper Profile API 同步皮肤。 -- 新增管理模式独立状态存储、玩家功能状态管理与 JEI 配方同步版本适配。 +- 新增管理模式独立状态存储与玩家功能状态管理。 ### 优化 - 按便捷方块、管理、修复与皮肤功能重新整理运行时模块。 - 完善潜影盒会话保护、TPA 请求队列、配置迁移、数据原子写入与持久化错误处理。 -- 适配 Paper `1.21.11`、`26.1.2` 与 `26.2`,最终插件保持 Java 21 字节码兼容。 +- 验证兼容 Paper `1.21.11` 与 `26.2`,最终插件保持 Java 21 字节码兼容。 +- 移除实验性的 JEI 配方同步及其版本适配子项目,插件继续通过 Paper 公共 API 兼容支持的服务端版本。 - 迁移 Paper 弃用 API,并将弃用警告设为构建错误。 - 统一 HuskHomes 风格的中英文消息、颜色和配置注释。 diff --git a/README.md b/README.md index e88a727..eb19b6b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ - 最低支持版本为 `Paper 1.21.11` - 已适配 `Paper 26.2` -- 主插件使用 `Java 21`,Paper 26.x 版本适配模块使用对应服务端要求的 Java 工具链 +- 插件统一使用 Paper 公共 API 和 Java 21 字节码,无需按服务端版本拆分构建 - 配置按职责拆分:主行为与 SkinBridge、模块开关、菜单布局、维护模式和语言文本分别管理 - 支持运行期模块开关,避免为不同功能组合构建多个插件版本 @@ -61,7 +61,6 @@ - 管理模式独立背包、装备栏与状态切换 - 维护模式:替换 MOTD、登录拦截、白名单放行和管理员拦截通知 - Enderman 掉落方块控制 -- JEI 配方同步修复 ## 模块配置 @@ -72,7 +71,6 @@ | `blocks` | 开启 | 便捷方块命令、`/essc blocks` 菜单、潜影盒快捷打开 | | `admin-mode` | 开启 | `/essc admin` 管理模式与独立状态保存 | | `tpsbar` | 开启 | 插件版 TPSBar,检测到服务端原生命令时自动避免冲突 | -| `jei-sync` | 开启 | Fabric / NeoForge JEI 配方同步修复 | | `mob-drops` | 关闭 | 末影人掉落控制,默认关闭以保留过去标准版行为 | | `maintenance` | 开启 | 维护模式命令、MOTD 替换、登录拦截、白名单和拦截通知 | | `skin-bridge` | 关闭 | 查询外置 Yggdrasil profile,并通过 MineSkin 与 Paper Profile API 同步皮肤 | @@ -97,7 +95,6 @@ SkinBridge 默认关闭。使用前需启用 `modules.yml` 中的 `modules.skin- - `config.yml` - 语言选择 - 管理模式行为 - - JEI 同步开关 - 掉落控制 - TPSBar 模式 - TPA 请求、预热、冷却和音效 @@ -190,8 +187,8 @@ Windows 可使用: ./gradlew shadowJar ./gradlew build ./gradlew deployToPaper12111 -./gradlew deployToPaper2612 ./gradlew deployToPaper262 +./gradlew deployToLeaves262 ``` ## 本地测试服 @@ -201,16 +198,15 @@ Windows 可使用: | 测试服 | 端口 | 部署任务 | 启动脚本 | | --- | --- | --- | --- | | Paper 1.21.11 | `25566` | `deployToPaper12111` | `test-server/paper-1.21.11/start.bat` | -| Paper 26.1.2 | `25565` | `deployToPaper2612` | `test-server/paper-26.1.2/start.bat` | -| Paper 26.2 | 以本地配置为准 | `deployToPaper262` | `test-server/paper-26.2/start.bat` | +| Paper 26.2 | `25567` | `deployToPaper262` | `test-server/paper-26.2/start.bat` | +| Leaves 26.2 | `25568` | `deployToLeaves262` | `test-server/leaves-26.2/start.bat` | -IDEA 运行配置会在启动测试服前自动执行对应部署任务。部署任务会替换 `EssentialsC*.jar`,并删除 `plugins/EssentialsC` 数据目录,以便测试新增默认配置和语言文本。 +IDEA 运行配置会先构建插件,再由启动脚本复制最新 JAR,并保留插件数据以便测试升级流程。手动执行部署任务会替换 `EssentialsC*.jar` 并删除 `plugins/EssentialsC` 数据目录,用于测试全新安装及默认配置生成。 ## 开发说明 - 使用 `paperweight-userdev` 进行 Paper 开发 - 使用 Paper Lifecycle Command API 注册命令,避免直接反射 Bukkit CommandMap -- 涉及 NMS 的功能通过 `compat-api` + `versions/v<版本>` 适配模块隔离,主插件只依赖稳定接口;当前包含 `1.21.11`、`26.1.2`、`26.2` - 运行时通过 `modules.yml` 控制模块加载,命令始终注册并按模块状态执行门禁 - 普通 push/PR 会执行构建与测试,版本标签继续生成 GitHub Release diff --git a/build.gradle b/build.gradle index de6f2ca..b10274a 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,8 @@ plugins { } group = 'cn.infstar' -version = providers.gradleProperty('pluginVersion').orElse('1.3.0').get() +def pluginVersion = providers.gradleProperty('pluginVersion').orElse('1.3.0').get() +version = pluginVersion repositories { mavenCentral() @@ -23,7 +24,6 @@ repositories { dependencies { paperweight.paperDevBundle('1.21.11-R0.1-SNAPSHOT') compileOnly 'net.luckperms:api:5.5' - implementation project(':compat-api') implementation 'com.google.code.gson:gson:2.11.0' testImplementation platform('org.junit:junit-bom:5.13.4') testImplementation 'org.junit.jupiter:junit-jupiter' @@ -52,9 +52,9 @@ tasks.withType(Test).configureEach { } tasks.named('processResources', ProcessResources).configure { - inputs.property('version', project.version) + inputs.property('version', pluginVersion) filesMatching('paper-plugin.yml') { - expand('version': project.version) + expand('version': pluginVersion) } } @@ -65,19 +65,9 @@ tasks.named('jar').configure { tasks.named('shadowJar', ShadowJar).configure { group = 'build' description = '构建包含全部运行期可开关模块的 EssentialsC 插件。' - archiveFileName.set("EssentialsC-${project.version}.jar") + archiveFileName.set("EssentialsC-${pluginVersion}.jar") configurations = [project.configurations.runtimeClasspath] relocate 'com.google.gson', 'cn.infstar.essentialsC.libs.gson' - - [ - project(':versions:v1_21_11'), - project(':versions:v26_1_2'), - project(':versions:v26_2') - ].each { adapterProject -> - def adapterJar = adapterProject.tasks.named('jar') - dependsOn(adapterJar) - from(adapterJar.flatMap { it.archiveFile }.map { archiveFile -> zipTree(archiveFile) }) - } } tasks.named('assemble').configure { @@ -137,5 +127,5 @@ def registerTestServerDeployTask = { String taskName, String serverPath, String } registerTestServerDeployTask('deployToPaper12111', 'test-server/paper-1.21.11', 'Paper 1.21.11') -registerTestServerDeployTask('deployToPaper2612', 'test-server/paper-26.1.2', 'Paper 26.1.2') registerTestServerDeployTask('deployToPaper262', 'test-server/paper-26.2', 'Paper 26.2') +registerTestServerDeployTask('deployToLeaves262', 'test-server/leaves-26.2', 'Leaves 26.2') diff --git a/compat-api/build.gradle b/compat-api/build.gradle deleted file mode 100644 index 34a58c1..0000000 --- a/compat-api/build.gradle +++ /dev/null @@ -1,23 +0,0 @@ -plugins { - id 'java-library' -} - -repositories { - mavenCentral() - maven { - name = 'papermc' - url = uri('https://repo.papermc.io/repository/maven-public/') - } -} - -dependencies { - compileOnly 'io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT' -} - -java { - toolchain.languageVersion.set(JavaLanguageVersion.of(21)) -} - -tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' -} diff --git a/compat-api/src/main/java/cn/infstar/essentialsC/compat/jei/JeiRecipeSyncAdapter.java b/compat-api/src/main/java/cn/infstar/essentialsC/compat/jei/JeiRecipeSyncAdapter.java deleted file mode 100644 index ac44e7b..0000000 --- a/compat-api/src/main/java/cn/infstar/essentialsC/compat/jei/JeiRecipeSyncAdapter.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.infstar.essentialsC.compat.jei; - -import org.bukkit.entity.Player; - -import java.util.logging.Logger; - -public interface JeiRecipeSyncAdapter { - - void sendFabricRecipeSync(Player player, Logger logger, boolean debug) throws Exception; - - void sendNeoForgeRecipeSync(Player player, Logger logger, boolean debug) throws Exception; - - void clearCache(); -} diff --git a/compat-api/src/main/java/cn/infstar/essentialsC/compat/jei/RecipeGrouping.java b/compat-api/src/main/java/cn/infstar/essentialsC/compat/jei/RecipeGrouping.java deleted file mode 100644 index a8fe22b..0000000 --- a/compat-api/src/main/java/cn/infstar/essentialsC/compat/jei/RecipeGrouping.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.infstar.essentialsC.compat.jei; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -public final class RecipeGrouping { - - private RecipeGrouping() { - } - - public static Map> groupBy(Iterable values, - Function keyFunction) { - Map> grouped = new LinkedHashMap<>(); - for (V value : values) { - grouped.computeIfAbsent(keyFunction.apply(value), ignored -> new ArrayList<>()).add(value); - } - return grouped; - } -} diff --git a/scripts/smoke-test-paper.sh b/scripts/smoke-test-paper.sh index 5be57f6..2e053bd 100644 --- a/scripts/smoke-test-paper.sh +++ b/scripts/smoke-test-paper.sh @@ -4,6 +4,14 @@ set -euo pipefail paper_version="${1:?必须提供 Paper 版本}" workspace="$(mktemp -d)" server_pid="" +plugin_jar="$(find build/libs -maxdepth 1 -type f -name 'EssentialsC-*.jar' -print -quit)" +if [[ -z "$plugin_jar" ]]; then + echo "未找到 EssentialsC 构建产物。" >&2 + exit 1 +fi +plugin_filename="$(basename "$plugin_jar")" +plugin_version="${plugin_filename#EssentialsC-}" +plugin_version="${plugin_version%.jar}" cleanup() { if [[ -n "$server_pid" ]] && kill -0 "$server_pid" 2>/dev/null; then @@ -20,7 +28,7 @@ download_url="$(curl --fail --silent --show-error --location "$metadata_url" \ curl --fail --silent --show-error --location "$download_url" --output "$workspace/paper.jar" mkdir -p "$workspace/plugins" -cp build/libs/EssentialsC-*.jar "$workspace/plugins/EssentialsC.jar" +cp "$plugin_jar" "$workspace/plugins/EssentialsC.jar" printf 'eula=true\n' > "$workspace/eula.txt" printf 'online-mode=false\nserver-port=0\nenable-query=false\n' > "$workspace/server.properties" @@ -31,7 +39,7 @@ printf 'online-mode=false\nserver-port=0\nenable-query=false\n' > "$workspace/se server_pid=$! for _ in $(seq 1 120); do - if grep -Fq 'EssentialsC v1.3.0 已启用' "$workspace/server.log"; then + if grep -Fq "EssentialsC v${plugin_version} 已启用" "$workspace/server.log"; then if grep -Eq 'UnsupportedClassVersionError|Error occurred while enabling EssentialsC|Could not load.*EssentialsC' "$workspace/server.log"; then cat "$workspace/server.log" exit 1 diff --git a/settings.gradle b/settings.gradle index feb3aa0..e64f93f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -20,8 +20,3 @@ plugins { } rootProject.name = 'EssentialsC' - -include 'compat-api' -include 'versions:v1_21_11' -include 'versions:v26_1_2' -include 'versions:v26_2' diff --git a/src/main/java/cn/infstar/essentialsC/EssentialsC.java b/src/main/java/cn/infstar/essentialsC/EssentialsC.java index 7369314..8ec59a7 100644 --- a/src/main/java/cn/infstar/essentialsC/EssentialsC.java +++ b/src/main/java/cn/infstar/essentialsC/EssentialsC.java @@ -7,7 +7,6 @@ import cn.infstar.essentialsC.commands.CommandRegistry; import cn.infstar.essentialsC.commands.HelpCommand; import cn.infstar.essentialsC.commands.PaperCommand; import cn.infstar.essentialsC.commands.VanishCommand; -import cn.infstar.essentialsC.listeners.JeiRecipeSyncListener; import cn.infstar.essentialsC.listeners.MobDropListener; import cn.infstar.essentialsC.listeners.MobDropMenuListener; import cn.infstar.essentialsC.listeners.ShulkerBoxListener; @@ -23,7 +22,6 @@ import org.bukkit.command.CommandSender; import org.bukkit.command.TabCompleter; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; -import org.bukkit.plugin.messaging.Messenger; import org.bukkit.plugin.java.JavaPlugin; import io.papermc.paper.command.brigadier.BasicCommand; import io.papermc.paper.command.brigadier.CommandSourceStack; @@ -47,7 +45,6 @@ public final class EssentialsC extends JavaPlugin { private TpsBarService tpsBarManager; private ShulkerBoxListener shulkerBoxListener; private BlocksMenuCommand blocksMenuCommandListener; - private JeiRecipeSyncListener jeiRecipeSyncListener; private MobDropListener mobDropListener; private MobDropMenuListener mobDropMenuListener; private VanishListener vanishListener; @@ -95,7 +92,6 @@ public final class EssentialsC extends JavaPlugin { } VanishCommand.clearAll(this); unregisterRuntimeListeners(); - unregisterPluginChannels(); getLogger().info("EssentialsC 已禁用。"); } @@ -142,7 +138,6 @@ public final class EssentialsC extends JavaPlugin { refreshMaintenance(); refreshTpsBar(); refreshBlocks(); - refreshJeiSync(); refreshMobDrops(); refreshSkinBridge(); } @@ -269,24 +264,6 @@ public final class EssentialsC extends JavaPlugin { setModuleStatus("便捷方块", true, "命令和潜影盒监听器已启用"); } - private void refreshJeiSync() { - if (jeiRecipeSyncListener != null) { - HandlerList.unregisterAll(jeiRecipeSyncListener); - jeiRecipeSyncListener = null; - } - unregisterPluginChannels(); - - if (!moduleManager.isEnabled(ModuleManager.JEI_SYNC)) { - setModuleStatus("JEI 同步", false, "已禁用"); - return; - } - - registerPluginChannels(); - jeiRecipeSyncListener = new JeiRecipeSyncListener(this); - getServer().getPluginManager().registerEvents(jeiRecipeSyncListener, this); - setModuleStatus("JEI 同步", true, "插件消息通道已注册"); - } - private void refreshMobDrops() { if (!moduleManager.isEnabled(ModuleManager.MOB_DROPS)) { if (mobDropListener != null) { @@ -334,24 +311,11 @@ public final class EssentialsC extends JavaPlugin { setModuleStatus("皮肤桥接", true, skinBridgeManager.getModuleDetail()); } - private void registerPluginChannels() { - Messenger messenger = getServer().getMessenger(); - messenger.registerOutgoingPluginChannel(this, "fabric:recipe_sync"); - messenger.registerOutgoingPluginChannel(this, "neoforge:recipe_content"); - } - - private void unregisterPluginChannels() { - Messenger messenger = getServer().getMessenger(); - messenger.unregisterOutgoingPluginChannel(this, "fabric:recipe_sync"); - messenger.unregisterOutgoingPluginChannel(this, "neoforge:recipe_content"); - } - private void unregisterRuntimeListeners() { unregisterListener(adminModeManager); unregisterListener(maintenanceListener); unregisterListener(shulkerBoxListener); unregisterListener(blocksMenuCommandListener); - unregisterListener(jeiRecipeSyncListener); unregisterListener(mobDropListener); unregisterListener(mobDropMenuListener); unregisterListener(vanishListener); diff --git a/src/main/java/cn/infstar/essentialsC/FeatureConfigManager.java b/src/main/java/cn/infstar/essentialsC/FeatureConfigManager.java index 0777f7f..83af5a0 100644 --- a/src/main/java/cn/infstar/essentialsC/FeatureConfigManager.java +++ b/src/main/java/cn/infstar/essentialsC/FeatureConfigManager.java @@ -44,6 +44,7 @@ public final class FeatureConfigManager { boolean mainChanged = mainConfig != loadedMainConfig; mainChanged |= migrateLegacyMainConfig(mainConfig); mainChanged |= migrateLegacyDebugSettings(mainConfig); + mainChanged |= removeRetiredJeiSettings(mainConfig); if (mainConfig.getInt("config-version", 0) != MAIN_CONFIG_VERSION) { mainConfig.set("config-version", MAIN_CONFIG_VERSION); mainChanged = true; @@ -135,6 +136,16 @@ public final class FeatureConfigManager { return true; } + private boolean removeRetiredJeiSettings(FileConfiguration mainConfig) { + if (!mainConfig.contains("jei-sync", true)) { + return false; + } + + mainConfig.set("jei-sync", null); + plugin.getLogger().info("已从 config.yml 移除停用的 JEI 配方同步配置。"); + return true; + } + private void copySection(ConfigurationSection source, FileConfiguration target) { if (source == null) { return; diff --git a/src/main/java/cn/infstar/essentialsC/ModuleManager.java b/src/main/java/cn/infstar/essentialsC/ModuleManager.java index 1772d4c..d44f5ee 100644 --- a/src/main/java/cn/infstar/essentialsC/ModuleManager.java +++ b/src/main/java/cn/infstar/essentialsC/ModuleManager.java @@ -1,5 +1,6 @@ package cn.infstar.essentialsC; +import cn.infstar.essentialsC.util.AtomicYamlWriter; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; @@ -17,7 +18,6 @@ public final class ModuleManager { public static final String BLOCKS = "blocks"; public static final String ADMIN_MODE = "admin-mode"; public static final String TPSBAR = "tpsbar"; - public static final String JEI_SYNC = "jei-sync"; public static final String MOB_DROPS = "mob-drops"; public static final String MAINTENANCE = "maintenance"; public static final String SKIN_BRIDGE = "skin-bridge"; @@ -28,7 +28,6 @@ public final class ModuleManager { DEFAULT_MODULES.put(BLOCKS, true); DEFAULT_MODULES.put(ADMIN_MODE, true); DEFAULT_MODULES.put(TPSBAR, true); - DEFAULT_MODULES.put(JEI_SYNC, true); DEFAULT_MODULES.put(MOB_DROPS, false); DEFAULT_MODULES.put(MAINTENANCE, true); DEFAULT_MODULES.put(SKIN_BRIDGE, false); @@ -58,11 +57,23 @@ public final class ModuleManager { throw new IllegalStateException("无法加载 modules.yml,请修复配置格式后重试。", exception); } modulesConfig = loaded; + boolean removedJeiSync = modulesConfig.contains("modules.jei-sync", true); + if (removedJeiSync) { + modulesConfig.set("modules.jei-sync", null); + } modulesConfig.addDefault("config-version", CURRENT_CONFIG_VERSION); for (Map.Entry module : DEFAULT_MODULES.entrySet()) { modulesConfig.addDefault(path(module.getKey()), module.getValue()); } modulesConfig.options().copyDefaults(true); + if (removedJeiSync) { + try { + AtomicYamlWriter.save(modulesConfig, modulesFile); + plugin.getLogger().info("已从 modules.yml 移除停用的 JEI 配方同步配置。"); + } catch (IOException exception) { + plugin.getLogger().warning("清理 modules.yml 中的 JEI 配置失败: " + exception.getMessage()); + } + } } public boolean isEnabled(String moduleKey) { diff --git a/src/main/java/cn/infstar/essentialsC/listeners/JeiRecipeSyncListener.java b/src/main/java/cn/infstar/essentialsC/listeners/JeiRecipeSyncListener.java deleted file mode 100644 index c963a17..0000000 --- a/src/main/java/cn/infstar/essentialsC/listeners/JeiRecipeSyncListener.java +++ /dev/null @@ -1,171 +0,0 @@ -package cn.infstar.essentialsC.listeners; - -import cn.infstar.essentialsC.EssentialsC; -import cn.infstar.essentialsC.compat.jei.JeiRecipeSyncAdapter; -import io.papermc.paper.event.server.ServerResourcesReloadedEvent; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; - -import java.util.Locale; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Level; - -/** - * 为 Fabric / NeoForge 客户端补发配方同步数据,修复 1.21.2+ 的 JEI 配方显示问题。 - */ -public class JeiRecipeSyncListener implements Listener { - - private static final Set WARNED_UNSUPPORTED_VERSIONS = ConcurrentHashMap.newKeySet(); - - private final EssentialsC plugin; - private final boolean debug; - private final boolean sendPlayerMessage; - private final int brandCheckDelayTicks; - private final JeiRecipeSyncAdapter adapter; - - public JeiRecipeSyncListener(EssentialsC plugin) { - this.plugin = plugin; - - FileConfiguration config = plugin.getConfig(); - config.addDefault("jei-sync.brand-check-delay-ticks", 20); - config.options().copyDefaults(true); - - this.debug = config.getBoolean("debug", false); - this.sendPlayerMessage = config.getBoolean("jei-sync.send-player-message", true); - this.brandCheckDelayTicks = Math.max(0, config.getInt("jei-sync.brand-check-delay-ticks", 20)); - this.adapter = loadAdapter(); - } - - @EventHandler - public void onPlayerJoin(PlayerJoinEvent event) { - if (adapter == null) { - if (debug) { - plugin.getLogger().warning("当前服务端版本没有可用的 JEI 配方同步适配器。"); - } - return; - } - - Player player = event.getPlayer(); - plugin.getServer().getScheduler().runTaskLater(plugin, () -> detectAndSync(player), brandCheckDelayTicks); - } - - @EventHandler - public void onServerResourcesReloaded(ServerResourcesReloadedEvent event) { - if (adapter != null) { - adapter.clearCache(); - } - } - - private void detectAndSync(Player player) { - if (!player.isOnline()) { - return; - } - - String clientBrand = player.getClientBrandName(); - - if (debug) { - plugin.getLogger().info("JEI 客户端检测: player=" + player.getName() - + ", brand=" + (clientBrand == null || clientBrand.isBlank() ? "unknown" : clientBrand) - + ", delayTicks=" + brandCheckDelayTicks); - } - - if (clientBrand == null || clientBrand.isEmpty()) { - if (debug) { - plugin.getLogger().info("跳过 " + player.getName() + ":客户端品牌为空"); - } - return; - } - - String brandLower = clientBrand.toLowerCase(Locale.ROOT); - if (brandLower.contains("fabric")) { - if (debug) { - plugin.getLogger().info("检测到 Fabric 客户端,开始发送配方同步..."); - } - sendPlayerMessage(player, "Fabric"); - sendFabricRecipeSync(player); - return; - } - - if (brandLower.contains("neoforge") || brandLower.contains("forge")) { - if (debug) { - plugin.getLogger().info("检测到 NeoForge/Forge 客户端,开始发送配方同步..."); - } - sendPlayerMessage(player, "NeoForge"); - sendNeoForgeRecipeSync(player); - return; - } - - if (debug) { - plugin.getLogger().info("跳过 " + player.getName() + ":不支持的客户端类型 '" + clientBrand + "'"); - } - } - - private void sendPlayerMessage(Player player, String clientType) { - if (!sendPlayerMessage) { - return; - } - - String messageKey; - if (clientType.equalsIgnoreCase("fabric")) { - messageKey = "messages.jei-sync-fabric"; - } else if (clientType.equalsIgnoreCase("neoforge")) { - messageKey = "messages.jei-sync-neoforge"; - } else { - return; - } - - player.sendMessage(EssentialsC.getLangManager().getPrefixedComponent(messageKey)); - } - - private void sendFabricRecipeSync(Player player) { - try { - adapter.sendFabricRecipeSync(player, plugin.getLogger(), debug); - } catch (Exception e) { - plugin.getLogger().warning("发送 Fabric 配方同步失败: " + e.getMessage()); - if (debug) { - plugin.getLogger().log(Level.WARNING, "Fabric 配方同步异常详情", e); - } - } - } - - private void sendNeoForgeRecipeSync(Player player) { - try { - adapter.sendNeoForgeRecipeSync(player, plugin.getLogger(), debug); - } catch (Exception e) { - plugin.getLogger().warning("发送 NeoForge 配方同步失败: " + e.getMessage()); - if (debug) { - plugin.getLogger().log(Level.WARNING, "NeoForge 配方同步异常详情", e); - } - } - } - - private JeiRecipeSyncAdapter loadAdapter() { - String versionKey = plugin.getServer().getBukkitVersion().split("-")[0] - .replace('.', '_'); - String className = "cn.infstar.essentialsC.v" + versionKey + ".jei.JeiRecipeSyncAdapterImpl"; - try { - Class adapterClass = Class.forName(className); - Object instance = adapterClass.getDeclaredConstructor().newInstance(); - if (instance instanceof JeiRecipeSyncAdapter jeiRecipeSyncAdapter) { - if (debug) { - plugin.getLogger().info("已加载 JEI 配方同步适配器: " + className); - } - return jeiRecipeSyncAdapter; - } - plugin.getLogger().warning("JEI 配方同步适配器类型无效: " + className); - } catch (ReflectiveOperationException | LinkageError e) { - if (WARNED_UNSUPPORTED_VERSIONS.add(versionKey)) { - plugin.getLogger().warning("当前 Paper 版本 " + plugin.getServer().getBukkitVersion() - + " 没有可用的 JEI 配方同步适配器;仅 JEI 同步功能已停用。"); - } - if (debug) { - plugin.getLogger().warning("加载 JEI 配方同步适配器失败: " + className + " (" + e.getMessage() + ")"); - } - } - return null; - } -} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 5617769..54774a8 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -17,16 +17,10 @@ admin-mode: actionbar: interval-ticks: 40 -# JEI 配方同步修复;是否加载由 modules.yml 控制。 -jei-sync: - # 玩家加入后延迟多少 tick 检测客户端品牌。 - brand-check-delay-ticks: 20 - send-player-message: true - # 生物掉落 mob-drops: enderman: - # 允许末影人掉落搬运的方块。 + # 控制该生物死亡时是否掉落物品(经验值正常掉落);当前仅支持末影人。 allow-drops: true # TPA 传送请求 diff --git a/src/main/resources/lang/en_US.yml b/src/main/resources/lang/en_US.yml index 55e9813..072e7d2 100644 --- a/src/main/resources/lang/en_US.yml +++ b/src/main/resources/lang/en_US.yml @@ -68,9 +68,6 @@ messages: shulkerbox-unstack-first: '<#FF3300>Error: <#FF7E5E>Please unstack the shulker box before quick opening it.' shulkerbox-open-failed: '<#FF3300>Error: <#FF7E5E>Failed to open the shulker box. Please try again.' shulkerbox-session-invalid: '<#FF3300>Error: <#FF7E5E>The shulker box session became invalid. Saving was stopped to prevent item duplication.' - jei-sync-fabric: '<#00FB9A>Synchronizing recipes for your Fabric client…' - jei-sync-neoforge: '<#00FB9A>Synchronizing recipes for your NeoForge client…' - help: title: '<#00FB9A>EssentialsC Command Help' version: 'Plugin version: {version}' diff --git a/src/main/resources/lang/zh_CN.yml b/src/main/resources/lang/zh_CN.yml index e4ca994..d5f3669 100644 --- a/src/main/resources/lang/zh_CN.yml +++ b/src/main/resources/lang/zh_CN.yml @@ -68,9 +68,6 @@ messages: shulkerbox-unstack-first: '<#FF3300>错误: <#FF7E5E>请先将潜影盒拆分为单个后再快捷打开.' shulkerbox-open-failed: '<#FF3300>错误: <#FF7E5E>潜影盒打开失败,请稍后重试.' shulkerbox-session-invalid: '<#FF3300>错误: <#FF7E5E>潜影盒会话异常,已停止保存以避免物品复制.' - jei-sync-fabric: '<#00FB9A>正在为 Fabric 客户端同步配方…' - jei-sync-neoforge: '<#00FB9A>正在为 NeoForge 客户端同步配方…' - help: title: '<#00FB9A>EssentialsC 命令帮助' version: '插件版本: {version}' diff --git a/src/main/resources/modules.yml b/src/main/resources/modules.yml index ddcaf23..5b06b68 100644 --- a/src/main/resources/modules.yml +++ b/src/main/resources/modules.yml @@ -18,9 +18,6 @@ modules: tpsbar: # 插件版 TPSBar。检测到服务端原生命令时会自动避免冲突。 enabled: true - jei-sync: - # Fabric / NeoForge JEI 配方同步兼容修复。 - enabled: true mob-drops: # 末影人掉落控制菜单和监听器;具体掉落状态可在游戏内切换。 enabled: false diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 6e6a2f8..21d04b5 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -155,7 +155,7 @@ permissions: description: 允许通过 Shift+右键快捷打开潜影盒 default: op essentialsc.mobdrops.enderman: - description: 允许控制末影人掉落方块 + description: 允许控制末影人死亡时是否掉落物品 default: op essentialsc.*: description: 授予 EssentialsC 的全部权限 diff --git a/src/test/java/cn/infstar/essentialsC/ConfigurationResourcesTest.java b/src/test/java/cn/infstar/essentialsC/ConfigurationResourcesTest.java index 2bac035..fd55e70 100644 --- a/src/test/java/cn/infstar/essentialsC/ConfigurationResourcesTest.java +++ b/src/test/java/cn/infstar/essentialsC/ConfigurationResourcesTest.java @@ -12,6 +12,7 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -56,6 +57,16 @@ class ConfigurationResourcesTest { assertEquals(chinese.getKeys(true), english.getKeys(true)); } + @Test + void retiredJeiFeatureIsAbsentFromResources() throws Exception { + assertFalse(loadResource("config.yml").contains("jei-sync", true)); + assertFalse(loadResource("modules.yml").contains("modules.jei-sync", true)); + assertFalse(loadResource("lang/zh_CN.yml").contains("messages.jei-sync-fabric", true)); + assertFalse(loadResource("lang/zh_CN.yml").contains("messages.jei-sync-neoforge", true)); + assertFalse(loadResource("lang/en_US.yml").contains("messages.jei-sync-fabric", true)); + assertFalse(loadResource("lang/en_US.yml").contains("messages.jei-sync-neoforge", true)); + } + @Test void miniMessageTagNamesAreNormalized() { assertEquals("文本", LangManager.normalizeMiniMessageTags("文本")); diff --git a/src/test/java/cn/infstar/essentialsC/compat/jei/RecipeGroupingTest.java b/src/test/java/cn/infstar/essentialsC/compat/jei/RecipeGroupingTest.java deleted file mode 100644 index 5338122..0000000 --- a/src/test/java/cn/infstar/essentialsC/compat/jei/RecipeGroupingTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.infstar.essentialsC.compat.jei; - -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class RecipeGroupingTest { - - @Test - void groupsValuesInOnePassAndPreservesOrder() { - List recipes = List.of("crafting:first", "smelting:second", "crafting:third"); - - Map> grouped = RecipeGrouping.groupBy( - recipes, value -> value.substring(0, value.indexOf(':'))); - - assertEquals(List.of("crafting", "smelting"), grouped.keySet().stream().toList()); - assertEquals(List.of("crafting:first", "crafting:third"), grouped.get("crafting")); - assertEquals(List.of("smelting:second"), grouped.get("smelting")); - } -} diff --git a/versions/v1_21_11/build.gradle b/versions/v1_21_11/build.gradle deleted file mode 100644 index bb17e82..0000000 --- a/versions/v1_21_11/build.gradle +++ /dev/null @@ -1,27 +0,0 @@ -plugins { - id 'java-library' - id 'io.papermc.paperweight.userdev' -} - -repositories { - mavenCentral() - maven { - name = 'papermc' - url = uri('https://repo.papermc.io/repository/maven-public/') - } -} - -dependencies { - api project(':compat-api') - paperweight.paperDevBundle('1.21.11-R0.1-SNAPSHOT') -} - -java { - toolchain.languageVersion.set(JavaLanguageVersion.of(21)) -} - -paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.getMOJANG_PRODUCTION() - -tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' -} diff --git a/versions/v1_21_11/src/main/java/cn/infstar/essentialsC/v1_21_11/jei/JeiRecipeSyncAdapterImpl.java b/versions/v1_21_11/src/main/java/cn/infstar/essentialsC/v1_21_11/jei/JeiRecipeSyncAdapterImpl.java deleted file mode 100644 index b2ef5d5..0000000 --- a/versions/v1_21_11/src/main/java/cn/infstar/essentialsC/v1_21_11/jei/JeiRecipeSyncAdapterImpl.java +++ /dev/null @@ -1,269 +0,0 @@ -package cn.infstar.essentialsC.v1_21_11.jei; - -import cn.infstar.essentialsC.compat.jei.JeiRecipeSyncAdapter; -import cn.infstar.essentialsC.compat.jei.RecipeGrouping; -import io.netty.buffer.Unpooled; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; -import net.minecraft.network.protocol.common.custom.DiscardedPayload; -import net.minecraft.resources.Identifier; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.item.crafting.RecipeHolder; -import net.minecraft.world.item.crafting.RecipeMap; -import net.minecraft.world.item.crafting.RecipeSerializer; -import org.bukkit.craftbukkit.entity.CraftPlayer; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.logging.Logger; - -public final class JeiRecipeSyncAdapterImpl implements JeiRecipeSyncAdapter { - - private DiscardedPayload fabricPayloadCache; - private DiscardedPayload neoForgePayloadCache; - - @Override - @SuppressWarnings({"unchecked", "deprecation"}) - public void sendFabricRecipeSync(Player player, Logger logger, boolean debug) { - ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); - MinecraftServer server = serverPlayer.level().getServer(); - if (server == null) { - if (debug) { - logger.warning("服务端实例为 null"); - } - return; - } - if (fabricPayloadCache != null) { - serverPlayer.connection.send(new ClientboundCustomPayloadPacket(fabricPayloadCache)); - return; - } - - RecipeMap recipeMap = server.getRecipeManager().recipes; - if (debug) { - logger.info("开始构建 Fabric 配方数据"); - } - - var list = new ArrayList(); - var seen = new HashSet>(); - var groupedRecipes = RecipeGrouping.groupBy( - recipeMap.values(), holder -> holder.value().getSerializer()); - - for (RecipeSerializer serializer : BuiltInRegistries.RECIPE_SERIALIZER) { - if (!seen.add(serializer)) { - continue; - } - - List> recipes = groupedRecipes.get(serializer); - - if (recipes != null && !recipes.isEmpty()) { - RecipeSerializer entrySerializer = recipes.get(0).value().getSerializer(); - list.add(new FabricRecipeEntry(entrySerializer, recipes)); - } - } - - var payload = new FabricRecipeSyncPayload(list); - if (debug) { - logger.info("Fabric 配方条目数: " + list.size()); - } - - RegistryFriendlyByteBuf buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), server.registryAccess()); - byte[] bytes; - try { - getFabricCodec().encode(buffer, payload); - bytes = new byte[buffer.writerIndex()]; - buffer.getBytes(0, bytes); - } finally { - buffer.release(); - } - - Identifier id = Identifier.fromNamespaceAndPath("fabric", "recipe_sync"); - DiscardedPayload discardedPayload = new DiscardedPayload(id, bytes); - fabricPayloadCache = discardedPayload; - serverPlayer.connection.send(new ClientboundCustomPayloadPacket(discardedPayload)); - - if (debug) { - logger.info("已发送 Fabric 配方同步 [" + id + "], 大小: " + bytes.length + " bytes"); - } - } - - @Override - @SuppressWarnings({"unchecked", "deprecation"}) - public void sendNeoForgeRecipeSync(Player player, Logger logger, boolean debug) { - ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); - MinecraftServer server = serverPlayer.level().getServer(); - if (server == null) { - if (debug) { - logger.warning("服务端实例为 null"); - } - return; - } - if (neoForgePayloadCache != null) { - serverPlayer.connection.send(new ClientboundCustomPayloadPacket(neoForgePayloadCache)); - sendTags(serverPlayer, server); - return; - } - - RecipeMap recipeMap = server.getRecipeManager().recipes; - if (debug) { - logger.info("开始构建 NeoForge 配方数据"); - } - - java.util.List> allRecipeTypes = - BuiltInRegistries.RECIPE_TYPE.stream().toList(); - if (debug) { - logger.info("NeoForge 配方类型数: " + allRecipeTypes.size()); - } - - var payload = createNeoForgePayload(allRecipeTypes, recipeMap); - RegistryFriendlyByteBuf buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), server.registryAccess()); - byte[] bytes; - try { - getNeoForgeStreamCodec().encode(buffer, payload); - bytes = new byte[buffer.writerIndex()]; - buffer.getBytes(0, bytes); - } finally { - buffer.release(); - } - - Identifier id = Identifier.fromNamespaceAndPath("neoforge", "recipe_content"); - DiscardedPayload discardedPayload = new DiscardedPayload(id, bytes); - neoForgePayloadCache = discardedPayload; - serverPlayer.connection.send(new ClientboundCustomPayloadPacket(discardedPayload)); - sendTags(serverPlayer, server); - - if (debug) { - logger.info("已发送 NeoForge 配方同步 [" + id + "], 大小: " + bytes.length + " bytes"); - } - } - - @Override - public void clearCache() { - fabricPayloadCache = null; - neoForgePayloadCache = null; - } - - private void sendTags(ServerPlayer serverPlayer, MinecraftServer server) { - serverPlayer.connection.send(new net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket( - net.minecraft.tags.TagNetworkSerialization.serializeTagsToNetwork(server.registries()) - )); - } - - @SuppressWarnings({"unchecked", "deprecation"}) - private net.minecraft.network.codec.StreamCodec getFabricCodec() { - return FabricRecipeEntry.CODEC.apply(net.minecraft.network.codec.ByteBufCodecs.list()) - .map(FabricRecipeSyncPayload::new, FabricRecipeSyncPayload::entries); - } - - private NeoForgeRecipeSyncPayload createNeoForgePayload( - java.util.List> recipeTypes, - RecipeMap recipeMap - ) { - var recipeTypeSet = new java.util.HashSet<>(recipeTypes); - if (recipeTypeSet.isEmpty()) { - return new NeoForgeRecipeSyncPayload(recipeTypeSet, java.util.List.of()); - } - - var recipeSubset = recipeMap.values().stream() - .filter(h -> recipeTypeSet.contains(h.value().getType())) - .toList(); - return new NeoForgeRecipeSyncPayload(recipeTypeSet, recipeSubset); - } - - @SuppressWarnings({"unchecked", "deprecation"}) - private net.minecraft.network.codec.StreamCodec getNeoForgeStreamCodec() { - return net.minecraft.network.codec.StreamCodec.composite( - net.minecraft.network.codec.ByteBufCodecs.registry(net.minecraft.core.registries.Registries.RECIPE_TYPE) - .apply(net.minecraft.network.codec.ByteBufCodecs.collection(java.util.HashSet::new)), - NeoForgeRecipeSyncPayload::recipeTypes, - RecipeHolder.STREAM_CODEC.apply(net.minecraft.network.codec.ByteBufCodecs.list()), - NeoForgeRecipeSyncPayload::recipes, - NeoForgeRecipeSyncPayload::new - ); - } - - @SuppressWarnings("deprecation") - private static class FabricRecipeEntry { - final Object serializer; - final List> recipes; - - FabricRecipeEntry(Object serializer, List> recipes) { - this.serializer = serializer; - this.recipes = recipes; - } - - static final net.minecraft.network.codec.StreamCodec CODEC = - net.minecraft.network.codec.StreamCodec.ofMember( - FabricRecipeEntry::write, - FabricRecipeEntry::read - ); - - @SuppressWarnings("unchecked") - private static FabricRecipeEntry read(RegistryFriendlyByteBuf buf) { - Identifier recipeSerializerId = buf.readIdentifier(); - RecipeSerializer recipeSerializer = BuiltInRegistries.RECIPE_SERIALIZER.getValue(recipeSerializerId); - - if (recipeSerializer == null) { - throw new RuntimeException("Tried syncing unsupported packet serializer '" + recipeSerializerId + "'!"); - } - - int count = buf.readVarInt(); - var list = new ArrayList>(); - for (int i = 0; i < count; i++) { - net.minecraft.resources.ResourceKey> id = - buf.readResourceKey(net.minecraft.core.registries.Registries.RECIPE); - - try { - var streamCodecMethod = recipeSerializer.getClass().getMethod("streamCodec"); - var streamCodec = streamCodecMethod.invoke(recipeSerializer); - net.minecraft.world.item.crafting.Recipe recipe = - ((net.minecraft.network.codec.StreamCodec>) streamCodec) - .decode(buf); - list.add(new RecipeHolder<>(id, recipe)); - } catch (Exception e) { - throw new RuntimeException("Failed to decode recipe: " + e.getMessage(), e); - } - } - - return new FabricRecipeEntry(recipeSerializer, list); - } - - private void write(RegistryFriendlyByteBuf buf) { - try { - var getKeyMethod = BuiltInRegistries.RECIPE_SERIALIZER.getClass().getMethod("getKey", Object.class); - Identifier identifier = (Identifier) getKeyMethod.invoke(BuiltInRegistries.RECIPE_SERIALIZER, this.serializer); - buf.writeIdentifier(identifier); - } catch (Exception e) { - throw new RuntimeException("Failed to get serializer key: " + e.getMessage(), e); - } - - buf.writeVarInt(this.recipes.size()); - - try { - var streamCodecMethod = this.serializer.getClass().getMethod("streamCodec"); - @SuppressWarnings("unchecked") - var codec = (net.minecraft.network.codec.StreamCodec>) - streamCodecMethod.invoke(this.serializer); - - for (RecipeHolder recipe : this.recipes) { - buf.writeResourceKey(recipe.id()); - codec.encode(buf, recipe.value()); - } - } catch (Exception e) { - throw new RuntimeException("Failed to encode recipe: " + e.getMessage(), e); - } - } - } - - private record FabricRecipeSyncPayload(List entries) { - } - - private record NeoForgeRecipeSyncPayload( - java.util.Set> recipeTypes, - java.util.List> recipes - ) { - } -} diff --git a/versions/v26_1_2/build.gradle b/versions/v26_1_2/build.gradle deleted file mode 100644 index 502ab86..0000000 --- a/versions/v26_1_2/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -import org.gradle.api.attributes.java.TargetJvmVersion - -plugins { - id 'java-library' - id 'io.papermc.paperweight.userdev' -} - -repositories { - mavenCentral() - maven { - name = 'papermc' - url = uri('https://repo.papermc.io/repository/maven-public/') - } -} - -dependencies { - api project(':compat-api') - paperweight.paperDevBundle('26.1.2.build.74-stable') -} - -java { - toolchain.languageVersion.set(JavaLanguageVersion.of(25)) -} - -configurations.configureEach { - attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25) -} - -paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.getMOJANG_PRODUCTION() - -tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' - options.release.set(21) -} diff --git a/versions/v26_1_2/src/main/java/cn/infstar/essentialsC/v26_1_2/jei/JeiRecipeSyncAdapterImpl.java b/versions/v26_1_2/src/main/java/cn/infstar/essentialsC/v26_1_2/jei/JeiRecipeSyncAdapterImpl.java deleted file mode 100644 index 5c8f4f9..0000000 --- a/versions/v26_1_2/src/main/java/cn/infstar/essentialsC/v26_1_2/jei/JeiRecipeSyncAdapterImpl.java +++ /dev/null @@ -1,270 +0,0 @@ -package cn.infstar.essentialsC.v26_1_2.jei; - -import cn.infstar.essentialsC.compat.jei.JeiRecipeSyncAdapter; -import cn.infstar.essentialsC.compat.jei.RecipeGrouping; -import io.netty.buffer.Unpooled; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; -import net.minecraft.network.protocol.common.custom.DiscardedPayload; -import net.minecraft.resources.Identifier; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.item.crafting.RecipeHolder; -import net.minecraft.world.item.crafting.RecipeMap; -import net.minecraft.world.item.crafting.RecipeSerializer; -import org.bukkit.craftbukkit.entity.CraftPlayer; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.logging.Logger; - -public final class JeiRecipeSyncAdapterImpl implements JeiRecipeSyncAdapter { - - private DiscardedPayload fabricPayloadCache; - private DiscardedPayload neoForgePayloadCache; - - @Override - @SuppressWarnings({"unchecked", "deprecation"}) - public void sendFabricRecipeSync(Player player, Logger logger, boolean debug) { - ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); - MinecraftServer server = serverPlayer.level().getServer(); - if (server == null) { - if (debug) { - logger.warning("服务端实例为 null"); - } - return; - } - if (fabricPayloadCache != null) { - serverPlayer.connection.send(new ClientboundCustomPayloadPacket(fabricPayloadCache)); - return; - } - - RecipeMap recipeMap = server.getRecipeManager().recipes; - if (debug) { - logger.info("开始构建 Fabric 配方数据"); - } - - var list = new ArrayList(); - var seen = new HashSet>(); - var groupedRecipes = RecipeGrouping.groupBy( - recipeMap.values(), holder -> holder.value().getSerializer()); - - for (RecipeSerializer serializer : BuiltInRegistries.RECIPE_SERIALIZER) { - if (!seen.add(serializer)) { - continue; - } - - List> recipes = groupedRecipes.get(serializer); - - if (recipes != null && !recipes.isEmpty()) { - RecipeSerializer entrySerializer = recipes.get(0).value().getSerializer(); - list.add(new FabricRecipeEntry(entrySerializer, recipes)); - } - } - - var payload = new FabricRecipeSyncPayload(list); - if (debug) { - logger.info("Fabric 配方条目数: " + list.size()); - } - - RegistryFriendlyByteBuf buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), server.registryAccess()); - byte[] bytes; - try { - getFabricCodec().encode(buffer, payload); - bytes = new byte[buffer.writerIndex()]; - buffer.getBytes(0, bytes); - } finally { - buffer.release(); - } - - Identifier id = Identifier.fromNamespaceAndPath("fabric", "recipe_sync"); - DiscardedPayload discardedPayload = new DiscardedPayload(id, bytes); - fabricPayloadCache = discardedPayload; - serverPlayer.connection.send(new ClientboundCustomPayloadPacket(discardedPayload)); - - if (debug) { - logger.info("已发送 Fabric 配方同步 [" + id + "], 大小: " + bytes.length + " bytes"); - } - } - - @Override - @SuppressWarnings({"unchecked", "deprecation"}) - public void sendNeoForgeRecipeSync(Player player, Logger logger, boolean debug) { - ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); - MinecraftServer server = serverPlayer.level().getServer(); - if (server == null) { - if (debug) { - logger.warning("服务端实例为 null"); - } - return; - } - if (neoForgePayloadCache != null) { - serverPlayer.connection.send(new ClientboundCustomPayloadPacket(neoForgePayloadCache)); - sendTags(serverPlayer, server); - return; - } - - RecipeMap recipeMap = server.getRecipeManager().recipes; - if (debug) { - logger.info("开始构建 NeoForge 配方数据"); - } - - java.util.List> allRecipeTypes = - BuiltInRegistries.RECIPE_TYPE.stream().toList(); - if (debug) { - logger.info("NeoForge 配方类型数: " + allRecipeTypes.size()); - } - - var payload = createNeoForgePayload(allRecipeTypes, recipeMap); - RegistryFriendlyByteBuf buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), server.registryAccess()); - byte[] bytes; - try { - getNeoForgeStreamCodec().encode(buffer, payload); - bytes = new byte[buffer.writerIndex()]; - buffer.getBytes(0, bytes); - } finally { - buffer.release(); - } - - Identifier id = Identifier.fromNamespaceAndPath("neoforge", "recipe_content"); - DiscardedPayload discardedPayload = new DiscardedPayload(id, bytes); - neoForgePayloadCache = discardedPayload; - serverPlayer.connection.send(new ClientboundCustomPayloadPacket(discardedPayload)); - sendTags(serverPlayer, server); - - if (debug) { - logger.info("已发送 NeoForge 配方同步 [" + id + "], 大小: " + bytes.length + " bytes"); - } - } - - @Override - public void clearCache() { - fabricPayloadCache = null; - neoForgePayloadCache = null; - } - - private void sendTags(ServerPlayer serverPlayer, MinecraftServer server) { - serverPlayer.connection.send(new net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket( - net.minecraft.tags.TagNetworkSerialization.serializeTagsToNetwork(server.registries()) - )); - } - - @SuppressWarnings({"unchecked", "deprecation"}) - private net.minecraft.network.codec.StreamCodec getFabricCodec() { - return FabricRecipeEntry.CODEC.apply(net.minecraft.network.codec.ByteBufCodecs.list()) - .map(FabricRecipeSyncPayload::new, FabricRecipeSyncPayload::entries); - } - - private NeoForgeRecipeSyncPayload createNeoForgePayload( - java.util.List> recipeTypes, - RecipeMap recipeMap - ) { - var recipeTypeSet = new java.util.HashSet<>(recipeTypes); - if (recipeTypeSet.isEmpty()) { - return new NeoForgeRecipeSyncPayload(recipeTypeSet, java.util.List.of()); - } - - var recipeSubset = recipeMap.values().stream() - .filter(h -> recipeTypeSet.contains(h.value().getType())) - .toList(); - return new NeoForgeRecipeSyncPayload(recipeTypeSet, recipeSubset); - } - - @SuppressWarnings({"unchecked", "deprecation"}) - private net.minecraft.network.codec.StreamCodec getNeoForgeStreamCodec() { - return net.minecraft.network.codec.StreamCodec.composite( - net.minecraft.network.codec.ByteBufCodecs.registry(net.minecraft.core.registries.Registries.RECIPE_TYPE) - .apply(net.minecraft.network.codec.ByteBufCodecs.collection(java.util.HashSet::new)), - NeoForgeRecipeSyncPayload::recipeTypes, - RecipeHolder.STREAM_CODEC.apply(net.minecraft.network.codec.ByteBufCodecs.list()), - NeoForgeRecipeSyncPayload::recipes, - NeoForgeRecipeSyncPayload::new - ); - } - - @SuppressWarnings("deprecation") - private static class FabricRecipeEntry { - final Object serializer; - final List> recipes; - - FabricRecipeEntry(Object serializer, List> recipes) { - this.serializer = serializer; - this.recipes = recipes; - } - - static final net.minecraft.network.codec.StreamCodec CODEC = - net.minecraft.network.codec.StreamCodec.ofMember( - FabricRecipeEntry::write, - FabricRecipeEntry::read - ); - - @SuppressWarnings("unchecked") - private static FabricRecipeEntry read(RegistryFriendlyByteBuf buf) { - Identifier recipeSerializerId = buf.readIdentifier(); - RecipeSerializer recipeSerializer = BuiltInRegistries.RECIPE_SERIALIZER.getValue(recipeSerializerId); - - if (recipeSerializer == null) { - throw new RuntimeException("Tried syncing unsupported packet serializer '" + recipeSerializerId + "'!"); - } - - int count = buf.readVarInt(); - var list = new ArrayList>(); - for (int i = 0; i < count; i++) { - net.minecraft.resources.ResourceKey> id = - buf.readResourceKey(net.minecraft.core.registries.Registries.RECIPE); - - try { - var streamCodecMethod = recipeSerializer.getClass().getMethod("streamCodec"); - var streamCodec = streamCodecMethod.invoke(recipeSerializer); - net.minecraft.world.item.crafting.Recipe recipe = - ((net.minecraft.network.codec.StreamCodec>) streamCodec) - .decode(buf); - list.add(new RecipeHolder<>(id, recipe)); - } catch (Exception e) { - throw new RuntimeException("Failed to decode recipe: " + e.getMessage(), e); - } - } - - return new FabricRecipeEntry(recipeSerializer, list); - } - - private void write(RegistryFriendlyByteBuf buf) { - try { - var getKeyMethod = BuiltInRegistries.RECIPE_SERIALIZER.getClass().getMethod("getKey", Object.class); - Identifier identifier = (Identifier) getKeyMethod.invoke(BuiltInRegistries.RECIPE_SERIALIZER, this.serializer); - buf.writeIdentifier(identifier); - } catch (Exception e) { - throw new RuntimeException("Failed to get serializer key: " + e.getMessage(), e); - } - - buf.writeVarInt(this.recipes.size()); - - try { - var streamCodecMethod = this.serializer.getClass().getMethod("streamCodec"); - @SuppressWarnings("unchecked") - var codec = (net.minecraft.network.codec.StreamCodec>) - streamCodecMethod.invoke(this.serializer); - - for (RecipeHolder recipe : this.recipes) { - buf.writeResourceKey(recipe.id()); - codec.encode(buf, recipe.value()); - } - } catch (Exception e) { - throw new RuntimeException("Failed to encode recipe: " + e.getMessage(), e); - } - } - } - - private record FabricRecipeSyncPayload(List entries) { - } - - private record NeoForgeRecipeSyncPayload( - java.util.Set> recipeTypes, - java.util.List> recipes - ) { - } -} - diff --git a/versions/v26_2/build.gradle b/versions/v26_2/build.gradle deleted file mode 100644 index 84968a3..0000000 --- a/versions/v26_2/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -import org.gradle.api.attributes.java.TargetJvmVersion - -plugins { - id 'java-library' - id 'io.papermc.paperweight.userdev' -} - -repositories { - mavenCentral() - maven { - name = 'papermc' - url = uri('https://repo.papermc.io/repository/maven-public/') - } -} - -dependencies { - api project(':compat-api') - paperweight.paperDevBundle('26.2.build.87-stable') -} - -java { - toolchain.languageVersion.set(JavaLanguageVersion.of(25)) -} - -configurations.configureEach { - attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25) -} - -paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.getMOJANG_PRODUCTION() - -tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' - options.release.set(21) -} diff --git a/versions/v26_2/src/main/java/cn/infstar/essentialsC/v26_2/jei/JeiRecipeSyncAdapterImpl.java b/versions/v26_2/src/main/java/cn/infstar/essentialsC/v26_2/jei/JeiRecipeSyncAdapterImpl.java deleted file mode 100644 index a2cee3e..0000000 --- a/versions/v26_2/src/main/java/cn/infstar/essentialsC/v26_2/jei/JeiRecipeSyncAdapterImpl.java +++ /dev/null @@ -1,270 +0,0 @@ -package cn.infstar.essentialsC.v26_2.jei; - -import cn.infstar.essentialsC.compat.jei.JeiRecipeSyncAdapter; -import cn.infstar.essentialsC.compat.jei.RecipeGrouping; -import io.netty.buffer.Unpooled; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; -import net.minecraft.network.protocol.common.custom.DiscardedPayload; -import net.minecraft.resources.Identifier; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.item.crafting.RecipeHolder; -import net.minecraft.world.item.crafting.RecipeMap; -import net.minecraft.world.item.crafting.RecipeSerializer; -import org.bukkit.craftbukkit.entity.CraftPlayer; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.logging.Logger; - -public final class JeiRecipeSyncAdapterImpl implements JeiRecipeSyncAdapter { - - private DiscardedPayload fabricPayloadCache; - private DiscardedPayload neoForgePayloadCache; - - @Override - @SuppressWarnings({"unchecked", "deprecation"}) - public void sendFabricRecipeSync(Player player, Logger logger, boolean debug) { - ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); - MinecraftServer server = serverPlayer.level().getServer(); - if (server == null) { - if (debug) { - logger.warning("服务端实例为 null"); - } - return; - } - if (fabricPayloadCache != null) { - serverPlayer.connection.send(new ClientboundCustomPayloadPacket(fabricPayloadCache)); - return; - } - - RecipeMap recipeMap = server.getRecipeManager().recipes; - if (debug) { - logger.info("开始构建 Fabric 配方数据"); - } - - var list = new ArrayList(); - var seen = new HashSet>(); - var groupedRecipes = RecipeGrouping.groupBy( - recipeMap.values(), holder -> holder.value().getSerializer()); - - for (RecipeSerializer serializer : BuiltInRegistries.RECIPE_SERIALIZER) { - if (!seen.add(serializer)) { - continue; - } - - List> recipes = groupedRecipes.get(serializer); - - if (recipes != null && !recipes.isEmpty()) { - RecipeSerializer entrySerializer = recipes.get(0).value().getSerializer(); - list.add(new FabricRecipeEntry(entrySerializer, recipes)); - } - } - - var payload = new FabricRecipeSyncPayload(list); - if (debug) { - logger.info("Fabric 配方条目数: " + list.size()); - } - - RegistryFriendlyByteBuf buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), server.registryAccess()); - byte[] bytes; - try { - getFabricCodec().encode(buffer, payload); - bytes = new byte[buffer.writerIndex()]; - buffer.getBytes(0, bytes); - } finally { - buffer.release(); - } - - Identifier id = Identifier.fromNamespaceAndPath("fabric", "recipe_sync"); - DiscardedPayload discardedPayload = new DiscardedPayload(id, bytes); - fabricPayloadCache = discardedPayload; - serverPlayer.connection.send(new ClientboundCustomPayloadPacket(discardedPayload)); - - if (debug) { - logger.info("已发送 Fabric 配方同步 [" + id + "], 大小: " + bytes.length + " bytes"); - } - } - - @Override - @SuppressWarnings({"unchecked", "deprecation"}) - public void sendNeoForgeRecipeSync(Player player, Logger logger, boolean debug) { - ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); - MinecraftServer server = serverPlayer.level().getServer(); - if (server == null) { - if (debug) { - logger.warning("服务端实例为 null"); - } - return; - } - if (neoForgePayloadCache != null) { - serverPlayer.connection.send(new ClientboundCustomPayloadPacket(neoForgePayloadCache)); - sendTags(serverPlayer, server); - return; - } - - RecipeMap recipeMap = server.getRecipeManager().recipes; - if (debug) { - logger.info("开始构建 NeoForge 配方数据"); - } - - java.util.List> allRecipeTypes = - BuiltInRegistries.RECIPE_TYPE.stream().toList(); - if (debug) { - logger.info("NeoForge 配方类型数: " + allRecipeTypes.size()); - } - - var payload = createNeoForgePayload(allRecipeTypes, recipeMap); - RegistryFriendlyByteBuf buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), server.registryAccess()); - byte[] bytes; - try { - getNeoForgeStreamCodec().encode(buffer, payload); - bytes = new byte[buffer.writerIndex()]; - buffer.getBytes(0, bytes); - } finally { - buffer.release(); - } - - Identifier id = Identifier.fromNamespaceAndPath("neoforge", "recipe_content"); - DiscardedPayload discardedPayload = new DiscardedPayload(id, bytes); - neoForgePayloadCache = discardedPayload; - serverPlayer.connection.send(new ClientboundCustomPayloadPacket(discardedPayload)); - sendTags(serverPlayer, server); - - if (debug) { - logger.info("已发送 NeoForge 配方同步 [" + id + "], 大小: " + bytes.length + " bytes"); - } - } - - @Override - public void clearCache() { - fabricPayloadCache = null; - neoForgePayloadCache = null; - } - - private void sendTags(ServerPlayer serverPlayer, MinecraftServer server) { - serverPlayer.connection.send(new net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket( - net.minecraft.tags.TagNetworkSerialization.serializeTagsToNetwork(server.registries()) - )); - } - - @SuppressWarnings({"unchecked", "deprecation"}) - private net.minecraft.network.codec.StreamCodec getFabricCodec() { - return FabricRecipeEntry.CODEC.apply(net.minecraft.network.codec.ByteBufCodecs.list()) - .map(FabricRecipeSyncPayload::new, FabricRecipeSyncPayload::entries); - } - - private NeoForgeRecipeSyncPayload createNeoForgePayload( - java.util.List> recipeTypes, - RecipeMap recipeMap - ) { - var recipeTypeSet = new java.util.HashSet<>(recipeTypes); - if (recipeTypeSet.isEmpty()) { - return new NeoForgeRecipeSyncPayload(recipeTypeSet, java.util.List.of()); - } - - var recipeSubset = recipeMap.values().stream() - .filter(h -> recipeTypeSet.contains(h.value().getType())) - .toList(); - return new NeoForgeRecipeSyncPayload(recipeTypeSet, recipeSubset); - } - - @SuppressWarnings({"unchecked", "deprecation"}) - private net.minecraft.network.codec.StreamCodec getNeoForgeStreamCodec() { - return net.minecraft.network.codec.StreamCodec.composite( - net.minecraft.network.codec.ByteBufCodecs.registry(net.minecraft.core.registries.Registries.RECIPE_TYPE) - .apply(net.minecraft.network.codec.ByteBufCodecs.collection(java.util.HashSet::new)), - NeoForgeRecipeSyncPayload::recipeTypes, - RecipeHolder.STREAM_CODEC.apply(net.minecraft.network.codec.ByteBufCodecs.list()), - NeoForgeRecipeSyncPayload::recipes, - NeoForgeRecipeSyncPayload::new - ); - } - - @SuppressWarnings("deprecation") - private static class FabricRecipeEntry { - final Object serializer; - final List> recipes; - - FabricRecipeEntry(Object serializer, List> recipes) { - this.serializer = serializer; - this.recipes = recipes; - } - - static final net.minecraft.network.codec.StreamCodec CODEC = - net.minecraft.network.codec.StreamCodec.ofMember( - FabricRecipeEntry::write, - FabricRecipeEntry::read - ); - - @SuppressWarnings("unchecked") - private static FabricRecipeEntry read(RegistryFriendlyByteBuf buf) { - Identifier recipeSerializerId = buf.readIdentifier(); - RecipeSerializer recipeSerializer = BuiltInRegistries.RECIPE_SERIALIZER.getValue(recipeSerializerId); - - if (recipeSerializer == null) { - throw new RuntimeException("Tried syncing unsupported packet serializer '" + recipeSerializerId + "'!"); - } - - int count = buf.readVarInt(); - var list = new ArrayList>(); - for (int i = 0; i < count; i++) { - net.minecraft.resources.ResourceKey> id = - buf.readResourceKey(net.minecraft.core.registries.Registries.RECIPE); - - try { - var streamCodecMethod = recipeSerializer.getClass().getMethod("streamCodec"); - var streamCodec = streamCodecMethod.invoke(recipeSerializer); - net.minecraft.world.item.crafting.Recipe recipe = - ((net.minecraft.network.codec.StreamCodec>) streamCodec) - .decode(buf); - list.add(new RecipeHolder<>(id, recipe)); - } catch (Exception e) { - throw new RuntimeException("Failed to decode recipe: " + e.getMessage(), e); - } - } - - return new FabricRecipeEntry(recipeSerializer, list); - } - - private void write(RegistryFriendlyByteBuf buf) { - try { - var getKeyMethod = BuiltInRegistries.RECIPE_SERIALIZER.getClass().getMethod("getKey", Object.class); - Identifier identifier = (Identifier) getKeyMethod.invoke(BuiltInRegistries.RECIPE_SERIALIZER, this.serializer); - buf.writeIdentifier(identifier); - } catch (Exception e) { - throw new RuntimeException("Failed to get serializer key: " + e.getMessage(), e); - } - - buf.writeVarInt(this.recipes.size()); - - try { - var streamCodecMethod = this.serializer.getClass().getMethod("streamCodec"); - @SuppressWarnings("unchecked") - var codec = (net.minecraft.network.codec.StreamCodec>) - streamCodecMethod.invoke(this.serializer); - - for (RecipeHolder recipe : this.recipes) { - buf.writeResourceKey(recipe.id()); - codec.encode(buf, recipe.value()); - } - } catch (Exception e) { - throw new RuntimeException("Failed to encode recipe: " + e.getMessage(), e); - } - } - } - - private record FabricRecipeSyncPayload(List entries) { - } - - private record NeoForgeRecipeSyncPayload( - java.util.Set> recipeTypes, - java.util.List> recipes - ) { - } -} -