diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a1c2a23
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
diff --git a/README.md b/README.md
index a119d30..5294fdd 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,23 @@
-# ControlPanel
\ No newline at end of file
+# ControlPanel Addon
+[![Discord](https://img.shields.io/discord/272499714048524288.svg?logo=discord)](https://discord.bentobox.world)
+[![Build Status](https://ci.codemc.org/buildStatus/icon?job=BentoBoxWorld/ControlPanel)](https://ci.codemc.org/job/BentoBoxWorld/job/ControlPanel/)
+
+This is simple ControlPanel for all BentoBox GameMode addons. Allows to customize GUI for users.
+
+## How to use
+
+1. Place the addon jar in the addons folder of the BentoBox plugin
+2. Restart the server
+3. Use admin command to import control panels.
+
+## Compatibility
+
+- [x] BentoBox - 1.7.0 version
+- [x] BSkyBlock
+- [x] AcidIsland
+- [x] SkyGrid
+- [x] CaveBlock
+
+## Information
+
+More information can be found in [Wiki Pages](https://github.com/BentoBoxWorld/ControlPanel/wiki).
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..2428e13
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,245 @@
+
+
+ 4.0.0
+
+ world.bentobox
+ controlpanel
+ ${revision}
+
+ ControlPanel
+ This is simple ControlPanel for all BentoBox GameMode addons.
+ https://github.com/BentoBoxWorld/ControlPanel
+
+ 2019
+
+
+
+
+ scm:git:https://github.com/BentoBoxWorld/ControlPanel.git
+ scm:git:git@github.com:BentoBoxWorld/ControlPanel.git
+ https://github.com/BentoBoxWorld/ControlPanel
+
+
+
+
+
+ jenkins
+ http://ci.codemc.org/job/BentoBoxWorld/job/ControlPanel
+
+
+
+
+
+ GitHub
+ https://github.com/BentoBoxWorld/ControlPanel/issues
+
+
+
+
+
+
+ codemc-snapshots
+ https://repo.codemc.org/repository/maven-snapshots
+
+
+ codemc-releases
+ https://repo.codemc.org/repository/maven-releases
+
+
+
+
+
+
+ UTF-8
+ UTF-8
+ 1.8
+
+
+ 1.14.4-R0.1-SNAPSHOT
+
+
+ 1.7.0
+
+
+ ${build.version}-SNAPSHOT
+
+ 1.7.0
+ -LOCAL
+
+
+
+
+ ci
+
+
+ env.BUILD_NUMBER
+
+
+
+
+ -#${env.BUILD_NUMBER}
+
+
+
+
+ master
+
+
+ env.GIT_BRANCH
+ origin/master
+
+
+
+
+ ${build.version}
+
+
+
+
+
+
+
+
+
+ spigot-repo
+ https://hub.spigotmc.org/nexus/content/repositories/snapshots
+
+
+ spigotmc-public
+ https://hub.spigotmc.org/nexus/content/groups/public/
+
+
+ codemc-repo
+ https://repo.codemc.org/repository/maven-public/
+
+
+ vault-repo
+ http://nexus.hc.to/content/repositories/pub_releases
+
+
+
+
+
+
+ org.spigotmc
+ spigot-api
+ ${spigot.version}
+ provided
+
+
+
+ world.bentobox
+ bentobox
+ ${bentobox.version}
+ provided
+
+
+
+
+
+ ${project.name}-${revision}${build.number}
+
+ clean package
+
+
+ src/main/resources
+ true
+
+
+ src/main/resources/locales
+ ./locales
+ false
+
+ *.yml
+ *.json
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-clean-plugin
+ 3.1.0
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.1.0
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.7.0
+
+
+ ${java.version}
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.0
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.1.0
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.0.1
+
+ public
+ false
+ -Xdoclint:none
+
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.0.1
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.1.1
+
+ true
+
+
+
+ package
+
+ shade
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+ 2.5.2
+
+
+
+
+
diff --git a/src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java b/src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java
new file mode 100644
index 0000000..ed4376e
--- /dev/null
+++ b/src/main/java/world/bentobox/controlpanel/ControlPanelAddon.java
@@ -0,0 +1,199 @@
+package world.bentobox.controlpanel;
+
+
+import org.bukkit.Bukkit;
+
+import world.bentobox.bentobox.api.addons.Addon;
+import world.bentobox.bentobox.api.configuration.Config;
+import world.bentobox.controlpanel.commands.admin.AdminCommand;
+import world.bentobox.controlpanel.commands.user.PlayerCommand;
+import world.bentobox.controlpanel.config.Settings;
+import world.bentobox.controlpanel.managers.ControlPanelManager;
+
+
+/**
+ * This is main Addon class. It allows to load it into BentoBox hierarchy.
+ */
+public class ControlPanelAddon extends Addon
+{
+ // ---------------------------------------------------------------------
+ // Section: Methods
+ // ---------------------------------------------------------------------
+
+
+ /**
+ * Executes code when loading the addon. This is called before {@link #onEnable()}.
+ * This must be used to setup configuration, worlds and commands.
+ */
+ @Override
+ public void onLoad()
+ {
+ super.onLoad();
+
+ // in most of addons, onLoad we want to store default configuration if it does not
+ // exist and load it.
+
+ // Storing default configuration is simple. But be aware, you need
+ // @StoreAt(filename="config.yml", path="addons/Likes") in header of your Config file.
+ this.saveDefaultConfig();
+
+ this.settings = new Config<>(this, Settings.class).loadConfigObject();
+
+ if (this.settings == null)
+ {
+ // If we failed to load Settings then we should not enable addon.
+ // We can log error and set state to DISABLED.
+
+ this.logError("ControlPanel settings could not load! Addon disabled.");
+ this.setState(State.DISABLED);
+ }
+ }
+
+
+ /**
+ * Executes code when enabling the addon. This is called after {@link #onLoad()}.
+ * Note that commands and worlds registration must be done in {@link
+ * #onLoad()}, if need be. Failure to do so will result in issues such as
+ * tab-completion not working for commands.
+ */
+ @Override
+ public void onEnable()
+ {
+ // Check if it is enabled - it might be loaded, but not enabled.
+
+ if (this.getPlugin() == null || !this.getPlugin().isEnabled())
+ {
+ Bukkit.getLogger().severe("BentoBox is not available or disabled!");
+ this.setState(State.DISABLED);
+ return;
+ }
+
+ // Check if addon is not disabled before.
+
+ if (this.getState().equals(State.DISABLED))
+ {
+ Bukkit.getLogger().severe("ControlPanel Addon is not available or disabled!");
+ return;
+ }
+
+ // Initialize data manager
+ this.manager = new ControlPanelManager(this);
+
+ // If your addon wants to hook into other GameModes, f.e. use flags, then you should
+ // hook these flags into each GameMode.
+
+ // Fortunately BentoBox provides ability to a list of all loaded GameModes.
+
+ this.getPlugin().getAddonsManager().getGameModeAddons().forEach(gameModeAddon -> {
+ // In Settings (and config) we define DisabledGameModes, list of GameModes where
+ // current Addon should not work.
+ // This is where we do not hook current addon into GameMode addon.
+
+ if (!this.settings.getDisabledGameModes().contains(gameModeAddon.getDescription().getName()))
+ {
+ // Each GameMode could have Player Command and Admin Command and we could
+ // want to integrate our Example Command into these commands.
+ // It provides ability to call command with GameMode command f.e. "/island example"
+
+ // Of course we should check if these commands exists, as it is possible to
+ // create GameMode without them.
+
+ gameModeAddon.getPlayerCommand().ifPresent(
+ playerCommand -> new PlayerCommand(this, playerCommand));
+
+ gameModeAddon.getAdminCommand().ifPresent(
+ adminCommand -> new AdminCommand(this, adminCommand));
+ }
+ });
+ }
+
+
+ /**
+ * Executes code when reloading the addon.
+ */
+ @Override
+ public void onReload()
+ {
+ super.onReload();
+
+ // onReload most of addons just need to reload configuration.
+ // If flags, listeners and handlers were set up correctly via Addon.class then
+ // they will be reloaded automatically.
+
+ this.settings = new Config<>(this, Settings.class).loadConfigObject();
+
+ if (this.settings == null)
+ {
+ // If we failed to load Settings then we should not enable addon.
+ // We can log error and set state to DISABLED.
+
+ this.logError("ControlPanel settings could not load! Addon disabled.");
+ this.setState(State.DISABLED);
+ }
+ else
+ {
+ this.manager.reload();
+ }
+ }
+
+
+ /**
+ * Executes code when disabling the addon.
+ */
+ @Override
+ public void onDisable()
+ {
+ // onDisable we would like to save exisitng settings. It is not necessary for
+ // addons that does not have interface for settings editing!
+
+ if (this.settings != null)
+ {
+ new Config<>(this, Settings.class).saveConfigObject(this.settings);
+ }
+
+ this.manager.save();
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Getters
+// ---------------------------------------------------------------------
+
+
+ /**
+ * Method LikesAddon#getSettings returns the settings of this object.
+ *
+ * @return the settings (type Settings) of this object.
+ */
+ public Settings getSettings()
+ {
+ return this.settings;
+ }
+
+
+ /**
+ * Method ControlPanel#getManager returns the manager of this object.
+ *
+ * @return the manager (type ControlPanelManager) of this object.
+ */
+ public ControlPanelManager getAddonManager()
+ {
+ return this.manager;
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Variables
+// ---------------------------------------------------------------------
+
+
+ /**
+ * Settings object contains
+ */
+ private Settings settings;
+
+ /**
+ * Likes addon manager.
+ */
+ private ControlPanelManager manager;
+}
diff --git a/src/main/java/world/bentobox/controlpanel/commands/admin/AdminCommand.java b/src/main/java/world/bentobox/controlpanel/commands/admin/AdminCommand.java
new file mode 100644
index 0000000..2d4b7de
--- /dev/null
+++ b/src/main/java/world/bentobox/controlpanel/commands/admin/AdminCommand.java
@@ -0,0 +1,69 @@
+package world.bentobox.controlpanel.commands.admin;
+
+
+import java.util.List;
+
+import world.bentobox.bentobox.api.commands.CompositeCommand;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.controlpanel.ControlPanelAddon;
+import world.bentobox.controlpanel.utils.Constants;
+
+
+/**
+ * This class process /{gamemode_admin_command} example command call.
+ */
+public class AdminCommand extends CompositeCommand
+{
+ /**
+ * This is simple constructor for initializing /{gamemode_admin_command} example command.
+ * @param addon Our Example addon.
+ * @param parentCommand Parent Command where we hook our command into.
+ */
+ public AdminCommand(ControlPanelAddon addon, CompositeCommand parentCommand)
+ {
+ super(addon, parentCommand, "controlpanel", "cp");
+ }
+
+
+ /**
+ * Setups anything that is needed for this command.
It is recommended you
+ * do the following in this method:
+ *
+ *
Register any of the sub-commands of this command;
+ *
Define the permission required to use this command using {@link
+ * CompositeCommand#setPermission(String)};
+ *
Define whether this command can only be run by players or not using {@link
+ * CompositeCommand#setOnlyPlayer(boolean)};
+ *
+ */
+ @Override
+ public void setup()
+ {
+ this.setPermission("controlpanel.admin");
+ this.setParametersHelp(Constants.COMMANDS + "admin.help.parameters");
+ this.setDescription(Constants.COMMANDS + "admin.help.description");
+
+ // Import Command ?
+ new ImportCommand(this.getAddon(), this);
+ // Edit Panel ?
+
+ // Settings Panel ?
+ }
+
+
+ /**
+ * Defines what will be executed when this command is run.
+ *
+ * @param user the {@link User} who is executing this command.
+ * @param label the label which has been used to execute this command. It can be
+ * {@link CompositeCommand#getLabel()} or an alias.
+ * @param args the command arguments.
+ * @return {@code true} if the command executed successfully, {@code false} otherwise.
+ */
+ @Override
+ public boolean execute(User user, String label, List args)
+ {
+ this.showHelp(this, user);
+ return true;
+ }
+}
diff --git a/src/main/java/world/bentobox/controlpanel/commands/admin/ImportCommand.java b/src/main/java/world/bentobox/controlpanel/commands/admin/ImportCommand.java
new file mode 100644
index 0000000..f324bf4
--- /dev/null
+++ b/src/main/java/world/bentobox/controlpanel/commands/admin/ImportCommand.java
@@ -0,0 +1,53 @@
+package world.bentobox.controlpanel.commands.admin;
+
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import world.bentobox.bentobox.api.addons.Addon;
+import world.bentobox.bentobox.api.commands.CompositeCommand;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.bentobox.util.Util;
+import world.bentobox.controlpanel.ControlPanelAddon;
+import world.bentobox.controlpanel.utils.Constants;
+
+
+/**
+ * This class process "cp import" command for admins.
+ */
+public class ImportCommand extends CompositeCommand
+{
+ public ImportCommand(Addon addon, CompositeCommand cmd)
+ {
+ super(addon, cmd, "import");
+ }
+
+
+ @Override
+ public void setup()
+ {
+ this.setPermission("controlpanel.admin.import");
+ this.setParametersHelp(Constants.COMMANDS + "admin.import.parameters");
+ this.setDescription(Constants.COMMANDS + "admin.import.description");
+ }
+
+
+ @Override
+ public boolean execute(User user, String label, List args)
+ {
+ ((ControlPanelAddon) this.getAddon()).getAddonManager().importControlPanels(user,
+ this.getWorld(),
+ !args.isEmpty() && args.get(0).equalsIgnoreCase("overwrite"));
+
+ return true;
+ }
+
+
+ @Override
+ public Optional> tabComplete(User user, String alias, List args)
+ {
+ String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : "";
+ return Optional.of(Util.tabLimit(Arrays.asList("overwrite"), lastArg));
+ }
+}
diff --git a/src/main/java/world/bentobox/controlpanel/commands/user/PlayerCommand.java b/src/main/java/world/bentobox/controlpanel/commands/user/PlayerCommand.java
new file mode 100644
index 0000000..aeb6998
--- /dev/null
+++ b/src/main/java/world/bentobox/controlpanel/commands/user/PlayerCommand.java
@@ -0,0 +1,119 @@
+package world.bentobox.controlpanel.commands.user;
+
+
+import java.util.List;
+
+import world.bentobox.bentobox.api.commands.CompositeCommand;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.controlpanel.ControlPanelAddon;
+import world.bentobox.controlpanel.database.objects.ControlPanelObject;
+import world.bentobox.controlpanel.panels.ControlPanelGenerator;
+import world.bentobox.controlpanel.utils.Constants;
+
+
+/**
+ * This class process /{gamemode_player_command} example command call.
+ */
+public class PlayerCommand extends CompositeCommand
+{
+ /**
+ * This is simple constructor for initializing /{gamemode_player_command} example command.
+ * @param addon Our Example addon.
+ * @param parentCommand Parent Command where we hook our command into.
+ */
+ public PlayerCommand(ControlPanelAddon addon, CompositeCommand parentCommand)
+ {
+ super(addon, parentCommand, "controlpanel", "cp");
+ }
+
+
+ /**
+ * Setups anything that is needed for this command.
It is recommended you
+ * do the following in this method:
+ *
+ *
Register any of the sub-commands of this command;
+ *
Define the permission required to use this command using {@link
+ * CompositeCommand#setPermission(String)};
+ *
Define whether this command can only be run by players or not using {@link
+ * CompositeCommand#setOnlyPlayer(boolean)};
+ *
+ */
+ @Override
+ public void setup()
+ {
+ this.setPermission("controlpanel");
+ this.setOnlyPlayer(true);
+ this.setParametersHelp(Constants.COMMANDS + "help.parameters");
+ this.setDescription(Constants.COMMANDS + "help.description");
+ }
+
+
+ /**
+ * Returns whether the command can be executed by this user or not. It is recommended
+ * to send messages to let this user know why they could not execute the command. Note
+ * that this is run previous to {@link #execute(User, String, List)}.
+ *
+ * @param user the {@link User} who is executing this command.
+ * @param label the label which has been used to execute this command. It can be
+ * {@link CompositeCommand#getLabel()} or an alias.
+ * @param args the command arguments.
+ * @return {@code true} if this command can be executed, {@code false} otherwise.
+ * @since 1.3.0
+ */
+ @Override
+ public boolean canExecute(User user, String label, List args)
+ {
+ this.panelObject = ((ControlPanelAddon) this.getAddon()).
+ getAddonManager().getUserControlPanel(user, this.getWorld(), this.getPermissionPrefix());
+
+ if (this.panelObject == null)
+ {
+ if (user.isOp())
+ {
+ user.sendMessage(Constants.ERRORS + "no-valid-panels-op");
+ }
+ else
+ {
+ user.sendMessage(Constants.ERRORS + "no-valid-panels");
+ }
+
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+
+ /**
+ * Defines what will be executed when this command is run.
+ *
+ * @param user the {@link User} who is executing this command.
+ * @param label the label which has been used to execute this command. It can be
+ * {@link CompositeCommand#getLabel()} or an alias.
+ * @param args the command arguments.
+ * @return {@code true} if the command executed successfully, {@code false} otherwise.
+ */
+ @Override
+ public boolean execute(User user, String label, List args)
+ {
+ // Execute panel
+ ControlPanelGenerator.open((ControlPanelAddon) this.getAddon(),
+ user,
+ this.panelObject,
+ this.getTopLabel());
+
+ return true;
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Variables
+// ---------------------------------------------------------------------
+
+ /**
+ * Panel that must be opened.
+ */
+ private ControlPanelObject panelObject;
+}
diff --git a/src/main/java/world/bentobox/controlpanel/config/Settings.java b/src/main/java/world/bentobox/controlpanel/config/Settings.java
new file mode 100644
index 0000000..7be79aa
--- /dev/null
+++ b/src/main/java/world/bentobox/controlpanel/config/Settings.java
@@ -0,0 +1,67 @@
+package world.bentobox.controlpanel.config;
+
+
+import java.util.HashSet;
+import java.util.Set;
+
+import world.bentobox.bentobox.api.configuration.ConfigComment;
+import world.bentobox.bentobox.api.configuration.ConfigEntry;
+import world.bentobox.bentobox.api.configuration.ConfigObject;
+import world.bentobox.bentobox.api.configuration.StoreAt;
+
+
+/**
+ * Settings that implements ConfigObject is powerful and dynamic Config Objects that
+ * does not need custom parsing. If it is correctly loaded, all its values will be available.
+ *
+ * Without Getter and Setter this class will not work.
+ *
+ * To specify location for config object to be stored, you should use @StoreAt(filename="{config file name}", path="{Path to your addon}")
+ * To save comments in config file you should use @ConfigComment("{message}") that adds any message you want to be in file.
+ */
+@StoreAt(filename="config.yml", path="addons/ControlPanel")
+@ConfigComment("ControlPanelAddon Configuration [version]")
+@ConfigComment("This config file is dynamic and saved when the server is shutdown.")
+@ConfigComment("")
+public class Settings implements ConfigObject
+{
+ // ---------------------------------------------------------------------
+ // Section: Getters and Setters
+ // ---------------------------------------------------------------------
+
+
+ /**
+ * This method returns the disabledGameModes value.
+ *
+ * @return the value of disabledGameModes.
+ */
+ public Set getDisabledGameModes()
+ {
+ return disabledGameModes;
+ }
+
+
+ /**
+ * This method sets the disabledGameModes value.
+ *
+ * @param disabledGameModes the disabledGameModes new value.
+ */
+ public void setDisabledGameModes(Set disabledGameModes)
+ {
+ this.disabledGameModes = disabledGameModes;
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Variables
+// ---------------------------------------------------------------------
+
+
+ @ConfigComment("")
+ @ConfigComment("This list stores GameModes in which Likes addon should not work.")
+ @ConfigComment("To disable addon it is necessary to write its name in new line that starts with -. Example:")
+ @ConfigComment("disabled-gamemodes:")
+ @ConfigComment(" - BSkyBlock")
+ @ConfigEntry(path = "disabled-gamemodes")
+ private Set disabledGameModes = new HashSet<>();
+}
diff --git a/src/main/java/world/bentobox/controlpanel/database/objects/ControlPanelObject.java b/src/main/java/world/bentobox/controlpanel/database/objects/ControlPanelObject.java
new file mode 100644
index 0000000..f9fe91f
--- /dev/null
+++ b/src/main/java/world/bentobox/controlpanel/database/objects/ControlPanelObject.java
@@ -0,0 +1,349 @@
+//
+// Created by BONNe
+// Copyright - 2019
+//
+
+
+package world.bentobox.controlpanel.database.objects;
+
+
+import com.google.gson.annotations.Expose;
+
+import org.bukkit.Material;
+import java.util.List;
+
+import world.bentobox.bentobox.database.objects.DataObject;
+
+
+/**
+ * Object that allows to define different control panels.
+ */
+public class ControlPanelObject implements DataObject
+{
+ /**
+ * Constructor ControlPanelObject creates a new ControlPanelObject instance.
+ */
+ public ControlPanelObject()
+ {
+ // Empty constructor
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Getters and Setters
+// ---------------------------------------------------------------------
+
+
+ /**
+ * @return the uniqueId
+ */
+ @Override
+ public String getUniqueId()
+ {
+ return this.uniqueId;
+ }
+
+
+ /**
+ * @param uniqueId - unique ID the uniqueId to set
+ */
+ @Override
+ public void setUniqueId(String uniqueId)
+ {
+ this.uniqueId = uniqueId;
+ }
+
+
+ /**
+ * Method LikesObject#getGameMode returns the gameMode of this object.
+ *
+ * @return the gameMode (type String) of this object.
+ */
+ public String getGameMode()
+ {
+ return gameMode;
+ }
+
+
+ /**
+ * Method LikesObject#setGameMode sets new value for the gameMode of this object.
+ *
+ * @param gameMode new value for this object.
+ */
+ public void setGameMode(String gameMode)
+ {
+ this.gameMode = gameMode;
+ }
+
+
+ /**
+ * Method ControlPanelObject#getPermissionSuffix returns the permissionSuffix of this object.
+ *
+ * @return the permissionSuffix (type String) of this object.
+ */
+ public String getPermissionSuffix()
+ {
+ return permissionSuffix;
+ }
+
+
+ /**
+ * Method ControlPanelObject#setPermissionSuffix sets new value for the permissionSuffix of this object.
+ * @param permissionSuffix new value for this object.
+ *
+ */
+ public void setPermissionSuffix(String permissionSuffix)
+ {
+ this.permissionSuffix = permissionSuffix;
+ }
+
+
+ /**
+ * Method ControlPanelObject#getPanelName returns the panelName of this object.
+ *
+ * @return the panelName (type String) of this object.
+ */
+ public String getPanelName()
+ {
+ return panelName;
+ }
+
+
+ /**
+ * Method ControlPanelObject#setPanelName sets new value for the panelName of this object.
+ * @param panelName new value for this object.
+ *
+ */
+ public void setPanelName(String panelName)
+ {
+ this.panelName = panelName;
+ }
+
+
+ /**
+ * Method ControlPanelObject#getPanelButtons returns the panelButtons of this object.
+ *
+ * @return the panelButtons (type List) of this object.
+ */
+ public List getPanelButtons()
+ {
+ return panelButtons;
+ }
+
+
+ /**
+ * Method ControlPanelObject#setPanelButtons sets new value for the panelButtons of this object.
+ * @param panelButtons new value for this object.
+ *
+ */
+ public void setPanelButtons(List panelButtons)
+ {
+ this.panelButtons = panelButtons;
+ }
+
+
+ /**
+ * Method ControlPanelObject#isDefaultPanel returns the defaultPanel of this object.
+ *
+ * @return the defaultPanel (type boolean) of this object.
+ */
+ public boolean isDefaultPanel()
+ {
+ return defaultPanel;
+ }
+
+
+ /**
+ * Method ControlPanelObject#setDefaultPanel sets new value for the defaultPanel of this object.
+ * @param defaultPanel new value for this object.
+ *
+ */
+ public void setDefaultPanel(boolean defaultPanel)
+ {
+ this.defaultPanel = defaultPanel;
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Private Class
+// ---------------------------------------------------------------------
+
+
+ /**
+ * This class allows to add custom buttons to current panel.
+ */
+ public static class ControlPanelButton
+ {
+ /**
+ * Constructor ControlPanelButton creates a new ControlPanelButton instance.
+ */
+ public ControlPanelButton()
+ {
+ // Empty constructor
+ }
+
+
+ // ---------------------------------------------------------------------
+ // Section: Getters and Setters
+ // ---------------------------------------------------------------------
+
+
+ /**
+ * Method ControlPanelButton#getSlot returns the slot of this object.
+ *
+ * @return the slot (type int) of this object.
+ */
+ public int getSlot()
+ {
+ return slot;
+ }
+
+
+ /**
+ * Method ControlPanelButton#setSlot sets new value for the slot of this object.
+ * @param slot new value for this object.
+ *
+ */
+ public void setSlot(int slot)
+ {
+ this.slot = slot;
+ }
+
+
+ /**
+ * Method ControlPanelButton#getMaterial returns the material of this object.
+ *
+ * @return the material (type Material) of this object.
+ */
+ public Material getMaterial()
+ {
+ return material;
+ }
+
+
+ /**
+ * Method ControlPanelButton#setMaterial sets new value for the material of this object.
+ * @param material new value for this object.
+ *
+ */
+ public void setMaterial(Material material)
+ {
+ this.material = material;
+ }
+
+
+ /**
+ * Method ControlPanelButton#getDescription returns the description of this object.
+ *
+ * @return the description (type String) of this object.
+ */
+ public String getDescription()
+ {
+ return description;
+ }
+
+
+ /**
+ * Method ControlPanelButton#setDescription sets new value for the description of this object.
+ * @param description new value for this object.
+ *
+ */
+ public void setDescription(String description)
+ {
+ this.description = description;
+ }
+
+
+ /**
+ * Method ControlPanelButton#getCommand returns the command of this object.
+ *
+ * @return the command (type String) of this object.
+ */
+ public String getCommand()
+ {
+ return command;
+ }
+
+
+ /**
+ * Method ControlPanelButton#setCommand sets new value for the command of this object.
+ * @param command new value for this object.
+ *
+ */
+ public void setCommand(String command)
+ {
+ this.command = command;
+ }
+
+
+ // ---------------------------------------------------------------------
+ // Section: Variables
+ // ---------------------------------------------------------------------
+
+
+ /**
+ * Slot number of the button
+ */
+ @Expose
+ private int slot;
+
+ /**
+ * Material icon for button
+ */
+ @Expose
+ private Material material;
+
+ /**
+ * Description for the button
+ */
+ @Expose
+ private String description;
+
+ /**
+ * Command that will run on the click.
+ */
+ @Expose
+ private String command;
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Variables
+// ---------------------------------------------------------------------
+
+ /**
+ * Likes object id. Island ID;
+ */
+ @Expose
+ private String uniqueId;
+
+ /**
+ * Indicate that current panel is default one.
+ */
+ @Expose
+ private boolean defaultPanel;
+
+ /**
+ * GameMode where current object operates.
+ */
+ @Expose
+ private String gameMode;
+
+ /**
+ * Permission suffix for panel to work
+ */
+ @Expose
+ private String permissionSuffix;
+
+ /**
+ * Name of current panel
+ */
+ @Expose
+ private String panelName;
+
+ /**
+ * List of buttons in current panel
+ */
+ @Expose
+ private List panelButtons;
+}
diff --git a/src/main/java/world/bentobox/controlpanel/managers/ControlPanelManager.java b/src/main/java/world/bentobox/controlpanel/managers/ControlPanelManager.java
new file mode 100644
index 0000000..c135727
--- /dev/null
+++ b/src/main/java/world/bentobox/controlpanel/managers/ControlPanelManager.java
@@ -0,0 +1,307 @@
+//
+// Created by BONNe
+// Copyright - 2019
+//
+
+
+package world.bentobox.controlpanel.managers;
+
+
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.YamlConfiguration;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.bentobox.database.Database;
+import world.bentobox.controlpanel.ControlPanelAddon;
+import world.bentobox.controlpanel.database.objects.ControlPanelObject;
+import world.bentobox.controlpanel.database.objects.ControlPanelObject.ControlPanelButton;
+import world.bentobox.controlpanel.utils.Constants;
+import world.bentobox.controlpanel.utils.Utils;
+
+
+/**
+ * This class manages control panel addon data.
+ */
+public class ControlPanelManager
+{
+// ---------------------------------------------------------------------
+// Section: Constructor
+// ---------------------------------------------------------------------
+
+
+ /**
+ * Default constructor.
+ * @param addon Likes Addon instance
+ */
+ public ControlPanelManager(ControlPanelAddon addon)
+ {
+ this.addon = addon;
+
+ this.controlFile = new File(this.addon.getDataFolder(), "controlPanels.yml");
+
+ if (!this.controlFile.exists())
+ {
+ this.addon.saveResource("controlPanels.yml", false);
+ }
+
+ this.controlPanelCache = new HashMap<>();
+ this.controlPanelDatabase = new Database<>(addon, ControlPanelObject.class);
+
+ this.load();
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Load Methods
+// ---------------------------------------------------------------------
+
+
+ /**
+ * This method loads all control panel objects.
+ */
+ public void load()
+ {
+ this.controlPanelCache.clear();
+
+ this.addon.getLogger().info("Loading control panels...");
+
+ this.controlPanelDatabase.loadObjects().forEach(this::load);
+ }
+
+
+ /**
+ * This method loads given controlPanelObject inside cache.
+ * @param controlPanelObject Object that must be added to cache.
+ */
+ private void load(ControlPanelObject controlPanelObject)
+ {
+ // Add object into cache
+ this.controlPanelCache.put(controlPanelObject.getUniqueId(), controlPanelObject);
+ }
+
+
+ /**
+ * This method reloads all control panels from database to cache.
+ */
+ public void reload()
+ {
+ this.controlPanelCache.clear();
+
+ this.addon.getLogger().info("Reloading control panels...");
+
+ this.controlPanelDatabase.loadObjects().forEach(this::load);
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Save methods
+// ---------------------------------------------------------------------
+
+
+ /**
+ * This method saves all cached values into database.
+ */
+ public void save()
+ {
+ this.controlPanelCache.values().forEach(this.controlPanelDatabase::saveObject);
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Wipe methods
+// ---------------------------------------------------------------------
+
+
+ /**
+ * This method removes all data from database that referee to given world.
+ */
+ public void wipeData(World world)
+ {
+ String gameMode = Utils.getGameMode(world);
+
+ // Empty sorted cache
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Import methods
+// ---------------------------------------------------------------------
+
+
+ /**
+ * This method imports control panels
+ *
+ * @param user - user
+ * @param world - world to import into
+ * @param overwrite - true if previous ones should be overwritten
+ * @return true if successful
+ */
+ public void importControlPanels(User user, World world, boolean overwrite)
+ {
+ if (!this.controlFile.exists())
+ {
+ user.sendMessage(Constants.ERRORS + "no-file");
+ return;
+ }
+
+ YamlConfiguration config = new YamlConfiguration();
+
+ try
+ {
+ config.load(this.controlFile);
+ }
+ catch (IOException | InvalidConfigurationException e)
+ {
+ user.sendMessage(Constants.ERRORS + "no-load",
+ "[message]",
+ e.getMessage());
+ return;
+ }
+
+ this.readControlPanel(config, user, Utils.getGameMode(world), overwrite);
+
+ // Update biome order.
+ this.addon.getAddonManager().save();
+ }
+
+
+ /**
+ * This method creates control panel object from config file.
+ * @param config YamlConfiguration that contains all control panels.
+ * @param user User who calls reading.
+ * @param gameMode GameMode where current panel works.
+ * @param overwrite Boolean that indicate if existing control panel should be overwritted.
+ */
+ private void readControlPanel(YamlConfiguration config, User user, final String gameMode, boolean overwrite)
+ {
+ int newControlPanelCount = 0;
+
+ ConfigurationSection reader = config.getConfigurationSection("panel-list");
+
+ for (String keyReference : Objects.requireNonNull(reader).getKeys(false))
+ {
+ final String uniqueId = gameMode + "_" + keyReference;
+
+ if (!this.controlPanelCache.containsKey(uniqueId) || overwrite)
+ {
+ ControlPanelObject controlPanel = new ControlPanelObject();
+ controlPanel.setUniqueId(uniqueId);
+ controlPanel.setGameMode(gameMode);
+
+ ConfigurationSection panelSection = reader.getConfigurationSection(keyReference);
+
+ if (panelSection != null)
+ {
+ controlPanel.setPanelName(panelSection.getString("panelName", "&1Commands"));
+ controlPanel.setPermissionSuffix(panelSection.getString("permission", "default"));
+ controlPanel.setDefaultPanel(panelSection.getBoolean("defaultPanel", false));
+
+ List buttonList = new ArrayList<>();
+ controlPanel.setPanelButtons(buttonList);
+
+ ConfigurationSection buttonListSection = panelSection.getConfigurationSection("buttons");
+
+ if (buttonListSection != null)
+ {
+ buttonListSection.getKeys(false).forEach(slotReference -> {
+ ControlPanelButton button = new ControlPanelButton();
+ button.setSlot(Integer.parseInt(slotReference));
+
+ ConfigurationSection buttonSection = buttonListSection.getConfigurationSection(slotReference);
+
+ if (buttonSection != null)
+ {
+ button.setCommand(buttonSection.getString("command", "[user_command]"));
+ button.setDescription(buttonSection.getString("description", "").replace("[gamemode]", gameMode.toLowerCase()));
+ button.setMaterial(Material.matchMaterial(buttonSection.getString("material", "GRASS")));
+
+ buttonList.add(button);
+ }
+ });
+ }
+
+ // Save and load in cache.
+ this.controlPanelDatabase.saveObject(controlPanel);
+ this.load(controlPanel);
+
+ newControlPanelCount++;
+ }
+ }
+ }
+
+ user.sendMessage(Constants.MESSAGE + "import-count",
+ "[number]",
+ String.valueOf(newControlPanelCount));
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Processing methods
+// ---------------------------------------------------------------------
+
+
+ /**
+ * This method finds corresponding ControlPanel Object for user in given world.
+ * @param user User who wants to open panel
+ * @param world World where panel should be opened
+ * @param permissionPrefix Permission prefix.
+ * @return ControlPanelObject or null.
+ */
+ public ControlPanelObject getUserControlPanel(User user, World world, String permissionPrefix)
+ {
+ String gameMode = Utils.getGameMode(world);
+
+ String permission = Utils.getPermissionValue(user,
+ permissionPrefix + "controlpanel.panel",
+ null);
+
+ if (permission == null || !this.controlPanelCache.containsKey(gameMode + "_" + permission))
+ {
+ // Find first default for current game mode.
+
+ return this.controlPanelCache.values().stream().
+ filter(panel -> panel.isDefaultPanel() && panel.getGameMode().equals(gameMode)).
+ findFirst().orElse(null);
+ }
+ else
+ {
+ return this.controlPanelCache.get(gameMode + "_" + permission);
+ }
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Instance Variables
+// ---------------------------------------------------------------------
+
+
+ /**
+ * Control Panel Addon instance.
+ */
+ private ControlPanelAddon addon;
+
+ /**
+ * This database allows to access to all stored control panels.
+ */
+ private Database controlPanelDatabase;
+
+ /**
+ * This map contains all control panel object linked to their reference game mode.
+ */
+ private Map controlPanelCache;
+
+ /**
+ * Variable stores template.yml location
+ */
+ private File controlFile;
+}
diff --git a/src/main/java/world/bentobox/controlpanel/panels/ControlPanelGenerator.java b/src/main/java/world/bentobox/controlpanel/panels/ControlPanelGenerator.java
new file mode 100644
index 0000000..e068b57
--- /dev/null
+++ b/src/main/java/world/bentobox/controlpanel/panels/ControlPanelGenerator.java
@@ -0,0 +1,164 @@
+//
+// Created by BONNe
+// Copyright - 2019
+//
+
+
+package world.bentobox.controlpanel.panels;
+
+
+import org.bukkit.Material;
+import java.util.Comparator;
+
+import world.bentobox.bentobox.api.panels.PanelItem;
+import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
+import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.controlpanel.ControlPanelAddon;
+import world.bentobox.controlpanel.database.objects.ControlPanelObject;
+import world.bentobox.controlpanel.database.objects.ControlPanelObject.ControlPanelButton;
+
+
+/**
+ * This class generates Control Panel using given ControlPanelObject.
+ */
+public class ControlPanelGenerator
+{
+ /**
+ * Default constructor.
+ * @param addon ControlPanelAddon instance.
+ * @param user User who opens panel.
+ * @param controlPanel ControlPanelSettings that must be constructed
+ * @param topLabel Main command.
+ */
+ private ControlPanelGenerator(ControlPanelAddon addon, User user, ControlPanelObject controlPanel, String topLabel)
+ {
+ this.addon = addon;
+ this.user = user;
+ this.controlPanel = controlPanel;
+
+ this.topLabel = topLabel;
+ }
+
+
+ /**
+ * This method opens Panel for user with given panel settings.
+ * @param addon ControlPanelAddon instance.
+ * @param user User who opens panel.
+ * @param controlPanel ControlPanelSettings that must be constructed
+ * @param topLabel Main command.
+ */
+ public static void open(ControlPanelAddon addon, User user, ControlPanelObject controlPanel, String topLabel)
+ {
+ new ControlPanelGenerator(addon, user, controlPanel, topLabel).build();
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Methods
+// ---------------------------------------------------------------------
+
+
+ /**
+ * This method build panel.
+ */
+ private void build()
+ {
+ PanelBuilder panelBuilder = new PanelBuilder().
+ user(this.user).
+ name(this.controlPanel.getPanelName());
+
+ // Sort and add.
+ this.controlPanel.getPanelButtons().stream().
+ sorted(Comparator.comparing(ControlPanelButton::getSlot)).
+ forEach(button -> {
+ if (button.getSlot() < 0 || button.getSlot() > 53 || panelBuilder.slotOccupied(button.getSlot()))
+ {
+ // TODO: need to manage empty slots as I can put 53 as first item and go *** other buttons :)
+ panelBuilder.item(this.generateButton(button));
+ }
+ else
+ {
+ panelBuilder.item(button.getSlot(), this.generateButton(button));
+ }
+ });
+
+ panelBuilder.build();
+ }
+
+
+ /**
+ * This method generates PanelItem from elements that is defined in ControlPanelButton object.
+ * @param button ControlPanelButton which must be transformed to PanelItem.
+ * @return PanelItem that corresponds to ControlPanelButton
+ */
+ private PanelItem generateButton(ControlPanelButton button)
+ {
+ final boolean runAsServer = button.getCommand().startsWith("[server]");
+ final String parsedCommand = button.getCommand().
+ replace("[label]", this.topLabel).
+ replace("[server]", "").
+ replace("[player]", this.user.getName()).
+ trim();
+
+ String description = this.addon.getPlugin().getPlaceholdersManager().
+ replacePlaceholders(this.user.getPlayer(), button.getDescription());
+
+ return new PanelItemBuilder().
+ name(parsedCommand.isEmpty() ? " " : "/" + parsedCommand).
+ icon(button.getMaterial() == null ? Material.PAPER : button.getMaterial()).
+ description(GuiUtils.stringSplit(description, 999)).
+ clickHandler((panel, user, clickType, slot) -> {
+
+ if (!parsedCommand.isEmpty())
+ {
+ if (runAsServer)
+ {
+ if (!this.addon.getServer().dispatchCommand(
+ this.addon.getServer().getConsoleSender(),
+ parsedCommand))
+ {
+ this.addon.logError("Problem executing command executed by server!");
+ this.addon.logError("Command was : " + parsedCommand);
+ }
+ }
+ else
+ {
+ if (!user.performCommand(parsedCommand))
+ {
+ this.addon.logError("Problem executing command executed by player!");
+ this.addon.logError("Command was : " + parsedCommand);
+ }
+ }
+ }
+
+ return true;
+ }).
+ build();
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Variables
+// ---------------------------------------------------------------------
+
+ /**
+ * Instance of Addon
+ */
+ private ControlPanelAddon addon;
+
+ /**
+ * User who wants to open panel.
+ */
+ private User user;
+
+ /**
+ * Object that holds information about custom panel structure.
+ */
+ private ControlPanelObject controlPanel;
+
+ /**
+ * Main command label string
+ */
+ private String topLabel;
+}
diff --git a/src/main/java/world/bentobox/controlpanel/panels/GuiUtils.java b/src/main/java/world/bentobox/controlpanel/panels/GuiUtils.java
new file mode 100644
index 0000000..7c620c4
--- /dev/null
+++ b/src/main/java/world/bentobox/controlpanel/panels/GuiUtils.java
@@ -0,0 +1,292 @@
+package world.bentobox.controlpanel.panels;
+
+
+import org.apache.commons.lang.WordUtils;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import world.bentobox.bentobox.api.panels.PanelItem;
+import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
+import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
+
+
+/**
+ * This class contains static methods that is used through multiple GUIs.
+ */
+public class GuiUtils
+{
+// ---------------------------------------------------------------------
+// Section: Border around GUIs
+// ---------------------------------------------------------------------
+
+
+ /**
+ * This method creates border of black panes around given panel with 5 rows.
+ * @param panelBuilder PanelBuilder which must be filled with border blocks.
+ */
+ public static void fillBorder(PanelBuilder panelBuilder)
+ {
+ GuiUtils.fillBorder(panelBuilder, 5, Material.BLACK_STAINED_GLASS_PANE);
+ }
+
+
+ /**
+ * This method sets black stained glass pane around Panel with given row count.
+ * @param panelBuilder object that builds Panel.
+ * @param rowCount in Panel.
+ */
+ public static void fillBorder(PanelBuilder panelBuilder, int rowCount)
+ {
+ GuiUtils.fillBorder(panelBuilder, rowCount, Material.BLACK_STAINED_GLASS_PANE);
+ }
+
+
+ /**
+ * This method sets blocks with given Material around Panel with 5 rows.
+ * @param panelBuilder object that builds Panel.
+ * @param material that will be around Panel.
+ */
+ public static void fillBorder(PanelBuilder panelBuilder, Material material)
+ {
+ GuiUtils.fillBorder(panelBuilder, 5, material);
+ }
+
+
+ /**
+ * This method sets blocks with given Material around Panel with given row count.
+ * @param panelBuilder object that builds Panel.
+ * @param rowCount in Panel.
+ * @param material that will be around Panel.
+ */
+ public static void fillBorder(PanelBuilder panelBuilder, int rowCount, Material material)
+ {
+ // Only for useful filling.
+ if (rowCount < 3)
+ {
+ return;
+ }
+
+ for (int i = 0; i < 9 * rowCount; i++)
+ {
+ // First (i < 9) and last (i > 35) rows must be filled
+ // First column (i % 9 == 0) and last column (i % 9 == 8) also must be filled.
+
+ if (i < 9 || i > 9 * (rowCount - 1) || i % 9 == 0 || i % 9 == 8)
+ {
+ panelBuilder.item(i, BorderBlock.getPanelBorder(material));
+ }
+ }
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: ItemStack transformations
+// ---------------------------------------------------------------------
+
+
+ /**
+ * This BorderBlock is simple PanelItem but without item meta data.
+ */
+ private static class BorderBlock extends PanelItem
+ {
+ private BorderBlock(ItemStack icon)
+ {
+ super(new PanelItemBuilder().
+ icon(icon.clone()).
+ name(" ").
+ description(Collections.emptyList()).
+ glow(false).
+ clickHandler(null));
+ }
+
+
+ /**
+ * This method retunrs BorderBlock with requested item stack.
+ * @param material of which broder must be created.
+ * @return PanelItem that acts like border.
+ */
+ private static BorderBlock getPanelBorder(Material material)
+ {
+ ItemStack itemStack = new ItemStack(material);
+ itemStack.getItemMeta().setDisplayName(" ");
+
+ return new BorderBlock(itemStack);
+ }
+ }
+
+
+ /**
+ * Simple splitter
+ *
+ * @param string - string to be split
+ * @param warpLength - whn warp should be affected.
+ * @return list of split strings
+ */
+ public static List stringSplit(String string, int warpLength)
+ {
+ // Remove all ending lines from string.
+ string = string.replaceAll("([\\r\\n])", "\\|");
+ string = ChatColor.translateAlternateColorCodes('&', string);
+ // Check length of lines
+ List result = new ArrayList<>();
+
+ Arrays.stream(string.split("\\|")).
+ map(line -> Arrays.asList(WordUtils.wrap(line, warpLength).split(System.getProperty("line.separator")))).
+ forEach(result::addAll);
+
+ // Fix colors, as splitting my lost that information.
+
+ for (int i = 0, resultSize = result.size(); i < resultSize; i++)
+ {
+ if (i > 0)
+ {
+ String lastColor = ChatColor.getLastColors(result.get(i - 1));
+ result.set(i, lastColor + result.get(i));
+ }
+ }
+
+ return result;
+ }
+
+
+ /**
+ * Simple splitter for all strings in list.
+ * @param stringList - list of string to be split
+ * @param warpLength - whn warp should be affected.
+ * @return list of split strings
+ */
+ public static List stringSplit(List stringList, int warpLength)
+ {
+ if (stringList.isEmpty())
+ {
+ return stringList;
+ }
+
+ List newList = new ArrayList<>(stringList.size());
+ stringList.stream().map(string -> GuiUtils.stringSplit(string, warpLength)).forEach(newList::addAll);
+ return newList;
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Materials
+// ---------------------------------------------------------------------
+
+
+ /**
+ * This method transforms material into item stack that can be displayed in users
+ * inventory.
+ * @param material Material which item stack must be returned.
+ * @return ItemStack that represents given material.
+ */
+ public static ItemStack getMaterialItem(Material material)
+ {
+ return GuiUtils.getMaterialItem(material, 1);
+ }
+
+
+ /**
+ * This method transforms material into item stack that can be displayed in users
+ * inventory.
+ * @param material Material which item stack must be returned.
+ * @param amount Amount of ItemStack elements.
+ * @return ItemStack that represents given material.
+ */
+ public static ItemStack getMaterialItem(Material material, int amount)
+ {
+ ItemStack itemStack;
+
+ // Process items that cannot be item-stacks.
+ if (material.name().contains("WALL_"))
+ {
+ // Materials that is attached to wall cannot be showed in GUI. But they should be in list.
+ itemStack = new ItemStack(Material.getMaterial(material.name().replace("WALL_", "")));
+ }
+ else if (material.name().startsWith("POTTED_"))
+ {
+ // Materials Potted elements cannot be in inventory.
+ itemStack = new ItemStack(Material.getMaterial(material.name().replace("POTTED_", "")));
+ }
+ else if (material.equals(Material.MELON_STEM) || material.equals(Material.ATTACHED_MELON_STEM))
+ {
+ itemStack = new ItemStack(Material.MELON_SEEDS);
+ }
+ else if (material.equals(Material.PUMPKIN_STEM) || material.equals(Material.ATTACHED_PUMPKIN_STEM))
+ {
+ itemStack = new ItemStack(Material.PUMPKIN_SEEDS);
+ }
+ else if (material.equals(Material.TALL_SEAGRASS))
+ {
+ itemStack = new ItemStack(Material.SEAGRASS);
+ }
+ else if (material.equals(Material.CARROTS))
+ {
+ itemStack = new ItemStack(Material.CARROT);
+ }
+ else if (material.equals(Material.BEETROOTS))
+ {
+ itemStack = new ItemStack(Material.BEETROOT);
+ }
+ else if (material.equals(Material.POTATOES))
+ {
+ itemStack = new ItemStack(Material.POTATO);
+ }
+ else if (material.equals(Material.COCOA))
+ {
+ itemStack = new ItemStack(Material.COCOA_BEANS);
+ }
+ else if (material.equals(Material.KELP_PLANT))
+ {
+ itemStack = new ItemStack(Material.KELP);
+ }
+ else if (material.equals(Material.REDSTONE_WIRE))
+ {
+ itemStack = new ItemStack(Material.REDSTONE);
+ }
+ else if (material.equals(Material.TRIPWIRE))
+ {
+ itemStack = new ItemStack(Material.STRING);
+ }
+ else if (material.equals(Material.FROSTED_ICE))
+ {
+ itemStack = new ItemStack(Material.ICE);
+ }
+ else if (material.equals(Material.END_PORTAL) || material.equals(Material.END_GATEWAY) || material.equals(Material.NETHER_PORTAL))
+ {
+ itemStack = new ItemStack(Material.PAPER);
+ }
+ else if (material.equals(Material.BUBBLE_COLUMN) || material.equals(Material.WATER))
+ {
+ itemStack = new ItemStack(Material.WATER_BUCKET);
+ }
+ else if (material.equals(Material.LAVA))
+ {
+ itemStack = new ItemStack(Material.LAVA_BUCKET);
+ }
+ else if (material.equals(Material.FIRE))
+ {
+ itemStack = new ItemStack(Material.FIRE_CHARGE);
+ }
+ else if (material.equals(Material.AIR) || material.equals(Material.CAVE_AIR) || material.equals(Material.VOID_AIR))
+ {
+ itemStack = new ItemStack(Material.GLASS_BOTTLE);
+ }
+ else if (material.equals(Material.PISTON_HEAD) || material.equals(Material.MOVING_PISTON))
+ {
+ itemStack = new ItemStack(Material.PISTON);
+ }
+ else
+ {
+ itemStack = new ItemStack(material);
+ }
+
+ itemStack.setAmount(amount);
+
+ return itemStack;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/world/bentobox/controlpanel/utils/Constants.java b/src/main/java/world/bentobox/controlpanel/utils/Constants.java
new file mode 100644
index 0000000..65172b6
--- /dev/null
+++ b/src/main/java/world/bentobox/controlpanel/utils/Constants.java
@@ -0,0 +1,51 @@
+//
+// Created by BONNe
+// Copyright - 2019
+//
+
+
+package world.bentobox.controlpanel.utils;
+
+
+public class Constants
+{
+ /**
+ * Reference string to GUI Titles in translations.
+ */
+ public static final String TITLE = "controlpanel.gui.titles.";
+
+ /**
+ * Reference string to GUI Buttons in translations.
+ */
+ public static final String BUTTON = "controlpanel.gui.buttons.";
+
+ /**
+ * Reference string to GUI Descriptions in translations.
+ */
+ public static final String DESCRIPTION = "controlpanel.gui.descriptions.";
+
+ /**
+ * Reference string to Messages in translations.
+ */
+ public static final String MESSAGE = "controlpanel.messages.";
+
+ /**
+ * Reference string to Errors in translations.
+ */
+ public static final String ERRORS = "controlpanel.errors.";
+
+ /**
+ * Reference string to Questions in translations.
+ */
+ public static final String QUESTIONS = "controlpanel.questions.";
+
+ /**
+ * Reference string to Commands in translations.
+ */
+ public static final String COMMANDS = "controlpanel.commands.";
+
+ /**
+ * Reference string to types in translations.
+ */
+ public static final String TYPES = "controlpanel.types.";
+}
diff --git a/src/main/java/world/bentobox/controlpanel/utils/Utils.java b/src/main/java/world/bentobox/controlpanel/utils/Utils.java
new file mode 100644
index 0000000..6e82522
--- /dev/null
+++ b/src/main/java/world/bentobox/controlpanel/utils/Utils.java
@@ -0,0 +1,146 @@
+//
+// Created by BONNe
+// Copyright - 2019
+//
+
+
+package world.bentobox.controlpanel.utils;
+
+
+import org.bukkit.World;
+import org.bukkit.permissions.PermissionAttachmentInfo;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import world.bentobox.bentobox.BentoBox;
+import world.bentobox.bentobox.api.addons.GameModeAddon;
+import world.bentobox.bentobox.api.user.User;
+
+
+public class Utils
+{
+ /**
+ * This method gets string value of given permission prefix. If user does not have
+ * given permission or it have all (*), then return default value.
+ * @param user User who's permission should be checked.
+ * @param permissionPrefix Prefix that need to be found.
+ * @param defaultValue Default value that will be returned if permission not found.
+ * @return String value that follows permissionPrefix.
+ */
+ public static String getPermissionValue(User user, String permissionPrefix, String defaultValue)
+ {
+ if (user.isPlayer())
+ {
+ if (permissionPrefix.endsWith("."))
+ {
+ permissionPrefix = permissionPrefix.substring(0, permissionPrefix.length() - 1);
+ }
+
+ String permPrefix = permissionPrefix + ".";
+
+ List permissions = user.getEffectivePermissions().stream().
+ map(PermissionAttachmentInfo::getPermission).
+ filter(permission -> permission.startsWith(permPrefix)).
+ collect(Collectors.toList());
+
+ for (String permission : permissions)
+ {
+ if (permission.contains(permPrefix + "*"))
+ {
+ // * means all. So continue to search more specific.
+ continue;
+ }
+
+ String[] parts = permission.split(permPrefix);
+
+ if (parts.length > 1)
+ {
+ return parts[1];
+ }
+ }
+ }
+
+ return defaultValue;
+ }
+
+
+ /**
+ * This method transforms given World into GameMode name. If world is not a GameMode
+ * world then it returns null.
+ * @param world World which gameMode name must be found out.
+ * @return GameMode name or null.
+ */
+ public static String getGameMode(World world)
+ {
+ return BentoBox.getInstance().getIWM().getAddon(world).
+ map(gameModeAddon -> gameModeAddon.getDescription().getName()).
+ orElse(null);
+ }
+
+
+ /**
+ * This method transforms given GameMode into name.
+ * @param gameModeAddon GameMode which name must be returned.
+ * @return GameMode name.
+ */
+ public static String getGameMode(GameModeAddon gameModeAddon)
+ {
+ return gameModeAddon.getDescription().getName();
+ }
+
+
+ /**
+ * This method allows to get next value from array list after given value.
+ * @param values Array that should be searched for given value.
+ * @param currentValue Value which next element should be found.
+ * @param Instance of given object.
+ * @return Next value after currentValue in values array.
+ */
+ public static T getNextValue(T[] values, T currentValue)
+ {
+ for (int i = 0; i < values.length; i++)
+ {
+ if (values[i].equals(currentValue))
+ {
+ if (i + 1 == values.length)
+ {
+ return values[0];
+ }
+ else
+ {
+ return values[i + 1];
+ }
+ }
+ }
+
+ return currentValue;
+ }
+
+
+ /**
+ * This method allows to get previous value from array list after given value.
+ * @param values Array that should be searched for given value.
+ * @param currentValue Value which previous element should be found.
+ * @param Instance of given object.
+ * @return Previous value before currentValue in values array.
+ */
+ public static T getPreviousValue(T[] values, T currentValue)
+ {
+ for (int i = 0; i < values.length; i++)
+ {
+ if (values[i].equals(currentValue))
+ {
+ if (i > 0)
+ {
+ return values[i - 1];
+ }
+ else
+ {
+ return values[values.length - 1];
+ }
+ }
+ }
+
+ return currentValue;
+ }
+}
diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml
new file mode 100644
index 0000000..fa5abb9
--- /dev/null
+++ b/src/main/resources/addon.yml
@@ -0,0 +1,74 @@
+# Name of your addon that wil lbe used in displaying it.
+name: ControlPanel
+# Addon main class. This class should extend Addon.class
+main: world.bentobox.controlpanel.ControlPanelAddon
+# Version of your addon. Can use maven variables.
+version: ${version}${build.number}
+# Allow to send metric about this addon usage.
+metrics: true
+# GitHub version check. Will work only for GitHub.
+repository: 'BentoBoxWorld/ControlPanel'
+# Icon of addon that will be displayed in Addon Manager.
+# Must use Material.values() with uppercase.
+icon: 'COMMAND_BLOCK'
+
+# List of addon authors.
+authors:
+ - BONNe
+
+# Soft dependencies of current addon.
+softdepend: AcidIsland, BSkyBlock, CaveBlock, SkyGrid
+
+# List of addon permissions
+permissions:
+ acidisland.controlpanel:
+ description: Allows access to control panel command
+ default: true
+ acidisland.controlpanel.admin:
+ description: Allows access to control panel admin command
+ default: op
+ acidisland.controlpanel.admin.import:
+ description: Allows importing control panels from file
+ default: op
+ acidisland.controlpanel.panel.default:
+ description: Allows access to default control panel layout
+ default: true
+
+ bskyblock.controlpanel:
+ description: Allows access to control panel command
+ default: true
+ bskyblock.controlpanel.admin:
+ description: Allows access to control panel admin command
+ default: op
+ bskyblock.controlpanel.admin.import:
+ description: Allows importing control panels from file
+ default: op
+ bskyblock.controlpanel.panel.default:
+ description: Allows access to default control panel layout
+ default: true
+
+ caveblock.controlpanel:
+ description: Allows access to control panel command
+ default: true
+ caveblock.controlpanel.admin:
+ description: Allows access to control panel admin command
+ default: op
+ caveblock.controlpanel.admin.import:
+ description: Allows importing control panels from file
+ default: op
+ caveblock.controlpanel.panel.default:
+ description: Allows access to default control panel layout
+ default: true
+
+ skygrid.controlpanel:
+ description: Allows access to control panel command
+ default: true
+ skygrid.controlpanel.admin:
+ description: Allows access to control panel admin command
+ default: op
+ skygrid.controlpanel.admin.import:
+ description: Allows importing control panels from file
+ default: op
+ skygrid.controlpanel.panel.default:
+ description: Allows access to default control panel layout
+ default: true
\ No newline at end of file
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000..84d005c
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1,7 @@
+# ControlPanel Configuration ${version}
+#
+# This list stores GameModes in which Likes addon should not work.
+# To disable addon it is necessary to write its name in new line that starts with -. Example:
+# disabled-gamemodes:
+# - BSkyBlock
+disabled-gamemodes: []
diff --git a/src/main/resources/controlPanels.yml b/src/main/resources/controlPanels.yml
new file mode 100644
index 0000000..fd83568
--- /dev/null
+++ b/src/main/resources/controlPanels.yml
@@ -0,0 +1,62 @@
+# Control Panel Template file
+# Command Parser Values:
+# [player] - [player] in command will be replaced with username,
+# f.e. 'give [player] diamond' will result in '/give BONNe1704 diamond'
+# [server] - Command will be run by console, f.e. '[server] op [player]' will result in '/op BONNe1704'
+# [label] - [label] in command will be replaced with corresponding GameMode user command,
+# f.e. '[label] challenges' in BSkyblock will result in 'island challenges'
+# material is used from Matherial.match
+# permission is a suffix that will be added to the end of "[gamemode].controlpanel.panel.[suffix]".
+# Adding permission means that user will open control panel defined by permission.
+# If user will have multiple panel permissions, it will open first encountered with default flag.
+# If user will have '*', it will open first defined panel with default flag.
+# description can include color codes using & and new lines using | and placeholders with with % at the
+# start and the end. [gamemode] in placeholders will be converted to correct string.
+# number before each button means slot location
+
+panel-list:
+ default:
+ defaultPanel: true
+ panelName: '&1Commands'
+ permission: 'default'
+ buttons:
+ 0:
+ material: GRASS
+ description: 'Go to your island'
+ command: '[label] go'
+ 8:
+ material: LAVA_BUCKET
+ description: 'Open your island settings'
+ command: '[label] settings'
+ 3:
+ material: SKULL_ITEM
+ description: 'List team members'
+ command: '[label] team'
+ 1:
+ material: WHITE_BED
+ description: 'Set your home here'
+ command: '[label] sethome'
+ 9:
+ material: ACACIA_STAIRS
+ description: 'Island level |Level: %Level_[gamemode]-island-level%'
+ command: '[label] level'
+ 10:
+ material: BOOK
+ description: 'List the Top 10 islands'
+ command: '[label] top'
+ 12:
+ material: OAK_SIGN
+ description: 'List warps available'
+ command: '[label] warps'
+ 5:
+ material: BEDROCK
+ description: 'Go to world spawn'
+ command: '[label] spawn'
+ 14:
+ material: OAK_SAPLING
+ description: 'Biomes'
+ command: '[label] biomes'
+ 15:
+ material: ENCHANTING_TABLE
+ description: 'Challenges'
+ command: '[label] challenges'
\ No newline at end of file
diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml
new file mode 100644
index 0000000..059eaff
--- /dev/null
+++ b/src/main/resources/locales/en-US.yml
@@ -0,0 +1,19 @@
+controlpanel:
+ messages:
+ import-count: "Imported [number] control panels"
+ errors:
+ no-load: "&4Cannot load controlPanels.yml file: [message]"
+ no-file: "&4Missing controlPanels.yml file"
+ no-valid-panels: "&4Could not find any valid control panel."
+ no-valid-panels-op: "&4Could not find any valid control panel. You should try to import it using /[admin] cp import"
+ commands:
+ admin:
+ help:
+ parameters: ""
+ description: "shows admin help commands"
+ import:
+ parameters: "[overwrite]"
+ description: "imports control panels from controlPanels.yml"
+ help:
+ parameters: ""
+ description: "opens control panel"
\ No newline at end of file
diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml
new file mode 100644
index 0000000..4289070
--- /dev/null
+++ b/src/main/resources/locales/es.yml
@@ -0,0 +1,19 @@
+---
+controlpanel:
+ messages:
+ import-count: Paneles de control importados [number]
+ errors:
+ no-load: "&4No se puede cargar el archivo controlPanels.yml: [message]"
+ no-file: "&4Archivo Missing controlPanels.yml"
+ no-valid-panels: "&4No se pudo encontrar ningún panel de control válido."
+ no-valid-panels-op: "&4No se pudo encontrar ningún panel de control válido. Deberías
+ intentar importarlo usando / [admin] cp import"
+ commands:
+ admin:
+ help:
+ description: muestra los comandos de ayuda del administrador
+ import:
+ parameters: "[overwrite]"
+ description: importa paneles de control desde controlPanels.yml
+ help:
+ description: abre el panel de control
diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml
new file mode 100644
index 0000000..b6bce4a
--- /dev/null
+++ b/src/main/resources/locales/fr.yml
@@ -0,0 +1,23 @@
+---
+controlpanel:
+ messages:
+ import-count: " [number] panneaux de commande importés."
+ errors:
+ no-load: "&4Impossible de charger le fichier controlPanels.yml: [message]"
+ no-file: "&4Fichier controlPanels.yml manquant"
+ no-valid-panels: "&4Impossible de trouver un panneau de configuration valide.\n"
+ no-valid-panels-op: "&4Impossible de trouver un panneau de configuration valide.
+ Vous devriez essayer de l'importer en utilisant /[admin] cp import"
+ commands:
+ admin:
+ help:
+ description: affiche les commandes d'aide de l'administrateur
+ import:
+ parameters: "[overwrite]"
+ description: |2-
+
+ importe les panneaux de contrôle de controlPanels.yml
+ help:
+ description: |2-
+
+ ouvre le panneau de configuration
diff --git a/src/main/resources/locales/lv.yml b/src/main/resources/locales/lv.yml
new file mode 100644
index 0000000..63ab403
--- /dev/null
+++ b/src/main/resources/locales/lv.yml
@@ -0,0 +1,19 @@
+---
+controlpanel:
+ messages:
+ import-count: Importēti [number] vadības paneļi
+ errors:
+ no-load: "&4Nevar ielādēt controlPanels.yml failu: [message]"
+ no-file: "&4Pietrūkst fails controlPanels.yml"
+ no-valid-panels: "&4Nevarēja atrast derīgu vadības paneli."
+ no-valid-panels-op: "&4Nevarētja atrast derīgu vadības paneli. Tev vajadzētu tos
+ ielādēt izmantojot /[admin] cp import"
+ commands:
+ admin:
+ import:
+ parameters: "[overwrite]"
+ description: ielādē vadības paneļus no controlPanels.yml
+ help:
+ description: parāda administratora palīdzības komandas
+ help:
+ description: atver vadības paneli