diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 497a33e..0c388d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,8 +26,8 @@ jobs: - name: 授予执行权限 run: chmod +x gradlew - - name: 构建全部版本 - run: ./gradlew buildAllVersions + - name: 构建插件 + run: ./gradlew build - name: 创建发行版 uses: softprops/action-gh-release@v1 diff --git a/README.md b/README.md index 9d0791a..2964801 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ - 最低支持版本为 `Paper 1.21.11` - 已适配 `Paper 26.1.2` - 构建环境固定为 `Java 21` -- 配置与文本分离:行为配置放在 `config.yml`,提示文本放在 `lang/` -- 支持模块裁剪,便于按需构建不同版本 +- 配置、模块开关与文本分离:行为配置放在 `config.yml`,模块开关放在 `modules.yml`,提示文本放在 `lang/` +- 支持运行期模块开关,避免为不同功能组合构建多个插件版本 ## 主要功能 @@ -52,17 +52,20 @@ - Enderman 掉落方块控制 - JEI 配方同步修复 -## 构建变体 +## 模块配置 -项目目前提供三个常用构建版本: +项目现在默认构建一个完整插件,功能是否启用由 `plugins/EssentialsC/modules.yml` 控制。 -| 版本 | 产物名 | 说明 | +| 模块 | 默认状态 | 说明 | | --- | --- | --- | -| 标准版 | `EssentialsC-.jar` | 默认版本,不包含 `mob-drops` 模块 | -| 完整版 | `EssentialsC-all-.jar` | 包含全部模块 | -| 精简版 | `EssentialsC-lite-.jar` | 仅排除 `blocks` 模块,保留 `mob-drops` | +| `blocks` | 开启 | 便捷方块命令、`/essc blocks` 菜单、潜影盒快捷打开 | +| `player` | 开启 | 飞行、夜视、发光、治疗、喂食、修复、帽子、自杀、隐身、查询玩家 | +| `admin-mode` | 开启 | `/essc admin` 管理模式与独立状态保存 | +| `tpsbar` | 开启 | 插件版 TPSBar,仍受 `config.yml` 中 `tpsbar.mode` 控制 | +| `jei-sync` | 开启 | Fabric / NeoForge JEI 配方同步修复 | +| `mob-drops` | 关闭 | 末影人掉落控制,默认关闭以保留过去标准版行为 | -如果需要进一步裁剪模块,也可以使用自定义构建参数生成 `custom` 版本。 +修改模块开关后建议重启服务器,使命令注册表和监听器状态完全刷新。`/essc reload` 可以刷新配置和已注册命令的执行检查,但无法从 Bukkit 命令表中真正热移除或新增直连命令。 ## 安装说明 @@ -83,6 +86,8 @@ - 掉落控制 - TPSBar 模式 - 便捷菜单布局 +- `modules.yml` + - 功能模块开关 - `lang/zh_CN.yml`、`lang/en_US.yml` - 命令反馈 - 帮助信息 @@ -122,29 +127,28 @@ essentialsc.* ```bash git clone https://github.com/Coldsmiles/EssentialsC.git cd EssentialsC -./gradlew buildAllVersions +./gradlew build ``` Windows 可使用: ```powershell -.\gradlew.bat buildAllVersions +.\gradlew.bat build ``` -构建产物输出到 `build/libs/`。 +构建产物输出到 `build/libs/EssentialsC-.jar`。 常用任务: ```bash -./gradlew shadowJarStandard -./gradlew shadowJarAll -./gradlew shadowJarLite +./gradlew shadowJar +./gradlew build ``` ## 开发说明 - 使用 `paperweight-userdev` 进行 Paper 开发 -- 运行时通过反射加载可选模块,避免裁剪版本因类缺失而启动失败 +- 运行时通过 `modules.yml` 控制模块加载,命令与监听器按模块状态注册 - 发布流程基于 GitHub Actions 和 Gradle Wrapper ## 许可证 diff --git a/build.gradle b/build.gradle index fe7688b..4adf0a8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,6 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.gradle.language.jvm.tasks.ProcessResources -import java.util.Collections - plugins { id 'java' id 'io.papermc.paperweight.userdev' version '2.0.0-beta.21' @@ -28,96 +26,6 @@ java { toolchain.languageVersion.set(JavaLanguageVersion.of(21)) } -def moduleExcludes = [ - 'blocks': [ - '**/commands/WorkbenchCommand.java', - '**/commands/AnvilCommand.java', - '**/commands/CartographyTableCommand.java', - '**/commands/GrindstoneCommand.java', - '**/commands/LoomCommand.java', - '**/commands/SmithingTableCommand.java', - '**/commands/StonecutterCommand.java', - '**/commands/EnderChestCommand.java', - '**/commands/BlocksMenuCommand.java', - '**/listeners/ShulkerBoxListener.java' - ], - 'player': [ - '**/commands/FlyCommand.java', - '**/commands/NightVisionCommand.java', - '**/commands/GlowCommand.java', - '**/commands/HealCommand.java', - '**/commands/FeedCommand.java', - '**/commands/VanishCommand.java', - '**/commands/SeenCommand.java', - '**/commands/HatCommand.java', - '**/commands/SuicideCommand.java', - '**/commands/RepairCommand.java', - '**/commands/AdminCommand.java', - '**/commands/TpsBarCommand.java', - '**/tpsbar/TpsBarManager.java' - ], - 'jei-fix': [ - '**/listeners/JeiRecipeSyncListener.java' - ], - 'mob-drops': [ - '**/listeners/MobDropListener.java', - '**/listeners/MobDropMenuListener.java', - '**/commands/MobDropCommand.java' - ] -] - -def variantDefinitions = [ - standard: [ - archiveFileName: "EssentialsC-${project.version}.jar", - excludedModules: ['mob-drops'] - ], - all: [ - archiveFileName: "EssentialsC-all-${project.version}.jar", - excludedModules: [] - ], - lite: [ - archiveFileName: "EssentialsC-lite-${project.version}.jar", - excludedModules: ['blocks'] - ] -] - -if (project.hasProperty('excludeModules')) { - def customExcludedModules = project.property('excludeModules') - .split(',') - .collect { it.trim() } - .findAll { !it.isEmpty() } - - variantDefinitions.custom = [ - archiveFileName: "EssentialsC-custom-${project.version}.jar", - excludedModules: customExcludedModules - ] -} - -def resolveExcludePatterns = { Collection modules -> - modules.collectMany { module -> moduleExcludes.get(module, Collections.emptyList()) }.unique() -} - -variantDefinitions.each { variantName, variantConfig -> - def unknownModules = variantConfig.excludedModules.findAll { !moduleExcludes.containsKey(it) } - if (!unknownModules.isEmpty()) { - throw new GradleException("Unknown modules for variant '${variantName}': ${unknownModules.join(', ')}") - } -} - -def variantSourceSets = [:] - -variantDefinitions.each { variantName, variantConfig -> - def sourceSet = sourceSets.create(variantName) - sourceSet.java.srcDirs = sourceSets.main.java.srcDirs - sourceSet.resources.srcDirs = sourceSets.main.resources.srcDirs - resolveExcludePatterns(variantConfig.excludedModules).each { pattern -> - sourceSet.java.exclude(pattern) - } - sourceSet.compileClasspath += sourceSets.main.compileClasspath - sourceSet.runtimeClasspath += sourceSet.output + sourceSet.compileClasspath - variantSourceSets[variantName] = sourceSet -} - tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' } @@ -126,14 +34,10 @@ tasks.withType(ProcessResources).configureEach { filteringCharset = 'UTF-8' } -variantDefinitions.keySet().each { variantName -> - def sourceSet = variantSourceSets[variantName] - def processTaskName = sourceSet.processResourcesTaskName - tasks.named(processTaskName, ProcessResources).configure { - inputs.property('version', project.version) - filesMatching('paper-plugin.yml') { - expand('version': project.version) - } +tasks.named('processResources', ProcessResources).configure { + inputs.property('version', project.version) + filesMatching('paper-plugin.yml') { + expand('version': project.version) } } @@ -141,72 +45,13 @@ tasks.named('jar').configure { enabled = false } -tasks.named('shadowJar').configure { - enabled = false -} - -def variantJarTasks = variantDefinitions.collect { variantName, variantConfig -> - def taskName = "shadowJar${variantName.capitalize()}" - def sourceSet = variantSourceSets[variantName] - - tasks.register(taskName, ShadowJar) { - group = 'build' - description = "Builds the ${variantName} plugin jar." - archiveFileName.set(variantConfig.archiveFileName as String) - from(sourceSet.output) - configurations = [project.configurations.runtimeClasspath] - dependsOn(tasks.named(sourceSet.classesTaskName)) - } +tasks.named('shadowJar', ShadowJar).configure { + group = 'build' + description = '构建包含全部运行期可开关模块的 EssentialsC 插件。' + archiveFileName.set("EssentialsC-${project.version}.jar") + configurations = [project.configurations.runtimeClasspath] } tasks.named('assemble').configure { - dependsOn(variantJarTasks) -} - -tasks.register('buildAllVersions') { - group = 'build' - description = 'Builds standard, all, and lite plugin jars.' - dependsOn(variantJarTasks) -} - -tasks.register('deployToPaper12111', Copy) { - group = 'deployment' - description = 'Deploys the all variant to the local Paper 1.21.11 test server.' - def artifact = tasks.named('shadowJarAll').flatMap { it.archiveFile } - dependsOn(tasks.named('shadowJarAll')) - from(artifact) - into(layout.projectDirectory.dir('test-server/paper-1.21.11/plugins')) -} - -tasks.register('deployToPaper26') { - group = 'deployment' - description = 'Deploys the all variant to the local Paper 26.1.2 test server.' - dependsOn(tasks.named('shadowJarAll')) - - doFirst { - def pluginsDir = file("${projectDir}/test-server/paper-26.1.2/plugins") - if (!pluginsDir.exists()) { - return - } - - fileTree(pluginsDir).matching { - include 'EssentialsC*.jar' - }.each { pluginJar -> - pluginJar.delete() - } - } - - doLast { - def artifact = tasks.named('shadowJarAll').flatMap { it.archiveFile } - copy { - from(artifact) - into("${projectDir}/test-server/paper-26.1.2/plugins") - } - } -} - -tasks.register('buildAndDeployToPaper26') { - group = 'deployment' - description = 'Builds and deploys the all variant to the local Paper 26.1.2 test server.' - dependsOn(tasks.named('clean'), tasks.named('deployToPaper26')) + dependsOn(tasks.named('shadowJar')) } diff --git a/src/main/java/cn/infstar/essentialsC/EssentialsC.java b/src/main/java/cn/infstar/essentialsC/EssentialsC.java index cf914b9..436f103 100644 --- a/src/main/java/cn/infstar/essentialsC/EssentialsC.java +++ b/src/main/java/cn/infstar/essentialsC/EssentialsC.java @@ -16,15 +16,23 @@ import java.lang.reflect.Field; public final class EssentialsC extends JavaPlugin { private static LangManager langManager; + private ModuleManager moduleManager; private AdminModeManager adminModeManager; private TpsBarService tpsBarManager; @Override public void onEnable() { langManager = new LangManager(this); - adminModeManager = new AdminModeManager(this); - getServer().getPluginManager().registerEvents(adminModeManager, this); - tpsBarManager = createOptionalService("cn.infstar.essentialsC.tpsbar.TpsBarManager", TpsBarService.class); + moduleManager = new ModuleManager(this); + + if (moduleManager.isEnabled(ModuleManager.ADMIN_MODE)) { + adminModeManager = new AdminModeManager(this); + getServer().getPluginManager().registerEvents(adminModeManager, 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); } @@ -32,7 +40,7 @@ public final class EssentialsC extends JavaPlugin { registerListeners(); registerCommands(); - getLogger().info("EssentialsC enabled. Version: " + getDescription().getVersion()); + getLogger().info("EssentialsC 已启用,版本: " + getDescription().getVersion()); } @Override @@ -43,7 +51,7 @@ public final class EssentialsC extends JavaPlugin { if (adminModeManager != null) { adminModeManager.shutdown(); } - getLogger().info("EssentialsC disabled."); + getLogger().info("EssentialsC 已禁用。"); } public static LangManager getLangManager() { @@ -54,28 +62,38 @@ public final class EssentialsC extends JavaPlugin { return adminModeManager; } + public ModuleManager getModuleManager() { + return moduleManager; + } + public TpsBarService getTpsBarManager() { return tpsBarManager; } private void registerPluginChannels() { + if (!moduleManager.isEnabled(ModuleManager.JEI_SYNC)) { + return; + } org.bukkit.plugin.messaging.Messenger messenger = getServer().getMessenger(); messenger.registerOutgoingPluginChannel(this, "fabric:recipe_sync"); messenger.registerOutgoingPluginChannel(this, "neoforge:recipe_content"); } private void registerListeners() { - if (registerListener("cn.infstar.essentialsC.listeners.ShulkerBoxListener")) { - getLogger().info("- Shulker box module"); + if (moduleManager.isEnabled(ModuleManager.BLOCKS) + && registerListener("cn.infstar.essentialsC.listeners.ShulkerBoxListener")) { + getLogger().info("- 潜影盒模块"); } - if (registerListener("cn.infstar.essentialsC.listeners.JeiRecipeSyncListener")) { - getLogger().info("- JEI recipe sync"); + if (moduleManager.isEnabled(ModuleManager.JEI_SYNC) + && registerListener("cn.infstar.essentialsC.listeners.JeiRecipeSyncListener")) { + getLogger().info("- JEI 配方同步"); } - if (registerListener("cn.infstar.essentialsC.listeners.MobDropListener")) { + if (moduleManager.isEnabled(ModuleManager.MOB_DROPS) + && registerListener("cn.infstar.essentialsC.listeners.MobDropListener")) { createOptionalInstance("cn.infstar.essentialsC.listeners.MobDropMenuListener"); - getLogger().info("- Mob drop control"); + getLogger().info("- 生物掉落控制"); } } @@ -127,7 +145,7 @@ public final class EssentialsC extends JavaPlugin { registerCommandWithAliases(commandMap, "essentialsc", new HelpCommand(), "essc"); } catch (Exception e) { - getLogger().severe("Failed to register commands: " + e.getMessage()); + getLogger().severe("注册命令失败: " + e.getMessage()); e.printStackTrace(); } } @@ -136,6 +154,10 @@ public final class EssentialsC extends JavaPlugin { 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); } @@ -155,6 +177,10 @@ public final class EssentialsC extends JavaPlugin { 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); } diff --git a/src/main/java/cn/infstar/essentialsC/LangManager.java b/src/main/java/cn/infstar/essentialsC/LangManager.java index 1b81975..3aba447 100644 --- a/src/main/java/cn/infstar/essentialsC/LangManager.java +++ b/src/main/java/cn/infstar/essentialsC/LangManager.java @@ -38,7 +38,7 @@ public class LangManager { public String getString(String path) { String value = langFile.getString(path); if (value == null) { - return translateColorCodes("&cMissing translation: " + path); + return translateColorCodes("&c缺少语言文本: " + path); } return translateColorCodes(value); } @@ -58,7 +58,7 @@ public class LangManager { public List getStringList(String path) { List values = langFile.getStringList(path); if (values.isEmpty()) { - values = List.of("&cMissing translation: " + path); + values = List.of("&c缺少语言文本: " + path); } List translated = new ArrayList<>(); @@ -92,7 +92,7 @@ public class LangManager { try { config.save(configFile); } catch (Exception e) { - plugin.getLogger().severe("Failed to save config.yml: " + e.getMessage()); + plugin.getLogger().severe("保存 config.yml 失败: " + e.getMessage()); } } @@ -115,10 +115,10 @@ public class LangManager { newConfig.set("language", language); newConfig.save(configFile); - plugin.getLogger().info("Migrated config.yml from version " + existingVersion - + " to " + CURRENT_CONFIG_VERSION + ". Backup saved to " + backupFile.getName()); + plugin.getLogger().info("已将 config.yml 从版本 " + existingVersion + + " 迁移到 " + CURRENT_CONFIG_VERSION + ",备份文件: " + backupFile.getName()); } catch (IOException e) { - plugin.getLogger().severe("Failed to migrate config.yml: " + e.getMessage()); + plugin.getLogger().severe("迁移 config.yml 失败: " + e.getMessage()); } } @@ -127,7 +127,7 @@ public class LangManager { File langFolder = new File(plugin.getDataFolder(), "lang"); if (!langFolder.exists() && !langFolder.mkdirs()) { - plugin.getLogger().warning("Failed to create language folder: " + langFolder.getAbsolutePath()); + plugin.getLogger().warning("创建语言文件夹失败: " + langFolder.getAbsolutePath()); } File langFileObj = new File(langFolder, currentLanguage + ".yml"); @@ -135,7 +135,7 @@ public class LangManager { if (plugin.getResource("lang/" + currentLanguage + ".yml") != null) { plugin.saveResource("lang/" + currentLanguage + ".yml", false); } else { - plugin.getLogger().warning("Language file not found: " + currentLanguage + ".yml, falling back to en_US"); + plugin.getLogger().warning("未找到语言文件: " + currentLanguage + ".yml,已回退到 en_US"); currentLanguage = "en_US"; plugin.saveResource("lang/en_US.yml", false); langFileObj = new File(langFolder, "en_US.yml"); diff --git a/src/main/java/cn/infstar/essentialsC/ModuleManager.java b/src/main/java/cn/infstar/essentialsC/ModuleManager.java new file mode 100644 index 0000000..708b6d9 --- /dev/null +++ b/src/main/java/cn/infstar/essentialsC/ModuleManager.java @@ -0,0 +1,76 @@ +package cn.infstar.essentialsC; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +public final class ModuleManager { + + private static final int CURRENT_CONFIG_VERSION = 1; + + public static final String BLOCKS = "blocks"; + public static final String PLAYER = "player"; + 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"; + + private static final Map DEFAULT_MODULES = new LinkedHashMap<>(); + + static { + DEFAULT_MODULES.put(BLOCKS, true); + DEFAULT_MODULES.put(PLAYER, true); + DEFAULT_MODULES.put(ADMIN_MODE, true); + DEFAULT_MODULES.put(TPSBAR, true); + DEFAULT_MODULES.put(JEI_SYNC, true); + DEFAULT_MODULES.put(MOB_DROPS, false); + } + + private final JavaPlugin plugin; + private final File modulesFile; + private FileConfiguration modulesConfig; + + public ModuleManager(JavaPlugin plugin) { + this.plugin = plugin; + this.modulesFile = new File(plugin.getDataFolder(), "modules.yml"); + reload(); + } + + public void reload() { + if (!modulesFile.exists()) { + plugin.saveResource("modules.yml", false); + } + + modulesConfig = YamlConfiguration.loadConfiguration(modulesFile); + 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); + save(); + } + + public boolean isEnabled(String moduleKey) { + if (moduleKey == null || moduleKey.isBlank()) { + return true; + } + return modulesConfig.getBoolean(path(moduleKey), true); + } + + private String path(String moduleKey) { + return "modules." + moduleKey + ".enabled"; + } + + private void save() { + try { + modulesConfig.save(modulesFile); + } catch (IOException e) { + plugin.getLogger().warning("保存 modules.yml 失败: " + e.getMessage()); + } + } +} diff --git a/src/main/java/cn/infstar/essentialsC/admin/AdminModeManager.java b/src/main/java/cn/infstar/essentialsC/admin/AdminModeManager.java index 19f694c..5b52ee4 100644 --- a/src/main/java/cn/infstar/essentialsC/admin/AdminModeManager.java +++ b/src/main/java/cn/infstar/essentialsC/admin/AdminModeManager.java @@ -328,7 +328,7 @@ public final class AdminModeManager implements Listener { try { data.save(dataFile); } catch (IOException e) { - plugin.getLogger().warning("Failed to save admin-mode.yml: " + e.getMessage()); + plugin.getLogger().warning("保存 admin-mode.yml 失败: " + e.getMessage()); } } diff --git a/src/main/java/cn/infstar/essentialsC/commands/AdminCommand.java b/src/main/java/cn/infstar/essentialsC/commands/AdminCommand.java index bc6072c..9f3a098 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/AdminCommand.java +++ b/src/main/java/cn/infstar/essentialsC/commands/AdminCommand.java @@ -10,6 +10,10 @@ public class AdminCommand extends BaseCommand { @Override protected boolean execute(Player player, String[] args) { + if (plugin.getAdminModeManager() == null) { + player.sendMessage(getLang().getPrefixedString("messages.module-disabled")); + return true; + } plugin.getAdminModeManager().toggle(player); return true; } diff --git a/src/main/java/cn/infstar/essentialsC/commands/CommandRegistry.java b/src/main/java/cn/infstar/essentialsC/commands/CommandRegistry.java index 90146dc..c74b423 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/CommandRegistry.java +++ b/src/main/java/cn/infstar/essentialsC/commands/CommandRegistry.java @@ -1,6 +1,7 @@ package cn.infstar.essentialsC.commands; import cn.infstar.essentialsC.EssentialsC; +import cn.infstar.essentialsC.ModuleManager; import cn.infstar.essentialsC.tpsbar.TpsBarService; import java.lang.reflect.Constructor; @@ -20,44 +21,44 @@ public final class CommandRegistry { private static final Set UNAVAILABLE_COMMANDS = new java.util.HashSet<>(); static { - register("workbench", "essentialsc.command.workbench", "cn.infstar.essentialsC.commands.WorkbenchCommand", "wb"); - register("anvil", "essentialsc.command.anvil", "cn.infstar.essentialsC.commands.AnvilCommand"); - register("cartographytable", "essentialsc.command.cartographytable", "cn.infstar.essentialsC.commands.CartographyTableCommand", "ct", "cartography"); - register("grindstone", "essentialsc.command.grindstone", "cn.infstar.essentialsC.commands.GrindstoneCommand", "gs"); - register("loom", "essentialsc.command.loom", "cn.infstar.essentialsC.commands.LoomCommand"); - register("smithingtable", "essentialsc.command.smithingtable", "cn.infstar.essentialsC.commands.SmithingTableCommand", "st", "smithing"); - register("stonecutter", "essentialsc.command.stonecutter", "cn.infstar.essentialsC.commands.StonecutterCommand", "sc"); - register("enderchest", "essentialsc.command.enderchest", "cn.infstar.essentialsC.commands.EnderChestCommand", "ec"); - register("blocks", "essentialsc.command.blocks", "cn.infstar.essentialsC.commands.BlocksMenuCommand"); - register("hat", "essentialsc.command.hat", "cn.infstar.essentialsC.commands.HatCommand"); - register("suicide", "essentialsc.command.suicide", "cn.infstar.essentialsC.commands.SuicideCommand", "die"); - register("fly", "essentialsc.command.fly", "cn.infstar.essentialsC.commands.FlyCommand"); - register("nightvision", "essentialsc.command.nightvision", "cn.infstar.essentialsC.commands.NightVisionCommand", "nv"); - register("glow", "essentialsc.command.glow", "cn.infstar.essentialsC.commands.GlowCommand"); - register("heal", "essentialsc.command.heal", "cn.infstar.essentialsC.commands.HealCommand"); - register("vanish", "essentialsc.command.vanish", "cn.infstar.essentialsC.commands.VanishCommand", "v"); - register("seen", "essentialsc.command.seen", "cn.infstar.essentialsC.commands.SeenCommand", "info"); - register("feed", "essentialsc.command.feed", "cn.infstar.essentialsC.commands.FeedCommand"); - register("repair", "essentialsc.command.repair", "cn.infstar.essentialsC.commands.RepairCommand", "rep"); - register("tpsbar", "essentialsc.command.tpsbar", "cn.infstar.essentialsC.commands.TpsBarCommand"); - register("mobdrops", "essentialsc.mobdrops.enderman", "cn.infstar.essentialsC.commands.MobDropCommand"); - registerSubCommand("admin", "essentialsc.command.admin", "cn.infstar.essentialsC.commands.AdminCommand"); + register("workbench", "essentialsc.command.workbench", ModuleManager.BLOCKS, "cn.infstar.essentialsC.commands.WorkbenchCommand", "wb"); + register("anvil", "essentialsc.command.anvil", ModuleManager.BLOCKS, "cn.infstar.essentialsC.commands.AnvilCommand"); + register("cartographytable", "essentialsc.command.cartographytable", ModuleManager.BLOCKS, "cn.infstar.essentialsC.commands.CartographyTableCommand", "ct", "cartography"); + register("grindstone", "essentialsc.command.grindstone", ModuleManager.BLOCKS, "cn.infstar.essentialsC.commands.GrindstoneCommand", "gs"); + register("loom", "essentialsc.command.loom", ModuleManager.BLOCKS, "cn.infstar.essentialsC.commands.LoomCommand"); + register("smithingtable", "essentialsc.command.smithingtable", ModuleManager.BLOCKS, "cn.infstar.essentialsC.commands.SmithingTableCommand", "st", "smithing"); + register("stonecutter", "essentialsc.command.stonecutter", ModuleManager.BLOCKS, "cn.infstar.essentialsC.commands.StonecutterCommand", "sc"); + register("enderchest", "essentialsc.command.enderchest", ModuleManager.BLOCKS, "cn.infstar.essentialsC.commands.EnderChestCommand", "ec"); + register("blocks", "essentialsc.command.blocks", ModuleManager.BLOCKS, "cn.infstar.essentialsC.commands.BlocksMenuCommand"); + register("hat", "essentialsc.command.hat", ModuleManager.PLAYER, "cn.infstar.essentialsC.commands.HatCommand"); + register("suicide", "essentialsc.command.suicide", ModuleManager.PLAYER, "cn.infstar.essentialsC.commands.SuicideCommand", "die"); + register("fly", "essentialsc.command.fly", ModuleManager.PLAYER, "cn.infstar.essentialsC.commands.FlyCommand"); + register("nightvision", "essentialsc.command.nightvision", ModuleManager.PLAYER, "cn.infstar.essentialsC.commands.NightVisionCommand", "nv"); + register("glow", "essentialsc.command.glow", ModuleManager.PLAYER, "cn.infstar.essentialsC.commands.GlowCommand"); + register("heal", "essentialsc.command.heal", ModuleManager.PLAYER, "cn.infstar.essentialsC.commands.HealCommand"); + register("vanish", "essentialsc.command.vanish", ModuleManager.PLAYER, "cn.infstar.essentialsC.commands.VanishCommand", "v"); + register("seen", "essentialsc.command.seen", ModuleManager.PLAYER, "cn.infstar.essentialsC.commands.SeenCommand", "info"); + register("feed", "essentialsc.command.feed", ModuleManager.PLAYER, "cn.infstar.essentialsC.commands.FeedCommand"); + register("repair", "essentialsc.command.repair", ModuleManager.PLAYER, "cn.infstar.essentialsC.commands.RepairCommand", "rep"); + register("tpsbar", "essentialsc.command.tpsbar", ModuleManager.TPSBAR, "cn.infstar.essentialsC.commands.TpsBarCommand"); + register("mobdrops", "essentialsc.mobdrops.enderman", ModuleManager.MOB_DROPS, "cn.infstar.essentialsC.commands.MobDropCommand"); + registerSubCommand("admin", "essentialsc.command.admin", ModuleManager.ADMIN_MODE, "cn.infstar.essentialsC.commands.AdminCommand"); } private CommandRegistry() { } - private static void register(String name, String permission, String className, String... aliases) { - register(name, permission, className, true, aliases); + private static void register(String name, String permission, String moduleKey, String className, String... aliases) { + register(name, permission, moduleKey, className, true, aliases); } - private static void registerSubCommand(String name, String permission, String className, String... aliases) { - register(name, permission, className, false, aliases); + private static void registerSubCommand(String name, String permission, String moduleKey, String className, String... aliases) { + register(name, permission, moduleKey, className, false, aliases); } - private static void register(String name, String permission, String className, boolean standalone, String... aliases) { + private static void register(String name, String permission, String moduleKey, String className, boolean standalone, String... aliases) { List aliasList = List.of(aliases); - CommandSpec spec = new CommandSpec(name, permission, className, aliasList, standalone); + CommandSpec spec = new CommandSpec(name, permission, moduleKey, className, aliasList, standalone); COMMANDS.put(name, spec); ALIAS_TO_COMMAND.put(name, name); for (String alias : aliasList) { @@ -91,6 +92,10 @@ public final class CommandRegistry { return null; } + if (isRuntimeDisabled(resolvedName)) { + return null; + } + BaseCommand cached = COMMAND_CACHE.get(resolvedName); if (cached != null) { return cached; @@ -98,9 +103,6 @@ public final class CommandRegistry { if (UNAVAILABLE_COMMANDS.contains(resolvedName)) { return null; } - if (isRuntimeDisabled(resolvedName)) { - return null; - } CommandSpec spec = COMMANDS.get(resolvedName); if (spec == null) { @@ -125,6 +127,21 @@ public final class CommandRegistry { } private static boolean isRuntimeDisabled(String resolvedName) { + CommandSpec spec = COMMANDS.get(resolvedName); + if (spec == null) { + return true; + } + + try { + EssentialsC plugin = EssentialsC.getPlugin(EssentialsC.class); + ModuleManager moduleManager = plugin.getModuleManager(); + if (moduleManager != null && !moduleManager.isEnabled(spec.moduleKey())) { + return true; + } + } catch (IllegalStateException ignored) { + return false; + } + if (!"tpsbar".equals(resolvedName)) { return false; } @@ -138,6 +155,11 @@ public final class CommandRegistry { } } - public record CommandSpec(String name, String permission, String className, List aliases, boolean standalone) { + public static void clearCache() { + COMMAND_CACHE.clear(); + UNAVAILABLE_COMMANDS.clear(); + } + + public record CommandSpec(String name, String permission, String moduleKey, String className, List aliases, boolean standalone) { } } diff --git a/src/main/java/cn/infstar/essentialsC/commands/HelpCommand.java b/src/main/java/cn/infstar/essentialsC/commands/HelpCommand.java index 45c3ea7..f64fd49 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/HelpCommand.java +++ b/src/main/java/cn/infstar/essentialsC/commands/HelpCommand.java @@ -2,6 +2,7 @@ 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; @@ -34,9 +35,15 @@ public class HelpCommand extends BaseCommand implements TabCompleter { } plugin.reloadConfig(); EssentialsC.getLangManager().reload(); + plugin.getModuleManager().reload(); + CommandRegistry.clearCache(); TpsBarService tpsBarService = plugin.getTpsBarManager(); if (tpsBarService != null) { - tpsBarService.reloadSettings(); + if (plugin.getModuleManager().isEnabled(ModuleManager.TPSBAR)) { + tpsBarService.reloadSettings(); + } else { + tpsBarService.shutdown(); + } } sender.sendMessage(getLang().getPrefixedString("messages.config-reloaded")); return true; @@ -56,9 +63,15 @@ public class HelpCommand extends BaseCommand implements TabCompleter { } plugin.reloadConfig(); EssentialsC.getLangManager().reload(); + plugin.getModuleManager().reload(); + CommandRegistry.clearCache(); TpsBarService tpsBarService = plugin.getTpsBarManager(); if (tpsBarService != null) { - tpsBarService.reloadSettings(); + if (plugin.getModuleManager().isEnabled(ModuleManager.TPSBAR)) { + tpsBarService.reloadSettings(); + } else { + tpsBarService.shutdown(); + } } sender.sendMessage(getLang().getPrefixedString("messages.config-reloaded")); return true; diff --git a/src/main/java/cn/infstar/essentialsC/commands/SeenCommand.java b/src/main/java/cn/infstar/essentialsC/commands/SeenCommand.java index e1a72f1..2f7497b 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/SeenCommand.java +++ b/src/main/java/cn/infstar/essentialsC/commands/SeenCommand.java @@ -30,26 +30,26 @@ public class SeenCommand extends BaseCommand { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); StringBuilder info = new StringBuilder(); - info.append(getLang().getPrefix()).append(ChatColor.GOLD).append("Player info: ") + info.append(getLang().getPrefix()).append(ChatColor.GOLD).append("玩家信息: ") .append(ChatColor.WHITE).append(target.getName()).append("\n"); if (target.isOnline()) { - info.append(ChatColor.GRAY).append("Status: ").append(ChatColor.GREEN).append("Online").append("\n"); + info.append(ChatColor.GRAY).append("状态: ").append(ChatColor.GREEN).append("在线").append("\n"); Player onlinePlayer = target.getPlayer(); if (onlinePlayer != null) { - info.append(ChatColor.GRAY).append("World: ").append(ChatColor.WHITE) + info.append(ChatColor.GRAY).append("所在世界: ").append(ChatColor.WHITE) .append(onlinePlayer.getWorld().getName()).append("\n"); } } else { - info.append(ChatColor.GRAY).append("Status: ").append(ChatColor.RED).append("Offline").append("\n"); + info.append(ChatColor.GRAY).append("状态: ").append(ChatColor.RED).append("离线").append("\n"); long lastSeen = target.getLastSeen(); if (lastSeen > 0) { - info.append(ChatColor.GRAY).append("Last seen: ").append(ChatColor.WHITE) + info.append(ChatColor.GRAY).append("最后在线: ").append(ChatColor.WHITE) .append(format.format(new Date(lastSeen))).append("\n"); } } - info.append(ChatColor.GRAY).append("First joined: ").append(ChatColor.WHITE) + info.append(ChatColor.GRAY).append("首次加入: ").append(ChatColor.WHITE) .append(format.format(new Date(target.getFirstPlayed()))); player.sendMessage(info.toString()); diff --git a/src/main/java/cn/infstar/essentialsC/commands/TpsBarCommand.java b/src/main/java/cn/infstar/essentialsC/commands/TpsBarCommand.java index d3b2ba6..3647e09 100644 --- a/src/main/java/cn/infstar/essentialsC/commands/TpsBarCommand.java +++ b/src/main/java/cn/infstar/essentialsC/commands/TpsBarCommand.java @@ -22,7 +22,7 @@ public class TpsBarCommand extends BaseCommand implements TabCompleter { protected boolean execute(Player player, String[] args) { TpsBarService tpsBarService = plugin.getTpsBarManager(); if (tpsBarService == null) { - player.sendMessage(getLang().getPrefixedString("messages.player-only")); + player.sendMessage(getLang().getPrefixedString("messages.module-disabled")); return true; } diff --git a/src/main/java/cn/infstar/essentialsC/listeners/ShulkerBoxListener.java b/src/main/java/cn/infstar/essentialsC/listeners/ShulkerBoxListener.java index 878a764..406bc63 100644 --- a/src/main/java/cn/infstar/essentialsC/listeners/ShulkerBoxListener.java +++ b/src/main/java/cn/infstar/essentialsC/listeners/ShulkerBoxListener.java @@ -274,12 +274,12 @@ public class ShulkerBoxListener implements Listener { private void writeInventoryBack(ItemStack shulkerItem, ItemStack[] contents) { if (!(shulkerItem.getItemMeta() instanceof BlockStateMeta blockStateMeta)) { - plugin.getLogger().warning("Failed to save shulker box contents: missing BlockStateMeta."); + plugin.getLogger().warning("保存潜影盒内容失败: 缺少 BlockStateMeta。"); return; } if (!(blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBox)) { - plugin.getLogger().warning("Failed to save shulker box contents: block state is not a ShulkerBox."); + plugin.getLogger().warning("保存潜影盒内容失败: 方块状态不是 ShulkerBox。"); return; } diff --git a/src/main/resources/lang/en_US.yml b/src/main/resources/lang/en_US.yml index 95cdce3..2048541 100644 --- a/src/main/resources/lang/en_US.yml +++ b/src/main/resources/lang/en_US.yml @@ -10,6 +10,7 @@ messages: paper-version: "&7Running on Paper {version}" unknown-subcommand: "&cUnknown subcommand: {command}" help-usage: "&7Use &f/essc help &7to view available commands." + module-disabled: "&cThis feature module is currently disabled. Please contact an administrator." blocks-menu-empty: "&cYou do not currently have any available shortcut menu entries." hat-success: "&aYou are now wearing {item} on your head!" diff --git a/src/main/resources/lang/zh_CN.yml b/src/main/resources/lang/zh_CN.yml index c86c40a..ec42e07 100644 --- a/src/main/resources/lang/zh_CN.yml +++ b/src/main/resources/lang/zh_CN.yml @@ -10,6 +10,7 @@ messages: paper-version: "&7当前运行于 Paper {version}" unknown-subcommand: "&c未知子命令:{command}" help-usage: "&7使用 &f/essc help &7查看可用命令。" + module-disabled: "&c该功能模块当前已关闭,请联系管理员。" blocks-menu-empty: "&c你当前没有可用的便捷菜单项目。" hat-success: "&a你已将 {item} 戴在头上!" diff --git a/src/main/resources/modules.yml b/src/main/resources/modules.yml new file mode 100644 index 0000000..1ca22c6 --- /dev/null +++ b/src/main/resources/modules.yml @@ -0,0 +1,28 @@ +# EssentialsC 模块配置 +# +# modules.yml 用于控制功能模块是否在运行时加载。 +# config.yml 仍然用于控制已启用模块内部的具体行为。 +# +# 将模块设置为 false 后,建议重启服务器,以完全注销对应命令和监听器。 + +config-version: 1 + +modules: + blocks: + # 工作台、铁砧等便捷方块命令、/essc blocks 菜单和潜影盒快捷打开。 + enabled: true + player: + # 玩家常用命令:飞行、夜视、发光、治疗、喂食、修复、帽子、自杀、隐身和查询玩家。 + enabled: true + admin-mode: + # /essc admin 管理模式,以及独立背包和状态管理。 + enabled: true + tpsbar: + # 插件版 TPSBar。config.yml 中的 tpsbar.mode 仍会决定遇到原生 /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 aa650e7..f7e2184 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -89,6 +89,9 @@ permissions: essentialsc.command.tpsbar: description: 允许使用 /tpsbar default: op + essentialsc.command.tpsbar.others: + description: 允许为其他玩家切换 /tpsbar + default: op essentialsc.shulkerbox.open: description: 允许通过 Shift+右键快捷打开潜影盒 default: op @@ -125,5 +128,6 @@ permissions: essentialsc.command.help: true essentialsc.command.reload: true essentialsc.command.tpsbar: true + essentialsc.command.tpsbar.others: true essentialsc.shulkerbox.open: true essentialsc.mobdrops.enderman: true