diff --git a/README.md b/README.md index c1bba64..6efde15 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ - Shift + 右键快捷打开潜影盒 - 潜影盒交互保护,尽量避免刷物品、吞物品和嵌套放入问题 - 管理模式独立背包、装备栏与状态切换 -- 维护模式:替换 MOTD,并阻止无绕过权限的玩家进入 +- 维护模式:替换 MOTD、登录拦截、白名单放行和管理员拦截通知 - Enderman 掉落方块控制 - JEI 配方同步修复 @@ -66,9 +66,9 @@ | `tpsbar` | 开启 | 插件版 TPSBar,仍受 `config.yml` 中 `tpsbar.mode` 控制 | | `jei-sync` | 开启 | Fabric / NeoForge JEI 配方同步修复 | | `mob-drops` | 关闭 | 末影人掉落控制,默认关闭以保留过去标准版行为 | -| `maintenance` | 开启 | 维护模式命令、MOTD 替换和登录拦截 | +| `maintenance` | 开启 | 维护模式命令、MOTD 替换、登录拦截、白名单和拦截通知 | -修改模块开关后建议重启服务器,使命令注册表和监听器状态完全刷新。`/essc reload` 可以刷新配置和已注册命令的执行检查,但无法从 Bukkit 命令表中真正热移除或新增直连命令。 +修改模块开关后可先使用 `/essc reload` 刷新运行期服务与监听器状态。由于 Bukkit 命令表不适合在运行期完整热增删,若模块是在启动时关闭的,对应直连命令可能仍需重启后才会注册;通过 `/essc <子命令>` 入口通常可立即按新的模块状态执行。 ## 安装说明 @@ -97,6 +97,8 @@ - 维护踢出提示 - 维护 BossBar - 绕过权限 + - 维护白名单 + - 拦截通知权限 - `lang/zh_CN.yml`、`lang/en_US.yml` - 命令反馈 - 帮助信息 @@ -124,6 +126,9 @@ essentialsc.command.vanish essentialsc.command.seen essentialsc.command.admin essentialsc.command.tpsbar +essentialsc.command.maintenance +essentialsc.maintenance.bypass +essentialsc.maintenance.notify essentialsc.shulkerbox.open essentialsc.mobdrops.enderman essentialsc.* @@ -170,6 +175,7 @@ IDEA 运行配置会在启动测试服前自动执行对应部署任务。部署 ## 开发说明 - 使用 `paperweight-userdev` 进行 Paper 开发 +- 使用 Paper Lifecycle Command API 注册命令,避免直接反射 Bukkit CommandMap - 运行时通过 `modules.yml` 控制模块加载,命令与监听器按模块状态注册 - 发布流程基于 GitHub Actions 和 Gradle Wrapper diff --git a/build.gradle b/build.gradle index 9849ee9..da956b4 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group = 'cn.infstar' -version = '1.4.0' +version = '1.5.0' repositories { mavenCentral() diff --git a/src/main/java/cn/infstar/essentialsC/EssentialsC.java b/src/main/java/cn/infstar/essentialsC/EssentialsC.java index 2f5ec77..9242a9c 100644 --- a/src/main/java/cn/infstar/essentialsC/EssentialsC.java +++ b/src/main/java/cn/infstar/essentialsC/EssentialsC.java @@ -4,16 +4,31 @@ import cn.infstar.essentialsC.admin.AdminModeManager; import cn.infstar.essentialsC.commands.BaseCommand; 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; +import cn.infstar.essentialsC.listeners.VanishListener; import cn.infstar.essentialsC.maintenance.MaintenanceListener; import cn.infstar.essentialsC.maintenance.MaintenanceManager; +import cn.infstar.essentialsC.tpsbar.TpsBarManager; import cn.infstar.essentialsC.tpsbar.TpsBarService; -import org.bukkit.Bukkit; -import org.bukkit.command.Command; 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; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; -import java.lang.reflect.Field; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; public final class EssentialsC extends JavaPlugin { @@ -21,34 +36,25 @@ public final class EssentialsC extends JavaPlugin { private ModuleManager moduleManager; private AdminModeManager adminModeManager; private MaintenanceManager maintenanceManager; + private MaintenanceListener maintenanceListener; private TpsBarService tpsBarManager; + private ShulkerBoxListener shulkerBoxListener; + private JeiRecipeSyncListener jeiRecipeSyncListener; + private MobDropListener mobDropListener; + private MobDropMenuListener mobDropMenuListener; + private VanishListener vanishListener; + private boolean commandsRegistered; + private final Map moduleStatus = new LinkedHashMap<>(); @Override public void onEnable() { langManager = new LangManager(this); moduleManager = new ModuleManager(this); - if (moduleManager.isEnabled(ModuleManager.ADMIN_MODE)) { - adminModeManager = new AdminModeManager(this); - getServer().getPluginManager().registerEvents(adminModeManager, this); - } - - if (moduleManager.isEnabled(ModuleManager.MAINTENANCE)) { - maintenanceManager = new MaintenanceManager(this); - getServer().getPluginManager().registerEvents(new MaintenanceListener(this), this); - } - - if (moduleManager.isEnabled(ModuleManager.TPSBAR)) { - tpsBarManager = createOptionalService("cn.infstar.essentialsC.tpsbar.TpsBarManager", TpsBarService.class); - } - if (tpsBarManager instanceof Listener listener) { - getServer().getPluginManager().registerEvents(listener, this); - } - registerPluginChannels(); - registerListeners(); + reloadRuntimeModules(); registerCommands(); - getLogger().info("EssentialsC 已启用,版本: " + getDescription().getVersion()); + logStartupSummary(); } @Override @@ -62,6 +68,9 @@ public final class EssentialsC extends JavaPlugin { if (maintenanceManager != null) { maintenanceManager.shutdown(); } + VanishCommand.clearAll(this); + unregisterRuntimeListeners(); + unregisterPluginChannels(); getLogger().info("EssentialsC 已禁用。"); } @@ -85,68 +94,230 @@ public final class EssentialsC extends JavaPlugin { return tpsBarManager; } - private void registerPluginChannels() { - if (!moduleManager.isEnabled(ModuleManager.JEI_SYNC)) { + public void reloadRuntimeModules() { + moduleStatus.clear(); + refreshPlayer(); + refreshAdminMode(); + refreshMaintenance(); + refreshTpsBar(); + refreshBlocks(); + refreshJeiSync(); + refreshMobDrops(); + } + + private void refreshPlayer() { + if (!moduleManager.isEnabled(ModuleManager.PLAYER)) { + VanishCommand.clearAll(this); + if (vanishListener != null) { + HandlerList.unregisterAll(vanishListener); + vanishListener = null; + } + setModuleStatus("玩家功能", false, "已禁用"); return; } - org.bukkit.plugin.messaging.Messenger messenger = getServer().getMessenger(); + + if (vanishListener == null) { + vanishListener = new VanishListener(this); + getServer().getPluginManager().registerEvents(vanishListener, this); + } + setModuleStatus("玩家功能", true, "命令可用"); + } + + private void refreshAdminMode() { + if (!moduleManager.isEnabled(ModuleManager.ADMIN_MODE)) { + if (adminModeManager != null) { + adminModeManager.shutdown(); + HandlerList.unregisterAll(adminModeManager); + adminModeManager = null; + } + setModuleStatus("管理模式", false, "已禁用"); + return; + } + + if (adminModeManager == null) { + adminModeManager = new AdminModeManager(this); + getServer().getPluginManager().registerEvents(adminModeManager, this); + } + setModuleStatus("管理模式", true, "监听器已注册"); + } + + private void refreshMaintenance() { + if (!moduleManager.isEnabled(ModuleManager.MAINTENANCE)) { + if (maintenanceManager != null) { + maintenanceManager.shutdown(); + maintenanceManager = null; + } + if (maintenanceListener != null) { + HandlerList.unregisterAll(maintenanceListener); + maintenanceListener = null; + } + setModuleStatus("维护模式", false, "已禁用"); + return; + } + + if (maintenanceManager == null) { + maintenanceManager = new MaintenanceManager(this); + } else { + maintenanceManager.reload(); + } + if (maintenanceListener == null) { + maintenanceListener = new MaintenanceListener(this); + getServer().getPluginManager().registerEvents(maintenanceListener, this); + } + setModuleStatus("维护模式", true, maintenanceManager.isEnabled() ? "当前开启" : "当前关闭"); + } + + private void refreshTpsBar() { + if (!moduleManager.isEnabled(ModuleManager.TPSBAR)) { + if (tpsBarManager != null) { + tpsBarManager.shutdown(); + if (tpsBarManager instanceof Listener listener) { + HandlerList.unregisterAll(listener); + } + tpsBarManager = null; + } + setModuleStatus("TPSBar", false, "模块已禁用"); + return; + } + + if (tpsBarManager == null) { + tpsBarManager = new TpsBarManager(this); + if (tpsBarManager instanceof Listener listener) { + getServer().getPluginManager().registerEvents(listener, this); + } + } else { + tpsBarManager.reloadSettings(); + } + if (tpsBarManager == null) { + setModuleStatus("TPSBar", false, "初始化失败"); + } else if (!tpsBarManager.isPluginCommandEnabled()) { + setModuleStatus("TPSBar", false, tpsBarManager.isNativeCommandAvailable() ? "使用服务端原生命令" : "插件命令关闭"); + } else { + setModuleStatus("TPSBar", true, "插件命令已启用"); + } + } + + private void refreshBlocks() { + if (!moduleManager.isEnabled(ModuleManager.BLOCKS)) { + if (shulkerBoxListener != null) { + HandlerList.unregisterAll(shulkerBoxListener); + shulkerBoxListener = null; + } + setModuleStatus("便捷方块", false, "已禁用"); + return; + } + + if (shulkerBoxListener == null) { + shulkerBoxListener = new ShulkerBoxListener(this); + getServer().getPluginManager().registerEvents(shulkerBoxListener, this); + } + 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) { + HandlerList.unregisterAll(mobDropListener); + mobDropListener = null; + } + if (mobDropMenuListener != null) { + HandlerList.unregisterAll(mobDropMenuListener); + mobDropMenuListener = null; + } + setModuleStatus("生物掉落", false, "已禁用"); + return; + } + + if (mobDropListener == null) { + mobDropListener = new MobDropListener(this); + getServer().getPluginManager().registerEvents(mobDropListener, this); + } else { + mobDropListener.reload(); + } + if (mobDropMenuListener == null) { + mobDropMenuListener = new MobDropMenuListener(this); + } + setModuleStatus("生物掉落", true, "末影人掉落控制已启用"); + } + + private void registerPluginChannels() { + Messenger messenger = getServer().getMessenger(); messenger.registerOutgoingPluginChannel(this, "fabric:recipe_sync"); messenger.registerOutgoingPluginChannel(this, "neoforge:recipe_content"); } - private void registerListeners() { - if (moduleManager.isEnabled(ModuleManager.BLOCKS) - && registerListener("cn.infstar.essentialsC.listeners.ShulkerBoxListener")) { - getLogger().info("- 潜影盒模块"); - } + private void unregisterPluginChannels() { + Messenger messenger = getServer().getMessenger(); + messenger.unregisterOutgoingPluginChannel(this, "fabric:recipe_sync"); + messenger.unregisterOutgoingPluginChannel(this, "neoforge:recipe_content"); + } - if (moduleManager.isEnabled(ModuleManager.JEI_SYNC) - && registerListener("cn.infstar.essentialsC.listeners.JeiRecipeSyncListener")) { - getLogger().info("- JEI 配方同步"); - } - - if (moduleManager.isEnabled(ModuleManager.MOB_DROPS) - && registerListener("cn.infstar.essentialsC.listeners.MobDropListener")) { - createOptionalInstance("cn.infstar.essentialsC.listeners.MobDropMenuListener"); - getLogger().info("- 生物掉落控制"); + private void unregisterRuntimeListeners() { + unregisterListener(adminModeManager); + unregisterListener(maintenanceListener); + unregisterListener(shulkerBoxListener); + unregisterListener(jeiRecipeSyncListener); + unregisterListener(mobDropListener); + unregisterListener(mobDropMenuListener); + unregisterListener(vanishListener); + if (tpsBarManager instanceof Listener listener) { + unregisterListener(listener); } } - private boolean registerListener(String className) { - try { - Class listenerClass = Class.forName(className); - Object listenerInstance = listenerClass.getConstructor(EssentialsC.class).newInstance(this); - getServer().getPluginManager().registerEvents((org.bukkit.event.Listener) listenerInstance, this); - return true; - } catch (Exception ignored) { - return false; + private void unregisterListener(Listener listener) { + if (listener != null) { + HandlerList.unregisterAll(listener); } } - private void createOptionalInstance(String className) { - try { - Class targetClass = Class.forName(className); - targetClass.getConstructor(EssentialsC.class).newInstance(this); - } catch (Exception ignored) { - } + private void setModuleStatus(String moduleName, boolean enabled, String detail) { + moduleStatus.put(moduleName, (enabled ? "启用" : "关闭") + " - " + detail); } - private T createOptionalService(String className, Class serviceType) { - try { - Class targetClass = Class.forName(className); - Object instance = targetClass.getConstructor(EssentialsC.class).newInstance(this); - return serviceType.cast(instance); - } catch (Exception ignored) { - return null; + private void logStartupSummary() { + long enabledCount = moduleStatus.values().stream() + .filter(status -> status.startsWith("启用")) + .count(); + long disabledCount = moduleStatus.size() - enabledCount; + + getLogger().info("EssentialsC v" + getDescription().getVersion() + + " 已启用 | 模块: " + enabledCount + " 启用, " + disabledCount + " 关闭"); + + if (!getConfig().getBoolean("debug", false)) { + return; + } + + getLogger().info("模块明细:"); + for (Map.Entry entry : moduleStatus.entrySet()) { + getLogger().info(" " + entry.getKey() + ": " + entry.getValue()); } } private void registerCommands() { - try { - Field bukkitCommandMap = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - bukkitCommandMap.setAccessible(true); - org.bukkit.command.CommandMap commandMap = (org.bukkit.command.CommandMap) bukkitCommandMap.get(Bukkit.getServer()); - + if (commandsRegistered) { + return; + } + commandsRegistered = true; + getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> { for (CommandRegistry.CommandSpec spec : CommandRegistry.getCommandSpecs()) { if (!spec.standalone()) { continue; @@ -155,61 +326,51 @@ public final class EssentialsC extends JavaPlugin { if (executor == null) { continue; } - registerCommandWithAliases(commandMap, spec.name(), executor, spec.aliases().toArray(String[]::new)); + event.registrar().register( + spec.name(), + spec.name(), + spec.aliases(), + new EssentialsBasicCommand(spec.name(), executor) + ); } - registerCommandWithAliases(commandMap, "essentialsc", new HelpCommand(), "essc"); - } catch (Exception e) { - getLogger().severe("注册命令失败: " + e.getMessage()); - e.printStackTrace(); - } + event.registrar().register( + "essentialsc", + "essentialsc", + List.of("essc"), + new EssentialsBasicCommand("essentialsc", new HelpCommand()) + ); + }); } - private void registerCommandWithAliases(org.bukkit.command.CommandMap commandMap, String name, BaseCommand executor, String... aliases) { - String fallbackPrefix = getName().toLowerCase(java.util.Locale.ROOT); - Command command = new Command(name) { - @Override - public boolean execute(CommandSender sender, String commandLabel, String[] args) { - if (CommandRegistry.resolveCommandName(name) != null && !CommandRegistry.isAvailable(name)) { - sender.sendMessage(EssentialsC.getLangManager().getPrefixedString("messages.module-disabled")); - return true; - } - return executor.onCommand(sender, this, commandLabel, args); + private static final class EssentialsBasicCommand implements BasicCommand { + + private final String name; + private final BaseCommand executor; + private final PaperCommand commandAdapter; + + private EssentialsBasicCommand(String name, BaseCommand executor) { + this.name = name; + this.executor = executor; + this.commandAdapter = new PaperCommand(name, executor); + } + + @Override + public void execute(CommandSourceStack commandSourceStack, String[] args) { + CommandSender sender = commandSourceStack.getSender(); + if (CommandRegistry.resolveCommandName(name) != null && !CommandRegistry.isAvailable(name)) { + sender.sendMessage(EssentialsC.getLangManager().getPrefixedString("messages.module-disabled")); + return; } + executor.onCommand(sender, commandAdapter, name, args); + } - @Override - public java.util.List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { - if (executor instanceof org.bukkit.command.TabCompleter completer) { - return completer.onTabComplete(sender, this, alias, args); - } - return super.tabComplete(sender, alias, args); + @Override + public Collection suggest(CommandSourceStack commandSourceStack, String[] args) { + if (executor instanceof TabCompleter completer) { + return completer.onTabComplete(commandSourceStack.getSender(), commandAdapter, name, args); } - }; - - command.setPermission(executor.getPermission()); - commandMap.register(fallbackPrefix, command); - - for (String alias : aliases) { - Command aliasCmd = new Command(alias) { - @Override - public boolean execute(CommandSender sender, String commandLabel, String[] args) { - if (CommandRegistry.resolveCommandName(name) != null && !CommandRegistry.isAvailable(name)) { - sender.sendMessage(EssentialsC.getLangManager().getPrefixedString("messages.module-disabled")); - return true; - } - return executor.onCommand(sender, this, commandLabel, args); - } - - @Override - public java.util.List tabComplete(CommandSender sender, String label, String[] args) throws IllegalArgumentException { - if (executor instanceof org.bukkit.command.TabCompleter completer) { - return completer.onTabComplete(sender, this, label, args); - } - return super.tabComplete(sender, label, args); - } - }; - aliasCmd.setPermission(executor.getPermission()); - commandMap.register(fallbackPrefix, aliasCmd); + return List.of(); } } } diff --git a/src/main/java/cn/infstar/essentialsC/LangManager.java b/src/main/java/cn/infstar/essentialsC/LangManager.java index 3aba447..94a8f3d 100644 --- a/src/main/java/cn/infstar/essentialsC/LangManager.java +++ b/src/main/java/cn/infstar/essentialsC/LangManager.java @@ -87,6 +87,7 @@ public class LangManager { config = YamlConfiguration.loadConfiguration(configFile); config.addDefault("config-version", CURRENT_CONFIG_VERSION); config.addDefault("language", "zh_CN"); + config.addDefault("debug", false); config.options().copyDefaults(true); try { @@ -103,17 +104,21 @@ public class LangManager { return; } - String language = existingConfig.getString("language", "zh_CN"); File backupFile = new File(plugin.getDataFolder(), "config.v" + existingVersion + ".bak-" + System.currentTimeMillis() + ".yml"); try { Files.copy(configFile.toPath(), backupFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - plugin.saveResource("config.yml", true); - - FileConfiguration newConfig = YamlConfiguration.loadConfiguration(configFile); - newConfig.set("language", language); - newConfig.save(configFile); + InputStream defaultConfigStream = plugin.getResource("config.yml"); + if (defaultConfigStream != null) { + YamlConfiguration defaultConfig = YamlConfiguration.loadConfiguration( + new InputStreamReader(defaultConfigStream, StandardCharsets.UTF_8) + ); + existingConfig.setDefaults(defaultConfig); + existingConfig.options().copyDefaults(true); + } + existingConfig.set("config-version", CURRENT_CONFIG_VERSION); + existingConfig.save(configFile); plugin.getLogger().info("已将 config.yml 从版本 " + existingVersion + " 迁移到 " + CURRENT_CONFIG_VERSION + ",备份文件: " + backupFile.getName()); diff --git a/src/main/java/cn/infstar/essentialsC/commands/BaseCommand.java b/src/main/java/cn/infstar/essentialsC/commands/BaseCommand.java index 541d50d..d08e74d 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/BaseCommand.java +++ b/src/main/java/cn/infstar/essentialsC/commands/BaseCommand.java @@ -33,19 +33,17 @@ public abstract class BaseCommand implements CommandExecutor { } protected void playBlockShortcutSound(Player player, Material material, Sound fallbackSound) { - Sound sound = resolvePlaceSound(material); + Sound sound = fallbackSound; if (sound == null) { - sound = fallbackSound; + sound = resolvePlaceSound(material); } - if (sound != null) { - player.playSound(player.getLocation(), sound, 1.0F, 1.0F); - } + playShortcutSound(player, sound); } protected void playShortcutSound(Player player, Sound sound) { if (sound != null) { - player.playSound(player.getLocation(), sound, 1.0F, 1.0F); + player.playSound(player.getLocation(), sound, 0.65F, 1.0F); } } diff --git a/src/main/java/cn/infstar/essentialsC/commands/BlocksMenuCommand.java b/src/main/java/cn/infstar/essentialsC/commands/BlocksMenuCommand.java index 1011f6c..7ecc0a9 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/BlocksMenuCommand.java +++ b/src/main/java/cn/infstar/essentialsC/commands/BlocksMenuCommand.java @@ -4,6 +4,7 @@ import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.NamespacedKey; +import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -70,6 +71,7 @@ public class BlocksMenuCommand extends BaseCommand implements Listener { return; } player.openInventory(menu); + playShortcutSound(player, Sound.UI_BUTTON_CLICK); return; } @@ -85,6 +87,7 @@ public class BlocksMenuCommand extends BaseCommand implements Listener { } player.openInventory(menu); + playShortcutSound(player, Sound.UI_BUTTON_CLICK); } private int renderSections(Inventory menu, Player player, org.bukkit.configuration.ConfigurationSection sectionsConfig) { diff --git a/src/main/java/cn/infstar/essentialsC/commands/HelpCommand.java b/src/main/java/cn/infstar/essentialsC/commands/HelpCommand.java index 507d8a6..508c2d4 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/HelpCommand.java +++ b/src/main/java/cn/infstar/essentialsC/commands/HelpCommand.java @@ -2,8 +2,6 @@ package cn.infstar.essentialsC.commands; import cn.infstar.essentialsC.EssentialsC; import cn.infstar.essentialsC.LangManager; -import cn.infstar.essentialsC.ModuleManager; -import cn.infstar.essentialsC.tpsbar.TpsBarService; import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; @@ -37,17 +35,7 @@ public class HelpCommand extends BaseCommand implements TabCompleter { EssentialsC.getLangManager().reload(); plugin.getModuleManager().reload(); CommandRegistry.clearCache(); - if (plugin.getMaintenanceManager() != null && plugin.getModuleManager().isEnabled(ModuleManager.MAINTENANCE)) { - plugin.getMaintenanceManager().reload(); - } - TpsBarService tpsBarService = plugin.getTpsBarManager(); - if (tpsBarService != null) { - if (plugin.getModuleManager().isEnabled(ModuleManager.TPSBAR)) { - tpsBarService.reloadSettings(); - } else { - tpsBarService.shutdown(); - } - } + plugin.reloadRuntimeModules(); sender.sendMessage(getLang().getPrefixedString("messages.config-reloaded")); return true; } @@ -68,17 +56,7 @@ public class HelpCommand extends BaseCommand implements TabCompleter { EssentialsC.getLangManager().reload(); plugin.getModuleManager().reload(); CommandRegistry.clearCache(); - if (plugin.getMaintenanceManager() != null && plugin.getModuleManager().isEnabled(ModuleManager.MAINTENANCE)) { - plugin.getMaintenanceManager().reload(); - } - TpsBarService tpsBarService = plugin.getTpsBarManager(); - if (tpsBarService != null) { - if (plugin.getModuleManager().isEnabled(ModuleManager.TPSBAR)) { - tpsBarService.reloadSettings(); - } else { - tpsBarService.shutdown(); - } - } + plugin.reloadRuntimeModules(); sender.sendMessage(getLang().getPrefixedString("messages.config-reloaded")); return true; } @@ -360,7 +338,7 @@ public class HelpCommand extends BaseCommand implements TabCompleter { private List completeMaintenanceArgs(String partialInput) { List completions = new ArrayList<>(); String partial = partialInput.toLowerCase(); - for (String option : List.of("on", "off", "status", "reload")) { + for (String option : List.of("on", "off", "status", "reload", "add", "remove", "list")) { if (option.startsWith(partial)) { completions.add(option); } diff --git a/src/main/java/cn/infstar/essentialsC/commands/MaintenanceCommand.java b/src/main/java/cn/infstar/essentialsC/commands/MaintenanceCommand.java index 0bf633f..b824cb2 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/MaintenanceCommand.java +++ b/src/main/java/cn/infstar/essentialsC/commands/MaintenanceCommand.java @@ -8,6 +8,8 @@ import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.List; +import java.util.Locale; +import java.util.Map; public class MaintenanceCommand extends BaseCommand implements TabCompleter { @@ -37,7 +39,7 @@ public class MaintenanceCommand extends BaseCommand implements TabCompleter { return true; } - switch (args[0].toLowerCase()) { + switch (args[0].toLowerCase(Locale.ROOT)) { case "on", "enable", "enabled" -> { maintenanceManager.setEnabled(true); sender.sendMessage(getLang().getPrefixedString("maintenance.messages.enabled")); @@ -53,6 +55,42 @@ public class MaintenanceCommand extends BaseCommand implements TabCompleter { sender.sendMessage(getLang().getPrefixedString("maintenance.messages.reloaded")); return true; } + case "add" -> { + if (args.length < 2) { + sender.sendMessage(getLang().getPrefixedString("maintenance.messages.add-usage")); + return true; + } + + String target = args[1]; + if (maintenanceManager.addWhitelistEntry(target)) { + sender.sendMessage(getLang().getPrefixedString("maintenance.messages.whitelist-added", + Map.of("player", target))); + } else { + sender.sendMessage(getLang().getPrefixedString("maintenance.messages.whitelist-exists", + Map.of("player", target))); + } + return true; + } + case "remove" -> { + if (args.length < 2) { + sender.sendMessage(getLang().getPrefixedString("maintenance.messages.remove-usage")); + return true; + } + + String target = args[1]; + if (maintenanceManager.removeWhitelistEntry(target)) { + sender.sendMessage(getLang().getPrefixedString("maintenance.messages.whitelist-removed", + Map.of("player", target))); + } else { + sender.sendMessage(getLang().getPrefixedString("maintenance.messages.whitelist-missing", + Map.of("player", target))); + } + return true; + } + case "list" -> { + sendWhitelist(sender, maintenanceManager); + return true; + } default -> { sender.sendMessage(getLang().getPrefixedString("maintenance.messages.usage")); return true; @@ -65,19 +103,58 @@ public class MaintenanceCommand extends BaseCommand implements TabCompleter { ? "maintenance.status.enabled" : "maintenance.status.disabled"); sender.sendMessage(getLang().getPrefixedString("maintenance.messages.status", - java.util.Map.of("status", status))); + Map.of( + "status", status, + "whitelist_count", String.valueOf(maintenanceManager.getWhitelistCount()) + ))); + } + + private void sendWhitelist(CommandSender sender, MaintenanceManager maintenanceManager) { + List entries = maintenanceManager.getWhitelistEntries(); + if (entries.isEmpty()) { + sender.sendMessage(getLang().getPrefixedString("maintenance.messages.whitelist-empty")); + return; + } + + sender.sendMessage(getLang().getPrefixedString("maintenance.messages.whitelist-list", + Map.of( + "count", String.valueOf(entries.size()), + "entries", String.join(", ", entries) + ))); } @Override public List onTabComplete(CommandSender sender, Command command, String label, String[] args) { - if (args.length != 1 || !sender.hasPermission(getPermission())) { + if (!sender.hasPermission(getPermission())) { return List.of(); } - String partial = args[0].toLowerCase(); + if (args.length == 1) { + return complete(args[0], List.of("on", "off", "status", "reload", "add", "remove", "list")); + } + + if (args.length == 2 && args[0].equalsIgnoreCase("add")) { + return complete(args[1], plugin.getServer().getOnlinePlayers().stream() + .map(Player::getName) + .toList()); + } + + if (args.length == 2 && args[0].equalsIgnoreCase("remove")) { + MaintenanceManager maintenanceManager = plugin.getMaintenanceManager(); + if (maintenanceManager == null) { + return List.of(); + } + return complete(args[1], maintenanceManager.getWhitelistEntries()); + } + + return List.of(); + } + + private List complete(String input, List options) { + String partial = input.toLowerCase(Locale.ROOT); List completions = new ArrayList<>(); - for (String option : List.of("on", "off", "status", "reload")) { - if (option.startsWith(partial)) { + for (String option : options) { + if (option.toLowerCase(Locale.ROOT).startsWith(partial)) { completions.add(option); } } diff --git a/src/main/java/cn/infstar/essentialsC/commands/VanishCommand.java b/src/main/java/cn/infstar/essentialsC/commands/VanishCommand.java index 6327767..a97a062 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/VanishCommand.java +++ b/src/main/java/cn/infstar/essentialsC/commands/VanishCommand.java @@ -1,5 +1,6 @@ package cn.infstar.essentialsC.commands; +import cn.infstar.essentialsC.EssentialsC; import org.bukkit.entity.Player; import java.util.HashSet; @@ -20,17 +21,38 @@ public class VanishCommand extends BaseCommand { if (vanishedPlayers.contains(uuid)) { vanishedPlayers.remove(uuid); - showPlayerToAll(player); + showPlayerToAll(plugin, player); player.sendMessage(getLang().getPrefixedString("messages.vanish-disabled")); } else { vanishedPlayers.add(uuid); - hidePlayerFromAll(player); + hidePlayerFromAll(plugin, player); player.sendMessage(getLang().getPrefixedString("messages.vanish-enabled")); } return true; } - private void hidePlayerFromAll(Player player) { + public static void hideVanishedPlayersFrom(EssentialsC plugin, Player observer) { + for (Player vanished : plugin.getServer().getOnlinePlayers()) { + if (!observer.equals(vanished) && isVanished(vanished)) { + observer.hidePlayer(plugin, vanished); + } + } + } + + public static void removeVanished(Player player) { + vanishedPlayers.remove(player.getUniqueId()); + } + + public static void clearAll(EssentialsC plugin) { + for (Player player : plugin.getServer().getOnlinePlayers()) { + if (isVanished(player)) { + showPlayerToAll(plugin, player); + } + } + vanishedPlayers.clear(); + } + + private static void hidePlayerFromAll(EssentialsC plugin, Player player) { for (Player online : player.getServer().getOnlinePlayers()) { if (online != player) { online.hidePlayer(plugin, player); @@ -38,7 +60,7 @@ public class VanishCommand extends BaseCommand { } } - private void showPlayerToAll(Player player) { + private static void showPlayerToAll(EssentialsC plugin, Player player) { for (Player online : player.getServer().getOnlinePlayers()) { if (online != player) { online.showPlayer(plugin, player); diff --git a/src/main/java/cn/infstar/essentialsC/commands/WorkbenchCommand.java b/src/main/java/cn/infstar/essentialsC/commands/WorkbenchCommand.java index 058b2da..911bd33 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/WorkbenchCommand.java +++ b/src/main/java/cn/infstar/essentialsC/commands/WorkbenchCommand.java @@ -13,7 +13,7 @@ public class WorkbenchCommand extends BaseCommand { @Override protected boolean execute(Player player, String[] args) { player.openWorkbench(null, true); - playBlockShortcutSound(player, Material.CRAFTING_TABLE, Sound.BLOCK_CRAFTER_CRAFT); + playBlockShortcutSound(player, Material.CRAFTING_TABLE, Sound.UI_BUTTON_CLICK); return true; } } diff --git a/src/main/java/cn/infstar/essentialsC/listeners/JeiRecipeSyncListener.java b/src/main/java/cn/infstar/essentialsC/listeners/JeiRecipeSyncListener.java index 44cc5a5..213926a 100644 --- a/src/main/java/cn/infstar/essentialsC/listeners/JeiRecipeSyncListener.java +++ b/src/main/java/cn/infstar/essentialsC/listeners/JeiRecipeSyncListener.java @@ -22,6 +22,7 @@ import org.bukkit.event.player.PlayerJoinEvent; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Locale; /** * 为 Fabric / NeoForge 客户端补发配方同步数据,修复 1.21.2+ 的 JEI 配方显示问题。 @@ -32,14 +33,20 @@ public class JeiRecipeSyncListener implements Listener { private final boolean enabled; private final boolean debug; private final boolean sendPlayerMessage; + private final int brandCheckDelayTicks; public JeiRecipeSyncListener(EssentialsC plugin) { this.plugin = plugin; FileConfiguration config = plugin.getConfig(); + config.addDefault("jei-sync.brand-check-delay-ticks", 20); + config.options().copyDefaults(true); + plugin.saveConfig(); + this.enabled = config.getBoolean("jei-sync.enabled", true); - this.debug = config.getBoolean("jei-sync.debug", false); + this.debug = config.getBoolean("jei-sync.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)); } @EventHandler @@ -49,14 +56,20 @@ public class JeiRecipeSyncListener implements Listener { } Player player = event.getPlayer(); + plugin.getServer().getScheduler().runTaskLater(plugin, () -> detectAndSync(player), brandCheckDelayTicks); + } + + private void detectAndSync(Player player) { + if (!player.isOnline()) { + return; + } + String clientBrand = player.getClientBrandName(); if (debug) { - plugin.getLogger().info("========================================"); - plugin.getLogger().info("玩家 " + player.getName() + " 加入"); - plugin.getLogger().info("客户端品牌: '" + (clientBrand != null ? clientBrand : "null") + "'"); - plugin.getLogger().info("JEI 同步功能: " + (enabled ? "启用" : "禁用")); - plugin.getLogger().info("========================================"); + plugin.getLogger().info("JEI 客户端检测: player=" + player.getName() + + ", brand=" + (clientBrand == null || clientBrand.isBlank() ? "unknown" : clientBrand) + + ", delayTicks=" + brandCheckDelayTicks); } if (clientBrand == null || clientBrand.isEmpty()) { @@ -66,7 +79,7 @@ public class JeiRecipeSyncListener implements Listener { return; } - String brandLower = clientBrand.toLowerCase(); + String brandLower = clientBrand.toLowerCase(Locale.ROOT); if (brandLower.contains("fabric")) { if (debug) { plugin.getLogger().info("检测到 Fabric 客户端,开始发送配方同步..."); diff --git a/src/main/java/cn/infstar/essentialsC/listeners/VanishListener.java b/src/main/java/cn/infstar/essentialsC/listeners/VanishListener.java new file mode 100644 index 0000000..c9906fd --- /dev/null +++ b/src/main/java/cn/infstar/essentialsC/listeners/VanishListener.java @@ -0,0 +1,29 @@ +package cn.infstar.essentialsC.listeners; + +import cn.infstar.essentialsC.EssentialsC; +import cn.infstar.essentialsC.commands.VanishCommand; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +public final class VanishListener implements Listener { + + private final EssentialsC plugin; + + public VanishListener(EssentialsC plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + VanishCommand.hideVanishedPlayersFrom(plugin, event.getPlayer()); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + VanishCommand.removeVanished(player); + } +} diff --git a/src/main/java/cn/infstar/essentialsC/maintenance/MaintenanceListener.java b/src/main/java/cn/infstar/essentialsC/maintenance/MaintenanceListener.java index ef6b716..3c9dd8a 100644 --- a/src/main/java/cn/infstar/essentialsC/maintenance/MaintenanceListener.java +++ b/src/main/java/cn/infstar/essentialsC/maintenance/MaintenanceListener.java @@ -2,6 +2,7 @@ package cn.infstar.essentialsC.maintenance; import cn.infstar.essentialsC.EssentialsC; import cn.infstar.essentialsC.ModuleManager; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerLoginEvent; @@ -9,6 +10,9 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.server.ServerListPingEvent; +import java.net.InetAddress; +import java.util.Map; + public final class MaintenanceListener implements Listener { private final EssentialsC plugin; @@ -39,11 +43,12 @@ public final class MaintenanceListener implements Listener { return; } - if (event.getPlayer().hasPermission(maintenanceManager.getBypassPermission())) { + if (maintenanceManager.canJoin(event.getPlayer())) { return; } event.disallow(PlayerLoginEvent.Result.KICK_OTHER, maintenanceManager.getKickMessage()); + notifyBlockedLogin(event, maintenanceManager); } @EventHandler @@ -63,4 +68,29 @@ public final class MaintenanceListener implements Listener { maintenanceManager.removeBossBarPlayer(event.getPlayer()); } } + + private void notifyBlockedLogin(PlayerLoginEvent event, MaintenanceManager maintenanceManager) { + if (!maintenanceManager.isNotifyEnabled()) { + return; + } + + String address = ""; + InetAddress inetAddress = event.getAddress(); + if (inetAddress != null) { + address = inetAddress.getHostAddress(); + } + + String message = EssentialsC.getLangManager().getPrefixedString("maintenance.messages.login-blocked", + Map.of( + "player", event.getPlayer().getName(), + "uuid", event.getPlayer().getUniqueId().toString(), + "address", address + )); + + for (Player player : plugin.getServer().getOnlinePlayers()) { + if (player.hasPermission(maintenanceManager.getNotifyPermission())) { + player.sendMessage(message); + } + } + } } diff --git a/src/main/java/cn/infstar/essentialsC/maintenance/MaintenanceManager.java b/src/main/java/cn/infstar/essentialsC/maintenance/MaintenanceManager.java index 4967591..417cca3 100644 --- a/src/main/java/cn/infstar/essentialsC/maintenance/MaintenanceManager.java +++ b/src/main/java/cn/infstar/essentialsC/maintenance/MaintenanceManager.java @@ -11,8 +11,12 @@ import org.bukkit.entity.Player; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; +import java.util.Set; +import java.util.UUID; public final class MaintenanceManager { @@ -38,6 +42,10 @@ public final class MaintenanceManager { config.addDefault("config-version", CURRENT_CONFIG_VERSION); config.addDefault("enabled", false); config.addDefault("bypass-permission", "essentialsc.maintenance.bypass"); + config.addDefault("notify.enabled", true); + config.addDefault("notify.permission", "essentialsc.maintenance.notify"); + config.addDefault("whitelist.uuids", List.of()); + config.addDefault("whitelist.names", List.of()); config.addDefault("motd.enabled", true); config.addDefault("motd.lines", List.of("&c服务器维护中", "&7请稍后再试")); config.addDefault("kick-message", List.of("&c服务器正在维护", "&7请稍后再进入")); @@ -65,6 +73,66 @@ public final class MaintenanceManager { return config.getString("bypass-permission", "essentialsc.maintenance.bypass"); } + public boolean isNotifyEnabled() { + return config.getBoolean("notify.enabled", true); + } + + public String getNotifyPermission() { + return config.getString("notify.permission", "essentialsc.maintenance.notify"); + } + + public boolean canJoin(Player player) { + return player != null && (player.hasPermission(getBypassPermission()) || isWhitelisted(player)); + } + + public boolean isWhitelisted(Player player) { + if (player == null) { + return false; + } + + return getWhitelistUuids().contains(player.getUniqueId().toString().toLowerCase(Locale.ROOT)) + || getWhitelistNames().contains(normalizeName(player.getName())); + } + + public boolean addWhitelistEntry(String input) { + String normalized = normalizeEntry(input); + if (normalized.isEmpty()) { + return false; + } + + UUID uuid = parseUuid(normalized); + if (uuid != null) { + return addWhitelistUuid(uuid.toString()); + } + + return addWhitelistName(input); + } + + public boolean removeWhitelistEntry(String input) { + String normalized = normalizeEntry(input); + if (normalized.isEmpty()) { + return false; + } + + UUID uuid = parseUuid(normalized); + if (uuid != null) { + return removeWhitelistUuid(uuid.toString()); + } + + return removeWhitelistName(input); + } + + public List getWhitelistEntries() { + List entries = new ArrayList<>(); + entries.addAll(getWhitelistNames()); + entries.addAll(getWhitelistUuids()); + return entries; + } + + public int getWhitelistCount() { + return getWhitelistEntries().size(); + } + public boolean isMotdEnabled() { return config.getBoolean("motd.enabled", true); } @@ -100,7 +168,7 @@ public final class MaintenanceManager { if (bossBar == null || player == null || !player.isOnline()) { return; } - if (player.hasPermission(getBypassPermission())) { + if (canJoin(player)) { bossBar.removePlayer(player); return; } @@ -154,6 +222,81 @@ public final class MaintenanceManager { return Math.max(0.0D, Math.min(1.0D, progress)); } + private boolean addWhitelistUuid(String uuid) { + return updateWhitelist("whitelist.uuids", uuid.toLowerCase(Locale.ROOT), true); + } + + private boolean addWhitelistName(String name) { + return updateWhitelist("whitelist.names", normalizeName(name), true); + } + + private boolean removeWhitelistUuid(String uuid) { + return updateWhitelist("whitelist.uuids", uuid.toLowerCase(Locale.ROOT), false); + } + + private boolean removeWhitelistName(String name) { + return updateWhitelist("whitelist.names", normalizeName(name), false); + } + + private boolean updateWhitelist(String path, String value, boolean add) { + if (value == null || value.isBlank()) { + return false; + } + + Set entries = path.endsWith(".names") ? getWhitelistNames() : getWhitelistUuids(); + boolean changed; + if (add) { + changed = entries.add(value); + } else { + changed = entries.remove(value); + } + + if (changed) { + config.set(path, new ArrayList<>(entries)); + save(); + refreshBossBar(); + } + return changed; + } + + private Set getWhitelistUuids() { + Set entries = new LinkedHashSet<>(); + for (String value : config.getStringList("whitelist.uuids")) { + String normalized = normalizeEntry(value).toLowerCase(Locale.ROOT); + if (!normalized.isEmpty()) { + entries.add(normalized); + } + } + return entries; + } + + private Set getWhitelistNames() { + Set entries = new LinkedHashSet<>(); + for (String value : config.getStringList("whitelist.names")) { + String normalized = normalizeName(value); + if (!normalized.isEmpty()) { + entries.add(normalized); + } + } + return entries; + } + + private UUID parseUuid(String input) { + try { + return UUID.fromString(input); + } catch (IllegalArgumentException ignored) { + return null; + } + } + + private String normalizeEntry(String input) { + return input == null ? "" : input.trim(); + } + + private String normalizeName(String input) { + return normalizeEntry(input).toLowerCase(Locale.ROOT); + } + private String colorizeLines(List lines) { if (lines == null || lines.isEmpty()) { return ""; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 94cdb02..1fd5cf8 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -6,6 +6,9 @@ config-version: 2 # 语言文件名称,对应插件数据目录下 lang/<语言名>.yml language: "zh_CN" +# 是否输出更详细的调试日志。 +debug: false + admin-mode: # 原版默认飞行速度为 0.1,这里 0.2 表示两倍飞行速度 fly-speed: 0.2 @@ -18,6 +21,8 @@ jei-sync: enabled: true # 是否输出调试日志 debug: false + # 玩家加入后延迟多少 tick 再检测客户端品牌,避免品牌信息尚未同步完成。 + brand-check-delay-ticks: 20 # 是否在玩家加入时发送同步提示 send-player-message: true diff --git a/src/main/resources/lang/en_US.yml b/src/main/resources/lang/en_US.yml index 84f1b82..960e19f 100644 --- a/src/main/resources/lang/en_US.yml +++ b/src/main/resources/lang/en_US.yml @@ -84,7 +84,7 @@ help: repair: " &f/repair &7- Repair held or all items" admin: " &f/essc admin &7- Toggle admin mode" tpsbar: " &f/tpsbar [player] &7- Toggle TPS boss bar" - maintenance: " &f/maintenance &7- Manage maintenance mode" + maintenance: " &f/maintenance &7- Manage maintenance mode" blocks-menu: title: "&6&lEssentialsC &8- &e&lShortcut Menu" @@ -180,5 +180,14 @@ maintenance: enabled: "&aMaintenance mode enabled." disabled: "&cMaintenance mode disabled." reloaded: "&aMaintenance configuration reloaded." - status: "&7Maintenance mode status: {status}" - usage: "&cUsage: /maintenance " + status: "&7Maintenance mode status: {status} &8| &7Whitelist: &f{whitelist_count}" + usage: "&cUsage: /maintenance " + add-usage: "&cUsage: /maintenance add " + remove-usage: "&cUsage: /maintenance remove " + whitelist-added: "&aAdded {player} to the maintenance whitelist." + whitelist-removed: "&aRemoved {player} from the maintenance whitelist." + whitelist-exists: "&e{player} is already on the maintenance whitelist." + whitelist-missing: "&c{player} is not on the maintenance whitelist." + whitelist-empty: "&7The maintenance whitelist is empty." + whitelist-list: "&7Maintenance whitelist ({count}): &f{entries}" + login-blocked: "&e{player} was blocked by maintenance mode. &7UUID: {uuid} &8| &7IP: {address}" diff --git a/src/main/resources/lang/zh_CN.yml b/src/main/resources/lang/zh_CN.yml index bc6c2b5..a3cf6be 100644 --- a/src/main/resources/lang/zh_CN.yml +++ b/src/main/resources/lang/zh_CN.yml @@ -84,7 +84,7 @@ help: repair: " &f/repair &7- 修复手中或全部物品" admin: " &f/essc admin &7- 切换管理模式" tpsbar: " &f/tpsbar [玩家] &7- 切换 TPS 状态栏" - maintenance: " &f/maintenance &7- 管理维护模式" + maintenance: " &f/maintenance &7- 管理维护模式" blocks-menu: title: "&6&lEssentialsC &8- &e&l便捷菜单" @@ -180,5 +180,14 @@ maintenance: enabled: "&a维护模式已开启。" disabled: "&c维护模式已关闭。" reloaded: "&a维护模式配置已重载。" - status: "&7维护模式当前状态:{status}" - usage: "&c用法:/maintenance " + status: "&7维护模式当前状态:{status} &8| &7白名单:&f{whitelist_count}" + usage: "&c用法:/maintenance " + add-usage: "&c用法:/maintenance add <玩家|UUID>" + remove-usage: "&c用法:/maintenance remove <玩家|UUID>" + whitelist-added: "&a已将 {player} 加入维护白名单。" + whitelist-removed: "&a已将 {player} 移出维护白名单。" + whitelist-exists: "&e{player} 已在维护白名单中。" + whitelist-missing: "&c{player} 不在维护白名单中。" + whitelist-empty: "&7维护白名单为空。" + whitelist-list: "&7维护白名单({count}):&f{entries}" + login-blocked: "&e{player} 被维护模式拦截。&7UUID:{uuid} &8| &7IP:{address}" diff --git a/src/main/resources/maintenance.yml b/src/main/resources/maintenance.yml index cb0be1d..f3d2db2 100644 --- a/src/main/resources/maintenance.yml +++ b/src/main/resources/maintenance.yml @@ -8,6 +8,18 @@ enabled: false # 拥有该权限的玩家可以在维护模式下进入服务器。 bypass-permission: "essentialsc.maintenance.bypass" +notify: + # 玩家因维护模式被拒绝进入时,是否通知在线管理员。 + enabled: true + # 拥有该权限的在线玩家会收到拦截通知。 + permission: "essentialsc.maintenance.notify" + +whitelist: + # 这些 UUID 可以在维护模式下进入服务器。 + uuids: [] + # 这些玩家名可以在维护模式下进入服务器。大小写不敏感。 + names: [] + motd: # 维护模式开启时是否替换服务器列表 MOTD。 enabled: true diff --git a/src/main/resources/modules.yml b/src/main/resources/modules.yml index 065bbd0..e3b8256 100644 --- a/src/main/resources/modules.yml +++ b/src/main/resources/modules.yml @@ -3,7 +3,8 @@ # modules.yml 用于控制功能模块是否在运行时加载。 # config.yml 仍然用于控制已启用模块内部的具体行为。 # -# 将模块设置为 false 后,建议重启服务器,以完全注销对应命令和监听器。 +# 修改模块开关后可使用 /essc reload 刷新运行期服务和监听器。 +# 直连命令的完整热增删仍建议通过重启服务器完成。 config-version: 1 diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 0cd381c..c874a87 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -98,6 +98,9 @@ permissions: essentialsc.maintenance.bypass: description: 允许在维护模式下进入服务器 default: op + essentialsc.maintenance.notify: + description: 允许接收维护模式拦截通知 + default: op essentialsc.shulkerbox.open: description: 允许通过 Shift+右键快捷打开潜影盒 default: op @@ -137,5 +140,6 @@ permissions: essentialsc.command.tpsbar.others: true essentialsc.command.maintenance: true essentialsc.maintenance.bypass: true + essentialsc.maintenance.notify: true essentialsc.shulkerbox.open: true essentialsc.mobdrops.enderman: true