+ * This is what makes edits to the template file take effect on {@code bbox reload}: + * without it, {@link #reload()} only re-reads the database and template changes are + * silently ignored. + * + * @param gameModeAddon Game mode whose panels must be re-imported from the template. + */ + public void reimportControlPanels(@NotNull GameModeAddon gameModeAddon) + { + // Clear existing data so removed/renamed panels do not linger, then re-read the template. + this.wipeData(gameModeAddon, null); + this.importControlPanels(null, gameModeAddon); + } + + // --------------------------------------------------------------------- // Section: Save methods // --------------------------------------------------------------------- @@ -226,7 +247,7 @@ public void importControlPanels(@Nullable User user, @NotNull World world, @NotN */ public void importControlPanels(@Nullable User user, @NotNull GameModeAddon addon) { - this.importControlPanels(user, addon, "controlPanelTemplate"); + this.importControlPanels(user, addon, this.addon.getSettings().getTemplateFile()); } diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml index de01fef..76dc9d8 100644 --- a/src/main/resources/addon.yml +++ b/src/main/resources/addon.yml @@ -12,7 +12,7 @@ repository: 'BentoBoxWorld/ControlPanel' # Must use Material.values() with uppercase. icon: 'COMMAND_BLOCK' # Minimum BentoBox API version -api-version: 3.10.0 +api-version: 2.7.1 # List of addon authors. authors: diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 84d005c..2d3e59d 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -5,3 +5,9 @@ # disabled-gamemodes: # - BSkyBlock disabled-gamemodes: [] +# +# The template file that control panels are imported from. +# This file is read when panels are first seeded and re-read on every reload, +# so editing it and running 'bbox reload' will apply your changes. +# It must exist in the addon folder (addons/ControlPanel). +template-file: controlPanelTemplate.yml diff --git a/src/main/resources/locales/zh-TW.yml b/src/main/resources/locales/zh-TW.yml index 9549694..f312fd3 100644 --- a/src/main/resources/locales/zh-TW.yml +++ b/src/main/resources/locales/zh-TW.yml @@ -1,23 +1,21 @@ ---- controlpanel: messages: - import-count: 導入 [number] 控制面板 - control-panels-wiped: "&2 所有為 [gamemode] 模式的控制面板已移除" + import-count: 已匯入 [number] 個控制面板。 + control-panels-wiped: '&2已移除 [gamemode] 的所有控制面板。' errors: - no-load: "&4 無法讀取 [file] 原因: [message]" - no-file: "&4 遺失 [file] 檔案" - no-valid-panels: "&4 未發現任何可用的控制面板" - no-valid-panels-op: "&4 未發現任何可用的控制面板. 你應該試著去導入他 請使用 /[admin] cp import" - not-a-gamemode-world: "&4 未發現任何有效的模式" + no-load: '&4無法載入檔案 [file]:[message]' + no-file: '&4找不到檔案 [file]。' + not-a-gamemode-world: '&4找不到有效的遊戲模式。' + no-valid-panels: '&4找不到任何有效的控制面板。' + no-valid-panels-op: '&4找不到任何有效的控制面板。請嘗試使用 /[admin] cp import 匯入。' commands: admin: help: - description: '顯示管理員幫助指令 - -' + description: 顯示管理員說明指令。 import: - parameters: "[file-name] - 要導入的檔案的名子. 檔案為空 所以我們將創建 一般面板." - description: 導入控制面板從 [file] - confirmation: 請確認您要從 [gamemode] 中刪除現有控制面板,並從 [file] 中導入控制面板 + parameters: '[file-name] - 要匯入的檔案名稱。留空則匯入預設範本。' + description: 從 [file] 匯入控制面板。 + confirmation: 請確認是否要移除 [gamemode] 目前的控制面板,並從 [file] 匯入新的控制面板。 help: - description: 開啟控制面板 + parameters: '' + description: 開啟控制面板。 diff --git a/src/test/java/world/bentobox/controlpanel/config/SettingsTest.java b/src/test/java/world/bentobox/controlpanel/config/SettingsTest.java index fca8b04..0e37f60 100644 --- a/src/test/java/world/bentobox/controlpanel/config/SettingsTest.java +++ b/src/test/java/world/bentobox/controlpanel/config/SettingsTest.java @@ -35,4 +35,15 @@ void testSetDisabledGameModes() { assertTrue(settings.getDisabledGameModes().contains("BSkyBlock")); assertTrue(settings.getDisabledGameModes().contains("AcidIsland")); } + + @Test + void testDefaultTemplateFile() { + assertEquals("controlPanelTemplate.yml", settings.getTemplateFile()); + } + + @Test + void testSetTemplateFile() { + settings.setTemplateFile("myPanels.yml"); + assertEquals("myPanels.yml", settings.getTemplateFile()); + } } diff --git a/src/test/java/world/bentobox/controlpanel/managers/ControlPanelManagerTest.java b/src/test/java/world/bentobox/controlpanel/managers/ControlPanelManagerTest.java index aaf0186..02e3dc3 100644 --- a/src/test/java/world/bentobox/controlpanel/managers/ControlPanelManagerTest.java +++ b/src/test/java/world/bentobox/controlpanel/managers/ControlPanelManagerTest.java @@ -57,6 +57,7 @@ public void setUp() throws Exception { when(addon.getPlugin()).thenReturn(plugin); when(addon.getLogger()).thenReturn(Logger.getAnonymousLogger()); + when(addon.getSettings()).thenReturn(new world.bentobox.controlpanel.config.Settings()); File dataFolder = new File("addons/ControlPanel"); dataFolder.mkdirs(); @@ -260,6 +261,96 @@ void testReload() { manager.reload(); } + @Test + void testReimportControlPanelsReadsTemplateFile() throws Exception { + // Make import's trailing getAddonManager().save() call resolve to this manager. + when(addon.getAddonManager()).thenReturn(manager); + + // Write a template file that reimport should read from disk. + writeTemplate(""" + panel-list: + default: + defaultPanel: true + panelName: 'From Template' + permission: 'default' + buttons: + 0: + name: 'Island' + material: GRASS_BLOCK + description: 'Go to your island' + command: '[label] go' + """); + + GameModeAddon gameModeAddon = mock(GameModeAddon.class); + manager.reimportControlPanels(gameModeAddon); + + // Template panel is now in the cache. + assertTrue(manager.hasAnyControlPanel(world)); + ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock."); + assertNotNull(panel); + assertEquals("From Template", panel.getPanelName()); + } + + @Test + void testReimportControlPanelsRemovesDeletedPanels() throws Exception { + when(addon.getAddonManager()).thenReturn(manager); + + // Seed the cache with an existing panel, as if previously stored in the database. + when(handler.loadObjects()).thenReturn(Collections.singletonList(createDefaultPanel())); + manager.reload(); + assertTrue(manager.hasAnyControlPanel(world)); + + // A template with no panel-list entries should wipe the stored panel on reimport. + writeTemplate("panel-list: {}\n"); + + GameModeAddon gameModeAddon = mock(GameModeAddon.class); + manager.reimportControlPanels(gameModeAddon); + + assertFalse(manager.hasAnyControlPanel(world)); + } + + @Test + void testReimportControlPanelsUsesConfiguredTemplateFile() throws Exception { + // Point the config at a custom template file name. + world.bentobox.controlpanel.config.Settings settings = + new world.bentobox.controlpanel.config.Settings(); + settings.setTemplateFile("customPanels.yml"); + when(addon.getSettings()).thenReturn(settings); + when(addon.getAddonManager()).thenReturn(manager); + + // The default template exists but is empty; only the custom file has a panel. + writeTemplate("panel-list: {}\n"); + writeNamedTemplate("customPanels.yml", """ + panel-list: + default: + defaultPanel: true + panelName: 'From Custom File' + permission: 'default' + buttons: + 0: + name: 'Island' + material: GRASS_BLOCK + description: 'Go to your island' + command: '[label] go' + """); + + GameModeAddon gameModeAddon = mock(GameModeAddon.class); + manager.reimportControlPanels(gameModeAddon); + + ControlPanelObject panel = manager.getUserControlPanel(User.getInstance(mockPlayer), world, "bskyblock."); + assertNotNull(panel); + assertEquals("From Custom File", panel.getPanelName()); + } + + private void writeTemplate(String content) throws Exception { + writeNamedTemplate("controlPanelTemplate.yml", content); + } + + private void writeNamedTemplate(String name, String content) throws Exception { + File templateFile = new File(new File("addons/ControlPanel"), name); + java.nio.file.Files.writeString(templateFile.toPath(), content); + } + private ControlPanelObject createDefaultPanel() { ControlPanelObject cpo = new ControlPanelObject(); cpo.setUniqueId("BSkyBlock_default");