diff --git a/src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java b/src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java index 183b882..ad5d79b 100644 --- a/src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java +++ b/src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java @@ -138,7 +138,19 @@ public void onReload() } else { + // Reload the database into cache first so wipe/re-import operate on a + // consistent view of what is currently stored. this.manager.reload(); + + // Re-import each active game mode's panels from controlPanelTemplate.yml so + // that admin edits to the template take effect on reload. Without this the + // panels stay pinned to whatever was first seeded into the database. + this.getPlugin().getAddonsManager().getGameModeAddons().forEach(gameModeAddon -> { + if (!this.settings.getDisabledGameModes().contains(gameModeAddon.getDescription().getName())) + { + this.manager.reimportControlPanels(gameModeAddon); + } + }); } } diff --git a/src/main/java/world/bentobox/controlpanel/commands/admin/AdminCommand.java b/src/main/java/world/bentobox/controlpanel/commands/admin/AdminCommand.java index f3d5fa0..9f26223 100644 --- a/src/main/java/world/bentobox/controlpanel/commands/admin/AdminCommand.java +++ b/src/main/java/world/bentobox/controlpanel/commands/admin/AdminCommand.java @@ -127,9 +127,10 @@ public void setup() @Override public boolean execute(User user, String label, List args) { - ControlPanelManager manager = this.getAddon().getAddonManager(); + ControlPanelAddon addon = this.getAddon(); + ControlPanelManager manager = addon.getAddonManager(); - String fileName = args.size() == 1 ? args.get(0) : "controlPanelTemplate.yml"; + String fileName = args.size() == 1 ? args.get(0) : addon.getSettings().getTemplateFile(); if (manager.hasAnyControlPanel(this.getWorld())) { diff --git a/src/main/java/world/bentobox/controlpanel/config/Settings.java b/src/main/java/world/bentobox/controlpanel/config/Settings.java index 7be79aa..9c03862 100644 --- a/src/main/java/world/bentobox/controlpanel/config/Settings.java +++ b/src/main/java/world/bentobox/controlpanel/config/Settings.java @@ -52,6 +52,28 @@ public void setDisabledGameModes(Set disabledGameModes) } + /** + * This method returns the templateFile value. + * + * @return the value of templateFile. + */ + public String getTemplateFile() + { + return templateFile; + } + + + /** + * This method sets the templateFile value. + * + * @param templateFile the templateFile new value. + */ + public void setTemplateFile(String templateFile) + { + this.templateFile = templateFile; + } + + // --------------------------------------------------------------------- // Section: Variables // --------------------------------------------------------------------- @@ -64,4 +86,12 @@ public void setDisabledGameModes(Set disabledGameModes) @ConfigComment(" - BSkyBlock") @ConfigEntry(path = "disabled-gamemodes") private Set disabledGameModes = new HashSet<>(); + + @ConfigComment("") + @ConfigComment("The template file that control panels are imported from.") + @ConfigComment("This file is read when panels are first seeded and re-read on every reload,") + @ConfigComment("so editing it and running 'bbox reload' will apply your changes.") + @ConfigComment("It must exist in the addon folder (addons/ControlPanel).") + @ConfigEntry(path = "template-file") + private String templateFile = "controlPanelTemplate.yml"; } diff --git a/src/main/java/world/bentobox/controlpanel/managers/ControlPanelManager.java b/src/main/java/world/bentobox/controlpanel/managers/ControlPanelManager.java index e7cc2f0..ed87ed6 100644 --- a/src/main/java/world/bentobox/controlpanel/managers/ControlPanelManager.java +++ b/src/main/java/world/bentobox/controlpanel/managers/ControlPanelManager.java @@ -55,7 +55,8 @@ public ControlPanelManager(ControlPanelAddon addon) { this.addon = addon; - // save template file into directory. + // Save the default template file into the directory. The configured template file + // may point elsewhere, but the packaged default is always named controlPanelTemplate.yml. if (!new File(this.addon.getDataFolder(), "controlPanelTemplate.yml").exists()) { this.addon.saveResource("controlPanelTemplate.yml", false); @@ -128,6 +129,26 @@ public void reload() } + /** + * This method re-imports the control panels for the given game mode from the + * template file configured via {@code template-file} in config.yml (defaults to + * {@code controlPanelTemplate.yml}), replacing whatever is currently stored in the + * database and cache. + *

+ * 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/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/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");