release: 发布 1.3.0
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<String, Boolean> 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) {
|
||||
|
||||
@@ -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<String> 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;
|
||||
}
|
||||
}
|
||||
@@ -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 传送请求
|
||||
|
||||
@@ -68,9 +68,6 @@ messages:
|
||||
shulkerbox-unstack-first: '<#FF3300>Error:</#FF3300> <#FF7E5E>Please unstack the shulker box before quick opening it.</#FF7E5E>'
|
||||
shulkerbox-open-failed: '<#FF3300>Error:</#FF3300> <#FF7E5E>Failed to open the shulker box. Please try again.</#FF7E5E>'
|
||||
shulkerbox-session-invalid: '<#FF3300>Error:</#FF3300> <#FF7E5E>The shulker box session became invalid. Saving was stopped to prevent item duplication.</#FF7E5E>'
|
||||
jei-sync-fabric: '<#00FB9A>Synchronizing recipes for your <bold>Fabric</bold> client…</#00FB9A>'
|
||||
jei-sync-neoforge: '<#00FB9A>Synchronizing recipes for your <bold>NeoForge</bold> client…</#00FB9A>'
|
||||
|
||||
help:
|
||||
title: '<#00FB9A><bold>EssentialsC</bold></#00FB9A> <gray>Command Help</gray>'
|
||||
version: '<gray>Plugin version: <white>{version}</white></gray>'
|
||||
|
||||
@@ -68,9 +68,6 @@ messages:
|
||||
shulkerbox-unstack-first: '<#FF3300>错误:</#FF3300> <#FF7E5E>请先将潜影盒拆分为单个后再快捷打开.</#FF7E5E>'
|
||||
shulkerbox-open-failed: '<#FF3300>错误:</#FF3300> <#FF7E5E>潜影盒打开失败,请稍后重试.</#FF7E5E>'
|
||||
shulkerbox-session-invalid: '<#FF3300>错误:</#FF3300> <#FF7E5E>潜影盒会话异常,已停止保存以避免物品复制.</#FF7E5E>'
|
||||
jei-sync-fabric: '<#00FB9A>正在为 <bold>Fabric</bold> 客户端同步配方…</#00FB9A>'
|
||||
jei-sync-neoforge: '<#00FB9A>正在为 <bold>NeoForge</bold> 客户端同步配方…</#00FB9A>'
|
||||
|
||||
help:
|
||||
title: '<#00FB9A><bold>EssentialsC</bold></#00FB9A> <gray>命令帮助</gray>'
|
||||
version: '<gray>插件版本: <white>{version}</white></gray>'
|
||||
|
||||
@@ -18,9 +18,6 @@ modules:
|
||||
tpsbar:
|
||||
# 插件版 TPSBar。检测到服务端原生命令时会自动避免冲突。
|
||||
enabled: true
|
||||
jei-sync:
|
||||
# Fabric / NeoForge JEI 配方同步兼容修复。
|
||||
enabled: true
|
||||
mob-drops:
|
||||
# 末影人掉落控制菜单和监听器;具体掉落状态可在游戏内切换。
|
||||
enabled: false
|
||||
|
||||
@@ -155,7 +155,7 @@ permissions:
|
||||
description: 允许通过 Shift+右键快捷打开潜影盒
|
||||
default: op
|
||||
essentialsc.mobdrops.enderman:
|
||||
description: 允许控制末影人掉落方块
|
||||
description: 允许控制末影人死亡时是否掉落物品
|
||||
default: op
|
||||
essentialsc.*:
|
||||
description: 授予 EssentialsC 的全部权限
|
||||
|
||||
@@ -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("<gray>文本</gray>", LangManager.normalizeMiniMessageTags("<GRAY>文本</GRAY>"));
|
||||
|
||||
@@ -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<String> recipes = List.of("crafting:first", "smelting:second", "crafting:third");
|
||||
|
||||
Map<String, List<String>> 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"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user