From 2ed39904a9b7e15517406792eab72a8aef1063b0 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 9 Jun 2021 15:09:48 -0400
Subject: [PATCH 01/74] add placeholder support for bedrock form

- also fix reload message and add papi as softdepend
---
 pom.xml                                       |  49 ++++----
 .../dev/projectg/geyserhub/GeyserHubMain.java |   2 +-
 .../geyserhub/command/ReloadCommand.java      |   2 +-
 .../module/{scoreboard => }/Placeholders.java |   3 +-
 .../geyserhub/module/menu/BedrockMenu.java    | 118 ++++++++++--------
 .../geyserhub/module/menu/JavaMenu.java       |   2 +-
 .../module/scoreboard/ScoreboardManager.java  |   1 +
 src/main/resources/plugin.yml                 |  10 +-
 8 files changed, 103 insertions(+), 84 deletions(-)
 rename src/main/java/dev/projectg/geyserhub/module/{scoreboard => }/Placeholders.java (95%)

diff --git a/pom.xml b/pom.xml
index 2eb240a..1044b85 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 
     <groupId>dev.projectg</groupId>
     <artifactId>GeyserHub</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -16,10 +16,6 @@
     </properties>
 
     <repositories>
-        <repository>
-            <id>papermc</id>
-            <url>https://papermc.io/repo/repository/maven-public/</url>
-        </repository>
         <repository>
             <id>spigot-repo</id>
             <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
@@ -34,14 +30,6 @@
                 <enabled>false</enabled>
             </snapshots>
         </repository>
-        <repository>
-            <id>essentials-snapshots</id>
-            <url>https://repo.essentialsx.net/snapshots/</url>
-        </repository>
-        <repository>
-            <id>placeholderapi</id>
-            <url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
-        </repository>
         <repository>
             <id>opencollab-snapshot-repo</id>
             <url>https://repo.opencollab.dev/maven-snapshots/</url>
@@ -52,6 +40,14 @@
                 <enabled>true</enabled>
             </snapshots>
         </repository>
+        <repository>
+            <id>essentials-snapshots</id>
+            <url>https://repo.essentialsx.net/snapshots/</url>
+        </repository>
+        <repository>
+            <id>placeholderapi</id>
+            <url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
+        </repository>
         <repository>
             <id>jitpack.io</id>
             <url>https://jitpack.io</url>
@@ -65,10 +61,10 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.bstats</groupId>
-            <artifactId>bstats-bukkit</artifactId>
-            <version>2.2.1</version>
-            <scope>compile</scope>
+            <groupId>org.geysermc.cumulus</groupId>
+            <artifactId>api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.geysermc.floodgate</groupId>
@@ -76,6 +72,12 @@
             <version>2.0-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.bstats</groupId>
+            <artifactId>bstats-bukkit</artifactId>
+            <version>2.2.1</version>
+            <scope>compile</scope>
+        </dependency>
         <dependency>
             <groupId>net.essentialsx</groupId>
             <artifactId>EssentialsX</artifactId>
@@ -83,9 +85,9 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.geysermc.cumulus</groupId>
-            <artifactId>api</artifactId>
-            <version>1.0-SNAPSHOT</version>
+            <groupId>me.clip</groupId>
+            <artifactId>placeholderapi</artifactId>
+            <version>2.10.9</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -94,13 +96,6 @@
             <version>1.7</version>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>me.clip</groupId>
-            <artifactId>placeholderapi</artifactId>
-            <version>2.10.9</version>
-            <scope>provided</scope>
-        </dependency>
-
     </dependencies>
     <build>
         <plugins>
diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index aed8f22..8462ec7 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -8,7 +8,7 @@
 import dev.projectg.geyserhub.module.listeners.ItemOnJoin;
 import dev.projectg.geyserhub.module.message.BroadCast;
 import dev.projectg.geyserhub.module.message.MessageJoin;
-import dev.projectg.geyserhub.module.scoreboard.Placeholders;
+import dev.projectg.geyserhub.module.Placeholders;
 import dev.projectg.geyserhub.module.scoreboard.ScoreboardManager;
 import dev.projectg.geyserhub.module.world.WorldSettings;
 import dev.projectg.geyserhub.utils.bstats.Metrics;
diff --git a/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java b/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java
index b3fc21f..37f9a3d 100644
--- a/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java
+++ b/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java
@@ -17,7 +17,7 @@ public boolean onCommand(@Nonnull CommandSender sender, @Nonnull  Command comman
 
         if (sender instanceof Player || sender instanceof ConsoleCommandSender) {
 
-            if (GeyserHubMain.getInstance().loadConfiguration()) {
+            if (!GeyserHubMain.getInstance().loadConfiguration()) {
                 sender.sendMessage("[GeyserHub] " + ChatColor.RED + "Failed to reload the configuration!");
                 return true;
             }
diff --git a/src/main/java/dev/projectg/geyserhub/module/scoreboard/Placeholders.java b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
similarity index 95%
rename from src/main/java/dev/projectg/geyserhub/module/scoreboard/Placeholders.java
rename to src/main/java/dev/projectg/geyserhub/module/Placeholders.java
index ba6029e..a50d106 100644
--- a/src/main/java/dev/projectg/geyserhub/module/scoreboard/Placeholders.java
+++ b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
@@ -1,4 +1,4 @@
-package dev.projectg.geyserhub.module.scoreboard;
+package dev.projectg.geyserhub.module;
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import me.clip.placeholderapi.PlaceholderAPI;
@@ -36,6 +36,7 @@ public static String getServerVersion() {
         return Bukkit.getServer().getClass().getPackage().getName().substring(23);
     }
 
+    // todo I dont think we need this...
     public static String replaceValues(Player player, String x) {
         if (vault != 0 && permission.hasGroupSupport()) {
             g = permission.getPrimaryGroup(player);
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java
index 5f396c0..3c7f716 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java
@@ -1,10 +1,10 @@
 package dev.projectg.geyserhub.module.menu;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.module.scoreboard.Placeholders;
 import dev.projectg.geyserhub.utils.bstats.Reloadable;
 import dev.projectg.geyserhub.utils.bstats.ReloadableRegistry;
 import dev.projectg.geyserhub.utils.bstats.SelectorLogger;
+import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
 import org.bukkit.configuration.ConfigurationSection;
@@ -22,20 +22,34 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
-public class BedrockMenu extends Placeholders implements Reloadable {
+public class BedrockMenu implements Reloadable {
 
     private static BedrockMenu instance;
 
     private boolean isEnabled = false;
 
-    private SimpleForm serverSelector;
-    private List<String> validServerNames;
-    private List<List<String>> validCommands;
-    private int commandsIndex;
+    private String title;
+    private String content;
+    private List<ButtonComponent> allButtons;
 
+    /**
+     * List of all the BungeeCord servers for all the buttons.
+     */
+    private List<String> serverNames;
+    /**
+     * List containing a list of commands for every button
+     */
+    private List<List<String>> commands;
+
+    /**
+     * Index at which command buttons start in {@link #allButtons}
+     */
+    int commandsIndex;
 
     /**
      *
@@ -77,18 +91,6 @@ private boolean load(@Nonnull FileConfiguration config) {
 
         SelectorLogger logger = SelectorLogger.getLogger();
 
-        List<ButtonComponent> allButtons= new ArrayList<>();
-        allButtons.addAll(getServerButtons(logger, config));
-        allButtons.addAll(getCommandButtons(logger, config));
-        if (allButtons.isEmpty()) {
-            logger.severe("Failed to create any valid buttons for the form! The form configuration is malformed!");
-            return false;
-        }
-
-        // The start index of the command buttons is simply the amount of server buttons
-        commandsIndex = validServerNames.size();
-
-        // Create the form without the Builder so that we have more control over the list of buttons
         String title = config.getString("Bedrock-Selector.Title");
         String content = config.getString("Bedrock-Selector.Content");
         if (title == null || content == null) {
@@ -96,17 +98,34 @@ private boolean load(@Nonnull FileConfiguration config) {
             return false;
         }
 
-        serverSelector = SimpleForm.of(title, content, allButtons);
+        Map<ButtonComponent, String> serverButtonMap = getServerButtons(config);
+        Map<ButtonComponent, List<String>> commandButtonMap = getCommandButtons(logger, config);
+        if (serverButtonMap.isEmpty() && commandButtonMap.isEmpty()) {
+            logger.severe("Failed to create any valid buttons for the form! The form configuration is malformed!");
+            return false;
+        }
+
+        // Only set everything once it has been validated
+        this.title = title;
+        this.content = content;
+        List<ButtonComponent> allButtons = new ArrayList<>();
+        allButtons.addAll(serverButtonMap.keySet());
+        allButtons.addAll(commandButtonMap.keySet());
+        this.allButtons = allButtons;
+        this.serverNames = new ArrayList<>(serverButtonMap.values());
+        this.commands = new ArrayList<>(commandButtonMap.values());
+        this.commandsIndex = serverButtonMap.size();
+
         return true;
     }
 
     /**
-     *  Get the server buttons and set {@link BedrockMenu#validServerNames}
-     * @param logger The logger to send messages to
+     *  Get the server buttons and each button's server
      * @param config The configuration to pull the servers from
      * @return A list of ButtonComponents, which may be empty.
      */
-    private List<ButtonComponent> getServerButtons(@Nonnull SelectorLogger logger, @Nonnull FileConfiguration config) {
+    private LinkedHashMap<ButtonComponent, String> getServerButtons(@Nonnull FileConfiguration config) {
+        SelectorLogger logger = SelectorLogger.getLogger();
 
         // Enter the Bedrock-Selector.Servers section
         ConfigurationSection serverSection;
@@ -115,18 +134,16 @@ private List<ButtonComponent> getServerButtons(@Nonnull SelectorLogger logger, @
             assert serverSection != null;
         } else {
             logger.debug("Failed to create any server buttons because the configuration is malformed! Regenerate it.");
-            return Collections.emptyList();
+            return new LinkedHashMap<>(Collections.emptyMap());
         }
         // Get all the defined servers in our config
         Set<String> allServers = serverSection.getKeys(false);
         if (allServers.isEmpty()) {
             logger.debug("Failed to create any server buttons because there are no defined servers in the form configuration!");
-            return Collections.emptyList();
+            return new LinkedHashMap<>(Collections.emptyMap());
         }
-        // Create a list of buttons. For every defined server with a valid button configuration, we add its button. The index value is the button id.
-        List<ButtonComponent> buttonComponents = new ArrayList<>();
-        // This list will contain the name of every server that has a valid button. The index value is the button id.
-        List<String> validServerNames = new ArrayList<>(2);
+        // Create a map of buttons and their server. For every defined server with a valid button configuration, we add its button.
+        LinkedHashMap<ButtonComponent, String> buttonComponents = new LinkedHashMap<>();
         for (String serverName : allServers) {
             ConfigurationSection serverInfo = serverSection.getConfigurationSection(serverName);
             if (serverInfo == null) {
@@ -141,13 +158,12 @@ private List<ButtonComponent> getServerButtons(@Nonnull SelectorLogger logger, @
                 if (serverInfo.contains("ImageURL", true)) {
                     String imageURL = serverInfo.getString("ImageURL");
                     assert imageURL != null;
-                    buttonComponents.add(ButtonComponent.of(buttonText, FormImage.Type.URL, imageURL));
+                    buttonComponents.put(ButtonComponent.of(buttonText, FormImage.Type.URL, imageURL), serverName);
                     logger.debug(serverName + " contains image");
                 } else {
-                    buttonComponents.add(ButtonComponent.of(buttonText));
+                    buttonComponents.put(ButtonComponent.of(buttonText), serverName);
                     logger.debug(serverName + " does not contain image");
                 }
-                validServerNames.add(serverName);
                 logger.debug("added server for \"" + serverName + "\" with button text: " + buttonText);
             }
         }
@@ -155,18 +171,16 @@ private List<ButtonComponent> getServerButtons(@Nonnull SelectorLogger logger, @
             logger.warn("Failed to create any valid server buttons for the form! The form configuration is malformed!");
         }
 
-        // Save the valid server names so that the response handler knows the server identity of each button
-        this.validServerNames = validServerNames;
         return buttonComponents;
     }
 
     /**
-     * Get the server buttons and set {@link BedrockMenu#validCommands}
+     * Get the command buttons and their commands
      * @param logger The logger to send messages to
      * @param config The configuration to pull the commands from
      * @return A list of ButtonComponents, which may be empty.
      */
-    private List<ButtonComponent> getCommandButtons(@Nonnull SelectorLogger logger, @Nonnull FileConfiguration config) {
+    private LinkedHashMap<ButtonComponent, List<String>> getCommandButtons(@Nonnull SelectorLogger logger, @Nonnull FileConfiguration config) {
 
         // Enter the Bedrock-Selector.Commands section
         ConfigurationSection commandSection;
@@ -175,18 +189,16 @@ private List<ButtonComponent> getCommandButtons(@Nonnull SelectorLogger logger,
             assert commandSection != null;
         } else {
             logger.debug("Failed to create any command buttons because the configuration is malformed! Regenerate it.");
-            return Collections.emptyList();
+            return new LinkedHashMap<>(Collections.emptyMap());
         }
-        // Get all the defined servers in our config
+        // Get all the defined commands in our config
         Set<String> allCommands = commandSection.getKeys(false);
         if (allCommands.isEmpty()) {
             logger.debug("Failed to create any command buttons because there are no defined commands in the form configuration!");
-            return Collections.emptyList();
+            return new LinkedHashMap<>(Collections.emptyMap());
         }
-        // Create a list of buttons. For every defined command with a valid configuration, we add its button. The index value is the button id.
-        List<ButtonComponent> buttonComponents = new ArrayList<>();
-        // This list will contain every command that has a valid button. The index value is the button id.
-        List<List<String>> validCommands = new ArrayList<>();
+        // Create a map of buttons and their commands. For every defined command with a valid configuration, we add its button.
+        LinkedHashMap<ButtonComponent, List<String>> buttonComponents = new LinkedHashMap<>();
         for (String commandEntry : allCommands) {
             ConfigurationSection commandInfo = commandSection.getConfigurationSection(commandEntry);
             if (commandInfo == null) {
@@ -201,22 +213,20 @@ private List<ButtonComponent> getCommandButtons(@Nonnull SelectorLogger logger,
                 if (commandInfo.contains("ImageURL", true)) {
                     String imageURL = commandInfo.getString("ImageURL");
                     assert imageURL != null;
-                    buttonComponents.add(ButtonComponent.of(buttonText, FormImage.Type.URL, imageURL));
+                    buttonComponents.put(ButtonComponent.of(buttonText, FormImage.Type.URL, imageURL), commandInfo.getStringList("Commands"));
                     logger.debug("Command " + commandEntry + " contains an image");
                 } else {
-                    buttonComponents.add(ButtonComponent.of(buttonText));
+                    buttonComponents.put(ButtonComponent.of(buttonText), commandInfo.getStringList("Commands"));
                     logger.debug("Command " + commandEntry + " does not contain an image");
                 }
-                validCommands.add(commandInfo.getStringList("Commands"));
                 logger.debug("added command for \"" + commandEntry + "\" with button text: " + buttonText);
             }
         }
+        // Warn if there were defined commands but they were all malformed
         if (buttonComponents.isEmpty()) {
             logger.warn("Failed to create any valid commands buttons for the form! The form configuration is malformed!");
         }
 
-        // Save the valid commands so that the response handler knows which command should be sent for each button
-        this.validCommands = validCommands;
         return buttonComponents;
     }
 
@@ -233,6 +243,14 @@ public void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
             return;
         }
 
+        // Resolve any placeholders in the button text
+        List<ButtonComponent> formattedButtons = new ArrayList<>();
+        for (ButtonComponent component : allButtons) {
+            formattedButtons.add(ButtonComponent.of(PlaceholderAPI.setPlaceholders(player, component.getText()), component.getImage()));
+        }
+        // Create the form
+        SimpleForm serverSelector = SimpleForm.of(title, content, formattedButtons);
+
         // Set the response handler
         serverSelector.setResponseHandler((responseData) -> {
             SimpleFormResponse response = serverSelector.parseResponse(responseData);
@@ -245,7 +263,7 @@ public void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
             int buttonID = response.getClickedButtonId();
             if (buttonID < commandsIndex) {
                 // This should never be out of bounds considering its size is the number of valid buttons
-                String serverName = validServerNames.get(buttonID);
+                String serverName = serverNames.get(buttonID);
                 ByteArrayOutputStream b = new ByteArrayOutputStream();
                 DataOutputStream out = new DataOutputStream(b);
                 try {
@@ -259,9 +277,9 @@ public void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
                 }
             } else {
                 // Get the commands from the list of commands and replace any playerName placeholders
-                for (String command : validCommands.get(buttonID - commandsIndex)) {
+                for (String command : commands.get(buttonID - commandsIndex)) {
                     String functionalCommand = command.replace("{playerName}", player.getName()).replace("{playerUUID}", player.getUniqueId().toString());
-                    Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), functionalCommand);
+                    Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), PlaceholderAPI.setPlaceholders(player, functionalCommand));
                 }
             }
         });
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/JavaMenu.java
index 89371d0..001375a 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/JavaMenu.java
@@ -1,7 +1,7 @@
 package dev.projectg.geyserhub.module.menu;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.module.scoreboard.Placeholders;
+import dev.projectg.geyserhub.module.Placeholders;
 import dev.projectg.geyserhub.utils.bstats.SelectorLogger;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
diff --git a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
index 3bb8964..468bf59 100644
--- a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
+++ b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
@@ -1,6 +1,7 @@
 package dev.projectg.geyserhub.module.scoreboard;
 
 import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.module.Placeholders;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
 import org.bukkit.entity.Player;
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 0e16b04..f168d80 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,10 +1,14 @@
-name: GeyserHub
 main: dev.projectg.geyserhub.GeyserHubMain
-version: 1.1.0
+name: GeyserHub
+version: 1.2.0
 api-version: 1.16
+description: A crossplay compatible lobby plugin
+
 depend:
   - floodgate
-description: An crossplay compatible lobby plugin
+softdepend:
+  - PlaceholderAPI
+
 commands:
   ghteleporter:
     description: Open a server selector form.

From c1488a4d3130d4f3010f32480bdc2baedd6ddd47 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 9 Jun 2021 15:53:36 -0400
Subject: [PATCH 02/74] add placeholder to other places

improve random broadcast and remove unnecessary scoreboard stuff
---
 pom.xml                                       | 26 ----------
 .../dev/projectg/geyserhub/GeyserHubMain.java | 47 ++-----------------
 .../geyserhub/module/Placeholders.java        | 44 +----------------
 .../geyserhub/module/message/BroadCast.java   | 42 -----------------
 .../geyserhub/module/message/Broadcast.java   | 46 ++++++++++++++++++
 .../geyserhub/module/message/MessageJoin.java |  3 +-
 .../module/scoreboard/ScoreboardManager.java  |  9 ++--
 7 files changed, 60 insertions(+), 157 deletions(-)
 delete mode 100644 src/main/java/dev/projectg/geyserhub/module/message/BroadCast.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java

diff --git a/pom.xml b/pom.xml
index 1044b85..06b709a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -40,18 +40,10 @@
                 <enabled>true</enabled>
             </snapshots>
         </repository>
-        <repository>
-            <id>essentials-snapshots</id>
-            <url>https://repo.essentialsx.net/snapshots/</url>
-        </repository>
         <repository>
             <id>placeholderapi</id>
             <url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
         </repository>
-        <repository>
-            <id>jitpack.io</id>
-            <url>https://jitpack.io</url>
-        </repository>
     </repositories>
     <dependencies>
         <dependency>
@@ -72,30 +64,12 @@
             <version>2.0-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>org.bstats</groupId>
-            <artifactId>bstats-bukkit</artifactId>
-            <version>2.2.1</version>
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
-            <groupId>net.essentialsx</groupId>
-            <artifactId>EssentialsX</artifactId>
-            <version>2.19.0-SNAPSHOT</version>
-            <scope>provided</scope>
-        </dependency>
         <dependency>
             <groupId>me.clip</groupId>
             <artifactId>placeholderapi</artifactId>
             <version>2.10.9</version>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>com.github.MilkBowl</groupId>
-            <artifactId>VaultAPI</artifactId>
-            <version>1.7</version>
-            <scope>provided</scope>
-        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 8462ec7..aa3315d 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -6,20 +6,17 @@
 import dev.projectg.geyserhub.module.listeners.ItemInteract;
 import dev.projectg.geyserhub.module.listeners.SelectorInventory;
 import dev.projectg.geyserhub.module.listeners.ItemOnJoin;
-import dev.projectg.geyserhub.module.message.BroadCast;
+import dev.projectg.geyserhub.module.message.Broadcast;
 import dev.projectg.geyserhub.module.message.MessageJoin;
 import dev.projectg.geyserhub.module.Placeholders;
 import dev.projectg.geyserhub.module.scoreboard.ScoreboardManager;
 import dev.projectg.geyserhub.module.world.WorldSettings;
 import dev.projectg.geyserhub.utils.bstats.Metrics;
 import dev.projectg.geyserhub.utils.bstats.SelectorLogger;
-import net.milkbowl.vault.economy.Economy;
-import net.milkbowl.vault.permission.Permission;
 import org.bukkit.Bukkit;
 import org.bukkit.configuration.InvalidConfigurationException;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.configuration.file.YamlConfiguration;
-import org.bukkit.plugin.RegisteredServiceProvider;
 import org.bukkit.plugin.java.JavaPlugin;
 
 import java.io.File;
@@ -55,36 +52,18 @@ public void onEnable() {
         Bukkit.getServer().getPluginManager().registerEvents(new WorldSettings(), this);
 
         if (getConfig().getBoolean("Scoreboard.Enable", false)) {
-            enableScoreboards();
+            initializeScoreboard();
         }
         if (getConfig().getBoolean("Enable-Join-Message", false)) {
             Bukkit.getServer().getPluginManager().registerEvents(new MessageJoin(), this);
         }
-            BroadCast.startBroadcastTimer(getServer().getScheduler());
+        Broadcast.startBroadcastTimer(getServer().getScheduler());
     }
 
     @Override
     public void onDisable() {
     }
 
-    private void enableScoreboards() {
-        if (this.getServer().getPluginManager().getPlugin("Vault") == null) {
-            Placeholders.vault = 0;
-        } else {
-            this.setupPermissions();
-        }
-        if (this.getServer().getPluginManager().getPlugin("PlaceholderAPI") == null) {
-            Placeholders.PAPI = 0;
-        }
-        if (this.getServer().getPluginManager().getPlugin("Essentials") == null) {
-            Placeholders.essentials = 0;
-        } else {
-            this.setupEconomy();
-        }
-        this.Scheduler();
-        Bukkit.getServer().getPluginManager().registerEvents(new ScoreboardManager(), this);
-    }
-
     public boolean loadConfiguration() {
         File configFile = new File(getDataFolder(), "config.yml");
         if (!configFile.exists()) {
@@ -118,30 +97,14 @@ public boolean loadConfiguration() {
             return false;
         }
     }
-    public void Scheduler() {
+    public void initializeScoreboard() {
         Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
             try {
                 ScoreboardManager.addScoreboard();
             } catch (Exception var2) {
                 var2.printStackTrace();
             }
-        }, 20L, Placeholders.isb * 20L);
-    }
-
-    // todo: I think we can just set vault as a softdepend instead of doing this?
-
-    private void setupEconomy() {
-        RegisteredServiceProvider economyProvider = getServer().getServicesManager().getRegistration(Economy.class);
-        if (economyProvider != null) {
-            Placeholders.economy = (Economy) economyProvider.getProvider();
-        }
-    }
-
-    private void setupPermissions() {
-        RegisteredServiceProvider permissionProvider = this.getServer().getServicesManager().getRegistration(Permission.class);
-        if (permissionProvider != null) {
-            Placeholders.permission = (Permission) permissionProvider.getProvider();
-        }
+        }, 20L, Placeholders.refreshRate * 20L);
     }
 
     public static GeyserHubMain getInstance() {
diff --git a/src/main/java/dev/projectg/geyserhub/module/Placeholders.java b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
index a50d106..9a25b60 100644
--- a/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
+++ b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
@@ -1,52 +1,12 @@
 package dev.projectg.geyserhub.module;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import me.clip.placeholderapi.PlaceholderAPI;
-import net.milkbowl.vault.economy.Economy;
-import net.milkbowl.vault.permission.Permission;
-import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
 import org.bukkit.entity.Player;
-import org.bukkit.event.Listener;
 
 import java.util.ArrayList;
 
-public class Placeholders implements Listener {
-    public static Economy economy = null;
-    public static Permission permission = null;
-    public static int PAPI = 1;
-    public static int vault = 1;
-    public static int essentials = 1;
+public class Placeholders {
     public static String[] colorCodes = {"&0", "&1", "&2", "&3", "&4", "&5", "&6", "&7", "&8", "&9", "&a", "&b", "&c", "&d", "&e", "&f"};
     public static ArrayList<Player> hide = new ArrayList<>();
-    public static int isb;
-    public static String g;
-    public static String b;
-
-    static {
-        isb = GeyserHubMain.getInstance().getConfig().getInt("Scoreboard.Refresh-rate");
-        g = "N/A";
-        b = "N/A";
-    }
-
-    public Placeholders() {
-    }
-
-    public static String getServerVersion() {
-        return Bukkit.getServer().getClass().getPackage().getName().substring(23);
-    }
-
-    // todo I dont think we need this...
-    public static String replaceValues(Player player, String x) {
-        if (vault != 0 && permission.hasGroupSupport()) {
-            g = permission.getPrimaryGroup(player);
-        }
-
-        if (essentials != 0) {
-            b = String.valueOf(economy.getBalance(player));
-        }
-
-        String m = ChatColor.translateAlternateColorCodes('&', x.replace("{playerName}", player.getName()).replace("{onlinePlayers}", String.valueOf(Bukkit.getOnlinePlayers().size())).replace("{maxPlayers}", String.valueOf(Bukkit.getMaxPlayers())).replace("{Group}", g).replace("{Money}", b)).replace("null", "N/A");
-        return PAPI == 1 ? PlaceholderAPI.setPlaceholders(player, m) : m;
-    }
+    public static int refreshRate = GeyserHubMain.getInstance().getConfig().getInt("Scoreboard.Refresh-rate");
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/BroadCast.java b/src/main/java/dev/projectg/geyserhub/module/message/BroadCast.java
deleted file mode 100644
index 11009ba..0000000
--- a/src/main/java/dev/projectg/geyserhub/module/message/BroadCast.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package dev.projectg.geyserhub.module.message;
-
-import dev.projectg.geyserhub.GeyserHubMain;
-import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
-import org.bukkit.Sound;
-import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.entity.Player;
-import org.bukkit.scheduler.BukkitScheduler;
-
-import java.util.Iterator;
-import java.util.Objects;
-import java.util.Random;
-import java.util.Set;
-
-public class BroadCast {
-    public static void startBroadcastTimer(BukkitScheduler scheduler) {
-        int scheduleId = scheduler.scheduleSyncDelayedTask(GeyserHubMain.getInstance(), () -> {
-            if (GeyserHubMain.getInstance().getConfig().getBoolean("Broadcasts-enabled")) {
-                Set<String> broadcastsList = Objects.requireNonNull(GeyserHubMain.getInstance().getConfig().getConfigurationSection("Broadcasts")).getKeys(false);
-                String broadcastId = getRandomElement(broadcastsList);
-                ConfigurationSection broadcast = GeyserHubMain.getInstance().getConfig().getConfigurationSection("Broadcasts." + broadcastId);
-                assert broadcast != null;
-                for (String message : broadcast.getStringList("Messages")) {
-                    for (Player player : Bukkit.getOnlinePlayers()) {
-                        player.sendMessage(ChatColor.translateAlternateColorCodes('&', message));
-                    }
-                }
-            }
-            startBroadcastTimer(scheduler);
-        }, GeyserHubMain.getInstance().getConfig().getLong("Broadcast-interval"));
-    }
-
-    private static String getRandomElement(Set<String> set) {
-        int index = new Random().nextInt(set.size());
-        Iterator<String> iter = set.iterator();
-        for (int i = 0; i < index; i++) {
-            iter.next();
-        }
-        return iter.next();
-    }
-}
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
new file mode 100644
index 0000000..3477eb2
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
@@ -0,0 +1,46 @@
+package dev.projectg.geyserhub.module.message;
+
+import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.utils.bstats.SelectorLogger;
+import me.clip.placeholderapi.PlaceholderAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitScheduler;
+
+import java.util.*;
+
+public class Broadcast {
+    public static void startBroadcastTimer(BukkitScheduler scheduler) {
+        int scheduleId = scheduler.scheduleSyncDelayedTask(GeyserHubMain.getInstance(), () -> {
+
+            if (GeyserHubMain.getInstance().getConfig().getBoolean("Broadcasts-enabled")) {
+                ConfigurationSection parentSection = GeyserHubMain.getInstance().getConfig().getConfigurationSection("Broadcasts");
+                if (parentSection == null) {
+                    SelectorLogger.getLogger().severe("Broadcast configuration section is malformed, unable to send.");
+                    return;
+                }
+
+                String broadcastId = getRandomElement(new ArrayList<>(parentSection.getKeys(false)));
+                ConfigurationSection broadcast = parentSection.getConfigurationSection(broadcastId);
+                assert broadcast != null;
+
+                if (broadcast.contains("Messages", true) && broadcast.isList("Messages")) {
+                    for (String message : broadcast.getStringList("Messages")) {
+                        for (Player player : Bukkit.getOnlinePlayers()) {
+                            player.sendMessage(ChatColor.translateAlternateColorCodes('&', PlaceholderAPI.setPlaceholders(player, message)));
+                        }
+                    }
+                } else {
+                    SelectorLogger.getLogger().severe("Broadcast with ID " + broadcastId + " has a malformed message list, unable to send.");
+                }
+            }
+            startBroadcastTimer(scheduler);
+        }, GeyserHubMain.getInstance().getConfig().getLong("Broadcast-interval"));
+    }
+
+    private static String getRandomElement(List<String> list) {
+        return list.get(new Random().nextInt(list.size()));
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java b/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
index 30cd44e..f80fb34 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
@@ -1,6 +1,7 @@
 package dev.projectg.geyserhub.module.message;
 
 import dev.projectg.geyserhub.GeyserHubMain;
+import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.ChatColor;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
@@ -18,7 +19,7 @@ public void onJoin(PlayerJoinEvent e) {
         List<String> messages = GeyserHubMain.getInstance().getConfig().getStringList("Join-Message");
 
         for (String message : messages) {
-            player.sendMessage(ChatColor.translateAlternateColorCodes('&', message.replace("{playerName}", player.getName())));
+            player.sendMessage(ChatColor.translateAlternateColorCodes('&', PlaceholderAPI.setPlaceholders(player, message)));
         }
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
index 468bf59..7ae1cd2 100644
--- a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
+++ b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
@@ -1,7 +1,7 @@
 package dev.projectg.geyserhub.module.scoreboard;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.module.Placeholders;
+import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
 import org.bukkit.entity.Player;
@@ -11,7 +11,7 @@
 import java.util.Objects;
 
 
-public class ScoreboardManager extends Placeholders {
+public class ScoreboardManager {
     public ScoreboardManager() {
     }
 
@@ -25,7 +25,8 @@ public static void addScoreboard() {
 
     public static void createScoreboard(Player player) {
         Scoreboard board = Objects.requireNonNull(Bukkit.getServer().getScoreboardManager()).getNewScoreboard();
-        Objective objective = board.registerNewObjective("GeyserHub", "dummy", replaceValues(player, GeyserHubMain.getInstance().getConfig().getString("Scoreboard.Title")));
+        Objective objective = board.registerNewObjective("GeyserHub", "dummy", PlaceholderAPI.setPlaceholders(player, GeyserHubMain.getInstance().getConfig().getString("Scoreboard.Title", "GeyserHub")));
+
         objective.setDisplaySlot(DisplaySlot.SIDEBAR);
         List<String> text = GeyserHubMain.getInstance().getConfig().getStringList("Scoreboard.Lines");
 
@@ -33,7 +34,7 @@ public static void createScoreboard(Player player) {
         int limit = Math.min(text.size(), 16);
 
         for (int index = 0; index < limit; index++) {
-            String formattedLine = replaceValues(player, text.get(index));
+            String formattedLine = PlaceholderAPI.setPlaceholders(player, text.get(index));
             Score score = objective.getScore(ChatColor.translateAlternateColorCodes('&', formattedLine));
             score.setScore(limit - index);
         }

From 4f4bc8052930f1343cfeafb2fbc4d3e01f4c2f4e Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 9 Jun 2021 16:04:14 -0400
Subject: [PATCH 03/74] add branch name to jenkinsfile discord push

---
 Jenkinsfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Jenkinsfile b/Jenkinsfile
index f6f8577..d1af352 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -11,7 +11,7 @@ pipeline {
         stage('Post') {
             steps {
                 archiveArtifacts 'target/GeyserHub*.jar'
-                discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}]" , footer: 'ProjectG', link: env.BUILD_URL, result: currentBuild.currentResult, title: "ProjectG/GeyserHub", webhookURL: "https://discord.com/api/webhooks/829602972098887720/kscr0LGNfA6cyYEtg0Gkfzu0gD4jmun6x-p3xPW2_xhH3BmOQD6ytc7jFx1j6cuTqlRq"
+                discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}]" , footer: 'ProjectG', link: env.BUILD_URL, result: currentBuild.currentResult, title: "ProjectG/GeyserHub/${env.BRANCH_NAME}", webhookURL: "https://discord.com/api/webhooks/829602972098887720/kscr0LGNfA6cyYEtg0Gkfzu0gD4jmun6x-p3xPW2_xhH3BmOQD6ytc7jFx1j6cuTqlRq"
                   }
 
                 }

From 8b03b00f0af5416e96461d0fa38f673f829fab25 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 9 Jun 2021 17:27:12 -0400
Subject: [PATCH 04/74] fix scoreboard line limit

---
 .../projectg/geyserhub/module/scoreboard/ScoreboardManager.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
index 7ae1cd2..016f747 100644
--- a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
+++ b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
@@ -31,7 +31,7 @@ public static void createScoreboard(Player player) {
         List<String> text = GeyserHubMain.getInstance().getConfig().getStringList("Scoreboard.Lines");
 
         // Scoreboards have a max of 15 lines
-        int limit = Math.min(text.size(), 16);
+        int limit = Math.min(text.size(), 15);
 
         for (int index = 0; index < limit; index++) {
             String formattedLine = PlaceholderAPI.setPlaceholders(player, text.get(index));

From fabff28df75757433cb9d3f1b46fabf0be19436c Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 9 Jun 2021 19:36:27 -0400
Subject: [PATCH 05/74] add branch and commit log message, fix util subpackage

---
 pom.xml                                       | 35 +++++++++++++++++++
 .../dev/projectg/geyserhub/GeyserHubMain.java | 17 ++++++++-
 .../{utils/bstats => }/Reloadable.java        |  2 +-
 .../bstats => }/ReloadableRegistry.java       |  7 ++--
 .../{utils/bstats => }/SelectorLogger.java    |  8 +++--
 .../geyserhub/command/ReloadCommand.java      |  4 +--
 .../geyserhub/command/SelectorCommand.java    |  2 +-
 .../geyserhub/module/Placeholders.java        |  2 +-
 .../module/listeners/SelectorInventory.java   |  2 +-
 .../geyserhub/module/menu/BedrockMenu.java    |  6 ++--
 .../geyserhub/module/menu/JavaMenu.java       |  2 +-
 .../geyserhub/module/message/Broadcast.java   |  6 ++--
 .../dev/projectg/geyserhub/utils/Utils.java   | 14 ++++++++
 src/main/resources/config.yml                 | 22 ++----------
 14 files changed, 91 insertions(+), 38 deletions(-)
 rename src/main/java/dev/projectg/geyserhub/{utils/bstats => }/Reloadable.java (85%)
 rename src/main/java/dev/projectg/geyserhub/{utils/bstats => }/ReloadableRegistry.java (77%)
 rename src/main/java/dev/projectg/geyserhub/{utils/bstats => }/SelectorLogger.java (91%)
 create mode 100644 src/main/java/dev/projectg/geyserhub/utils/Utils.java

diff --git a/pom.xml b/pom.xml
index 06b709a..36c7c80 100644
--- a/pom.xml
+++ b/pom.xml
@@ -82,6 +82,41 @@
                     <target>11</target>
                 </configuration>
             </plugin>
+            <plugin>
+                <groupId>pl.project13.maven</groupId>
+                <artifactId>git-commit-id-plugin</artifactId>
+                <version>4.0.0</version>
+                <executions>
+                    <execution>
+                        <id>get-the-git-infos</id>
+                        <goals>
+                            <goal>revision</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <generateGitPropertiesFile>true</generateGitPropertiesFile>
+                    <generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
+                    <format>properties</format>
+                    <failOnNoGitDirectory>false</failOnNoGitDirectory>
+                    <failOnUnableToExtractRepoInfo>false</failOnUnableToExtractRepoInfo>
+                    <runOnlyOnce>false</runOnlyOnce>
+                    <verbose>true</verbose>
+                    <skipPoms>false</skipPoms>
+                    <excludeProperties>
+                        <excludeProperty>git.user.*</excludeProperty>
+                        <excludeProperty>git.*.user.*</excludeProperty>
+                        <excludeProperty>git.closest.*</excludeProperty>
+                        <excludeProperty>git.commit.id.describe</excludeProperty>
+                        <excludeProperty>git.commit.id.describe-short</excludeProperty>
+                        <excludeProperty>git.commit.message.short</excludeProperty>
+                    </excludeProperties>
+                    <commitIdGenerationMode>flat</commitIdGenerationMode>
+                    <gitDescribe>
+                        <always>true</always>
+                    </gitDescribe>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 </project>
diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index aa3315d..edf68b5 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -11,8 +11,8 @@
 import dev.projectg.geyserhub.module.Placeholders;
 import dev.projectg.geyserhub.module.scoreboard.ScoreboardManager;
 import dev.projectg.geyserhub.module.world.WorldSettings;
+import dev.projectg.geyserhub.utils.Utils;
 import dev.projectg.geyserhub.utils.bstats.Metrics;
-import dev.projectg.geyserhub.utils.bstats.SelectorLogger;
 import org.bukkit.Bukkit;
 import org.bukkit.configuration.InvalidConfigurationException;
 import org.bukkit.configuration.file.FileConfiguration;
@@ -22,6 +22,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.Objects;
+import java.util.Properties;
 
 public class GeyserHubMain extends JavaPlugin {
     private static GeyserHubMain plugin;
@@ -33,11 +34,25 @@ public class GeyserHubMain extends JavaPlugin {
     public void onEnable() {
         plugin = this;
         new Metrics(this, 11427);
+        // getting the logger forces the config to load before our loadConfiguration() is called...
         logger = SelectorLogger.getLogger();
+
+        try {
+            Properties gitProperties = new Properties();
+            gitProperties.load(Utils.getResource("git.properties"));
+            logger.info("Branch: " + gitProperties.getProperty("git.branch", "Unknown") + ", Commit: " + gitProperties.getProperty("git.commit.id.abbrev", "Unknown"));
+        } catch (IOException e) {
+            logger.warn("Unable to load resource: git.properties");
+            if (logger.isDebug()) {
+                e.printStackTrace();
+            }
+        }
+
         if (!loadConfiguration()) {
             logger.severe("Disabling due to configuration error. Fix the formatting or regenerate a new one");
             return;
         }
+
         // Bungee channel for selector
         getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
 
diff --git a/src/main/java/dev/projectg/geyserhub/utils/bstats/Reloadable.java b/src/main/java/dev/projectg/geyserhub/Reloadable.java
similarity index 85%
rename from src/main/java/dev/projectg/geyserhub/utils/bstats/Reloadable.java
rename to src/main/java/dev/projectg/geyserhub/Reloadable.java
index 928ac7c..58f7b0c 100644
--- a/src/main/java/dev/projectg/geyserhub/utils/bstats/Reloadable.java
+++ b/src/main/java/dev/projectg/geyserhub/Reloadable.java
@@ -1,4 +1,4 @@
-package dev.projectg.geyserhub.utils.bstats;
+package dev.projectg.geyserhub;
 
 /**
  * Any classes that implements this interface should be able to reload their functionality.
diff --git a/src/main/java/dev/projectg/geyserhub/utils/bstats/ReloadableRegistry.java b/src/main/java/dev/projectg/geyserhub/ReloadableRegistry.java
similarity index 77%
rename from src/main/java/dev/projectg/geyserhub/utils/bstats/ReloadableRegistry.java
rename to src/main/java/dev/projectg/geyserhub/ReloadableRegistry.java
index 4641ff5..fb8b55a 100644
--- a/src/main/java/dev/projectg/geyserhub/utils/bstats/ReloadableRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/ReloadableRegistry.java
@@ -1,14 +1,17 @@
-package dev.projectg.geyserhub.utils.bstats;
+package dev.projectg.geyserhub;
+
+import dev.projectg.geyserhub.Reloadable;
 
 import javax.annotation.Nonnull;
 import java.util.HashSet;
+import java.util.Set;
 
 public class ReloadableRegistry {
 
     /**
      * A set of instances that implement the Reloadable interface
      */
-    private static final HashSet<Reloadable> reloadables = new HashSet<>();
+    private static final Set<Reloadable> reloadables = new HashSet<>();
 
     /**
      * Register a reloadable
diff --git a/src/main/java/dev/projectg/geyserhub/utils/bstats/SelectorLogger.java b/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
similarity index 91%
rename from src/main/java/dev/projectg/geyserhub/utils/bstats/SelectorLogger.java
rename to src/main/java/dev/projectg/geyserhub/SelectorLogger.java
index 23d2e8b..98366c6 100644
--- a/src/main/java/dev/projectg/geyserhub/utils/bstats/SelectorLogger.java
+++ b/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
@@ -1,6 +1,4 @@
-package dev.projectg.geyserhub.utils.bstats;
-
-import dev.projectg.geyserhub.GeyserHubMain;
+package dev.projectg.geyserhub;
 
 public class SelectorLogger implements Reloadable {
 
@@ -34,6 +32,10 @@ public void debug(String message) {
         }
     }
 
+    public boolean isDebug() {
+        return debug;
+    }
+
     @Override
     public boolean reload() {
         debug = plugin.getConfig().getBoolean("Enable-Debug", false);
diff --git a/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java b/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java
index 37f9a3d..fa8f2b3 100644
--- a/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java
+++ b/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java
@@ -1,8 +1,8 @@
 package dev.projectg.geyserhub.command;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.utils.bstats.Reloadable;
-import dev.projectg.geyserhub.utils.bstats.ReloadableRegistry;
+import dev.projectg.geyserhub.Reloadable;
+import dev.projectg.geyserhub.ReloadableRegistry;
 import org.bukkit.ChatColor;
 import org.bukkit.command.Command;
 import org.bukkit.command.CommandExecutor;
diff --git a/src/main/java/dev/projectg/geyserhub/command/SelectorCommand.java b/src/main/java/dev/projectg/geyserhub/command/SelectorCommand.java
index 0e6efd6..407d250 100644
--- a/src/main/java/dev/projectg/geyserhub/command/SelectorCommand.java
+++ b/src/main/java/dev/projectg/geyserhub/command/SelectorCommand.java
@@ -1,7 +1,7 @@
 package dev.projectg.geyserhub.command;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.utils.bstats.SelectorLogger;
+import dev.projectg.geyserhub.SelectorLogger;
 import dev.projectg.geyserhub.module.menu.BedrockMenu;
 import dev.projectg.geyserhub.module.menu.JavaMenu;
 import org.bukkit.ChatColor;
diff --git a/src/main/java/dev/projectg/geyserhub/module/Placeholders.java b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
index 9a25b60..d8a4d26 100644
--- a/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
+++ b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
@@ -6,7 +6,7 @@
 import java.util.ArrayList;
 
 public class Placeholders {
-    public static String[] colorCodes = {"&0", "&1", "&2", "&3", "&4", "&5", "&6", "&7", "&8", "&9", "&a", "&b", "&c", "&d", "&e", "&f"};
+    public static final String[] colorCodes = {"&0", "&1", "&2", "&3", "&4", "&5", "&6", "&7", "&8", "&9", "&a", "&b", "&c", "&d", "&e", "&f"};
     public static ArrayList<Player> hide = new ArrayList<>();
     public static int refreshRate = GeyserHubMain.getInstance().getConfig().getInt("Scoreboard.Refresh-rate");
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/listeners/SelectorInventory.java b/src/main/java/dev/projectg/geyserhub/module/listeners/SelectorInventory.java
index b642a49..0f38d54 100644
--- a/src/main/java/dev/projectg/geyserhub/module/listeners/SelectorInventory.java
+++ b/src/main/java/dev/projectg/geyserhub/module/listeners/SelectorInventory.java
@@ -1,7 +1,7 @@
 package dev.projectg.geyserhub.module.listeners;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.utils.bstats.SelectorLogger;
+import dev.projectg.geyserhub.SelectorLogger;
 import org.bukkit.ChatColor;
 import org.bukkit.Material;
 import org.bukkit.NamespacedKey;
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java
index 3c7f716..5241c96 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java
@@ -1,9 +1,9 @@
 package dev.projectg.geyserhub.module.menu;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.utils.bstats.Reloadable;
-import dev.projectg.geyserhub.utils.bstats.ReloadableRegistry;
-import dev.projectg.geyserhub.utils.bstats.SelectorLogger;
+import dev.projectg.geyserhub.Reloadable;
+import dev.projectg.geyserhub.ReloadableRegistry;
+import dev.projectg.geyserhub.SelectorLogger;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/JavaMenu.java
index 001375a..0b4e593 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/JavaMenu.java
@@ -2,7 +2,7 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.module.Placeholders;
-import dev.projectg.geyserhub.utils.bstats.SelectorLogger;
+import dev.projectg.geyserhub.SelectorLogger;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
index 3477eb2..db2737d 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
@@ -1,7 +1,7 @@
 package dev.projectg.geyserhub.module.message;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.utils.bstats.SelectorLogger;
+import dev.projectg.geyserhub.SelectorLogger;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
@@ -15,7 +15,7 @@ public class Broadcast {
     public static void startBroadcastTimer(BukkitScheduler scheduler) {
         int scheduleId = scheduler.scheduleSyncDelayedTask(GeyserHubMain.getInstance(), () -> {
 
-            if (GeyserHubMain.getInstance().getConfig().getBoolean("Broadcasts-enabled")) {
+            if (GeyserHubMain.getInstance().getConfig().getBoolean("Broadcasts-Enabled")) {
                 ConfigurationSection parentSection = GeyserHubMain.getInstance().getConfig().getConfigurationSection("Broadcasts");
                 if (parentSection == null) {
                     SelectorLogger.getLogger().severe("Broadcast configuration section is malformed, unable to send.");
@@ -37,7 +37,7 @@ public static void startBroadcastTimer(BukkitScheduler scheduler) {
                 }
             }
             startBroadcastTimer(scheduler);
-        }, GeyserHubMain.getInstance().getConfig().getLong("Broadcast-interval"));
+        }, GeyserHubMain.getInstance().getConfig().getLong("Broadcast-Interval"));
     }
 
     private static String getRandomElement(List<String> list) {
diff --git a/src/main/java/dev/projectg/geyserhub/utils/Utils.java b/src/main/java/dev/projectg/geyserhub/utils/Utils.java
new file mode 100644
index 0000000..f5b9c7e
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/utils/Utils.java
@@ -0,0 +1,14 @@
+package dev.projectg.geyserhub.utils;
+
+import java.io.InputStream;
+
+public class Utils {
+
+    public static InputStream getResource(String resource) {
+        InputStream stream = Utils.class.getClassLoader().getResourceAsStream(resource);
+        if (stream == null) {
+            throw new AssertionError("Unable to find resource: " + resource);
+        }
+        return stream;
+    }
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 8690af1..2d2be2c 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -85,9 +85,9 @@ Scoreboard:
     - "&a  *  &6https://projectg.dev  &a*"
 
 
-Broadcasts-enabled: true
+Broadcasts-Enabled: true
 # ticks between each broadcast (default: 3 minutes)
-Broadcast-interval: 3600
+Broadcast-Interval: 3600
 Broadcasts:
   Message1:
     Messages:
@@ -97,20 +97,4 @@ Broadcasts:
       - '&6*----------------*----------------* '
       - '&b -*-*-*- GeyserHub -*-*-*-'
       - '&b -*-*- Made by ProjectG -*-*-'
-      - '&6*----------------*----------------* '
-
-World-settings:
-  disable-hunger-loss: true
-  disable-fall-damage: true
-  disable-drowning: true
-  disable-fire-damage: true
-  disable-player-pvp: true
-  disable-weather-change: true
-  disable-mob-spawning: true
-  disable-block-burn: true
-  disable-block-fire-spread: true
-  disable_block-leaf-decay: true
-  disable-block-place: true
-  disable-block-break: true
-
-
+      - '&6*----------------*----------------* '
\ No newline at end of file

From 41cbf0f5647a126c232cd1de1224c319e9bc822c Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 9 Jun 2021 22:26:57 -0400
Subject: [PATCH 06/74] add join teleporter, refactor config

---
 .../dev/projectg/geyserhub/GeyserHubMain.java |  6 +-
 .../geyserhub/command/ReloadCommand.java      |  6 +-
 .../geyserhub/module/menu/BedrockMenu.java    |  1 -
 .../geyserhub/module/message/Broadcast.java   | 14 ++-
 .../geyserhub/module/message/MessageJoin.java |  2 +-
 .../module/teleporter/JoinTeleporter.java     | 97 +++++++++++++++++++
 src/main/resources/config.yml                 | 57 +++++++----
 7 files changed, 151 insertions(+), 32 deletions(-)
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index edf68b5..ea690cd 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -10,6 +10,7 @@
 import dev.projectg.geyserhub.module.message.MessageJoin;
 import dev.projectg.geyserhub.module.Placeholders;
 import dev.projectg.geyserhub.module.scoreboard.ScoreboardManager;
+import dev.projectg.geyserhub.module.teleporter.JoinTeleporter;
 import dev.projectg.geyserhub.module.world.WorldSettings;
 import dev.projectg.geyserhub.utils.Utils;
 import dev.projectg.geyserhub.utils.bstats.Metrics;
@@ -28,7 +29,7 @@ public class GeyserHubMain extends JavaPlugin {
     private static GeyserHubMain plugin;
     private SelectorLogger logger;
 
-    public static final int configVersion = 3;
+    public static final int configVersion = 4;
 
     @Override
     public void onEnable() {
@@ -64,6 +65,7 @@ public void onEnable() {
         Bukkit.getServer().getPluginManager().registerEvents(new ItemInteract(), this);
         Bukkit.getServer().getPluginManager().registerEvents(new SelectorInventory(), this);
         Bukkit.getServer().getPluginManager().registerEvents(new ItemOnJoin(), this);
+        Bukkit.getServer().getPluginManager().registerEvents(new JoinTeleporter(), this);
         Bukkit.getServer().getPluginManager().registerEvents(new WorldSettings(), this);
 
         if (getConfig().getBoolean("Scoreboard.Enable", false)) {
@@ -101,7 +103,7 @@ public boolean loadConfiguration() {
                 logger.severe("Config-Version is not an integer!");
                 return false;
             } else if (!(config.getInt("Config-Version") == configVersion)) {
-                logger.severe("Mismatched config version!");
+                logger.severe("Mismatched config version! Generate a new config and migrate your settings!");
                 return false;
             } else {
                 reloadConfig();
diff --git a/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java b/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java
index fa8f2b3..4926dcc 100644
--- a/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java
+++ b/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java
@@ -17,7 +17,9 @@ public boolean onCommand(@Nonnull CommandSender sender, @Nonnull  Command comman
 
         if (sender instanceof Player || sender instanceof ConsoleCommandSender) {
 
-            if (!GeyserHubMain.getInstance().loadConfiguration()) {
+            if (GeyserHubMain.getInstance().loadConfiguration()) {
+                sender.sendMessage("[GeyserHub] Reloaded the configuration, reloading modules...");
+            } else {
                 sender.sendMessage("[GeyserHub] " + ChatColor.RED + "Failed to reload the configuration!");
                 return true;
             }
@@ -27,6 +29,8 @@ public boolean onCommand(@Nonnull CommandSender sender, @Nonnull  Command comman
                     sender.sendMessage("[GeyserHub] " + ChatColor.RED + "Failed to reload class: " + ChatColor.RESET + reloadable.getClass().toString());
                 }
             }
+
+            sender.sendMessage("[GeyserHub] Finished reload.");
         }
         return true;
     }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java
index 5241c96..2b996fe 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java
@@ -52,7 +52,6 @@ public class BedrockMenu implements Reloadable {
     int commandsIndex;
 
     /**
-     *
      * @return Get the latest BedrockMenu instance that was created
      */
     public static BedrockMenu getInstance() {
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
index db2737d..d448877 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
@@ -15,19 +15,17 @@ public class Broadcast {
     public static void startBroadcastTimer(BukkitScheduler scheduler) {
         int scheduleId = scheduler.scheduleSyncDelayedTask(GeyserHubMain.getInstance(), () -> {
 
-            if (GeyserHubMain.getInstance().getConfig().getBoolean("Broadcasts-Enabled")) {
-                ConfigurationSection parentSection = GeyserHubMain.getInstance().getConfig().getConfigurationSection("Broadcasts");
+            if (GeyserHubMain.getInstance().getConfig().getBoolean("Broadcasts.Enable", false)) {
+                ConfigurationSection parentSection = GeyserHubMain.getInstance().getConfig().getConfigurationSection("Broadcasts.Messages");
                 if (parentSection == null) {
-                    SelectorLogger.getLogger().severe("Broadcast configuration section is malformed, unable to send.");
+                    SelectorLogger.getLogger().severe("Broadcasts.Messages configuration section is malformed, unable to send.");
                     return;
                 }
 
                 String broadcastId = getRandomElement(new ArrayList<>(parentSection.getKeys(false)));
-                ConfigurationSection broadcast = parentSection.getConfigurationSection(broadcastId);
-                assert broadcast != null;
 
-                if (broadcast.contains("Messages", true) && broadcast.isList("Messages")) {
-                    for (String message : broadcast.getStringList("Messages")) {
+                if (parentSection.contains(broadcastId, true) && parentSection.isList(broadcastId)) {
+                    for (String message : parentSection.getStringList(broadcastId)) {
                         for (Player player : Bukkit.getOnlinePlayers()) {
                             player.sendMessage(ChatColor.translateAlternateColorCodes('&', PlaceholderAPI.setPlaceholders(player, message)));
                         }
@@ -37,7 +35,7 @@ public static void startBroadcastTimer(BukkitScheduler scheduler) {
                 }
             }
             startBroadcastTimer(scheduler);
-        }, GeyserHubMain.getInstance().getConfig().getLong("Broadcast-Interval"));
+        }, GeyserHubMain.getInstance().getConfig().getLong("Broadcasts-Interval", 3600));
     }
 
     private static String getRandomElement(List<String> list) {
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java b/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
index f80fb34..8be7a98 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
@@ -16,7 +16,7 @@ public class MessageJoin implements Listener {
     @EventHandler
     public void onJoin(PlayerJoinEvent e) {
         Player player = e.getPlayer();
-        List<String> messages = GeyserHubMain.getInstance().getConfig().getStringList("Join-Message");
+        List<String> messages = GeyserHubMain.getInstance().getConfig().getStringList("Join-Message.Messages");
 
         for (String message : messages) {
             player.sendMessage(ChatColor.translateAlternateColorCodes('&', PlaceholderAPI.setPlaceholders(player, message)));
diff --git a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
new file mode 100644
index 0000000..47d009e
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
@@ -0,0 +1,97 @@
+package dev.projectg.geyserhub.module.teleporter;
+
+import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.Reloadable;
+import dev.projectg.geyserhub.ReloadableRegistry;
+import dev.projectg.geyserhub.SelectorLogger;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+
+import javax.annotation.Nonnull;
+import java.util.Arrays;
+
+public class JoinTeleporter implements Listener, Reloadable {
+
+    private static final String COORDINATE_REGEX = "^(-\\d+|\\d+);(-\\d+|\\d+);(-\\d+|\\d+)$\n";
+
+    private boolean enabled;
+    private Location location;
+
+    public JoinTeleporter() {
+        ReloadableRegistry.registerReloadable(this);
+        enabled = load(GeyserHubMain.getInstance().getConfig());
+    }
+
+    @EventHandler
+    public void onPlayerJoin(PlayerJoinEvent event) {
+        if (enabled) {
+            event.getPlayer().teleport(location);
+        }
+    }
+
+    private boolean load(@Nonnull FileConfiguration config) {
+        SelectorLogger logger = SelectorLogger.getLogger();
+
+        // Get our section
+        if (!(config.contains("Join-Teleporter", true) && config.isConfigurationSection("Join-Teleporter"))) {
+            logger.warn("Configuration does not contain Join-Teleporter section, skipping module.");
+            return false;
+        }
+        ConfigurationSection section = config.getConfigurationSection("Join-Teleporter");
+        assert section != null;
+
+        // Validate all our values
+        if (!(section.contains("Enable", true) && section.isBoolean(("Enable")))) {
+            logger.severe("Join-Teleporter config section does not contain a valid Enable value, skipping module!");
+            return false;
+        }
+        if (!(section.contains("World") && section.isString("World"))) {
+            logger.severe("Join-Teleporter config section does not contain a valid World string, skipping module!");
+            return false;
+        }
+        String worldName = section.getString("World");
+        assert worldName != null;
+        World world = Bukkit.getServer().getWorld(worldName);
+        if (world == null) {
+            logger.severe("Join-Teleporter.World in the config is not a valid world, skipping module!");
+            return false;
+        }
+
+        if (section.contains("Location") && section.isString("Location")) {
+            // Make sure the given coordinates are in the correct format
+            String composedCoords = section.getString("Location");
+            assert composedCoords != null;
+            if (!composedCoords.matches(COORDINATE_REGEX)) {
+                logger.severe("Join-Teleporter.Location in the config is not of the format <integer;integer;integer>, skipping module!");
+                return false;
+            }
+
+            // Decompose the coordinate string into usable values, and set the location to use if successful
+            String[] coordinates = composedCoords.split(";", 3);
+            try {
+                int x = Integer.parseInt(coordinates[0]);
+                int y = Integer.parseInt(coordinates[1]);
+                int z = Integer.parseInt(coordinates[2]);
+                location = new Location(world, x, y, z);
+                return true;
+            } catch (NumberFormatException e) {
+                throw new AssertionError("Failed to decompose the following coordinates: " + composedCoords + " -> " + Arrays.toString(coordinates));
+            }
+        } else {
+            logger.severe("Join-Teleporter config section does not contain a valid Location value!");
+            return false;
+        }
+    }
+
+    @Override
+    public boolean reload() {
+        enabled = load(GeyserHubMain.getInstance().getConfig());
+        return enabled;
+    }
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 2d2be2c..db9283a 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -4,7 +4,7 @@
 Enable-Debug: false
 
 # Don't touch this
-Config-Version: 3
+Config-Version: 4
 
 
 # Gives the player a compass which they can use to open the selector form
@@ -37,7 +37,7 @@ Java-Selector:
       Display-Name: "Survival"
       Material: Emerald
       Lore:
-        - "&2online players: %bungee_Survival%"
+        - "&2online players: %bungee_survival%"
       Slot: 6
 
 Bedrock-Selector:
@@ -48,28 +48,34 @@ Bedrock-Selector:
 
   Servers:
     lobby:
-      Button-Text: "Server Lobby"
+      Button-Text: "Server Lobby: %bungee_lobby% players"
       ImageURL: "https://www.digminecraft.com/block_recipes/images/cyan_concrete.png"
     survival:
-      Button-Text: "Survival of the fittest"
+      Button-Text: "Survival: %bungee_survival% players"
       ImageURL: "https://www.digminecraft.com/block_recipes/images/blue_concrete.png"
   Commands:
     extraOne:
       Button-Text: "Spawn"
       ImageURL: "https://www.digminecraft.com/decoration_recipes/images/lodestone.png"
       Commands:
-        - "execute as {playerName} run spawn"
+        - "execute as %player_name% run spawn"
     extraTwo:
       Button-Text: "The Void"
       Commands:
-        - "tp {playerName} 0 -10 0"
-        - "say {playerName} made a fatal decision."
+        - "tp %player_uuid% 0 -10 0"
+        - "say %player_name% made a fatal decision."
 
-# Join and leave messages. {playerName} is replaced with the player's name.
-Enable-Join-Message: true
+# Join and leave messages. Supports papi placeholders.
 Join-Message:
-  - "&fWelcome &6{playerName}&f on geyser network!"
-  - "&bHave a great time playing on our network!"
+  Enable: true
+  Messages:
+    - "&fWelcome &6%player_name%&f on geyser network!"
+    - "&bHave a great time playing on our network!"
+
+Join-Teleporter:
+  Enable: false
+  World: world
+  Coordinates: 0;65;0
 
 Scoreboard:
   Enable: true
@@ -85,16 +91,29 @@ Scoreboard:
     - "&a  *  &6https://projectg.dev  &a*"
 
 
-Broadcasts-Enabled: true
-# ticks between each broadcast (default: 3 minutes)
-Broadcast-Interval: 3600
 Broadcasts:
-  Message1:
-    Messages:
+  Enable: true
+  # ticks between each broadcast (default: 3 minutes)
+  Interval: 3600
+  Messages:
+    Message1:
       - '&6 -*-*-  Welcome to GeyserNetwork  -*-*- '
-  Message2:
-    Messages:
+    Message2:
       - '&6*----------------*----------------* '
       - '&b -*-*-*- GeyserHub -*-*-*-'
       - '&b -*-*- Made by ProjectG -*-*-'
-      - '&6*----------------*----------------* '
\ No newline at end of file
+      - '&6*----------------*----------------* '
+
+World-settings:
+  disable-hunger-loss: true
+  disable-fall-damage: true
+  disable-drowning: true
+  disable-fire-damage: true
+  disable-player-pvp: true
+  disable-weather-change: true
+  disable-mob-spawning: true
+  disable-block-burn: true
+  disable-block-fire-spread: true
+  disable_block-leaf-decay: true
+  disable-block-place: true
+  disable-block-break: true
\ No newline at end of file

From c7c9345e3d49d5fc8eb3e9eacc030f5dc847c508 Mon Sep 17 00:00:00 2001
From: Jens Collaert <jenscollaertprive@hotmail.com>
Date: Thu, 10 Jun 2021 17:28:30 +0200
Subject: [PATCH 07/74] added item hotbat selecting on slot

---
 .../java/dev/projectg/geyserhub/module/listeners/ItemOnJoin.java | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/main/java/dev/projectg/geyserhub/module/listeners/ItemOnJoin.java b/src/main/java/dev/projectg/geyserhub/module/listeners/ItemOnJoin.java
index 46ec4c4..d9d4fd3 100644
--- a/src/main/java/dev/projectg/geyserhub/module/listeners/ItemOnJoin.java
+++ b/src/main/java/dev/projectg/geyserhub/module/listeners/ItemOnJoin.java
@@ -14,6 +14,7 @@ public class ItemOnJoin implements Listener {
     @SuppressWarnings("unused")
     @EventHandler
     public void onPlayerJoin(PlayerJoinEvent event) {
+        event.getPlayer().getInventory().setHeldItemSlot(GeyserHubMain.getInstance().getConfig().getInt("Slot"));
         if (GeyserHubMain.getInstance().getConfig().getBoolean("Item-Join")) {
             Player player = event.getPlayer();
             ItemStack compass = SelectorItem.getItem();

From 515af0f4c3121f3a983c5c5e4f90026be54adf06 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Thu, 10 Jun 2021 17:09:22 -0400
Subject: [PATCH 08/74] add backend for multiple forms (breaks config)

---
 .../dev/projectg/geyserhub/GeyserHubMain.java |   3 +-
 .../geyserhub/command/SelectorCommand.java    |   8 +-
 .../module/listeners/ItemInteract.java        |   4 +-
 .../geyserhub/module/menu/BedrockMenu.java    | 289 ------------------
 .../module/menu/bedrock/BedrockForm.java      | 222 ++++++++++++++
 .../module/menu/bedrock/BedrockMenu.java      | 102 +++++++
 .../geyserhub/module/menu/bedrock/Button.java |  27 ++
 .../module/menu/bedrock/CommandButton.java    |  29 ++
 .../module/menu/bedrock/ServerButton.java     |  31 ++
 .../module/menu/{ => java}/JavaMenu.java      |   2 +-
 src/main/resources/config.yml                 |  55 ++--
 11 files changed, 454 insertions(+), 318 deletions(-)
 delete mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/bedrock/Button.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/bedrock/CommandButton.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/bedrock/ServerButton.java
 rename src/main/java/dev/projectg/geyserhub/module/menu/{ => java}/JavaMenu.java (98%)

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index ea690cd..2d517e4 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -2,10 +2,10 @@
 
 import dev.projectg.geyserhub.command.ReloadCommand;
 import dev.projectg.geyserhub.command.SelectorCommand;
-import dev.projectg.geyserhub.module.menu.BedrockMenu;
 import dev.projectg.geyserhub.module.listeners.ItemInteract;
 import dev.projectg.geyserhub.module.listeners.SelectorInventory;
 import dev.projectg.geyserhub.module.listeners.ItemOnJoin;
+import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenu;
 import dev.projectg.geyserhub.module.message.Broadcast;
 import dev.projectg.geyserhub.module.message.MessageJoin;
 import dev.projectg.geyserhub.module.Placeholders;
@@ -59,6 +59,7 @@ public void onEnable() {
 
         new BedrockMenu();
 
+        // todo: squash all our commands into one (maybe "ghub"), add command for the different forms, and add command suggestions/completions
         Objects.requireNonNull(getCommand("ghteleporter")).setExecutor(new SelectorCommand());
         Objects.requireNonNull(getCommand("ghreload")).setExecutor(new ReloadCommand());
 
diff --git a/src/main/java/dev/projectg/geyserhub/command/SelectorCommand.java b/src/main/java/dev/projectg/geyserhub/command/SelectorCommand.java
index 407d250..d6d652f 100644
--- a/src/main/java/dev/projectg/geyserhub/command/SelectorCommand.java
+++ b/src/main/java/dev/projectg/geyserhub/command/SelectorCommand.java
@@ -2,8 +2,8 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
-import dev.projectg.geyserhub.module.menu.BedrockMenu;
-import dev.projectg.geyserhub.module.menu.JavaMenu;
+import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenu;
+import dev.projectg.geyserhub.module.menu.java.JavaMenu;
 import org.bukkit.ChatColor;
 import org.bukkit.command.Command;
 import org.bukkit.command.CommandExecutor;
@@ -22,13 +22,13 @@ public boolean onCommand(@Nonnull CommandSender sender, @Nonnull  Command comman
                 if (BedrockMenu.getInstance().isEnabled()){
                     BedrockMenu.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()));
                 } else {
-                    player.sendMessage("[GeyserHub] " + ChatColor.RED + "Sorry, selector form is not available to Bedrock players!");
+                    player.sendMessage("[GeyserHub] " + ChatColor.RED + "Sorry, the selector form is currently not available to Bedrock players!");
                 }
             } else {
                 if (JavaMenu.isEnabled()) {
                     JavaMenu.openMenu(player, GeyserHubMain.getInstance().getConfig());
                 } else {
-                    player.sendMessage("[GeyserHub] " + ChatColor.RED + "Sorry, selector form is not available to Java players!");
+                    player.sendMessage("[GeyserHub] " + ChatColor.RED + "Sorry, the selector form is currently not available to Java players!");
                 }
             }
         } else if (sender instanceof ConsoleCommandSender) {
diff --git a/src/main/java/dev/projectg/geyserhub/module/listeners/ItemInteract.java b/src/main/java/dev/projectg/geyserhub/module/listeners/ItemInteract.java
index c369358..41091ca 100644
--- a/src/main/java/dev/projectg/geyserhub/module/listeners/ItemInteract.java
+++ b/src/main/java/dev/projectg/geyserhub/module/listeners/ItemInteract.java
@@ -1,8 +1,8 @@
 package dev.projectg.geyserhub.module.listeners;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.module.menu.BedrockMenu;
-import dev.projectg.geyserhub.module.menu.JavaMenu;
+import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenu;
+import dev.projectg.geyserhub.module.menu.java.JavaMenu;
 import dev.projectg.geyserhub.module.menu.SelectorItem;
 import org.bukkit.Material;
 import org.bukkit.entity.Player;
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java
deleted file mode 100644
index 2b996fe..0000000
--- a/src/main/java/dev/projectg/geyserhub/module/menu/BedrockMenu.java
+++ /dev/null
@@ -1,289 +0,0 @@
-package dev.projectg.geyserhub.module.menu;
-
-import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.Reloadable;
-import dev.projectg.geyserhub.ReloadableRegistry;
-import dev.projectg.geyserhub.SelectorLogger;
-import me.clip.placeholderapi.PlaceholderAPI;
-import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
-import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.configuration.file.FileConfiguration;
-import org.bukkit.entity.Player;
-import org.geysermc.cumulus.SimpleForm;
-import org.geysermc.cumulus.component.ButtonComponent;
-import org.geysermc.cumulus.response.SimpleFormResponse;
-import org.geysermc.cumulus.util.FormImage;
-import org.geysermc.floodgate.api.player.FloodgatePlayer;
-
-import javax.annotation.Nonnull;
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class BedrockMenu implements Reloadable {
-
-    private static BedrockMenu instance;
-
-    private boolean isEnabled = false;
-
-    private String title;
-    private String content;
-    private List<ButtonComponent> allButtons;
-
-    /**
-     * List of all the BungeeCord servers for all the buttons.
-     */
-    private List<String> serverNames;
-    /**
-     * List containing a list of commands for every button
-     */
-    private List<List<String>> commands;
-
-    /**
-     * Index at which command buttons start in {@link #allButtons}
-     */
-    int commandsIndex;
-
-    /**
-     * @return Get the latest BedrockMenu instance that was created
-     */
-    public static BedrockMenu getInstance() {
-        return instance;
-    }
-
-    /**
-     * Create a new bedrock selector form and initializes it with the current loaded config
-     */
-    public BedrockMenu() {
-        instance = this;
-        ReloadableRegistry.registerReloadable(this);
-        reload();
-    }
-    @Override
-    public boolean reload() {
-        if (GeyserHubMain.getInstance().getConfig().getBoolean("Bedrock-Selector.Enable", true)) {
-            if (load(GeyserHubMain.getInstance().getConfig())) {
-                isEnabled = true;
-            } else {
-                isEnabled = false;
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public boolean isEnabled() {
-        return isEnabled;
-    }
-
-    /**
-     * Initialize or refresh the server selector form
-     */
-    private boolean load(@Nonnull FileConfiguration config) {
-
-        SelectorLogger logger = SelectorLogger.getLogger();
-
-        String title = config.getString("Bedrock-Selector.Title");
-        String content = config.getString("Bedrock-Selector.Content");
-        if (title == null || content == null) {
-            logger.severe("Value of Bedrock-Selector.Title or Bedrock-Selector.Content has no value in the config! Failed to create the bedrock selector form.");
-            return false;
-        }
-
-        Map<ButtonComponent, String> serverButtonMap = getServerButtons(config);
-        Map<ButtonComponent, List<String>> commandButtonMap = getCommandButtons(logger, config);
-        if (serverButtonMap.isEmpty() && commandButtonMap.isEmpty()) {
-            logger.severe("Failed to create any valid buttons for the form! The form configuration is malformed!");
-            return false;
-        }
-
-        // Only set everything once it has been validated
-        this.title = title;
-        this.content = content;
-        List<ButtonComponent> allButtons = new ArrayList<>();
-        allButtons.addAll(serverButtonMap.keySet());
-        allButtons.addAll(commandButtonMap.keySet());
-        this.allButtons = allButtons;
-        this.serverNames = new ArrayList<>(serverButtonMap.values());
-        this.commands = new ArrayList<>(commandButtonMap.values());
-        this.commandsIndex = serverButtonMap.size();
-
-        return true;
-    }
-
-    /**
-     *  Get the server buttons and each button's server
-     * @param config The configuration to pull the servers from
-     * @return A list of ButtonComponents, which may be empty.
-     */
-    private LinkedHashMap<ButtonComponent, String> getServerButtons(@Nonnull FileConfiguration config) {
-        SelectorLogger logger = SelectorLogger.getLogger();
-
-        // Enter the Bedrock-Selector.Servers section
-        ConfigurationSection serverSection;
-        if (config.contains("Bedrock-Selector.Servers", true)) {
-            serverSection = config.getConfigurationSection("Bedrock-Selector.Servers");
-            assert serverSection != null;
-        } else {
-            logger.debug("Failed to create any server buttons because the configuration is malformed! Regenerate it.");
-            return new LinkedHashMap<>(Collections.emptyMap());
-        }
-        // Get all the defined servers in our config
-        Set<String> allServers = serverSection.getKeys(false);
-        if (allServers.isEmpty()) {
-            logger.debug("Failed to create any server buttons because there are no defined servers in the form configuration!");
-            return new LinkedHashMap<>(Collections.emptyMap());
-        }
-        // Create a map of buttons and their server. For every defined server with a valid button configuration, we add its button.
-        LinkedHashMap<ButtonComponent, String> buttonComponents = new LinkedHashMap<>();
-        for (String serverName : allServers) {
-            ConfigurationSection serverInfo = serverSection.getConfigurationSection(serverName);
-            if (serverInfo == null) {
-                // This will be null if the serverName key isn't actually a configuration section
-                logger.warn("Server entry with name \"" + serverName + "\" was not added to the bedrock selector because it was not formatted correctly!");
-                continue;
-            }
-
-            if (serverInfo.contains("Button-Text", true) && serverInfo.isString("Button-Text")) {
-                String buttonText = serverInfo.getString("Button-Text");
-                assert buttonText != null;
-                if (serverInfo.contains("ImageURL", true)) {
-                    String imageURL = serverInfo.getString("ImageURL");
-                    assert imageURL != null;
-                    buttonComponents.put(ButtonComponent.of(buttonText, FormImage.Type.URL, imageURL), serverName);
-                    logger.debug(serverName + " contains image");
-                } else {
-                    buttonComponents.put(ButtonComponent.of(buttonText), serverName);
-                    logger.debug(serverName + " does not contain image");
-                }
-                logger.debug("added server for \"" + serverName + "\" with button text: " + buttonText);
-            }
-        }
-        if (buttonComponents.isEmpty()) {
-            logger.warn("Failed to create any valid server buttons for the form! The form configuration is malformed!");
-        }
-
-        return buttonComponents;
-    }
-
-    /**
-     * Get the command buttons and their commands
-     * @param logger The logger to send messages to
-     * @param config The configuration to pull the commands from
-     * @return A list of ButtonComponents, which may be empty.
-     */
-    private LinkedHashMap<ButtonComponent, List<String>> getCommandButtons(@Nonnull SelectorLogger logger, @Nonnull FileConfiguration config) {
-
-        // Enter the Bedrock-Selector.Commands section
-        ConfigurationSection commandSection;
-        if (config.contains("Bedrock-Selector.Commands", true)) {
-            commandSection = config.getConfigurationSection("Bedrock-Selector.Commands");
-            assert commandSection != null;
-        } else {
-            logger.debug("Failed to create any command buttons because the configuration is malformed! Regenerate it.");
-            return new LinkedHashMap<>(Collections.emptyMap());
-        }
-        // Get all the defined commands in our config
-        Set<String> allCommands = commandSection.getKeys(false);
-        if (allCommands.isEmpty()) {
-            logger.debug("Failed to create any command buttons because there are no defined commands in the form configuration!");
-            return new LinkedHashMap<>(Collections.emptyMap());
-        }
-        // Create a map of buttons and their commands. For every defined command with a valid configuration, we add its button.
-        LinkedHashMap<ButtonComponent, List<String>> buttonComponents = new LinkedHashMap<>();
-        for (String commandEntry : allCommands) {
-            ConfigurationSection commandInfo = commandSection.getConfigurationSection(commandEntry);
-            if (commandInfo == null) {
-                // This will be null if the serverName key isn't actually a configuration section
-                logger.warn("Command entry with name \"" + commandEntry + "\" was not added to the bedrock selector because it was not formatted correctly!");
-                continue;
-            }
-
-            if (commandInfo.contains("Button-Text", true) && commandInfo.isString("Button-Text") && commandInfo.contains("Commands", true) && commandInfo.isList("Commands")) {
-                String buttonText = commandInfo.getString("Button-Text");
-                assert buttonText != null;
-                if (commandInfo.contains("ImageURL", true)) {
-                    String imageURL = commandInfo.getString("ImageURL");
-                    assert imageURL != null;
-                    buttonComponents.put(ButtonComponent.of(buttonText, FormImage.Type.URL, imageURL), commandInfo.getStringList("Commands"));
-                    logger.debug("Command " + commandEntry + " contains an image");
-                } else {
-                    buttonComponents.put(ButtonComponent.of(buttonText), commandInfo.getStringList("Commands"));
-                    logger.debug("Command " + commandEntry + " does not contain an image");
-                }
-                logger.debug("added command for \"" + commandEntry + "\" with button text: " + buttonText);
-            }
-        }
-        // Warn if there were defined commands but they were all malformed
-        if (buttonComponents.isEmpty()) {
-            logger.warn("Failed to create any valid commands buttons for the form! The form configuration is malformed!");
-        }
-
-        return buttonComponents;
-    }
-
-    /**
-     * Send the server selector
-     * @param floodgatePlayer the floodgate player to send it to
-     */
-    public void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
-        SelectorLogger logger = SelectorLogger.getLogger();
-
-        Player player = Bukkit.getServer().getPlayer(floodgatePlayer.getCorrectUniqueId());
-        if (player == null) {
-            logger.severe("Unable to find a Bukkit Player for the given Floodgate Player: " + floodgatePlayer.getCorrectUniqueId().toString());
-            return;
-        }
-
-        // Resolve any placeholders in the button text
-        List<ButtonComponent> formattedButtons = new ArrayList<>();
-        for (ButtonComponent component : allButtons) {
-            formattedButtons.add(ButtonComponent.of(PlaceholderAPI.setPlaceholders(player, component.getText()), component.getImage()));
-        }
-        // Create the form
-        SimpleForm serverSelector = SimpleForm.of(title, content, formattedButtons);
-
-        // Set the response handler
-        serverSelector.setResponseHandler((responseData) -> {
-            SimpleFormResponse response = serverSelector.parseResponse(responseData);
-            if (!response.isCorrect()) {
-                // isCorrect() = !isClosed() && !isInvalid()
-                // player closed the form or returned invalid info (see FormResponse)
-                return;
-            }
-
-            int buttonID = response.getClickedButtonId();
-            if (buttonID < commandsIndex) {
-                // This should never be out of bounds considering its size is the number of valid buttons
-                String serverName = serverNames.get(buttonID);
-                ByteArrayOutputStream b = new ByteArrayOutputStream();
-                DataOutputStream out = new DataOutputStream(b);
-                try {
-                    out.writeUTF("Connect");
-                    out.writeUTF(serverName);
-                    player.sendPluginMessage(GeyserHubMain.getInstance(), "BungeeCord", b.toByteArray());
-                    player.sendMessage(ChatColor.DARK_AQUA + "Trying to send you to: " + ChatColor.GREEN + serverName);
-                } catch (IOException e) {
-                    logger.severe("Failed to send a plugin message to Bungeecord!");
-                    e.printStackTrace();
-                }
-            } else {
-                // Get the commands from the list of commands and replace any playerName placeholders
-                for (String command : commands.get(buttonID - commandsIndex)) {
-                    String functionalCommand = command.replace("{playerName}", player.getName()).replace("{playerUUID}", player.getUniqueId().toString());
-                    Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), PlaceholderAPI.setPlaceholders(player, functionalCommand));
-                }
-            }
-        });
-
-        // Send the form to the floodgate player
-        floodgatePlayer.sendForm(serverSelector);
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
new file mode 100644
index 0000000..e8b02da
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -0,0 +1,222 @@
+package dev.projectg.geyserhub.module.menu.bedrock;
+
+import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.SelectorLogger;
+import me.clip.placeholderapi.PlaceholderAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.entity.Player;
+import org.geysermc.cumulus.SimpleForm;
+import org.geysermc.cumulus.response.SimpleFormResponse;
+import org.geysermc.cumulus.util.FormImage;
+import org.geysermc.floodgate.api.player.FloodgatePlayer;
+
+import javax.annotation.Nonnull;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class BedrockForm {
+
+    private boolean isEnabled;
+
+    private String title;
+    private String content;
+    private List<Button> allButtons;
+
+    /**
+     * Create a new bedrock selector form and initializes it with the current loaded config
+     */
+    protected BedrockForm(@Nonnull ConfigurationSection section) {
+        isEnabled = load(section);
+    }
+
+    protected boolean isEnabled() {
+        return isEnabled;
+    }
+
+    /**
+     * Initialize or refresh the server selector form
+     */
+    private boolean load(@Nonnull ConfigurationSection configSection) {
+
+        SelectorLogger logger = SelectorLogger.getLogger();
+
+        String title = configSection.getString("Title");
+        String content = configSection.getString("Content");
+        if (title == null || content == null) {
+            logger.severe("Value of Bedrock-Selector.Title or Bedrock-Selector.Content has no value in the config for form: "  + configSection.getName() + "! Failed to create the bedrock selector form.");
+            return false;
+        }
+
+        // Get our Buttons
+        if (!(configSection.contains("Buttons", true) && configSection.isConfigurationSection("Buttons"))) {
+            logger.warn("Form: " + configSection.getName() + " does not contain a Buttons section, unable to create form");
+            return false;
+        }
+        ConfigurationSection buttonSection = configSection.getConfigurationSection("Buttons");
+        assert buttonSection != null;
+        List<Button> buttons = getButtons(buttonSection);
+        if (buttons.isEmpty()) {
+            logger.warn("Failed to create any valid buttons of form: " + configSection.getName() + "! All listed buttons have a malformed section!");
+            return false;
+        } else {
+            logger.debug("Finished adding buttons to form: " + configSection.getName());
+        }
+
+        // Only set everything once it has been validated
+        this.title = title;
+        this.content = content;
+        this.allButtons = buttons;
+
+        return true;
+    }
+
+    /**
+     *  Get the server buttons and each button's server
+     * @param configSection The configuration section to pull the data from
+     * @return A list of Buttons, which may be empty.
+     */
+    private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
+        SelectorLogger logger = SelectorLogger.getLogger();
+
+        // Get the form name
+        String formName;
+        ConfigurationSection parent = configSection.getParent();
+        if (parent == null) {
+            formName = "null";
+        } else {
+            formName = parent.getName();
+        }
+        logger.debug("Getting buttons for form: " + formName);
+
+        // Get all the defined buttons in the buttons section
+        Set<String> allButtonIds = configSection.getKeys(false);
+        if (allButtonIds.isEmpty()) {
+            logger.warn("No buttons were listed for form: " + formName);
+            return Collections.emptyList();
+        }
+
+        // Create a list of buttons. For every defined button with a valid server or command configuration, we add its button.
+        List<Button> compiledButtons = new ArrayList<>();
+        for (String buttonId : allButtonIds) {
+            ConfigurationSection buttonInfo = configSection.getConfigurationSection(buttonId);
+            if (buttonInfo == null) {
+                // This will be null if the buttonId key isn't actually a configuration section
+                logger.warn(buttonId + " was not added because it is not a configuration section!");
+                continue;
+            }
+
+            if (buttonInfo.contains("Button-Text", true) && buttonInfo.isString("Button-Text")) {
+                String buttonText = buttonInfo.getString("Button-Text");
+                assert buttonText != null;
+                logger.debug(buttonId + " has Button-Text: " + buttonText);
+
+                // Add image if specified
+                FormImage image = null;
+                if (buttonInfo.contains("ImageURL", true)) {
+                    String imageURL = buttonInfo.getString("ImageURL");
+                    assert imageURL != null;
+                    image = FormImage.of(FormImage.Type.URL, imageURL);
+                    logger.debug(buttonId + " contains image with URL: " + image);
+                } else {
+                    logger.debug(buttonId + " does not contain image URL");
+                }
+
+                if (buttonInfo.contains("Server", true) && buttonInfo.contains("Commands")) {
+                    logger.warn(buttonId + " contains both a Server and Commands key. Only one is allowed. Ignoring commands.");
+                }
+
+                // Get the server or command data and compile the button
+                if (buttonInfo.contains("Server") && buttonInfo.isString("Server")) {
+                    String serverName = buttonInfo.getString("Server");
+                    assert serverName != null;
+                    logger.debug(buttonId + " contains BungeeCord server: " + serverName);
+                    compiledButtons.add(new ServerButton(serverName, buttonText, image));
+                } else if (buttonInfo.contains("Commands") && buttonInfo.isList("Commands")) {
+                    List<String> commands = buttonInfo.getStringList("Commands");
+                    logger.debug(buttonId + " contains commands: " + commands);
+                    compiledButtons.add(new CommandButton(commands, buttonText, image));
+                } else {
+                    logger.warn(buttonId + " did not define a BungeeCord server or commands list, not adding.");
+                    continue;
+                }
+                logger.debug(buttonId + " was successfully added.");
+            } else {
+                logger.warn(buttonId + " does not contain a valid Button-Text value, not adding.");
+            }
+        }
+
+        return compiledButtons;
+    }
+
+    /**
+     * Send the server selector
+     * @param floodgatePlayer the floodgate player to send it to
+     */
+    protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
+        SelectorLogger logger = SelectorLogger.getLogger();
+
+        Player player = Bukkit.getServer().getPlayer(floodgatePlayer.getCorrectUniqueId());
+        if (player == null) {
+            logger.severe("Unable to find a Bukkit Player for the given Floodgate Player: " + floodgatePlayer.getCorrectUniqueId().toString());
+            return;
+        }
+
+        // Resolve any placeholders in the button text
+        List<Button> formattedButtons = new ArrayList<>();
+        for (Button button : allButtons) {
+            if (button instanceof ServerButton) {
+                ServerButton serverButton = (ServerButton) button;
+                formattedButtons.add(new ServerButton(serverButton.getServerName(), PlaceholderAPI.setPlaceholders(player, serverButton.getButtonComponent().getText()), serverButton.getButtonComponent().getImage()));
+            } else if (button instanceof CommandButton) {
+                CommandButton commandButton = (CommandButton) button;
+                formattedButtons.add(new CommandButton(commandButton.getCommands(), PlaceholderAPI.setPlaceholders(player, commandButton.getButtonComponent().getText()), commandButton.getButtonComponent().getImage()));
+            } else {
+                throw new AssertionError("Failed to account for all possible types of " + button.toString());
+            }
+        }
+        // Create the form
+        SimpleForm serverSelector = SimpleForm.of(title, content, formattedButtons.stream().map(Button::getButtonComponent).collect(Collectors.toList()));
+
+        // Set the response handler
+        serverSelector.setResponseHandler((responseData) -> {
+            SimpleFormResponse response = serverSelector.parseResponse(responseData);
+            if (!response.isCorrect()) {
+                // isCorrect() = !isClosed() && !isInvalid()
+                // player closed the form or returned invalid info (see FormResponse)
+                return;
+            }
+
+            Button button = formattedButtons.get(response.getClickedButtonId());
+            if (button instanceof ServerButton) {
+                // This should never be out of bounds considering its size is the number of valid buttons
+                String serverName = ((ServerButton) button).getServerName();
+                ByteArrayOutputStream b = new ByteArrayOutputStream();
+                DataOutputStream out = new DataOutputStream(b);
+                try {
+                    out.writeUTF("Connect");
+                    out.writeUTF(serverName);
+                    player.sendPluginMessage(GeyserHubMain.getInstance(), "BungeeCord", b.toByteArray());
+                    player.sendMessage(ChatColor.DARK_AQUA + "Trying to send you to: " + ChatColor.GREEN + serverName);
+                } catch (IOException e) {
+                    logger.severe("Failed to send a plugin message to Bungeecord!");
+                    e.printStackTrace();
+                }
+            } else if (button instanceof CommandButton){
+                // Get the commands from the list of commands and replace any playerName placeholders
+                for (String command : ((CommandButton) button).getCommands()) {
+                    Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), PlaceholderAPI.setPlaceholders(player, command));
+                }
+            } else {
+                throw new AssertionError("Failed to account for all possible types of " + button.toString());
+            }
+        });
+
+        // Send the form to the floodgate player
+        floodgatePlayer.sendForm(serverSelector);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java
new file mode 100644
index 0000000..7e82394
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java
@@ -0,0 +1,102 @@
+package dev.projectg.geyserhub.module.menu.bedrock;
+
+import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.Reloadable;
+import dev.projectg.geyserhub.ReloadableRegistry;
+import dev.projectg.geyserhub.SelectorLogger;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.geysermc.floodgate.api.player.FloodgatePlayer;
+
+import javax.annotation.Nonnull;
+import java.util.HashMap;
+import java.util.Map;
+
+public class BedrockMenu implements Reloadable {
+
+    private static BedrockMenu INSTANCE;
+
+    private boolean isEnabled;
+    private final Map<String, BedrockForm> enabledForms = new HashMap<>();
+
+    public static BedrockMenu getInstance() {
+        return INSTANCE;
+    }
+
+    public BedrockMenu() {
+        ReloadableRegistry.registerReloadable(this);
+        isEnabled = load();
+        INSTANCE = this;
+    }
+
+    private boolean load() {
+        FileConfiguration config = GeyserHubMain.getInstance().getConfig();
+        SelectorLogger logger = SelectorLogger.getLogger();
+
+        enabledForms.clear();
+
+        if (config.contains("Bedrock-Selector", true) && config.isConfigurationSection("Bedrock-Selector")) {
+            ConfigurationSection selectorSection = config.getConfigurationSection("Bedrock-Selector");
+            assert selectorSection != null;
+
+            if (selectorSection.contains("Enable", true) && selectorSection.isBoolean("Enable")) {
+                if (selectorSection.getBoolean("Enable")) {
+                    if (selectorSection.contains("Forms", true) && selectorSection.isConfigurationSection("Forms")) {
+                        ConfigurationSection forms = selectorSection.getConfigurationSection("forms");
+                        assert forms != null;
+
+                        boolean noSuccess = true;
+                        for (String entry : forms.getKeys(false)) {
+                            if (!forms.isConfigurationSection(entry)) {
+                                logger.warn("Bedrock form with name " + entry + " is being skipped because it is not a configuration section");
+                                continue;
+                            }
+                            ConfigurationSection formInfo = forms.getConfigurationSection(entry);
+                            assert formInfo != null;
+                            BedrockForm form = new BedrockForm(formInfo);
+                            if (form.isEnabled()) {
+                                enabledForms.put(entry, form);
+                                noSuccess = false;
+                            }
+                        }
+
+                        if (noSuccess) {
+                            isEnabled = false;
+                            logger.warn("Failed to ALL bedrock forms, due to configuration error.");
+                        } else {
+                            isEnabled = true;
+                            logger.info("Valid Bedrock forms are: " + enabledForms.keySet());
+                            return true;
+                        }
+                    }
+                } else {
+                    logger.debug("Not enabling bedrock forms because it is disabled in the config.");
+                    isEnabled = false;
+                }
+            } else {
+                logger.warn("Not enabling bedrock forms because the Enable value is not present in the config.");
+            }
+        } else {
+            logger.warn("Not enabling bedrock forms because the whole configuration section is not present.");
+        }
+        return false;
+    }
+
+    public void sendForm(@Nonnull FloodgatePlayer player) {
+        enabledForms.get("parent").sendForm(player);
+    }
+
+    public void sendForm(@Nonnull FloodgatePlayer player, @Nonnull String form) {
+        enabledForms.get(form).sendForm(player);
+    }
+
+    public boolean isEnabled() {
+        return isEnabled;
+    }
+
+    @Override
+    public boolean reload() {
+        isEnabled = load();
+        return isEnabled;
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/Button.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/Button.java
new file mode 100644
index 0000000..f4dd779
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/Button.java
@@ -0,0 +1,27 @@
+package dev.projectg.geyserhub.module.menu.bedrock;
+
+import org.geysermc.cumulus.component.ButtonComponent;
+import org.geysermc.cumulus.util.FormImage;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class Button {
+
+    private final ButtonComponent buttonComponent;
+
+    // This class should only be constructed if its being inherited, otherwise it is pointless (just construct a ButtonComponent)
+    protected Button(@Nonnull String text) {
+        this.buttonComponent = ButtonComponent.of(text);
+    }
+    protected Button(@Nonnull String text, @Nullable FormImage image) {
+        this.buttonComponent = ButtonComponent.of(text, image);
+    }
+    protected Button(@Nonnull String text, @Nonnull FormImage.Type type, @Nonnull String data) {
+        this.buttonComponent = ButtonComponent.of(text, type, data);
+    }
+
+    public ButtonComponent getButtonComponent() {
+        return buttonComponent;
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/CommandButton.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/CommandButton.java
new file mode 100644
index 0000000..7a64e62
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/CommandButton.java
@@ -0,0 +1,29 @@
+package dev.projectg.geyserhub.module.menu.bedrock;
+
+import org.geysermc.cumulus.util.FormImage;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.List;
+
+public class CommandButton extends Button{
+
+    private final List<String> commands;
+
+    CommandButton(@Nonnull List<String> commands, @Nonnull String text) {
+        super(text);
+        this.commands = commands;
+    }
+    CommandButton(@Nonnull List<String> commands, @Nonnull String text, @Nullable FormImage image) {
+        super(text, image);
+        this.commands = commands;
+    }
+    CommandButton(@Nonnull List<String> commands, @Nonnull String text, @Nonnull FormImage.Type type, @Nonnull String data) {
+        super(text, type, data);
+        this.commands = commands;
+    }
+
+    public List<String> getCommands() {
+        return commands;
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/ServerButton.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/ServerButton.java
new file mode 100644
index 0000000..d28ef48
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/ServerButton.java
@@ -0,0 +1,31 @@
+package dev.projectg.geyserhub.module.menu.bedrock;
+
+import org.geysermc.cumulus.util.FormImage;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class ServerButton extends Button{
+
+    private final String serverName;
+
+    ServerButton(@Nonnull String serverName, @Nonnull String text) {
+        super(text);
+        this.serverName = serverName;
+    }
+
+    ServerButton(@Nonnull String serverName, @Nonnull String text, @Nullable FormImage image) {
+        super(text, image);
+        this.serverName = serverName;
+    }
+
+    ServerButton(@Nonnull String serverName, @Nonnull String text, @Nonnull FormImage.Type type, @Nonnull String data) {
+        super(text, type, data);
+        this.serverName = serverName;
+    }
+
+
+    public String getServerName() {
+        return serverName;
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
similarity index 98%
rename from src/main/java/dev/projectg/geyserhub/module/menu/JavaMenu.java
rename to src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index 0b4e593..54e1a01 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -1,4 +1,4 @@
-package dev.projectg.geyserhub.module.menu;
+package dev.projectg.geyserhub.module.menu.java;
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.module.Placeholders;
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index db9283a..928ebf8 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -43,27 +43,40 @@ Java-Selector:
 Bedrock-Selector:
   Enable: true
 
-  Title: "Server Selector"
-  Content: "Click on the server button of choice."
-
-  Servers:
-    lobby:
-      Button-Text: "Server Lobby: %bungee_lobby% players"
-      ImageURL: "https://www.digminecraft.com/block_recipes/images/cyan_concrete.png"
-    survival:
-      Button-Text: "Survival: %bungee_survival% players"
-      ImageURL: "https://www.digminecraft.com/block_recipes/images/blue_concrete.png"
-  Commands:
-    extraOne:
-      Button-Text: "Spawn"
-      ImageURL: "https://www.digminecraft.com/decoration_recipes/images/lodestone.png"
-      Commands:
-        - "execute as %player_name% run spawn"
-    extraTwo:
-      Button-Text: "The Void"
-      Commands:
-        - "tp %player_uuid% 0 -10 0"
-        - "say %player_name% made a fatal decision."
+  Forms:
+    parent:
+      Title: "Server Selector"
+      Content: "Click on the server button of choice."
+      Buttons:
+        lobby:
+          Button-Text: "Server Lobby: %bungee_lobby% players"
+          ImageURL: "https://www.digminecraft.com/block_recipes/images/cyan_concrete.png"
+          Server: "lobby"
+        survival:
+          Button-Text: "Survival: %bungee_survival% players"
+          ImageURL: "https://www.digminecraft.com/block_recipes/images/blue_concrete.png"
+          Server: "survival"
+        commandOne:
+          Button-Text: "Minigames"
+          Image-URL: "https://www.digminecraft.com/weapon_recipes/images/diamond_sword.png"
+          Commands:
+            - "execute as %player_name% run ghform minigames"
+        commandTwo:
+          Button-Text: "Spawn"
+          ImageURL: "https://www.digminecraft.com/decoration_recipes/images/lodestone.png"
+          Commands:
+            - "execute as %player_name% run spawn"
+    minigames:
+      Title: "Minigames"
+      Content: "Click on a minigame of choice."
+      Buttons:
+        spleef:
+          Button-Text: "Spleef"
+          ImageURL: "https://www.digminecraft.com/materials/images/snowball.png"
+          Server: "spleef"
+        hideseek:
+          Button-Text: "Hide & Seek"
+          Server: "hideseek"
 
 # Join and leave messages. Supports papi placeholders.
 Join-Message:

From 3ab5eea0bf0702a561a8390b960f928efee9fa5a Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Sat, 12 Jun 2021 01:40:56 -0400
Subject: [PATCH 09/74] refactor command, add frontend support for different
 bedrock forms

---
 .../dev/projectg/geyserhub/GeyserHubMain.java |   8 +-
 .../geyserhub/ReloadableRegistry.java         |  30 -----
 .../projectg/geyserhub/SelectorLogger.java    |   3 +
 .../geyserhub/command/GeyserHubCommand.java   | 111 ++++++++++++++++++
 .../geyserhub/command/ReloadCommand.java      |  37 ------
 .../geyserhub/command/SelectorCommand.java    |  39 ------
 .../module/listeners/ItemInteract.java        |   2 +-
 .../module/menu/bedrock/BedrockForm.java      |  11 +-
 .../module/menu/bedrock/BedrockMenu.java      |  42 ++++---
 .../geyserhub/module/menu/java/JavaMenu.java  |   1 +
 .../module/teleporter/JoinTeleporter.java     |   4 +-
 .../{ => reloadable}/Reloadable.java          |   4 +-
 .../reloadable/ReloadableRegistry.java        |  54 +++++++++
 src/main/resources/config.yml                 |   4 +-
 src/main/resources/plugin.yml                 |  19 ++-
 15 files changed, 225 insertions(+), 144 deletions(-)
 delete mode 100644 src/main/java/dev/projectg/geyserhub/ReloadableRegistry.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
 delete mode 100644 src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java
 delete mode 100644 src/main/java/dev/projectg/geyserhub/command/SelectorCommand.java
 rename src/main/java/dev/projectg/geyserhub/{ => reloadable}/Reloadable.java (70%)
 create mode 100644 src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 2d517e4..21242ba 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -1,7 +1,6 @@
 package dev.projectg.geyserhub;
 
-import dev.projectg.geyserhub.command.ReloadCommand;
-import dev.projectg.geyserhub.command.SelectorCommand;
+import dev.projectg.geyserhub.command.GeyserHubCommand;
 import dev.projectg.geyserhub.module.listeners.ItemInteract;
 import dev.projectg.geyserhub.module.listeners.SelectorInventory;
 import dev.projectg.geyserhub.module.listeners.ItemOnJoin;
@@ -59,9 +58,8 @@ public void onEnable() {
 
         new BedrockMenu();
 
-        // todo: squash all our commands into one (maybe "ghub"), add command for the different forms, and add command suggestions/completions
-        Objects.requireNonNull(getCommand("ghteleporter")).setExecutor(new SelectorCommand());
-        Objects.requireNonNull(getCommand("ghreload")).setExecutor(new ReloadCommand());
+        // todo: and add command suggestions/completions, help pages that only shows available commands
+        Objects.requireNonNull(getCommand("ghub")).setExecutor(new GeyserHubCommand());
 
         Bukkit.getServer().getPluginManager().registerEvents(new ItemInteract(), this);
         Bukkit.getServer().getPluginManager().registerEvents(new SelectorInventory(), this);
diff --git a/src/main/java/dev/projectg/geyserhub/ReloadableRegistry.java b/src/main/java/dev/projectg/geyserhub/ReloadableRegistry.java
deleted file mode 100644
index fb8b55a..0000000
--- a/src/main/java/dev/projectg/geyserhub/ReloadableRegistry.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package dev.projectg.geyserhub;
-
-import dev.projectg.geyserhub.Reloadable;
-
-import javax.annotation.Nonnull;
-import java.util.HashSet;
-import java.util.Set;
-
-public class ReloadableRegistry {
-
-    /**
-     * A set of instances that implement the Reloadable interface
-     */
-    private static final Set<Reloadable> reloadables = new HashSet<>();
-
-    /**
-     * Register a reloadable
-     * @param reloadable the reloadable
-     */
-    public static void registerReloadable(@Nonnull Reloadable reloadable) {
-        reloadables.add(reloadable);
-    }
-
-    /**
-     * @return A copy of all registered reloadables
-     */
-    public static Reloadable[] getRegisteredReloadables() {
-        return reloadables.toArray(new Reloadable[0]);
-    }
-}
diff --git a/src/main/java/dev/projectg/geyserhub/SelectorLogger.java b/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
index 98366c6..80f18a6 100644
--- a/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
+++ b/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
@@ -1,5 +1,8 @@
 package dev.projectg.geyserhub;
 
+import dev.projectg.geyserhub.reloadable.Reloadable;
+import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
+
 public class SelectorLogger implements Reloadable {
 
     private static final SelectorLogger LOGGER = new SelectorLogger(GeyserHubMain.getInstance());
diff --git a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
new file mode 100644
index 0000000..ef133b6
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
@@ -0,0 +1,111 @@
+package dev.projectg.geyserhub.command;
+
+import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
+import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenu;
+import dev.projectg.geyserhub.module.menu.java.JavaMenu;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+import org.geysermc.floodgate.api.FloodgateApi;
+import org.jetbrains.annotations.NotNull;
+
+import javax.annotation.Nonnull;
+import java.util.UUID;
+
+public class GeyserHubCommand implements CommandExecutor {
+
+    private static final String[] HELP = {
+            "/ghub - Opens the default form if one exists. If not, shows the help page",
+            "/ghub - Opens the help page",
+            "/ghub <form> - Sends the player a selector with the defined name",
+            "/ghub reload - reloads the selector"
+    };
+
+    private static final String NO_PERMISSION = "[GeyserHub] Sorry, you don't have permission to run that command!";
+    private static final String UNKNOWN = "[GeyserHub] Sorry, that's an unknown command!";
+
+    @Override
+    public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] args) {
+        if (!(commandSender instanceof Player || commandSender instanceof ConsoleCommandSender)) {
+            return false;
+        }
+
+        if (args.length == 0) {
+            // send the default form, help if console
+            sendForm(commandSender, BedrockMenu.DEFAULT);
+            return true;
+        }
+
+        // At least one arg
+        switch (args[0]) {
+            case "reload":
+                if (commandSender.hasPermission("geyserhub.reload")) {
+                    if (ReloadableRegistry.reloadAll()) {
+                        commandSender.sendMessage("[GeyserHub] Successfully reloaded.");
+                    } else {
+                        commandSender.sendMessage("[GeyserHub] There was an error reloading something! Please check the server console for further information.");
+                    }
+                } else {
+                    commandSender.sendMessage(NO_PERMISSION);
+                }
+                break;
+            case "help":
+                sendHelp(commandSender);
+                break;
+            case "form":
+                if (commandSender.hasPermission("geyserhub.form")) {
+                    if (args.length == 1) {
+                        commandSender.sendMessage("[GeyserHub] Please specify a form to open! Specify a form with \"/ghub form <form>\"");
+                    } else if (args.length > 2) {
+                        commandSender.sendMessage("[GeyserHub] This command only takes one argument!");
+                    } else {
+                        sendForm(commandSender, args[1]);
+                    }
+                } else {
+                    commandSender.sendMessage(NO_PERMISSION);
+                }
+                break;
+            default:
+                commandSender.sendMessage(UNKNOWN);
+                break;
+        }
+        return true;
+    }
+
+    private void sendHelp(CommandSender commandSender) {
+        // todo: only show players with the given permissions certain entries? not sure if it can be integrated any way into spigot command completions
+        // todo: check if these are sent on consecutive lines or the same one :(
+        commandSender.sendMessage(HELP);
+    }
+
+    /**
+     * send a form to a command sender. if the commandsender is a console then it will just send the help page.
+     * @param commandSender the command sender.
+     * @param formName the form name to send
+     */
+    private void sendForm(@Nonnull CommandSender commandSender, @Nonnull String formName) {
+
+        if (commandSender instanceof Player) {
+            Player player = (Player) commandSender;
+            UUID uuid = player.getUniqueId();
+            if (FloodgateApi.getInstance().isFloodgateId(uuid)) {
+                if (BedrockMenu.getInstance().isEnabled()) {
+                    if (BedrockMenu.getInstance().getFormNames().contains(formName)) {
+                        BedrockMenu.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(uuid), formName);
+                    } else {
+                        player.sendMessage("Sorry, that form doesn't exist! Specify a form with \"/ghub form <form>\"");
+                    }
+                } else {
+                    player.sendMessage("Sorry, Bedrock forms are disabled!");
+                }
+            } else {
+                JavaMenu.openMenu(player, GeyserHubMain.getInstance().getConfig());
+            }
+        } else if (commandSender instanceof ConsoleCommandSender) {
+            sendHelp(commandSender);
+        }
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java b/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java
deleted file mode 100644
index 4926dcc..0000000
--- a/src/main/java/dev/projectg/geyserhub/command/ReloadCommand.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package dev.projectg.geyserhub.command;
-
-import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.Reloadable;
-import dev.projectg.geyserhub.ReloadableRegistry;
-import org.bukkit.ChatColor;
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.command.ConsoleCommandSender;
-import org.bukkit.entity.Player;
-
-import javax.annotation.Nonnull;
-
-public class ReloadCommand implements CommandExecutor {
-    public boolean onCommand(@Nonnull CommandSender sender, @Nonnull  Command command, @Nonnull String label, String[] args) {
-
-        if (sender instanceof Player || sender instanceof ConsoleCommandSender) {
-
-            if (GeyserHubMain.getInstance().loadConfiguration()) {
-                sender.sendMessage("[GeyserHub] Reloaded the configuration, reloading modules...");
-            } else {
-                sender.sendMessage("[GeyserHub] " + ChatColor.RED + "Failed to reload the configuration!");
-                return true;
-            }
-
-            for (Reloadable reloadable : ReloadableRegistry.getRegisteredReloadables()) {
-                if (!reloadable.reload()) {
-                    sender.sendMessage("[GeyserHub] " + ChatColor.RED + "Failed to reload class: " + ChatColor.RESET + reloadable.getClass().toString());
-                }
-            }
-
-            sender.sendMessage("[GeyserHub] Finished reload.");
-        }
-        return true;
-    }
-}
diff --git a/src/main/java/dev/projectg/geyserhub/command/SelectorCommand.java b/src/main/java/dev/projectg/geyserhub/command/SelectorCommand.java
deleted file mode 100644
index d6d652f..0000000
--- a/src/main/java/dev/projectg/geyserhub/command/SelectorCommand.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package dev.projectg.geyserhub.command;
-
-import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.SelectorLogger;
-import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenu;
-import dev.projectg.geyserhub.module.menu.java.JavaMenu;
-import org.bukkit.ChatColor;
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.command.ConsoleCommandSender;
-import org.bukkit.entity.Player;
-import org.geysermc.floodgate.api.FloodgateApi;
-
-import javax.annotation.Nonnull;
-
-public class SelectorCommand implements CommandExecutor {
-    public boolean onCommand(@Nonnull CommandSender sender, @Nonnull  Command command, @Nonnull String label, String[] args) {
-        if (sender instanceof Player) {
-            Player player = (Player) sender;
-            if (FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {
-                if (BedrockMenu.getInstance().isEnabled()){
-                    BedrockMenu.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()));
-                } else {
-                    player.sendMessage("[GeyserHub] " + ChatColor.RED + "Sorry, the selector form is currently not available to Bedrock players!");
-                }
-            } else {
-                if (JavaMenu.isEnabled()) {
-                    JavaMenu.openMenu(player, GeyserHubMain.getInstance().getConfig());
-                } else {
-                    player.sendMessage("[GeyserHub] " + ChatColor.RED + "Sorry, the selector form is currently not available to Java players!");
-                }
-            }
-        } else if (sender instanceof ConsoleCommandSender) {
-            SelectorLogger.getLogger().warn("This command only works in-game!");
-        }
-        return true;
-    }
-}
diff --git a/src/main/java/dev/projectg/geyserhub/module/listeners/ItemInteract.java b/src/main/java/dev/projectg/geyserhub/module/listeners/ItemInteract.java
index 41091ca..b843ec6 100644
--- a/src/main/java/dev/projectg/geyserhub/module/listeners/ItemInteract.java
+++ b/src/main/java/dev/projectg/geyserhub/module/listeners/ItemInteract.java
@@ -35,7 +35,7 @@ public void onInteract(PlayerInteractEvent event) {
         if (player.getInventory().getItemInMainHand().isSimilar(SelectorItem.getItem())) {
             if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
                 if (FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {
-                    BedrockMenu.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()));
+                    BedrockMenu.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()), BedrockMenu.DEFAULT);
                 } else {
                     JavaMenu.openMenu(player, GeyserHubMain.getInstance().getConfig());
                 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index e8b02da..ef4e687 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -21,7 +21,7 @@
 
 public class BedrockForm {
 
-    private boolean isEnabled;
+    private final boolean isEnabled;
 
     private String title;
     private String content;
@@ -166,6 +166,11 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
             return;
         }
 
+        if (!isEnabled) {
+            // todo: Do something less dirty to prevent this
+            throw new AssertionError("Form: " + title + " that failed to load was called to be sent to player " + player);
+        }
+
         // Resolve any placeholders in the button text
         List<Button> formattedButtons = new ArrayList<>();
         for (Button button : allButtons) {
@@ -195,9 +200,7 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
             if (button instanceof ServerButton) {
                 // This should never be out of bounds considering its size is the number of valid buttons
                 String serverName = ((ServerButton) button).getServerName();
-                ByteArrayOutputStream b = new ByteArrayOutputStream();
-                DataOutputStream out = new DataOutputStream(b);
-                try {
+                try (ByteArrayOutputStream b = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(b)) {
                     out.writeUTF("Connect");
                     out.writeUTF(serverName);
                     player.sendPluginMessage(GeyserHubMain.getInstance(), "BungeeCord", b.toByteArray());
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java
index 7e82394..43f6a06 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java
@@ -1,22 +1,28 @@
 package dev.projectg.geyserhub.module.menu.bedrock;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.Reloadable;
-import dev.projectg.geyserhub.ReloadableRegistry;
+import dev.projectg.geyserhub.reloadable.Reloadable;
+import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
 import dev.projectg.geyserhub.SelectorLogger;
 import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.geysermc.floodgate.api.player.FloodgatePlayer;
 
 import javax.annotation.Nonnull;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 public class BedrockMenu implements Reloadable {
 
     private static BedrockMenu INSTANCE;
+    public static final String DEFAULT = "default";
 
-    private boolean isEnabled;
+    /**
+     * If the bedrock form is enabled in the config. the {@link #getFormNames()} may still return an empty list.
+     */
+    private boolean isEnabled = false;
     private final Map<String, BedrockForm> enabledForms = new HashMap<>();
 
     public static BedrockMenu getInstance() {
@@ -25,11 +31,11 @@ public static BedrockMenu getInstance() {
 
     public BedrockMenu() {
         ReloadableRegistry.registerReloadable(this);
-        isEnabled = load();
+        load();
         INSTANCE = this;
     }
 
-    private boolean load() {
+    private void load() {
         FileConfiguration config = GeyserHubMain.getInstance().getConfig();
         SelectorLogger logger = SelectorLogger.getLogger();
 
@@ -41,11 +47,13 @@ private boolean load() {
 
             if (selectorSection.contains("Enable", true) && selectorSection.isBoolean("Enable")) {
                 if (selectorSection.getBoolean("Enable")) {
+                    isEnabled = true;
                     if (selectorSection.contains("Forms", true) && selectorSection.isConfigurationSection("Forms")) {
                         ConfigurationSection forms = selectorSection.getConfigurationSection("forms");
                         assert forms != null;
 
                         boolean noSuccess = true;
+                        boolean containsDefault = false;
                         for (String entry : forms.getKeys(false)) {
                             if (!forms.isConfigurationSection(entry)) {
                                 logger.warn("Bedrock form with name " + entry + " is being skipped because it is not a configuration section");
@@ -58,20 +66,24 @@ private boolean load() {
                                 enabledForms.put(entry, form);
                                 noSuccess = false;
                             }
+                            if ("default".equals(entry)) {
+                                containsDefault = true;
+                            }
                         }
 
                         if (noSuccess) {
-                            isEnabled = false;
-                            logger.warn("Failed to ALL bedrock forms, due to configuration error.");
+                            logger.warn("Failed to load ALL bedrock forms, due to configuration error.");
+                            return;
                         } else {
-                            isEnabled = true;
                             logger.info("Valid Bedrock forms are: " + enabledForms.keySet());
-                            return true;
+                        }
+
+                        if (!containsDefault) {
+                            logger.warn("Failed to load a default form! The Server Selector compass will not work and players will not be able to open the default form with \"/ghub\"");
                         }
                     }
                 } else {
                     logger.debug("Not enabling bedrock forms because it is disabled in the config.");
-                    isEnabled = false;
                 }
             } else {
                 logger.warn("Not enabling bedrock forms because the Enable value is not present in the config.");
@@ -79,11 +91,6 @@ private boolean load() {
         } else {
             logger.warn("Not enabling bedrock forms because the whole configuration section is not present.");
         }
-        return false;
-    }
-
-    public void sendForm(@Nonnull FloodgatePlayer player) {
-        enabledForms.get("parent").sendForm(player);
     }
 
     public void sendForm(@Nonnull FloodgatePlayer player, @Nonnull String form) {
@@ -93,10 +100,13 @@ public void sendForm(@Nonnull FloodgatePlayer player, @Nonnull String form) {
     public boolean isEnabled() {
         return isEnabled;
     }
+    public List<String> getFormNames() {
+        return new ArrayList<>(enabledForms.keySet());
+    }
 
     @Override
     public boolean reload() {
-        isEnabled = load();
+        load();
         return isEnabled;
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index 54e1a01..1bdeb51 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -26,6 +26,7 @@ public static boolean isEnabled() {
         return GeyserHubMain.getInstance().getConfig().getBoolean("Java-Selector.Enabled", true);
     }
 
+    // todo: maybe just remove the FileConfiguration parameter
     public static void openMenu(@Nonnull Player player, @Nonnull FileConfiguration config) {
         SelectorLogger logger = SelectorLogger.getLogger();
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
index 47d009e..51cf6d3 100644
--- a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
+++ b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
@@ -1,8 +1,8 @@
 package dev.projectg.geyserhub.module.teleporter;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.Reloadable;
-import dev.projectg.geyserhub.ReloadableRegistry;
+import dev.projectg.geyserhub.reloadable.Reloadable;
+import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
 import dev.projectg.geyserhub.SelectorLogger;
 import org.bukkit.Bukkit;
 import org.bukkit.Location;
diff --git a/src/main/java/dev/projectg/geyserhub/Reloadable.java b/src/main/java/dev/projectg/geyserhub/reloadable/Reloadable.java
similarity index 70%
rename from src/main/java/dev/projectg/geyserhub/Reloadable.java
rename to src/main/java/dev/projectg/geyserhub/reloadable/Reloadable.java
index 58f7b0c..5767b76 100644
--- a/src/main/java/dev/projectg/geyserhub/Reloadable.java
+++ b/src/main/java/dev/projectg/geyserhub/reloadable/Reloadable.java
@@ -1,4 +1,4 @@
-package dev.projectg.geyserhub;
+package dev.projectg.geyserhub.reloadable;
 
 /**
  * Any classes that implements this interface should be able to reload their functionality.
@@ -7,7 +7,7 @@ public interface Reloadable {
 
     /**
      * Reload the functionality of the class.
-     * @return true if the reload was successful
+     * @return false if there was a severe error
      */
     boolean reload();
 }
diff --git a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
new file mode 100644
index 0000000..e5df696
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
@@ -0,0 +1,54 @@
+package dev.projectg.geyserhub.reloadable;
+
+import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.SelectorLogger;
+import org.bukkit.ChatColor;
+
+import javax.annotation.Nonnull;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ReloadableRegistry {
+
+    /**
+     * A set of instances that implement the Reloadable interface
+     */
+    private static final Set<Reloadable> reloadables = new HashSet<>();
+
+    /**
+     * Register a reloadable
+     * @param reloadable the reloadable
+     */
+    public static void registerReloadable(@Nonnull Reloadable reloadable) {
+        reloadables.add(reloadable);
+    }
+
+    /**
+     * @return A copy of all registered reloadables
+     */
+    public static Reloadable[] getRegisteredReloadables() {
+        return reloadables.toArray(new Reloadable[0]);
+    }
+
+    public static boolean reloadAll() {
+        SelectorLogger logger = SelectorLogger.getLogger();
+
+        if (GeyserHubMain.getInstance().loadConfiguration()) {
+            logger.info("[GeyserHub] Reloaded the configuration, reloading modules...");
+        } else {
+            logger.severe("[GeyserHub] " + ChatColor.RED + "Failed to reload the configuration!");
+            return false;
+        }
+
+        boolean success = true;
+        for (Reloadable reloadable : ReloadableRegistry.getRegisteredReloadables()) {
+            if (!reloadable.reload()) {
+                logger.severe("[GeyserHub] " + ChatColor.RED + "Failed to reload class: " + ChatColor.RESET + reloadable.getClass().toString());
+                success = false;
+            }
+        }
+
+        logger.info("[GeyserHub] Finished reload.");
+        return success;
+    }
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 928ebf8..75c262e 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -44,7 +44,7 @@ Bedrock-Selector:
   Enable: true
 
   Forms:
-    parent:
+    default:
       Title: "Server Selector"
       Content: "Click on the server button of choice."
       Buttons:
@@ -60,7 +60,7 @@ Bedrock-Selector:
           Button-Text: "Minigames"
           Image-URL: "https://www.digminecraft.com/weapon_recipes/images/diamond_sword.png"
           Commands:
-            - "execute as %player_name% run ghform minigames"
+            - "execute as %player_name% run ghub form minigames"
         commandTwo:
           Button-Text: "Spawn"
           ImageURL: "https://www.digminecraft.com/decoration_recipes/images/lodestone.png"
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index f168d80..177c97c 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -10,13 +10,20 @@ softdepend:
   - PlaceholderAPI
 
 commands:
-  ghteleporter:
-    description: Open a server selector form.
-    permission: geyserhub.teleporter
-  ghreload:
-    description: Reload the selector form.
-    permission: geyserhub.reload
+  ghhub:
+    description: General command
+    permission: geyserhub.main
+
 permissions:
+  geyserhub.main:
+    description: Access to the base command /ghub
+    default: true
+  geyserhub.form:
+    description: Access to open a form with /ghub form <form>
+    default: true
+  geyserhub.reload:
+    description: Access to /ghub reload
+    default: op
   geyserhub.blockbreak:
     description: Allows player to break blocks
     default: op

From 74f3a0048068e7b9468e7b34677a2974a320c48b Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Mon, 14 Jun 2021 13:20:26 -0400
Subject: [PATCH 10/74] fix NPE with config

---
 .../dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java
index 43f6a06..ec8a680 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java
@@ -49,7 +49,7 @@ private void load() {
                 if (selectorSection.getBoolean("Enable")) {
                     isEnabled = true;
                     if (selectorSection.contains("Forms", true) && selectorSection.isConfigurationSection("Forms")) {
-                        ConfigurationSection forms = selectorSection.getConfigurationSection("forms");
+                        ConfigurationSection forms = selectorSection.getConfigurationSection("Forms");
                         assert forms != null;
 
                         boolean noSuccess = true;

From f320f96677ab9a5752348c7930969a069c1ced45 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Mon, 14 Jun 2021 14:48:45 -0400
Subject: [PATCH 11/74] add custom item option (breaks config), refactor some
 stuff

- refactor menu packaging
- refactor form packaging
- replace some assert statements with Objects.requireNonNull()
---
 .../dev/projectg/geyserhub/GeyserHubMain.java | 15 ++--
 .../geyserhub/module/Placeholders.java        |  1 -
 .../module/listeners/ItemInteract.java        | 56 -------------
 .../module/listeners/ItemOnJoin.java          | 41 ----------
 .../geyserhub/module/menu/AccessItem.java     | 78 +++++++++++++++++++
 .../module/menu/CommonMenuListeners.java      | 65 ++++++++++++++++
 .../geyserhub/module/menu/SelectorItem.java   | 24 ------
 .../module/menu/bedrock/BedrockForm.java      | 11 ++-
 .../module/menu/bedrock/BedrockMenu.java      | 11 +--
 .../menu/bedrock/BedrockMenuListeners.java    | 28 +++++++
 .../menu/bedrock/{ => button}/Button.java     |  2 +-
 .../bedrock/{ => button}/CommandButton.java   | 10 +--
 .../bedrock/{ => button}/ServerButton.java    | 10 +--
 .../geyserhub/module/menu/java/JavaMenu.java  | 10 +--
 .../java/JavaMenuListeners.java}              |  4 +-
 .../module/teleporter/JoinTeleporter.java     |  7 +-
 src/main/resources/config.yml                 | 32 ++++----
 17 files changed, 232 insertions(+), 173 deletions(-)
 delete mode 100644 src/main/java/dev/projectg/geyserhub/module/listeners/ItemInteract.java
 delete mode 100644 src/main/java/dev/projectg/geyserhub/module/listeners/ItemOnJoin.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
 delete mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/SelectorItem.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
 rename src/main/java/dev/projectg/geyserhub/module/menu/bedrock/{ => button}/Button.java (93%)
 rename src/main/java/dev/projectg/geyserhub/module/menu/bedrock/{ => button}/CommandButton.java (51%)
 rename src/main/java/dev/projectg/geyserhub/module/menu/bedrock/{ => button}/ServerButton.java (51%)
 rename src/main/java/dev/projectg/geyserhub/module/{listeners/SelectorInventory.java => menu/java/JavaMenuListeners.java} (94%)

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 21242ba..119c8cb 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -1,9 +1,9 @@
 package dev.projectg.geyserhub;
 
 import dev.projectg.geyserhub.command.GeyserHubCommand;
-import dev.projectg.geyserhub.module.listeners.ItemInteract;
-import dev.projectg.geyserhub.module.listeners.SelectorInventory;
-import dev.projectg.geyserhub.module.listeners.ItemOnJoin;
+import dev.projectg.geyserhub.module.menu.CommonMenuListeners;
+import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenuListeners;
+import dev.projectg.geyserhub.module.menu.java.JavaMenuListeners;
 import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenu;
 import dev.projectg.geyserhub.module.message.Broadcast;
 import dev.projectg.geyserhub.module.message.MessageJoin;
@@ -61,10 +61,12 @@ public void onEnable() {
         // todo: and add command suggestions/completions, help pages that only shows available commands
         Objects.requireNonNull(getCommand("ghub")).setExecutor(new GeyserHubCommand());
 
-        Bukkit.getServer().getPluginManager().registerEvents(new ItemInteract(), this);
-        Bukkit.getServer().getPluginManager().registerEvents(new SelectorInventory(), this);
-        Bukkit.getServer().getPluginManager().registerEvents(new ItemOnJoin(), this);
+        Bukkit.getServer().getPluginManager().registerEvents(new CommonMenuListeners(), this);
+        Bukkit.getServer().getPluginManager().registerEvents(new BedrockMenuListeners(), this);
+        Bukkit.getServer().getPluginManager().registerEvents(new JavaMenuListeners(), this);
+
         Bukkit.getServer().getPluginManager().registerEvents(new JoinTeleporter(), this);
+
         Bukkit.getServer().getPluginManager().registerEvents(new WorldSettings(), this);
 
         if (getConfig().getBoolean("Scoreboard.Enable", false)) {
@@ -106,6 +108,7 @@ public boolean loadConfiguration() {
                 return false;
             } else {
                 reloadConfig();
+                logger.debug("Loaded configuration successfully");
                 return true;
             }
         } catch (IOException | InvalidConfigurationException e) {
diff --git a/src/main/java/dev/projectg/geyserhub/module/Placeholders.java b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
index d8a4d26..6148920 100644
--- a/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
+++ b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
@@ -7,6 +7,5 @@
 
 public class Placeholders {
     public static final String[] colorCodes = {"&0", "&1", "&2", "&3", "&4", "&5", "&6", "&7", "&8", "&9", "&a", "&b", "&c", "&d", "&e", "&f"};
-    public static ArrayList<Player> hide = new ArrayList<>();
     public static int refreshRate = GeyserHubMain.getInstance().getConfig().getInt("Scoreboard.Refresh-rate");
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/listeners/ItemInteract.java b/src/main/java/dev/projectg/geyserhub/module/listeners/ItemInteract.java
deleted file mode 100644
index b843ec6..0000000
--- a/src/main/java/dev/projectg/geyserhub/module/listeners/ItemInteract.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package dev.projectg.geyserhub.module.listeners;
-
-import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenu;
-import dev.projectg.geyserhub.module.menu.java.JavaMenu;
-import dev.projectg.geyserhub.module.menu.SelectorItem;
-import org.bukkit.Material;
-import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.Listener;
-import org.bukkit.event.block.Action;
-import org.bukkit.event.inventory.InventoryClickEvent;
-import org.bukkit.event.player.PlayerDropItemEvent;
-import org.bukkit.event.player.PlayerInteractEvent;
-import org.geysermc.floodgate.api.FloodgateApi;
-
-import java.util.Objects;
-
-@SuppressWarnings("unused")
-public class ItemInteract implements Listener {
-
-    @EventHandler
-    public void onInventoryClick(InventoryClickEvent event) {
-        if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) {
-            return;
-        }
-        if (!GeyserHubMain.getInstance().getConfig().getBoolean("Allow-Item-Move") && Objects.requireNonNull(event.getCurrentItem()).isSimilar(SelectorItem.getItem())) {
-                event.setCancelled(true);
-            }
-    }
-
-    @EventHandler
-    public void onInteract(PlayerInteractEvent event) {
-        Player player = event.getPlayer();
-        if (player.getInventory().getItemInMainHand().isSimilar(SelectorItem.getItem())) {
-            if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
-                if (FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {
-                    BedrockMenu.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()), BedrockMenu.DEFAULT);
-                } else {
-                    JavaMenu.openMenu(player, GeyserHubMain.getInstance().getConfig());
-                }
-            }
-        }
-    }
-
-    @EventHandler
-    public void onPlayerDropItem(PlayerDropItemEvent event) {
-        if (event.getItemDrop().getItemStack().isSimilar(SelectorItem.getItem())) {
-            if (!GeyserHubMain.getInstance().getConfig().getBoolean("Allow-Item-Drop")) {
-                event.setCancelled(true);
-            } else if (GeyserHubMain.getInstance().getConfig().getBoolean("Destroy-Dropped-Item")) {
-                event.getItemDrop().remove();
-            }
-        }
-    }
-}
diff --git a/src/main/java/dev/projectg/geyserhub/module/listeners/ItemOnJoin.java b/src/main/java/dev/projectg/geyserhub/module/listeners/ItemOnJoin.java
deleted file mode 100644
index d9d4fd3..0000000
--- a/src/main/java/dev/projectg/geyserhub/module/listeners/ItemOnJoin.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package dev.projectg.geyserhub.module.listeners;
-
-import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.module.menu.SelectorItem;
-import org.bukkit.Material;
-import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.Listener;
-import org.bukkit.event.player.PlayerJoinEvent;
-import org.bukkit.inventory.ItemStack;
-
-public class ItemOnJoin implements Listener {
-
-    @SuppressWarnings("unused")
-    @EventHandler
-    public void onPlayerJoin(PlayerJoinEvent event) {
-        event.getPlayer().getInventory().setHeldItemSlot(GeyserHubMain.getInstance().getConfig().getInt("Slot"));
-        if (GeyserHubMain.getInstance().getConfig().getBoolean("Item-Join")) {
-            Player player = event.getPlayer();
-            ItemStack compass = SelectorItem.getItem();
-            if (player.getInventory().contains(compass)) {
-                return;
-            }
-
-            int desiredSlot = GeyserHubMain.getInstance().getConfig().getInt("Slot");
-            ItemStack oldItem = player.getInventory().getItem(desiredSlot);
-            if (oldItem == null || oldItem.getType() == Material.AIR) {
-                player.getInventory().setItem(desiredSlot, compass);
-            } else {
-                for (int i = 0; i < 10; i++) {
-                    if (player.getInventory().getItem(i) == null || player.getInventory().getItem(i).getType() == Material.AIR) {
-                        player.getInventory().setItem(i, oldItem);
-                        player.getInventory().setItem(desiredSlot, compass);
-                        break;
-                    }
-                }
-                // If the player doesn't have the space in their hotbar then they don't get it
-            }
-        }
-    }
-}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
new file mode 100644
index 0000000..8a607c0
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
@@ -0,0 +1,78 @@
+package dev.projectg.geyserhub.module.menu;
+
+
+import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.SelectorLogger;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+public class AccessItem {
+
+    private static final ItemStack ACCESS_ITEM;
+    static {
+        FileConfiguration config = GeyserHubMain.getInstance().getConfig();
+
+        // Get the material
+        Material material;
+        if (config.contains("Selector-Item.Material", true)) {
+            String materialName = config.getString("Selector-Item.Material");
+            Objects.requireNonNull(materialName);
+            material = Material.getMaterial(materialName, false);
+            if (material == null) {
+                material = Material.getMaterial(materialName, true);
+                if (material == null) {
+                    SelectorLogger.getLogger().warn("Failed to find a Material for \"" + materialName + "\". Defaulting to COMPASS for the access item.");
+                    material = Material.COMPASS;
+                }
+            }
+            if (material == Material.AIR || material == Material.CAVE_AIR || material == Material.VOID_AIR) {
+                SelectorLogger.getLogger().warn("\"Selector-Item.Material\" cannot be AIR! Choose a different material. Defaulting to COMPASS for the access item.");
+            }
+        } else {
+            SelectorLogger.getLogger().warn("Failed to find \"Selector-Item.Material\" in the config! Defaulting to COMPASS for the access item.");
+            material = Material.COMPASS;
+        }
+
+        // Create a new item with the material, get the meta
+        ItemStack item = new ItemStack(material);
+        ItemMeta meta = item.getItemMeta();
+        Objects.requireNonNull(meta);
+
+        // Set the display name in the meta
+        String name;
+        if (config.contains("Selector-Item.Name", true)) {
+            name = config.getString("Selector-Item.Name");
+            Objects.requireNonNull(name);
+        } else {
+            SelectorLogger.getLogger().warn("\"Selector-Item.Name\" in the config does not exist. Defaulting to \"&6Server Selector\" for the access item name.");
+            name = "&6Server Selector";
+        }
+        meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', name));
+
+        // Set the lore in the meta
+        List<String> lore;
+        if (config.contains("Selector-Item.Lore") && config.isList("Selector-Item.Lore")) {
+            lore = config.getStringList("Selector-Item.Lore");
+        } else {
+            lore = Collections.emptyList();
+        }
+        meta.setLore(lore);
+
+
+        // Set the meta and set the field
+        item.setItemMeta(meta);
+        ACCESS_ITEM = item;
+        SelectorLogger.getLogger().debug("Created and set the access item from the configuration.");
+    }
+
+    public static ItemStack getItem() {
+        return ACCESS_ITEM;
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
new file mode 100644
index 0000000..89be93a
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
@@ -0,0 +1,65 @@
+package dev.projectg.geyserhub.module.menu;
+
+import dev.projectg.geyserhub.GeyserHubMain;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.player.PlayerDropItemEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.Objects;
+
+@SuppressWarnings("unused")
+public class CommonMenuListeners implements Listener {
+
+    @EventHandler
+    public void onInventoryClick(InventoryClickEvent event) {
+        if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) {
+            return;
+        }
+        if (!GeyserHubMain.getInstance().getConfig().getBoolean("Selector-Item.Allow-Move") && Objects.requireNonNull(event.getCurrentItem()).isSimilar(AccessItem.getItem())) {
+                event.setCancelled(true);
+            }
+    }
+
+    @EventHandler
+    public void onPlayerDropItem(PlayerDropItemEvent event) {
+        if (event.getItemDrop().getItemStack().isSimilar(AccessItem.getItem())) {
+            if (!GeyserHubMain.getInstance().getConfig().getBoolean("Selector-Item.Allow-Drop")) {
+                event.setCancelled(true);
+            } else if (GeyserHubMain.getInstance().getConfig().getBoolean("Selector-Item.Destroy-Dropped")) {
+                event.getItemDrop().remove();
+            }
+        }
+    }
+
+    @EventHandler
+    public void onPlayerJoin(PlayerJoinEvent event) {
+        event.getPlayer().getInventory().setHeldItemSlot(GeyserHubMain.getInstance().getConfig().getInt("Selector-Item.Slot"));
+        if (GeyserHubMain.getInstance().getConfig().getBoolean("Selector-Item.Join")) {
+            Player player = event.getPlayer();
+            ItemStack accessItem = AccessItem.getItem();
+            if (player.getInventory().contains(accessItem)) {
+                return;
+            }
+
+            int desiredSlot = GeyserHubMain.getInstance().getConfig().getInt("Selector-Item.Slot");
+            ItemStack oldItem = player.getInventory().getItem(desiredSlot);
+            if (oldItem == null || oldItem.getType() == Material.AIR) {
+                player.getInventory().setItem(desiredSlot, accessItem);
+            } else {
+                for (int i = 0; i < 10; i++) {
+                    if (player.getInventory().getItem(i) == null || Objects.requireNonNull(player.getInventory().getItem(i)).getType() == Material.AIR) {
+                        player.getInventory().setItem(i, oldItem);
+                        player.getInventory().setItem(desiredSlot, accessItem);
+                        break;
+                    }
+                }
+                // If the player doesn't have the space in their hotbar then they don't get it
+            }
+        }
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/SelectorItem.java b/src/main/java/dev/projectg/geyserhub/module/menu/SelectorItem.java
deleted file mode 100644
index b7055ed..0000000
--- a/src/main/java/dev/projectg/geyserhub/module/menu/SelectorItem.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package dev.projectg.geyserhub.module.menu;
-
-
-import org.bukkit.ChatColor;
-import org.bukkit.Material;
-import org.bukkit.inventory.ItemStack;
-import org.bukkit.inventory.meta.ItemMeta;
-
-public class SelectorItem {
-
-    private static final ItemStack SELECTOR_ITEM;
-    static {
-        ItemStack compass = new ItemStack(Material.COMPASS);
-        ItemMeta compassMeta = compass.getItemMeta();
-        assert compassMeta != null;
-        compassMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&6Server Selector"));
-        compass.setItemMeta(compassMeta);
-        SELECTOR_ITEM = compass;
-    }
-
-    public static ItemStack getItem() {
-        return SELECTOR_ITEM;
-    }
-}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index ef4e687..ba2cbbf 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -2,6 +2,9 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
+import dev.projectg.geyserhub.module.menu.bedrock.button.Button;
+import dev.projectg.geyserhub.module.menu.bedrock.button.CommandButton;
+import dev.projectg.geyserhub.module.menu.bedrock.button.ServerButton;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
@@ -58,7 +61,7 @@ private boolean load(@Nonnull ConfigurationSection configSection) {
             return false;
         }
         ConfigurationSection buttonSection = configSection.getConfigurationSection("Buttons");
-        assert buttonSection != null;
+        Objects.requireNonNull(buttonSection);
         List<Button> buttons = getButtons(buttonSection);
         if (buttons.isEmpty()) {
             logger.warn("Failed to create any valid buttons of form: " + configSection.getName() + "! All listed buttons have a malformed section!");
@@ -112,14 +115,14 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
 
             if (buttonInfo.contains("Button-Text", true) && buttonInfo.isString("Button-Text")) {
                 String buttonText = buttonInfo.getString("Button-Text");
-                assert buttonText != null;
+                Objects.requireNonNull(buttonText);
                 logger.debug(buttonId + " has Button-Text: " + buttonText);
 
                 // Add image if specified
                 FormImage image = null;
                 if (buttonInfo.contains("ImageURL", true)) {
                     String imageURL = buttonInfo.getString("ImageURL");
-                    assert imageURL != null;
+                    Objects.requireNonNull(imageURL);
                     image = FormImage.of(FormImage.Type.URL, imageURL);
                     logger.debug(buttonId + " contains image with URL: " + image);
                 } else {
@@ -133,7 +136,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
                 // Get the server or command data and compile the button
                 if (buttonInfo.contains("Server") && buttonInfo.isString("Server")) {
                     String serverName = buttonInfo.getString("Server");
-                    assert serverName != null;
+                    Objects.requireNonNull(serverName);
                     logger.debug(buttonId + " contains BungeeCord server: " + serverName);
                     compiledButtons.add(new ServerButton(serverName, buttonText, image));
                 } else if (buttonInfo.contains("Commands") && buttonInfo.isList("Commands")) {
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java
index ec8a680..ae292ca 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java
@@ -9,10 +9,7 @@
 import org.geysermc.floodgate.api.player.FloodgatePlayer;
 
 import javax.annotation.Nonnull;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 public class BedrockMenu implements Reloadable {
 
@@ -43,14 +40,14 @@ private void load() {
 
         if (config.contains("Bedrock-Selector", true) && config.isConfigurationSection("Bedrock-Selector")) {
             ConfigurationSection selectorSection = config.getConfigurationSection("Bedrock-Selector");
-            assert selectorSection != null;
+            Objects.requireNonNull(selectorSection);
 
             if (selectorSection.contains("Enable", true) && selectorSection.isBoolean("Enable")) {
                 if (selectorSection.getBoolean("Enable")) {
                     isEnabled = true;
                     if (selectorSection.contains("Forms", true) && selectorSection.isConfigurationSection("Forms")) {
                         ConfigurationSection forms = selectorSection.getConfigurationSection("Forms");
-                        assert forms != null;
+                        Objects.requireNonNull(forms);
 
                         boolean noSuccess = true;
                         boolean containsDefault = false;
@@ -60,7 +57,7 @@ private void load() {
                                 continue;
                             }
                             ConfigurationSection formInfo = forms.getConfigurationSection(entry);
-                            assert formInfo != null;
+                            Objects.requireNonNull(formInfo);
                             BedrockForm form = new BedrockForm(formInfo);
                             if (form.isEnabled()) {
                                 enabledForms.put(entry, form);
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
new file mode 100644
index 0000000..a2cce7f
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
@@ -0,0 +1,28 @@
+package dev.projectg.geyserhub.module.menu.bedrock;
+
+import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.module.menu.AccessItem;
+import dev.projectg.geyserhub.module.menu.java.JavaMenu;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.player.PlayerInteractEvent;
+import org.geysermc.floodgate.api.FloodgateApi;
+
+public class BedrockMenuListeners implements Listener {
+
+    @EventHandler
+    public void onInteract(PlayerInteractEvent event) {
+        Player player = event.getPlayer();
+        if (player.getInventory().getItemInMainHand().isSimilar(AccessItem.getItem())) {
+            if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
+                if (FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {
+                    BedrockMenu.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()), BedrockMenu.DEFAULT);
+                } else {
+                    JavaMenu.openMenu(player, GeyserHubMain.getInstance().getConfig());
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/Button.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/Button.java
similarity index 93%
rename from src/main/java/dev/projectg/geyserhub/module/menu/bedrock/Button.java
rename to src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/Button.java
index f4dd779..ff5ba3f 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/Button.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/Button.java
@@ -1,4 +1,4 @@
-package dev.projectg.geyserhub.module.menu.bedrock;
+package dev.projectg.geyserhub.module.menu.bedrock.button;
 
 import org.geysermc.cumulus.component.ButtonComponent;
 import org.geysermc.cumulus.util.FormImage;
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/CommandButton.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/CommandButton.java
similarity index 51%
rename from src/main/java/dev/projectg/geyserhub/module/menu/bedrock/CommandButton.java
rename to src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/CommandButton.java
index 7a64e62..49d7140 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/CommandButton.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/CommandButton.java
@@ -1,4 +1,4 @@
-package dev.projectg.geyserhub.module.menu.bedrock;
+package dev.projectg.geyserhub.module.menu.bedrock.button;
 
 import org.geysermc.cumulus.util.FormImage;
 
@@ -6,19 +6,19 @@
 import javax.annotation.Nullable;
 import java.util.List;
 
-public class CommandButton extends Button{
+public class CommandButton extends Button {
 
     private final List<String> commands;
 
-    CommandButton(@Nonnull List<String> commands, @Nonnull String text) {
+    public CommandButton(@Nonnull List<String> commands, @Nonnull String text) {
         super(text);
         this.commands = commands;
     }
-    CommandButton(@Nonnull List<String> commands, @Nonnull String text, @Nullable FormImage image) {
+    public CommandButton(@Nonnull List<String> commands, @Nonnull String text, @Nullable FormImage image) {
         super(text, image);
         this.commands = commands;
     }
-    CommandButton(@Nonnull List<String> commands, @Nonnull String text, @Nonnull FormImage.Type type, @Nonnull String data) {
+    public CommandButton(@Nonnull List<String> commands, @Nonnull String text, @Nonnull FormImage.Type type, @Nonnull String data) {
         super(text, type, data);
         this.commands = commands;
     }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/ServerButton.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/ServerButton.java
similarity index 51%
rename from src/main/java/dev/projectg/geyserhub/module/menu/bedrock/ServerButton.java
rename to src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/ServerButton.java
index d28ef48..1dab57b 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/ServerButton.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/ServerButton.java
@@ -1,25 +1,25 @@
-package dev.projectg.geyserhub.module.menu.bedrock;
+package dev.projectg.geyserhub.module.menu.bedrock.button;
 
 import org.geysermc.cumulus.util.FormImage;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-public class ServerButton extends Button{
+public class ServerButton extends Button {
 
     private final String serverName;
 
-    ServerButton(@Nonnull String serverName, @Nonnull String text) {
+    public ServerButton(@Nonnull String serverName, @Nonnull String text) {
         super(text);
         this.serverName = serverName;
     }
 
-    ServerButton(@Nonnull String serverName, @Nonnull String text, @Nullable FormImage image) {
+    public ServerButton(@Nonnull String serverName, @Nonnull String text, @Nullable FormImage image) {
         super(text, image);
         this.serverName = serverName;
     }
 
-    ServerButton(@Nonnull String serverName, @Nonnull String text, @Nonnull FormImage.Type type, @Nonnull String data) {
+    public ServerButton(@Nonnull String serverName, @Nonnull String text, @Nonnull FormImage.Type type, @Nonnull String data) {
         super(text, type, data);
         this.serverName = serverName;
     }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index 1bdeb51..5390f16 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -36,9 +36,9 @@ public static void openMenu(@Nonnull Player player, @Nonnull FileConfiguration c
             return;
         }
         ConfigurationSection javaSection = config.getConfigurationSection("Java-Selector");
-        assert javaSection != null;
+        Objects.requireNonNull(javaSection);
         ConfigurationSection serverSection = javaSection.getConfigurationSection("Servers");
-        assert serverSection != null;
+        Objects.requireNonNull(serverSection);
 
         Inventory selectorGUI = Bukkit.createInventory(player, javaSection.getInt("Size"), ChatColor.DARK_AQUA + javaSection.getString("Title"));
 
@@ -51,13 +51,13 @@ public static void openMenu(@Nonnull Player player, @Nonnull FileConfiguration c
                 continue;
             }
             ConfigurationSection serverInfo = serverSection.getConfigurationSection(serverName);
-            assert serverInfo != null;
+            Objects.requireNonNull(serverInfo);
 
             if (serverInfo.contains("Display-Name", true) && serverInfo.isString("Display-Name") && serverInfo.contains("Material", true) && serverInfo.isString("Material") && serverInfo.contains("Slot", true) && serverInfo.isInt("Slot")) {
 
                 // Get all the required info
                 String displayName = serverInfo.getString("Display-Name");
-                assert displayName != null;
+                Objects.requireNonNull(displayName);
                 String materialName = serverInfo.getString("Material");
                 Material material = Material.matchMaterial(Objects.requireNonNull(serverInfo.getString("Material")));
                 if (material == null || material.isAir()) {
@@ -70,7 +70,7 @@ public static void openMenu(@Nonnull Player player, @Nonnull FileConfiguration c
                 ItemStack serverStack = new ItemStack(material);
                 ItemMeta itemMeta = serverStack.getItemMeta();
                 // It will only be null if the itemstack is air, which we already filter against.
-                assert itemMeta != null;
+                Objects.requireNonNull(itemMeta);
                 itemMeta.setDisplayName(displayName);
                 itemMeta.getPersistentDataContainer().set(new NamespacedKey(GeyserHubMain.getInstance(), "bungeeName"), PersistentDataType.STRING, serverName);
                 if (serverInfo.contains("Lore", false) && serverInfo.isList("Lore")) {
diff --git a/src/main/java/dev/projectg/geyserhub/module/listeners/SelectorInventory.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
similarity index 94%
rename from src/main/java/dev/projectg/geyserhub/module/listeners/SelectorInventory.java
rename to src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
index 0f38d54..6145e37 100644
--- a/src/main/java/dev/projectg/geyserhub/module/listeners/SelectorInventory.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
@@ -1,4 +1,4 @@
-package dev.projectg.geyserhub.module.listeners;
+package dev.projectg.geyserhub.module.menu.java;
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
@@ -16,7 +16,7 @@
 import java.io.IOException;
 import java.util.Objects;
 
-public class SelectorInventory implements Listener {
+public class JavaMenuListeners implements Listener {
 
     @SuppressWarnings("unused")
     @EventHandler
diff --git a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
index 51cf6d3..5449bcf 100644
--- a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
+++ b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
@@ -15,6 +15,7 @@
 
 import javax.annotation.Nonnull;
 import java.util.Arrays;
+import java.util.Objects;
 
 public class JoinTeleporter implements Listener, Reloadable {
 
@@ -44,7 +45,7 @@ private boolean load(@Nonnull FileConfiguration config) {
             return false;
         }
         ConfigurationSection section = config.getConfigurationSection("Join-Teleporter");
-        assert section != null;
+        Objects.requireNonNull(section);
 
         // Validate all our values
         if (!(section.contains("Enable", true) && section.isBoolean(("Enable")))) {
@@ -56,7 +57,7 @@ private boolean load(@Nonnull FileConfiguration config) {
             return false;
         }
         String worldName = section.getString("World");
-        assert worldName != null;
+        Objects.requireNonNull(worldName);
         World world = Bukkit.getServer().getWorld(worldName);
         if (world == null) {
             logger.severe("Join-Teleporter.World in the config is not a valid world, skipping module!");
@@ -66,7 +67,7 @@ private boolean load(@Nonnull FileConfiguration config) {
         if (section.contains("Location") && section.isString("Location")) {
             // Make sure the given coordinates are in the correct format
             String composedCoords = section.getString("Location");
-            assert composedCoords != null;
+            Objects.requireNonNull(composedCoords);
             if (!composedCoords.matches(COORDINATE_REGEX)) {
                 logger.severe("Join-Teleporter.Location in the config is not of the format <integer;integer;integer>, skipping module!");
                 return false;
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 75c262e..8b47411 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -3,20 +3,23 @@
 # Toggle debug logging
 Enable-Debug: false
 
-# Don't touch this
-Config-Version: 4
+# Item to access the default menu
+Selector-Item:
+  Material: COMPASS
+  Name: "&6Server Selector"
+  Lore:
+    - "Right click me!"
+  Slot: 4
 
+  # Gives the player the item which they can use to open the default form
+  Join: true
+  # Stop the player from dropping the item
+  Allow-Drop: false
+  # Destroy the compass if the player drops it
+  Destroy-Dropped: true
+  # Stop the player from moving the compass in their inventory
+  Allow-Move: false
 
-# Gives the player a compass which they can use to open the selector form
-Item-Join: true
-# The slot in which the item will be given
-Slot: 4
-# Stop the player from dropping the compass
-Allow-Item-Drop: false
-# Destroy the compass if the player drops it
-Destroy-Dropped-Item: true
-# Stop the player from moving the compass
-Allow-Item-Move: false
 
 # Please see our readme for information on configuring this section.
 # https://github.com/ProjectG-Plugins/GeyserServerSelector#Configuration
@@ -129,4 +132,7 @@ World-settings:
   disable-block-fire-spread: true
   disable_block-leaf-decay: true
   disable-block-place: true
-  disable-block-break: true
\ No newline at end of file
+  disable-block-break: true
+
+# Don't touch this
+Config-Version: 4
\ No newline at end of file

From 3e6f98eb5712e1b39ea3395675374a9858e6d379 Mon Sep 17 00:00:00 2001
From: Jens Collaert <jenscollaertprive@hotmail.com>
Date: Tue, 15 Jun 2021 16:25:16 +0200
Subject: [PATCH 12/74] Fix error onEnable

---
 src/main/resources/plugin.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 177c97c..610ba3c 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -10,7 +10,7 @@ softdepend:
   - PlaceholderAPI
 
 commands:
-  ghhub:
+  ghub:
     description: General command
     permission: geyserhub.main
 

From 0d32f7472d65b8ff4235f1b879731ec13b1ff141 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Tue, 15 Jun 2021 17:36:07 -0400
Subject: [PATCH 13/74] fix reloadable registry log messages

---
 .../projectg/geyserhub/reloadable/ReloadableRegistry.java | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
index e5df696..90cc379 100644
--- a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
@@ -34,21 +34,21 @@ public static boolean reloadAll() {
         SelectorLogger logger = SelectorLogger.getLogger();
 
         if (GeyserHubMain.getInstance().loadConfiguration()) {
-            logger.info("[GeyserHub] Reloaded the configuration, reloading modules...");
+            logger.info("Reloaded the configuration, reloading modules...");
         } else {
-            logger.severe("[GeyserHub] " + ChatColor.RED + "Failed to reload the configuration!");
+            logger.severe(ChatColor.RED + "Failed to reload the configuration!");
             return false;
         }
 
         boolean success = true;
         for (Reloadable reloadable : ReloadableRegistry.getRegisteredReloadables()) {
             if (!reloadable.reload()) {
-                logger.severe("[GeyserHub] " + ChatColor.RED + "Failed to reload class: " + ChatColor.RESET + reloadable.getClass().toString());
+                logger.severe(ChatColor.RED + "Failed to reload class: " + ChatColor.RESET + reloadable.getClass().toString());
                 success = false;
             }
         }
 
-        logger.info("[GeyserHub] Finished reload.");
+        logger.info("Finished reload.");
         return success;
     }
 }

From b970c0cb8cda3cb62e7dc4d5a81f7d894e1c0207 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Tue, 15 Jun 2021 20:13:58 -0400
Subject: [PATCH 14/74] allow buttons to have both a command and a server.

- commands, if specified, are run before the server switch.
---
 .../geyserhub/module/Placeholders.java        |  5 +-
 .../geyserhub/module/menu/Button.java         | 71 ++++++++++++++++
 .../module/menu/bedrock/BedrockForm.java      | 83 ++++++++++---------
 .../module/menu/bedrock/button/Button.java    | 27 ------
 .../menu/bedrock/button/CommandButton.java    | 29 -------
 .../menu/bedrock/button/ServerButton.java     | 31 -------
 src/main/resources/config.yml                 |  2 +
 7 files changed, 116 insertions(+), 132 deletions(-)
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/Button.java
 delete mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/Button.java
 delete mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/CommandButton.java
 delete mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/ServerButton.java

diff --git a/src/main/java/dev/projectg/geyserhub/module/Placeholders.java b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
index 6148920..caa458d 100644
--- a/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
+++ b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
@@ -1,11 +1,8 @@
 package dev.projectg.geyserhub.module;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import org.bukkit.entity.Player;
-
-import java.util.ArrayList;
 
 public class Placeholders {
     public static final String[] colorCodes = {"&0", "&1", "&2", "&3", "&4", "&5", "&6", "&7", "&8", "&9", "&a", "&b", "&c", "&d", "&e", "&f"};
     public static int refreshRate = GeyserHubMain.getInstance().getConfig().getInt("Scoreboard.Refresh-rate");
-}
+}
\ No newline at end of file
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/Button.java b/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
new file mode 100644
index 0000000..779efc7
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
@@ -0,0 +1,71 @@
+package dev.projectg.geyserhub.module.menu;
+
+import org.geysermc.cumulus.component.ButtonComponent;
+import org.geysermc.cumulus.util.FormImage;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Objects;
+
+public class Button {
+
+    // base requirement for ButtonComponent
+    private String text;
+    private FormImage image;
+
+    // Everything extra
+    private List<String> commands;
+    private String server;
+
+    public Button(@Nonnull String text) {
+        this.text = Objects.requireNonNull(text);
+    }
+
+
+    public Button setText(@Nonnull String text) {
+        this.text = Objects.requireNonNull(text);
+        return this;
+    }
+
+    public Button setImage(@Nullable FormImage image) {
+        this.image = image;
+        return this;
+    }
+    public Button setImage(@Nonnull FormImage.Type type, @Nonnull String data) {
+        this.image = FormImage.of(Objects.requireNonNull(type), Objects.requireNonNull(data));
+        return this;
+    }
+    public Button setCommands(@Nullable List<String> commands) {
+        this.commands = commands;
+        return this;
+    }
+    public Button setServer(@Nullable String server) {
+        this.server = server;
+        return this;
+    }
+
+    public ButtonComponent getButtonComponent() {
+        return ButtonComponent.of(text, image);
+    }
+
+    @Nonnull
+    public String getText() {
+        return this.text;
+    }
+
+    @Nullable
+    public FormImage getImage() {
+        return this.image;
+    }
+
+    @Nullable
+    public List<String> getCommands() {
+        return this.commands;
+    }
+
+    @Nullable
+    public String getServer() {
+        return this.server;
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index ba2cbbf..b985960 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -2,9 +2,7 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
-import dev.projectg.geyserhub.module.menu.bedrock.button.Button;
-import dev.projectg.geyserhub.module.menu.bedrock.button.CommandButton;
-import dev.projectg.geyserhub.module.menu.bedrock.button.ServerButton;
+import dev.projectg.geyserhub.module.menu.Button;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
@@ -19,7 +17,11 @@
 import java.io.ByteArrayOutputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 public class BedrockForm {
@@ -125,28 +127,34 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
                     Objects.requireNonNull(imageURL);
                     image = FormImage.of(FormImage.Type.URL, imageURL);
                     logger.debug(buttonId + " contains image with URL: " + image);
-                } else {
-                    logger.debug(buttonId + " does not contain image URL");
                 }
 
-                if (buttonInfo.contains("Server", true) && buttonInfo.contains("Commands")) {
-                    logger.warn(buttonId + " contains both a Server and Commands key. Only one is allowed. Ignoring commands.");
+                // Add commands if specified
+                List<String> commands = null;
+                if (buttonInfo.contains("Commands") && buttonInfo.isList("Commands")) {
+                    List<String> potentialCommands = buttonInfo.getStringList("Commands");
+                    if (potentialCommands.isEmpty()) {
+                        logger.warn(buttonId + " contains commands list but the list was empty.");
+                    } else {
+                        commands = potentialCommands;
+                        logger.debug(buttonId + " contains commands: " + commands);
+                    }
                 }
 
-                // Get the server or command data and compile the button
+                // Add server if specified
+                String serverName = null;
                 if (buttonInfo.contains("Server") && buttonInfo.isString("Server")) {
-                    String serverName = buttonInfo.getString("Server");
+                    serverName = buttonInfo.getString("Server");
                     Objects.requireNonNull(serverName);
-                    logger.debug(buttonId + " contains BungeeCord server: " + serverName);
-                    compiledButtons.add(new ServerButton(serverName, buttonText, image));
-                } else if (buttonInfo.contains("Commands") && buttonInfo.isList("Commands")) {
-                    List<String> commands = buttonInfo.getStringList("Commands");
-                    logger.debug(buttonId + " contains commands: " + commands);
-                    compiledButtons.add(new CommandButton(commands, buttonText, image));
-                } else {
-                    logger.warn(buttonId + " did not define a BungeeCord server or commands list, not adding.");
-                    continue;
+                    logger.debug(buttonId + " contains BungeeCord target server: " + serverName);
                 }
+
+                compiledButtons.add(
+                        new Button(buttonText)
+                        .setImage(image)
+                        .setCommands(commands)
+                        .setServer(serverName));
+
                 logger.debug(buttonId + " was successfully added.");
             } else {
                 logger.warn(buttonId + " does not contain a valid Button-Text value, not adding.");
@@ -175,17 +183,9 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
         }
 
         // Resolve any placeholders in the button text
-        List<Button> formattedButtons = new ArrayList<>();
-        for (Button button : allButtons) {
-            if (button instanceof ServerButton) {
-                ServerButton serverButton = (ServerButton) button;
-                formattedButtons.add(new ServerButton(serverButton.getServerName(), PlaceholderAPI.setPlaceholders(player, serverButton.getButtonComponent().getText()), serverButton.getButtonComponent().getImage()));
-            } else if (button instanceof CommandButton) {
-                CommandButton commandButton = (CommandButton) button;
-                formattedButtons.add(new CommandButton(commandButton.getCommands(), PlaceholderAPI.setPlaceholders(player, commandButton.getButtonComponent().getText()), commandButton.getButtonComponent().getImage()));
-            } else {
-                throw new AssertionError("Failed to account for all possible types of " + button.toString());
-            }
+        List<Button> formattedButtons = new ArrayList<>(allButtons);
+        for (Button button : formattedButtons) {
+            button.setText(PlaceholderAPI.setPlaceholders(player, button.getText()));
         }
         // Create the form
         SimpleForm serverSelector = SimpleForm.of(title, content, formattedButtons.stream().map(Button::getButtonComponent).collect(Collectors.toList()));
@@ -200,25 +200,26 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
             }
 
             Button button = formattedButtons.get(response.getClickedButtonId());
-            if (button instanceof ServerButton) {
+
+            if (button.getCommands() != null) {
+                // Get the commands from the list of commands and replace any playerName placeholders
+                for (String command : button.getCommands()) {
+                    Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), PlaceholderAPI.setPlaceholders(player, command));
+                }
+            }
+
+            if (button.getServer() != null) {
                 // This should never be out of bounds considering its size is the number of valid buttons
-                String serverName = ((ServerButton) button).getServerName();
-                try (ByteArrayOutputStream b = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(b)) {
+                String serverName = button.getServer();
+                try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(baos)) {
                     out.writeUTF("Connect");
                     out.writeUTF(serverName);
-                    player.sendPluginMessage(GeyserHubMain.getInstance(), "BungeeCord", b.toByteArray());
+                    player.sendPluginMessage(GeyserHubMain.getInstance(), "BungeeCord", baos.toByteArray());
                     player.sendMessage(ChatColor.DARK_AQUA + "Trying to send you to: " + ChatColor.GREEN + serverName);
                 } catch (IOException e) {
                     logger.severe("Failed to send a plugin message to Bungeecord!");
                     e.printStackTrace();
                 }
-            } else if (button instanceof CommandButton){
-                // Get the commands from the list of commands and replace any playerName placeholders
-                for (String command : ((CommandButton) button).getCommands()) {
-                    Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), PlaceholderAPI.setPlaceholders(player, command));
-                }
-            } else {
-                throw new AssertionError("Failed to account for all possible types of " + button.toString());
             }
         });
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/Button.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/Button.java
deleted file mode 100644
index ff5ba3f..0000000
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/Button.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package dev.projectg.geyserhub.module.menu.bedrock.button;
-
-import org.geysermc.cumulus.component.ButtonComponent;
-import org.geysermc.cumulus.util.FormImage;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-public class Button {
-
-    private final ButtonComponent buttonComponent;
-
-    // This class should only be constructed if its being inherited, otherwise it is pointless (just construct a ButtonComponent)
-    protected Button(@Nonnull String text) {
-        this.buttonComponent = ButtonComponent.of(text);
-    }
-    protected Button(@Nonnull String text, @Nullable FormImage image) {
-        this.buttonComponent = ButtonComponent.of(text, image);
-    }
-    protected Button(@Nonnull String text, @Nonnull FormImage.Type type, @Nonnull String data) {
-        this.buttonComponent = ButtonComponent.of(text, type, data);
-    }
-
-    public ButtonComponent getButtonComponent() {
-        return buttonComponent;
-    }
-}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/CommandButton.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/CommandButton.java
deleted file mode 100644
index 49d7140..0000000
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/CommandButton.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package dev.projectg.geyserhub.module.menu.bedrock.button;
-
-import org.geysermc.cumulus.util.FormImage;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import java.util.List;
-
-public class CommandButton extends Button {
-
-    private final List<String> commands;
-
-    public CommandButton(@Nonnull List<String> commands, @Nonnull String text) {
-        super(text);
-        this.commands = commands;
-    }
-    public CommandButton(@Nonnull List<String> commands, @Nonnull String text, @Nullable FormImage image) {
-        super(text, image);
-        this.commands = commands;
-    }
-    public CommandButton(@Nonnull List<String> commands, @Nonnull String text, @Nonnull FormImage.Type type, @Nonnull String data) {
-        super(text, type, data);
-        this.commands = commands;
-    }
-
-    public List<String> getCommands() {
-        return commands;
-    }
-}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/ServerButton.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/ServerButton.java
deleted file mode 100644
index 1dab57b..0000000
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/button/ServerButton.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package dev.projectg.geyserhub.module.menu.bedrock.button;
-
-import org.geysermc.cumulus.util.FormImage;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-public class ServerButton extends Button {
-
-    private final String serverName;
-
-    public ServerButton(@Nonnull String serverName, @Nonnull String text) {
-        super(text);
-        this.serverName = serverName;
-    }
-
-    public ServerButton(@Nonnull String serverName, @Nonnull String text, @Nullable FormImage image) {
-        super(text, image);
-        this.serverName = serverName;
-    }
-
-    public ServerButton(@Nonnull String serverName, @Nonnull String text, @Nonnull FormImage.Type type, @Nonnull String data) {
-        super(text, type, data);
-        this.serverName = serverName;
-    }
-
-
-    public String getServerName() {
-        return serverName;
-    }
-}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 8b47411..318adc4 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -58,6 +58,8 @@ Bedrock-Selector:
         survival:
           Button-Text: "Survival: %bungee_survival% players"
           ImageURL: "https://www.digminecraft.com/block_recipes/images/blue_concrete.png"
+          Commands:
+            - "Survival is currently in development, please report any bugs to staff!"
           Server: "survival"
         commandOne:
           Button-Text: "Minigames"

From 26986d097d5cd0bf27eeb3c2a2a30e4b4cb0abc9 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Tue, 15 Jun 2021 20:34:40 -0400
Subject: [PATCH 15/74] some renames and add javadoc

---
 .../dev/projectg/geyserhub/GeyserHubMain.java |  4 +-
 .../geyserhub/command/GeyserHubCommand.java   | 10 ++---
 .../geyserhub/module/menu/Button.java         | 38 ++++++++++++++++++-
 ...rockMenu.java => BedrockFormRegistry.java} | 14 ++++---
 .../menu/bedrock/BedrockMenuListeners.java    |  2 +-
 5 files changed, 54 insertions(+), 14 deletions(-)
 rename src/main/java/dev/projectg/geyserhub/module/menu/bedrock/{BedrockMenu.java => BedrockFormRegistry.java} (93%)

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 119c8cb..0959d91 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -4,7 +4,7 @@
 import dev.projectg.geyserhub.module.menu.CommonMenuListeners;
 import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenuListeners;
 import dev.projectg.geyserhub.module.menu.java.JavaMenuListeners;
-import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenu;
+import dev.projectg.geyserhub.module.menu.bedrock.BedrockFormRegistry;
 import dev.projectg.geyserhub.module.message.Broadcast;
 import dev.projectg.geyserhub.module.message.MessageJoin;
 import dev.projectg.geyserhub.module.Placeholders;
@@ -56,7 +56,7 @@ public void onEnable() {
         // Bungee channel for selector
         getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
 
-        new BedrockMenu();
+        new BedrockFormRegistry();
 
         // todo: and add command suggestions/completions, help pages that only shows available commands
         Objects.requireNonNull(getCommand("ghub")).setExecutor(new GeyserHubCommand());
diff --git a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
index ef133b6..7f2b37d 100644
--- a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
+++ b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
@@ -2,7 +2,7 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
-import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenu;
+import dev.projectg.geyserhub.module.menu.bedrock.BedrockFormRegistry;
 import dev.projectg.geyserhub.module.menu.java.JavaMenu;
 import org.bukkit.command.Command;
 import org.bukkit.command.CommandExecutor;
@@ -35,7 +35,7 @@ public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command
 
         if (args.length == 0) {
             // send the default form, help if console
-            sendForm(commandSender, BedrockMenu.DEFAULT);
+            sendForm(commandSender, BedrockFormRegistry.DEFAULT);
             return true;
         }
 
@@ -92,9 +92,9 @@ private void sendForm(@Nonnull CommandSender commandSender, @Nonnull String form
             Player player = (Player) commandSender;
             UUID uuid = player.getUniqueId();
             if (FloodgateApi.getInstance().isFloodgateId(uuid)) {
-                if (BedrockMenu.getInstance().isEnabled()) {
-                    if (BedrockMenu.getInstance().getFormNames().contains(formName)) {
-                        BedrockMenu.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(uuid), formName);
+                if (BedrockFormRegistry.getInstance().isEnabled()) {
+                    if (BedrockFormRegistry.getInstance().getFormNames().contains(formName)) {
+                        BedrockFormRegistry.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(uuid), formName);
                     } else {
                         player.sendMessage("Sorry, that form doesn't exist! Specify a form with \"/ghub form <form>\"");
                     }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/Button.java b/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
index 779efc7..27cf194 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
@@ -18,33 +18,69 @@ public class Button {
     private List<String> commands;
     private String server;
 
+    /**
+     * Create a button.
+     * @param text the text of the button
+     */
     public Button(@Nonnull String text) {
         this.text = Objects.requireNonNull(text);
     }
 
-
+    /**
+     * Set the text of the button.
+     * @param text the new text
+     * @return the same Button instance
+     */
     public Button setText(@Nonnull String text) {
         this.text = Objects.requireNonNull(text);
         return this;
     }
 
+    /**
+     * Set the image of the button.
+     * @param image the image
+     * @return the same Button instance
+     */
     public Button setImage(@Nullable FormImage image) {
         this.image = image;
         return this;
     }
+
+    /**
+     * Set the image of the button.
+     * @param type the type of image
+     * @param data the image data
+     * @return the same Button instance
+     */
     public Button setImage(@Nonnull FormImage.Type type, @Nonnull String data) {
         this.image = FormImage.of(Objects.requireNonNull(type), Objects.requireNonNull(data));
         return this;
     }
+
+    /**
+     * set the commands list.
+     * @param commands the commands list
+     * @return the same Button instance
+     */
     public Button setCommands(@Nullable List<String> commands) {
         this.commands = commands;
         return this;
     }
+
+    /**
+     * Set the server name.
+     * @param server the server name
+     * @return the same Button instance
+     */
     public Button setServer(@Nullable String server) {
         this.server = server;
         return this;
     }
 
+    /**
+     * Get the button component based off the text and image off the Button.
+     * @return the button component
+     */
     public ButtonComponent getButtonComponent() {
         return ButtonComponent.of(text, image);
     }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
similarity index 93%
rename from src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java
rename to src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
index ae292ca..f357c98 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
@@ -9,11 +9,15 @@
 import org.geysermc.floodgate.api.player.FloodgatePlayer;
 
 import javax.annotation.Nonnull;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 
-public class BedrockMenu implements Reloadable {
+public class BedrockFormRegistry implements Reloadable {
 
-    private static BedrockMenu INSTANCE;
+    private static BedrockFormRegistry INSTANCE;
     public static final String DEFAULT = "default";
 
     /**
@@ -22,11 +26,11 @@ public class BedrockMenu implements Reloadable {
     private boolean isEnabled = false;
     private final Map<String, BedrockForm> enabledForms = new HashMap<>();
 
-    public static BedrockMenu getInstance() {
+    public static BedrockFormRegistry getInstance() {
         return INSTANCE;
     }
 
-    public BedrockMenu() {
+    public BedrockFormRegistry() {
         ReloadableRegistry.registerReloadable(this);
         load();
         INSTANCE = this;
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
index a2cce7f..33c7e1d 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
@@ -18,7 +18,7 @@ public void onInteract(PlayerInteractEvent event) {
         if (player.getInventory().getItemInMainHand().isSimilar(AccessItem.getItem())) {
             if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
                 if (FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {
-                    BedrockMenu.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()), BedrockMenu.DEFAULT);
+                    BedrockFormRegistry.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()), BedrockFormRegistry.DEFAULT);
                 } else {
                     JavaMenu.openMenu(player, GeyserHubMain.getInstance().getConfig());
                 }

From 527610c5923a5feb6d6a97a4d9be6f3f69f1ce67 Mon Sep 17 00:00:00 2001
From: Jens Collaert <jenscollaertprive@hotmail.com>
Date: Wed, 16 Jun 2021 16:57:04 +0200
Subject: [PATCH 16/74] Config rework

---
 .../dev/projectg/geyserhub/GeyserHubMain.java | 53 +++---------
 .../projectg/geyserhub/SelectorLogger.java    | 15 ++--
 .../menu/bedrock/BedrockFormRegistry.java     |  3 +-
 .../reloadable/ReloadableRegistry.java        | 12 +--
 .../geyserhub/utils/ConfigManager.java        | 85 +++++++++++++++++++
 src/main/resources/config.yml                 | 80 -----------------
 src/main/resources/selector.yml               | 81 ++++++++++++++++++
 7 files changed, 192 insertions(+), 137 deletions(-)
 create mode 100644 src/main/java/dev/projectg/geyserhub/utils/ConfigManager.java
 create mode 100644 src/main/resources/selector.yml

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 0959d91..d9f9c37 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -1,6 +1,7 @@
 package dev.projectg.geyserhub;
 
 import dev.projectg.geyserhub.command.GeyserHubCommand;
+import dev.projectg.geyserhub.utils.ConfigManager;
 import dev.projectg.geyserhub.module.menu.CommonMenuListeners;
 import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenuListeners;
 import dev.projectg.geyserhub.module.menu.java.JavaMenuListeners;
@@ -14,20 +15,16 @@
 import dev.projectg.geyserhub.utils.Utils;
 import dev.projectg.geyserhub.utils.bstats.Metrics;
 import org.bukkit.Bukkit;
-import org.bukkit.configuration.InvalidConfigurationException;
-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.Objects;
 import java.util.Properties;
 
 public class GeyserHubMain extends JavaPlugin {
     private static GeyserHubMain plugin;
-    private SelectorLogger logger;
-
+    public static SelectorLogger logger;
+    public static final int selectorConfigVersion = 1;
     public static final int configVersion = 4;
 
     @Override
@@ -40,7 +37,7 @@ public void onEnable() {
         try {
             Properties gitProperties = new Properties();
             gitProperties.load(Utils.getResource("git.properties"));
-            logger.info("Branch: " + gitProperties.getProperty("git.branch", "Unknown") + ", Commit: " + gitProperties.getProperty("git.commit.id.abbrev", "Unknown"));
+            SelectorLogger.info("Branch: " + gitProperties.getProperty("git.branch", "Unknown") + ", Commit: " + gitProperties.getProperty("git.commit.id.abbrev", "Unknown"));
         } catch (IOException e) {
             logger.warn("Unable to load resource: git.properties");
             if (logger.isDebug()) {
@@ -48,8 +45,12 @@ public void onEnable() {
             }
         }
 
-        if (!loadConfiguration()) {
-            logger.severe("Disabling due to configuration error. Fix the formatting or regenerate a new one");
+        if (!ConfigManager.loadDefaultConfiguration()) {
+            SelectorLogger.severe("Disabling due to configuration error. Fix the formatting or regenerate a new config file");
+            return;
+        }
+        if (!ConfigManager.loadSelectorConfiguration()) {
+            SelectorLogger.severe("Disabling due to configuration error. Fix the formatting or regenerate a new selector file");
             return;
         }
 
@@ -82,40 +83,6 @@ public void onEnable() {
     public void onDisable() {
     }
 
-    public boolean loadConfiguration() {
-        File configFile = new File(getDataFolder(), "config.yml");
-        if (!configFile.exists()) {
-            try {
-                configFile.getParentFile().mkdirs();
-            } catch (SecurityException e) {
-                e.printStackTrace();
-                return false;
-            }
-            saveResource("config.yml", false);
-        }
-        // Get the config but don't actually load it into the main memory config
-        FileConfiguration config = new YamlConfiguration();
-        try {
-            config.load(configFile);
-            if (!config.contains("Config-Version", true)) {
-                logger.severe("Config-Version does not exist!");
-                return false;
-            } else if (!config.isInt("Config-Version")) {
-                logger.severe("Config-Version is not an integer!");
-                return false;
-            } else if (!(config.getInt("Config-Version") == configVersion)) {
-                logger.severe("Mismatched config version! Generate a new config and migrate your settings!");
-                return false;
-            } else {
-                reloadConfig();
-                logger.debug("Loaded configuration successfully");
-                return true;
-            }
-        } catch (IOException | InvalidConfigurationException e) {
-            e.printStackTrace();
-            return false;
-        }
-    }
     public void initializeScoreboard() {
         Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
             try {
diff --git a/src/main/java/dev/projectg/geyserhub/SelectorLogger.java b/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
index 80f18a6..9a3ce49 100644
--- a/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
+++ b/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
@@ -6,9 +6,10 @@
 public class SelectorLogger implements Reloadable {
 
     private static final SelectorLogger LOGGER = new SelectorLogger(GeyserHubMain.getInstance());
+    private static String message;
 
     private final GeyserHubMain plugin;
-    private boolean debug;
+    private static boolean debug;
 
     public static SelectorLogger getLogger() {
         return LOGGER;
@@ -20,18 +21,18 @@ private SelectorLogger(GeyserHubMain plugin) {
         ReloadableRegistry.registerReloadable(this);
     }
 
-    public void info(String message) {
-        plugin.getLogger().info(message);
+    public static void info(String message) {
+        SelectorLogger.info(message);
     }
     public void warn(String message) {
         plugin.getLogger().warning(message);
     }
-    public void severe(String message) {
-        plugin.getLogger().severe(message);
+    public static void severe(String message) {
+        SelectorLogger.severe(message);
     }
-    public void debug(String message) {
+    public static void debug(String message) {
         if (debug) {
-            plugin.getLogger().info(message);
+            SelectorLogger.info(message);
         }
     }
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
index f357c98..fd410d0 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
@@ -4,6 +4,7 @@
 import dev.projectg.geyserhub.reloadable.Reloadable;
 import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
 import dev.projectg.geyserhub.SelectorLogger;
+import dev.projectg.geyserhub.utils.ConfigManager;
 import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.geysermc.floodgate.api.player.FloodgatePlayer;
@@ -37,7 +38,7 @@ public BedrockFormRegistry() {
     }
 
     private void load() {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfig();
+        FileConfiguration config = ConfigManager.get();
         SelectorLogger logger = SelectorLogger.getLogger();
 
         enabledForms.clear();
diff --git a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
index 90cc379..41b7830 100644
--- a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
@@ -1,7 +1,7 @@
 package dev.projectg.geyserhub.reloadable;
 
-import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
+import dev.projectg.geyserhub.utils.ConfigManager;
 import org.bukkit.ChatColor;
 
 import javax.annotation.Nonnull;
@@ -33,22 +33,22 @@ public static Reloadable[] getRegisteredReloadables() {
     public static boolean reloadAll() {
         SelectorLogger logger = SelectorLogger.getLogger();
 
-        if (GeyserHubMain.getInstance().loadConfiguration()) {
-            logger.info("Reloaded the configuration, reloading modules...");
+        if (ConfigManager.loadDefaultConfiguration()) {
+            SelectorLogger.info("Reloaded the configuration, reloading modules...");
         } else {
-            logger.severe(ChatColor.RED + "Failed to reload the configuration!");
+            SelectorLogger.severe(ChatColor.RED + "Failed to reload the configuration!");
             return false;
         }
 
         boolean success = true;
         for (Reloadable reloadable : ReloadableRegistry.getRegisteredReloadables()) {
             if (!reloadable.reload()) {
-                logger.severe(ChatColor.RED + "Failed to reload class: " + ChatColor.RESET + reloadable.getClass().toString());
+                SelectorLogger.severe(ChatColor.RED + "Failed to reload class: " + ChatColor.RESET + reloadable.getClass().toString());
                 success = false;
             }
         }
 
-        logger.info("Finished reload.");
+        SelectorLogger.info("Finished reload.");
         return success;
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/utils/ConfigManager.java b/src/main/java/dev/projectg/geyserhub/utils/ConfigManager.java
new file mode 100644
index 0000000..9d055c9
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/utils/ConfigManager.java
@@ -0,0 +1,85 @@
+package dev.projectg.geyserhub.utils;
+
+import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.SelectorLogger;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.io.IOException;
+
+import static dev.projectg.geyserhub.GeyserHubMain.configVersion;
+import static dev.projectg.geyserhub.GeyserHubMain.selectorConfigVersion;
+
+public class ConfigManager {
+
+    private static FileConfiguration selectorConfig;
+
+    public static boolean loadDefaultConfiguration() {
+        File configFile = new File(GeyserHubMain.getInstance().getDataFolder(), "config.yml");
+        if (!configFile.exists()) {
+            try {
+                configFile.getParentFile().mkdirs();
+            } catch (SecurityException e) {
+                e.printStackTrace();
+                return false;
+            }
+            GeyserHubMain.getInstance().saveResource("config.yml", false);
+        }
+        // Get the config but don't actually load it into the main memory config
+        FileConfiguration config = new YamlConfiguration();
+        try {
+            config.load(configFile);
+            if (!config.contains("Config-Version", true)) {
+                SelectorLogger.severe("Config-Version does not exist!");
+                return false;
+            } else if (!config.isInt("Config-Version")) {
+                SelectorLogger.severe("Config-Version is not an integer!");
+                return false;
+            } else if (!(config.getInt("Config-Version") == configVersion)) {
+                SelectorLogger.severe("Mismatched config version! Generate a new config and migrate your settings!");
+                return false;
+            } else {
+                GeyserHubMain.getInstance().reloadConfig();
+                SelectorLogger.debug("Loaded configuration successfully");
+                return true;
+            }
+        } catch (IOException | InvalidConfigurationException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+    public static boolean loadSelectorConfiguration() {
+        File file = new File(GeyserHubMain.getInstance().getDataFolder(), "selector.yml");
+        if (!file.exists()) {
+            try {
+                file.getParentFile().mkdirs();
+            } catch (SecurityException e) {
+                e.printStackTrace();
+                return false;
+            }
+            GeyserHubMain.getInstance().saveResource("selector.yml", false);
+        }
+        // Get the config but don't actually load it into the main memory config
+        FileConfiguration selectorConfig = new YamlConfiguration();
+        ConfigManager.selectorConfig = YamlConfiguration.loadConfiguration(file);
+        if (!selectorConfig.contains("Config-Version", true)) {
+            SelectorLogger.severe("Config-Version does not exist!");
+            return false;
+        } else if (!selectorConfig.isInt("Config-Version")) {
+            SelectorLogger.severe("Config-Version is not an integer!");
+            return false;
+        } else if (!(selectorConfig.getInt("Config-Version") == selectorConfigVersion)) {
+            SelectorLogger.severe("Mismatched config version! Generate a new config and migrate your settings!");
+            return false;
+        } else {
+            GeyserHubMain.getInstance().reloadConfig();
+            SelectorLogger.debug("Loaded configuration successfully");
+            return true;
+        }
+    }
+    public static FileConfiguration get(){
+        return selectorConfig;
+    }
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 318adc4..52bf443 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -3,86 +3,6 @@
 # Toggle debug logging
 Enable-Debug: false
 
-# Item to access the default menu
-Selector-Item:
-  Material: COMPASS
-  Name: "&6Server Selector"
-  Lore:
-    - "Right click me!"
-  Slot: 4
-
-  # Gives the player the item which they can use to open the default form
-  Join: true
-  # Stop the player from dropping the item
-  Allow-Drop: false
-  # Destroy the compass if the player drops it
-  Destroy-Dropped: true
-  # Stop the player from moving the compass in their inventory
-  Allow-Move: false
-
-
-# Please see our readme for information on configuring this section.
-# https://github.com/ProjectG-Plugins/GeyserServerSelector#Configuration
-Java-Selector:
-  Enable: true
-
-  Title: "Server Selector"
-  Size: 9
-
-  Servers:
-    lobby:
-      Display-Name: "Lobby"
-      Material: Diamond
-      Lore:
-        - "&2Online players: %bungee_lobby%"
-      Slot: 2
-    survival:
-      Display-Name: "Survival"
-      Material: Emerald
-      Lore:
-        - "&2online players: %bungee_survival%"
-      Slot: 6
-
-Bedrock-Selector:
-  Enable: true
-
-  Forms:
-    default:
-      Title: "Server Selector"
-      Content: "Click on the server button of choice."
-      Buttons:
-        lobby:
-          Button-Text: "Server Lobby: %bungee_lobby% players"
-          ImageURL: "https://www.digminecraft.com/block_recipes/images/cyan_concrete.png"
-          Server: "lobby"
-        survival:
-          Button-Text: "Survival: %bungee_survival% players"
-          ImageURL: "https://www.digminecraft.com/block_recipes/images/blue_concrete.png"
-          Commands:
-            - "Survival is currently in development, please report any bugs to staff!"
-          Server: "survival"
-        commandOne:
-          Button-Text: "Minigames"
-          Image-URL: "https://www.digminecraft.com/weapon_recipes/images/diamond_sword.png"
-          Commands:
-            - "execute as %player_name% run ghub form minigames"
-        commandTwo:
-          Button-Text: "Spawn"
-          ImageURL: "https://www.digminecraft.com/decoration_recipes/images/lodestone.png"
-          Commands:
-            - "execute as %player_name% run spawn"
-    minigames:
-      Title: "Minigames"
-      Content: "Click on a minigame of choice."
-      Buttons:
-        spleef:
-          Button-Text: "Spleef"
-          ImageURL: "https://www.digminecraft.com/materials/images/snowball.png"
-          Server: "spleef"
-        hideseek:
-          Button-Text: "Hide & Seek"
-          Server: "hideseek"
-
 # Join and leave messages. Supports papi placeholders.
 Join-Message:
   Enable: true
diff --git a/src/main/resources/selector.yml b/src/main/resources/selector.yml
new file mode 100644
index 0000000..ba93ad8
--- /dev/null
+++ b/src/main/resources/selector.yml
@@ -0,0 +1,81 @@
+# Item to access the default menu
+Selector-Item:
+  Material: COMPASS
+  Name: "&6Server Selector"
+  Lore:
+    - "Right click me!"
+  Slot: 4
+
+  # Gives the player the item which they can use to open the default form
+  Join: true
+  # Stop the player from dropping the item
+  Allow-Drop: false
+  # Destroy the compass if the player drops it
+  Destroy-Dropped: true
+  # Stop the player from moving the compass in their inventory
+  Allow-Move: false
+
+# Please see our readme for information on configuring this section.
+# https://github.com/ProjectG-Plugins/GeyserServerSelector#Configuration
+Java-Selector:
+  Enable: true
+
+  Title: "Server Selector"
+  Size: 9
+
+  Servers:
+    lobby:
+      Display-Name: "Lobby"
+      Material: Diamond
+      Lore:
+        - "&2Online players: %bungee_lobby%"
+      Slot: 2
+    survival:
+      Display-Name: "Survival"
+      Material: Emerald
+      Lore:
+        - "&2online players: %bungee_survival%"
+      Slot: 6
+
+Bedrock-Selector:
+  Enable: true
+
+  Forms:
+    default:
+      Title: "Server Selector"
+      Content: "Click on the server button of choice."
+      Buttons:
+        lobby:
+          Button-Text: "Server Lobby: %bungee_lobby% players"
+          ImageURL: "https://www.digminecraft.com/block_recipes/images/cyan_concrete.png"
+          Server: "lobby"
+        survival:
+          Button-Text: "Survival: %bungee_survival% players"
+          ImageURL: "https://www.digminecraft.com/block_recipes/images/blue_concrete.png"
+          Commands:
+            - "Survival is currently in development, please report any bugs to staff!"
+          Server: "survival"
+        commandOne:
+          Button-Text: "Minigames"
+          Image-URL: "https://www.digminecraft.com/weapon_recipes/images/diamond_sword.png"
+          Commands:
+            - "execute as %player_name% run ghub form minigames"
+        commandTwo:
+          Button-Text: "Spawn"
+          ImageURL: "https://www.digminecraft.com/decoration_recipes/images/lodestone.png"
+          Commands:
+            - "execute as %player_name% run spawn"
+    minigames:
+      Title: "Minigames"
+      Content: "Click on a minigame of choice."
+      Buttons:
+        spleef:
+          Button-Text: "Spleef"
+          ImageURL: "https://www.digminecraft.com/materials/images/snowball.png"
+          Server: "spleef"
+        hideseek:
+          Button-Text: "Hide & Seek"
+          Server: "hideseek"
+
+# Don't touch this
+Config-Version: 1
\ No newline at end of file

From 8daef5e1b437a99cdd250deab670727ef90528d0 Mon Sep 17 00:00:00 2001
From: Jens Collaert <jenscollaertprive@hotmail.com>
Date: Wed, 16 Jun 2021 17:29:36 +0200
Subject: [PATCH 17/74] Fixed static loggers still static problem in loggers
 class

---
 .../dev/projectg/geyserhub/SelectorLogger.java |  2 --
 .../geyserhub/module/menu/AccessItem.java      |  2 +-
 .../module/menu/bedrock/BedrockForm.java       | 18 +++++++++---------
 .../menu/bedrock/BedrockFormRegistry.java      |  4 ++--
 .../geyserhub/module/menu/java/JavaMenu.java   |  2 +-
 .../module/menu/java/JavaMenuListeners.java    |  2 +-
 .../geyserhub/module/message/Broadcast.java    |  4 ++--
 .../module/teleporter/JoinTeleporter.java      | 10 +++++-----
 .../reloadable/ReloadableRegistry.java         |  9 +++++++--
 9 files changed, 28 insertions(+), 25 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/SelectorLogger.java b/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
index 9a3ce49..a579b01 100644
--- a/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
+++ b/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
@@ -7,10 +7,8 @@ public class SelectorLogger implements Reloadable {
 
     private static final SelectorLogger LOGGER = new SelectorLogger(GeyserHubMain.getInstance());
     private static String message;
-
     private final GeyserHubMain plugin;
     private static boolean debug;
-
     public static SelectorLogger getLogger() {
         return LOGGER;
     }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
index 8a607c0..40199d6 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
@@ -69,7 +69,7 @@ public class AccessItem {
         // Set the meta and set the field
         item.setItemMeta(meta);
         ACCESS_ITEM = item;
-        SelectorLogger.getLogger().debug("Created and set the access item from the configuration.");
+        SelectorLogger.debug("Created and set the access item from the configuration.");
     }
 
     public static ItemStack getItem() {
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index b985960..368e2ae 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -53,7 +53,7 @@ private boolean load(@Nonnull ConfigurationSection configSection) {
         String title = configSection.getString("Title");
         String content = configSection.getString("Content");
         if (title == null || content == null) {
-            logger.severe("Value of Bedrock-Selector.Title or Bedrock-Selector.Content has no value in the config for form: "  + configSection.getName() + "! Failed to create the bedrock selector form.");
+            SelectorLogger.severe("Value of Bedrock-Selector.Title or Bedrock-Selector.Content has no value in the config for form: "  + configSection.getName() + "! Failed to create the bedrock selector form.");
             return false;
         }
 
@@ -69,7 +69,7 @@ private boolean load(@Nonnull ConfigurationSection configSection) {
             logger.warn("Failed to create any valid buttons of form: " + configSection.getName() + "! All listed buttons have a malformed section!");
             return false;
         } else {
-            logger.debug("Finished adding buttons to form: " + configSection.getName());
+            SelectorLogger.debug("Finished adding buttons to form: " + configSection.getName());
         }
 
         // Only set everything once it has been validated
@@ -96,7 +96,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
         } else {
             formName = parent.getName();
         }
-        logger.debug("Getting buttons for form: " + formName);
+        SelectorLogger.debug("Getting buttons for form: " + formName);
 
         // Get all the defined buttons in the buttons section
         Set<String> allButtonIds = configSection.getKeys(false);
@@ -118,7 +118,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
             if (buttonInfo.contains("Button-Text", true) && buttonInfo.isString("Button-Text")) {
                 String buttonText = buttonInfo.getString("Button-Text");
                 Objects.requireNonNull(buttonText);
-                logger.debug(buttonId + " has Button-Text: " + buttonText);
+                SelectorLogger.debug(buttonId + " has Button-Text: " + buttonText);
 
                 // Add image if specified
                 FormImage image = null;
@@ -126,7 +126,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
                     String imageURL = buttonInfo.getString("ImageURL");
                     Objects.requireNonNull(imageURL);
                     image = FormImage.of(FormImage.Type.URL, imageURL);
-                    logger.debug(buttonId + " contains image with URL: " + image);
+                    SelectorLogger.debug(buttonId + " contains image with URL: " + image);
                 }
 
                 // Add commands if specified
@@ -137,7 +137,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
                         logger.warn(buttonId + " contains commands list but the list was empty.");
                     } else {
                         commands = potentialCommands;
-                        logger.debug(buttonId + " contains commands: " + commands);
+                        SelectorLogger.debug(buttonId + " contains commands: " + commands);
                     }
                 }
 
@@ -146,7 +146,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
                 if (buttonInfo.contains("Server") && buttonInfo.isString("Server")) {
                     serverName = buttonInfo.getString("Server");
                     Objects.requireNonNull(serverName);
-                    logger.debug(buttonId + " contains BungeeCord target server: " + serverName);
+                    SelectorLogger.debug(buttonId + " contains BungeeCord target server: " + serverName);
                 }
 
                 compiledButtons.add(
@@ -155,7 +155,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
                         .setCommands(commands)
                         .setServer(serverName));
 
-                logger.debug(buttonId + " was successfully added.");
+                SelectorLogger.debug(buttonId + " was successfully added.");
             } else {
                 logger.warn(buttonId + " does not contain a valid Button-Text value, not adding.");
             }
@@ -173,7 +173,7 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
 
         Player player = Bukkit.getServer().getPlayer(floodgatePlayer.getCorrectUniqueId());
         if (player == null) {
-            logger.severe("Unable to find a Bukkit Player for the given Floodgate Player: " + floodgatePlayer.getCorrectUniqueId().toString());
+            SelectorLogger.severe("Unable to find a Bukkit Player for the given Floodgate Player: " + floodgatePlayer.getCorrectUniqueId().toString());
             return;
         }
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
index fd410d0..f779c4f 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
@@ -77,7 +77,7 @@ private void load() {
                             logger.warn("Failed to load ALL bedrock forms, due to configuration error.");
                             return;
                         } else {
-                            logger.info("Valid Bedrock forms are: " + enabledForms.keySet());
+                            SelectorLogger.info("Valid Bedrock forms are: " + enabledForms.keySet());
                         }
 
                         if (!containsDefault) {
@@ -85,7 +85,7 @@ private void load() {
                         }
                     }
                 } else {
-                    logger.debug("Not enabling bedrock forms because it is disabled in the config.");
+                    SelectorLogger.debug("Not enabling bedrock forms because it is disabled in the config.");
                 }
             } else {
                 logger.warn("Not enabling bedrock forms because the Enable value is not present in the config.");
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index 5390f16..120ae01 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -78,7 +78,7 @@ public static void openMenu(@Nonnull Player player, @Nonnull FileConfiguration c
                     List<String> withPlaceholders = PlaceholderAPI.setPlaceholders(player, lore);
                     itemMeta.setLore(withPlaceholders);
                 } else {
-                    logger.debug("Server entry with name \"" + serverName + "\" does not have a valid lore list");
+                    SelectorLogger.debug("Server entry with name \"" + serverName + "\" does not have a valid lore list");
                 }
                 serverStack.setItemMeta(itemMeta);
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
index 6145e37..b3e4605 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
@@ -36,7 +36,7 @@ public void onInventoryClick(InventoryClickEvent event) {
                 player.sendPluginMessage(GeyserHubMain.getInstance(), "BungeeCord", b.toByteArray());
                 player.sendMessage(ChatColor.DARK_AQUA + "Trying to send you to: " + ChatColor.GREEN + bungeeName);
             } catch (IOException er) {
-                SelectorLogger.getLogger().severe("Failed to send a plugin message to Bungeecord!");
+                SelectorLogger.severe("Failed to send a plugin message to Bungeecord!");
             }
             event.setCancelled(true);
         } catch (Exception ignored) {
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
index d448877..c76f4c7 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
@@ -18,7 +18,7 @@ public static void startBroadcastTimer(BukkitScheduler scheduler) {
             if (GeyserHubMain.getInstance().getConfig().getBoolean("Broadcasts.Enable", false)) {
                 ConfigurationSection parentSection = GeyserHubMain.getInstance().getConfig().getConfigurationSection("Broadcasts.Messages");
                 if (parentSection == null) {
-                    SelectorLogger.getLogger().severe("Broadcasts.Messages configuration section is malformed, unable to send.");
+                    SelectorLogger.severe("Broadcasts.Messages configuration section is malformed, unable to send.");
                     return;
                 }
 
@@ -31,7 +31,7 @@ public static void startBroadcastTimer(BukkitScheduler scheduler) {
                         }
                     }
                 } else {
-                    SelectorLogger.getLogger().severe("Broadcast with ID " + broadcastId + " has a malformed message list, unable to send.");
+                    SelectorLogger.severe("Broadcast with ID " + broadcastId + " has a malformed message list, unable to send.");
                 }
             }
             startBroadcastTimer(scheduler);
diff --git a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
index 5449bcf..470f48f 100644
--- a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
+++ b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
@@ -49,18 +49,18 @@ private boolean load(@Nonnull FileConfiguration config) {
 
         // Validate all our values
         if (!(section.contains("Enable", true) && section.isBoolean(("Enable")))) {
-            logger.severe("Join-Teleporter config section does not contain a valid Enable value, skipping module!");
+            SelectorLogger.severe("Join-Teleporter config section does not contain a valid Enable value, skipping module!");
             return false;
         }
         if (!(section.contains("World") && section.isString("World"))) {
-            logger.severe("Join-Teleporter config section does not contain a valid World string, skipping module!");
+            SelectorLogger.severe("Join-Teleporter config section does not contain a valid World string, skipping module!");
             return false;
         }
         String worldName = section.getString("World");
         Objects.requireNonNull(worldName);
         World world = Bukkit.getServer().getWorld(worldName);
         if (world == null) {
-            logger.severe("Join-Teleporter.World in the config is not a valid world, skipping module!");
+            SelectorLogger.severe("Join-Teleporter.World in the config is not a valid world, skipping module!");
             return false;
         }
 
@@ -69,7 +69,7 @@ private boolean load(@Nonnull FileConfiguration config) {
             String composedCoords = section.getString("Location");
             Objects.requireNonNull(composedCoords);
             if (!composedCoords.matches(COORDINATE_REGEX)) {
-                logger.severe("Join-Teleporter.Location in the config is not of the format <integer;integer;integer>, skipping module!");
+                SelectorLogger.severe("Join-Teleporter.Location in the config is not of the format <integer;integer;integer>, skipping module!");
                 return false;
             }
 
@@ -85,7 +85,7 @@ private boolean load(@Nonnull FileConfiguration config) {
                 throw new AssertionError("Failed to decompose the following coordinates: " + composedCoords + " -> " + Arrays.toString(coordinates));
             }
         } else {
-            logger.severe("Join-Teleporter config section does not contain a valid Location value!");
+            SelectorLogger.severe("Join-Teleporter config section does not contain a valid Location value!");
             return false;
         }
     }
diff --git a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
index 41b7830..5026bb5 100644
--- a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
@@ -31,10 +31,15 @@ public static Reloadable[] getRegisteredReloadables() {
     }
 
     public static boolean reloadAll() {
-        SelectorLogger logger = SelectorLogger.getLogger();
 
         if (ConfigManager.loadDefaultConfiguration()) {
-            SelectorLogger.info("Reloaded the configuration, reloading modules...");
+            SelectorLogger.info("Reloaded the default configuration, reloading modules...");
+        } else {
+            SelectorLogger.severe(ChatColor.RED + "Failed to reload the configuration!");
+            return false;
+        }
+        if (ConfigManager.loadSelectorConfiguration()) {
+            SelectorLogger.info("Reloaded the Selector configuration, reloading modules...");
         } else {
             SelectorLogger.severe(ChatColor.RED + "Failed to reload the configuration!");
             return false;

From c03eb8728c4b5d659ad1750329c546e11a6ec8e8 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 16 Jun 2021 13:59:09 -0400
Subject: [PATCH 18/74] Revert "Fixed static loggers still static problem in
 loggers class"

This reverts commit 8daef5e1
---
 .../geyserhub/module/menu/AccessItem.java      |  2 +-
 .../module/menu/bedrock/BedrockForm.java       | 18 +++++++++---------
 .../menu/bedrock/BedrockFormRegistry.java      |  4 ++--
 .../geyserhub/module/menu/java/JavaMenu.java   |  2 +-
 .../module/menu/java/JavaMenuListeners.java    |  2 +-
 .../geyserhub/module/message/Broadcast.java    |  4 ++--
 .../module/teleporter/JoinTeleporter.java      | 10 +++++-----
 .../reloadable/ReloadableRegistry.java         |  1 +
 8 files changed, 22 insertions(+), 21 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
index 40199d6..8a607c0 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
@@ -69,7 +69,7 @@ public class AccessItem {
         // Set the meta and set the field
         item.setItemMeta(meta);
         ACCESS_ITEM = item;
-        SelectorLogger.debug("Created and set the access item from the configuration.");
+        SelectorLogger.getLogger().debug("Created and set the access item from the configuration.");
     }
 
     public static ItemStack getItem() {
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index 368e2ae..b985960 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -53,7 +53,7 @@ private boolean load(@Nonnull ConfigurationSection configSection) {
         String title = configSection.getString("Title");
         String content = configSection.getString("Content");
         if (title == null || content == null) {
-            SelectorLogger.severe("Value of Bedrock-Selector.Title or Bedrock-Selector.Content has no value in the config for form: "  + configSection.getName() + "! Failed to create the bedrock selector form.");
+            logger.severe("Value of Bedrock-Selector.Title or Bedrock-Selector.Content has no value in the config for form: "  + configSection.getName() + "! Failed to create the bedrock selector form.");
             return false;
         }
 
@@ -69,7 +69,7 @@ private boolean load(@Nonnull ConfigurationSection configSection) {
             logger.warn("Failed to create any valid buttons of form: " + configSection.getName() + "! All listed buttons have a malformed section!");
             return false;
         } else {
-            SelectorLogger.debug("Finished adding buttons to form: " + configSection.getName());
+            logger.debug("Finished adding buttons to form: " + configSection.getName());
         }
 
         // Only set everything once it has been validated
@@ -96,7 +96,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
         } else {
             formName = parent.getName();
         }
-        SelectorLogger.debug("Getting buttons for form: " + formName);
+        logger.debug("Getting buttons for form: " + formName);
 
         // Get all the defined buttons in the buttons section
         Set<String> allButtonIds = configSection.getKeys(false);
@@ -118,7 +118,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
             if (buttonInfo.contains("Button-Text", true) && buttonInfo.isString("Button-Text")) {
                 String buttonText = buttonInfo.getString("Button-Text");
                 Objects.requireNonNull(buttonText);
-                SelectorLogger.debug(buttonId + " has Button-Text: " + buttonText);
+                logger.debug(buttonId + " has Button-Text: " + buttonText);
 
                 // Add image if specified
                 FormImage image = null;
@@ -126,7 +126,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
                     String imageURL = buttonInfo.getString("ImageURL");
                     Objects.requireNonNull(imageURL);
                     image = FormImage.of(FormImage.Type.URL, imageURL);
-                    SelectorLogger.debug(buttonId + " contains image with URL: " + image);
+                    logger.debug(buttonId + " contains image with URL: " + image);
                 }
 
                 // Add commands if specified
@@ -137,7 +137,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
                         logger.warn(buttonId + " contains commands list but the list was empty.");
                     } else {
                         commands = potentialCommands;
-                        SelectorLogger.debug(buttonId + " contains commands: " + commands);
+                        logger.debug(buttonId + " contains commands: " + commands);
                     }
                 }
 
@@ -146,7 +146,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
                 if (buttonInfo.contains("Server") && buttonInfo.isString("Server")) {
                     serverName = buttonInfo.getString("Server");
                     Objects.requireNonNull(serverName);
-                    SelectorLogger.debug(buttonId + " contains BungeeCord target server: " + serverName);
+                    logger.debug(buttonId + " contains BungeeCord target server: " + serverName);
                 }
 
                 compiledButtons.add(
@@ -155,7 +155,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
                         .setCommands(commands)
                         .setServer(serverName));
 
-                SelectorLogger.debug(buttonId + " was successfully added.");
+                logger.debug(buttonId + " was successfully added.");
             } else {
                 logger.warn(buttonId + " does not contain a valid Button-Text value, not adding.");
             }
@@ -173,7 +173,7 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
 
         Player player = Bukkit.getServer().getPlayer(floodgatePlayer.getCorrectUniqueId());
         if (player == null) {
-            SelectorLogger.severe("Unable to find a Bukkit Player for the given Floodgate Player: " + floodgatePlayer.getCorrectUniqueId().toString());
+            logger.severe("Unable to find a Bukkit Player for the given Floodgate Player: " + floodgatePlayer.getCorrectUniqueId().toString());
             return;
         }
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
index f779c4f..fd410d0 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
@@ -77,7 +77,7 @@ private void load() {
                             logger.warn("Failed to load ALL bedrock forms, due to configuration error.");
                             return;
                         } else {
-                            SelectorLogger.info("Valid Bedrock forms are: " + enabledForms.keySet());
+                            logger.info("Valid Bedrock forms are: " + enabledForms.keySet());
                         }
 
                         if (!containsDefault) {
@@ -85,7 +85,7 @@ private void load() {
                         }
                     }
                 } else {
-                    SelectorLogger.debug("Not enabling bedrock forms because it is disabled in the config.");
+                    logger.debug("Not enabling bedrock forms because it is disabled in the config.");
                 }
             } else {
                 logger.warn("Not enabling bedrock forms because the Enable value is not present in the config.");
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index 120ae01..5390f16 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -78,7 +78,7 @@ public static void openMenu(@Nonnull Player player, @Nonnull FileConfiguration c
                     List<String> withPlaceholders = PlaceholderAPI.setPlaceholders(player, lore);
                     itemMeta.setLore(withPlaceholders);
                 } else {
-                    SelectorLogger.debug("Server entry with name \"" + serverName + "\" does not have a valid lore list");
+                    logger.debug("Server entry with name \"" + serverName + "\" does not have a valid lore list");
                 }
                 serverStack.setItemMeta(itemMeta);
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
index b3e4605..6145e37 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
@@ -36,7 +36,7 @@ public void onInventoryClick(InventoryClickEvent event) {
                 player.sendPluginMessage(GeyserHubMain.getInstance(), "BungeeCord", b.toByteArray());
                 player.sendMessage(ChatColor.DARK_AQUA + "Trying to send you to: " + ChatColor.GREEN + bungeeName);
             } catch (IOException er) {
-                SelectorLogger.severe("Failed to send a plugin message to Bungeecord!");
+                SelectorLogger.getLogger().severe("Failed to send a plugin message to Bungeecord!");
             }
             event.setCancelled(true);
         } catch (Exception ignored) {
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
index c76f4c7..d448877 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
@@ -18,7 +18,7 @@ public static void startBroadcastTimer(BukkitScheduler scheduler) {
             if (GeyserHubMain.getInstance().getConfig().getBoolean("Broadcasts.Enable", false)) {
                 ConfigurationSection parentSection = GeyserHubMain.getInstance().getConfig().getConfigurationSection("Broadcasts.Messages");
                 if (parentSection == null) {
-                    SelectorLogger.severe("Broadcasts.Messages configuration section is malformed, unable to send.");
+                    SelectorLogger.getLogger().severe("Broadcasts.Messages configuration section is malformed, unable to send.");
                     return;
                 }
 
@@ -31,7 +31,7 @@ public static void startBroadcastTimer(BukkitScheduler scheduler) {
                         }
                     }
                 } else {
-                    SelectorLogger.severe("Broadcast with ID " + broadcastId + " has a malformed message list, unable to send.");
+                    SelectorLogger.getLogger().severe("Broadcast with ID " + broadcastId + " has a malformed message list, unable to send.");
                 }
             }
             startBroadcastTimer(scheduler);
diff --git a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
index 470f48f..5449bcf 100644
--- a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
+++ b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
@@ -49,18 +49,18 @@ private boolean load(@Nonnull FileConfiguration config) {
 
         // Validate all our values
         if (!(section.contains("Enable", true) && section.isBoolean(("Enable")))) {
-            SelectorLogger.severe("Join-Teleporter config section does not contain a valid Enable value, skipping module!");
+            logger.severe("Join-Teleporter config section does not contain a valid Enable value, skipping module!");
             return false;
         }
         if (!(section.contains("World") && section.isString("World"))) {
-            SelectorLogger.severe("Join-Teleporter config section does not contain a valid World string, skipping module!");
+            logger.severe("Join-Teleporter config section does not contain a valid World string, skipping module!");
             return false;
         }
         String worldName = section.getString("World");
         Objects.requireNonNull(worldName);
         World world = Bukkit.getServer().getWorld(worldName);
         if (world == null) {
-            SelectorLogger.severe("Join-Teleporter.World in the config is not a valid world, skipping module!");
+            logger.severe("Join-Teleporter.World in the config is not a valid world, skipping module!");
             return false;
         }
 
@@ -69,7 +69,7 @@ private boolean load(@Nonnull FileConfiguration config) {
             String composedCoords = section.getString("Location");
             Objects.requireNonNull(composedCoords);
             if (!composedCoords.matches(COORDINATE_REGEX)) {
-                SelectorLogger.severe("Join-Teleporter.Location in the config is not of the format <integer;integer;integer>, skipping module!");
+                logger.severe("Join-Teleporter.Location in the config is not of the format <integer;integer;integer>, skipping module!");
                 return false;
             }
 
@@ -85,7 +85,7 @@ private boolean load(@Nonnull FileConfiguration config) {
                 throw new AssertionError("Failed to decompose the following coordinates: " + composedCoords + " -> " + Arrays.toString(coordinates));
             }
         } else {
-            SelectorLogger.severe("Join-Teleporter config section does not contain a valid Location value!");
+            logger.severe("Join-Teleporter config section does not contain a valid Location value!");
             return false;
         }
     }
diff --git a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
index 5026bb5..8e49914 100644
--- a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
@@ -31,6 +31,7 @@ public static Reloadable[] getRegisteredReloadables() {
     }
 
     public static boolean reloadAll() {
+        SelectorLogger logger = SelectorLogger.getLogger();
 
         if (ConfigManager.loadDefaultConfiguration()) {
             SelectorLogger.info("Reloaded the default configuration, reloading modules...");

From 5a0b7955f4d798c1a6a02261cc2ae71be4e37225 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 16 Jun 2021 14:49:16 -0400
Subject: [PATCH 19/74] improve config manager, revert changes to logger

---
 .../dev/projectg/geyserhub/ConfigManager.java | 118 ++++++++++++++++++
 .../dev/projectg/geyserhub/GeyserHubMain.java |  22 ++--
 .../projectg/geyserhub/SelectorLogger.java    |  16 +--
 .../menu/bedrock/BedrockFormRegistry.java     |   7 +-
 .../reloadable/ReloadableRegistry.java        |  20 ++-
 .../geyserhub/utils/ConfigManager.java        |  85 -------------
 6 files changed, 151 insertions(+), 117 deletions(-)
 create mode 100644 src/main/java/dev/projectg/geyserhub/ConfigManager.java
 delete mode 100644 src/main/java/dev/projectg/geyserhub/utils/ConfigManager.java

diff --git a/src/main/java/dev/projectg/geyserhub/ConfigManager.java b/src/main/java/dev/projectg/geyserhub/ConfigManager.java
new file mode 100644
index 0000000..91e10b5
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/ConfigManager.java
@@ -0,0 +1,118 @@
+package dev.projectg.geyserhub;
+
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class ConfigManager {
+
+    private static ConfigManager CONFIG_MANAGER;
+
+    private static final int DEFAULT_VERSION = 4;
+    private static final int SELECTOR_VERSION = 1;
+
+    private final Map<String, FileConfiguration> configurations = new HashMap<>();
+
+    public ConfigManager() {
+        if (CONFIG_MANAGER == null) {
+            CONFIG_MANAGER = this;
+        } else {
+            throw new UnsupportedOperationException("Only one instance of ConfigManager is allowed!");
+        }
+    }
+
+    // todo: better code
+
+    public boolean loadDefaultConfiguration() {
+        SelectorLogger logger = SelectorLogger.getLogger();
+
+        File configFile = new File(GeyserHubMain.getInstance().getDataFolder(), "config.yml");
+        if (!configFile.exists()) {
+            try {
+                configFile.getParentFile().mkdirs();
+            } catch (SecurityException e) {
+                e.printStackTrace();
+                return false;
+            }
+            GeyserHubMain.getInstance().saveResource("config.yml", false);
+        }
+        // Get the config but don't actually load it into the main memory config
+        FileConfiguration config = new YamlConfiguration();
+        try {
+            config.load(configFile);
+        } catch (IOException | InvalidConfigurationException e) {
+            e.printStackTrace();
+            return false;
+        }
+        try {
+            config.load(configFile);
+            if (!config.contains("Config-Version", true)) {
+                logger.severe("Config-Version does not exist!");
+                return false;
+            } else if (!config.isInt("Config-Version")) {
+                logger.severe("Config-Version is not an integer!");
+                return false;
+            } else if (config.getInt("Config-Version") != DEFAULT_VERSION) {
+                logger.severe("Mismatched config version! Generate a new config and migrate your settings!");
+                return false;
+            } else {
+                GeyserHubMain.getInstance().reloadConfig();
+                logger.debug("Loaded configuration successfully");
+                return true;
+            }
+        } catch (IOException | InvalidConfigurationException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+    public boolean loadSelectorConfiguration() {
+        SelectorLogger logger = SelectorLogger.getLogger();
+
+        File file = new File(GeyserHubMain.getInstance().getDataFolder(), "selector.yml");
+        if (!file.exists()) {
+            try {
+                file.getParentFile().mkdirs();
+            } catch (SecurityException e) {
+                e.printStackTrace();
+                return false;
+            }
+            GeyserHubMain.getInstance().saveResource("selector.yml", false);
+        }
+        FileConfiguration selectorConfig = new YamlConfiguration();
+        try {
+            selectorConfig.load(file);
+        } catch (IOException | InvalidConfigurationException e) {
+            e.printStackTrace();
+            return false;
+        }
+        if (!selectorConfig.contains("Config-Version", true)) {
+            logger.severe("Config-Version does not exist!");
+            return false;
+        } else if (!selectorConfig.isInt("Config-Version")) {
+            logger.severe("Config-Version is not an integer!");
+            return false;
+        } else if (selectorConfig.getInt("Config-Version") != SELECTOR_VERSION) {
+            logger.severe("Mismatched config version! Generate a new config and migrate your settings!");
+            return false;
+        } else {
+            GeyserHubMain.getInstance().reloadConfig();
+            this.configurations.put("selector", selectorConfig);
+            logger.debug("Loaded configuration successfully");
+            return true;
+        }
+    }
+
+    @Nullable
+    public FileConfiguration getFileConfiguration(@Nonnull String configName) {
+        Objects.requireNonNull(configName);
+        return configurations.get(configName);
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index d9f9c37..a01db4a 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -1,7 +1,6 @@
 package dev.projectg.geyserhub;
 
 import dev.projectg.geyserhub.command.GeyserHubCommand;
-import dev.projectg.geyserhub.utils.ConfigManager;
 import dev.projectg.geyserhub.module.menu.CommonMenuListeners;
 import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenuListeners;
 import dev.projectg.geyserhub.module.menu.java.JavaMenuListeners;
@@ -23,21 +22,21 @@
 
 public class GeyserHubMain extends JavaPlugin {
     private static GeyserHubMain plugin;
-    public static SelectorLogger logger;
-    public static final int selectorConfigVersion = 1;
-    public static final int configVersion = 4;
+
+    private ConfigManager configManager;
 
     @Override
     public void onEnable() {
         plugin = this;
         new Metrics(this, 11427);
         // getting the logger forces the config to load before our loadConfiguration() is called...
-        logger = SelectorLogger.getLogger();
+        SelectorLogger logger = SelectorLogger.getLogger();
+        configManager = new ConfigManager();
 
         try {
             Properties gitProperties = new Properties();
             gitProperties.load(Utils.getResource("git.properties"));
-            SelectorLogger.info("Branch: " + gitProperties.getProperty("git.branch", "Unknown") + ", Commit: " + gitProperties.getProperty("git.commit.id.abbrev", "Unknown"));
+            logger.info("Branch: " + gitProperties.getProperty("git.branch", "Unknown") + ", Commit: " + gitProperties.getProperty("git.commit.id.abbrev", "Unknown"));
         } catch (IOException e) {
             logger.warn("Unable to load resource: git.properties");
             if (logger.isDebug()) {
@@ -45,12 +44,12 @@ public void onEnable() {
             }
         }
 
-        if (!ConfigManager.loadDefaultConfiguration()) {
-            SelectorLogger.severe("Disabling due to configuration error. Fix the formatting or regenerate a new config file");
+        if (!configManager.loadDefaultConfiguration()) {
+            logger.severe("Disabling due to configuration error. Fix the formatting or regenerate a new config file");
             return;
         }
-        if (!ConfigManager.loadSelectorConfiguration()) {
-            SelectorLogger.severe("Disabling due to configuration error. Fix the formatting or regenerate a new selector file");
+        if (!configManager.loadSelectorConfiguration()) {
+            logger.severe("Disabling due to configuration error. Fix the formatting or regenerate a new selector file");
             return;
         }
 
@@ -97,4 +96,7 @@ public static GeyserHubMain getInstance() {
         return plugin;
     }
 
+    public ConfigManager getConfigManager() {
+        return configManager;
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/dev/projectg/geyserhub/SelectorLogger.java b/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
index a579b01..8063bd7 100644
--- a/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
+++ b/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
@@ -7,8 +7,10 @@ public class SelectorLogger implements Reloadable {
 
     private static final SelectorLogger LOGGER = new SelectorLogger(GeyserHubMain.getInstance());
     private static String message;
+
     private final GeyserHubMain plugin;
-    private static boolean debug;
+    private boolean debug;
+
     public static SelectorLogger getLogger() {
         return LOGGER;
     }
@@ -19,18 +21,18 @@ private SelectorLogger(GeyserHubMain plugin) {
         ReloadableRegistry.registerReloadable(this);
     }
 
-    public static void info(String message) {
-        SelectorLogger.info(message);
+    public void info(String message) {
+        plugin.getLogger().info(message);
     }
     public void warn(String message) {
         plugin.getLogger().warning(message);
     }
-    public static void severe(String message) {
-        SelectorLogger.severe(message);
+    public void severe(String message) {
+        plugin.getLogger().severe(message);
     }
-    public static void debug(String message) {
+    public void debug(String message) {
         if (debug) {
-            SelectorLogger.info(message);
+            plugin.getLogger().info(message);
         }
     }
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
index fd410d0..785873c 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
@@ -1,10 +1,9 @@
 package dev.projectg.geyserhub.module.menu.bedrock;
 
-import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.reloadable.Reloadable;
 import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
 import dev.projectg.geyserhub.SelectorLogger;
-import dev.projectg.geyserhub.utils.ConfigManager;
+import dev.projectg.geyserhub.ConfigManager;
 import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.geysermc.floodgate.api.player.FloodgatePlayer;
@@ -38,7 +37,9 @@ public BedrockFormRegistry() {
     }
 
     private void load() {
-        FileConfiguration config = ConfigManager.get();
+        ConfigManager configManager = new ConfigManager();
+        FileConfiguration config = configManager.getFileConfiguration("selector");
+        Objects.requireNonNull(config);
         SelectorLogger logger = SelectorLogger.getLogger();
 
         enabledForms.clear();
diff --git a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
index 8e49914..d76c584 100644
--- a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
@@ -1,7 +1,8 @@
 package dev.projectg.geyserhub.reloadable;
 
+import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
-import dev.projectg.geyserhub.utils.ConfigManager;
+import dev.projectg.geyserhub.ConfigManager;
 import org.bukkit.ChatColor;
 
 import javax.annotation.Nonnull;
@@ -33,28 +34,23 @@ public static Reloadable[] getRegisteredReloadables() {
     public static boolean reloadAll() {
         SelectorLogger logger = SelectorLogger.getLogger();
 
-        if (ConfigManager.loadDefaultConfiguration()) {
-            SelectorLogger.info("Reloaded the default configuration, reloading modules...");
+        ConfigManager configManager = GeyserHubMain.getInstance().getConfigManager();
+        if (configManager.loadDefaultConfiguration() && configManager.loadSelectorConfiguration()) {
+            logger.info("Reloaded the configuration, reloading modules...");
         } else {
-            SelectorLogger.severe(ChatColor.RED + "Failed to reload the configuration!");
-            return false;
-        }
-        if (ConfigManager.loadSelectorConfiguration()) {
-            SelectorLogger.info("Reloaded the Selector configuration, reloading modules...");
-        } else {
-            SelectorLogger.severe(ChatColor.RED + "Failed to reload the configuration!");
+            logger.severe(ChatColor.RED + "Failed to reload the configuration!");
             return false;
         }
 
         boolean success = true;
         for (Reloadable reloadable : ReloadableRegistry.getRegisteredReloadables()) {
             if (!reloadable.reload()) {
-                SelectorLogger.severe(ChatColor.RED + "Failed to reload class: " + ChatColor.RESET + reloadable.getClass().toString());
+                logger.severe(ChatColor.RED + "Failed to reload class: " + ChatColor.RESET + reloadable.getClass().toString());
                 success = false;
             }
         }
 
-        SelectorLogger.info("Finished reload.");
+        logger.info("Finished reload.");
         return success;
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/utils/ConfigManager.java b/src/main/java/dev/projectg/geyserhub/utils/ConfigManager.java
deleted file mode 100644
index 9d055c9..0000000
--- a/src/main/java/dev/projectg/geyserhub/utils/ConfigManager.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package dev.projectg.geyserhub.utils;
-
-import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.SelectorLogger;
-import org.bukkit.configuration.InvalidConfigurationException;
-import org.bukkit.configuration.file.FileConfiguration;
-import org.bukkit.configuration.file.YamlConfiguration;
-
-import java.io.File;
-import java.io.IOException;
-
-import static dev.projectg.geyserhub.GeyserHubMain.configVersion;
-import static dev.projectg.geyserhub.GeyserHubMain.selectorConfigVersion;
-
-public class ConfigManager {
-
-    private static FileConfiguration selectorConfig;
-
-    public static boolean loadDefaultConfiguration() {
-        File configFile = new File(GeyserHubMain.getInstance().getDataFolder(), "config.yml");
-        if (!configFile.exists()) {
-            try {
-                configFile.getParentFile().mkdirs();
-            } catch (SecurityException e) {
-                e.printStackTrace();
-                return false;
-            }
-            GeyserHubMain.getInstance().saveResource("config.yml", false);
-        }
-        // Get the config but don't actually load it into the main memory config
-        FileConfiguration config = new YamlConfiguration();
-        try {
-            config.load(configFile);
-            if (!config.contains("Config-Version", true)) {
-                SelectorLogger.severe("Config-Version does not exist!");
-                return false;
-            } else if (!config.isInt("Config-Version")) {
-                SelectorLogger.severe("Config-Version is not an integer!");
-                return false;
-            } else if (!(config.getInt("Config-Version") == configVersion)) {
-                SelectorLogger.severe("Mismatched config version! Generate a new config and migrate your settings!");
-                return false;
-            } else {
-                GeyserHubMain.getInstance().reloadConfig();
-                SelectorLogger.debug("Loaded configuration successfully");
-                return true;
-            }
-        } catch (IOException | InvalidConfigurationException e) {
-            e.printStackTrace();
-            return false;
-        }
-    }
-    public static boolean loadSelectorConfiguration() {
-        File file = new File(GeyserHubMain.getInstance().getDataFolder(), "selector.yml");
-        if (!file.exists()) {
-            try {
-                file.getParentFile().mkdirs();
-            } catch (SecurityException e) {
-                e.printStackTrace();
-                return false;
-            }
-            GeyserHubMain.getInstance().saveResource("selector.yml", false);
-        }
-        // Get the config but don't actually load it into the main memory config
-        FileConfiguration selectorConfig = new YamlConfiguration();
-        ConfigManager.selectorConfig = YamlConfiguration.loadConfiguration(file);
-        if (!selectorConfig.contains("Config-Version", true)) {
-            SelectorLogger.severe("Config-Version does not exist!");
-            return false;
-        } else if (!selectorConfig.isInt("Config-Version")) {
-            SelectorLogger.severe("Config-Version is not an integer!");
-            return false;
-        } else if (!(selectorConfig.getInt("Config-Version") == selectorConfigVersion)) {
-            SelectorLogger.severe("Mismatched config version! Generate a new config and migrate your settings!");
-            return false;
-        } else {
-            GeyserHubMain.getInstance().reloadConfig();
-            SelectorLogger.debug("Loaded configuration successfully");
-            return true;
-        }
-    }
-    public static FileConfiguration get(){
-        return selectorConfig;
-    }
-}

From 854b28961389c8ee4fb308624122e83a4f6b6479 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 16 Jun 2021 14:58:27 -0400
Subject: [PATCH 20/74] remove unused var in logger

---
 src/main/java/dev/projectg/geyserhub/SelectorLogger.java | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/main/java/dev/projectg/geyserhub/SelectorLogger.java b/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
index 8063bd7..80f18a6 100644
--- a/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
+++ b/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
@@ -6,7 +6,6 @@
 public class SelectorLogger implements Reloadable {
 
     private static final SelectorLogger LOGGER = new SelectorLogger(GeyserHubMain.getInstance());
-    private static String message;
 
     private final GeyserHubMain plugin;
     private boolean debug;

From 59ab2cbbd3b14e257d8073afff3d51821fd085ec Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 16 Jun 2021 15:40:37 -0400
Subject: [PATCH 21/74] better warning message for config

---
 src/main/java/dev/projectg/geyserhub/GeyserHubMain.java     | 6 +++---
 .../java/dev/projectg/geyserhub/module/Placeholders.java    | 2 +-
 .../projectg/geyserhub/module/menu/bedrock/BedrockForm.java | 2 +-
 .../dev/projectg/geyserhub/module/world/WorldSettings.java  | 2 --
 src/main/resources/config.yml                               | 2 +-
 src/main/resources/plugin.yml                               | 2 +-
 src/main/resources/selector.yml                             | 2 +-
 7 files changed, 8 insertions(+), 10 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index a01db4a..9cdd8ab 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -45,11 +45,11 @@ public void onEnable() {
         }
 
         if (!configManager.loadDefaultConfiguration()) {
-            logger.severe("Disabling due to configuration error. Fix the formatting or regenerate a new config file");
+            logger.severe("Disabling due to configuration error in config.yml - Fix the formatting or regenerate a new file.");
             return;
         }
         if (!configManager.loadSelectorConfiguration()) {
-            logger.severe("Disabling due to configuration error. Fix the formatting or regenerate a new selector file");
+            logger.severe("Disabling due to configuration error in selector.yml - Fix the formatting or regenerate a new file.");
             return;
         }
 
@@ -99,4 +99,4 @@ public static GeyserHubMain getInstance() {
     public ConfigManager getConfigManager() {
         return configManager;
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/Placeholders.java b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
index caa458d..d8e83b4 100644
--- a/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
+++ b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
@@ -5,4 +5,4 @@
 public class Placeholders {
     public static final String[] colorCodes = {"&0", "&1", "&2", "&3", "&4", "&5", "&6", "&7", "&8", "&9", "&a", "&b", "&c", "&d", "&e", "&f"};
     public static int refreshRate = GeyserHubMain.getInstance().getConfig().getInt("Scoreboard.Refresh-rate");
-}
\ No newline at end of file
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index b985960..e26c141 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -226,4 +226,4 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
         // Send the form to the floodgate player
         floodgatePlayer.sendForm(serverSelector);
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java b/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
index 4f9b076..7679df4 100644
--- a/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
+++ b/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
@@ -122,6 +122,4 @@ public void onBlockPlace(BlockPlaceEvent event) {
         player.sendMessage(ChatColor.RESET + "You can't place blocks here!");
         event.setCancelled(true);
     }
-
-
 }
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 52bf443..0839f1a 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -57,4 +57,4 @@ World-settings:
   disable-block-break: true
 
 # Don't touch this
-Config-Version: 4
\ No newline at end of file
+Config-Version: 4
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 610ba3c..4fd0204 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -29,4 +29,4 @@ permissions:
     default: op
   geyserhub.blockplace:
     description: Allows player to place blocks
-    default: op
\ No newline at end of file
+    default: op
diff --git a/src/main/resources/selector.yml b/src/main/resources/selector.yml
index ba93ad8..efd6ca2 100644
--- a/src/main/resources/selector.yml
+++ b/src/main/resources/selector.yml
@@ -78,4 +78,4 @@ Bedrock-Selector:
           Server: "hideseek"
 
 # Don't touch this
-Config-Version: 1
\ No newline at end of file
+Config-Version: 1

From 0236d825d25e3e2186b955270851193f7043bc2d Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 16 Jun 2021 16:38:42 -0400
Subject: [PATCH 22/74] improve ConfigManager

---
 .../dev/projectg/geyserhub/ConfigManager.java | 87 ++++++-------------
 .../dev/projectg/geyserhub/GeyserHubMain.java |  6 +-
 .../menu/bedrock/BedrockFormRegistry.java     |  5 +-
 .../reloadable/ReloadableRegistry.java        | 14 +--
 4 files changed, 42 insertions(+), 70 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/ConfigManager.java b/src/main/java/dev/projectg/geyserhub/ConfigManager.java
index 91e10b5..01085b0 100644
--- a/src/main/java/dev/projectg/geyserhub/ConfigManager.java
+++ b/src/main/java/dev/projectg/geyserhub/ConfigManager.java
@@ -16,9 +16,6 @@ public class ConfigManager {
 
     private static ConfigManager CONFIG_MANAGER;
 
-    private static final int DEFAULT_VERSION = 4;
-    private static final int SELECTOR_VERSION = 1;
-
     private final Map<String, FileConfiguration> configurations = new HashMap<>();
 
     public ConfigManager() {
@@ -29,54 +26,14 @@ public ConfigManager() {
         }
     }
 
-    // todo: better code
+    public boolean loadConfiguration(@Nonnull String configName) {
+        Objects.requireNonNull(configName);
 
-    public boolean loadDefaultConfiguration() {
+        GeyserHubMain plugin = GeyserHubMain.getInstance();
         SelectorLogger logger = SelectorLogger.getLogger();
 
-        File configFile = new File(GeyserHubMain.getInstance().getDataFolder(), "config.yml");
-        if (!configFile.exists()) {
-            try {
-                configFile.getParentFile().mkdirs();
-            } catch (SecurityException e) {
-                e.printStackTrace();
-                return false;
-            }
-            GeyserHubMain.getInstance().saveResource("config.yml", false);
-        }
-        // Get the config but don't actually load it into the main memory config
-        FileConfiguration config = new YamlConfiguration();
-        try {
-            config.load(configFile);
-        } catch (IOException | InvalidConfigurationException e) {
-            e.printStackTrace();
-            return false;
-        }
-        try {
-            config.load(configFile);
-            if (!config.contains("Config-Version", true)) {
-                logger.severe("Config-Version does not exist!");
-                return false;
-            } else if (!config.isInt("Config-Version")) {
-                logger.severe("Config-Version is not an integer!");
-                return false;
-            } else if (config.getInt("Config-Version") != DEFAULT_VERSION) {
-                logger.severe("Mismatched config version! Generate a new config and migrate your settings!");
-                return false;
-            } else {
-                GeyserHubMain.getInstance().reloadConfig();
-                logger.debug("Loaded configuration successfully");
-                return true;
-            }
-        } catch (IOException | InvalidConfigurationException e) {
-            e.printStackTrace();
-            return false;
-        }
-    }
-    public boolean loadSelectorConfiguration() {
-        SelectorLogger logger = SelectorLogger.getLogger();
-
-        File file = new File(GeyserHubMain.getInstance().getDataFolder(), "selector.yml");
+        String configFileName = configName + ".yml";
+        File file = new File(plugin.getDataFolder(), configFileName);
         if (!file.exists()) {
             try {
                 file.getParentFile().mkdirs();
@@ -84,35 +41,47 @@ public boolean loadSelectorConfiguration() {
                 e.printStackTrace();
                 return false;
             }
-            GeyserHubMain.getInstance().saveResource("selector.yml", false);
+            plugin.saveResource(configFileName, false);
         }
-        FileConfiguration selectorConfig = new YamlConfiguration();
+        FileConfiguration configuration = new YamlConfiguration();
         try {
-            selectorConfig.load(file);
+            configuration.load(file);
         } catch (IOException | InvalidConfigurationException e) {
             e.printStackTrace();
             return false;
         }
-        if (!selectorConfig.contains("Config-Version", true)) {
-            logger.severe("Config-Version does not exist!");
+        if (!configuration.contains("Config-Version", true)) {
+            logger.severe("Config-Version does not exist in" + configFileName + " !");
             return false;
-        } else if (!selectorConfig.isInt("Config-Version")) {
-            logger.severe("Config-Version is not an integer!");
+        } else if (!configuration.isInt("Config-Version")) {
+            logger.severe("Config-Version is not an integer in" + configFileName + " !");
             return false;
-        } else if (selectorConfig.getInt("Config-Version") != SELECTOR_VERSION) {
-            logger.severe("Mismatched config version! Generate a new config and migrate your settings!");
+        } else if (configuration.getInt("Config-Version") != Objects.requireNonNull(configuration.getDefaults()).getInt("Config-Version")) {
+            logger.severe("Mismatched config version in " + configFileName + " ! Generate a new config and migrate your settings!");
             return false;
         } else {
-            GeyserHubMain.getInstance().reloadConfig();
-            this.configurations.put("selector", selectorConfig);
+            plugin.reloadConfig();
+            this.configurations.put(configName, configuration);
             logger.debug("Loaded configuration successfully");
             return true;
         }
     }
 
+    /**
+     * Get the given FileConfiguration in the stored map.
+     * @param configName the name of the config file, without ".yml"
+     * @return the FileConfiguration
+     */
     @Nullable
     public FileConfiguration getFileConfiguration(@Nonnull String configName) {
         Objects.requireNonNull(configName);
         return configurations.get(configName);
     }
+
+    /**
+     * @return A the map of configuration names to FileConfigurations
+     */
+    public Map<String, FileConfiguration> getAllFileConfigurations() {
+        return configurations;
+    }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 9cdd8ab..7bf12dc 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -31,7 +31,6 @@ public void onEnable() {
         new Metrics(this, 11427);
         // getting the logger forces the config to load before our loadConfiguration() is called...
         SelectorLogger logger = SelectorLogger.getLogger();
-        configManager = new ConfigManager();
 
         try {
             Properties gitProperties = new Properties();
@@ -44,11 +43,12 @@ public void onEnable() {
             }
         }
 
-        if (!configManager.loadDefaultConfiguration()) {
+        configManager = new ConfigManager();
+        if (!configManager.loadConfiguration("config")) {
             logger.severe("Disabling due to configuration error in config.yml - Fix the formatting or regenerate a new file.");
             return;
         }
-        if (!configManager.loadSelectorConfiguration()) {
+        if (!configManager.loadConfiguration("selector")) {
             logger.severe("Disabling due to configuration error in selector.yml - Fix the formatting or regenerate a new file.");
             return;
         }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
index 785873c..16760fc 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
@@ -1,9 +1,9 @@
 package dev.projectg.geyserhub.module.menu.bedrock;
 
+import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.reloadable.Reloadable;
 import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
 import dev.projectg.geyserhub.SelectorLogger;
-import dev.projectg.geyserhub.ConfigManager;
 import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.geysermc.floodgate.api.player.FloodgatePlayer;
@@ -37,8 +37,7 @@ public BedrockFormRegistry() {
     }
 
     private void load() {
-        ConfigManager configManager = new ConfigManager();
-        FileConfiguration config = configManager.getFileConfiguration("selector");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("selector");
         Objects.requireNonNull(config);
         SelectorLogger logger = SelectorLogger.getLogger();
 
diff --git a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
index d76c584..f8d03d0 100644
--- a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
@@ -35,12 +35,16 @@ public static boolean reloadAll() {
         SelectorLogger logger = SelectorLogger.getLogger();
 
         ConfigManager configManager = GeyserHubMain.getInstance().getConfigManager();
-        if (configManager.loadDefaultConfiguration() && configManager.loadSelectorConfiguration()) {
-            logger.info("Reloaded the configuration, reloading modules...");
-        } else {
-            logger.severe(ChatColor.RED + "Failed to reload the configuration!");
-            return false;
+        // loadConfiguration() will never remove a key so I don't think this will result in ConcurrentModificationException...
+        for (String configName : configManager.getAllFileConfigurations().keySet()) {
+            if (configManager.loadConfiguration(configName)) {
+                logger.debug("Reloaded config file: " + configName + ".yml");
+            } else {
+                logger.severe(ChatColor.RED + "Failed to reload configuration: " + configName + ".yml");
+                return false;
+            }
         }
+        logger.info("Reloaded the configuration, reloading modules...");
 
         boolean success = true;
         for (Reloadable reloadable : ReloadableRegistry.getRegisteredReloadables()) {

From 9aed62fcc5ee14263082d56b5c8dde0f27775620 Mon Sep 17 00:00:00 2001
From: Jens Collaert <jenscollaertprive@hotmail.com>
Date: Thu, 17 Jun 2021 01:29:48 +0200
Subject: [PATCH 23/74] Changed all default configurations, as far as i tested
 all is working correctly.

---
 .../dev/projectg/geyserhub/ConfigManager.java |  6 +--
 .../geyserhub/module/Placeholders.java        |  5 +-
 .../geyserhub/module/menu/AccessItem.java     |  3 +-
 .../module/menu/CommonMenuListeners.java      | 17 +++++--
 .../menu/bedrock/BedrockMenuListeners.java    |  7 ++-
 .../geyserhub/module/menu/java/JavaMenu.java  |  4 +-
 .../geyserhub/module/message/Broadcast.java   |  9 ++--
 .../geyserhub/module/message/MessageJoin.java |  7 ++-
 .../module/scoreboard/ScoreboardManager.java  |  7 ++-
 .../module/teleporter/JoinTeleporter.java     |  8 +++-
 .../geyserhub/module/world/WorldSettings.java | 48 ++++++++++++++-----
 11 files changed, 87 insertions(+), 34 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/ConfigManager.java b/src/main/java/dev/projectg/geyserhub/ConfigManager.java
index 01085b0..1bb3e87 100644
--- a/src/main/java/dev/projectg/geyserhub/ConfigManager.java
+++ b/src/main/java/dev/projectg/geyserhub/ConfigManager.java
@@ -56,9 +56,9 @@ public boolean loadConfiguration(@Nonnull String configName) {
         } else if (!configuration.isInt("Config-Version")) {
             logger.severe("Config-Version is not an integer in" + configFileName + " !");
             return false;
-        } else if (configuration.getInt("Config-Version") != Objects.requireNonNull(configuration.getDefaults()).getInt("Config-Version")) {
-            logger.severe("Mismatched config version in " + configFileName + " ! Generate a new config and migrate your settings!");
-            return false;
+        //} else if (configuration.getInt("Config-Version") != Objects.requireNonNull(configuration.getDefaults()).getInt("Config-Version")) {
+         //   logger.severe("Mismatched config version in " + configFileName + " ! Generate a new config and migrate your settings!");
+         //   return false;
         } else {
             plugin.reloadConfig();
             this.configurations.put(configName, configuration);
diff --git a/src/main/java/dev/projectg/geyserhub/module/Placeholders.java b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
index d8e83b4..5914334 100644
--- a/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
+++ b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
@@ -2,7 +2,10 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 
+import java.util.Objects;
+
 public class Placeholders {
+
     public static final String[] colorCodes = {"&0", "&1", "&2", "&3", "&4", "&5", "&6", "&7", "&8", "&9", "&a", "&b", "&c", "&d", "&e", "&f"};
-    public static int refreshRate = GeyserHubMain.getInstance().getConfig().getInt("Scoreboard.Refresh-rate");
+    public static int refreshRate = Objects.requireNonNull(GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config")).getInt("Scoreboard.Refresh-rate");
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
index 8a607c0..e1a6899 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
@@ -17,7 +17,8 @@ public class AccessItem {
 
     private static final ItemStack ACCESS_ITEM;
     static {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfig();
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("selector");
+        Objects.requireNonNull(config);
 
         // Get the material
         Material material;
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
index 89be93a..d636a2e 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
@@ -2,6 +2,7 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import org.bukkit.Material;
+import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
@@ -17,20 +18,24 @@ public class CommonMenuListeners implements Listener {
 
     @EventHandler
     public void onInventoryClick(InventoryClickEvent event) {
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("selector");
+        Objects.requireNonNull(config);
         if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) {
             return;
         }
-        if (!GeyserHubMain.getInstance().getConfig().getBoolean("Selector-Item.Allow-Move") && Objects.requireNonNull(event.getCurrentItem()).isSimilar(AccessItem.getItem())) {
+        if (!config.getBoolean("Selector-Item.Allow-Move") && Objects.requireNonNull(event.getCurrentItem()).isSimilar(AccessItem.getItem())) {
                 event.setCancelled(true);
             }
     }
 
     @EventHandler
     public void onPlayerDropItem(PlayerDropItemEvent event) {
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("selector");
+        Objects.requireNonNull(config);
         if (event.getItemDrop().getItemStack().isSimilar(AccessItem.getItem())) {
-            if (!GeyserHubMain.getInstance().getConfig().getBoolean("Selector-Item.Allow-Drop")) {
+            if (!config.getBoolean("Selector-Item.Allow-Drop")) {
                 event.setCancelled(true);
-            } else if (GeyserHubMain.getInstance().getConfig().getBoolean("Selector-Item.Destroy-Dropped")) {
+            } else if (config.getBoolean("Selector-Item.Destroy-Dropped")) {
                 event.getItemDrop().remove();
             }
         }
@@ -38,15 +43,17 @@ public void onPlayerDropItem(PlayerDropItemEvent event) {
 
     @EventHandler
     public void onPlayerJoin(PlayerJoinEvent event) {
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("selector");
+        Objects.requireNonNull(config);
         event.getPlayer().getInventory().setHeldItemSlot(GeyserHubMain.getInstance().getConfig().getInt("Selector-Item.Slot"));
-        if (GeyserHubMain.getInstance().getConfig().getBoolean("Selector-Item.Join")) {
+        if (config.getBoolean("Selector-Item.Join")) {
             Player player = event.getPlayer();
             ItemStack accessItem = AccessItem.getItem();
             if (player.getInventory().contains(accessItem)) {
                 return;
             }
 
-            int desiredSlot = GeyserHubMain.getInstance().getConfig().getInt("Selector-Item.Slot");
+            int desiredSlot = config.getInt("Selector-Item.Slot");
             ItemStack oldItem = player.getInventory().getItem(desiredSlot);
             if (oldItem == null || oldItem.getType() == Material.AIR) {
                 player.getInventory().setItem(desiredSlot, accessItem);
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
index 33c7e1d..b559c0f 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
@@ -3,6 +3,7 @@
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.module.menu.AccessItem;
 import dev.projectg.geyserhub.module.menu.java.JavaMenu;
+import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
@@ -10,17 +11,21 @@
 import org.bukkit.event.player.PlayerInteractEvent;
 import org.geysermc.floodgate.api.FloodgateApi;
 
+import java.util.Objects;
+
 public class BedrockMenuListeners implements Listener {
 
     @EventHandler
     public void onInteract(PlayerInteractEvent event) {
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("selector");
+        Objects.requireNonNull(config);
         Player player = event.getPlayer();
         if (player.getInventory().getItemInMainHand().isSimilar(AccessItem.getItem())) {
             if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
                 if (FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {
                     BedrockFormRegistry.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()), BedrockFormRegistry.DEFAULT);
                 } else {
-                    JavaMenu.openMenu(player, GeyserHubMain.getInstance().getConfig());
+                    JavaMenu.openMenu(player, config);
                 }
             }
         }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index 5390f16..d59dd35 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -23,7 +23,9 @@
 public class JavaMenu extends Placeholders {
 
     public static boolean isEnabled() {
-        return GeyserHubMain.getInstance().getConfig().getBoolean("Java-Selector.Enabled", true);
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("selector");
+        Objects.requireNonNull(config);
+        return config.getBoolean("Java-Selector.Enabled", true);
     }
 
     // todo: maybe just remove the FileConfiguration parameter
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
index d448877..6a77ed9 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
@@ -6,6 +6,7 @@
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
 import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
 import org.bukkit.scheduler.BukkitScheduler;
 
@@ -13,10 +14,12 @@
 
 public class Broadcast {
     public static void startBroadcastTimer(BukkitScheduler scheduler) {
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
         int scheduleId = scheduler.scheduleSyncDelayedTask(GeyserHubMain.getInstance(), () -> {
 
-            if (GeyserHubMain.getInstance().getConfig().getBoolean("Broadcasts.Enable", false)) {
-                ConfigurationSection parentSection = GeyserHubMain.getInstance().getConfig().getConfigurationSection("Broadcasts.Messages");
+            if (config.getBoolean("Broadcasts.Enable", false)) {
+                ConfigurationSection parentSection = config.getConfigurationSection("Broadcasts.Messages");
                 if (parentSection == null) {
                     SelectorLogger.getLogger().severe("Broadcasts.Messages configuration section is malformed, unable to send.");
                     return;
@@ -35,7 +38,7 @@ public static void startBroadcastTimer(BukkitScheduler scheduler) {
                 }
             }
             startBroadcastTimer(scheduler);
-        }, GeyserHubMain.getInstance().getConfig().getLong("Broadcasts-Interval", 3600));
+        }, config.getLong("Broadcasts-Interval", 3600));
     }
 
     private static String getRandomElement(List<String> list) {
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java b/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
index 8be7a98..78b19bd 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
@@ -3,20 +3,23 @@
 import dev.projectg.geyserhub.GeyserHubMain;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.ChatColor;
+import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
 import org.bukkit.event.player.PlayerJoinEvent;
 
 import java.util.List;
+import java.util.Objects;
 
 public class MessageJoin implements Listener {
 
-    @SuppressWarnings("unused")
     @EventHandler
     public void onJoin(PlayerJoinEvent e) {
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
         Player player = e.getPlayer();
-        List<String> messages = GeyserHubMain.getInstance().getConfig().getStringList("Join-Message.Messages");
+        List<String> messages = config.getStringList("Join-Message.Messages");
 
         for (String message : messages) {
             player.sendMessage(ChatColor.translateAlternateColorCodes('&', PlaceholderAPI.setPlaceholders(player, message)));
diff --git a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
index 016f747..9e55f9a 100644
--- a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
+++ b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
@@ -4,6 +4,7 @@
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
+import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
 import org.bukkit.scoreboard.*;
 
@@ -24,11 +25,13 @@ public static void addScoreboard() {
     }
 
     public static void createScoreboard(Player player) {
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
         Scoreboard board = Objects.requireNonNull(Bukkit.getServer().getScoreboardManager()).getNewScoreboard();
-        Objective objective = board.registerNewObjective("GeyserHub", "dummy", PlaceholderAPI.setPlaceholders(player, GeyserHubMain.getInstance().getConfig().getString("Scoreboard.Title", "GeyserHub")));
+        Objective objective = board.registerNewObjective("GeyserHub", "dummy", PlaceholderAPI.setPlaceholders(player, config.getString("Scoreboard.Title", "GeyserHub")));
 
         objective.setDisplaySlot(DisplaySlot.SIDEBAR);
-        List<String> text = GeyserHubMain.getInstance().getConfig().getStringList("Scoreboard.Lines");
+        List<String> text = config.getStringList("Scoreboard.Lines");
 
         // Scoreboards have a max of 15 lines
         int limit = Math.min(text.size(), 15);
diff --git a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
index 5449bcf..2482a9f 100644
--- a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
+++ b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
@@ -25,8 +25,10 @@ public class JoinTeleporter implements Listener, Reloadable {
     private Location location;
 
     public JoinTeleporter() {
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
         ReloadableRegistry.registerReloadable(this);
-        enabled = load(GeyserHubMain.getInstance().getConfig());
+        enabled = load(config);
     }
 
     @EventHandler
@@ -92,7 +94,9 @@ private boolean load(@Nonnull FileConfiguration config) {
 
     @Override
     public boolean reload() {
-        enabled = load(GeyserHubMain.getInstance().getConfig());
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
+        enabled = load(config);
         return enabled;
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java b/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
index 7679df4..ece2e68 100644
--- a/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
+++ b/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
@@ -3,6 +3,7 @@
 import dev.projectg.geyserhub.GeyserHubMain;
 import org.bukkit.ChatColor;
 import org.bukkit.Material;
+import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.EventPriority;
@@ -15,23 +16,26 @@
 import org.bukkit.event.weather.WeatherChangeEvent;
 import org.bukkit.inventory.ItemStack;
 
+import java.util.Objects;
+
 
 public class WorldSettings implements Listener {
 
     @EventHandler
     public void onEntityDamage(EntityDamageEvent event) {
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
         if (!(event.getEntity() instanceof Player)) return;
-        Player player = (Player) event.getEntity();
 
-        if (GeyserHubMain.getInstance().getConfig().getBoolean("World-settings.disable-fall-damage")
+        if (config.getBoolean("World-settings.disable-fall-damage")
                 && event.getCause() == EntityDamageEvent.DamageCause.FALL)
             event.setCancelled(true);
 
-        else if (GeyserHubMain.getInstance().getConfig().getBoolean("World-settings.disable-drowning")
+        else if (config.getBoolean("World-settings.disable-drowning")
                 && event.getCause() == EntityDamageEvent.DamageCause.DROWNING)
             event.setCancelled(true);
 
-        else if (GeyserHubMain.getInstance().getConfig().getBoolean("World-settings.disable-fire-damage")
+        else if (config.getBoolean("World-settings.disable-fire-damage")
                 && (event.getCause() == EntityDamageEvent.DamageCause.FIRE
                 || event.getCause() == EntityDamageEvent.DamageCause.FIRE_TICK || event.getCause()
                 == EntityDamageEvent.DamageCause.LAVA))
@@ -40,7 +44,9 @@ else if (GeyserHubMain.getInstance().getConfig().getBoolean("World-settings.disa
 
     @EventHandler(priority = EventPriority.HIGH)
     public void onFoodChange(FoodLevelChangeEvent event) {
-        if (!GeyserHubMain.getInstance().getConfig().getBoolean("World-settings.disable-hunger-loss"))
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
+        if (!config.getBoolean("World-settings.disable-hunger-loss"))
             return;
         if (!(event.getEntity() instanceof Player))
             return;
@@ -49,7 +55,9 @@ public void onFoodChange(FoodLevelChangeEvent event) {
 
     @EventHandler
     public void onFireSpread(BlockIgniteEvent event) {
-        if (!GeyserHubMain.getInstance().getConfig().getBoolean("World-settings.disable-block-fire-spread"))
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
+        if (!config.getBoolean("World-settings.disable-block-fire-spread"))
             return;
         if (event.getCause() == BlockIgniteEvent.IgniteCause.SPREAD)
             event.setCancelled(true);
@@ -57,21 +65,27 @@ public void onFireSpread(BlockIgniteEvent event) {
 
     @EventHandler
     public void onBlockBurn(BlockBurnEvent event) {
-        if (!GeyserHubMain.getInstance().getConfig().getBoolean("World-settings.disable-disable-block-burn"))
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
+        if (!config.getBoolean("World-settings.disable-disable-block-burn"))
             return;
         event.setCancelled(true);
     }
 
     @EventHandler(priority = EventPriority.HIGH)
     public void onLeafDecay(LeavesDecayEvent event) {
-        if (!GeyserHubMain.getInstance().getConfig().getBoolean("World-settings.disable_block-leaf-decay"))
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
+        if (!config.getBoolean("World-settings.disable_block-leaf-decay"))
             return;
         event.setCancelled(true);
     }
 
     @EventHandler(priority = EventPriority.HIGH)
     public void onCreatureSpawn(CreatureSpawnEvent event) {
-        if (!GeyserHubMain.getInstance().getConfig().getBoolean("World-settings.disable-mob-spawning"))
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
+        if (!config.getBoolean("World-settings.disable-mob-spawning"))
             return;
         if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.CUSTOM) return;
         event.setCancelled(true);
@@ -79,7 +93,9 @@ public void onCreatureSpawn(CreatureSpawnEvent event) {
 
     @EventHandler(priority = EventPriority.HIGH)
     public void onWeatherChange(WeatherChangeEvent event) {
-        if (!GeyserHubMain.getInstance().getConfig().getBoolean("World-settings.disable-weather-change"))
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
+        if (!config.getBoolean("World-settings.disable-weather-change"))
             return;
 
         event.setCancelled(event.toWeatherState());
@@ -87,7 +103,9 @@ public void onWeatherChange(WeatherChangeEvent event) {
 
     @EventHandler(priority = EventPriority.HIGH)
     public void onEntityDamage(EntityDamageByEntityEvent event) {
-        if (!GeyserHubMain.getInstance().getConfig().getBoolean("World-settings.disable-player-pvp"))
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
+        if (!config.getBoolean("World-settings.disable-player-pvp"))
             return;
         if (!(event.getEntity() instanceof Player)) return;
         event.setCancelled(true);
@@ -95,7 +113,9 @@ public void onEntityDamage(EntityDamageByEntityEvent event) {
     }
     @EventHandler(priority = EventPriority.HIGH)
     public void onBlockBreak(BlockBreakEvent event) {
-        if (!GeyserHubMain.getInstance().getConfig().getBoolean("World-settings.disable-block-break")
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
+        if (!config.getBoolean("World-settings.disable-block-break")
                 || event.isCancelled())
             return;
         Player player = event.getPlayer();
@@ -109,7 +129,9 @@ public void onBlockBreak(BlockBreakEvent event) {
 
     @EventHandler(priority = EventPriority.HIGH)
     public void onBlockPlace(BlockPlaceEvent event) {
-        if (!GeyserHubMain.getInstance().getConfig().getBoolean("World-settings.disable-block-place")
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        Objects.requireNonNull(config);
+        if (!config.getBoolean("World-settings.disable-block-place")
                 || event.isCancelled())
             return;
         ItemStack item = event.getItemInHand();

From 78a827c3e53d558d6fa0c82c5d46f6bed492287d Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Sat, 19 Jun 2021 18:22:33 -0400
Subject: [PATCH 24/74] fix form image debug logger

---
 .../dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index e26c141..5638ca4 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -126,7 +126,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
                     String imageURL = buttonInfo.getString("ImageURL");
                     Objects.requireNonNull(imageURL);
                     image = FormImage.of(FormImage.Type.URL, imageURL);
-                    logger.debug(buttonId + " contains image with URL: " + image);
+                    logger.debug(buttonId + " contains image with URL: " + image.getData());
                 }
 
                 // Add commands if specified

From a8cd2ec071bfa486f14510f938a6ee5989f8d33c Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Sat, 19 Jun 2021 18:43:22 -0400
Subject: [PATCH 25/74] implement configuration IDs instead of using Strings

---
 .../dev/projectg/geyserhub/ConfigManager.java | 45 ++++++++++++++-----
 .../dev/projectg/geyserhub/GeyserHubMain.java |  8 ++--
 .../reloadable/ReloadableRegistry.java        |  8 ++--
 3 files changed, 41 insertions(+), 20 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/ConfigManager.java b/src/main/java/dev/projectg/geyserhub/ConfigManager.java
index 1bb3e87..b8e0fac 100644
--- a/src/main/java/dev/projectg/geyserhub/ConfigManager.java
+++ b/src/main/java/dev/projectg/geyserhub/ConfigManager.java
@@ -16,7 +16,7 @@ public class ConfigManager {
 
     private static ConfigManager CONFIG_MANAGER;
 
-    private final Map<String, FileConfiguration> configurations = new HashMap<>();
+    private final Map<ConfigId, FileConfiguration> configurations = new HashMap<>();
 
     public ConfigManager() {
         if (CONFIG_MANAGER == null) {
@@ -26,13 +26,18 @@ public ConfigManager() {
         }
     }
 
-    public boolean loadConfiguration(@Nonnull String configName) {
-        Objects.requireNonNull(configName);
+    /**
+     * Load a configuration from file.
+     * @param config The configuration to load
+     * @return The success state
+     */
+    public boolean loadConfiguration(@Nonnull ConfigId config) {
+        Objects.requireNonNull(config);
 
         GeyserHubMain plugin = GeyserHubMain.getInstance();
         SelectorLogger logger = SelectorLogger.getLogger();
 
-        String configFileName = configName + ".yml";
+        String configFileName = config.fileName + ".yml";
         File file = new File(plugin.getDataFolder(), configFileName);
         if (!file.exists()) {
             try {
@@ -57,11 +62,11 @@ public boolean loadConfiguration(@Nonnull String configName) {
             logger.severe("Config-Version is not an integer in" + configFileName + " !");
             return false;
         //} else if (configuration.getInt("Config-Version") != Objects.requireNonNull(configuration.getDefaults()).getInt("Config-Version")) {
-         //   logger.severe("Mismatched config version in " + configFileName + " ! Generate a new config and migrate your settings!");
-         //   return false;
+            //logger.severe("Mismatched config version in " + configFileName + " ! Generate a new config and migrate your settings!");
+            //return false;
         } else {
             plugin.reloadConfig();
-            this.configurations.put(configName, configuration);
+            this.configurations.put(config, configuration);
             logger.debug("Loaded configuration successfully");
             return true;
         }
@@ -69,19 +74,35 @@ public boolean loadConfiguration(@Nonnull String configName) {
 
     /**
      * Get the given FileConfiguration in the stored map.
-     * @param configName the name of the config file, without ".yml"
+     * @param config the config ID
      * @return the FileConfiguration
      */
     @Nullable
-    public FileConfiguration getFileConfiguration(@Nonnull String configName) {
-        Objects.requireNonNull(configName);
-        return configurations.get(configName);
+    public FileConfiguration getFileConfiguration(@Nonnull ConfigId config) {
+        Objects.requireNonNull(config);
+        return configurations.get(config);
     }
 
     /**
      * @return A the map of configuration names to FileConfigurations
      */
-    public Map<String, FileConfiguration> getAllFileConfigurations() {
+    public Map<ConfigId, FileConfiguration> getAllFileConfigurations() {
         return configurations;
     }
+
+    /**
+     * An enum containing the identities of all valid configuration files.
+     */
+    public enum ConfigId {
+        MAIN("config.yml"),
+        SELECTOR("selector.yml");
+
+        public static final ConfigId[] VALUES = values();
+
+        public final String fileName;
+
+        ConfigId(String fileName){
+            this.fileName = fileName;
+        }
+    }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 7bf12dc..33060f6 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -44,12 +44,12 @@ public void onEnable() {
         }
 
         configManager = new ConfigManager();
-        if (!configManager.loadConfiguration("config")) {
-            logger.severe("Disabling due to configuration error in config.yml - Fix the formatting or regenerate a new file.");
+        if (!configManager.loadConfiguration(ConfigManager.ConfigId.MAIN)) {
+            logger.severe("Disabling due to configuration error in " + ConfigManager.ConfigId.MAIN.fileName + " - Fix the formatting or regenerate a new file.");
             return;
         }
-        if (!configManager.loadConfiguration("selector")) {
-            logger.severe("Disabling due to configuration error in selector.yml - Fix the formatting or regenerate a new file.");
+        if (!configManager.loadConfiguration(ConfigManager.ConfigId.SELECTOR)) {
+            logger.severe("Disabling due to configuration error in " + ConfigManager.ConfigId.SELECTOR.fileName + " - Fix the formatting or regenerate a new file.");
             return;
         }
 
diff --git a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
index f8d03d0..a119aed 100644
--- a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
@@ -36,11 +36,11 @@ public static boolean reloadAll() {
 
         ConfigManager configManager = GeyserHubMain.getInstance().getConfigManager();
         // loadConfiguration() will never remove a key so I don't think this will result in ConcurrentModificationException...
-        for (String configName : configManager.getAllFileConfigurations().keySet()) {
-            if (configManager.loadConfiguration(configName)) {
-                logger.debug("Reloaded config file: " + configName + ".yml");
+        for (ConfigManager.ConfigId configId : configManager.getAllFileConfigurations().keySet()) {
+            if (configManager.loadConfiguration(configId)) {
+                logger.debug("Reloaded config file: " + configId.fileName);
             } else {
-                logger.severe(ChatColor.RED + "Failed to reload configuration: " + configName + ".yml");
+                logger.severe(ChatColor.RED + "Failed to reload configuration: " + configId.fileName);
                 return false;
             }
         }

From 212436fe25b9b99cec8b26b5322e1875d24c56aa Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Sat, 19 Jun 2021 18:45:34 -0400
Subject: [PATCH 26/74] don't add the file extension in loadConfiguration

it is already there
---
 src/main/java/dev/projectg/geyserhub/ConfigManager.java | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/ConfigManager.java b/src/main/java/dev/projectg/geyserhub/ConfigManager.java
index b8e0fac..5b8523f 100644
--- a/src/main/java/dev/projectg/geyserhub/ConfigManager.java
+++ b/src/main/java/dev/projectg/geyserhub/ConfigManager.java
@@ -37,8 +37,7 @@ public boolean loadConfiguration(@Nonnull ConfigId config) {
         GeyserHubMain plugin = GeyserHubMain.getInstance();
         SelectorLogger logger = SelectorLogger.getLogger();
 
-        String configFileName = config.fileName + ".yml";
-        File file = new File(plugin.getDataFolder(), configFileName);
+        File file = new File(plugin.getDataFolder(), config.fileName);
         if (!file.exists()) {
             try {
                 file.getParentFile().mkdirs();
@@ -46,7 +45,7 @@ public boolean loadConfiguration(@Nonnull ConfigId config) {
                 e.printStackTrace();
                 return false;
             }
-            plugin.saveResource(configFileName, false);
+            plugin.saveResource(config.fileName, false);
         }
         FileConfiguration configuration = new YamlConfiguration();
         try {
@@ -56,10 +55,10 @@ public boolean loadConfiguration(@Nonnull ConfigId config) {
             return false;
         }
         if (!configuration.contains("Config-Version", true)) {
-            logger.severe("Config-Version does not exist in" + configFileName + " !");
+            logger.severe("Config-Version does not exist in" + config.fileName + " !");
             return false;
         } else if (!configuration.isInt("Config-Version")) {
-            logger.severe("Config-Version is not an integer in" + configFileName + " !");
+            logger.severe("Config-Version is not an integer in" + config.fileName + " !");
             return false;
         //} else if (configuration.getInt("Config-Version") != Objects.requireNonNull(configuration.getDefaults()).getInt("Config-Version")) {
             //logger.severe("Mismatched config version in " + configFileName + " ! Generate a new config and migrate your settings!");

From 7e53f9bee26f65399f8c1febbd13145ce5415fee Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Sat, 19 Jun 2021 20:31:01 -0400
Subject: [PATCH 27/74] fix config version checking

---
 .../dev/projectg/geyserhub/ConfigManager.java | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/ConfigManager.java b/src/main/java/dev/projectg/geyserhub/ConfigManager.java
index 5b8523f..de30236 100644
--- a/src/main/java/dev/projectg/geyserhub/ConfigManager.java
+++ b/src/main/java/dev/projectg/geyserhub/ConfigManager.java
@@ -18,12 +18,16 @@ public class ConfigManager {
 
     private final Map<ConfigId, FileConfiguration> configurations = new HashMap<>();
 
+    private final SelectorLogger logger;
+
     public ConfigManager() {
         if (CONFIG_MANAGER == null) {
             CONFIG_MANAGER = this;
         } else {
             throw new UnsupportedOperationException("Only one instance of ConfigManager is allowed!");
         }
+
+        this.logger = SelectorLogger.getLogger();
     }
 
     /**
@@ -35,7 +39,6 @@ public boolean loadConfiguration(@Nonnull ConfigId config) {
         Objects.requireNonNull(config);
 
         GeyserHubMain plugin = GeyserHubMain.getInstance();
-        SelectorLogger logger = SelectorLogger.getLogger();
 
         File file = new File(plugin.getDataFolder(), config.fileName);
         if (!file.exists()) {
@@ -60,9 +63,9 @@ public boolean loadConfiguration(@Nonnull ConfigId config) {
         } else if (!configuration.isInt("Config-Version")) {
             logger.severe("Config-Version is not an integer in" + config.fileName + " !");
             return false;
-        //} else if (configuration.getInt("Config-Version") != Objects.requireNonNull(configuration.getDefaults()).getInt("Config-Version")) {
-            //logger.severe("Mismatched config version in " + configFileName + " ! Generate a new config and migrate your settings!");
-            //return false;
+        } else if (configuration.getInt("Config-Version") != config.version) {
+            logger.severe("Mismatched config version in " + config.fileName + " ! Generate a new config and migrate your settings!");
+            return false;
         } else {
             plugin.reloadConfig();
             this.configurations.put(config, configuration);
@@ -93,15 +96,17 @@ public Map<ConfigId, FileConfiguration> getAllFileConfigurations() {
      * An enum containing the identities of all valid configuration files.
      */
     public enum ConfigId {
-        MAIN("config.yml"),
-        SELECTOR("selector.yml");
+        MAIN("config.yml", 4),
+        SELECTOR("selector.yml", 1);
 
         public static final ConfigId[] VALUES = values();
 
         public final String fileName;
+        public final int version;
 
-        ConfigId(String fileName){
+        ConfigId(String fileName, int version) {
             this.fileName = fileName;
+            this.version = version;
         }
     }
 }

From 2dae419b218dad16816fdad384cbcbe3955055c0 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Sat, 19 Jun 2021 21:07:07 -0400
Subject: [PATCH 28/74] move ConfigId to its own class, fix building, other
 misc

---
 .../dev/projectg/geyserhub/GeyserHubMain.java | 13 +++++-----
 .../projectg/geyserhub/config/ConfigId.java   | 23 ++++++++++++++++
 .../geyserhub/{ => config}/ConfigManager.java | 26 +++++--------------
 .../geyserhub/module/Placeholders.java        | 11 --------
 .../geyserhub/module/menu/AccessItem.java     |  3 ++-
 .../module/menu/CommonMenuListeners.java      |  8 +++---
 .../menu/bedrock/BedrockFormRegistry.java     |  3 ++-
 .../menu/bedrock/BedrockMenuListeners.java    |  3 ++-
 .../geyserhub/module/menu/java/JavaMenu.java  |  7 ++---
 .../geyserhub/module/message/Broadcast.java   |  5 ++--
 .../geyserhub/module/message/MessageJoin.java |  3 ++-
 .../module/scoreboard/ScoreboardManager.java  |  8 +++---
 .../geyserhub/module/world/WorldSettings.java | 21 ++++++++-------
 .../reloadable/ReloadableRegistry.java        |  5 ++--
 14 files changed, 74 insertions(+), 65 deletions(-)
 create mode 100644 src/main/java/dev/projectg/geyserhub/config/ConfigId.java
 rename src/main/java/dev/projectg/geyserhub/{ => config}/ConfigManager.java (85%)
 delete mode 100644 src/main/java/dev/projectg/geyserhub/module/Placeholders.java

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 33060f6..5dc8ac5 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -1,13 +1,14 @@
 package dev.projectg.geyserhub;
 
 import dev.projectg.geyserhub.command.GeyserHubCommand;
+import dev.projectg.geyserhub.config.ConfigId;
+import dev.projectg.geyserhub.config.ConfigManager;
 import dev.projectg.geyserhub.module.menu.CommonMenuListeners;
 import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenuListeners;
 import dev.projectg.geyserhub.module.menu.java.JavaMenuListeners;
 import dev.projectg.geyserhub.module.menu.bedrock.BedrockFormRegistry;
 import dev.projectg.geyserhub.module.message.Broadcast;
 import dev.projectg.geyserhub.module.message.MessageJoin;
-import dev.projectg.geyserhub.module.Placeholders;
 import dev.projectg.geyserhub.module.scoreboard.ScoreboardManager;
 import dev.projectg.geyserhub.module.teleporter.JoinTeleporter;
 import dev.projectg.geyserhub.module.world.WorldSettings;
@@ -44,12 +45,12 @@ public void onEnable() {
         }
 
         configManager = new ConfigManager();
-        if (!configManager.loadConfiguration(ConfigManager.ConfigId.MAIN)) {
-            logger.severe("Disabling due to configuration error in " + ConfigManager.ConfigId.MAIN.fileName + " - Fix the formatting or regenerate a new file.");
+        if (!configManager.loadConfiguration(ConfigId.MAIN)) {
+            logger.severe("Disabling due to configuration error in " + ConfigId.MAIN.fileName + " - Fix the formatting or regenerate a new file.");
             return;
         }
-        if (!configManager.loadConfiguration(ConfigManager.ConfigId.SELECTOR)) {
-            logger.severe("Disabling due to configuration error in " + ConfigManager.ConfigId.SELECTOR.fileName + " - Fix the formatting or regenerate a new file.");
+        if (!configManager.loadConfiguration(ConfigId.SELECTOR)) {
+            logger.severe("Disabling due to configuration error in " + ConfigId.SELECTOR.fileName + " - Fix the formatting or regenerate a new file.");
             return;
         }
 
@@ -89,7 +90,7 @@ public void initializeScoreboard() {
             } catch (Exception var2) {
                 var2.printStackTrace();
             }
-        }, 20L, Placeholders.refreshRate * 20L);
+        }, 20L, ScoreboardManager.refreshRate * 20L);
     }
 
     public static GeyserHubMain getInstance() {
diff --git a/src/main/java/dev/projectg/geyserhub/config/ConfigId.java b/src/main/java/dev/projectg/geyserhub/config/ConfigId.java
new file mode 100644
index 0000000..9d997c4
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/config/ConfigId.java
@@ -0,0 +1,23 @@
+package dev.projectg.geyserhub.config;
+
+/**
+ * An enum containing the identities of all valid configuration files.
+ */
+public enum ConfigId {
+    MAIN("config.yml", 4),
+    SELECTOR("selector.yml", 1);
+
+    public static final ConfigId[] VALUES = values();
+
+    public final String fileName;
+    public final int version;
+
+    /**
+     * @param fileName the filename, including the extension, of the configuration
+     * @param version the version of the configuration
+     */
+    ConfigId(String fileName, int version) {
+        this.fileName = fileName;
+        this.version = version;
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/ConfigManager.java b/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
similarity index 85%
rename from src/main/java/dev/projectg/geyserhub/ConfigManager.java
rename to src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
index de30236..1444956 100644
--- a/src/main/java/dev/projectg/geyserhub/ConfigManager.java
+++ b/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
@@ -1,5 +1,7 @@
-package dev.projectg.geyserhub;
+package dev.projectg.geyserhub.config;
 
+import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.SelectorLogger;
 import org.bukkit.configuration.InvalidConfigurationException;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.configuration.file.YamlConfiguration;
@@ -43,7 +45,10 @@ public boolean loadConfiguration(@Nonnull ConfigId config) {
         File file = new File(plugin.getDataFolder(), config.fileName);
         if (!file.exists()) {
             try {
-                file.getParentFile().mkdirs();
+                if (!file.getParentFile().mkdirs()) {
+                    logger.severe("Failed to create plugin folder!");
+                    return false;
+                }
             } catch (SecurityException e) {
                 e.printStackTrace();
                 return false;
@@ -92,21 +97,4 @@ public Map<ConfigId, FileConfiguration> getAllFileConfigurations() {
         return configurations;
     }
 
-    /**
-     * An enum containing the identities of all valid configuration files.
-     */
-    public enum ConfigId {
-        MAIN("config.yml", 4),
-        SELECTOR("selector.yml", 1);
-
-        public static final ConfigId[] VALUES = values();
-
-        public final String fileName;
-        public final int version;
-
-        ConfigId(String fileName, int version) {
-            this.fileName = fileName;
-            this.version = version;
-        }
-    }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/Placeholders.java b/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
deleted file mode 100644
index 5914334..0000000
--- a/src/main/java/dev/projectg/geyserhub/module/Placeholders.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package dev.projectg.geyserhub.module;
-
-import dev.projectg.geyserhub.GeyserHubMain;
-
-import java.util.Objects;
-
-public class Placeholders {
-
-    public static final String[] colorCodes = {"&0", "&1", "&2", "&3", "&4", "&5", "&6", "&7", "&8", "&9", "&a", "&b", "&c", "&d", "&e", "&f"};
-    public static int refreshRate = Objects.requireNonNull(GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config")).getInt("Scoreboard.Refresh-rate");
-}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
index e1a6899..10a090a 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
@@ -3,6 +3,7 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
+import dev.projectg.geyserhub.config.ConfigId;
 import org.bukkit.ChatColor;
 import org.bukkit.Material;
 import org.bukkit.configuration.file.FileConfiguration;
@@ -17,7 +18,7 @@ public class AccessItem {
 
     private static final ItemStack ACCESS_ITEM;
     static {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("selector");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
         Objects.requireNonNull(config);
 
         // Get the material
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
index d636a2e..e8ee77a 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
@@ -1,6 +1,7 @@
 package dev.projectg.geyserhub.module.menu;
 
 import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.config.ConfigId;
 import org.bukkit.Material;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
@@ -13,12 +14,11 @@
 
 import java.util.Objects;
 
-@SuppressWarnings("unused")
 public class CommonMenuListeners implements Listener {
 
     @EventHandler
     public void onInventoryClick(InventoryClickEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("selector");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
         Objects.requireNonNull(config);
         if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) {
             return;
@@ -30,7 +30,7 @@ public void onInventoryClick(InventoryClickEvent event) {
 
     @EventHandler
     public void onPlayerDropItem(PlayerDropItemEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("selector");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
         Objects.requireNonNull(config);
         if (event.getItemDrop().getItemStack().isSimilar(AccessItem.getItem())) {
             if (!config.getBoolean("Selector-Item.Allow-Drop")) {
@@ -43,7 +43,7 @@ public void onPlayerDropItem(PlayerDropItemEvent event) {
 
     @EventHandler
     public void onPlayerJoin(PlayerJoinEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("selector");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
         Objects.requireNonNull(config);
         event.getPlayer().getInventory().setHeldItemSlot(GeyserHubMain.getInstance().getConfig().getInt("Selector-Item.Slot"));
         if (config.getBoolean("Selector-Item.Join")) {
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
index 16760fc..252136d 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
@@ -1,6 +1,7 @@
 package dev.projectg.geyserhub.module.menu.bedrock;
 
 import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.reloadable.Reloadable;
 import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
 import dev.projectg.geyserhub.SelectorLogger;
@@ -37,7 +38,7 @@ public BedrockFormRegistry() {
     }
 
     private void load() {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("selector");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
         Objects.requireNonNull(config);
         SelectorLogger logger = SelectorLogger.getLogger();
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
index b559c0f..cb90701 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
@@ -1,6 +1,7 @@
 package dev.projectg.geyserhub.module.menu.bedrock;
 
 import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.module.menu.AccessItem;
 import dev.projectg.geyserhub.module.menu.java.JavaMenu;
 import org.bukkit.configuration.file.FileConfiguration;
@@ -17,7 +18,7 @@ public class BedrockMenuListeners implements Listener {
 
     @EventHandler
     public void onInteract(PlayerInteractEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("selector");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
         Objects.requireNonNull(config);
         Player player = event.getPlayer();
         if (player.getInventory().getItemInMainHand().isSimilar(AccessItem.getItem())) {
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index d59dd35..3866508 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -1,7 +1,7 @@
 package dev.projectg.geyserhub.module.menu.java;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.module.Placeholders;
+import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.SelectorLogger;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
@@ -20,10 +20,11 @@
 import java.util.List;
 import java.util.Objects;
 
-public class JavaMenu extends Placeholders {
+public class JavaMenu {
 
+    // todo: use this
     public static boolean isEnabled() {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("selector");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
         Objects.requireNonNull(config);
         return config.getBoolean("Java-Selector.Enabled", true);
     }
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
index 6a77ed9..4b4fcd4 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
@@ -2,6 +2,7 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
+import dev.projectg.geyserhub.config.ConfigId;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
@@ -14,9 +15,9 @@
 
 public class Broadcast {
     public static void startBroadcastTimer(BukkitScheduler scheduler) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
-        int scheduleId = scheduler.scheduleSyncDelayedTask(GeyserHubMain.getInstance(), () -> {
+        scheduler.scheduleSyncDelayedTask(GeyserHubMain.getInstance(), () -> {
 
             if (config.getBoolean("Broadcasts.Enable", false)) {
                 ConfigurationSection parentSection = config.getConfigurationSection("Broadcasts.Messages");
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java b/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
index 78b19bd..59ac1bf 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
@@ -1,6 +1,7 @@
 package dev.projectg.geyserhub.module.message;
 
 import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.config.ConfigId;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.ChatColor;
 import org.bukkit.configuration.file.FileConfiguration;
@@ -16,7 +17,7 @@ public class MessageJoin implements Listener {
 
     @EventHandler
     public void onJoin(PlayerJoinEvent e) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
         Player player = e.getPlayer();
         List<String> messages = config.getStringList("Join-Message.Messages");
diff --git a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
index 9e55f9a..e6c5be5 100644
--- a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
+++ b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
@@ -1,6 +1,7 @@
 package dev.projectg.geyserhub.module.scoreboard;
 
 import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.config.ConfigId;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
@@ -13,19 +14,18 @@
 
 
 public class ScoreboardManager {
-    public ScoreboardManager() {
-    }
+
+    public static final int refreshRate = Objects.requireNonNull(GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN)).getInt("Scoreboard.Refresh-rate");
 
     public static void addScoreboard() {
 
         for (Player all : Bukkit.getOnlinePlayers()) {
             createScoreboard(all);
         }
-
     }
 
     public static void createScoreboard(Player player) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
         Scoreboard board = Objects.requireNonNull(Bukkit.getServer().getScoreboardManager()).getNewScoreboard();
         Objective objective = board.registerNewObjective("GeyserHub", "dummy", PlaceholderAPI.setPlaceholders(player, config.getString("Scoreboard.Title", "GeyserHub")));
diff --git a/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java b/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
index ece2e68..5c4c847 100644
--- a/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
+++ b/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
@@ -1,6 +1,7 @@
 package dev.projectg.geyserhub.module.world;
 
 import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.config.ConfigId;
 import org.bukkit.ChatColor;
 import org.bukkit.Material;
 import org.bukkit.configuration.file.FileConfiguration;
@@ -23,7 +24,7 @@ public class WorldSettings implements Listener {
 
     @EventHandler
     public void onEntityDamage(EntityDamageEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
         if (!(event.getEntity() instanceof Player)) return;
 
@@ -44,7 +45,7 @@ else if (config.getBoolean("World-settings.disable-fire-damage")
 
     @EventHandler(priority = EventPriority.HIGH)
     public void onFoodChange(FoodLevelChangeEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-hunger-loss"))
             return;
@@ -55,7 +56,7 @@ public void onFoodChange(FoodLevelChangeEvent event) {
 
     @EventHandler
     public void onFireSpread(BlockIgniteEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-block-fire-spread"))
             return;
@@ -65,7 +66,7 @@ public void onFireSpread(BlockIgniteEvent event) {
 
     @EventHandler
     public void onBlockBurn(BlockBurnEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-disable-block-burn"))
             return;
@@ -74,7 +75,7 @@ public void onBlockBurn(BlockBurnEvent event) {
 
     @EventHandler(priority = EventPriority.HIGH)
     public void onLeafDecay(LeavesDecayEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable_block-leaf-decay"))
             return;
@@ -83,7 +84,7 @@ public void onLeafDecay(LeavesDecayEvent event) {
 
     @EventHandler(priority = EventPriority.HIGH)
     public void onCreatureSpawn(CreatureSpawnEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-mob-spawning"))
             return;
@@ -93,7 +94,7 @@ public void onCreatureSpawn(CreatureSpawnEvent event) {
 
     @EventHandler(priority = EventPriority.HIGH)
     public void onWeatherChange(WeatherChangeEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-weather-change"))
             return;
@@ -103,7 +104,7 @@ public void onWeatherChange(WeatherChangeEvent event) {
 
     @EventHandler(priority = EventPriority.HIGH)
     public void onEntityDamage(EntityDamageByEntityEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-player-pvp"))
             return;
@@ -113,7 +114,7 @@ public void onEntityDamage(EntityDamageByEntityEvent event) {
     }
     @EventHandler(priority = EventPriority.HIGH)
     public void onBlockBreak(BlockBreakEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-block-break")
                 || event.isCancelled())
@@ -129,7 +130,7 @@ public void onBlockBreak(BlockBreakEvent event) {
 
     @EventHandler(priority = EventPriority.HIGH)
     public void onBlockPlace(BlockPlaceEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-block-place")
                 || event.isCancelled())
diff --git a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
index a119aed..38dcc50 100644
--- a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
@@ -2,7 +2,8 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
-import dev.projectg.geyserhub.ConfigManager;
+import dev.projectg.geyserhub.config.ConfigId;
+import dev.projectg.geyserhub.config.ConfigManager;
 import org.bukkit.ChatColor;
 
 import javax.annotation.Nonnull;
@@ -36,7 +37,7 @@ public static boolean reloadAll() {
 
         ConfigManager configManager = GeyserHubMain.getInstance().getConfigManager();
         // loadConfiguration() will never remove a key so I don't think this will result in ConcurrentModificationException...
-        for (ConfigManager.ConfigId configId : configManager.getAllFileConfigurations().keySet()) {
+        for (ConfigId configId : configManager.getAllFileConfigurations().keySet()) {
             if (configManager.loadConfiguration(configId)) {
                 logger.debug("Reloaded config file: " + configId.fileName);
             } else {

From 4676933316a9bfa616ba38018287ce6c164e9edf Mon Sep 17 00:00:00 2001
From: Jens Collaert <jenscollaertprive@hotmail.com>
Date: Sun, 20 Jun 2021 12:20:00 +0200
Subject: [PATCH 29/74] Added info in selector.yml

---
 src/main/resources/selector.yml | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/src/main/resources/selector.yml b/src/main/resources/selector.yml
index efd6ca2..6a8d839 100644
--- a/src/main/resources/selector.yml
+++ b/src/main/resources/selector.yml
@@ -30,6 +30,7 @@ Java-Selector:
       Lore:
         - "&2Online players: %bungee_lobby%"
       Slot: 2
+
     survival:
       Display-Name: "Survival"
       Material: Emerald
@@ -41,30 +42,42 @@ Bedrock-Selector:
   Enable: true
 
   Forms:
+    # Name of the form, Do not change this!
     default:
+      # Title of the form.
       Title: "Server Selector"
+      # Content of the form.
       Content: "Click on the server button of choice."
+      # Button list, Do not change this!
       Buttons:
+        # Name of the section.
         lobby:
+          # Button text, Placeholders can be used in here.
           Button-Text: "Server Lobby: %bungee_lobby% players"
+          # Here you can set your own custom icon, This has to be an URL or you can delete it if not needed!
           ImageURL: "https://www.digminecraft.com/block_recipes/images/cyan_concrete.png"
+          # Server has to be exact the same (case sensitive) as in the bungeecord config!
           Server: "lobby"
+
         survival:
           Button-Text: "Survival: %bungee_survival% players"
           ImageURL: "https://www.digminecraft.com/block_recipes/images/blue_concrete.png"
-          Commands:
-            - "Survival is currently in development, please report any bugs to staff!"
           Server: "survival"
+
+        # You can also execute commands with buttons as seen below!
         commandOne:
           Button-Text: "Minigames"
           Image-URL: "https://www.digminecraft.com/weapon_recipes/images/diamond_sword.png"
           Commands:
             - "execute as %player_name% run ghub form minigames"
+
         commandTwo:
           Button-Text: "Spawn"
           ImageURL: "https://www.digminecraft.com/decoration_recipes/images/lodestone.png"
           Commands:
             - "execute as %player_name% run spawn"
+    # Minigames is an new form that can be set to anything. This form can be opened with
+    # /ghub form minigames as a command or as seen on "commandOne:"
     minigames:
       Title: "Minigames"
       Content: "Click on a minigame of choice."
@@ -73,6 +86,7 @@ Bedrock-Selector:
           Button-Text: "Spleef"
           ImageURL: "https://www.digminecraft.com/materials/images/snowball.png"
           Server: "spleef"
+
         hideseek:
           Button-Text: "Hide & Seek"
           Server: "hideseek"

From 214e0268e8ff1cb84b96d5b4e0fd55b499177744 Mon Sep 17 00:00:00 2001
From: Jens Collaert <jenscollaertprive@hotmail.com>
Date: Sun, 20 Jun 2021 12:25:01 +0200
Subject: [PATCH 30/74] Jointeleporter fix -> old config check

---
 .../projectg/geyserhub/module/teleporter/JoinTeleporter.java | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
index 2482a9f..fed5f84 100644
--- a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
+++ b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
@@ -1,6 +1,7 @@
 package dev.projectg.geyserhub.module.teleporter;
 
 import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.reloadable.Reloadable;
 import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
 import dev.projectg.geyserhub.SelectorLogger;
@@ -25,7 +26,7 @@ public class JoinTeleporter implements Listener, Reloadable {
     private Location location;
 
     public JoinTeleporter() {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
         ReloadableRegistry.registerReloadable(this);
         enabled = load(config);
@@ -94,7 +95,7 @@ private boolean load(@Nonnull FileConfiguration config) {
 
     @Override
     public boolean reload() {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration("config");
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Objects.requireNonNull(config);
         enabled = load(config);
         return enabled;

From f74a3ed20f025a20b8018762dd152307f2eb8f10 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Sun, 20 Jun 2021 09:44:16 -0400
Subject: [PATCH 31/74] fix config in java selector

---
 .../java/dev/projectg/geyserhub/GeyserHubMain.java  |  2 +-
 .../geyserhub/command/GeyserHubCommand.java         |  4 ++--
 .../projectg/geyserhub/config/ConfigManager.java    |  3 ---
 .../projectg/geyserhub/module/menu/AccessItem.java  |  1 -
 .../geyserhub/module/menu/CommonMenuListeners.java  |  3 ---
 .../module/menu/bedrock/BedrockFormRegistry.java    |  1 -
 .../module/menu/bedrock/BedrockMenuListeners.java   |  8 +-------
 .../geyserhub/module/menu/java/JavaMenu.java        |  7 +++++--
 .../geyserhub/module/message/Broadcast.java         |  1 -
 .../geyserhub/module/message/MessageJoin.java       |  2 --
 .../module/scoreboard/ScoreboardManager.java        |  3 +--
 .../geyserhub/module/teleporter/JoinTeleporter.java |  4 +---
 .../geyserhub/module/world/WorldSettings.java       | 13 -------------
 .../projectg/geyserhub/utils/bstats/Metrics.java    |  2 +-
 14 files changed, 12 insertions(+), 42 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 5dc8ac5..b9b83a1 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -90,7 +90,7 @@ public void initializeScoreboard() {
             } catch (Exception var2) {
                 var2.printStackTrace();
             }
-        }, 20L, ScoreboardManager.refreshRate * 20L);
+        }, 20L, ScoreboardManager.REFRESH_RATE * 20L);
     }
 
     public static GeyserHubMain getInstance() {
diff --git a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
index 7f2b37d..bca01e9 100644
--- a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
+++ b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
@@ -1,6 +1,6 @@
 package dev.projectg.geyserhub.command;
 
-import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
 import dev.projectg.geyserhub.module.menu.bedrock.BedrockFormRegistry;
 import dev.projectg.geyserhub.module.menu.java.JavaMenu;
@@ -102,7 +102,7 @@ private void sendForm(@Nonnull CommandSender commandSender, @Nonnull String form
                     player.sendMessage("Sorry, Bedrock forms are disabled!");
                 }
             } else {
-                JavaMenu.openMenu(player, GeyserHubMain.getInstance().getConfig());
+                JavaMenu.openMenu(player, ConfigId.SELECTOR);
             }
         } else if (commandSender instanceof ConsoleCommandSender) {
             sendHelp(commandSender);
diff --git a/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java b/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
index 1444956..88c7ee8 100644
--- a/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
+++ b/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
@@ -38,8 +38,6 @@ public ConfigManager() {
      * @return The success state
      */
     public boolean loadConfiguration(@Nonnull ConfigId config) {
-        Objects.requireNonNull(config);
-
         GeyserHubMain plugin = GeyserHubMain.getInstance();
 
         File file = new File(plugin.getDataFolder(), config.fileName);
@@ -84,7 +82,6 @@ public boolean loadConfiguration(@Nonnull ConfigId config) {
      * @param config the config ID
      * @return the FileConfiguration
      */
-    @Nullable
     public FileConfiguration getFileConfiguration(@Nonnull ConfigId config) {
         Objects.requireNonNull(config);
         return configurations.get(config);
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
index 10a090a..5af5cb2 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
@@ -19,7 +19,6 @@ public class AccessItem {
     private static final ItemStack ACCESS_ITEM;
     static {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
-        Objects.requireNonNull(config);
 
         // Get the material
         Material material;
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
index e8ee77a..d9aba81 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
@@ -19,7 +19,6 @@ public class CommonMenuListeners implements Listener {
     @EventHandler
     public void onInventoryClick(InventoryClickEvent event) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
-        Objects.requireNonNull(config);
         if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) {
             return;
         }
@@ -31,7 +30,6 @@ public void onInventoryClick(InventoryClickEvent event) {
     @EventHandler
     public void onPlayerDropItem(PlayerDropItemEvent event) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
-        Objects.requireNonNull(config);
         if (event.getItemDrop().getItemStack().isSimilar(AccessItem.getItem())) {
             if (!config.getBoolean("Selector-Item.Allow-Drop")) {
                 event.setCancelled(true);
@@ -44,7 +42,6 @@ public void onPlayerDropItem(PlayerDropItemEvent event) {
     @EventHandler
     public void onPlayerJoin(PlayerJoinEvent event) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
-        Objects.requireNonNull(config);
         event.getPlayer().getInventory().setHeldItemSlot(GeyserHubMain.getInstance().getConfig().getInt("Selector-Item.Slot"));
         if (config.getBoolean("Selector-Item.Join")) {
             Player player = event.getPlayer();
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
index 252136d..a00fab0 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
@@ -39,7 +39,6 @@ public BedrockFormRegistry() {
 
     private void load() {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
-        Objects.requireNonNull(config);
         SelectorLogger logger = SelectorLogger.getLogger();
 
         enabledForms.clear();
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
index cb90701..4e6d29d 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
@@ -1,10 +1,8 @@
 package dev.projectg.geyserhub.module.menu.bedrock;
 
-import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.module.menu.AccessItem;
 import dev.projectg.geyserhub.module.menu.java.JavaMenu;
-import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
@@ -12,21 +10,17 @@
 import org.bukkit.event.player.PlayerInteractEvent;
 import org.geysermc.floodgate.api.FloodgateApi;
 
-import java.util.Objects;
-
 public class BedrockMenuListeners implements Listener {
 
     @EventHandler
     public void onInteract(PlayerInteractEvent event) {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
-        Objects.requireNonNull(config);
         Player player = event.getPlayer();
         if (player.getInventory().getItemInMainHand().isSimilar(AccessItem.getItem())) {
             if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
                 if (FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {
                     BedrockFormRegistry.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()), BedrockFormRegistry.DEFAULT);
                 } else {
-                    JavaMenu.openMenu(player, config);
+                    JavaMenu.openMenu(player, ConfigId.SELECTOR);
                 }
             }
         }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index 3866508..076ef20 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -3,6 +3,7 @@
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.SelectorLogger;
+import dev.projectg.geyserhub.config.ConfigManager;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
@@ -25,12 +26,14 @@ public class JavaMenu {
     // todo: use this
     public static boolean isEnabled() {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
-        Objects.requireNonNull(config);
         return config.getBoolean("Java-Selector.Enabled", true);
     }
 
     // todo: maybe just remove the FileConfiguration parameter
-    public static void openMenu(@Nonnull Player player, @Nonnull FileConfiguration config) {
+    public static void openMenu(@Nonnull Player player, @Nonnull ConfigId configId) {
+        Objects.requireNonNull(configId);
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(configId);
+
         SelectorLogger logger = SelectorLogger.getLogger();
 
         // Get the java selector section
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
index 4b4fcd4..84e8eab 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
@@ -16,7 +16,6 @@
 public class Broadcast {
     public static void startBroadcastTimer(BukkitScheduler scheduler) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         scheduler.scheduleSyncDelayedTask(GeyserHubMain.getInstance(), () -> {
 
             if (config.getBoolean("Broadcasts.Enable", false)) {
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java b/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
index 59ac1bf..d892269 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
@@ -11,14 +11,12 @@
 import org.bukkit.event.player.PlayerJoinEvent;
 
 import java.util.List;
-import java.util.Objects;
 
 public class MessageJoin implements Listener {
 
     @EventHandler
     public void onJoin(PlayerJoinEvent e) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         Player player = e.getPlayer();
         List<String> messages = config.getStringList("Join-Message.Messages");
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
index e6c5be5..6c4a3a1 100644
--- a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
+++ b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
@@ -15,7 +15,7 @@
 
 public class ScoreboardManager {
 
-    public static final int refreshRate = Objects.requireNonNull(GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN)).getInt("Scoreboard.Refresh-rate");
+    public static final int REFRESH_RATE = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN).getInt("Scoreboard.Refresh-rate");
 
     public static void addScoreboard() {
 
@@ -26,7 +26,6 @@ public static void addScoreboard() {
 
     public static void createScoreboard(Player player) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         Scoreboard board = Objects.requireNonNull(Bukkit.getServer().getScoreboardManager()).getNewScoreboard();
         Objective objective = board.registerNewObjective("GeyserHub", "dummy", PlaceholderAPI.setPlaceholders(player, config.getString("Scoreboard.Title", "GeyserHub")));
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
index fed5f84..9863cc4 100644
--- a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
+++ b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
@@ -27,7 +27,6 @@ public class JoinTeleporter implements Listener, Reloadable {
 
     public JoinTeleporter() {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         ReloadableRegistry.registerReloadable(this);
         enabled = load(config);
     }
@@ -88,7 +87,7 @@ private boolean load(@Nonnull FileConfiguration config) {
                 throw new AssertionError("Failed to decompose the following coordinates: " + composedCoords + " -> " + Arrays.toString(coordinates));
             }
         } else {
-            logger.severe("Join-Teleporter config section does not contain a valid Location value!");
+            logger.severe("Join-Teleporter config section does not contain a Location value or it is not a string!");
             return false;
         }
     }
@@ -96,7 +95,6 @@ private boolean load(@Nonnull FileConfiguration config) {
     @Override
     public boolean reload() {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         enabled = load(config);
         return enabled;
     }
diff --git a/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java b/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
index 5c4c847..e9772c8 100644
--- a/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
+++ b/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
@@ -17,15 +17,11 @@
 import org.bukkit.event.weather.WeatherChangeEvent;
 import org.bukkit.inventory.ItemStack;
 
-import java.util.Objects;
-
-
 public class WorldSettings implements Listener {
 
     @EventHandler
     public void onEntityDamage(EntityDamageEvent event) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         if (!(event.getEntity() instanceof Player)) return;
 
         if (config.getBoolean("World-settings.disable-fall-damage")
@@ -46,7 +42,6 @@ else if (config.getBoolean("World-settings.disable-fire-damage")
     @EventHandler(priority = EventPriority.HIGH)
     public void onFoodChange(FoodLevelChangeEvent event) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-hunger-loss"))
             return;
         if (!(event.getEntity() instanceof Player))
@@ -57,7 +52,6 @@ public void onFoodChange(FoodLevelChangeEvent event) {
     @EventHandler
     public void onFireSpread(BlockIgniteEvent event) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-block-fire-spread"))
             return;
         if (event.getCause() == BlockIgniteEvent.IgniteCause.SPREAD)
@@ -67,7 +61,6 @@ public void onFireSpread(BlockIgniteEvent event) {
     @EventHandler
     public void onBlockBurn(BlockBurnEvent event) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-disable-block-burn"))
             return;
         event.setCancelled(true);
@@ -76,7 +69,6 @@ public void onBlockBurn(BlockBurnEvent event) {
     @EventHandler(priority = EventPriority.HIGH)
     public void onLeafDecay(LeavesDecayEvent event) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable_block-leaf-decay"))
             return;
         event.setCancelled(true);
@@ -85,7 +77,6 @@ public void onLeafDecay(LeavesDecayEvent event) {
     @EventHandler(priority = EventPriority.HIGH)
     public void onCreatureSpawn(CreatureSpawnEvent event) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-mob-spawning"))
             return;
         if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.CUSTOM) return;
@@ -95,7 +86,6 @@ public void onCreatureSpawn(CreatureSpawnEvent event) {
     @EventHandler(priority = EventPriority.HIGH)
     public void onWeatherChange(WeatherChangeEvent event) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-weather-change"))
             return;
 
@@ -105,7 +95,6 @@ public void onWeatherChange(WeatherChangeEvent event) {
     @EventHandler(priority = EventPriority.HIGH)
     public void onEntityDamage(EntityDamageByEntityEvent event) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-player-pvp"))
             return;
         if (!(event.getEntity() instanceof Player)) return;
@@ -115,7 +104,6 @@ public void onEntityDamage(EntityDamageByEntityEvent event) {
     @EventHandler(priority = EventPriority.HIGH)
     public void onBlockBreak(BlockBreakEvent event) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-block-break")
                 || event.isCancelled())
             return;
@@ -131,7 +119,6 @@ public void onBlockBreak(BlockBreakEvent event) {
     @EventHandler(priority = EventPriority.HIGH)
     public void onBlockPlace(BlockPlaceEvent event) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        Objects.requireNonNull(config);
         if (!config.getBoolean("World-settings.disable-block-place")
                 || event.isCancelled())
             return;
diff --git a/src/main/java/dev/projectg/geyserhub/utils/bstats/Metrics.java b/src/main/java/dev/projectg/geyserhub/utils/bstats/Metrics.java
index d5fc47d..40526b7 100644
--- a/src/main/java/dev/projectg/geyserhub/utils/bstats/Metrics.java
+++ b/src/main/java/dev/projectg/geyserhub/utils/bstats/Metrics.java
@@ -58,7 +58,7 @@ public class Metrics {
     private static final String URL = "https://bStats.org/submitData/bukkit";
 
     // Is bStats enabled on this server?
-    private boolean enabled;
+    private final boolean enabled;
 
     // Should failed requests be logged?
     private static boolean logFailedRequests;

From 46f5279e643618f23106bee861044154fa8ecf75 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Sun, 20 Jun 2021 09:50:12 -0400
Subject: [PATCH 32/74] fix config file generation

---
 .../projectg/geyserhub/config/ConfigManager.java   | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java b/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
index 88c7ee8..ae908b4 100644
--- a/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
+++ b/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
@@ -42,14 +42,16 @@ public boolean loadConfiguration(@Nonnull ConfigId config) {
 
         File file = new File(plugin.getDataFolder(), config.fileName);
         if (!file.exists()) {
-            try {
-                if (!file.getParentFile().mkdirs()) {
-                    logger.severe("Failed to create plugin folder!");
+            if (!file.getParentFile().exists()) {
+                try {
+                    if (!file.getParentFile().mkdirs()) {
+                        logger.severe("Failed to create plugin folder!");
+                        return false;
+                    }
+                } catch (SecurityException e) {
+                    e.printStackTrace();
                     return false;
                 }
-            } catch (SecurityException e) {
-                e.printStackTrace();
-                return false;
             }
             plugin.saveResource(config.fileName, false);
         }

From 392475cdf9297a997ef8e761b1a42dd1ae75faab Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Sun, 20 Jun 2021 11:43:31 -0400
Subject: [PATCH 33/74] fix form command for linked floodgate players

---
 .../java/dev/projectg/geyserhub/command/GeyserHubCommand.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
index bca01e9..9085386 100644
--- a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
+++ b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
@@ -91,7 +91,7 @@ private void sendForm(@Nonnull CommandSender commandSender, @Nonnull String form
         if (commandSender instanceof Player) {
             Player player = (Player) commandSender;
             UUID uuid = player.getUniqueId();
-            if (FloodgateApi.getInstance().isFloodgateId(uuid)) {
+            if (FloodgateApi.getInstance().isFloodgatePlayer(uuid)) {
                 if (BedrockFormRegistry.getInstance().isEnabled()) {
                     if (BedrockFormRegistry.getInstance().getFormNames().contains(formName)) {
                         BedrockFormRegistry.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(uuid), formName);

From 61d476c9f63f0a53e26f130046924ef641bf0990 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+konicai@users.noreply.github.com>
Date: Sun, 20 Jun 2021 12:47:34 -0400
Subject: [PATCH 34/74] move open menu listener

---
 .../dev/projectg/geyserhub/GeyserHubMain.java |  6 ----
 .../module/menu/CommonMenuListeners.java      | 25 +++++++++++++++--
 .../menu/bedrock/BedrockMenuListeners.java    | 28 -------------------
 3 files changed, 22 insertions(+), 37 deletions(-)
 delete mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index b9b83a1..55a5b9a 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -4,7 +4,6 @@
 import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.config.ConfigManager;
 import dev.projectg.geyserhub.module.menu.CommonMenuListeners;
-import dev.projectg.geyserhub.module.menu.bedrock.BedrockMenuListeners;
 import dev.projectg.geyserhub.module.menu.java.JavaMenuListeners;
 import dev.projectg.geyserhub.module.menu.bedrock.BedrockFormRegistry;
 import dev.projectg.geyserhub.module.message.Broadcast;
@@ -63,7 +62,6 @@ public void onEnable() {
         Objects.requireNonNull(getCommand("ghub")).setExecutor(new GeyserHubCommand());
 
         Bukkit.getServer().getPluginManager().registerEvents(new CommonMenuListeners(), this);
-        Bukkit.getServer().getPluginManager().registerEvents(new BedrockMenuListeners(), this);
         Bukkit.getServer().getPluginManager().registerEvents(new JavaMenuListeners(), this);
 
         Bukkit.getServer().getPluginManager().registerEvents(new JoinTeleporter(), this);
@@ -79,10 +77,6 @@ public void onEnable() {
         Broadcast.startBroadcastTimer(getServer().getScheduler());
     }
 
-    @Override
-    public void onDisable() {
-    }
-
     public void initializeScoreboard() {
         Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
             try {
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
index d9aba81..0c6149c 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
@@ -2,22 +2,41 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.config.ConfigId;
+import dev.projectg.geyserhub.module.menu.bedrock.BedrockFormRegistry;
+import dev.projectg.geyserhub.module.menu.java.JavaMenu;
 import org.bukkit.Material;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
+import org.bukkit.event.block.Action;
 import org.bukkit.event.inventory.InventoryClickEvent;
 import org.bukkit.event.player.PlayerDropItemEvent;
+import org.bukkit.event.player.PlayerInteractEvent;
 import org.bukkit.event.player.PlayerJoinEvent;
 import org.bukkit.inventory.ItemStack;
+import org.geysermc.floodgate.api.FloodgateApi;
 
 import java.util.Objects;
 
 public class CommonMenuListeners implements Listener {
 
     @EventHandler
-    public void onInventoryClick(InventoryClickEvent event) {
+    public void onInteract(PlayerInteractEvent event) { // open the menu through the access item
+        Player player = event.getPlayer();
+        if (player.getInventory().getItemInMainHand().isSimilar(AccessItem.getItem())) {
+            if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
+                if (FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {
+                    BedrockFormRegistry.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()), BedrockFormRegistry.DEFAULT);
+                } else {
+                    JavaMenu.openMenu(player, ConfigId.SELECTOR);
+                }
+            }
+        }
+    }
+
+    @EventHandler
+    public void onInventoryClick(InventoryClickEvent event) { // keep the access item in place
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
         if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) {
             return;
@@ -28,7 +47,7 @@ public void onInventoryClick(InventoryClickEvent event) {
     }
 
     @EventHandler
-    public void onPlayerDropItem(PlayerDropItemEvent event) {
+    public void onPlayerDropItem(PlayerDropItemEvent event) { // dont let the access item be dropped
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
         if (event.getItemDrop().getItemStack().isSimilar(AccessItem.getItem())) {
             if (!config.getBoolean("Selector-Item.Allow-Drop")) {
@@ -40,7 +59,7 @@ public void onPlayerDropItem(PlayerDropItemEvent event) {
     }
 
     @EventHandler
-    public void onPlayerJoin(PlayerJoinEvent event) {
+    public void onPlayerJoin(PlayerJoinEvent event) { // give the access item when the player joins
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
         event.getPlayer().getInventory().setHeldItemSlot(GeyserHubMain.getInstance().getConfig().getInt("Selector-Item.Slot"));
         if (config.getBoolean("Selector-Item.Join")) {
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
deleted file mode 100644
index 4e6d29d..0000000
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockMenuListeners.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package dev.projectg.geyserhub.module.menu.bedrock;
-
-import dev.projectg.geyserhub.config.ConfigId;
-import dev.projectg.geyserhub.module.menu.AccessItem;
-import dev.projectg.geyserhub.module.menu.java.JavaMenu;
-import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.Listener;
-import org.bukkit.event.block.Action;
-import org.bukkit.event.player.PlayerInteractEvent;
-import org.geysermc.floodgate.api.FloodgateApi;
-
-public class BedrockMenuListeners implements Listener {
-
-    @EventHandler
-    public void onInteract(PlayerInteractEvent event) {
-        Player player = event.getPlayer();
-        if (player.getInventory().getItemInMainHand().isSimilar(AccessItem.getItem())) {
-            if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
-                if (FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {
-                    BedrockFormRegistry.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()), BedrockFormRegistry.DEFAULT);
-                } else {
-                    JavaMenu.openMenu(player, ConfigId.SELECTOR);
-                }
-            }
-        }
-    }
-}

From 8d3fff6a9ad8b286d45dc708f15e486730665284 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+konicai@users.noreply.github.com>
Date: Sun, 20 Jun 2021 13:11:29 -0400
Subject: [PATCH 35/74] create reloadALlConfigs() method in ConfigManager

---
 .../dev/projectg/geyserhub/GeyserHubMain.java  |  8 ++------
 .../geyserhub/config/ConfigManager.java        | 18 ++++++++++++++++--
 .../reloadable/ReloadableRegistry.java         |  2 +-
 3 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 55a5b9a..720439d 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -44,12 +44,8 @@ public void onEnable() {
         }
 
         configManager = new ConfigManager();
-        if (!configManager.loadConfiguration(ConfigId.MAIN)) {
-            logger.severe("Disabling due to configuration error in " + ConfigId.MAIN.fileName + " - Fix the formatting or regenerate a new file.");
-            return;
-        }
-        if (!configManager.loadConfiguration(ConfigId.SELECTOR)) {
-            logger.severe("Disabling due to configuration error in " + ConfigId.SELECTOR.fileName + " - Fix the formatting or regenerate a new file.");
+        if (!configManager.loadAllConfigs()) {
+            logger.severe("Disabling due to configuration error.");
             return;
         }
 
diff --git a/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java b/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
index ae908b4..34ac504 100644
--- a/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
+++ b/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
@@ -7,7 +7,6 @@
 import org.bukkit.configuration.file.YamlConfiguration;
 
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
 import java.io.File;
 import java.io.IOException;
 import java.util.HashMap;
@@ -32,12 +31,27 @@ public ConfigManager() {
         this.logger = SelectorLogger.getLogger();
     }
 
+    /**
+     * Load every config in {@link ConfigId}
+     * @return false if there was a failure loading any of configurations
+     */
+    public boolean loadAllConfigs() {
+        boolean failOccurred = false;
+        for (ConfigId configId : ConfigId.VALUES) {
+            if (!loadConfig(configId)) {
+                failOccurred = true;
+                logger.severe("Configuration error in " + ConfigId.MAIN.fileName + " - Fix the issue or regenerate a new file.");
+            }
+        }
+        return failOccurred;
+    }
+
     /**
      * Load a configuration from file.
      * @param config The configuration to load
      * @return The success state
      */
-    public boolean loadConfiguration(@Nonnull ConfigId config) {
+    public boolean loadConfig(@Nonnull ConfigId config) {
         GeyserHubMain plugin = GeyserHubMain.getInstance();
 
         File file = new File(plugin.getDataFolder(), config.fileName);
diff --git a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
index 38dcc50..0e796b9 100644
--- a/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/reloadable/ReloadableRegistry.java
@@ -38,7 +38,7 @@ public static boolean reloadAll() {
         ConfigManager configManager = GeyserHubMain.getInstance().getConfigManager();
         // loadConfiguration() will never remove a key so I don't think this will result in ConcurrentModificationException...
         for (ConfigId configId : configManager.getAllFileConfigurations().keySet()) {
-            if (configManager.loadConfiguration(configId)) {
+            if (configManager.loadConfig(configId)) {
                 logger.debug("Reloaded config file: " + configId.fileName);
             } else {
                 logger.severe(ChatColor.RED + "Failed to reload configuration: " + configId.fileName);

From e4f94002d18d126fb6c136da6b132fb6469828b2 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+konicai@users.noreply.github.com>
Date: Sun, 20 Jun 2021 13:34:50 -0400
Subject: [PATCH 36/74] remove redundant Objects.requireNonNull()

---
 .../dev/projectg/geyserhub/module/menu/CommonMenuListeners.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
index 0c6149c..8460bc4 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
@@ -41,7 +41,7 @@ public void onInventoryClick(InventoryClickEvent event) { // keep the access ite
         if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) {
             return;
         }
-        if (!config.getBoolean("Selector-Item.Allow-Move") && Objects.requireNonNull(event.getCurrentItem()).isSimilar(AccessItem.getItem())) {
+        if (!config.getBoolean("Selector-Item.Allow-Move") && event.getCurrentItem().isSimilar(AccessItem.getItem())) {
                 event.setCancelled(true);
             }
     }

From d5b4b3ea019d83c6756896f5369d277386571d57 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+konicai@users.noreply.github.com>
Date: Sun, 20 Jun 2021 13:56:00 -0400
Subject: [PATCH 37/74] update java config for future multi menu implementation

---
 src/main/resources/selector.yml | 72 +++++++++++++++++++++++++--------
 1 file changed, 55 insertions(+), 17 deletions(-)

diff --git a/src/main/resources/selector.yml b/src/main/resources/selector.yml
index 6a8d839..fede1be 100644
--- a/src/main/resources/selector.yml
+++ b/src/main/resources/selector.yml
@@ -20,23 +20,61 @@ Selector-Item:
 Java-Selector:
   Enable: true
 
-  Title: "Server Selector"
-  Size: 9
-
-  Servers:
-    lobby:
-      Display-Name: "Lobby"
-      Material: Diamond
-      Lore:
-        - "&2Online players: %bungee_lobby%"
-      Slot: 2
-
-    survival:
-      Display-Name: "Survival"
-      Material: Emerald
-      Lore:
-        - "&2online players: %bungee_survival%"
-      Slot: 6
+  Menus:
+    default:
+      Title: "Server Selector"
+      Size: 9
+      Buttons:
+        2:
+          Display-Name: "Lobby"
+          Material: Diamond
+          Lore:
+            - "&2Online players: %bungee_lobby%"
+          Right-Click:
+            Server: "lobby"
+            Commands:
+              - "tell %player_name% Sending you to the lobby in a right click fashion..."
+          Left-Click:
+            Server: "lobby"
+            Commands:
+              - "tell %player_name% Sending you to the lobby in a left click fashion..."
+        4:
+          Display-Name: "Minigames"
+          Material: Grass
+          Lore:
+            - "Currently Available:"
+            - "Spleef"
+            - "Hide & Seek"
+          Any-Click:
+            Commands:
+              - "execute as %player_name% run ghub form minigames"
+        6:
+          Display-Name: "Survival"
+          Material: Emerald
+          Lore:
+            - "&2online players: %bungee_survival%"
+          Any-Click:
+            Server: "survival"
+            Commands:
+              - "tell %player_name% Sending you to Survival..."
+    minigames:
+      Title: "Server Selector"
+      Size: 3
+      Buttons:
+        0:
+          Display-Name: "Spleef"
+          Material: SNOWBALL
+          Lore:
+            - "Play Spleef!"
+          Any-Click:
+            Server: "spleef"
+        2:
+          Display-Name: "Hide & Seek"
+          Material: DIRT
+          Lore:
+            - "Play Hide & Seek!"
+          Any-Click:
+            Server: "hideseek"
 
 Bedrock-Selector:
   Enable: true

From 693bed652cfb87a39ee25222320d6baf4faebb00 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Sun, 20 Jun 2021 15:18:07 -0400
Subject: [PATCH 38/74] fix loadAllConfigs()

---
 .../java/dev/projectg/geyserhub/config/ConfigManager.java | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java b/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
index 34ac504..1b1aadf 100644
--- a/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
+++ b/src/main/java/dev/projectg/geyserhub/config/ConfigManager.java
@@ -36,14 +36,14 @@ public ConfigManager() {
      * @return false if there was a failure loading any of configurations
      */
     public boolean loadAllConfigs() {
-        boolean failOccurred = false;
+        boolean totalSuccess = true;
         for (ConfigId configId : ConfigId.VALUES) {
             if (!loadConfig(configId)) {
-                failOccurred = true;
+                totalSuccess = false;
                 logger.severe("Configuration error in " + ConfigId.MAIN.fileName + " - Fix the issue or regenerate a new file.");
             }
         }
-        return failOccurred;
+        return totalSuccess;
     }
 
     /**
@@ -88,7 +88,7 @@ public boolean loadConfig(@Nonnull ConfigId config) {
         } else {
             plugin.reloadConfig();
             this.configurations.put(config, configuration);
-            logger.debug("Loaded configuration successfully");
+            logger.debug("Loaded configuration " + config.fileName + " successfully");
             return true;
         }
     }

From 95b3e936455b689375ee137e436c13a18ed33d4d Mon Sep 17 00:00:00 2001
From: Konicai <71294714+konicai@users.noreply.github.com>
Date: Mon, 21 Jun 2021 16:50:21 -0400
Subject: [PATCH 39/74] hopefully fix placeholders being permanently set

---
 .../geyserhub/module/menu/Button.java         | 41 +++++++++++--------
 .../module/menu/bedrock/BedrockForm.java      |  9 ++--
 2 files changed, 30 insertions(+), 20 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/Button.java b/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
index 27cf194..07f85c2 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
@@ -5,6 +5,7 @@
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
@@ -26,6 +27,17 @@ public Button(@Nonnull String text) {
         this.text = Objects.requireNonNull(text);
     }
 
+    /**
+     * Copy constructor
+     * @param button The button to make a copy of
+     */
+    public Button(@Nonnull Button button) {
+        this.text = button.text;
+        this.image = button.image;
+        this.commands = button.getCommands(); // lists are mutable, everything else here isn't
+        this.server = button.server;
+    }
+
     /**
      * Set the text of the button.
      * @param text the new text
@@ -46,24 +58,19 @@ public Button setImage(@Nullable FormImage image) {
         return this;
     }
 
-    /**
-     * Set the image of the button.
-     * @param type the type of image
-     * @param data the image data
-     * @return the same Button instance
-     */
-    public Button setImage(@Nonnull FormImage.Type type, @Nonnull String data) {
-        this.image = FormImage.of(Objects.requireNonNull(type), Objects.requireNonNull(data));
-        return this;
-    }
-
     /**
      * set the commands list.
      * @param commands the commands list
      * @return the same Button instance
      */
     public Button setCommands(@Nullable List<String> commands) {
-        this.commands = commands;
+        if (commands == null) {
+            this.commands = null;
+        } else {
+            this.commands = new ArrayList<>(commands);
+        }
+        this.commands = commands == null ? null : new ArrayList<>(commands);
+
         return this;
     }
 
@@ -82,26 +89,26 @@ public Button setServer(@Nullable String server) {
      * @return the button component
      */
     public ButtonComponent getButtonComponent() {
-        return ButtonComponent.of(text, image);
+        return ButtonComponent.of(text, image); //both are immutable
     }
 
     @Nonnull
     public String getText() {
-        return this.text;
+        return this.text; // Strings are immutable
     }
 
     @Nullable
     public FormImage getImage() {
-        return this.image;
+        return this.image; // Form image is immutable
     }
 
     @Nullable
     public List<String> getCommands() {
-        return this.commands;
+        return new ArrayList<>(this.commands); // Lists are mutable
     }
 
     @Nullable
     public String getServer() {
-        return this.server;
+        return this.server; // Strings are immutable
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index 5638ca4..4048a6a 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -183,10 +183,13 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
         }
 
         // Resolve any placeholders in the button text
-        List<Button> formattedButtons = new ArrayList<>(allButtons);
-        for (Button button : formattedButtons) {
-            button.setText(PlaceholderAPI.setPlaceholders(player, button.getText()));
+        List<Button> formattedButtons = new ArrayList<>();
+        for (Button rawButton : allButtons) {
+            Button copiedButton = new Button(rawButton);
+            copiedButton.setText(PlaceholderAPI.setPlaceholders(player, copiedButton.getText()));
+            formattedButtons.add(copiedButton);
         }
+
         // Create the form
         SimpleForm serverSelector = SimpleForm.of(title, content, formattedButtons.stream().map(Button::getButtonComponent).collect(Collectors.toList()));
 

From 29f2028e3acfea2aa1398af928cad05ccc68f2e7 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+konicai@users.noreply.github.com>
Date: Mon, 21 Jun 2021 16:58:06 -0400
Subject: [PATCH 40/74] Revert "update java config for future multi menu
 implementation"

This reverts commit d5b4b3ea
---
 src/main/resources/selector.yml | 72 ++++++++-------------------------
 1 file changed, 17 insertions(+), 55 deletions(-)

diff --git a/src/main/resources/selector.yml b/src/main/resources/selector.yml
index fede1be..6a8d839 100644
--- a/src/main/resources/selector.yml
+++ b/src/main/resources/selector.yml
@@ -20,61 +20,23 @@ Selector-Item:
 Java-Selector:
   Enable: true
 
-  Menus:
-    default:
-      Title: "Server Selector"
-      Size: 9
-      Buttons:
-        2:
-          Display-Name: "Lobby"
-          Material: Diamond
-          Lore:
-            - "&2Online players: %bungee_lobby%"
-          Right-Click:
-            Server: "lobby"
-            Commands:
-              - "tell %player_name% Sending you to the lobby in a right click fashion..."
-          Left-Click:
-            Server: "lobby"
-            Commands:
-              - "tell %player_name% Sending you to the lobby in a left click fashion..."
-        4:
-          Display-Name: "Minigames"
-          Material: Grass
-          Lore:
-            - "Currently Available:"
-            - "Spleef"
-            - "Hide & Seek"
-          Any-Click:
-            Commands:
-              - "execute as %player_name% run ghub form minigames"
-        6:
-          Display-Name: "Survival"
-          Material: Emerald
-          Lore:
-            - "&2online players: %bungee_survival%"
-          Any-Click:
-            Server: "survival"
-            Commands:
-              - "tell %player_name% Sending you to Survival..."
-    minigames:
-      Title: "Server Selector"
-      Size: 3
-      Buttons:
-        0:
-          Display-Name: "Spleef"
-          Material: SNOWBALL
-          Lore:
-            - "Play Spleef!"
-          Any-Click:
-            Server: "spleef"
-        2:
-          Display-Name: "Hide & Seek"
-          Material: DIRT
-          Lore:
-            - "Play Hide & Seek!"
-          Any-Click:
-            Server: "hideseek"
+  Title: "Server Selector"
+  Size: 9
+
+  Servers:
+    lobby:
+      Display-Name: "Lobby"
+      Material: Diamond
+      Lore:
+        - "&2Online players: %bungee_lobby%"
+      Slot: 2
+
+    survival:
+      Display-Name: "Survival"
+      Material: Emerald
+      Lore:
+        - "&2online players: %bungee_survival%"
+      Slot: 6
 
 Bedrock-Selector:
   Enable: true

From ee05e6d3e410c2c71854e9246b9a44b75366e7ad Mon Sep 17 00:00:00 2001
From: Konicai <71294714+konicai@users.noreply.github.com>
Date: Tue, 22 Jun 2021 15:24:30 -0400
Subject: [PATCH 41/74] fix NPE with button#getCommands()

---
 src/main/java/dev/projectg/geyserhub/module/menu/Button.java | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/Button.java b/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
index 07f85c2..63b1a1d 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
@@ -6,6 +6,7 @@
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -16,7 +17,7 @@ public class Button {
     private FormImage image;
 
     // Everything extra
-    private List<String> commands;
+    private List<String> commands = Collections.emptyList();
     private String server;
 
     /**

From 7d42194caab98272475192aec4534615874d971e Mon Sep 17 00:00:00 2001
From: Konicai <71294714+konicai@users.noreply.github.com>
Date: Tue, 22 Jun 2021 16:03:53 -0400
Subject: [PATCH 42/74] improve null safety in Bedrock forms and Button

---
 .../geyserhub/module/menu/Button.java         | 29 ++++++++++---------
 .../module/menu/bedrock/BedrockForm.java      | 18 +++++-------
 .../menu/bedrock/BedrockFormRegistry.java     |  2 ++
 3 files changed, 25 insertions(+), 24 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/Button.java b/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
index 63b1a1d..429f2a8 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
@@ -1,5 +1,6 @@
 package dev.projectg.geyserhub.module.menu;
 
+import edu.umd.cs.findbugs.annotations.NonNull;
 import org.geysermc.cumulus.component.ButtonComponent;
 import org.geysermc.cumulus.util.FormImage;
 
@@ -13,12 +14,12 @@
 public class Button {
 
     // base requirement for ButtonComponent
-    private String text;
-    private FormImage image;
+    @Nonnull private String text;
+    @Nullable private FormImage image;
 
     // Everything extra
-    private List<String> commands = Collections.emptyList();
-    private String server;
+    @Nonnull private List<String> commands = Collections.emptyList();
+    @Nullable private String server;
 
     /**
      * Create a button.
@@ -61,17 +62,12 @@ public Button setImage(@Nullable FormImage image) {
 
     /**
      * set the commands list.
-     * @param commands the commands list
+     * @param commands the commands list, which can be empty.
      * @return the same Button instance
      */
-    public Button setCommands(@Nullable List<String> commands) {
-        if (commands == null) {
-            this.commands = null;
-        } else {
-            this.commands = new ArrayList<>(commands);
-        }
-        this.commands = commands == null ? null : new ArrayList<>(commands);
-
+    public Button setCommands(@Nonnull List<String> commands) {
+        Objects.requireNonNull(commands);
+        this.commands = new ArrayList<>(commands);
         return this;
     }
 
@@ -89,6 +85,7 @@ public Button setServer(@Nullable String server) {
      * Get the button component based off the text and image off the Button.
      * @return the button component
      */
+    @NonNull
     public ButtonComponent getButtonComponent() {
         return ButtonComponent.of(text, image); //both are immutable
     }
@@ -103,7 +100,11 @@ public FormImage getImage() {
         return this.image; // Form image is immutable
     }
 
-    @Nullable
+    /**
+     * Get the commands that should be executed when this button is pressed
+     * @return a List of commands, which may be empty.
+     */
+    @Nonnull
     public List<String> getCommands() {
         return new ArrayList<>(this.commands); // Lists are mutable
     }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index 4048a6a..3792d70 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -130,13 +130,12 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
                 }
 
                 // Add commands if specified
-                List<String> commands = null;
+                List<String> commands = Collections.emptyList();
                 if (buttonInfo.contains("Commands") && buttonInfo.isList("Commands")) {
-                    List<String> potentialCommands = buttonInfo.getStringList("Commands");
-                    if (potentialCommands.isEmpty()) {
+                    if (buttonInfo.getStringList("Commands").isEmpty()) {
                         logger.warn(buttonId + " contains commands list but the list was empty.");
                     } else {
-                        commands = potentialCommands;
+                        commands = buttonInfo.getStringList("Commands");
                         logger.debug(buttonId + " contains commands: " + commands);
                     }
                 }
@@ -169,6 +168,10 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
      * @param floodgatePlayer the floodgate player to send it to
      */
     protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
+        if (!isEnabled) {
+            throw new AssertionError("Form: " + title + " that failed to load was called to be sent to a player!");
+        }
+
         SelectorLogger logger = SelectorLogger.getLogger();
 
         Player player = Bukkit.getServer().getPlayer(floodgatePlayer.getCorrectUniqueId());
@@ -177,11 +180,6 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
             return;
         }
 
-        if (!isEnabled) {
-            // todo: Do something less dirty to prevent this
-            throw new AssertionError("Form: " + title + " that failed to load was called to be sent to player " + player);
-        }
-
         // Resolve any placeholders in the button text
         List<Button> formattedButtons = new ArrayList<>();
         for (Button rawButton : allButtons) {
@@ -204,7 +202,7 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
 
             Button button = formattedButtons.get(response.getClickedButtonId());
 
-            if (button.getCommands() != null) {
+            if (!button.getCommands().isEmpty()) {
                 // Get the commands from the list of commands and replace any playerName placeholders
                 for (String command : button.getCommands()) {
                     Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), PlaceholderAPI.setPlaceholders(player, command));
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
index a00fab0..3c8f9bf 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
@@ -67,6 +67,8 @@ private void load() {
                             if (form.isEnabled()) {
                                 enabledForms.put(entry, form);
                                 noSuccess = false;
+                            } else {
+                                logger.warn("Not adding form for config section: " + entry + " because there was a failure loading it.");
                             }
                             if ("default".equals(entry)) {
                                 containsDefault = true;

From 49f2c08fc5d7d1b526031c918761d6ee1a6d4809 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Tue, 22 Jun 2021 21:34:38 -0400
Subject: [PATCH 43/74] add ability to run commands as the player

---
 .../geyserhub/command/GeyserHubCommand.java   |  2 +-
 .../geyserhub/module/menu/CommandUtils.java   | 28 +++++++++++++++++++
 .../module/menu/bedrock/BedrockForm.java      |  3 +-
 src/main/resources/selector.yml               | 23 ++++++++-------
 4 files changed, 44 insertions(+), 12 deletions(-)
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java

diff --git a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
index 9085386..1a59802 100644
--- a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
+++ b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
@@ -32,7 +32,7 @@ public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command
         if (!(commandSender instanceof Player || commandSender instanceof ConsoleCommandSender)) {
             return false;
         }
-
+        // todo: cleanup
         if (args.length == 0) {
             // send the default form, help if console
             sendForm(commandSender, BedrockFormRegistry.DEFAULT);
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java b/src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java
new file mode 100644
index 0000000..cb0dddd
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java
@@ -0,0 +1,28 @@
+package dev.projectg.geyserhub.module.menu;
+
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import javax.annotation.Nonnull;
+
+public class CommandUtils {
+
+    public static final String playerPrefix = "player;";
+    public static final String consolePrefix = "console;";
+
+    /**
+     * Process a prefixed command and run it
+     * @param prefixedCommand A command that is prefixed with "player;" to run the command as the player, or "console;", to run the command as the console.
+     * @param player the Player to run the command as, if prefixed with "player;"
+     */
+    public static void runCommand(@Nonnull String prefixedCommand, @Nonnull Player player) {
+        CommandSender sender = Bukkit.getServer().getConsoleSender();
+        if (prefixedCommand.startsWith(playerPrefix)) {
+            sender = player;
+        }
+        // Split the input into two strings between ";" and get the second string
+        String executableCommand = prefixedCommand.split(";", 2)[1];
+        Bukkit.getServer().dispatchCommand(sender, executableCommand);
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index 3792d70..2928fcd 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -3,6 +3,7 @@
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
 import dev.projectg.geyserhub.module.menu.Button;
+import dev.projectg.geyserhub.module.menu.CommandUtils;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
@@ -205,7 +206,7 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
             if (!button.getCommands().isEmpty()) {
                 // Get the commands from the list of commands and replace any playerName placeholders
                 for (String command : button.getCommands()) {
-                    Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), PlaceholderAPI.setPlaceholders(player, command));
+                    CommandUtils.runCommand(PlaceholderAPI.setPlaceholders(player, command), player);
                 }
             }
 
diff --git a/src/main/resources/selector.yml b/src/main/resources/selector.yml
index 6a8d839..cb40888 100644
--- a/src/main/resources/selector.yml
+++ b/src/main/resources/selector.yml
@@ -40,15 +40,16 @@ Java-Selector:
 
 Bedrock-Selector:
   Enable: true
-
+  # The default form can be opened with the item, /ghub
   Forms:
-    # Name of the form, Do not change this!
+    # Name of the form. "default" must exist. Create as many more as you want.
     default:
       # Title of the form.
       Title: "Server Selector"
       # Content of the form.
       Content: "Click on the server button of choice."
-      # Button list, Do not change this!
+      # The list of buttons. Add as many buttons as you want.
+      # Button-Text is a required value. ImageURL can be removed. Each button can contain a Commands list, a Server value, or both.
       Buttons:
         # Name of the section.
         lobby:
@@ -56,6 +57,10 @@ Bedrock-Selector:
           Button-Text: "Server Lobby: %bungee_lobby% players"
           # Here you can set your own custom icon, This has to be an URL or you can delete it if not needed!
           ImageURL: "https://www.digminecraft.com/block_recipes/images/cyan_concrete.png"
+          # A list of commands to run. prefix with "console;" to run the command from the console, or "player;" to run the command as the player.
+          # All commands are run before the player is teleported to the server (if specified).
+          Commands:
+            - "console; tell %player_name% Taking you to the lobby."
           # Server has to be exact the same (case sensitive) as in the bungeecord config!
           Server: "lobby"
 
@@ -65,19 +70,17 @@ Bedrock-Selector:
           Server: "survival"
 
         # You can also execute commands with buttons as seen below!
-        commandOne:
+        gamesCommand:
           Button-Text: "Minigames"
           Image-URL: "https://www.digminecraft.com/weapon_recipes/images/diamond_sword.png"
           Commands:
-            - "execute as %player_name% run ghub form minigames"
+            - "player; ghub form minigames"
 
-        commandTwo:
+        spawnCommand:
           Button-Text: "Spawn"
-          ImageURL: "https://www.digminecraft.com/decoration_recipes/images/lodestone.png"
           Commands:
-            - "execute as %player_name% run spawn"
-    # Minigames is an new form that can be set to anything. This form can be opened with
-    # /ghub form minigames as a command or as seen on "commandOne:"
+            - "console; tp %player_name% tp 0 80 0"
+    # An example secondary form.
     minigames:
       Title: "Minigames"
       Content: "Click on a minigame of choice."

From 773a1cdd606594bde95bad8fa3a3fa98a1f3fed4 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Tue, 22 Jun 2021 22:39:46 -0400
Subject: [PATCH 44/74] fix JoinTeleporter, cleanup form registry

---
 .../menu/bedrock/BedrockFormRegistry.java     | 23 ++++-----
 .../module/teleporter/JoinTeleporter.java     | 50 ++++++++++++++-----
 2 files changed, 48 insertions(+), 25 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
index 3c8f9bf..5291c28 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
@@ -22,9 +22,9 @@ public class BedrockFormRegistry implements Reloadable {
     public static final String DEFAULT = "default";
 
     /**
-     * If the bedrock form is enabled in the config. the {@link #getFormNames()} may still return an empty list.
+     * If bedrock forms are enabled. may be false if disabled in the config or if all forms failed to load.
      */
-    private boolean isEnabled = false;
+    private boolean isEnabled;
     private final Map<String, BedrockForm> enabledForms = new HashMap<>();
 
     public static BedrockFormRegistry getInstance() {
@@ -33,11 +33,11 @@ public static BedrockFormRegistry getInstance() {
 
     public BedrockFormRegistry() {
         ReloadableRegistry.registerReloadable(this);
-        load();
+        isEnabled = load();
         INSTANCE = this;
     }
 
-    private void load() {
+    private boolean load() {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
         SelectorLogger logger = SelectorLogger.getLogger();
 
@@ -49,7 +49,6 @@ private void load() {
 
             if (selectorSection.contains("Enable", true) && selectorSection.isBoolean("Enable")) {
                 if (selectorSection.getBoolean("Enable")) {
-                    isEnabled = true;
                     if (selectorSection.contains("Forms", true) && selectorSection.isConfigurationSection("Forms")) {
                         ConfigurationSection forms = selectorSection.getConfigurationSection("Forms");
                         Objects.requireNonNull(forms);
@@ -75,15 +74,14 @@ private void load() {
                             }
                         }
 
+                        if (!containsDefault) {
+                            logger.warn("Failed to load a default form! The Server Selector compass will not work and players will not be able to open the default form with \"/ghub\"");
+                        }
                         if (noSuccess) {
                             logger.warn("Failed to load ALL bedrock forms, due to configuration error.");
-                            return;
                         } else {
                             logger.info("Valid Bedrock forms are: " + enabledForms.keySet());
-                        }
-
-                        if (!containsDefault) {
-                            logger.warn("Failed to load a default form! The Server Selector compass will not work and players will not be able to open the default form with \"/ghub\"");
+                            return true;
                         }
                     }
                 } else {
@@ -95,6 +93,7 @@ private void load() {
         } else {
             logger.warn("Not enabling bedrock forms because the whole configuration section is not present.");
         }
+        return false;
     }
 
     public void sendForm(@Nonnull FloodgatePlayer player, @Nonnull String form) {
@@ -110,7 +109,7 @@ public List<String> getFormNames() {
 
     @Override
     public boolean reload() {
-        load();
-        return isEnabled;
+        isEnabled = load();
+        return true;
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
index 9863cc4..8d18b91 100644
--- a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
+++ b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
@@ -20,8 +20,14 @@
 
 public class JoinTeleporter implements Listener, Reloadable {
 
-    private static final String COORDINATE_REGEX = "^(-\\d+|\\d+);(-\\d+|\\d+);(-\\d+|\\d+)$\n";
+    /**
+     * Makes sure that the input looks something like "integer;integer;integer"
+     */
+    private static final String COORDINATE_REGEX = "(-\\d+|\\d+);(-\\d+|\\d+);(-\\d+|\\d+)";
 
+    /**
+     * True if the module isn't disabled in the config and all loading was successful.
+     */
     private boolean enabled;
     private Location location;
 
@@ -38,22 +44,41 @@ public void onPlayerJoin(PlayerJoinEvent event) {
         }
     }
 
+    /**
+     * Load the the JoinTeleporter
+     * @param config the config to pull from
+     * @return false if there was an error loading or if its disabled in the config
+     */
     private boolean load(@Nonnull FileConfiguration config) {
         SelectorLogger logger = SelectorLogger.getLogger();
 
-        // Get our section
-        if (!(config.contains("Join-Teleporter", true) && config.isConfigurationSection("Join-Teleporter"))) {
+        if (!config.contains("Join-Teleporter", true) || !config.isConfigurationSection("Join-Teleporter")) {
             logger.warn("Configuration does not contain Join-Teleporter section, skipping module.");
             return false;
         }
         ConfigurationSection section = config.getConfigurationSection("Join-Teleporter");
         Objects.requireNonNull(section);
 
-        // Validate all our values
-        if (!(section.contains("Enable", true) && section.isBoolean(("Enable")))) {
-            logger.severe("Join-Teleporter config section does not contain a valid Enable value, skipping module!");
+        if (section.contains("Enable") && section.isBoolean("Enable")) {
+            if (section.getBoolean("Enable")) {
+                return setLocation(section);
+            } else {
+                return false;
+            }
+        } else {
+            logger.warn("Join-Teleporter config section does not contain a valid Enable value, skipping module!");
             return false;
         }
+    }
+
+    /**
+     * Set the location to teleport to.
+     * @param section the configuration section to pull the data from
+     * @return false if there was an error setting the location
+     */
+    private boolean setLocation(@Nonnull ConfigurationSection section) {
+        SelectorLogger logger = SelectorLogger.getLogger();
+
         if (!(section.contains("World") && section.isString("World"))) {
             logger.severe("Join-Teleporter config section does not contain a valid World string, skipping module!");
             return false;
@@ -66,12 +91,12 @@ private boolean load(@Nonnull FileConfiguration config) {
             return false;
         }
 
-        if (section.contains("Location") && section.isString("Location")) {
+        if (section.contains("Coordinates") && section.isString("Coordinates")) {
             // Make sure the given coordinates are in the correct format
-            String composedCoords = section.getString("Location");
+            String composedCoords = section.getString("Coordinates");
             Objects.requireNonNull(composedCoords);
             if (!composedCoords.matches(COORDINATE_REGEX)) {
-                logger.severe("Join-Teleporter.Location in the config is not of the format <integer;integer;integer>, skipping module!");
+                logger.severe("Join-Teleporter.Coordinates in the config is not of the format <integer;integer;integer>, skipping module!");
                 return false;
             }
 
@@ -87,15 +112,14 @@ private boolean load(@Nonnull FileConfiguration config) {
                 throw new AssertionError("Failed to decompose the following coordinates: " + composedCoords + " -> " + Arrays.toString(coordinates));
             }
         } else {
-            logger.severe("Join-Teleporter config section does not contain a Location value or it is not a string!");
+            logger.severe("Join-Teleporter config section does not contain a Coordinates value or it is not a string!");
             return false;
         }
     }
 
     @Override
     public boolean reload() {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        enabled = load(config);
-        return enabled;
+        enabled = load(GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN));
+        return true;
     }
 }

From b8bcd91b52b655de84ffb00c9ea277c130fc9c73 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Tue, 22 Jun 2021 22:52:48 -0400
Subject: [PATCH 45/74] PlaceholderAPI is no longer required

---
 .../dev/projectg/geyserhub/GeyserHubMain.java |  5 ++--
 .../module/menu/bedrock/BedrockForm.java      |  6 ++---
 .../geyserhub/module/message/Broadcast.java   |  4 ++--
 .../geyserhub/module/message/MessageJoin.java |  4 ++--
 .../module/scoreboard/ScoreboardManager.java  |  6 ++---
 .../utils/{Utils.java => FileUtils.java}      |  4 ++--
 .../geyserhub/utils/PlaceholderUtils.java     | 24 +++++++++++++++++++
 7 files changed, 38 insertions(+), 15 deletions(-)
 rename src/main/java/dev/projectg/geyserhub/utils/{Utils.java => FileUtils.java} (70%)
 create mode 100644 src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 720439d..58a676d 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -1,7 +1,6 @@
 package dev.projectg.geyserhub;
 
 import dev.projectg.geyserhub.command.GeyserHubCommand;
-import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.config.ConfigManager;
 import dev.projectg.geyserhub.module.menu.CommonMenuListeners;
 import dev.projectg.geyserhub.module.menu.java.JavaMenuListeners;
@@ -11,7 +10,7 @@
 import dev.projectg.geyserhub.module.scoreboard.ScoreboardManager;
 import dev.projectg.geyserhub.module.teleporter.JoinTeleporter;
 import dev.projectg.geyserhub.module.world.WorldSettings;
-import dev.projectg.geyserhub.utils.Utils;
+import dev.projectg.geyserhub.utils.FileUtils;
 import dev.projectg.geyserhub.utils.bstats.Metrics;
 import org.bukkit.Bukkit;
 import org.bukkit.plugin.java.JavaPlugin;
@@ -34,7 +33,7 @@ public void onEnable() {
 
         try {
             Properties gitProperties = new Properties();
-            gitProperties.load(Utils.getResource("git.properties"));
+            gitProperties.load(FileUtils.getResource("git.properties"));
             logger.info("Branch: " + gitProperties.getProperty("git.branch", "Unknown") + ", Commit: " + gitProperties.getProperty("git.commit.id.abbrev", "Unknown"));
         } catch (IOException e) {
             logger.warn("Unable to load resource: git.properties");
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index 2928fcd..872c1c5 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -4,7 +4,7 @@
 import dev.projectg.geyserhub.SelectorLogger;
 import dev.projectg.geyserhub.module.menu.Button;
 import dev.projectg.geyserhub.module.menu.CommandUtils;
-import me.clip.placeholderapi.PlaceholderAPI;
+import dev.projectg.geyserhub.utils.PlaceholderUtils;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
 import org.bukkit.configuration.ConfigurationSection;
@@ -185,7 +185,7 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
         List<Button> formattedButtons = new ArrayList<>();
         for (Button rawButton : allButtons) {
             Button copiedButton = new Button(rawButton);
-            copiedButton.setText(PlaceholderAPI.setPlaceholders(player, copiedButton.getText()));
+            copiedButton.setText(PlaceholderUtils.setPlaceholders(player, copiedButton.getText()));
             formattedButtons.add(copiedButton);
         }
 
@@ -206,7 +206,7 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
             if (!button.getCommands().isEmpty()) {
                 // Get the commands from the list of commands and replace any playerName placeholders
                 for (String command : button.getCommands()) {
-                    CommandUtils.runCommand(PlaceholderAPI.setPlaceholders(player, command), player);
+                    CommandUtils.runCommand(PlaceholderUtils.setPlaceholders(player, command), player);
                 }
             }
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
index 84e8eab..e7b3ba8 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
@@ -3,7 +3,7 @@
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
 import dev.projectg.geyserhub.config.ConfigId;
-import me.clip.placeholderapi.PlaceholderAPI;
+import dev.projectg.geyserhub.utils.PlaceholderUtils;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
 import org.bukkit.configuration.ConfigurationSection;
@@ -30,7 +30,7 @@ public static void startBroadcastTimer(BukkitScheduler scheduler) {
                 if (parentSection.contains(broadcastId, true) && parentSection.isList(broadcastId)) {
                     for (String message : parentSection.getStringList(broadcastId)) {
                         for (Player player : Bukkit.getOnlinePlayers()) {
-                            player.sendMessage(ChatColor.translateAlternateColorCodes('&', PlaceholderAPI.setPlaceholders(player, message)));
+                            player.sendMessage(ChatColor.translateAlternateColorCodes('&', PlaceholderUtils.setPlaceholders(player, message)));
                         }
                     }
                 } else {
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java b/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
index d892269..99b98e3 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
@@ -2,7 +2,7 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.config.ConfigId;
-import me.clip.placeholderapi.PlaceholderAPI;
+import dev.projectg.geyserhub.utils.PlaceholderUtils;
 import org.bukkit.ChatColor;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
@@ -21,7 +21,7 @@ public void onJoin(PlayerJoinEvent e) {
         List<String> messages = config.getStringList("Join-Message.Messages");
 
         for (String message : messages) {
-            player.sendMessage(ChatColor.translateAlternateColorCodes('&', PlaceholderAPI.setPlaceholders(player, message)));
+            player.sendMessage(ChatColor.translateAlternateColorCodes('&', PlaceholderUtils.setPlaceholders(player, message)));
         }
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
index 6c4a3a1..4a07f3f 100644
--- a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
+++ b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
@@ -2,7 +2,7 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.config.ConfigId;
-import me.clip.placeholderapi.PlaceholderAPI;
+import dev.projectg.geyserhub.utils.PlaceholderUtils;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
 import org.bukkit.configuration.file.FileConfiguration;
@@ -27,7 +27,7 @@ public static void addScoreboard() {
     public static void createScoreboard(Player player) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
         Scoreboard board = Objects.requireNonNull(Bukkit.getServer().getScoreboardManager()).getNewScoreboard();
-        Objective objective = board.registerNewObjective("GeyserHub", "dummy", PlaceholderAPI.setPlaceholders(player, config.getString("Scoreboard.Title", "GeyserHub")));
+        Objective objective = board.registerNewObjective("GeyserHub", "dummy", PlaceholderUtils.setPlaceholders(player, config.getString("Scoreboard.Title", "GeyserHub")));
 
         objective.setDisplaySlot(DisplaySlot.SIDEBAR);
         List<String> text = config.getStringList("Scoreboard.Lines");
@@ -36,7 +36,7 @@ public static void createScoreboard(Player player) {
         int limit = Math.min(text.size(), 15);
 
         for (int index = 0; index < limit; index++) {
-            String formattedLine = PlaceholderAPI.setPlaceholders(player, text.get(index));
+            String formattedLine = PlaceholderUtils.setPlaceholders(player, text.get(index));
             Score score = objective.getScore(ChatColor.translateAlternateColorCodes('&', formattedLine));
             score.setScore(limit - index);
         }
diff --git a/src/main/java/dev/projectg/geyserhub/utils/Utils.java b/src/main/java/dev/projectg/geyserhub/utils/FileUtils.java
similarity index 70%
rename from src/main/java/dev/projectg/geyserhub/utils/Utils.java
rename to src/main/java/dev/projectg/geyserhub/utils/FileUtils.java
index f5b9c7e..fdecd7a 100644
--- a/src/main/java/dev/projectg/geyserhub/utils/Utils.java
+++ b/src/main/java/dev/projectg/geyserhub/utils/FileUtils.java
@@ -2,10 +2,10 @@
 
 import java.io.InputStream;
 
-public class Utils {
+public class FileUtils {
 
     public static InputStream getResource(String resource) {
-        InputStream stream = Utils.class.getClassLoader().getResourceAsStream(resource);
+        InputStream stream = FileUtils.class.getClassLoader().getResourceAsStream(resource);
         if (stream == null) {
             throw new AssertionError("Unable to find resource: " + resource);
         }
diff --git a/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java b/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java
new file mode 100644
index 0000000..cbc3ccc
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java
@@ -0,0 +1,24 @@
+package dev.projectg.geyserhub.utils;
+
+import me.clip.placeholderapi.PlaceholderAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+
+import javax.annotation.Nonnull;
+
+public class PlaceholderUtils {
+
+    /**
+     * Returns the inputted text with placeholders set, if PlaceholderAPI is loaded. If not, it returns the same text.
+     * @param player The player
+     * @param text The text
+     * @return the formatted text.
+     */
+    public static String setPlaceholders(@Nonnull Player player, @Nonnull String text) {
+        if (Bukkit.getServer().getPluginManager().isPluginEnabled("PlaceholderAPI")) {
+            return PlaceholderAPI.setPlaceholders(player, text);
+        } else {
+            return text;
+        }
+    }
+}

From 66550da8923bc992ed225be0f0e78f176891c395 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Tue, 22 Jun 2021 23:01:56 -0400
Subject: [PATCH 46/74] add debug for sent commands and fix config default

---
 .../java/dev/projectg/geyserhub/module/menu/CommandUtils.java   | 2 ++
 src/main/resources/selector.yml                                 | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java b/src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java
index cb0dddd..087a3a2 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java
@@ -1,5 +1,6 @@
 package dev.projectg.geyserhub.module.menu;
 
+import dev.projectg.geyserhub.SelectorLogger;
 import org.bukkit.Bukkit;
 import org.bukkit.command.CommandSender;
 import org.bukkit.entity.Player;
@@ -23,6 +24,7 @@ public static void runCommand(@Nonnull String prefixedCommand, @Nonnull Player p
         }
         // Split the input into two strings between ";" and get the second string
         String executableCommand = prefixedCommand.split(";", 2)[1];
+        SelectorLogger.getLogger().debug("Running command: " + executableCommand + " as " + sender.getName());
         Bukkit.getServer().dispatchCommand(sender, executableCommand);
     }
 }
diff --git a/src/main/resources/selector.yml b/src/main/resources/selector.yml
index cb40888..e0b84d0 100644
--- a/src/main/resources/selector.yml
+++ b/src/main/resources/selector.yml
@@ -79,7 +79,7 @@ Bedrock-Selector:
         spawnCommand:
           Button-Text: "Spawn"
           Commands:
-            - "console; tp %player_name% tp 0 80 0"
+            - "console; tp %player_name% 0 80 0"
     # An example secondary form.
     minigames:
       Title: "Minigames"

From d9e0320b3afb1dcb8a7a51f5d72cdc49db7e4ce1 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Tue, 22 Jun 2021 23:25:43 -0400
Subject: [PATCH 47/74] strip leading whitespace from commands

---
 .../java/dev/projectg/geyserhub/module/menu/CommandUtils.java | 4 ++--
 .../java/dev/projectg/geyserhub/utils/PlaceholderUtils.java   | 2 ++
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java b/src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java
index 087a3a2..6af8ebd 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java
@@ -23,8 +23,8 @@ public static void runCommand(@Nonnull String prefixedCommand, @Nonnull Player p
             sender = player;
         }
         // Split the input into two strings between ";" and get the second string
-        String executableCommand = prefixedCommand.split(";", 2)[1];
-        SelectorLogger.getLogger().debug("Running command: " + executableCommand + " as " + sender.getName());
+        String executableCommand = prefixedCommand.split(";", 2)[1].stripLeading();
+        SelectorLogger.getLogger().debug("Running command: [" + executableCommand + "] as " + sender.getName());
         Bukkit.getServer().dispatchCommand(sender, executableCommand);
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java b/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java
index cbc3ccc..3d4b1c8 100644
--- a/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java
+++ b/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java
@@ -1,5 +1,6 @@
 package dev.projectg.geyserhub.utils;
 
+import dev.projectg.geyserhub.SelectorLogger;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.entity.Player;
@@ -16,6 +17,7 @@ public class PlaceholderUtils {
      */
     public static String setPlaceholders(@Nonnull Player player, @Nonnull String text) {
         if (Bukkit.getServer().getPluginManager().isPluginEnabled("PlaceholderAPI")) {
+            SelectorLogger.getLogger().debug("PlaceholderAPI was used.");
             return PlaceholderAPI.setPlaceholders(player, text);
         } else {
             return text;

From dab39e58e4f6acccea28e9e358668ca05e5b07da Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Tue, 22 Jun 2021 23:37:22 -0400
Subject: [PATCH 48/74] add debug logger for join-teleporter

---
 .../dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
index 8d18b91..0d7dac6 100644
--- a/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
+++ b/src/main/java/dev/projectg/geyserhub/module/teleporter/JoinTeleporter.java
@@ -107,6 +107,7 @@ private boolean setLocation(@Nonnull ConfigurationSection section) {
                 int y = Integer.parseInt(coordinates[1]);
                 int z = Integer.parseInt(coordinates[2]);
                 location = new Location(world, x, y, z);
+                logger.debug("Join-Teleporter is enabled and has coordinates: [" + x + ", " + y + ", " + z + "] in [" + worldName + "].");
                 return true;
             } catch (NumberFormatException e) {
                 throw new AssertionError("Failed to decompose the following coordinates: " + composedCoords + " -> " + Arrays.toString(coordinates));

From 95f7a2f1405ec34d4714bad0d42e5a0ed9c15231 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Tue, 22 Jun 2021 23:42:45 -0400
Subject: [PATCH 49/74] Update selector.yml

---
 src/main/resources/selector.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/resources/selector.yml b/src/main/resources/selector.yml
index e0b84d0..ee36762 100644
--- a/src/main/resources/selector.yml
+++ b/src/main/resources/selector.yml
@@ -40,7 +40,7 @@ Java-Selector:
 
 Bedrock-Selector:
   Enable: true
-  # The default form can be opened with the item, /ghub
+
   Forms:
     # Name of the form. "default" must exist. Create as many more as you want.
     default:

From 05972fe133cd67dc173a07be8017da62ffd50ac2 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 23 Jun 2021 16:40:02 -0400
Subject: [PATCH 50/74] add command to make other players open a form

---
 .../projectg/geyserhub/SelectorLogger.java    | 24 +++++++
 .../geyserhub/command/GeyserHubCommand.java   | 71 +++++++++++++++----
 src/main/resources/plugin.yml                 |  7 +-
 3 files changed, 86 insertions(+), 16 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/SelectorLogger.java b/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
index 80f18a6..6c9ffb2 100644
--- a/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
+++ b/src/main/java/dev/projectg/geyserhub/SelectorLogger.java
@@ -20,6 +20,23 @@ private SelectorLogger(GeyserHubMain plugin) {
         ReloadableRegistry.registerReloadable(this);
     }
 
+    public void log(Level level, String message) {
+        switch (level) {
+            default: // intentional fallthrough
+            case INFO:
+                info(message);
+                break;
+            case WARN:
+                warn(message);
+                break;
+            case SEVERE:
+                severe(message);
+                break;
+            case DEBUG:
+                debug(message);
+                break;
+        }
+    }
     public void info(String message) {
         plugin.getLogger().info(message);
     }
@@ -35,6 +52,13 @@ public void debug(String message) {
         }
     }
 
+    public enum Level {
+        INFO,
+        WARN,
+        SEVERE,
+        DEBUG
+    }
+
     public boolean isDebug() {
         return debug;
     }
diff --git a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
index 1a59802..cdf316f 100644
--- a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
+++ b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
@@ -1,9 +1,12 @@
 package dev.projectg.geyserhub.command;
 
+import dev.projectg.geyserhub.SelectorLogger;
 import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
 import dev.projectg.geyserhub.module.menu.bedrock.BedrockFormRegistry;
 import dev.projectg.geyserhub.module.menu.java.JavaMenu;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
 import org.bukkit.command.Command;
 import org.bukkit.command.CommandExecutor;
 import org.bukkit.command.CommandSender;
@@ -13,6 +16,8 @@
 import org.jetbrains.annotations.NotNull;
 
 import javax.annotation.Nonnull;
+import javax.management.ObjectName;
+import java.util.Objects;
 import java.util.UUID;
 
 public class GeyserHubCommand implements CommandExecutor {
@@ -20,12 +25,13 @@ public class GeyserHubCommand implements CommandExecutor {
     private static final String[] HELP = {
             "/ghub - Opens the default form if one exists. If not, shows the help page",
             "/ghub - Opens the help page",
-            "/ghub <form> - Sends the player a selector with the defined name",
+            "/ghub form <form> - Open a form with the defined name",
+            "/ghub form <form> <player> - Sends a form to a given player",
             "/ghub reload - reloads the selector"
     };
 
-    private static final String NO_PERMISSION = "[GeyserHub] Sorry, you don't have permission to run that command!";
-    private static final String UNKNOWN = "[GeyserHub] Sorry, that's an unknown command!";
+    private static final String NO_PERMISSION = "Sorry, you don't have permission to run that command!";
+    private static final String UNKNOWN = "Sorry, that's an unknown command!";
 
     @Override
     public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] args) {
@@ -44,12 +50,12 @@ public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command
             case "reload":
                 if (commandSender.hasPermission("geyserhub.reload")) {
                     if (ReloadableRegistry.reloadAll()) {
-                        commandSender.sendMessage("[GeyserHub] Successfully reloaded.");
+                        sendMessage(commandSender, SelectorLogger.Level.INFO, "Successfully reloaded.");
                     } else {
-                        commandSender.sendMessage("[GeyserHub] There was an error reloading something! Please check the server console for further information.");
+                        sendMessage(commandSender, SelectorLogger.Level.SEVERE, "There was an error reloading something! Please check the server console for further information.");
                     }
                 } else {
-                    commandSender.sendMessage(NO_PERMISSION);
+                    sendMessage(commandSender, SelectorLogger.Level.SEVERE, NO_PERMISSION);
                 }
                 break;
             case "help":
@@ -58,18 +64,30 @@ public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command
             case "form":
                 if (commandSender.hasPermission("geyserhub.form")) {
                     if (args.length == 1) {
-                        commandSender.sendMessage("[GeyserHub] Please specify a form to open! Specify a form with \"/ghub form <form>\"");
-                    } else if (args.length > 2) {
-                        commandSender.sendMessage("[GeyserHub] This command only takes one argument!");
-                    } else {
+                        sendMessage(commandSender, SelectorLogger.Level.SEVERE, "Please specify a form to open! Specify a form with \"/ghub form <form>\"");
+                    } else if (args.length == 2) {
                         sendForm(commandSender, args[1]);
+                    } else if (args.length == 3) {
+                        if (commandSender.hasPermission("geyserhub.form.others")) {
+                            Player target = Bukkit.getServer().getPlayer(args[2]);
+                            if (target == null) {
+                                sendMessage(commandSender, SelectorLogger.Level.SEVERE, "That player doesn't exist!");
+                            } else {
+                                sendForm(target, args[1]);
+                                sendMessage(commandSender, SelectorLogger.Level.INFO, "Made " + target.getName() + " open form: " + args[1]);
+                            }
+                        } else {
+                            sendMessage(commandSender, SelectorLogger.Level.SEVERE, NO_PERMISSION);
+                        }
+                    } else {
+                        sendMessage(commandSender, SelectorLogger.Level.SEVERE, "Too many command arguments!");
                     }
                 } else {
-                    commandSender.sendMessage(NO_PERMISSION);
+                    sendMessage(commandSender, SelectorLogger.Level.SEVERE, NO_PERMISSION);
                 }
                 break;
             default:
-                commandSender.sendMessage(UNKNOWN);
+                sendMessage(commandSender, SelectorLogger.Level.SEVERE, UNKNOWN);
                 break;
         }
         return true;
@@ -96,10 +114,10 @@ private void sendForm(@Nonnull CommandSender commandSender, @Nonnull String form
                     if (BedrockFormRegistry.getInstance().getFormNames().contains(formName)) {
                         BedrockFormRegistry.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(uuid), formName);
                     } else {
-                        player.sendMessage("Sorry, that form doesn't exist! Specify a form with \"/ghub form <form>\"");
+                        sendMessage(player, SelectorLogger.Level.SEVERE, "Sorry, that form doesn't exist! Specify a form with \"/ghub form <form>\"");
                     }
                 } else {
-                    player.sendMessage("Sorry, Bedrock forms are disabled!");
+                    sendMessage(player, SelectorLogger.Level.SEVERE, "Sorry, Bedrock forms are disabled!");
                 }
             } else {
                 JavaMenu.openMenu(player, ConfigId.SELECTOR);
@@ -108,4 +126,29 @@ private void sendForm(@Nonnull CommandSender commandSender, @Nonnull String form
             sendHelp(commandSender);
         }
     }
+
+    private void sendMessage(@Nonnull CommandSender sender, @Nonnull SelectorLogger.Level level, @Nonnull String message) {
+        Objects.requireNonNull(sender);
+        Objects.requireNonNull(level);
+        Objects.requireNonNull(message);
+
+        if (sender instanceof ConsoleCommandSender) {
+            SelectorLogger.getLogger().log(level, message);
+        } else {
+            ChatColor colour;
+            switch (level) {
+                default: // intentional fallthrough
+                case INFO:
+                    colour = ChatColor.RESET;
+                    break;
+                case WARN:
+                    colour = ChatColor.GOLD;
+                    break;
+                case SEVERE:
+                    colour = ChatColor.RED;
+                    break;
+            }
+            sender.sendMessage("[GeyserHub] " + colour + message);
+        }
+    }
 }
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 4fd0204..7776491 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -16,11 +16,14 @@ commands:
 
 permissions:
   geyserhub.main:
-    description: Access to the base command /ghub
+    description: Access to the base command /ghub. Opens the default form if a player runs the command.
     default: true
   geyserhub.form:
-    description: Access to open a form with /ghub form <form>
+    description: Access to /ghub form <form>
     default: true
+  geyserhub.form.others:
+    description: Access to /ghub form <form> <playername>
+    default: op
   geyserhub.reload:
     description: Access to /ghub reload
     default: op

From 5ad7ee26f8978595ab448677fc258d3d7172ee01 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 23 Jun 2021 17:35:48 -0400
Subject: [PATCH 51/74] add papi for bedrock title/content, other misc

- comments in main plugin class
- remove unnecessary ChatColor
---
 src/main/java/dev/projectg/geyserhub/GeyserHubMain.java  | 9 +++++++++
 .../geyserhub/module/menu/bedrock/BedrockForm.java       | 2 +-
 .../projectg/geyserhub/module/world/WorldSettings.java   | 5 ++---
 3 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 58a676d..aa8a5ea 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -51,24 +51,33 @@ public void onEnable() {
         // Bungee channel for selector
         getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
 
+        // Load bedrock forms
         new BedrockFormRegistry();
 
         // todo: and add command suggestions/completions, help pages that only shows available commands
         Objects.requireNonNull(getCommand("ghub")).setExecutor(new GeyserHubCommand());
 
+        // Listeners for the Bedrock and Java menus
         Bukkit.getServer().getPluginManager().registerEvents(new CommonMenuListeners(), this);
         Bukkit.getServer().getPluginManager().registerEvents(new JavaMenuListeners(), this);
 
+        // Listener the Join Teleporter module
         Bukkit.getServer().getPluginManager().registerEvents(new JoinTeleporter(), this);
 
+        // Listener for world settings
         Bukkit.getServer().getPluginManager().registerEvents(new WorldSettings(), this);
 
+        // load the scoreboard if enabled
         if (getConfig().getBoolean("Scoreboard.Enable", false)) {
             initializeScoreboard();
         }
+
+        // Enable the join message if enabled
         if (getConfig().getBoolean("Enable-Join-Message", false)) {
             Bukkit.getServer().getPluginManager().registerEvents(new MessageJoin(), this);
         }
+
+        // The random interval broadcast module
         Broadcast.startBroadcastTimer(getServer().getScheduler());
     }
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index 872c1c5..69431ac 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -190,7 +190,7 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
         }
 
         // Create the form
-        SimpleForm serverSelector = SimpleForm.of(title, content, formattedButtons.stream().map(Button::getButtonComponent).collect(Collectors.toList()));
+        SimpleForm serverSelector = SimpleForm.of(PlaceholderUtils.setPlaceholders(player, title), PlaceholderUtils.setPlaceholders(player, content), formattedButtons.stream().map(Button::getButtonComponent).collect(Collectors.toList()));
 
         // Set the response handler
         serverSelector.setResponseHandler((responseData) -> {
diff --git a/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java b/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
index e9772c8..374be7c 100644
--- a/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
+++ b/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
@@ -2,7 +2,6 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.config.ConfigId;
-import org.bukkit.ChatColor;
 import org.bukkit.Material;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
@@ -111,7 +110,7 @@ public void onBlockBreak(BlockBreakEvent event) {
         if (player.hasPermission("geyserhub.blockbreak")) {
             return;
         }
-        player.sendMessage(ChatColor.RESET + "You can't break blocks here!");
+        player.sendMessage("You can't break blocks here!");
         event.setCancelled(true);
 
     }
@@ -129,7 +128,7 @@ public void onBlockPlace(BlockPlaceEvent event) {
          if (player.hasPermission("geyserhub.blockplace")) {
              return;
          }
-        player.sendMessage(ChatColor.RESET + "You can't place blocks here!");
+        player.sendMessage("You can't place blocks here!");
         event.setCancelled(true);
     }
 }

From 75684e76eabf89a54f5be5c736e1c1a34a3392cd Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Thu, 24 Jun 2021 22:34:13 -0400
Subject: [PATCH 52/74] split the button object into different classes (using
 extension)

paves the path for easier java menu construction
---
 .../geyserhub/module/menu/Button.java         | 116 ------------------
 .../module/menu/bedrock/BedrockButton.java    |  54 ++++++++
 .../module/menu/bedrock/BedrockForm.java      |  29 +++--
 .../module/menu/button/OutcomeButton.java     |  63 ++++++++++
 .../module/menu/button/TextButton.java        |  39 ++++++
 5 files changed, 170 insertions(+), 131 deletions(-)
 delete mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/Button.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockButton.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/button/OutcomeButton.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/button/TextButton.java

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/Button.java b/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
deleted file mode 100644
index 429f2a8..0000000
--- a/src/main/java/dev/projectg/geyserhub/module/menu/Button.java
+++ /dev/null
@@ -1,116 +0,0 @@
-package dev.projectg.geyserhub.module.menu;
-
-import edu.umd.cs.findbugs.annotations.NonNull;
-import org.geysermc.cumulus.component.ButtonComponent;
-import org.geysermc.cumulus.util.FormImage;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-public class Button {
-
-    // base requirement for ButtonComponent
-    @Nonnull private String text;
-    @Nullable private FormImage image;
-
-    // Everything extra
-    @Nonnull private List<String> commands = Collections.emptyList();
-    @Nullable private String server;
-
-    /**
-     * Create a button.
-     * @param text the text of the button
-     */
-    public Button(@Nonnull String text) {
-        this.text = Objects.requireNonNull(text);
-    }
-
-    /**
-     * Copy constructor
-     * @param button The button to make a copy of
-     */
-    public Button(@Nonnull Button button) {
-        this.text = button.text;
-        this.image = button.image;
-        this.commands = button.getCommands(); // lists are mutable, everything else here isn't
-        this.server = button.server;
-    }
-
-    /**
-     * Set the text of the button.
-     * @param text the new text
-     * @return the same Button instance
-     */
-    public Button setText(@Nonnull String text) {
-        this.text = Objects.requireNonNull(text);
-        return this;
-    }
-
-    /**
-     * Set the image of the button.
-     * @param image the image
-     * @return the same Button instance
-     */
-    public Button setImage(@Nullable FormImage image) {
-        this.image = image;
-        return this;
-    }
-
-    /**
-     * set the commands list.
-     * @param commands the commands list, which can be empty.
-     * @return the same Button instance
-     */
-    public Button setCommands(@Nonnull List<String> commands) {
-        Objects.requireNonNull(commands);
-        this.commands = new ArrayList<>(commands);
-        return this;
-    }
-
-    /**
-     * Set the server name.
-     * @param server the server name
-     * @return the same Button instance
-     */
-    public Button setServer(@Nullable String server) {
-        this.server = server;
-        return this;
-    }
-
-    /**
-     * Get the button component based off the text and image off the Button.
-     * @return the button component
-     */
-    @NonNull
-    public ButtonComponent getButtonComponent() {
-        return ButtonComponent.of(text, image); //both are immutable
-    }
-
-    @Nonnull
-    public String getText() {
-        return this.text; // Strings are immutable
-    }
-
-    @Nullable
-    public FormImage getImage() {
-        return this.image; // Form image is immutable
-    }
-
-    /**
-     * Get the commands that should be executed when this button is pressed
-     * @return a List of commands, which may be empty.
-     */
-    @Nonnull
-    public List<String> getCommands() {
-        return new ArrayList<>(this.commands); // Lists are mutable
-    }
-
-    @Nullable
-    public String getServer() {
-        return this.server; // Strings are immutable
-    }
-}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockButton.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockButton.java
new file mode 100644
index 0000000..bc886a4
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockButton.java
@@ -0,0 +1,54 @@
+package dev.projectg.geyserhub.module.menu.bedrock;
+
+import dev.projectg.geyserhub.module.menu.button.OutcomeButton;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import org.geysermc.cumulus.component.ButtonComponent;
+import org.geysermc.cumulus.util.FormImage;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class BedrockButton extends OutcomeButton {
+
+    @Nullable private FormImage image;
+
+    /**
+     * Create a button.
+     * @param text the text of the button
+     */
+    public BedrockButton(@Nonnull String text) {
+        super(text);
+    }
+
+    /**
+     * Copy constructor
+     * @param button The button to make a copy of
+     */
+    public BedrockButton(@Nonnull BedrockButton button) {
+        super(button);
+        this.image = button.image;
+    }
+
+    /**
+     * Set the image of the button.
+     * @param image the image
+     */
+    public void setImage(@Nullable FormImage image) {
+        this.image = image;
+    }
+
+    @Nullable
+    public FormImage getImage() {
+        return this.image; // Form image is immutable
+    }
+
+    /**
+     * Get the button component based off the text and image off the Button.
+     * @return the button component
+     */
+    @NonNull
+    public ButtonComponent getButtonComponent() {
+        return ButtonComponent.of(getText(), getImage());
+    }
+
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index 69431ac..34e6910 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -2,7 +2,6 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
-import dev.projectg.geyserhub.module.menu.Button;
 import dev.projectg.geyserhub.module.menu.CommandUtils;
 import dev.projectg.geyserhub.utils.PlaceholderUtils;
 import org.bukkit.Bukkit;
@@ -31,7 +30,7 @@ public class BedrockForm {
 
     private String title;
     private String content;
-    private List<Button> allButtons;
+    private List<BedrockButton> allButtons;
 
     /**
      * Create a new bedrock selector form and initializes it with the current loaded config
@@ -65,7 +64,7 @@ private boolean load(@Nonnull ConfigurationSection configSection) {
         }
         ConfigurationSection buttonSection = configSection.getConfigurationSection("Buttons");
         Objects.requireNonNull(buttonSection);
-        List<Button> buttons = getButtons(buttonSection);
+        List<BedrockButton> buttons = getButtons(buttonSection);
         if (buttons.isEmpty()) {
             logger.warn("Failed to create any valid buttons of form: " + configSection.getName() + "! All listed buttons have a malformed section!");
             return false;
@@ -86,7 +85,7 @@ private boolean load(@Nonnull ConfigurationSection configSection) {
      * @param configSection The configuration section to pull the data from
      * @return A list of Buttons, which may be empty.
      */
-    private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
+    private List<BedrockButton> getButtons(@Nonnull ConfigurationSection configSection) {
         SelectorLogger logger = SelectorLogger.getLogger();
 
         // Get the form name
@@ -107,7 +106,7 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
         }
 
         // Create a list of buttons. For every defined button with a valid server or command configuration, we add its button.
-        List<Button> compiledButtons = new ArrayList<>();
+        List<BedrockButton> compiledButtons = new ArrayList<>();
         for (String buttonId : allButtonIds) {
             ConfigurationSection buttonInfo = configSection.getConfigurationSection(buttonId);
             if (buttonInfo == null) {
@@ -149,11 +148,11 @@ private List<Button> getButtons(@Nonnull ConfigurationSection configSection) {
                     logger.debug(buttonId + " contains BungeeCord target server: " + serverName);
                 }
 
-                compiledButtons.add(
-                        new Button(buttonText)
-                        .setImage(image)
-                        .setCommands(commands)
-                        .setServer(serverName));
+                BedrockButton button = new BedrockButton(buttonText);
+                button.setImage(image);
+                button.setCommands(commands);
+                button.setServer(serverName);
+                compiledButtons.add(button);
 
                 logger.debug(buttonId + " was successfully added.");
             } else {
@@ -182,15 +181,15 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
         }
 
         // Resolve any placeholders in the button text
-        List<Button> formattedButtons = new ArrayList<>();
-        for (Button rawButton : allButtons) {
-            Button copiedButton = new Button(rawButton);
+        List<BedrockButton> formattedButtons = new ArrayList<>();
+        for (BedrockButton rawButton : allButtons) {
+            BedrockButton copiedButton = new BedrockButton(rawButton);
             copiedButton.setText(PlaceholderUtils.setPlaceholders(player, copiedButton.getText()));
             formattedButtons.add(copiedButton);
         }
 
         // Create the form
-        SimpleForm serverSelector = SimpleForm.of(PlaceholderUtils.setPlaceholders(player, title), PlaceholderUtils.setPlaceholders(player, content), formattedButtons.stream().map(Button::getButtonComponent).collect(Collectors.toList()));
+        SimpleForm serverSelector = SimpleForm.of(PlaceholderUtils.setPlaceholders(player, title), PlaceholderUtils.setPlaceholders(player, content), formattedButtons.stream().map(BedrockButton::getButtonComponent).collect(Collectors.toList()));
 
         // Set the response handler
         serverSelector.setResponseHandler((responseData) -> {
@@ -201,7 +200,7 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
                 return;
             }
 
-            Button button = formattedButtons.get(response.getClickedButtonId());
+            BedrockButton button = formattedButtons.get(response.getClickedButtonId());
 
             if (!button.getCommands().isEmpty()) {
                 // Get the commands from the list of commands and replace any playerName placeholders
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/button/OutcomeButton.java b/src/main/java/dev/projectg/geyserhub/module/menu/button/OutcomeButton.java
new file mode 100644
index 0000000..b4ce962
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/button/OutcomeButton.java
@@ -0,0 +1,63 @@
+package dev.projectg.geyserhub.module.menu.button;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+public class OutcomeButton extends TextButton {
+
+    @Nonnull private List<String> commands = Collections.emptyList();
+    @Nullable private String server;
+
+    /**
+     * Create a button.
+     * @param text the text of the button
+     */
+    public OutcomeButton(@Nonnull String text) {
+        super(text);
+    }
+
+    /**
+     * Copy constructor
+     * @param button The button to make a copy of
+     */
+    public OutcomeButton(@Nonnull OutcomeButton button) {
+        super(button);
+        this.commands = button.getCommands(); // lists are mutable, everything else here isn't
+        this.server = button.getServer();
+    }
+
+    /**
+     * Set the commands list.
+     * @param commands the commands list, which can be empty.
+     */
+    public void setCommands(@Nonnull List<String> commands) {
+        Objects.requireNonNull(commands);
+        this.commands = new ArrayList<>(commands);
+    }
+
+    /**
+     * Set the server name.
+     * @param server the server name
+     */
+    public void setServer(@Nullable String server) {
+        this.server = server;
+    }
+
+    /**
+     * Get the commands that should be executed when this button is pressed
+     * @return a List of commands, which may be empty.
+     */
+    @Nonnull
+    public List<String> getCommands() {
+        return new ArrayList<>(this.commands); // Lists are mutable
+    }
+
+    @Nullable
+    public String getServer() {
+        return this.server; // Strings are immutable
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/button/TextButton.java b/src/main/java/dev/projectg/geyserhub/module/menu/button/TextButton.java
new file mode 100644
index 0000000..8ef8070
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/button/TextButton.java
@@ -0,0 +1,39 @@
+package dev.projectg.geyserhub.module.menu.button;
+
+import javax.annotation.Nonnull;
+import java.util.Objects;
+
+public class TextButton {
+
+    @Nonnull private String text;
+
+    /**
+     * Create a button.
+     * @param text the text of the button
+     */
+    public TextButton(@Nonnull String text) {
+        this.text = Objects.requireNonNull(text);
+    }
+
+    /**
+     * Copy constructor
+     * @param button The button to make a copy of
+     */
+    public TextButton(@Nonnull TextButton button) {
+        this.text = button.text;
+    }
+
+    /**
+     * Set the text of the button.
+     * @param text the new text
+     */
+    public void setText(@Nonnull String text) {
+        this.text = Objects.requireNonNull(text);
+    }
+
+    @Nonnull
+    public String getText() {
+        return this.text; // Strings are immutable
+    }
+
+}

From 247e68d60fee39b4c3e3a1797829b4c61c7e2e43 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Thu, 24 Jun 2021 23:07:00 -0400
Subject: [PATCH 53/74] add unused ItemButton object for java, change
 annotation syntax

---
 .../module/menu/bedrock/BedrockButton.java    |  8 +-
 .../module/menu/button/OutcomeButton.java     | 14 ++-
 .../module/menu/button/TextButton.java        |  5 +-
 .../module/menu/java/ItemButton.java          | 88 +++++++++++++++++++
 4 files changed, 99 insertions(+), 16 deletions(-)
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/java/ItemButton.java

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockButton.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockButton.java
index bc886a4..ccc6ccd 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockButton.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockButton.java
@@ -21,7 +21,7 @@ public BedrockButton(@Nonnull String text) {
     }
 
     /**
-     * Copy constructor
+     * Copy constructor.
      * @param button The button to make a copy of
      */
     public BedrockButton(@Nonnull BedrockButton button) {
@@ -37,8 +37,7 @@ public void setImage(@Nullable FormImage image) {
         this.image = image;
     }
 
-    @Nullable
-    public FormImage getImage() {
+    public @Nullable FormImage getImage() {
         return this.image; // Form image is immutable
     }
 
@@ -46,8 +45,7 @@ public FormImage getImage() {
      * Get the button component based off the text and image off the Button.
      * @return the button component
      */
-    @NonNull
-    public ButtonComponent getButtonComponent() {
+    public @NonNull ButtonComponent getButtonComponent() {
         return ButtonComponent.of(getText(), getImage());
     }
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/button/OutcomeButton.java b/src/main/java/dev/projectg/geyserhub/module/menu/button/OutcomeButton.java
index b4ce962..8e17fc5 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/button/OutcomeButton.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/button/OutcomeButton.java
@@ -21,7 +21,7 @@ public OutcomeButton(@Nonnull String text) {
     }
 
     /**
-     * Copy constructor
+     * Copy constructor.
      * @param button The button to make a copy of
      */
     public OutcomeButton(@Nonnull OutcomeButton button) {
@@ -32,7 +32,7 @@ public OutcomeButton(@Nonnull OutcomeButton button) {
 
     /**
      * Set the commands list.
-     * @param commands the commands list, which can be empty.
+     * @param commands the commands list, which can be empty
      */
     public void setCommands(@Nonnull List<String> commands) {
         Objects.requireNonNull(commands);
@@ -48,16 +48,14 @@ public void setServer(@Nullable String server) {
     }
 
     /**
-     * Get the commands that should be executed when this button is pressed
-     * @return a List of commands, which may be empty.
+     * Get the commands that should be executed when this button is pressed.
+     * @return a List of commands, which may be empty
      */
-    @Nonnull
-    public List<String> getCommands() {
+    public @Nonnull List<String> getCommands() {
         return new ArrayList<>(this.commands); // Lists are mutable
     }
 
-    @Nullable
-    public String getServer() {
+    public @Nullable String getServer() {
         return this.server; // Strings are immutable
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/button/TextButton.java b/src/main/java/dev/projectg/geyserhub/module/menu/button/TextButton.java
index 8ef8070..921cab6 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/button/TextButton.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/button/TextButton.java
@@ -16,7 +16,7 @@ public TextButton(@Nonnull String text) {
     }
 
     /**
-     * Copy constructor
+     * Copy constructor.
      * @param button The button to make a copy of
      */
     public TextButton(@Nonnull TextButton button) {
@@ -31,8 +31,7 @@ public void setText(@Nonnull String text) {
         this.text = Objects.requireNonNull(text);
     }
 
-    @Nonnull
-    public String getText() {
+    public @Nonnull String getText() {
         return this.text; // Strings are immutable
     }
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/ItemButton.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/ItemButton.java
new file mode 100644
index 0000000..818d573
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/ItemButton.java
@@ -0,0 +1,88 @@
+package dev.projectg.geyserhub.module.menu.java;
+
+import dev.projectg.geyserhub.module.menu.button.OutcomeButton;
+import org.bukkit.Material;
+import org.jetbrains.annotations.NotNull;
+
+import javax.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+public class ItemButton {
+
+    @Nonnull private String displayName;
+    @Nonnull private Material material;
+    @Nonnull private List<String> lore = Collections.emptyList();
+
+    @Nonnull private final OutcomeButton rightClickButton;
+    @Nonnull private final OutcomeButton leftClickButton;
+
+    /**
+     * Main constructor.
+     * @param text the display name of the item button
+     * @param material the material of the item button
+     */
+    public ItemButton(@Nonnull String text, @Nonnull Material material) {
+        this.displayName = Objects.requireNonNull(text);
+        this.material = Objects.requireNonNull(material);
+
+        rightClickButton = new OutcomeButton(text);
+        leftClickButton = new OutcomeButton(text);
+    }
+
+    /**
+     * Copy constructor.
+     * @param button The button to make a copy of
+     */
+    public ItemButton(@Nonnull ItemButton button) {
+        this.displayName = button.getDisplayName();
+        this.material = button.getMaterial();
+        this.lore = button.getLore();
+
+        this.rightClickButton = button.getRightClickButton();
+        this.leftClickButton = button.getLeftClickButton();
+    }
+
+    public @Nonnull String getDisplayName() {
+        return this.displayName;
+    }
+    public @Nonnull Material getMaterial() {
+        return this.material;
+    }
+    public @Nonnull List<String> getLore() {
+        return new ArrayList<>(lore); // lists are mutable, make a new list instance
+    }
+
+    public void setDisplayName(@Nonnull String displayName) {
+        Objects.requireNonNull(displayName);
+        this.displayName = displayName;
+    }
+    public void setMaterial(@Nonnull Material material) {
+        Objects.requireNonNull(material);
+        this.material = material;
+    }
+    public void setLore(@Nonnull List<String> lore) {
+        Objects.requireNonNull(lore);
+        this.lore = lore;
+    }
+
+    /**
+     * Get the OutcomeButton for when the player right clicks on this ItemButton.
+     * Warning: the text of the OutcomeButton is ignored.
+     * @return the OutcomeButton
+     */
+    public @NotNull OutcomeButton getRightClickButton() {
+        return rightClickButton;
+    }
+
+    /**
+     * Get the OutcomeButton for when the player left clicks on this ItemButton.
+     * Warning: the text of the OutcomeButton is ignored.
+     * @return the OutcomeButton
+     */
+    public @NotNull OutcomeButton getLeftClickButton() {
+        return leftClickButton;
+    }
+}

From 660b1adc5f6a3a2e92cbdab22b314faed8aa4010 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Fri, 25 Jun 2021 12:02:50 -0400
Subject: [PATCH 54/74] remove static getter from BedrockFormRegistry

---
 .../java/dev/projectg/geyserhub/GeyserHubMain.java  |  6 +++---
 .../geyserhub/command/GeyserHubCommand.java         | 13 +++++++++----
 .../geyserhub/module/menu/CommonMenuListeners.java  |  8 +++++++-
 .../module/menu/bedrock/BedrockFormRegistry.java    |  6 ------
 4 files changed, 19 insertions(+), 14 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index aa8a5ea..9210789 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -52,13 +52,13 @@ public void onEnable() {
         getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
 
         // Load bedrock forms
-        new BedrockFormRegistry();
+        BedrockFormRegistry bedrockFormRegistry = new BedrockFormRegistry();
 
         // todo: and add command suggestions/completions, help pages that only shows available commands
-        Objects.requireNonNull(getCommand("ghub")).setExecutor(new GeyserHubCommand());
+        Objects.requireNonNull(getCommand("ghub")).setExecutor(new GeyserHubCommand(bedrockFormRegistry));
 
         // Listeners for the Bedrock and Java menus
-        Bukkit.getServer().getPluginManager().registerEvents(new CommonMenuListeners(), this);
+        Bukkit.getServer().getPluginManager().registerEvents(new CommonMenuListeners(bedrockFormRegistry), this);
         Bukkit.getServer().getPluginManager().registerEvents(new JavaMenuListeners(), this);
 
         // Listener the Join Teleporter module
diff --git a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
index cdf316f..768c49c 100644
--- a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
+++ b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
@@ -16,7 +16,6 @@
 import org.jetbrains.annotations.NotNull;
 
 import javax.annotation.Nonnull;
-import javax.management.ObjectName;
 import java.util.Objects;
 import java.util.UUID;
 
@@ -33,6 +32,12 @@ public class GeyserHubCommand implements CommandExecutor {
     private static final String NO_PERMISSION = "Sorry, you don't have permission to run that command!";
     private static final String UNKNOWN = "Sorry, that's an unknown command!";
 
+    private final BedrockFormRegistry bedrockRegistry;
+
+    public GeyserHubCommand(BedrockFormRegistry bedrockRegistry) {
+        this.bedrockRegistry = bedrockRegistry;
+    }
+
     @Override
     public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] args) {
         if (!(commandSender instanceof Player || commandSender instanceof ConsoleCommandSender)) {
@@ -110,9 +115,9 @@ private void sendForm(@Nonnull CommandSender commandSender, @Nonnull String form
             Player player = (Player) commandSender;
             UUID uuid = player.getUniqueId();
             if (FloodgateApi.getInstance().isFloodgatePlayer(uuid)) {
-                if (BedrockFormRegistry.getInstance().isEnabled()) {
-                    if (BedrockFormRegistry.getInstance().getFormNames().contains(formName)) {
-                        BedrockFormRegistry.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(uuid), formName);
+                if (bedrockRegistry.isEnabled()) {
+                    if (bedrockRegistry.getFormNames().contains(formName)) {
+                        bedrockRegistry.sendForm(FloodgateApi.getInstance().getPlayer(uuid), formName);
                     } else {
                         sendMessage(player, SelectorLogger.Level.SEVERE, "Sorry, that form doesn't exist! Specify a form with \"/ghub form <form>\"");
                     }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
index 8460bc4..2f94929 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
@@ -21,13 +21,19 @@
 
 public class CommonMenuListeners implements Listener {
 
+    private final BedrockFormRegistry bedrockFormRegistry;
+
+    public CommonMenuListeners(BedrockFormRegistry bedrockFormRegistry) {
+        this.bedrockFormRegistry = bedrockFormRegistry;
+    }
+
     @EventHandler
     public void onInteract(PlayerInteractEvent event) { // open the menu through the access item
         Player player = event.getPlayer();
         if (player.getInventory().getItemInMainHand().isSimilar(AccessItem.getItem())) {
             if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
                 if (FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {
-                    BedrockFormRegistry.getInstance().sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()), BedrockFormRegistry.DEFAULT);
+                    bedrockFormRegistry.sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()), BedrockFormRegistry.DEFAULT);
                 } else {
                     JavaMenu.openMenu(player, ConfigId.SELECTOR);
                 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
index 5291c28..95a1c9e 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
@@ -18,7 +18,6 @@
 
 public class BedrockFormRegistry implements Reloadable {
 
-    private static BedrockFormRegistry INSTANCE;
     public static final String DEFAULT = "default";
 
     /**
@@ -27,14 +26,9 @@ public class BedrockFormRegistry implements Reloadable {
     private boolean isEnabled;
     private final Map<String, BedrockForm> enabledForms = new HashMap<>();
 
-    public static BedrockFormRegistry getInstance() {
-        return INSTANCE;
-    }
-
     public BedrockFormRegistry() {
         ReloadableRegistry.registerReloadable(this);
         isEnabled = load();
-        INSTANCE = this;
     }
 
     private boolean load() {

From 1ea539cf4cd50336f5528406495fbfeb85d3ce24 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Fri, 25 Jun 2021 12:10:51 -0400
Subject: [PATCH 55/74] Revert "Revert "update java config for future multi
 menu implementation""

This reverts commit 29f2028e
---
 src/main/resources/selector.yml | 72 +++++++++++++++++++++++++--------
 1 file changed, 55 insertions(+), 17 deletions(-)

diff --git a/src/main/resources/selector.yml b/src/main/resources/selector.yml
index ee36762..b1cfe47 100644
--- a/src/main/resources/selector.yml
+++ b/src/main/resources/selector.yml
@@ -20,23 +20,61 @@ Selector-Item:
 Java-Selector:
   Enable: true
 
-  Title: "Server Selector"
-  Size: 9
-
-  Servers:
-    lobby:
-      Display-Name: "Lobby"
-      Material: Diamond
-      Lore:
-        - "&2Online players: %bungee_lobby%"
-      Slot: 2
-
-    survival:
-      Display-Name: "Survival"
-      Material: Emerald
-      Lore:
-        - "&2online players: %bungee_survival%"
-      Slot: 6
+  Menus:
+    default:
+      Title: "Server Selector"
+      Size: 9
+      Buttons:
+        2:
+          Display-Name: "Lobby"
+          Material: Diamond
+          Lore:
+            - "&2Online players: %bungee_lobby%"
+          Right-Click:
+            Server: "lobby"
+            Commands:
+              - "tell %player_name% Sending you to the lobby in a right click fashion..."
+          Left-Click:
+            Server: "lobby"
+            Commands:
+              - "tell %player_name% Sending you to the lobby in a left click fashion..."
+        4:
+          Display-Name: "Minigames"
+          Material: Grass
+          Lore:
+            - "Currently Available:"
+            - "Spleef"
+            - "Hide & Seek"
+          Any-Click:
+            Commands:
+              - "execute as %player_name% run ghub form minigames"
+        6:
+          Display-Name: "Survival"
+          Material: Emerald
+          Lore:
+            - "&2online players: %bungee_survival%"
+          Any-Click:
+            Server: "survival"
+            Commands:
+              - "tell %player_name% Sending you to Survival..."
+    minigames:
+      Title: "Server Selector"
+      Size: 3
+      Buttons:
+        0:
+          Display-Name: "Spleef"
+          Material: SNOWBALL
+          Lore:
+            - "Play Spleef!"
+          Any-Click:
+            Server: "spleef"
+        2:
+          Display-Name: "Hide & Seek"
+          Material: DIRT
+          Lore:
+            - "Play Hide & Seek!"
+          Any-Click:
+            Server: "hideseek"
 
 Bedrock-Selector:
   Enable: true

From 6dd448275a8fb55b420ca062685db0cd32a63be2 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Fri, 25 Jun 2021 15:59:55 -0400
Subject: [PATCH 56/74] unfinished multi java menu

---
 .../geyserhub/module/menu/CommandUtils.java   |  30 ---
 .../geyserhub/module/menu/MenuUtils.java      |  73 +++++++
 .../module/menu/bedrock/BedrockForm.java      |  70 +++----
 .../menu/bedrock/BedrockFormRegistry.java     |   2 +-
 .../geyserhub/module/menu/java/JavaForm.java  | 197 ++++++++++++++++++
 .../module/menu/java/JavaMenuRegistry.java    |  58 ++++++
 6 files changed, 356 insertions(+), 74 deletions(-)
 delete mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/java/JavaForm.java
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java b/src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java
deleted file mode 100644
index 6af8ebd..0000000
--- a/src/main/java/dev/projectg/geyserhub/module/menu/CommandUtils.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package dev.projectg.geyserhub.module.menu;
-
-import dev.projectg.geyserhub.SelectorLogger;
-import org.bukkit.Bukkit;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import javax.annotation.Nonnull;
-
-public class CommandUtils {
-
-    public static final String playerPrefix = "player;";
-    public static final String consolePrefix = "console;";
-
-    /**
-     * Process a prefixed command and run it
-     * @param prefixedCommand A command that is prefixed with "player;" to run the command as the player, or "console;", to run the command as the console.
-     * @param player the Player to run the command as, if prefixed with "player;"
-     */
-    public static void runCommand(@Nonnull String prefixedCommand, @Nonnull Player player) {
-        CommandSender sender = Bukkit.getServer().getConsoleSender();
-        if (prefixedCommand.startsWith(playerPrefix)) {
-            sender = player;
-        }
-        // Split the input into two strings between ";" and get the second string
-        String executableCommand = prefixedCommand.split(";", 2)[1].stripLeading();
-        SelectorLogger.getLogger().debug("Running command: [" + executableCommand + "] as " + sender.getName());
-        Bukkit.getServer().dispatchCommand(sender, executableCommand);
-    }
-}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java b/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
new file mode 100644
index 0000000..60bae0e
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
@@ -0,0 +1,73 @@
+package dev.projectg.geyserhub.module.menu;
+
+import dev.projectg.geyserhub.SelectorLogger;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.entity.Player;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+public class MenuUtils {
+
+    public static final String playerPrefix = "player;";
+    public static final String consolePrefix = "console;";
+
+    /**
+     * Process a prefixed command and run it
+     * @param prefixedCommand A command that is prefixed with "player;" to run the command as the player, or "console;", to run the command as the console.
+     * @param player the Player to run the command as, if prefixed with "player;"
+     */
+    public static void runCommand(@Nonnull String prefixedCommand, @Nonnull Player player) {
+        CommandSender sender = Bukkit.getServer().getConsoleSender();
+        if (prefixedCommand.startsWith(playerPrefix)) {
+            sender = player;
+        }
+        // Split the input into two strings between ";" and get the second string
+        String executableCommand = prefixedCommand.split(";", 2)[1].stripLeading();
+        SelectorLogger.getLogger().debug("Running command: [" + executableCommand + "] as " + sender.getName());
+        Bukkit.getServer().dispatchCommand(sender, executableCommand);
+    }
+
+    @Nonnull
+    public static List<String> getCommands(@Nonnull ConfigurationSection buttonData) {
+        SelectorLogger logger = SelectorLogger.getLogger();
+
+        if (buttonData.contains("Commands") && buttonData.isList("Commands")) {
+            if (buttonData.getStringList("Commands").isEmpty()) {
+                logger.warn(getParentName(buttonData) + "." + buttonData.getName() + " contains commands list but the list was empty.");
+            } else {
+                List<String> commands = buttonData.getStringList("Commands");
+                logger.debug(getParentName(buttonData) + "." + buttonData.getName() + " contains commands: " + commands);
+                return commands;
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    @Nullable
+    public static String getServer(@Nonnull ConfigurationSection buttonData) {
+        SelectorLogger logger = SelectorLogger.getLogger();
+
+        if (buttonData.contains("Server") && buttonData.isString("Server")) {
+            String serverName = Objects.requireNonNull(buttonData.getString("Server"));
+            logger.debug(getParentName(buttonData) + "." + buttonData.getName() + " contains BungeeCord target server: " + serverName);
+            return serverName;
+        }
+        return null;
+    }
+
+    @Nonnull
+    public static String getParentName(@Nonnull ConfigurationSection configSection) {
+        ConfigurationSection parent = configSection.getParent();
+        if (parent == null) {
+            return "null";
+        } else {
+            return parent.getName();
+        }
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index 34e6910..76ed2d2 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -2,7 +2,7 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
-import dev.projectg.geyserhub.module.menu.CommandUtils;
+import dev.projectg.geyserhub.module.menu.MenuUtils;
 import dev.projectg.geyserhub.utils.PlaceholderUtils;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
@@ -26,7 +26,10 @@
 
 public class BedrockForm {
 
-    private final boolean isEnabled;
+    private final SelectorLogger logger;
+
+    public final boolean isEnabled;
+    public final String formName;
 
     private String title;
     private String content;
@@ -35,49 +38,40 @@ public class BedrockForm {
     /**
      * Create a new bedrock selector form and initializes it with the current loaded config
      */
-    protected BedrockForm(@Nonnull ConfigurationSection section) {
-        isEnabled = load(section);
-    }
-
-    protected boolean isEnabled() {
-        return isEnabled;
-    }
-
-    /**
-     * Initialize or refresh the server selector form
-     */
-    private boolean load(@Nonnull ConfigurationSection configSection) {
-
-        SelectorLogger logger = SelectorLogger.getLogger();
-
-        String title = configSection.getString("Title");
-        String content = configSection.getString("Content");
-        if (title == null || content == null) {
-            logger.severe("Value of Bedrock-Selector.Title or Bedrock-Selector.Content has no value in the config for form: "  + configSection.getName() + "! Failed to create the bedrock selector form.");
-            return false;
+    protected BedrockForm(@Nonnull ConfigurationSection configSection) {
+        logger = SelectorLogger.getLogger();
+        Objects.requireNonNull(configSection);
+        formName = configSection.getName();
+
+        // Get the Title and Content
+        if (!configSection.contains("Title") || !configSection.contains("Content")) {
+            logger.warn("Bedrock Form: "  + formName + " does not contain a Title or Content value! Failed to create the form.");
+            isEnabled = false;
+            return;
+        } else {
+            this.title = Objects.requireNonNull(configSection.getString("Title"));
+            this.content = Objects.requireNonNull(configSection.getString("Content"));
         }
 
         // Get our Buttons
         if (!(configSection.contains("Buttons", true) && configSection.isConfigurationSection("Buttons"))) {
-            logger.warn("Form: " + configSection.getName() + " does not contain a Buttons section, unable to create form");
-            return false;
+            logger.warn("Bedrock Form: " + formName + " does not contain a Buttons section, unable to create form");
+            isEnabled = false;
+            return;
         }
         ConfigurationSection buttonSection = configSection.getConfigurationSection("Buttons");
         Objects.requireNonNull(buttonSection);
         List<BedrockButton> buttons = getButtons(buttonSection);
         if (buttons.isEmpty()) {
-            logger.warn("Failed to create any valid buttons of form: " + configSection.getName() + "! All listed buttons have a malformed section!");
-            return false;
+            logger.warn("Failed to create any valid buttons of Bedrock form: " + formName + "! All listed buttons have a malformed section!");
+            isEnabled = false;
+            return;
         } else {
-            logger.debug("Finished adding buttons to form: " + configSection.getName());
+            logger.debug("Finished adding buttons to bedrock form: " + formName);
         }
-
-        // Only set everything once it has been validated
-        this.title = title;
-        this.content = content;
         this.allButtons = buttons;
 
-        return true;
+        isEnabled = true;
     }
 
     /**
@@ -86,16 +80,6 @@ private boolean load(@Nonnull ConfigurationSection configSection) {
      * @return A list of Buttons, which may be empty.
      */
     private List<BedrockButton> getButtons(@Nonnull ConfigurationSection configSection) {
-        SelectorLogger logger = SelectorLogger.getLogger();
-
-        // Get the form name
-        String formName;
-        ConfigurationSection parent = configSection.getParent();
-        if (parent == null) {
-            formName = "null";
-        } else {
-            formName = parent.getName();
-        }
         logger.debug("Getting buttons for form: " + formName);
 
         // Get all the defined buttons in the buttons section
@@ -205,7 +189,7 @@ protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
             if (!button.getCommands().isEmpty()) {
                 // Get the commands from the list of commands and replace any playerName placeholders
                 for (String command : button.getCommands()) {
-                    CommandUtils.runCommand(PlaceholderUtils.setPlaceholders(player, command), player);
+                    MenuUtils.runCommand(PlaceholderUtils.setPlaceholders(player, command), player);
                 }
             }
 
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
index 95a1c9e..b067177 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
@@ -57,7 +57,7 @@ private boolean load() {
                             ConfigurationSection formInfo = forms.getConfigurationSection(entry);
                             Objects.requireNonNull(formInfo);
                             BedrockForm form = new BedrockForm(formInfo);
-                            if (form.isEnabled()) {
+                            if (form.isEnabled) {
                                 enabledForms.put(entry, form);
                                 noSuccess = false;
                             } else {
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaForm.java
new file mode 100644
index 0000000..6d730ce
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaForm.java
@@ -0,0 +1,197 @@
+package dev.projectg.geyserhub.module.menu.java;
+
+import dev.projectg.geyserhub.SelectorLogger;
+import dev.projectg.geyserhub.module.menu.MenuUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.*;
+
+public class JavaForm {
+
+    private final SelectorLogger logger;
+
+    private final boolean isEnabled;
+    @Nonnull private final String menuName;
+
+    private String title;
+    private int size;
+    private Map<Integer, ItemButton> buttons;
+
+    protected JavaForm(@Nonnull ConfigurationSection configSection) {
+        logger = SelectorLogger.getLogger();
+        Objects.requireNonNull(configSection);
+        menuName = configSection.getName();
+
+        // Get the inventory title and size
+        if (configSection.contains("Title") && configSection.contains("Size") && configSection.isInt("Size")) {
+            title = Objects.requireNonNull(configSection.getString("Title"));
+            size = configSection.getInt("Size");
+            logger.debug("Java Menu: " + menuName + " has Title: " + title + " Size: " + size);
+        } else {
+            logger.warn("Java Menu: " + menuName + " does not contain a Title or Size value, unable to create menu");
+            isEnabled = false;
+            return;
+        }
+
+        // Get the Buttons
+        if (configSection.contains("Buttons", true) && configSection.isConfigurationSection("Buttons")) {
+            ConfigurationSection buttonSection = configSection.getConfigurationSection("Buttons");
+            Objects.requireNonNull(buttonSection);
+            Map<Integer, ItemButton> buttons = getAllButtons(buttonSection);
+            if (buttons.isEmpty()) {
+                logger.warn("Failed to create any valid buttons of Bedrock form: " + menuName + "! All listed buttons have a malformed section!");
+                isEnabled = false;
+                return;
+            } else {
+                logger.debug("Finished adding buttons to Java menu: " + menuName);
+            }
+            this.buttons = buttons;
+        } else {
+            logger.warn("Java Menu: " + menuName + " does not contain a Buttons section, unable to create form.");
+            isEnabled = false;
+            return;
+        }
+
+        // Make sure the inventory will be able to hold all the buttons
+        int highestGivenSlot = 0;
+        for (Integer slot: buttons.keySet()) {
+            highestGivenSlot = Math.max(slot, highestGivenSlot);
+        }
+        int minimumSize = highestGivenSlot + 1;
+        if (minimumSize > size) {
+            logger.warn("Java Menu: " + menuName + " has a button with slot " + highestGivenSlot + ", but the inventory size is only " + size + ". Expanding the size to " + minimumSize + " (+" + (minimumSize - size) +  ")." );
+            size = minimumSize;
+        }
+
+        isEnabled = true;
+    }
+
+    @Nonnull
+    private Map<Integer, ItemButton> getAllButtons(@Nonnull ConfigurationSection configSection) {
+        logger.debug("Getting buttons for java form: " + menuName);
+
+        // Get all the defined buttons in the buttons section
+        Set<String> allButtonIds = configSection.getKeys(false);
+        if (allButtonIds.isEmpty()) {
+            logger.warn("No buttons were listed for form: " + menuName);
+            return Collections.emptyMap();
+        }
+
+        // Create a list of buttons. For every defined button with a valid server or command configuration, we add its button.
+        Map<Integer, ItemButton> compiledButtons = new HashMap<>();
+        for (String buttonId : allButtonIds) {
+
+            // Make sure its a configuration section (we know it exists)
+            ConfigurationSection buttonInfo = configSection.getConfigurationSection(buttonId);
+            if (buttonInfo == null) {
+                logger.warn("Java Button: " + menuName + "." + buttonId + " was not added because it is not a configuration section!");
+                continue;
+            }
+            // Make sure the key is a integer (the slot value of the button item)
+            int slot;
+            try {
+                slot = Integer.parseUnsignedInt(buttonId);
+            } catch (NumberFormatException e) {
+                logger.warn("Java Button: " + menuName + "." + buttonId + " was not added because its config name is not a positive integer!");
+                continue;
+            }
+
+            ItemButton button = getButton(buttonInfo);
+            if (button != null) {
+                compiledButtons.put(slot, button);
+                logger.debug("Java Button: " + menuName + "." + buttonId + " was successfully added.");
+            }
+        }
+        return compiledButtons;
+    }
+
+    @Nullable
+    private ItemButton getButton(@Nonnull ConfigurationSection buttonInfo) {
+        String buttonId = buttonInfo.getName();
+
+        String displayName;
+        if (buttonInfo.contains("Display-Name", true) && buttonInfo.isString("Display-Name")) {
+            displayName = buttonInfo.getString("Display-Name");
+            Objects.requireNonNull(displayName);
+            logger.debug("Java Button: " + menuName + "." + buttonId + " has Display-Name: " + displayName);
+        } else {
+            logger.warn("Java Button: " + menuName + "." + buttonId + " does not contain a valid Button-Text value, not adding.");
+            return null;
+        }
+
+        Material material;
+        if (buttonInfo.contains("Material") && buttonInfo.isString("Material")) {
+            String materialName = buttonInfo.getString("Material");
+            Objects.requireNonNull(materialName);
+            material = Material.getMaterial(materialName, false);
+            if (material == null) {
+                material = Material.getMaterial(materialName, true);
+                if (material == null) {
+                    logger.warn("Java Button: " + menuName + "." + buttonId + " was not added because the Material it provided was not valid.");
+                    return null;
+                } else {
+                    logger.warn("Java Button: " + menuName + "." + buttonId + "specified a legacy Material, please update it: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html");
+                }
+            }
+        } else {
+            logger.warn("Java Button: " + menuName + "." + buttonId + " was not added because it is does not contain a Material value!");
+            return null;
+        }
+
+        // Create the button
+        ItemButton button = new ItemButton(displayName, material);
+
+        // Set server(s) and commands
+        ConfigurationSection rightClick = null;
+        if (buttonInfo.contains("Right-Click") && buttonInfo.isConfigurationSection("Right-Click")) {
+            rightClick = buttonInfo.getConfigurationSection("Right-Click");
+            Objects.requireNonNull(rightClick);
+            button.getRightClickButton().setCommands(MenuUtils.getCommands(rightClick));
+            button.getRightClickButton().setServer(MenuUtils.getServer(rightClick));
+        }
+        ConfigurationSection leftClick = null;
+        if (buttonInfo.contains("Left-Click") && buttonInfo.isConfigurationSection("Left-Click")) {
+            leftClick = buttonInfo.getConfigurationSection("Left-Click");
+            Objects.requireNonNull(leftClick);
+            button.getLeftClickButton().setCommands(MenuUtils.getCommands(leftClick));
+            button.getLeftClickButton().setServer(MenuUtils.getServer(leftClick));
+        }
+
+        if (buttonInfo.contains("Any-Click") && buttonInfo.isConfigurationSection("Any-Click")) {
+            if (rightClick != null || leftClick != null) {
+                logger.warn("Java Button: " + menuName + "." + buttonId + " Cannot define both Any-Click behaviour and also Right/Left-Click behaviour! Ignoring Any-Click section.");
+            } else {
+                ConfigurationSection anyClick = buttonInfo.getConfigurationSection("Any-Click");
+                Objects.requireNonNull(anyClick);
+                button.getRightClickButton().setCommands(MenuUtils.getCommands(anyClick));
+                button.getRightClickButton().setServer(MenuUtils.getServer(anyClick));
+                button.getLeftClickButton().setCommands(MenuUtils.getCommands(anyClick));
+                button.getLeftClickButton().setServer(MenuUtils.getServer(anyClick));
+            }
+        }
+
+        return button;
+    }
+
+    protected void sendMenu(@Nonnull Player player) {
+        if (!isEnabled) {
+            throw new AssertionError("Tried to send Java Menu: " + menuName + " to a player but the form was not enabled");
+        }
+
+        Inventory selectorGUI = Bukkit.createInventory(player, size, ChatColor.DARK_AQUA + title);
+
+        for (Integer slot : buttons.keySet()) {
+            ItemButton button = buttons.get(slot);
+        }
+
+        // todo: finish
+
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java
new file mode 100644
index 0000000..958754c
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java
@@ -0,0 +1,58 @@
+package dev.projectg.geyserhub.module.menu.java;
+
+import dev.projectg.geyserhub.GeyserHubMain;
+import dev.projectg.geyserhub.SelectorLogger;
+import dev.projectg.geyserhub.config.ConfigId;
+import dev.projectg.geyserhub.reloadable.Reloadable;
+import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.entity.Player;
+import org.geysermc.floodgate.api.player.FloodgatePlayer;
+
+import javax.annotation.Nonnull;
+import java.util.*;
+
+public class JavaMenuRegistry implements Reloadable {
+
+    public static final String DEFAULT = "default";
+
+    /**
+     * If bedrock forms are enabled. may be false if disabled in the config or if all forms failed to load.
+     */
+    private boolean isEnabled;
+    private final Map<String, JavaForm> enabledMenus = new HashMap<>();
+
+    public JavaMenuRegistry() {
+        ReloadableRegistry.registerReloadable(this);
+        isEnabled = load();
+    }
+
+    private boolean load() {
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
+        SelectorLogger logger = SelectorLogger.getLogger();
+
+        enabledMenus.clear();
+
+        //
+
+        return false;
+    }
+
+    public void sendForm(@Nonnull Player player, @Nonnull String form) {
+        enabledMenus.get(form);
+        // todo: fill
+    }
+
+    public boolean isEnabled() {
+        return isEnabled;
+    }
+    public List<String> getFormNames() {
+        return new ArrayList<>(enabledMenus.keySet());
+    }
+
+    @Override
+    public boolean reload() {
+        isEnabled = load();
+        return true;
+    }
+}

From af4bdcfa3a9fa978cb153863ce15798c49d51144 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Fri, 25 Jun 2021 17:37:58 -0400
Subject: [PATCH 57/74] more work to java menus, still unfinished

---
 .../geyserhub/module/menu/java/JavaForm.java  | 46 ++++++++++++++--
 .../module/menu/java/JavaMenuRegistry.java    | 55 +++++++++++++++++--
 2 files changed, 92 insertions(+), 9 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaForm.java
index 6d730ce..839ef45 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaForm.java
@@ -1,13 +1,19 @@
 package dev.projectg.geyserhub.module.menu.java;
 
+import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
 import dev.projectg.geyserhub.module.menu.MenuUtils;
+import dev.projectg.geyserhub.utils.PlaceholderUtils;
+import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
 import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
 import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.entity.Player;
 import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.bukkit.persistence.PersistentDataType;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -15,9 +21,12 @@
 
 public class JavaForm {
 
+    private static final String PERSISTENT_KEY = "geyserHubButton";
+    private static final Byte PERSISTENT_VALUE = (byte) 1;
+
     private final SelectorLogger logger;
 
-    private final boolean isEnabled;
+    public final boolean isEnabled;
     @Nonnull private final String menuName;
 
     private String title;
@@ -66,10 +75,16 @@ protected JavaForm(@Nonnull ConfigurationSection configSection) {
         }
         int minimumSize = highestGivenSlot + 1;
         if (minimumSize > size) {
-            logger.warn("Java Menu: " + menuName + " has a button with slot " + highestGivenSlot + ", but the inventory size is only " + size + ". Expanding the size to " + minimumSize + " (+" + (minimumSize - size) +  ")." );
+            logger.warn("Java Menu: " + menuName + " has a button with slot " + highestGivenSlot + ", but the inventory size is only " + size + ". Increasing the size.");
             size = minimumSize;
         }
 
+        // Make sure that the inventory size is a multiple of 9
+        if (size % 9 != 0) {
+            // Divide the size by 9, round the ratio up to the next int value, then multiply by 9 to get the closest higher number that is a multiple of 9
+            size = (int) (9*(Math.ceil(Math.abs(size/9))));
+        }
+
         isEnabled = true;
     }
 
@@ -185,13 +200,34 @@ protected void sendMenu(@Nonnull Player player) {
             throw new AssertionError("Tried to send Java Menu: " + menuName + " to a player but the form was not enabled");
         }
 
-        Inventory selectorGUI = Bukkit.createInventory(player, size, ChatColor.DARK_AQUA + title);
+        Inventory selectorGUI = Bukkit.createInventory(player, size, PlaceholderUtils.setPlaceholders(player, title));
 
         for (Integer slot : buttons.keySet()) {
             ItemButton button = buttons.get(slot);
+
+            // Construct the item
+            ItemStack serverStack = new ItemStack(button.getMaterial());
+            ItemMeta itemMeta = serverStack.getItemMeta();
+            if (itemMeta != null) {
+                itemMeta.setDisplayName(PlaceholderUtils.setPlaceholders(player, button.getDisplayName()));
+                itemMeta.setLore(PlaceholderAPI.setPlaceholders(player, button.getLore()));
+                serverStack.setItemMeta(itemMeta);
+            } else {
+                logger.warn("Java Button: " + menuName + "." + slot + " with Material: " + button.getMaterial() + " returned null ItemMeta, failed to set display name or lore.");
+            }
+
+            selectorGUI.setItem(slot, serverStack);
         }
 
-        // todo: finish
+        // Set a persistent data key in the first ItemStack with ItemMeta, to check in the future if the inventory is a menu
+        for (ItemStack stack : selectorGUI) {
+            ItemMeta meta = stack.getItemMeta();
+            if (meta != null) {
+                meta.getPersistentDataContainer().set(new NamespacedKey(GeyserHubMain.getInstance(), PERSISTENT_KEY), PersistentDataType.BYTE, PERSISTENT_VALUE);
+                break;
+            }
+        }
 
+        player.openInventory(selectorGUI);
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java
index 958754c..f0b6a48 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java
@@ -5,9 +5,9 @@
 import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.reloadable.Reloadable;
 import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
+import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
-import org.geysermc.floodgate.api.player.FloodgatePlayer;
 
 import javax.annotation.Nonnull;
 import java.util.*;
@@ -33,14 +33,61 @@ private boolean load() {
 
         enabledMenus.clear();
 
-        //
+        if (config.contains("Java-Selector", true) && config.isConfigurationSection("Java-Selector")) {
+            ConfigurationSection selectorSection = config.getConfigurationSection("Java-Selector");
+            Objects.requireNonNull(selectorSection);
 
+            if (selectorSection.contains("Enable", true) && selectorSection.isBoolean("Enable")) {
+                if (selectorSection.getBoolean("Enable")) {
+                    if (selectorSection.contains("Menus", true) && selectorSection.isConfigurationSection("Menus")) {
+                        ConfigurationSection menus = selectorSection.getConfigurationSection("Menus");
+                        Objects.requireNonNull(menus);
+
+                        boolean noSuccess = true;
+                        boolean containsDefault = false;
+                        for (String entry : menus.getKeys(false)) {
+                            if (!menus.isConfigurationSection(entry)) {
+                                logger.warn("Java menu with name " + entry + " is being skipped because it is not a configuration section");
+                                continue;
+                            }
+                            ConfigurationSection formInfo = menus.getConfigurationSection(entry);
+                            Objects.requireNonNull(formInfo);
+                            JavaForm menu = new JavaForm(formInfo);
+                            if (menu.isEnabled) {
+                                enabledMenus.put(entry, menu);
+                                noSuccess = false;
+                            } else {
+                                logger.warn("Not adding Java manu for config section: " + entry + " because there was a failure loading it.");
+                            }
+                            if ("default".equals(entry)) {
+                                containsDefault = true;
+                            }
+                        }
+
+                        if (!containsDefault) {
+                            logger.warn("Failed to load a default Java menus! The Java Server Selector compass will not work and players will not be able to open the default form with \"/ghub\"");
+                        }
+                        if (noSuccess) {
+                            logger.warn("Failed to load ALL Java menus, due to configuration error.");
+                        } else {
+                            logger.info("Valid Java menus are: " + enabledMenus.keySet());
+                            return true;
+                        }
+                    }
+                } else {
+                    logger.debug("Not enabling Java menus because it is disabled in the config.");
+                }
+            } else {
+                logger.warn("Not enabling Java menus because the Enable value is not present in the config.");
+            }
+        } else {
+            logger.warn("Not enabling Java menus because the whole configuration section is not present.");
+        }
         return false;
     }
 
     public void sendForm(@Nonnull Player player, @Nonnull String form) {
-        enabledMenus.get(form);
-        // todo: fill
+        enabledMenus.get(form).sendMenu(player);
     }
 
     public boolean isEnabled() {

From 722e2070197fdea13d0b40e31d628a04018c807f Mon Sep 17 00:00:00 2001
From: Konicai <71294714+konicai@users.noreply.github.com>
Date: Sat, 26 Jun 2021 13:52:16 -0400
Subject: [PATCH 58/74] Frontend now uses the new java menu system

---
 .../dev/projectg/geyserhub/GeyserHubMain.java |   8 +-
 .../geyserhub/command/GeyserHubCommand.java   |  17 +-
 .../module/menu/CommonMenuListeners.java      |   8 +-
 .../geyserhub/module/menu/java/JavaForm.java  | 233 -----------------
 .../geyserhub/module/menu/java/JavaMenu.java  | 246 +++++++++++++-----
 .../module/menu/java/JavaMenuListeners.java   |   1 -
 .../module/menu/java/JavaMenuRegistry.java    |   6 +-
 7 files changed, 212 insertions(+), 307 deletions(-)
 delete mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/java/JavaForm.java

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 9210789..865d7da 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -5,6 +5,7 @@
 import dev.projectg.geyserhub.module.menu.CommonMenuListeners;
 import dev.projectg.geyserhub.module.menu.java.JavaMenuListeners;
 import dev.projectg.geyserhub.module.menu.bedrock.BedrockFormRegistry;
+import dev.projectg.geyserhub.module.menu.java.JavaMenuRegistry;
 import dev.projectg.geyserhub.module.message.Broadcast;
 import dev.projectg.geyserhub.module.message.MessageJoin;
 import dev.projectg.geyserhub.module.scoreboard.ScoreboardManager;
@@ -51,14 +52,15 @@ public void onEnable() {
         // Bungee channel for selector
         getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
 
-        // Load bedrock forms
+        // Load forms
         BedrockFormRegistry bedrockFormRegistry = new BedrockFormRegistry();
+        JavaMenuRegistry javaMenuRegistry = new JavaMenuRegistry();
 
         // todo: and add command suggestions/completions, help pages that only shows available commands
-        Objects.requireNonNull(getCommand("ghub")).setExecutor(new GeyserHubCommand(bedrockFormRegistry));
+        Objects.requireNonNull(getCommand("ghub")).setExecutor(new GeyserHubCommand(bedrockFormRegistry, javaMenuRegistry));
 
         // Listeners for the Bedrock and Java menus
-        Bukkit.getServer().getPluginManager().registerEvents(new CommonMenuListeners(bedrockFormRegistry), this);
+        Bukkit.getServer().getPluginManager().registerEvents(new CommonMenuListeners(bedrockFormRegistry, javaMenuRegistry), this);
         Bukkit.getServer().getPluginManager().registerEvents(new JavaMenuListeners(), this);
 
         // Listener the Join Teleporter module
diff --git a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
index 768c49c..79821e8 100644
--- a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
+++ b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
@@ -1,10 +1,9 @@
 package dev.projectg.geyserhub.command;
 
 import dev.projectg.geyserhub.SelectorLogger;
-import dev.projectg.geyserhub.config.ConfigId;
+import dev.projectg.geyserhub.module.menu.java.JavaMenuRegistry;
 import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
 import dev.projectg.geyserhub.module.menu.bedrock.BedrockFormRegistry;
-import dev.projectg.geyserhub.module.menu.java.JavaMenu;
 import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
 import org.bukkit.command.Command;
@@ -33,9 +32,11 @@ public class GeyserHubCommand implements CommandExecutor {
     private static final String UNKNOWN = "Sorry, that's an unknown command!";
 
     private final BedrockFormRegistry bedrockRegistry;
+    private final JavaMenuRegistry javaMenuRegistry;
 
-    public GeyserHubCommand(BedrockFormRegistry bedrockRegistry) {
+    public GeyserHubCommand(BedrockFormRegistry bedrockRegistry, JavaMenuRegistry javaMenuRegistry) {
         this.bedrockRegistry = bedrockRegistry;
+        this.javaMenuRegistry = javaMenuRegistry;
     }
 
     @Override
@@ -125,7 +126,15 @@ private void sendForm(@Nonnull CommandSender commandSender, @Nonnull String form
                     sendMessage(player, SelectorLogger.Level.SEVERE, "Sorry, Bedrock forms are disabled!");
                 }
             } else {
-                JavaMenu.openMenu(player, ConfigId.SELECTOR);
+                if (javaMenuRegistry.isEnabled()) {
+                    if (javaMenuRegistry.getFormNames().contains(formName)) {
+                        javaMenuRegistry.sendForm(player, formName);
+                    } else {
+                        sendMessage(player, SelectorLogger.Level.SEVERE, "Sorry, that form doesn't exist! Specify a form with \"/ghub form <form>\"");
+                    }
+                } else {
+                    sendMessage(player, SelectorLogger.Level.SEVERE, "Sorry, Java menus are disabled!");
+                }
             }
         } else if (commandSender instanceof ConsoleCommandSender) {
             sendHelp(commandSender);
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
index 2f94929..ec02c73 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
@@ -3,7 +3,7 @@
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.module.menu.bedrock.BedrockFormRegistry;
-import dev.projectg.geyserhub.module.menu.java.JavaMenu;
+import dev.projectg.geyserhub.module.menu.java.JavaMenuRegistry;
 import org.bukkit.Material;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
@@ -22,9 +22,11 @@
 public class CommonMenuListeners implements Listener {
 
     private final BedrockFormRegistry bedrockFormRegistry;
+    private final JavaMenuRegistry javaMenuRegistry;
 
-    public CommonMenuListeners(BedrockFormRegistry bedrockFormRegistry) {
+    public CommonMenuListeners(BedrockFormRegistry bedrockFormRegistry, JavaMenuRegistry javaMenuRegistry) {
         this.bedrockFormRegistry = bedrockFormRegistry;
+        this.javaMenuRegistry = javaMenuRegistry;
     }
 
     @EventHandler
@@ -35,7 +37,7 @@ public void onInteract(PlayerInteractEvent event) { // open the menu through the
                 if (FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {
                     bedrockFormRegistry.sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()), BedrockFormRegistry.DEFAULT);
                 } else {
-                    JavaMenu.openMenu(player, ConfigId.SELECTOR);
+                    javaMenuRegistry.sendForm(player, JavaMenuRegistry.DEFAULT);
                 }
             }
         }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaForm.java
deleted file mode 100644
index 839ef45..0000000
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaForm.java
+++ /dev/null
@@ -1,233 +0,0 @@
-package dev.projectg.geyserhub.module.menu.java;
-
-import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.SelectorLogger;
-import dev.projectg.geyserhub.module.menu.MenuUtils;
-import dev.projectg.geyserhub.utils.PlaceholderUtils;
-import me.clip.placeholderapi.PlaceholderAPI;
-import org.bukkit.Bukkit;
-import org.bukkit.Material;
-import org.bukkit.NamespacedKey;
-import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.entity.Player;
-import org.bukkit.inventory.Inventory;
-import org.bukkit.inventory.ItemStack;
-import org.bukkit.inventory.meta.ItemMeta;
-import org.bukkit.persistence.PersistentDataType;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import java.util.*;
-
-public class JavaForm {
-
-    private static final String PERSISTENT_KEY = "geyserHubButton";
-    private static final Byte PERSISTENT_VALUE = (byte) 1;
-
-    private final SelectorLogger logger;
-
-    public final boolean isEnabled;
-    @Nonnull private final String menuName;
-
-    private String title;
-    private int size;
-    private Map<Integer, ItemButton> buttons;
-
-    protected JavaForm(@Nonnull ConfigurationSection configSection) {
-        logger = SelectorLogger.getLogger();
-        Objects.requireNonNull(configSection);
-        menuName = configSection.getName();
-
-        // Get the inventory title and size
-        if (configSection.contains("Title") && configSection.contains("Size") && configSection.isInt("Size")) {
-            title = Objects.requireNonNull(configSection.getString("Title"));
-            size = configSection.getInt("Size");
-            logger.debug("Java Menu: " + menuName + " has Title: " + title + " Size: " + size);
-        } else {
-            logger.warn("Java Menu: " + menuName + " does not contain a Title or Size value, unable to create menu");
-            isEnabled = false;
-            return;
-        }
-
-        // Get the Buttons
-        if (configSection.contains("Buttons", true) && configSection.isConfigurationSection("Buttons")) {
-            ConfigurationSection buttonSection = configSection.getConfigurationSection("Buttons");
-            Objects.requireNonNull(buttonSection);
-            Map<Integer, ItemButton> buttons = getAllButtons(buttonSection);
-            if (buttons.isEmpty()) {
-                logger.warn("Failed to create any valid buttons of Bedrock form: " + menuName + "! All listed buttons have a malformed section!");
-                isEnabled = false;
-                return;
-            } else {
-                logger.debug("Finished adding buttons to Java menu: " + menuName);
-            }
-            this.buttons = buttons;
-        } else {
-            logger.warn("Java Menu: " + menuName + " does not contain a Buttons section, unable to create form.");
-            isEnabled = false;
-            return;
-        }
-
-        // Make sure the inventory will be able to hold all the buttons
-        int highestGivenSlot = 0;
-        for (Integer slot: buttons.keySet()) {
-            highestGivenSlot = Math.max(slot, highestGivenSlot);
-        }
-        int minimumSize = highestGivenSlot + 1;
-        if (minimumSize > size) {
-            logger.warn("Java Menu: " + menuName + " has a button with slot " + highestGivenSlot + ", but the inventory size is only " + size + ". Increasing the size.");
-            size = minimumSize;
-        }
-
-        // Make sure that the inventory size is a multiple of 9
-        if (size % 9 != 0) {
-            // Divide the size by 9, round the ratio up to the next int value, then multiply by 9 to get the closest higher number that is a multiple of 9
-            size = (int) (9*(Math.ceil(Math.abs(size/9))));
-        }
-
-        isEnabled = true;
-    }
-
-    @Nonnull
-    private Map<Integer, ItemButton> getAllButtons(@Nonnull ConfigurationSection configSection) {
-        logger.debug("Getting buttons for java form: " + menuName);
-
-        // Get all the defined buttons in the buttons section
-        Set<String> allButtonIds = configSection.getKeys(false);
-        if (allButtonIds.isEmpty()) {
-            logger.warn("No buttons were listed for form: " + menuName);
-            return Collections.emptyMap();
-        }
-
-        // Create a list of buttons. For every defined button with a valid server or command configuration, we add its button.
-        Map<Integer, ItemButton> compiledButtons = new HashMap<>();
-        for (String buttonId : allButtonIds) {
-
-            // Make sure its a configuration section (we know it exists)
-            ConfigurationSection buttonInfo = configSection.getConfigurationSection(buttonId);
-            if (buttonInfo == null) {
-                logger.warn("Java Button: " + menuName + "." + buttonId + " was not added because it is not a configuration section!");
-                continue;
-            }
-            // Make sure the key is a integer (the slot value of the button item)
-            int slot;
-            try {
-                slot = Integer.parseUnsignedInt(buttonId);
-            } catch (NumberFormatException e) {
-                logger.warn("Java Button: " + menuName + "." + buttonId + " was not added because its config name is not a positive integer!");
-                continue;
-            }
-
-            ItemButton button = getButton(buttonInfo);
-            if (button != null) {
-                compiledButtons.put(slot, button);
-                logger.debug("Java Button: " + menuName + "." + buttonId + " was successfully added.");
-            }
-        }
-        return compiledButtons;
-    }
-
-    @Nullable
-    private ItemButton getButton(@Nonnull ConfigurationSection buttonInfo) {
-        String buttonId = buttonInfo.getName();
-
-        String displayName;
-        if (buttonInfo.contains("Display-Name", true) && buttonInfo.isString("Display-Name")) {
-            displayName = buttonInfo.getString("Display-Name");
-            Objects.requireNonNull(displayName);
-            logger.debug("Java Button: " + menuName + "." + buttonId + " has Display-Name: " + displayName);
-        } else {
-            logger.warn("Java Button: " + menuName + "." + buttonId + " does not contain a valid Button-Text value, not adding.");
-            return null;
-        }
-
-        Material material;
-        if (buttonInfo.contains("Material") && buttonInfo.isString("Material")) {
-            String materialName = buttonInfo.getString("Material");
-            Objects.requireNonNull(materialName);
-            material = Material.getMaterial(materialName, false);
-            if (material == null) {
-                material = Material.getMaterial(materialName, true);
-                if (material == null) {
-                    logger.warn("Java Button: " + menuName + "." + buttonId + " was not added because the Material it provided was not valid.");
-                    return null;
-                } else {
-                    logger.warn("Java Button: " + menuName + "." + buttonId + "specified a legacy Material, please update it: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html");
-                }
-            }
-        } else {
-            logger.warn("Java Button: " + menuName + "." + buttonId + " was not added because it is does not contain a Material value!");
-            return null;
-        }
-
-        // Create the button
-        ItemButton button = new ItemButton(displayName, material);
-
-        // Set server(s) and commands
-        ConfigurationSection rightClick = null;
-        if (buttonInfo.contains("Right-Click") && buttonInfo.isConfigurationSection("Right-Click")) {
-            rightClick = buttonInfo.getConfigurationSection("Right-Click");
-            Objects.requireNonNull(rightClick);
-            button.getRightClickButton().setCommands(MenuUtils.getCommands(rightClick));
-            button.getRightClickButton().setServer(MenuUtils.getServer(rightClick));
-        }
-        ConfigurationSection leftClick = null;
-        if (buttonInfo.contains("Left-Click") && buttonInfo.isConfigurationSection("Left-Click")) {
-            leftClick = buttonInfo.getConfigurationSection("Left-Click");
-            Objects.requireNonNull(leftClick);
-            button.getLeftClickButton().setCommands(MenuUtils.getCommands(leftClick));
-            button.getLeftClickButton().setServer(MenuUtils.getServer(leftClick));
-        }
-
-        if (buttonInfo.contains("Any-Click") && buttonInfo.isConfigurationSection("Any-Click")) {
-            if (rightClick != null || leftClick != null) {
-                logger.warn("Java Button: " + menuName + "." + buttonId + " Cannot define both Any-Click behaviour and also Right/Left-Click behaviour! Ignoring Any-Click section.");
-            } else {
-                ConfigurationSection anyClick = buttonInfo.getConfigurationSection("Any-Click");
-                Objects.requireNonNull(anyClick);
-                button.getRightClickButton().setCommands(MenuUtils.getCommands(anyClick));
-                button.getRightClickButton().setServer(MenuUtils.getServer(anyClick));
-                button.getLeftClickButton().setCommands(MenuUtils.getCommands(anyClick));
-                button.getLeftClickButton().setServer(MenuUtils.getServer(anyClick));
-            }
-        }
-
-        return button;
-    }
-
-    protected void sendMenu(@Nonnull Player player) {
-        if (!isEnabled) {
-            throw new AssertionError("Tried to send Java Menu: " + menuName + " to a player but the form was not enabled");
-        }
-
-        Inventory selectorGUI = Bukkit.createInventory(player, size, PlaceholderUtils.setPlaceholders(player, title));
-
-        for (Integer slot : buttons.keySet()) {
-            ItemButton button = buttons.get(slot);
-
-            // Construct the item
-            ItemStack serverStack = new ItemStack(button.getMaterial());
-            ItemMeta itemMeta = serverStack.getItemMeta();
-            if (itemMeta != null) {
-                itemMeta.setDisplayName(PlaceholderUtils.setPlaceholders(player, button.getDisplayName()));
-                itemMeta.setLore(PlaceholderAPI.setPlaceholders(player, button.getLore()));
-                serverStack.setItemMeta(itemMeta);
-            } else {
-                logger.warn("Java Button: " + menuName + "." + slot + " with Material: " + button.getMaterial() + " returned null ItemMeta, failed to set display name or lore.");
-            }
-
-            selectorGUI.setItem(slot, serverStack);
-        }
-
-        // Set a persistent data key in the first ItemStack with ItemMeta, to check in the future if the inventory is a menu
-        for (ItemStack stack : selectorGUI) {
-            ItemMeta meta = stack.getItemMeta();
-            if (meta != null) {
-                meta.getPersistentDataContainer().set(new NamespacedKey(GeyserHubMain.getInstance(), PERSISTENT_KEY), PersistentDataType.BYTE, PERSISTENT_VALUE);
-                break;
-            }
-        }
-
-        player.openInventory(selectorGUI);
-    }
-}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index 076ef20..beb39ca 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -1,16 +1,14 @@
 package dev.projectg.geyserhub.module.menu.java;
 
 import dev.projectg.geyserhub.GeyserHubMain;
-import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.SelectorLogger;
-import dev.projectg.geyserhub.config.ConfigManager;
+import dev.projectg.geyserhub.module.menu.MenuUtils;
+import dev.projectg.geyserhub.utils.PlaceholderUtils;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
 import org.bukkit.Material;
 import org.bukkit.NamespacedKey;
 import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
 import org.bukkit.inventory.Inventory;
 import org.bukkit.inventory.ItemStack;
@@ -18,81 +16,209 @@
 import org.bukkit.persistence.PersistentDataType;
 
 import javax.annotation.Nonnull;
-import java.util.List;
-import java.util.Objects;
+import javax.annotation.Nullable;
+import java.util.*;
 
 public class JavaMenu {
 
-    // todo: use this
-    public static boolean isEnabled() {
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
-        return config.getBoolean("Java-Selector.Enabled", true);
-    }
+    private static final NamespacedKey BUTTON_KEY = new NamespacedKey(GeyserHubMain.getInstance(), "geyserHubButton");
+
+    private final SelectorLogger logger;
+
+    public final boolean isEnabled;
+    @Nonnull private final String menuName;
 
-    // todo: maybe just remove the FileConfiguration parameter
-    public static void openMenu(@Nonnull Player player, @Nonnull ConfigId configId) {
-        Objects.requireNonNull(configId);
-        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(configId);
+    private String title;
+    private int size;
+    private Map<Integer, ItemButton> buttons;
 
-        SelectorLogger logger = SelectorLogger.getLogger();
+    protected JavaMenu(@Nonnull ConfigurationSection configSection) {
+        logger = SelectorLogger.getLogger();
+        Objects.requireNonNull(configSection);
+        menuName = configSection.getName();
+
+        // Get the inventory title and size
+        if (configSection.contains("Title") && configSection.contains("Size") && configSection.isInt("Size")) {
+            title = Objects.requireNonNull(configSection.getString("Title"));
+            size = configSection.getInt("Size");
+            logger.debug("Java Menu: " + menuName + " has Title: " + title + " Size: " + size);
+        } else {
+            logger.warn("Java Menu: " + menuName + " does not contain a Title or Size value, unable to create menu");
+            isEnabled = false;
+            return;
+        }
 
-        // Get the java selector section
-        if (!config.contains("Java-Selector") || !config.isConfigurationSection("Java-Selector") || !config.contains("Java-Selector.Servers") || !config.isConfigurationSection("Java-Selector.Servers")) {
-            logger.warn("Failed to create a java selector because the configuration is malformed!");
+        // Get the Buttons
+        if (configSection.contains("Buttons", true) && configSection.isConfigurationSection("Buttons")) {
+            ConfigurationSection buttonSection = configSection.getConfigurationSection("Buttons");
+            Objects.requireNonNull(buttonSection);
+            Map<Integer, ItemButton> buttons = getAllButtons(buttonSection);
+            if (buttons.isEmpty()) {
+                logger.warn("Failed to create any valid buttons of Bedrock form: " + menuName + "! All listed buttons have a malformed section!");
+                isEnabled = false;
+                return;
+            } else {
+                logger.debug("Finished adding buttons to Java menu: " + menuName);
+            }
+            this.buttons = buttons;
+        } else {
+            logger.warn("Java Menu: " + menuName + " does not contain a Buttons section, unable to create form.");
+            isEnabled = false;
             return;
         }
-        ConfigurationSection javaSection = config.getConfigurationSection("Java-Selector");
-        Objects.requireNonNull(javaSection);
-        ConfigurationSection serverSection = javaSection.getConfigurationSection("Servers");
-        Objects.requireNonNull(serverSection);
 
-        Inventory selectorGUI = Bukkit.createInventory(player, javaSection.getInt("Size"), ChatColor.DARK_AQUA + javaSection.getString("Title"));
+        // Make sure the inventory will be able to hold all the buttons
+        int highestGivenSlot = 0;
+        for (Integer slot: buttons.keySet()) {
+            highestGivenSlot = Math.max(slot, highestGivenSlot);
+        }
+        int minimumSize = highestGivenSlot + 1;
+        if (minimumSize > size) {
+            logger.warn("Java Menu: " + menuName + " has a button with slot " + highestGivenSlot + ", but the inventory size is only " + size + ". Increasing the size.");
+            size = minimumSize;
+        }
+
+        // Make sure that the inventory size is a multiple of 9
+        if (size % 9 != 0) {
+            // Divide the size by 9, round the ratio up to the next int value, then multiply by 9 to get the closest higher number that is a multiple of 9
+            size = (int) (9*(Math.ceil(Math.abs(size/9))));
+        }
+
+        isEnabled = true;
+    }
+
+    @Nonnull
+    private Map<Integer, ItemButton> getAllButtons(@Nonnull ConfigurationSection configSection) {
+        logger.debug("Getting buttons for java form: " + menuName);
 
-        // Loop through every server entry
-        for (String serverName : serverSection.getKeys(false)) {
-            String failMessage = "Server entry with name \"" + serverName + "\" was not added to the java selector";
+        // Get all the defined buttons in the buttons section
+        Set<String> allButtonIds = configSection.getKeys(false);
+        if (allButtonIds.isEmpty()) {
+            logger.warn("No buttons were listed for form: " + menuName);
+            return Collections.emptyMap();
+        }
+
+        // Create a list of buttons. For every defined button with a valid server or command configuration, we add its button.
+        Map<Integer, ItemButton> compiledButtons = new HashMap<>();
+        for (String buttonId : allButtonIds) {
 
-            if (!serverSection.isConfigurationSection(serverName)) {
-                logger.warn(failMessage + " because it is not a configuration section!");
+            // Make sure its a configuration section (we know it exists)
+            ConfigurationSection buttonInfo = configSection.getConfigurationSection(buttonId);
+            if (buttonInfo == null) {
+                logger.warn("Java Button: " + menuName + "." + buttonId + " was not added because it is not a configuration section!");
                 continue;
             }
-            ConfigurationSection serverInfo = serverSection.getConfigurationSection(serverName);
-            Objects.requireNonNull(serverInfo);
-
-            if (serverInfo.contains("Display-Name", true) && serverInfo.isString("Display-Name") && serverInfo.contains("Material", true) && serverInfo.isString("Material") && serverInfo.contains("Slot", true) && serverInfo.isInt("Slot")) {
-
-                // Get all the required info
-                String displayName = serverInfo.getString("Display-Name");
-                Objects.requireNonNull(displayName);
-                String materialName = serverInfo.getString("Material");
-                Material material = Material.matchMaterial(Objects.requireNonNull(serverInfo.getString("Material")));
-                if (material == null || material.isAir()) {
-                    logger.warn(failMessage + " because \"" + materialName + "\" is not a valid material!");
-                    continue;
-                }
-                int slot = serverInfo.getInt("Slot");
-
-                // Construct the item
-                ItemStack serverStack = new ItemStack(material);
-                ItemMeta itemMeta = serverStack.getItemMeta();
-                // It will only be null if the itemstack is air, which we already filter against.
-                Objects.requireNonNull(itemMeta);
-                itemMeta.setDisplayName(displayName);
-                itemMeta.getPersistentDataContainer().set(new NamespacedKey(GeyserHubMain.getInstance(), "bungeeName"), PersistentDataType.STRING, serverName);
-                if (serverInfo.contains("Lore", false) && serverInfo.isList("Lore")) {
-                    List<String> lore = serverInfo.getStringList("Lore");
-                    List<String> withPlaceholders = PlaceholderAPI.setPlaceholders(player, lore);
-                    itemMeta.setLore(withPlaceholders);
+            // Make sure the key is a integer (the slot value of the button item)
+            int slot;
+            try {
+                slot = Integer.parseUnsignedInt(buttonId);
+            } catch (NumberFormatException e) {
+                logger.warn("Java Button: " + menuName + "." + buttonId + " was not added because its config name is not a positive integer!");
+                continue;
+            }
+
+            ItemButton button = getButton(buttonInfo);
+            if (button != null) {
+                compiledButtons.put(slot, button);
+                logger.debug("Java Button: " + menuName + "." + buttonId + " was successfully added.");
+            }
+        }
+        return compiledButtons;
+    }
+
+    @Nullable
+    private ItemButton getButton(@Nonnull ConfigurationSection buttonInfo) {
+        String buttonId = buttonInfo.getName();
+
+        String displayName;
+        if (buttonInfo.contains("Display-Name", true) && buttonInfo.isString("Display-Name")) {
+            displayName = buttonInfo.getString("Display-Name");
+            Objects.requireNonNull(displayName);
+            logger.debug("Java Button: " + menuName + "." + buttonId + " has Display-Name: " + displayName);
+        } else {
+            logger.warn("Java Button: " + menuName + "." + buttonId + " does not contain a valid Button-Text value, not adding.");
+            return null;
+        }
+
+        Material material;
+        if (buttonInfo.contains("Material") && buttonInfo.isString("Material")) {
+            String materialName = buttonInfo.getString("Material");
+            Objects.requireNonNull(materialName);
+            material = Material.getMaterial(materialName, false);
+            if (material == null) {
+                material = Material.getMaterial(materialName, true);
+                if (material == null) {
+                    logger.warn("Java Button: " + menuName + "." + buttonId + " was not added because the Material it provided was not valid.");
+                    return null;
                 } else {
-                    logger.debug("Server entry with name \"" + serverName + "\" does not have a valid lore list");
+                    logger.warn("Java Button: " + menuName + "." + buttonId + "specified a legacy Material, please update it: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html");
                 }
-                serverStack.setItemMeta(itemMeta);
+            }
+        } else {
+            logger.warn("Java Button: " + menuName + "." + buttonId + " was not added because it is does not contain a Material value!");
+            return null;
+        }
+
+        // Create the button
+        ItemButton button = new ItemButton(displayName, material);
+
+        // Set server(s) and commands
+        ConfigurationSection rightClick = null;
+        if (buttonInfo.contains("Right-Click") && buttonInfo.isConfigurationSection("Right-Click")) {
+            rightClick = buttonInfo.getConfigurationSection("Right-Click");
+            Objects.requireNonNull(rightClick);
+            button.getRightClickButton().setCommands(MenuUtils.getCommands(rightClick));
+            button.getRightClickButton().setServer(MenuUtils.getServer(rightClick));
+        }
+        ConfigurationSection leftClick = null;
+        if (buttonInfo.contains("Left-Click") && buttonInfo.isConfigurationSection("Left-Click")) {
+            leftClick = buttonInfo.getConfigurationSection("Left-Click");
+            Objects.requireNonNull(leftClick);
+            button.getLeftClickButton().setCommands(MenuUtils.getCommands(leftClick));
+            button.getLeftClickButton().setServer(MenuUtils.getServer(leftClick));
+        }
+
+        if (buttonInfo.contains("Any-Click") && buttonInfo.isConfigurationSection("Any-Click")) {
+            if (rightClick != null || leftClick != null) {
+                logger.warn("Java Button: " + menuName + "." + buttonId + " Cannot define both Any-Click behaviour and also Right/Left-Click behaviour! Ignoring Any-Click section.");
+            } else {
+                ConfigurationSection anyClick = buttonInfo.getConfigurationSection("Any-Click");
+                Objects.requireNonNull(anyClick);
+                button.getRightClickButton().setCommands(MenuUtils.getCommands(anyClick));
+                button.getRightClickButton().setServer(MenuUtils.getServer(anyClick));
+                button.getLeftClickButton().setCommands(MenuUtils.getCommands(anyClick));
+                button.getLeftClickButton().setServer(MenuUtils.getServer(anyClick));
+            }
+        }
+
+        return button;
+    }
+
+    protected void sendMenu(@Nonnull Player player) {
+        if (!isEnabled) {
+            throw new AssertionError("Tried to send Java Menu: " + menuName + " to a player but the form was not enabled");
+        }
+
+        Inventory selectorGUI = Bukkit.createInventory(player, size, PlaceholderUtils.setPlaceholders(player, title));
+
+        for (Integer slot : buttons.keySet()) {
+            ItemButton button = buttons.get(slot);
 
-                selectorGUI.setItem(slot, serverStack);
+            // Construct the item
+            ItemStack serverStack = new ItemStack(button.getMaterial());
+            ItemMeta itemMeta = serverStack.getItemMeta();
+            if (itemMeta != null) {
+                itemMeta.setDisplayName(PlaceholderUtils.setPlaceholders(player, button.getDisplayName()));
+                itemMeta.setLore(PlaceholderAPI.setPlaceholders(player, button.getLore()));
+                itemMeta.getPersistentDataContainer().set(BUTTON_KEY, PersistentDataType.STRING, menuName);
+                serverStack.setItemMeta(itemMeta);
             } else {
-                logger.warn(failMessage + " because it does not contain a valid Display-Name, Material, or Slot value!");
+                logger.warn("Java Button: " + menuName + "." + slot + " with Material: " + button.getMaterial() + " returned null ItemMeta, failed to set display name or lore. The button will not have any results.");
             }
+
+            selectorGUI.setItem(slot, serverStack);
         }
+
         player.openInventory(selectorGUI);
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
index 6145e37..dab909d 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
@@ -18,7 +18,6 @@
 
 public class JavaMenuListeners implements Listener {
 
-    @SuppressWarnings("unused")
     @EventHandler
     public void onInventoryClick(InventoryClickEvent event) {
         Player player = (Player) event.getWhoClicked();
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java
index f0b6a48..9741fa9 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java
@@ -17,10 +17,10 @@ public class JavaMenuRegistry implements Reloadable {
     public static final String DEFAULT = "default";
 
     /**
-     * If bedrock forms are enabled. may be false if disabled in the config or if all forms failed to load.
+     * If java menus are enabled. may be false if disabled in the config or if all forms failed to load.
      */
     private boolean isEnabled;
-    private final Map<String, JavaForm> enabledMenus = new HashMap<>();
+    private final Map<String, JavaMenu> enabledMenus = new HashMap<>();
 
     public JavaMenuRegistry() {
         ReloadableRegistry.registerReloadable(this);
@@ -52,7 +52,7 @@ private boolean load() {
                             }
                             ConfigurationSection formInfo = menus.getConfigurationSection(entry);
                             Objects.requireNonNull(formInfo);
-                            JavaForm menu = new JavaForm(formInfo);
+                            JavaMenu menu = new JavaMenu(formInfo);
                             if (menu.isEnabled) {
                                 enabledMenus.put(entry, menu);
                                 noSuccess = false;

From 335d8e551df0af217481ce5d7880673e3d98294d Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Tue, 29 Jun 2021 17:05:31 -0400
Subject: [PATCH 59/74] fix some stuff up, start work for java menu click
 handler

---
 pom.xml                                       |  2 +-
 .../dev/projectg/geyserhub/GeyserHubMain.java |  6 ++--
 .../projectg/geyserhub/config/ConfigId.java   |  2 ++
 .../geyserhub/module/menu/MenuUtils.java      | 15 +++++++++
 .../module/menu/bedrock/BedrockForm.java      |  4 ++-
 .../module/menu/java/ClickHandler.java        | 31 +++++++++++++++++++
 .../geyserhub/module/menu/java/JavaMenu.java  |  5 +++
 .../geyserhub/module/world/WorldSettings.java |  4 ++-
 src/main/resources/selector.yml               |  4 +--
 9 files changed, 65 insertions(+), 8 deletions(-)
 create mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/java/ClickHandler.java

diff --git a/pom.xml b/pom.xml
index 36c7c80..0ed60e7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 
     <groupId>dev.projectg</groupId>
     <artifactId>GeyserHub</artifactId>
-    <version>1.2.0-SNAPSHOT</version>
+    <version>1.2.0</version>
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 865d7da..3d58219 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -38,9 +38,7 @@ public void onEnable() {
             logger.info("Branch: " + gitProperties.getProperty("git.branch", "Unknown") + ", Commit: " + gitProperties.getProperty("git.commit.id.abbrev", "Unknown"));
         } catch (IOException e) {
             logger.warn("Unable to load resource: git.properties");
-            if (logger.isDebug()) {
-                e.printStackTrace();
-            }
+            e.printStackTrace();
         }
 
         configManager = new ConfigManager();
@@ -59,6 +57,8 @@ public void onEnable() {
         // todo: and add command suggestions/completions, help pages that only shows available commands
         Objects.requireNonNull(getCommand("ghub")).setExecutor(new GeyserHubCommand(bedrockFormRegistry, javaMenuRegistry));
 
+        // todo: sort all of this, and make checking for enable value in config consistent
+
         // Listeners for the Bedrock and Java menus
         Bukkit.getServer().getPluginManager().registerEvents(new CommonMenuListeners(bedrockFormRegistry, javaMenuRegistry), this);
         Bukkit.getServer().getPluginManager().registerEvents(new JavaMenuListeners(), this);
diff --git a/src/main/java/dev/projectg/geyserhub/config/ConfigId.java b/src/main/java/dev/projectg/geyserhub/config/ConfigId.java
index 9d997c4..aece8a6 100644
--- a/src/main/java/dev/projectg/geyserhub/config/ConfigId.java
+++ b/src/main/java/dev/projectg/geyserhub/config/ConfigId.java
@@ -12,6 +12,8 @@ public enum ConfigId {
     public final String fileName;
     public final int version;
 
+    // todo: maybe load configs on enum init
+
     /**
      * @param fileName the filename, including the extension, of the configuration
      * @param version the version of the configuration
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java b/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
index 60bae0e..2361fa6 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
@@ -33,6 +33,11 @@ public static void runCommand(@Nonnull String prefixedCommand, @Nonnull Player p
         Bukkit.getServer().dispatchCommand(sender, executableCommand);
     }
 
+    /**
+     * Gets the commands from a config section with a "Commands" string list.
+     * @param buttonData the config section with the string list
+     * @return the commands. will return an empty list in case of failure, or if the list was empty.
+     */
     @Nonnull
     public static List<String> getCommands(@Nonnull ConfigurationSection buttonData) {
         SelectorLogger logger = SelectorLogger.getLogger();
@@ -49,6 +54,11 @@ public static List<String> getCommands(@Nonnull ConfigurationSection buttonData)
         return Collections.emptyList();
     }
 
+    /**
+     * Get the server name from a button configuration section
+     * @param buttonData the config section
+     * @return the server name, null if there was no server
+     */
     @Nullable
     public static String getServer(@Nonnull ConfigurationSection buttonData) {
         SelectorLogger logger = SelectorLogger.getLogger();
@@ -61,6 +71,11 @@ public static String getServer(@Nonnull ConfigurationSection buttonData) {
         return null;
     }
 
+    /**
+     * Get the name of the parent config section of the given config section
+     * @param configSection the config section to get the parent name of
+     * @return the parent name, "null" if there was no parent
+     */
     @Nonnull
     public static String getParentName(@Nonnull ConfigurationSection configSection) {
         ConfigurationSection parent = configSection.getParent();
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index 76ed2d2..4b6f803 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -35,8 +35,10 @@ public class BedrockForm {
     private String content;
     private List<BedrockButton> allButtons;
 
+    // todo: constructor that doesnt use config section
+
     /**
-     * Create a new bedrock selector form and initializes it with the current loaded config
+     * Create a new bedrock selector form and initializes it with the given form config section
      */
     protected BedrockForm(@Nonnull ConfigurationSection configSection) {
         logger = SelectorLogger.getLogger();
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/ClickHandler.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/ClickHandler.java
new file mode 100644
index 0000000..bb23231
--- /dev/null
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/ClickHandler.java
@@ -0,0 +1,31 @@
+package dev.projectg.geyserhub.module.menu.java;
+
+import org.bukkit.entity.Player;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ClickHandler {
+
+    private static final Map<Player, ClickHandler> TRACKED_HANDLERS = new HashMap<>();
+
+    private final Map<Integer, List<String>> commands;
+    private final Map<Integer, String> servers;
+
+
+    public ClickHandler(Player player, Map<Integer, List<String>> commands, Map<Integer, String> servers) {
+        this.commands = commands;
+        this.servers = servers;
+
+        TRACKED_HANDLERS.put(player, this);
+    }
+
+
+    public static void process(Player player, int buttonId) {
+
+        // todo
+
+        TRACKED_HANDLERS.remove(player);
+    }
+}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index beb39ca..ae1153d 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -32,6 +32,11 @@ public class JavaMenu {
     private int size;
     private Map<Integer, ItemButton> buttons;
 
+    // todo: constructor that doesnt use config section
+
+    /**
+     * Create a new java selector menu and initializes it with the given menu config section
+     */
     protected JavaMenu(@Nonnull ConfigurationSection configSection) {
         logger = SelectorLogger.getLogger();
         Objects.requireNonNull(configSection);
diff --git a/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java b/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
index 374be7c..41cd72c 100644
--- a/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
+++ b/src/main/java/dev/projectg/geyserhub/module/world/WorldSettings.java
@@ -21,7 +21,9 @@ public class WorldSettings implements Listener {
     @EventHandler
     public void onEntityDamage(EntityDamageEvent event) {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.MAIN);
-        if (!(event.getEntity() instanceof Player)) return;
+        if (!(event.getEntity() instanceof Player)) {
+            return;
+        }
 
         if (config.getBoolean("World-settings.disable-fall-damage")
                 && event.getCause() == EntityDamageEvent.DamageCause.FALL)
diff --git a/src/main/resources/selector.yml b/src/main/resources/selector.yml
index b1cfe47..c16a5a5 100644
--- a/src/main/resources/selector.yml
+++ b/src/main/resources/selector.yml
@@ -47,7 +47,7 @@ Java-Selector:
             - "Hide & Seek"
           Any-Click:
             Commands:
-              - "execute as %player_name% run ghub form minigames"
+              - "player; ghub form minigames"
         6:
           Display-Name: "Survival"
           Material: Emerald
@@ -107,7 +107,7 @@ Bedrock-Selector:
           ImageURL: "https://www.digminecraft.com/block_recipes/images/blue_concrete.png"
           Server: "survival"
 
-        # You can also execute commands with buttons as seen below!
+        # You can also execute commands with buttons as seen below! Use "player;" or "console;" to choose who to run the command as.
         gamesCommand:
           Button-Text: "Minigames"
           Image-URL: "https://www.digminecraft.com/weapon_recipes/images/diamond_sword.png"

From 88a996ecffcc3ced128e7d8cfe206b7644e7bd49 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 30 Jun 2021 21:10:28 -0400
Subject: [PATCH 60/74] Slight refactor of send form/menu code

- removes click handler from last commit
---
 .../geyserhub/command/GeyserHubCommand.java   | 14 ++++----
 .../module/menu/CommonMenuListeners.java      | 25 ++++++++++++--
 .../module/menu/bedrock/BedrockForm.java      |  2 +-
 .../menu/bedrock/BedrockFormRegistry.java     | 33 ++++++++++++++-----
 .../module/menu/java/ClickHandler.java        | 31 -----------------
 .../geyserhub/module/menu/java/JavaMenu.java  |  7 +++-
 .../module/menu/java/JavaMenuRegistry.java    | 29 ++++++++++++----
 7 files changed, 84 insertions(+), 57 deletions(-)
 delete mode 100644 src/main/java/dev/projectg/geyserhub/module/menu/java/ClickHandler.java

diff --git a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
index 79821e8..3bfd1ff 100644
--- a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
+++ b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
@@ -1,6 +1,8 @@
 package dev.projectg.geyserhub.command;
 
 import dev.projectg.geyserhub.SelectorLogger;
+import dev.projectg.geyserhub.module.menu.bedrock.BedrockForm;
+import dev.projectg.geyserhub.module.menu.java.JavaMenu;
 import dev.projectg.geyserhub.module.menu.java.JavaMenuRegistry;
 import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
 import dev.projectg.geyserhub.module.menu.bedrock.BedrockFormRegistry;
@@ -16,7 +18,6 @@
 
 import javax.annotation.Nonnull;
 import java.util.Objects;
-import java.util.UUID;
 
 public class GeyserHubCommand implements CommandExecutor {
 
@@ -114,11 +115,11 @@ private void sendForm(@Nonnull CommandSender commandSender, @Nonnull String form
 
         if (commandSender instanceof Player) {
             Player player = (Player) commandSender;
-            UUID uuid = player.getUniqueId();
-            if (FloodgateApi.getInstance().isFloodgatePlayer(uuid)) {
+            if (FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {
                 if (bedrockRegistry.isEnabled()) {
                     if (bedrockRegistry.getFormNames().contains(formName)) {
-                        bedrockRegistry.sendForm(FloodgateApi.getInstance().getPlayer(uuid), formName);
+                        BedrockForm form = Objects.requireNonNull(bedrockRegistry.getMenu(formName));
+                        form.sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()));
                     } else {
                         sendMessage(player, SelectorLogger.Level.SEVERE, "Sorry, that form doesn't exist! Specify a form with \"/ghub form <form>\"");
                     }
@@ -127,8 +128,9 @@ private void sendForm(@Nonnull CommandSender commandSender, @Nonnull String form
                 }
             } else {
                 if (javaMenuRegistry.isEnabled()) {
-                    if (javaMenuRegistry.getFormNames().contains(formName)) {
-                        javaMenuRegistry.sendForm(player, formName);
+                    if (javaMenuRegistry.getMenuNames().contains(formName)) {
+                        JavaMenu menu = Objects.requireNonNull(javaMenuRegistry.getMenu(formName));
+                        menu.sendMenu(player);
                     } else {
                         sendMessage(player, SelectorLogger.Level.SEVERE, "Sorry, that form doesn't exist! Specify a form with \"/ghub form <form>\"");
                     }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
index ec02c73..0541b55 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
@@ -2,8 +2,11 @@
 
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.config.ConfigId;
+import dev.projectg.geyserhub.module.menu.bedrock.BedrockForm;
 import dev.projectg.geyserhub.module.menu.bedrock.BedrockFormRegistry;
+import dev.projectg.geyserhub.module.menu.java.JavaMenu;
 import dev.projectg.geyserhub.module.menu.java.JavaMenuRegistry;
+import org.bukkit.ChatColor;
 import org.bukkit.Material;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
@@ -35,9 +38,27 @@ public void onInteract(PlayerInteractEvent event) { // open the menu through the
         if (player.getInventory().getItemInMainHand().isSimilar(AccessItem.getItem())) {
             if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
                 if (FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {
-                    bedrockFormRegistry.sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()), BedrockFormRegistry.DEFAULT);
+                    if (bedrockFormRegistry.isEnabled()) {
+                        if (bedrockFormRegistry.getFormNames().contains(BedrockFormRegistry.DEFAULT)) {
+                            BedrockForm form = Objects.requireNonNull(bedrockFormRegistry.getMenu(BedrockFormRegistry.DEFAULT));
+                            form.sendForm(FloodgateApi.getInstance().getPlayer(player.getUniqueId()));
+                        } else {
+                            player.sendMessage("[GeyserHub]" + ChatColor.RED + " Sorry, this item can't be used because a default form hasn't been specified!");
+                        }
+                    } else {
+                        player.sendMessage("[GeyserHub]" + ChatColor.RED + " Sorry, Bedrock forms are not enabled!");
+                    }
                 } else {
-                    javaMenuRegistry.sendForm(player, JavaMenuRegistry.DEFAULT);
+                    if (javaMenuRegistry.isEnabled()) {
+                        if (javaMenuRegistry.getMenuNames().contains(JavaMenuRegistry.DEFAULT)) {
+                            JavaMenu menu = Objects.requireNonNull(javaMenuRegistry.getMenu(JavaMenuRegistry.DEFAULT));
+                            menu.sendMenu(player);
+                        } else {
+                            player.sendMessage("[GeyserHub]" + ChatColor.RED + " Sorry, this item can't be used because a default menu hasn't been specified!");
+                        }
+                    } else {
+                        player.sendMessage("[GeyserHub]" + ChatColor.RED + " Sorry, Java menus are not enabled!");
+                    }
                 }
             }
         }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index 4b6f803..9a3deb0 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -153,7 +153,7 @@ private List<BedrockButton> getButtons(@Nonnull ConfigurationSection configSecti
      * Send the server selector
      * @param floodgatePlayer the floodgate player to send it to
      */
-    protected void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
+    public void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
         if (!isEnabled) {
             throw new AssertionError("Form: " + title + " that failed to load was called to be sent to a player!");
         }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
index b067177..66ae5f5 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockFormRegistry.java
@@ -7,14 +7,14 @@
 import dev.projectg.geyserhub.SelectorLogger;
 import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.configuration.file.FileConfiguration;
-import org.geysermc.floodgate.api.player.FloodgatePlayer;
 
 import javax.annotation.Nonnull;
-import java.util.ArrayList;
+import javax.annotation.Nullable;
 import java.util.HashMap;
-import java.util.List;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
 public class BedrockFormRegistry implements Reloadable {
 
@@ -90,15 +90,30 @@ private boolean load() {
         return false;
     }
 
-    public void sendForm(@Nonnull FloodgatePlayer player, @Nonnull String form) {
-        enabledForms.get(form).sendForm(player);
-    }
-
+    /**
+     * @return True, if Java menus are enabled.
+     */
     public boolean isEnabled() {
         return isEnabled;
     }
-    public List<String> getFormNames() {
-        return new ArrayList<>(enabledForms.keySet());
+
+    /**
+     * @return A copy of the keyset of the current enabled menus
+     */
+    @Nonnull
+    public Set<String> getFormNames() {
+        return new HashSet<>(enabledForms.keySet());
+    }
+
+    /**
+     * Get a Java menu, based off its name.
+     * @param menuName The menu name
+     * @return the JavaMenu, null if it doesn't exist.
+     */
+    @Nullable
+    public BedrockForm getMenu(@Nonnull String menuName) {
+        Objects.requireNonNull(menuName);
+        return enabledForms.get(menuName);
     }
 
     @Override
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/ClickHandler.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/ClickHandler.java
deleted file mode 100644
index bb23231..0000000
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/ClickHandler.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package dev.projectg.geyserhub.module.menu.java;
-
-import org.bukkit.entity.Player;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class ClickHandler {
-
-    private static final Map<Player, ClickHandler> TRACKED_HANDLERS = new HashMap<>();
-
-    private final Map<Integer, List<String>> commands;
-    private final Map<Integer, String> servers;
-
-
-    public ClickHandler(Player player, Map<Integer, List<String>> commands, Map<Integer, String> servers) {
-        this.commands = commands;
-        this.servers = servers;
-
-        TRACKED_HANDLERS.put(player, this);
-    }
-
-
-    public static void process(Player player, int buttonId) {
-
-        // todo
-
-        TRACKED_HANDLERS.remove(player);
-    }
-}
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index ae1153d..52403dd 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -199,7 +199,12 @@ private ItemButton getButton(@Nonnull ConfigurationSection buttonInfo) {
         return button;
     }
 
-    protected void sendMenu(@Nonnull Player player) {
+    @Nonnull
+    public Map<Integer, ItemButton> getContents() {
+        return this.buttons;
+    }
+
+    public void sendMenu(@Nonnull Player player) {
         if (!isEnabled) {
             throw new AssertionError("Tried to send Java Menu: " + menuName + " to a player but the form was not enabled");
         }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java
index 9741fa9..d48254b 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuRegistry.java
@@ -7,9 +7,9 @@
 import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
 import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.configuration.file.FileConfiguration;
-import org.bukkit.entity.Player;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import java.util.*;
 
 public class JavaMenuRegistry implements Reloadable {
@@ -86,15 +86,30 @@ private boolean load() {
         return false;
     }
 
-    public void sendForm(@Nonnull Player player, @Nonnull String form) {
-        enabledMenus.get(form).sendMenu(player);
-    }
-
+    /**
+     * @return True, if Java menus are enabled.
+     */
     public boolean isEnabled() {
         return isEnabled;
     }
-    public List<String> getFormNames() {
-        return new ArrayList<>(enabledMenus.keySet());
+
+    /**
+     * @return A copy of the keyset of the current enabled menus
+     */
+    @Nonnull
+    public Set<String> getMenuNames() {
+        return new HashSet<>(enabledMenus.keySet());
+    }
+
+    /**
+     * Get a Java menu, based off its name.
+     * @param menuName The menu name
+     * @return the JavaMenu, null if it doesn't exist.
+     */
+    @Nullable
+    public JavaMenu getMenu(@Nonnull String menuName) {
+        Objects.requireNonNull(menuName);
+        return enabledMenus.get(menuName);
     }
 
     @Override

From cca06071a3357d2d741cf28225d9e410c4642277 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 30 Jun 2021 22:22:23 -0400
Subject: [PATCH 61/74] hopefully finish java menu buttons, other improvements

---
 .../dev/projectg/geyserhub/GeyserHubMain.java |  2 +-
 .../geyserhub/module/menu/MenuUtils.java      | 45 ++++++++++++
 .../module/menu/bedrock/BedrockForm.java      | 51 ++++++-------
 .../module/menu/java/ItemButton.java          | 25 +++----
 .../geyserhub/module/menu/java/JavaMenu.java  | 72 ++++++++++++++++---
 .../module/menu/java/JavaMenuListeners.java   | 50 +++++++------
 6 files changed, 165 insertions(+), 80 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index 3d58219..d981706 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -61,7 +61,7 @@ public void onEnable() {
 
         // Listeners for the Bedrock and Java menus
         Bukkit.getServer().getPluginManager().registerEvents(new CommonMenuListeners(bedrockFormRegistry, javaMenuRegistry), this);
-        Bukkit.getServer().getPluginManager().registerEvents(new JavaMenuListeners(), this);
+        Bukkit.getServer().getPluginManager().registerEvents(new JavaMenuListeners(javaMenuRegistry), this);
 
         // Listener the Join Teleporter module
         Bukkit.getServer().getPluginManager().registerEvents(new JoinTeleporter(), this);
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java b/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
index 2361fa6..35b9d35 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
@@ -1,13 +1,19 @@
 package dev.projectg.geyserhub.module.menu;
 
+import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
+import dev.projectg.geyserhub.utils.PlaceholderUtils;
 import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
 import org.bukkit.command.CommandSender;
 import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.entity.Player;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -17,12 +23,47 @@ public class MenuUtils {
     public static final String playerPrefix = "player;";
     public static final String consolePrefix = "console;";
 
+    /**
+     * @param commands Commands list, an empty list can be passed for no commands.
+     * @param serverName The server name, can passed as null for no server.
+     * @param player the Player to run everything on.
+     */
+    public static void affectPlayer(@Nonnull List<String> commands, @Nullable String serverName, @Nonnull Player player) {
+        Objects.requireNonNull(commands);
+        Objects.requireNonNull(player);
+
+        if (!commands.isEmpty()) {
+            // Get the commands from the list of commands and replace any playerName placeholders
+            for (String command : commands) {
+                MenuUtils.runCommand(PlaceholderUtils.setPlaceholders(player, command), player);
+            }
+        }
+
+        if (serverName != null) {
+            // This should never be out of bounds considering its size is the number of valid buttons
+            try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(baos)) {
+                out.writeUTF("Connect");
+                out.writeUTF(serverName);
+                player.sendPluginMessage(GeyserHubMain.getInstance(), "BungeeCord", baos.toByteArray());
+                player.sendMessage(ChatColor.DARK_AQUA + "Trying to send you to: " + ChatColor.GREEN + serverName);
+            } catch (IOException e) {
+                SelectorLogger.getLogger().severe("Failed to send a plugin message to Bungeecord!");
+                e.printStackTrace();
+            }
+        }
+    }
+
+
+
     /**
      * Process a prefixed command and run it
      * @param prefixedCommand A command that is prefixed with "player;" to run the command as the player, or "console;", to run the command as the console.
      * @param player the Player to run the command as, if prefixed with "player;"
      */
     public static void runCommand(@Nonnull String prefixedCommand, @Nonnull Player player) {
+        Objects.requireNonNull(prefixedCommand);
+        Objects.requireNonNull(player);
+
         CommandSender sender = Bukkit.getServer().getConsoleSender();
         if (prefixedCommand.startsWith(playerPrefix)) {
             sender = player;
@@ -40,6 +81,7 @@ public static void runCommand(@Nonnull String prefixedCommand, @Nonnull Player p
      */
     @Nonnull
     public static List<String> getCommands(@Nonnull ConfigurationSection buttonData) {
+        Objects.requireNonNull(buttonData);
         SelectorLogger logger = SelectorLogger.getLogger();
 
         if (buttonData.contains("Commands") && buttonData.isList("Commands")) {
@@ -61,6 +103,7 @@ public static List<String> getCommands(@Nonnull ConfigurationSection buttonData)
      */
     @Nullable
     public static String getServer(@Nonnull ConfigurationSection buttonData) {
+        Objects.requireNonNull(buttonData);
         SelectorLogger logger = SelectorLogger.getLogger();
 
         if (buttonData.contains("Server") && buttonData.isString("Server")) {
@@ -78,6 +121,8 @@ public static String getServer(@Nonnull ConfigurationSection buttonData) {
      */
     @Nonnull
     public static String getParentName(@Nonnull ConfigurationSection configSection) {
+        Objects.requireNonNull(configSection);
+
         ConfigurationSection parent = configSection.getParent();
         if (parent == null) {
             return "null";
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index 9a3deb0..29f92b1 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -1,11 +1,9 @@
 package dev.projectg.geyserhub.module.menu.bedrock;
 
-import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
 import dev.projectg.geyserhub.module.menu.MenuUtils;
 import dev.projectg.geyserhub.utils.PlaceholderUtils;
 import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
 import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.entity.Player;
 import org.geysermc.cumulus.SimpleForm;
@@ -14,11 +12,9 @@
 import org.geysermc.floodgate.api.player.FloodgatePlayer;
 
 import javax.annotation.Nonnull;
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -28,11 +24,29 @@ public class BedrockForm {
 
     private final SelectorLogger logger;
 
+    /**
+     * If the form actually works and can be used.
+     */
     public final boolean isEnabled;
+
+    /**
+     * The name of the form, from the config.
+     */
     public final String formName;
 
+    /**
+     * The title of the Bedrock form (shown in the GUI)
+     */
     private String title;
+
+    /**
+     * The text shown under the {@link #title}
+     */
     private String content;
+
+    /**
+     * A list of all the buttons. Index is the button ID.
+     */
     private List<BedrockButton> allButtons;
 
     // todo: constructor that doesnt use config section
@@ -77,8 +91,7 @@ protected BedrockForm(@Nonnull ConfigurationSection configSection) {
     }
 
     /**
-     *  Get the server buttons and each button's server
-     * @param configSection The configuration section to pull the data from
+     *  Get all the buttons in the "Buttons" section
      * @return A list of Buttons, which may be empty.
      */
     private List<BedrockButton> getButtons(@Nonnull ConfigurationSection configSection) {
@@ -92,7 +105,7 @@ private List<BedrockButton> getButtons(@Nonnull ConfigurationSection configSecti
         }
 
         // Create a list of buttons. For every defined button with a valid server or command configuration, we add its button.
-        List<BedrockButton> compiledButtons = new ArrayList<>();
+        List<BedrockButton> compiledButtons = new LinkedList<>();
         for (String buttonId : allButtonIds) {
             ConfigurationSection buttonInfo = configSection.getConfigurationSection(buttonId);
             if (buttonInfo == null) {
@@ -188,26 +201,8 @@ public void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
 
             BedrockButton button = formattedButtons.get(response.getClickedButtonId());
 
-            if (!button.getCommands().isEmpty()) {
-                // Get the commands from the list of commands and replace any playerName placeholders
-                for (String command : button.getCommands()) {
-                    MenuUtils.runCommand(PlaceholderUtils.setPlaceholders(player, command), player);
-                }
-            }
-
-            if (button.getServer() != null) {
-                // This should never be out of bounds considering its size is the number of valid buttons
-                String serverName = button.getServer();
-                try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(baos)) {
-                    out.writeUTF("Connect");
-                    out.writeUTF(serverName);
-                    player.sendPluginMessage(GeyserHubMain.getInstance(), "BungeeCord", baos.toByteArray());
-                    player.sendMessage(ChatColor.DARK_AQUA + "Trying to send you to: " + ChatColor.GREEN + serverName);
-                } catch (IOException e) {
-                    logger.severe("Failed to send a plugin message to Bungeecord!");
-                    e.printStackTrace();
-                }
-            }
+            // Run the commands if given, move the player to another server if given.
+            MenuUtils.affectPlayer(button.getCommands(), button.getServer(), player);
         });
 
         // Send the form to the floodgate player
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/ItemButton.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/ItemButton.java
index 818d573..7836792 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/ItemButton.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/ItemButton.java
@@ -2,7 +2,6 @@
 
 import dev.projectg.geyserhub.module.menu.button.OutcomeButton;
 import org.bukkit.Material;
-import org.jetbrains.annotations.NotNull;
 
 import javax.annotation.Nonnull;
 import java.util.ArrayList;
@@ -41,8 +40,8 @@ public ItemButton(@Nonnull ItemButton button) {
         this.material = button.getMaterial();
         this.lore = button.getLore();
 
-        this.rightClickButton = button.getRightClickButton();
-        this.leftClickButton = button.getLeftClickButton();
+        this.rightClickButton = button.getOutcomeButton(true);
+        this.leftClickButton = button.getOutcomeButton(false);
     }
 
     public @Nonnull String getDisplayName() {
@@ -69,20 +68,16 @@ public void setLore(@Nonnull List<String> lore) {
     }
 
     /**
-     * Get the OutcomeButton for when the player right clicks on this ItemButton.
+     * Get the {@link OutcomeButton} for when the player clicks on this ItemButton.
      * Warning: the text of the OutcomeButton is ignored.
+     * @param rightClick True to get the right side OutcomeButton, or false to get the left side.
      * @return the OutcomeButton
      */
-    public @NotNull OutcomeButton getRightClickButton() {
-        return rightClickButton;
-    }
-
-    /**
-     * Get the OutcomeButton for when the player left clicks on this ItemButton.
-     * Warning: the text of the OutcomeButton is ignored.
-     * @return the OutcomeButton
-     */
-    public @NotNull OutcomeButton getLeftClickButton() {
-        return leftClickButton;
+    public @Nonnull OutcomeButton getOutcomeButton(boolean rightClick) {
+        if (rightClick) {
+            return rightClickButton;
+        } else {
+            return leftClickButton;
+        }
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index 52403dd..f6711a8 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -3,6 +3,7 @@
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
 import dev.projectg.geyserhub.module.menu.MenuUtils;
+import dev.projectg.geyserhub.module.menu.button.OutcomeButton;
 import dev.projectg.geyserhub.utils.PlaceholderUtils;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
@@ -21,15 +22,34 @@
 
 public class JavaMenu {
 
-    private static final NamespacedKey BUTTON_KEY = new NamespacedKey(GeyserHubMain.getInstance(), "geyserHubButton");
+    public static final NamespacedKey MENU_NAME_KEY = new NamespacedKey(GeyserHubMain.getInstance(), "geyserHubMenu");
+    public static final PersistentDataType<String, String>  MENU_NAME_TYPE = PersistentDataType.STRING;
 
     private final SelectorLogger logger;
 
+    /**
+     * If the menu actually works and can be used.
+     */
     public final boolean isEnabled;
+
+    /**
+     * The name of the menu, from the config.
+     */
     @Nonnull private final String menuName;
 
+    /**
+     * The title of the inventory (shown in the GUI)
+     */
     private String title;
+
+    /**
+     * The size of the inventory
+     */
     private int size;
+
+    /**
+     * Map of inventory slot to ItemButton
+     */
     private Map<Integer, ItemButton> buttons;
 
     // todo: constructor that doesnt use config section
@@ -92,6 +112,10 @@ protected JavaMenu(@Nonnull ConfigurationSection configSection) {
         isEnabled = true;
     }
 
+    /**
+     *  Get all the buttons in the "Buttons" section
+     * @return A list of Buttons, which may be empty.
+     */
     @Nonnull
     private Map<Integer, ItemButton> getAllButtons(@Nonnull ConfigurationSection configSection) {
         logger.debug("Getting buttons for java form: " + menuName);
@@ -131,6 +155,10 @@ private Map<Integer, ItemButton> getAllButtons(@Nonnull ConfigurationSection con
         return compiledButtons;
     }
 
+    /**
+     * Process the config section of a single button config section
+     * @return the ItemButton. may be null.
+     */
     @Nullable
     private ItemButton getButton(@Nonnull ConfigurationSection buttonInfo) {
         String buttonId = buttonInfo.getName();
@@ -172,15 +200,15 @@ private ItemButton getButton(@Nonnull ConfigurationSection buttonInfo) {
         if (buttonInfo.contains("Right-Click") && buttonInfo.isConfigurationSection("Right-Click")) {
             rightClick = buttonInfo.getConfigurationSection("Right-Click");
             Objects.requireNonNull(rightClick);
-            button.getRightClickButton().setCommands(MenuUtils.getCommands(rightClick));
-            button.getRightClickButton().setServer(MenuUtils.getServer(rightClick));
+            button.getOutcomeButton(true).setCommands(MenuUtils.getCommands(rightClick));
+            button.getOutcomeButton(true).setServer(MenuUtils.getServer(rightClick));
         }
         ConfigurationSection leftClick = null;
         if (buttonInfo.contains("Left-Click") && buttonInfo.isConfigurationSection("Left-Click")) {
             leftClick = buttonInfo.getConfigurationSection("Left-Click");
             Objects.requireNonNull(leftClick);
-            button.getLeftClickButton().setCommands(MenuUtils.getCommands(leftClick));
-            button.getLeftClickButton().setServer(MenuUtils.getServer(leftClick));
+            button.getOutcomeButton(false).setCommands(MenuUtils.getCommands(leftClick));
+            button.getOutcomeButton(false).setServer(MenuUtils.getServer(leftClick));
         }
 
         if (buttonInfo.contains("Any-Click") && buttonInfo.isConfigurationSection("Any-Click")) {
@@ -189,16 +217,19 @@ private ItemButton getButton(@Nonnull ConfigurationSection buttonInfo) {
             } else {
                 ConfigurationSection anyClick = buttonInfo.getConfigurationSection("Any-Click");
                 Objects.requireNonNull(anyClick);
-                button.getRightClickButton().setCommands(MenuUtils.getCommands(anyClick));
-                button.getRightClickButton().setServer(MenuUtils.getServer(anyClick));
-                button.getLeftClickButton().setCommands(MenuUtils.getCommands(anyClick));
-                button.getLeftClickButton().setServer(MenuUtils.getServer(anyClick));
+                button.getOutcomeButton(true).setCommands(MenuUtils.getCommands(anyClick));
+                button.getOutcomeButton(true).setServer(MenuUtils.getServer(anyClick));
+                button.getOutcomeButton(false).setCommands(MenuUtils.getCommands(anyClick));
+                button.getOutcomeButton(false).setServer(MenuUtils.getServer(anyClick));
             }
         }
 
         return button;
     }
 
+    /**
+     * @return A non-copy of the contents of this form.
+     */
     @Nonnull
     public Map<Integer, ItemButton> getContents() {
         return this.buttons;
@@ -220,7 +251,7 @@ public void sendMenu(@Nonnull Player player) {
             if (itemMeta != null) {
                 itemMeta.setDisplayName(PlaceholderUtils.setPlaceholders(player, button.getDisplayName()));
                 itemMeta.setLore(PlaceholderAPI.setPlaceholders(player, button.getLore()));
-                itemMeta.getPersistentDataContainer().set(BUTTON_KEY, PersistentDataType.STRING, menuName);
+                itemMeta.getPersistentDataContainer().set(MENU_NAME_KEY, PersistentDataType.STRING, menuName);
                 serverStack.setItemMeta(itemMeta);
             } else {
                 logger.warn("Java Button: " + menuName + "." + slot + " with Material: " + button.getMaterial() + " returned null ItemMeta, failed to set display name or lore. The button will not have any results.");
@@ -231,4 +262,25 @@ public void sendMenu(@Nonnull Player player) {
 
         player.openInventory(selectorGUI);
     }
+
+    /**
+     * @param slot The inventory slot
+     * @return If there is a button at the given inventory slot
+     */
+    public boolean isButton(int slot) {
+        return getContents().get(slot) != null;
+    }
+
+    /**
+     * Process a button click by a player in the menu.
+     * @param slot The slot in the inventory. Nothing will happen if the slot does not contain a button.
+     * @param rightClick True if it was a right click, false if a left click.
+     * @param player the Player who clicked on the button.
+     */
+    public void process(int slot, boolean rightClick, @Nonnull Player player) {
+        if (this.isButton(slot)) {
+            OutcomeButton button = this.getContents().get(slot).getOutcomeButton(rightClick);
+            MenuUtils.affectPlayer(button.getCommands(), button.getServer(), player);
+        }
+    }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
index dab909d..792238c 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
@@ -1,45 +1,43 @@
 package dev.projectg.geyserhub.module.menu.java;
 
-import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
-import org.bukkit.ChatColor;
-import org.bukkit.Material;
-import org.bukkit.NamespacedKey;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
 import org.bukkit.event.inventory.InventoryClickEvent;
-import org.bukkit.persistence.PersistentDataType;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
 
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
+import javax.annotation.Nonnull;
 import java.util.Objects;
 
 public class JavaMenuListeners implements Listener {
 
+    private final JavaMenuRegistry javaMenuRegistry;
+
+    public JavaMenuListeners(@Nonnull JavaMenuRegistry javaMenuRegistry) {
+        this.javaMenuRegistry = Objects.requireNonNull(javaMenuRegistry);
+    }
+
     @EventHandler
     public void onInventoryClick(InventoryClickEvent event) {
         Player player = (Player) event.getWhoClicked();
-        if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) {
-            return;
-        }
-        try {
-            String bungeeName = Objects.requireNonNull(Objects.requireNonNull(event.getCurrentItem()).getItemMeta()).getPersistentDataContainer().get(new NamespacedKey(GeyserHubMain.getInstance(), "bungeeName"), PersistentDataType.STRING);
-            ByteArrayOutputStream b = new ByteArrayOutputStream();
-            DataOutputStream out = new DataOutputStream(b);
-            try {
-                out.writeUTF("Connect");
-                assert bungeeName != null;
-                out.writeUTF(bungeeName);
-                player.sendPluginMessage(GeyserHubMain.getInstance(), "BungeeCord", b.toByteArray());
-                player.sendMessage(ChatColor.DARK_AQUA + "Trying to send you to: " + ChatColor.GREEN + bungeeName);
-            } catch (IOException er) {
-                SelectorLogger.getLogger().severe("Failed to send a plugin message to Bungeecord!");
-            }
-            event.setCancelled(true);
-        } catch (Exception ignored) {
+        SelectorLogger logger = SelectorLogger.getLogger();
 
+        ItemStack item = event.getCurrentItem();
+        if (item != null) {
+            ItemMeta meta = item.getItemMeta();
+            if (meta != null) {
+                String menuName = meta.getPersistentDataContainer().get(JavaMenu.MENU_NAME_KEY, JavaMenu.MENU_NAME_TYPE);
+                if (menuName != null) {
+                    JavaMenu menu = javaMenuRegistry.getMenu(menuName);
+                    if (menu == null) {
+                        logger.warn("Failed to find any Java menu under the name '" + menuName + "' in order to process inventory click by player: " + player.getName());
+                    } else {
+                        menu.process(event.getSlot(), event.isRightClick(), player);
+                    }
+                }
+            }
         }
     }
 }
\ No newline at end of file

From 73f4abb9df58c55cf289be46a3eed62a7663a9fa Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Wed, 30 Jun 2021 23:41:27 -0400
Subject: [PATCH 62/74] try to not access material stuff from older versions,
 on older versions

---
 .../geyserhub/module/menu/AccessItem.java     | 24 ++++++++++++-------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
index 5af5cb2..bb420c4 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
@@ -4,6 +4,7 @@
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
 import dev.projectg.geyserhub.config.ConfigId;
+import org.bukkit.Bukkit;
 import org.bukkit.ChatColor;
 import org.bukkit.Material;
 import org.bukkit.configuration.file.FileConfiguration;
@@ -16,6 +17,8 @@
 
 public class AccessItem {
 
+    private static final String NON_LEGACY_MATERIAL_VERSIONS = "(1\\.14\\S*)|(1\\.15\\S*|1\\.16\\S*|1\\.17\\S*|1\\.18\\S*)";
+
     private static final ItemStack ACCESS_ITEM;
     static {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
@@ -25,17 +28,22 @@ public class AccessItem {
         if (config.contains("Selector-Item.Material", true)) {
             String materialName = config.getString("Selector-Item.Material");
             Objects.requireNonNull(materialName);
-            material = Material.getMaterial(materialName, false);
+            material = Material.getMaterial(materialName);
             if (material == null) {
-                material = Material.getMaterial(materialName, true);
-                if (material == null) {
-                    SelectorLogger.getLogger().warn("Failed to find a Material for \"" + materialName + "\". Defaulting to COMPASS for the access item.");
-                    material = Material.COMPASS;
+                SelectorLogger.getLogger().warn("Failed to find a Material for \"" + materialName + "\". Defaulting to COMPASS for the access item.");
+                material = Material.COMPASS;
+            } else {
+                // Hacky way to avoid using enum values that don't exist on older versions if we are on older versions
+                if (Bukkit.getServer().getVersion().matches(NON_LEGACY_MATERIAL_VERSIONS)) {
+                    if (material == Material.AIR || material == Material.CAVE_AIR || material == Material.VOID_AIR) {
+                        SelectorLogger.getLogger().warn("\"Selector-Item.Material\" cannot be AIR! Choose a different material. Defaulting to COMPASS for the access item.");
+                    }
+                } else {
+                    if (material == Material.AIR) {
+                        SelectorLogger.getLogger().warn("\"Selector-Item.Material\" cannot be AIR! Choose a different material. Defaulting to COMPASS for the access item.");
+                    }
                 }
             }
-            if (material == Material.AIR || material == Material.CAVE_AIR || material == Material.VOID_AIR) {
-                SelectorLogger.getLogger().warn("\"Selector-Item.Material\" cannot be AIR! Choose a different material. Defaulting to COMPASS for the access item.");
-            }
         } else {
             SelectorLogger.getLogger().warn("Failed to find \"Selector-Item.Material\" in the config! Defaulting to COMPASS for the access item.");
             material = Material.COMPASS;

From af69263140c1c5917caf0bca823c38fd518b481f Mon Sep 17 00:00:00 2001
From: Jens Collaert <jenscollaertprive@hotmail.com>
Date: Thu, 1 Jul 2021 07:54:28 +0200
Subject: [PATCH 63/74] Added boolean check on listener

---
 .../geyserhub/module/menu/java/JavaMenuListeners.java     | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
index 792238c..9aff14a 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
@@ -1,6 +1,10 @@
 package dev.projectg.geyserhub.module.menu.java;
 
+import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
+import dev.projectg.geyserhub.config.ConfigId;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
@@ -21,6 +25,10 @@ public JavaMenuListeners(@Nonnull JavaMenuRegistry javaMenuRegistry) {
 
     @EventHandler
     public void onInventoryClick(InventoryClickEvent event) {
+        FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
+        if (!config.getBoolean("Java-Selector.Enable")){
+            return;
+        }
         Player player = (Player) event.getWhoClicked();
         SelectorLogger logger = SelectorLogger.getLogger();
 

From 13b6bdce12fa2613185dc5dbdb235830a16701e7 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Thu, 1 Jul 2021 16:04:32 -0400
Subject: [PATCH 64/74] improve debug logging for forms and menus

---
 .../geyserhub/module/menu/MenuUtils.java      | 23 ++++++++++---------
 .../geyserhub/module/menu/java/JavaMenu.java  |  5 ++--
 2 files changed, 15 insertions(+), 13 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java b/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
index 35b9d35..3dbc01c 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
@@ -89,7 +89,7 @@ public static List<String> getCommands(@Nonnull ConfigurationSection buttonData)
                 logger.warn(getParentName(buttonData) + "." + buttonData.getName() + " contains commands list but the list was empty.");
             } else {
                 List<String> commands = buttonData.getStringList("Commands");
-                logger.debug(getParentName(buttonData) + "." + buttonData.getName() + " contains commands: " + commands);
+                logger.debug(getParentName(buttonData.getParent()) + "." + getParentName(buttonData) + "." + buttonData.getName() + " contains commands: " + commands);
                 return commands;
             }
         }
@@ -108,7 +108,7 @@ public static String getServer(@Nonnull ConfigurationSection buttonData) {
 
         if (buttonData.contains("Server") && buttonData.isString("Server")) {
             String serverName = Objects.requireNonNull(buttonData.getString("Server"));
-            logger.debug(getParentName(buttonData) + "." + buttonData.getName() + " contains BungeeCord target server: " + serverName);
+            logger.debug(getParentName(buttonData.getParent()) + "." + getParentName(buttonData) + "." + buttonData.getName() + " contains BungeeCord target server: " + serverName);
             return serverName;
         }
         return null;
@@ -117,17 +117,18 @@ public static String getServer(@Nonnull ConfigurationSection buttonData) {
     /**
      * Get the name of the parent config section of the given config section
      * @param configSection the config section to get the parent name of
-     * @return the parent name, "null" if there was no parent
+     * @return the parent name, "null" if there was no parent, or if the the given configSection was null
      */
     @Nonnull
-    public static String getParentName(@Nonnull ConfigurationSection configSection) {
-        Objects.requireNonNull(configSection);
-
-        ConfigurationSection parent = configSection.getParent();
-        if (parent == null) {
-            return "null";
-        } else {
-            return parent.getName();
+    public static String getParentName(@Nullable ConfigurationSection configSection) {
+
+        if (configSection != null) {
+            ConfigurationSection parent = configSection.getParent();
+            if (parent != null) {
+                return parent.getName();
+            }
         }
+
+        return "null";
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index f6711a8..f0b35bc 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -118,7 +118,7 @@ protected JavaMenu(@Nonnull ConfigurationSection configSection) {
      */
     @Nonnull
     private Map<Integer, ItemButton> getAllButtons(@Nonnull ConfigurationSection configSection) {
-        logger.debug("Getting buttons for java form: " + menuName);
+        logger.debug("Getting buttons for Java form: " + menuName);
 
         // Get all the defined buttons in the buttons section
         Set<String> allButtonIds = configSection.getKeys(false);
@@ -146,10 +146,11 @@ private Map<Integer, ItemButton> getAllButtons(@Nonnull ConfigurationSection con
                 continue;
             }
 
+            logger.debug("Creating button: " + buttonId);
             ItemButton button = getButton(buttonInfo);
             if (button != null) {
                 compiledButtons.put(slot, button);
-                logger.debug("Java Button: " + menuName + "." + buttonId + " was successfully added.");
+                logger.debug("Created Button: " + buttonId);
             }
         }
         return compiledButtons;

From a06c10d66c0003b558d2f560cac226c3798fb0bd Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Thu, 1 Jul 2021 16:05:46 -0400
Subject: [PATCH 65/74] fix java menu Materials in default config

---
 src/main/resources/selector.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/main/resources/selector.yml b/src/main/resources/selector.yml
index c16a5a5..9624ca7 100644
--- a/src/main/resources/selector.yml
+++ b/src/main/resources/selector.yml
@@ -27,7 +27,7 @@ Java-Selector:
       Buttons:
         2:
           Display-Name: "Lobby"
-          Material: Diamond
+          Material: DIAMOND
           Lore:
             - "&2Online players: %bungee_lobby%"
           Right-Click:
@@ -40,7 +40,7 @@ Java-Selector:
               - "tell %player_name% Sending you to the lobby in a left click fashion..."
         4:
           Display-Name: "Minigames"
-          Material: Grass
+          Material: GRASS
           Lore:
             - "Currently Available:"
             - "Spleef"
@@ -50,7 +50,7 @@ Java-Selector:
               - "player; ghub form minigames"
         6:
           Display-Name: "Survival"
-          Material: Emerald
+          Material: EMERALD
           Lore:
             - "&2online players: %bungee_survival%"
           Any-Click:

From 29a2d0e53bea34f208e04266e7a15a7919d08a60 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Thu, 1 Jul 2021 16:23:34 -0400
Subject: [PATCH 66/74] even more improvements to debug logging

---
 .../geyserhub/module/menu/MenuUtils.java      | 17 ++---
 .../module/menu/bedrock/BedrockForm.java      | 31 +++------
 .../geyserhub/module/menu/java/JavaMenu.java  | 64 +++++++++++--------
 3 files changed, 50 insertions(+), 62 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java b/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
index 3dbc01c..3bf5873 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
@@ -53,8 +53,6 @@ public static void affectPlayer(@Nonnull List<String> commands, @Nullable String
         }
     }
 
-
-
     /**
      * Process a prefixed command and run it
      * @param prefixedCommand A command that is prefixed with "player;" to run the command as the player, or "console;", to run the command as the console.
@@ -88,9 +86,7 @@ public static List<String> getCommands(@Nonnull ConfigurationSection buttonData)
             if (buttonData.getStringList("Commands").isEmpty()) {
                 logger.warn(getParentName(buttonData) + "." + buttonData.getName() + " contains commands list but the list was empty.");
             } else {
-                List<String> commands = buttonData.getStringList("Commands");
-                logger.debug(getParentName(buttonData.getParent()) + "." + getParentName(buttonData) + "." + buttonData.getName() + " contains commands: " + commands);
-                return commands;
+                return buttonData.getStringList("Commands");
             }
         }
         return Collections.emptyList();
@@ -104,31 +100,26 @@ public static List<String> getCommands(@Nonnull ConfigurationSection buttonData)
     @Nullable
     public static String getServer(@Nonnull ConfigurationSection buttonData) {
         Objects.requireNonNull(buttonData);
-        SelectorLogger logger = SelectorLogger.getLogger();
 
         if (buttonData.contains("Server") && buttonData.isString("Server")) {
-            String serverName = Objects.requireNonNull(buttonData.getString("Server"));
-            logger.debug(getParentName(buttonData.getParent()) + "." + getParentName(buttonData) + "." + buttonData.getName() + " contains BungeeCord target server: " + serverName);
-            return serverName;
+            return Objects.requireNonNull(buttonData.getString("Server"));
         }
         return null;
     }
 
     /**
-     * Get the name of the parent config section of the given config section
-     * @param configSection the config section to get the parent name of
+     * Get the name of the parent of a config section
+     * @param configSection the config section
      * @return the parent name, "null" if there was no parent, or if the the given configSection was null
      */
     @Nonnull
     public static String getParentName(@Nullable ConfigurationSection configSection) {
-
         if (configSection != null) {
             ConfigurationSection parent = configSection.getParent();
             if (parent != null) {
                 return parent.getName();
             }
         }
-
         return "null";
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
index 29f92b1..6a689fb 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/bedrock/BedrockForm.java
@@ -82,8 +82,6 @@ protected BedrockForm(@Nonnull ConfigurationSection configSection) {
             logger.warn("Failed to create any valid buttons of Bedrock form: " + formName + "! All listed buttons have a malformed section!");
             isEnabled = false;
             return;
-        } else {
-            logger.debug("Finished adding buttons to bedrock form: " + formName);
         }
         this.allButtons = buttons;
 
@@ -95,7 +93,7 @@ protected BedrockForm(@Nonnull ConfigurationSection configSection) {
      * @return A list of Buttons, which may be empty.
      */
     private List<BedrockButton> getButtons(@Nonnull ConfigurationSection configSection) {
-        logger.debug("Getting buttons for form: " + formName);
+        logger.debug("Getting buttons for Bedrock form: " + formName);
 
         // Get all the defined buttons in the buttons section
         Set<String> allButtonIds = configSection.getKeys(false);
@@ -117,7 +115,7 @@ private List<BedrockButton> getButtons(@Nonnull ConfigurationSection configSecti
             if (buttonInfo.contains("Button-Text", true) && buttonInfo.isString("Button-Text")) {
                 String buttonText = buttonInfo.getString("Button-Text");
                 Objects.requireNonNull(buttonText);
-                logger.debug(buttonId + " has Button-Text: " + buttonText);
+                logger.debug(formName + "." + buttonId + " has text: " + buttonText);
 
                 // Add image if specified
                 FormImage image = null;
@@ -125,26 +123,19 @@ private List<BedrockButton> getButtons(@Nonnull ConfigurationSection configSecti
                     String imageURL = buttonInfo.getString("ImageURL");
                     Objects.requireNonNull(imageURL);
                     image = FormImage.of(FormImage.Type.URL, imageURL);
-                    logger.debug(buttonId + " contains image with URL: " + image.getData());
+                    logger.debug(formName + "." + buttonId + " contains image: " + image.getData());
                 }
 
                 // Add commands if specified
-                List<String> commands = Collections.emptyList();
-                if (buttonInfo.contains("Commands") && buttonInfo.isList("Commands")) {
-                    if (buttonInfo.getStringList("Commands").isEmpty()) {
-                        logger.warn(buttonId + " contains commands list but the list was empty.");
-                    } else {
-                        commands = buttonInfo.getStringList("Commands");
-                        logger.debug(buttonId + " contains commands: " + commands);
-                    }
+                List<String> commands = MenuUtils.getCommands(buttonInfo);
+                if (!commands.isEmpty()) {
+                    logger.debug(formName + "." + buttonId + " contains commands: " + commands);
                 }
 
                 // Add server if specified
-                String serverName = null;
-                if (buttonInfo.contains("Server") && buttonInfo.isString("Server")) {
-                    serverName = buttonInfo.getString("Server");
-                    Objects.requireNonNull(serverName);
-                    logger.debug(buttonId + " contains BungeeCord target server: " + serverName);
+                String serverName = MenuUtils.getServer(buttonInfo);
+                if (serverName != null) {
+                    logger.debug(formName + "." + buttonId + " contains target server: " + serverName);
                 }
 
                 BedrockButton button = new BedrockButton(buttonText);
@@ -152,8 +143,6 @@ private List<BedrockButton> getButtons(@Nonnull ConfigurationSection configSecti
                 button.setCommands(commands);
                 button.setServer(serverName);
                 compiledButtons.add(button);
-
-                logger.debug(buttonId + " was successfully added.");
             } else {
                 logger.warn(buttonId + " does not contain a valid Button-Text value, not adding.");
             }
@@ -168,7 +157,7 @@ private List<BedrockButton> getButtons(@Nonnull ConfigurationSection configSecti
      */
     public void sendForm(@Nonnull FloodgatePlayer floodgatePlayer) {
         if (!isEnabled) {
-            throw new AssertionError("Form: " + title + " that failed to load was called to be sent to a player!");
+            throw new AssertionError("Bedrock Form: " + title + " that failed to load was called to be sent to a player!");
         }
 
         SelectorLogger logger = SelectorLogger.getLogger();
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index f0b35bc..2060d8c 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -66,7 +66,7 @@ protected JavaMenu(@Nonnull ConfigurationSection configSection) {
         if (configSection.contains("Title") && configSection.contains("Size") && configSection.isInt("Size")) {
             title = Objects.requireNonNull(configSection.getString("Title"));
             size = configSection.getInt("Size");
-            logger.debug("Java Menu: " + menuName + " has Title: " + title + " Size: " + size);
+            logger.debug("Java Menu: " + menuName + " has Title: '" + title + "', Size: " + size);
         } else {
             logger.warn("Java Menu: " + menuName + " does not contain a Title or Size value, unable to create menu");
             isEnabled = false;
@@ -82,8 +82,6 @@ protected JavaMenu(@Nonnull ConfigurationSection configSection) {
                 logger.warn("Failed to create any valid buttons of Bedrock form: " + menuName + "! All listed buttons have a malformed section!");
                 isEnabled = false;
                 return;
-            } else {
-                logger.debug("Finished adding buttons to Java menu: " + menuName);
             }
             this.buttons = buttons;
         } else {
@@ -150,7 +148,6 @@ private Map<Integer, ItemButton> getAllButtons(@Nonnull ConfigurationSection con
             ItemButton button = getButton(buttonInfo);
             if (button != null) {
                 compiledButtons.put(slot, button);
-                logger.debug("Created Button: " + buttonId);
             }
         }
         return compiledButtons;
@@ -168,7 +165,7 @@ private ItemButton getButton(@Nonnull ConfigurationSection buttonInfo) {
         if (buttonInfo.contains("Display-Name", true) && buttonInfo.isString("Display-Name")) {
             displayName = buttonInfo.getString("Display-Name");
             Objects.requireNonNull(displayName);
-            logger.debug("Java Button: " + menuName + "." + buttonId + " has Display-Name: " + displayName);
+            logger.debug(menuName + "." + buttonId + " has Display-Name: " + displayName);
         } else {
             logger.warn("Java Button: " + menuName + "." + buttonId + " does not contain a valid Button-Text value, not adding.");
             return null;
@@ -195,34 +192,45 @@ private ItemButton getButton(@Nonnull ConfigurationSection buttonInfo) {
 
         // Create the button
         ItemButton button = new ItemButton(displayName, material);
+        OutcomeButton rightOutcome = button.getOutcomeButton(true);
+        OutcomeButton leftOutcome = button.getOutcomeButton(false);
 
         // Set server(s) and commands
-        ConfigurationSection rightClick = null;
-        if (buttonInfo.contains("Right-Click") && buttonInfo.isConfigurationSection("Right-Click")) {
-            rightClick = buttonInfo.getConfigurationSection("Right-Click");
-            Objects.requireNonNull(rightClick);
-            button.getOutcomeButton(true).setCommands(MenuUtils.getCommands(rightClick));
-            button.getOutcomeButton(true).setServer(MenuUtils.getServer(rightClick));
-        }
-        ConfigurationSection leftClick = null;
-        if (buttonInfo.contains("Left-Click") && buttonInfo.isConfigurationSection("Left-Click")) {
-            leftClick = buttonInfo.getConfigurationSection("Left-Click");
-            Objects.requireNonNull(leftClick);
-            button.getOutcomeButton(false).setCommands(MenuUtils.getCommands(leftClick));
-            button.getOutcomeButton(false).setServer(MenuUtils.getServer(leftClick));
-        }
-
-        if (buttonInfo.contains("Any-Click") && buttonInfo.isConfigurationSection("Any-Click")) {
+        ConfigurationSection rightClick = buttonInfo.getConfigurationSection("Right-Click");
+        ConfigurationSection leftClick = buttonInfo.getConfigurationSection("Left-Click");
+        ConfigurationSection anyClick = buttonInfo.getConfigurationSection("Any-Click");
+        if (anyClick != null) {
             if (rightClick != null || leftClick != null) {
                 logger.warn("Java Button: " + menuName + "." + buttonId + " Cannot define both Any-Click behaviour and also Right/Left-Click behaviour! Ignoring Any-Click section.");
-            } else {
-                ConfigurationSection anyClick = buttonInfo.getConfigurationSection("Any-Click");
-                Objects.requireNonNull(anyClick);
-                button.getOutcomeButton(true).setCommands(MenuUtils.getCommands(anyClick));
-                button.getOutcomeButton(true).setServer(MenuUtils.getServer(anyClick));
-                button.getOutcomeButton(false).setCommands(MenuUtils.getCommands(anyClick));
-                button.getOutcomeButton(false).setServer(MenuUtils.getServer(anyClick));
             }
+            List<String> commands = MenuUtils.getCommands(anyClick);
+            String server = MenuUtils.getServer(anyClick);
+            rightOutcome.setCommands(commands);
+            rightOutcome.setServer(server);
+            leftOutcome.setCommands(commands);
+            leftOutcome.setServer(server);
+        } else {
+            if (rightClick != null) {
+                rightOutcome.setCommands(MenuUtils.getCommands(rightClick));
+                rightOutcome.setServer(MenuUtils.getServer(rightClick));
+            }
+            if (leftClick != null) {
+                leftOutcome.setCommands(MenuUtils.getCommands(leftClick));
+                leftOutcome.setServer(MenuUtils.getServer(leftClick));
+            }
+        }
+
+        if (!rightOutcome.getCommands().isEmpty()) {
+            logger.debug(menuName + "." + buttonId + ".right" + " contains commands: " + rightOutcome.getCommands());
+        }
+        if (rightOutcome.getServer() != null) {
+            logger.debug(menuName + "." + buttonId + ".right" +  " contains target server: " + rightOutcome.getServer());
+        }
+        if (!leftOutcome.getCommands().isEmpty()) {
+            logger.debug(menuName + "." + buttonId + ".left" + " contains commands: " + leftOutcome.getCommands());
+        }
+        if (leftOutcome.getServer() != null) {
+            logger.debug(menuName + "." + buttonId + ".left" +  " contains target server: " + leftOutcome.getServer());
         }
 
         return button;

From 9f7d1ef10475770deba506a1b8ca4d20a29dd328 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Thu, 1 Jul 2021 18:22:50 -0400
Subject: [PATCH 67/74] implement basic player name uuid placeholders if papi
 isnt installed

---
 .../java/dev/projectg/geyserhub/GeyserHubMain.java     |  3 +++
 .../dev/projectg/geyserhub/utils/PlaceholderUtils.java | 10 ++++++----
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
index d981706..9bae261 100644
--- a/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
+++ b/src/main/java/dev/projectg/geyserhub/GeyserHubMain.java
@@ -31,6 +31,9 @@ public void onEnable() {
         new Metrics(this, 11427);
         // getting the logger forces the config to load before our loadConfiguration() is called...
         SelectorLogger logger = SelectorLogger.getLogger();
+        if (!Bukkit.getServer().getPluginManager().isPluginEnabled("PlaceholderAPI")) {
+            logger.warn("This plugin works best with PlaceholderAPI! Since you don't have it installed, only %player_name% and %player_uuid% will work in the GeyserHub config!");
+        }
 
         try {
             Properties gitProperties = new Properties();
diff --git a/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java b/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java
index 3d4b1c8..be625fe 100644
--- a/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java
+++ b/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java
@@ -1,6 +1,5 @@
 package dev.projectg.geyserhub.utils;
 
-import dev.projectg.geyserhub.SelectorLogger;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.entity.Player;
@@ -16,11 +15,14 @@ public class PlaceholderUtils {
      * @return the formatted text.
      */
     public static String setPlaceholders(@Nonnull Player player, @Nonnull String text) {
+        String processedText = text;
+
         if (Bukkit.getServer().getPluginManager().isPluginEnabled("PlaceholderAPI")) {
-            SelectorLogger.getLogger().debug("PlaceholderAPI was used.");
-            return PlaceholderAPI.setPlaceholders(player, text);
+            processedText = PlaceholderAPI.setPlaceholders(player, text);
         } else {
-            return text;
+            processedText = processedText.replace("%player_name%", player.getName()).replace("%player_uuid%", player.getUniqueId().toString());
         }
+
+        return processedText;
     }
 }

From e30415ad87d5d27e4fa74bf49337dc0872c24788 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Thu, 1 Jul 2021 20:33:36 -0400
Subject: [PATCH 68/74] fix placeholder usage in one spot

---
 .../geyserhub/module/menu/java/JavaMenu.java  |  2 +-
 .../geyserhub/utils/PlaceholderUtils.java     | 29 +++++++++++++++----
 2 files changed, 25 insertions(+), 6 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index 2060d8c..5931358 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -259,7 +259,7 @@ public void sendMenu(@Nonnull Player player) {
             ItemMeta itemMeta = serverStack.getItemMeta();
             if (itemMeta != null) {
                 itemMeta.setDisplayName(PlaceholderUtils.setPlaceholders(player, button.getDisplayName()));
-                itemMeta.setLore(PlaceholderAPI.setPlaceholders(player, button.getLore()));
+                itemMeta.setLore(PlaceholderUtils.setPlaceholders(player, button.getLore()));
                 itemMeta.getPersistentDataContainer().set(MENU_NAME_KEY, PersistentDataType.STRING, menuName);
                 serverStack.setItemMeta(itemMeta);
             } else {
diff --git a/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java b/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java
index be625fe..27c80ca 100644
--- a/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java
+++ b/src/main/java/dev/projectg/geyserhub/utils/PlaceholderUtils.java
@@ -1,13 +1,22 @@
 package dev.projectg.geyserhub.utils;
 
+import dev.projectg.geyserhub.SelectorLogger;
 import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.entity.Player;
 
 import javax.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.List;
 
 public class PlaceholderUtils {
 
+    private static final boolean usePlaceholders;
+    static {
+        SelectorLogger.getLogger().debug("Initializing PlaceholderUtils");
+        usePlaceholders = Bukkit.getServer().getPluginManager().isPluginEnabled("PlaceholderAPI");
+    }
+
     /**
      * Returns the inputted text with placeholders set, if PlaceholderAPI is loaded. If not, it returns the same text.
      * @param player The player
@@ -15,14 +24,24 @@ public class PlaceholderUtils {
      * @return the formatted text.
      */
     public static String setPlaceholders(@Nonnull Player player, @Nonnull String text) {
-        String processedText = text;
-
-        if (Bukkit.getServer().getPluginManager().isPluginEnabled("PlaceholderAPI")) {
-            processedText = PlaceholderAPI.setPlaceholders(player, text);
+        if (usePlaceholders) {
+            return PlaceholderAPI.setPlaceholders(player, text);
         } else {
-            processedText = processedText.replace("%player_name%", player.getName()).replace("%player_uuid%", player.getUniqueId().toString());
+            return text.replace("%player_name%", player.getName()).replace("%player_uuid%", player.getUniqueId().toString());
         }
+    }
 
+    /**
+     * Returns the inputted text with placeholders set, if PlaceholderAPI is loaded. If not, it returns the same text.
+     * @param player The player
+     * @param text The text
+     * @return the formatted text.
+     */
+    public static List<String> setPlaceholders(@Nonnull Player player, @Nonnull List<String> text) {
+        List<String> processedText = new ArrayList<>();
+        for (String line : text) {
+            processedText.add(setPlaceholders(player, line));
+        }
         return processedText;
     }
 }

From 11175fa1f41ebc55c3e19f54f837cafb2d810194 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Thu, 1 Jul 2021 22:47:29 -0400
Subject: [PATCH 69/74] fix stuff with java form

---
 .../geyserhub/module/menu/MenuUtils.java      | 32 ++++++--
 .../geyserhub/module/menu/java/JavaMenu.java  | 82 +++++++++++++------
 .../module/menu/java/JavaMenuListeners.java   |  1 +
 src/main/resources/selector.yml               |  8 +-
 4 files changed, 86 insertions(+), 37 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java b/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
index 3bf5873..a099095 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/MenuUtils.java
@@ -54,20 +54,34 @@ public static void affectPlayer(@Nonnull List<String> commands, @Nullable String
     }
 
     /**
-     * Process a prefixed command and run it
-     * @param prefixedCommand A command that is prefixed with "player;" to run the command as the player, or "console;", to run the command as the console.
+     * Process a command and run it.
+     * If the command is prefixed with "player;" the command will be run as the player given, which CANNOT be null.
+     * If the command is prefixed with "console;" the command will be run as the console.
+     *
+     * @param command The command to run
      * @param player the Player to run the command as, if prefixed with "player;"
      */
-    public static void runCommand(@Nonnull String prefixedCommand, @Nonnull Player player) {
-        Objects.requireNonNull(prefixedCommand);
-        Objects.requireNonNull(player);
+    public static void runCommand(@Nonnull String command, @Nullable Player player) {
+        Objects.requireNonNull(command);
 
+        // Run as console by default
         CommandSender sender = Bukkit.getServer().getConsoleSender();
-        if (prefixedCommand.startsWith(playerPrefix)) {
-            sender = player;
+        if (command.startsWith(playerPrefix)) {
+            if (player == null) {
+                throw new IllegalArgumentException("The following command is denoted to be run by a player, but a null player was passed internally: " + command);
+            } else {
+                sender = player;
+            }
         }
-        // Split the input into two strings between ";" and get the second string
-        String executableCommand = prefixedCommand.split(";", 2)[1].stripLeading();
+
+        String executableCommand;
+        if (command.startsWith(playerPrefix) || command.startsWith(consolePrefix)) {
+            // Split the input into two strings between ";" and get the second string
+             executableCommand = command.split(";", 2)[1].trim();
+        } else {
+            executableCommand = command;
+        }
+
         SelectorLogger.getLogger().debug("Running command: [" + executableCommand + "] as " + sender.getName());
         Bukkit.getServer().dispatchCommand(sender, executableCommand);
     }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index 5931358..9f8a626 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -5,12 +5,12 @@
 import dev.projectg.geyserhub.module.menu.MenuUtils;
 import dev.projectg.geyserhub.module.menu.button.OutcomeButton;
 import dev.projectg.geyserhub.utils.PlaceholderUtils;
-import me.clip.placeholderapi.PlaceholderAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.Material;
 import org.bukkit.NamespacedKey;
 import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.InventoryType;
 import org.bukkit.inventory.Inventory;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.inventory.meta.ItemMeta;
@@ -22,6 +22,9 @@
 
 public class JavaMenu {
 
+    public static final int MAX_SIZE = 54;
+    public static final int HOPPER_SIZE = 5;
+
     public static final NamespacedKey MENU_NAME_KEY = new NamespacedKey(GeyserHubMain.getInstance(), "geyserHubMenu");
     public static final PersistentDataType<String, String>  MENU_NAME_TYPE = PersistentDataType.STRING;
 
@@ -65,7 +68,7 @@ protected JavaMenu(@Nonnull ConfigurationSection configSection) {
         // Get the inventory title and size
         if (configSection.contains("Title") && configSection.contains("Size") && configSection.isInt("Size")) {
             title = Objects.requireNonNull(configSection.getString("Title"));
-            size = configSection.getInt("Size");
+            size = Math.abs(configSection.getInt("Size"));
             logger.debug("Java Menu: " + menuName + " has Title: '" + title + "', Size: " + size);
         } else {
             logger.warn("Java Menu: " + menuName + " does not contain a Title or Size value, unable to create menu");
@@ -90,22 +93,7 @@ protected JavaMenu(@Nonnull ConfigurationSection configSection) {
             return;
         }
 
-        // Make sure the inventory will be able to hold all the buttons
-        int highestGivenSlot = 0;
-        for (Integer slot: buttons.keySet()) {
-            highestGivenSlot = Math.max(slot, highestGivenSlot);
-        }
-        int minimumSize = highestGivenSlot + 1;
-        if (minimumSize > size) {
-            logger.warn("Java Menu: " + menuName + " has a button with slot " + highestGivenSlot + ", but the inventory size is only " + size + ". Increasing the size.");
-            size = minimumSize;
-        }
-
-        // Make sure that the inventory size is a multiple of 9
-        if (size % 9 != 0) {
-            // Divide the size by 9, round the ratio up to the next int value, then multiply by 9 to get the closest higher number that is a multiple of 9
-            size = (int) (9*(Math.ceil(Math.abs(size/9))));
-        }
+        validateSize();
 
         isEnabled = true;
     }
@@ -236,6 +224,47 @@ private ItemButton getButton(@Nonnull ConfigurationSection buttonInfo) {
         return button;
     }
 
+    /**
+     * Modifies the {@link #buttons} list and {@link #size} to not exceed what is allowed.
+     */
+    private void validateSize() {
+
+        int minimumSize = 0; // used for later
+        for (int slot = 0; slot < buttons.size(); slot++) {
+            if (buttons.containsKey(slot)) {
+                if (slot + 1 > MAX_SIZE) {
+                    buttons.remove(slot);
+                    logger.warn("Removing button with index " + slot + " from Java menu " + menuName + " because it exceeds the max index of " + (MAX_SIZE - 1) + "(max size of " + MAX_SIZE + ")");
+                } else {
+                    minimumSize = Math.max(slot + 1, minimumSize);
+                }
+            }
+        }
+
+        if (size > MAX_SIZE) {
+            size = MAX_SIZE;
+            logger.warn("Setting the size of Java menu " + menuName + " to " + MAX_SIZE + " because it exceeded the maximum size.");
+            return;
+        }
+
+        // Increase the size if the buttons don't fit
+        boolean increasedSize = false;
+        if (minimumSize > size) {
+            logger.warn("Java Menu: " + menuName + " has a button that needs a size of " + minimumSize + ", but the inventory size is only " + size + ". Increasing the size.");
+            size = minimumSize;
+            increasedSize = true;
+        }
+
+        // Make sure that the inventory size is a multiple of 9, unless its the hopper size.
+        if (size != HOPPER_SIZE && size % 9 != 0) {
+            // Divide the size by 9D, round the ratio up to the next int value, then multiply by 9 to get the closest higher number that is a multiple of 9
+            size = (int) (9*(Math.ceil(size/9D)));
+            if (!increasedSize) {
+                logger.warn("Java Menu: " + menuName + " size is not 5 (allowed value, for anvils), and is not a multiple of 9 between 9 and 54 (allowed values for chests). Increasing size to " + size);
+            }
+        }
+    }
+
     /**
      * @return A non-copy of the contents of this form.
      */
@@ -249,7 +278,13 @@ public void sendMenu(@Nonnull Player player) {
             throw new AssertionError("Tried to send Java Menu: " + menuName + " to a player but the form was not enabled");
         }
 
-        Inventory selectorGUI = Bukkit.createInventory(player, size, PlaceholderUtils.setPlaceholders(player, title));
+        Inventory selectorGUI;
+        if (size == HOPPER_SIZE) {
+            selectorGUI = Bukkit.createInventory(player, InventoryType.ANVIL, PlaceholderUtils.setPlaceholders(player, title));
+        } else {
+            selectorGUI = Bukkit.createInventory(player, size, PlaceholderUtils.setPlaceholders(player, title));
+        }
+
 
         for (Integer slot : buttons.keySet()) {
             ItemButton button = buttons.get(slot);
@@ -257,16 +292,15 @@ public void sendMenu(@Nonnull Player player) {
             // Construct the item
             ItemStack serverStack = new ItemStack(button.getMaterial());
             ItemMeta itemMeta = serverStack.getItemMeta();
-            if (itemMeta != null) {
+            if (itemMeta == null) {
+                logger.warn("Java Button: " + menuName + "." + slot + " with Material: " + button.getMaterial() + " returned null ItemMeta, not adding the button!");
+            } else {
                 itemMeta.setDisplayName(PlaceholderUtils.setPlaceholders(player, button.getDisplayName()));
                 itemMeta.setLore(PlaceholderUtils.setPlaceholders(player, button.getLore()));
                 itemMeta.getPersistentDataContainer().set(MENU_NAME_KEY, PersistentDataType.STRING, menuName);
                 serverStack.setItemMeta(itemMeta);
-            } else {
-                logger.warn("Java Button: " + menuName + "." + slot + " with Material: " + button.getMaterial() + " returned null ItemMeta, failed to set display name or lore. The button will not have any results.");
+                selectorGUI.setItem(slot, serverStack);
             }
-
-            selectorGUI.setItem(slot, serverStack);
         }
 
         player.openInventory(selectorGUI);
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
index 9aff14a..f38829f 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenuListeners.java
@@ -38,6 +38,7 @@ public void onInventoryClick(InventoryClickEvent event) {
             if (meta != null) {
                 String menuName = meta.getPersistentDataContainer().get(JavaMenu.MENU_NAME_KEY, JavaMenu.MENU_NAME_TYPE);
                 if (menuName != null) {
+                    event.setCancelled(true);
                     JavaMenu menu = javaMenuRegistry.getMenu(menuName);
                     if (menu == null) {
                         logger.warn("Failed to find any Java menu under the name '" + menuName + "' in order to process inventory click by player: " + player.getName());
diff --git a/src/main/resources/selector.yml b/src/main/resources/selector.yml
index 9624ca7..5d3dcb6 100644
--- a/src/main/resources/selector.yml
+++ b/src/main/resources/selector.yml
@@ -58,17 +58,17 @@ Java-Selector:
             Commands:
               - "tell %player_name% Sending you to Survival..."
     minigames:
-      Title: "Server Selector"
-      Size: 3
+      Title: "Minigames"
+      Size: 5
       Buttons:
-        0:
+        1:
           Display-Name: "Spleef"
           Material: SNOWBALL
           Lore:
             - "Play Spleef!"
           Any-Click:
             Server: "spleef"
-        2:
+        3:
           Display-Name: "Hide & Seek"
           Material: DIRT
           Lore:

From a6e5818eca67618b47153fdedfd61d3714feb707 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Fri, 2 Jul 2021 00:31:07 -0400
Subject: [PATCH 70/74] more stuff

---
 .../geyserhub/module/menu/java/JavaMenu.java  | 25 +++++++++++++------
 1 file changed, 18 insertions(+), 7 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index 9f8a626..8e570a2 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -6,6 +6,7 @@
 import dev.projectg.geyserhub.module.menu.button.OutcomeButton;
 import dev.projectg.geyserhub.utils.PlaceholderUtils;
 import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
 import org.bukkit.Material;
 import org.bukkit.NamespacedKey;
 import org.bukkit.configuration.ConfigurationSection;
@@ -69,7 +70,7 @@ protected JavaMenu(@Nonnull ConfigurationSection configSection) {
         if (configSection.contains("Title") && configSection.contains("Size") && configSection.isInt("Size")) {
             title = Objects.requireNonNull(configSection.getString("Title"));
             size = Math.abs(configSection.getInt("Size"));
-            logger.debug("Java Menu: " + menuName + " has Title: '" + title + "', Size: " + size);
+            logger.debug("Java Menu: " + menuName + " has Title: " + title);
         } else {
             logger.warn("Java Menu: " + menuName + " does not contain a Title or Size value, unable to create menu");
             isEnabled = false;
@@ -94,6 +95,7 @@ protected JavaMenu(@Nonnull ConfigurationSection configSection) {
         }
 
         validateSize();
+        logger.debug("Java menu '" + menuName + "' has a total inventory size of " + size);
 
         isEnabled = true;
     }
@@ -104,7 +106,6 @@ protected JavaMenu(@Nonnull ConfigurationSection configSection) {
      */
     @Nonnull
     private Map<Integer, ItemButton> getAllButtons(@Nonnull ConfigurationSection configSection) {
-        logger.debug("Getting buttons for Java form: " + menuName);
 
         // Get all the defined buttons in the buttons section
         Set<String> allButtonIds = configSection.getKeys(false);
@@ -132,7 +133,6 @@ private Map<Integer, ItemButton> getAllButtons(@Nonnull ConfigurationSection con
                 continue;
             }
 
-            logger.debug("Creating button: " + buttonId);
             ItemButton button = getButton(buttonInfo);
             if (button != null) {
                 compiledButtons.put(slot, button);
@@ -153,14 +153,24 @@ private ItemButton getButton(@Nonnull ConfigurationSection buttonInfo) {
         if (buttonInfo.contains("Display-Name", true) && buttonInfo.isString("Display-Name")) {
             displayName = buttonInfo.getString("Display-Name");
             Objects.requireNonNull(displayName);
+            displayName = ChatColor.translateAlternateColorCodes('&', displayName);
             logger.debug(menuName + "." + buttonId + " has Display-Name: " + displayName);
         } else {
             logger.warn("Java Button: " + menuName + "." + buttonId + " does not contain a valid Button-Text value, not adding.");
             return null;
         }
 
+        List<String> lore = new LinkedList<>();
+        if (buttonInfo.contains("Lore", true) && buttonInfo.isList("Lore")) {
+            List<String> unprocessedLore = buttonInfo.getStringList("Lore");
+            for (String line : unprocessedLore) {
+                lore.add(ChatColor.translateAlternateColorCodes('&', line));
+            }
+            logger.debug(menuName + "." + buttonId + "has Lore: " + unprocessedLore);
+        }
+
         Material material;
-        if (buttonInfo.contains("Material") && buttonInfo.isString("Material")) {
+        if (buttonInfo.contains("Material", true) && buttonInfo.isString("Material")) {
             String materialName = buttonInfo.getString("Material");
             Objects.requireNonNull(materialName);
             material = Material.getMaterial(materialName, false);
@@ -180,6 +190,7 @@ private ItemButton getButton(@Nonnull ConfigurationSection buttonInfo) {
 
         // Create the button
         ItemButton button = new ItemButton(displayName, material);
+        button.setLore(lore);
         OutcomeButton rightOutcome = button.getOutcomeButton(true);
         OutcomeButton leftOutcome = button.getOutcomeButton(false);
 
@@ -250,7 +261,7 @@ private void validateSize() {
         // Increase the size if the buttons don't fit
         boolean increasedSize = false;
         if (minimumSize > size) {
-            logger.warn("Java Menu: " + menuName + " has a button that needs a size of " + minimumSize + ", but the inventory size is only " + size + ". Increasing the size.");
+            logger.warn("Java Menu: " + menuName + " has a button that needs a size of " + minimumSize + ", but the inventory size is only " + size);
             size = minimumSize;
             increasedSize = true;
         }
@@ -260,7 +271,7 @@ private void validateSize() {
             // Divide the size by 9D, round the ratio up to the next int value, then multiply by 9 to get the closest higher number that is a multiple of 9
             size = (int) (9*(Math.ceil(size/9D)));
             if (!increasedSize) {
-                logger.warn("Java Menu: " + menuName + " size is not 5 (allowed value, for anvils), and is not a multiple of 9 between 9 and 54 (allowed values for chests). Increasing size to " + size);
+                logger.warn("Java Menu: " + menuName + " size is not 5 (allowed value, for hopper), and is not a multiple of 9 between 9 and 54 (allowed values for chests). Increasing size to " + size);
             }
         }
     }
@@ -280,7 +291,7 @@ public void sendMenu(@Nonnull Player player) {
 
         Inventory selectorGUI;
         if (size == HOPPER_SIZE) {
-            selectorGUI = Bukkit.createInventory(player, InventoryType.ANVIL, PlaceholderUtils.setPlaceholders(player, title));
+            selectorGUI = Bukkit.createInventory(player, InventoryType.HOPPER, PlaceholderUtils.setPlaceholders(player, title));
         } else {
             selectorGUI = Bukkit.createInventory(player, size, PlaceholderUtils.setPlaceholders(player, title));
         }

From 2c3f7e5c3b2039efac9b056a81e00e50a4c48948 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Fri, 2 Jul 2021 00:42:54 -0400
Subject: [PATCH 71/74] Remove support for & colour code

---
 .../geyserhub/module/menu/AccessItem.java     |  2 +-
 .../geyserhub/module/menu/java/JavaMenu.java  | 11 +++-----
 .../geyserhub/module/message/Broadcast.java   |  2 +-
 .../geyserhub/module/message/MessageJoin.java |  3 +--
 .../module/scoreboard/ScoreboardManager.java  |  3 +--
 src/main/resources/config.yml                 | 26 +++++++++----------
 src/main/resources/selector.yml               | 18 ++++++-------
 7 files changed, 29 insertions(+), 36 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
index bb420c4..2163ee0 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
@@ -63,7 +63,7 @@ public class AccessItem {
             SelectorLogger.getLogger().warn("\"Selector-Item.Name\" in the config does not exist. Defaulting to \"&6Server Selector\" for the access item name.");
             name = "&6Server Selector";
         }
-        meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', name));
+        meta.setDisplayName(name);
 
         // Set the lore in the meta
         List<String> lore;
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
index 8e570a2..6a312af 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/java/JavaMenu.java
@@ -6,7 +6,6 @@
 import dev.projectg.geyserhub.module.menu.button.OutcomeButton;
 import dev.projectg.geyserhub.utils.PlaceholderUtils;
 import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
 import org.bukkit.Material;
 import org.bukkit.NamespacedKey;
 import org.bukkit.configuration.ConfigurationSection;
@@ -153,20 +152,16 @@ private ItemButton getButton(@Nonnull ConfigurationSection buttonInfo) {
         if (buttonInfo.contains("Display-Name", true) && buttonInfo.isString("Display-Name")) {
             displayName = buttonInfo.getString("Display-Name");
             Objects.requireNonNull(displayName);
-            displayName = ChatColor.translateAlternateColorCodes('&', displayName);
             logger.debug(menuName + "." + buttonId + " has Display-Name: " + displayName);
         } else {
             logger.warn("Java Button: " + menuName + "." + buttonId + " does not contain a valid Button-Text value, not adding.");
             return null;
         }
 
-        List<String> lore = new LinkedList<>();
+        List<String> lore = Collections.emptyList();
         if (buttonInfo.contains("Lore", true) && buttonInfo.isList("Lore")) {
-            List<String> unprocessedLore = buttonInfo.getStringList("Lore");
-            for (String line : unprocessedLore) {
-                lore.add(ChatColor.translateAlternateColorCodes('&', line));
-            }
-            logger.debug(menuName + "." + buttonId + "has Lore: " + unprocessedLore);
+            lore = buttonInfo.getStringList("Lore");
+            logger.debug(menuName + "." + buttonId + " has Lore: " + lore);
         }
 
         Material material;
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
index e7b3ba8..ad828f2 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/Broadcast.java
@@ -30,7 +30,7 @@ public static void startBroadcastTimer(BukkitScheduler scheduler) {
                 if (parentSection.contains(broadcastId, true) && parentSection.isList(broadcastId)) {
                     for (String message : parentSection.getStringList(broadcastId)) {
                         for (Player player : Bukkit.getOnlinePlayers()) {
-                            player.sendMessage(ChatColor.translateAlternateColorCodes('&', PlaceholderUtils.setPlaceholders(player, message)));
+                            player.sendMessage(PlaceholderUtils.setPlaceholders(player, message));
                         }
                     }
                 } else {
diff --git a/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java b/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
index 99b98e3..d98ec0d 100644
--- a/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
+++ b/src/main/java/dev/projectg/geyserhub/module/message/MessageJoin.java
@@ -3,7 +3,6 @@
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.utils.PlaceholderUtils;
-import org.bukkit.ChatColor;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
@@ -21,7 +20,7 @@ public void onJoin(PlayerJoinEvent e) {
         List<String> messages = config.getStringList("Join-Message.Messages");
 
         for (String message : messages) {
-            player.sendMessage(ChatColor.translateAlternateColorCodes('&', PlaceholderUtils.setPlaceholders(player, message)));
+            player.sendMessage(PlaceholderUtils.setPlaceholders(player, message));
         }
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
index 4a07f3f..fa2a26e 100644
--- a/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
+++ b/src/main/java/dev/projectg/geyserhub/module/scoreboard/ScoreboardManager.java
@@ -4,7 +4,6 @@
 import dev.projectg.geyserhub.config.ConfigId;
 import dev.projectg.geyserhub.utils.PlaceholderUtils;
 import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
 import org.bukkit.scoreboard.*;
@@ -37,7 +36,7 @@ public static void createScoreboard(Player player) {
 
         for (int index = 0; index < limit; index++) {
             String formattedLine = PlaceholderUtils.setPlaceholders(player, text.get(index));
-            Score score = objective.getScore(ChatColor.translateAlternateColorCodes('&', formattedLine));
+            Score score = objective.getScore(formattedLine);
             score.setScore(limit - index);
         }
         player.setScoreboard(board);
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 0839f1a..e60d611 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -7,8 +7,8 @@ Enable-Debug: false
 Join-Message:
   Enable: true
   Messages:
-    - "&fWelcome &6%player_name%&f on geyser network!"
-    - "&bHave a great time playing on our network!"
+    - "§fWelcome §6%player_name%§f on geyser network!"
+    - "§bHave a great time playing on our network!"
 
 Join-Teleporter:
   Enable: false
@@ -21,12 +21,12 @@ Scoreboard:
   Refresh-rate: 7
   Title: "Welcome to GeyserServer"
   Lines:
-    - "&f+----------------------+"
-    - "&f+&b          GeyserLobby      &f+"
-    - "&f+&b      Plugin provided by &f   +"
-    - "&f+&b            ProjectG.        &f +"
-    - "&f&f+----------------------+"
-    - "&a  *  &6https://projectg.dev  &a*"
+    - "§f+----------------------+"
+    - "§f+§b          GeyserLobby      §f+"
+    - "§f+§b      Plugin provided by §f   +"
+    - "§f+§b            ProjectG.        §f +"
+    - "§f§f+----------------------+"
+    - "§a  *  §6https://projectg.dev  §a*"
 
 
 Broadcasts:
@@ -35,12 +35,12 @@ Broadcasts:
   Interval: 3600
   Messages:
     Message1:
-      - '&6 -*-*-  Welcome to GeyserNetwork  -*-*- '
+      - '§6 -*-*-  Welcome to GeyserNetwork  -*-*- '
     Message2:
-      - '&6*----------------*----------------* '
-      - '&b -*-*-*- GeyserHub -*-*-*-'
-      - '&b -*-*- Made by ProjectG -*-*-'
-      - '&6*----------------*----------------* '
+      - '§6*----------------*----------------* '
+      - '§b -*-*-*- GeyserHub -*-*-*-'
+      - '§b -*-*- Made by ProjectG -*-*-'
+      - '§6*----------------*----------------* '
 
 World-settings:
   disable-hunger-loss: true
diff --git a/src/main/resources/selector.yml b/src/main/resources/selector.yml
index 5d3dcb6..b453360 100644
--- a/src/main/resources/selector.yml
+++ b/src/main/resources/selector.yml
@@ -1,7 +1,7 @@
 # Item to access the default menu
 Selector-Item:
   Material: COMPASS
-  Name: "&6Server Selector"
+  Name: "§6Server Selector"
   Lore:
     - "Right click me!"
   Slot: 4
@@ -22,14 +22,14 @@ Java-Selector:
 
   Menus:
     default:
-      Title: "Server Selector"
+      Title: "§6Server Selector"
       Size: 9
       Buttons:
         2:
           Display-Name: "Lobby"
           Material: DIAMOND
           Lore:
-            - "&2Online players: %bungee_lobby%"
+            - "§2Online players: %bungee_lobby%"
           Right-Click:
             Server: "lobby"
             Commands:
@@ -39,12 +39,12 @@ Java-Selector:
             Commands:
               - "tell %player_name% Sending you to the lobby in a left click fashion..."
         4:
-          Display-Name: "Minigames"
+          Display-Name: "§6Minigames"
           Material: GRASS
           Lore:
             - "Currently Available:"
-            - "Spleef"
-            - "Hide & Seek"
+            - "§fSpleef"
+            - "§fHide § Seek"
           Any-Click:
             Commands:
               - "player; ghub form minigames"
@@ -52,7 +52,7 @@ Java-Selector:
           Display-Name: "Survival"
           Material: EMERALD
           Lore:
-            - "&2online players: %bungee_survival%"
+            - "§2online players: %bungee_survival%"
           Any-Click:
             Server: "survival"
             Commands:
@@ -69,10 +69,10 @@ Java-Selector:
           Any-Click:
             Server: "spleef"
         3:
-          Display-Name: "Hide & Seek"
+          Display-Name: "Hide § Seek"
           Material: DIRT
           Lore:
-            - "Play Hide & Seek!"
+            - "Play Hide § Seek!"
           Any-Click:
             Server: "hideseek"
 

From 87152f85917cdc11a0c954819e2c4b1280db7789 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Fri, 2 Jul 2021 00:57:47 -0400
Subject: [PATCH 72/74] update default config

---
 src/main/resources/selector.yml | 36 ++++++++++++++++++---------------
 1 file changed, 20 insertions(+), 16 deletions(-)

diff --git a/src/main/resources/selector.yml b/src/main/resources/selector.yml
index b453360..fd752ca 100644
--- a/src/main/resources/selector.yml
+++ b/src/main/resources/selector.yml
@@ -22,22 +22,29 @@ Java-Selector:
 
   Menus:
     default:
-      Title: "§6Server Selector"
+      # The title of the inventory that the player sees
+      Title: "§0Server Selector"
+      # The size of the inventory. Must be greater by at least one than the highest button number. Must be 5, or a multiple of 9 (54 or lower).
       Size: 9
       Buttons:
+        # The slot of the button in the inventory
         2:
-          Display-Name: "Lobby"
+          Display-Name: "§6Lobby"
+          # The material of the item. Must exist here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html
           Material: DIAMOND
+          # The lore for the item. Can be removed.
           Lore:
             - "§2Online players: %bungee_lobby%"
+          # You can specify different functionality for different clicks.
           Right-Click:
-            Server: "lobby"
+            # Both Server and Commands are optional; they can be removed. Commands are run before the player is moved to the new server.
             Commands:
-              - "tell %player_name% Sending you to the lobby in a right click fashion..."
-          Left-Click:
+              - "console; tell %player_name% Sending you to the lobby in a right click fashion..."
             Server: "lobby"
+          Left-Click:
             Commands:
-              - "tell %player_name% Sending you to the lobby in a left click fashion..."
+              - "console; tell %player_name% Sending you to the lobby in a left click fashion..."
+            Server: "lobby"
         4:
           Display-Name: "§6Minigames"
           Material: GRASS
@@ -49,30 +56,27 @@ Java-Selector:
             Commands:
               - "player; ghub form minigames"
         6:
-          Display-Name: "Survival"
+          Display-Name: "§6Survival"
           Material: EMERALD
           Lore:
             - "§2online players: %bungee_survival%"
+          # You can also only use "Any-Click" to specify identical behaviour for both left and right clicks.
           Any-Click:
-            Server: "survival"
             Commands:
-              - "tell %player_name% Sending you to Survival..."
+              - "console; tell %player_name% Sending you to Survival..."
+            Server: "survival"
     minigames:
-      Title: "Minigames"
+      Title: "§0Minigames"
       Size: 5
       Buttons:
         1:
-          Display-Name: "Spleef"
+          Display-Name: "§6Spleef"
           Material: SNOWBALL
-          Lore:
-            - "Play Spleef!"
           Any-Click:
             Server: "spleef"
         3:
-          Display-Name: "Hide § Seek"
+          Display-Name: "§6Hide § Seek"
           Material: DIRT
-          Lore:
-            - "Play Hide § Seek!"
           Any-Click:
             Server: "hideseek"
 

From c3a110556f8deadadaa3668f1fe3f0265f83c6ca Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Fri, 2 Jul 2021 01:31:35 -0400
Subject: [PATCH 73/74] reload access item

---
 .../geyserhub/module/menu/AccessItem.java     | 25 ++++++++++++++-----
 .../module/menu/CommonMenuListeners.java      |  5 ++--
 2 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
index 2163ee0..5b32fe0 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/AccessItem.java
@@ -4,8 +4,9 @@
 import dev.projectg.geyserhub.GeyserHubMain;
 import dev.projectg.geyserhub.SelectorLogger;
 import dev.projectg.geyserhub.config.ConfigId;
+import dev.projectg.geyserhub.reloadable.Reloadable;
+import dev.projectg.geyserhub.reloadable.ReloadableRegistry;
 import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
 import org.bukkit.Material;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.inventory.ItemStack;
@@ -15,12 +16,26 @@
 import java.util.List;
 import java.util.Objects;
 
-public class AccessItem {
+public class AccessItem implements Reloadable {
 
     private static final String NON_LEGACY_MATERIAL_VERSIONS = "(1\\.14\\S*)|(1\\.15\\S*|1\\.16\\S*|1\\.17\\S*|1\\.18\\S*)";
 
-    private static final ItemStack ACCESS_ITEM;
+    private static ItemStack ACCESS_ITEM;
     static {
+        new AccessItem();
+    }
+
+    public static ItemStack getItem() {
+        return ACCESS_ITEM;
+    }
+
+    public AccessItem() {
+        reload();
+        ReloadableRegistry.registerReloadable(this);
+    }
+
+    @Override
+    public boolean reload() {
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
 
         // Get the material
@@ -79,9 +94,7 @@ public class AccessItem {
         item.setItemMeta(meta);
         ACCESS_ITEM = item;
         SelectorLogger.getLogger().debug("Created and set the access item from the configuration.");
-    }
 
-    public static ItemStack getItem() {
-        return ACCESS_ITEM;
+        return true;
     }
 }
diff --git a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
index 0541b55..0e48e64 100644
--- a/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
+++ b/src/main/java/dev/projectg/geyserhub/module/menu/CommonMenuListeners.java
@@ -66,13 +66,14 @@ public void onInteract(PlayerInteractEvent event) { // open the menu through the
 
     @EventHandler
     public void onInventoryClick(InventoryClickEvent event) { // keep the access item in place
+        // todo: don't allow duplication for creative players
         FileConfiguration config = GeyserHubMain.getInstance().getConfigManager().getFileConfiguration(ConfigId.SELECTOR);
         if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) {
             return;
         }
         if (!config.getBoolean("Selector-Item.Allow-Move") && event.getCurrentItem().isSimilar(AccessItem.getItem())) {
-                event.setCancelled(true);
-            }
+            event.setCancelled(true);
+        }
     }
 
     @EventHandler

From a87afef3f6f72310a12cbf52f8aaa3e79e8d9556 Mon Sep 17 00:00:00 2001
From: Konicai <71294714+Konicai@users.noreply.github.com>
Date: Fri, 2 Jul 2021 01:50:04 -0400
Subject: [PATCH 74/74] final thing

---
 .../java/dev/projectg/geyserhub/command/GeyserHubCommand.java  | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
index 3bfd1ff..21f4cc9 100644
--- a/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
+++ b/src/main/java/dev/projectg/geyserhub/command/GeyserHubCommand.java
@@ -102,7 +102,6 @@ public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command
 
     private void sendHelp(CommandSender commandSender) {
         // todo: only show players with the given permissions certain entries? not sure if it can be integrated any way into spigot command completions
-        // todo: check if these are sent on consecutive lines or the same one :(
         commandSender.sendMessage(HELP);
     }
 
@@ -112,7 +111,7 @@ private void sendHelp(CommandSender commandSender) {
      * @param formName the form name to send
      */
     private void sendForm(@Nonnull CommandSender commandSender, @Nonnull String formName) {
-
+        // todo: same code is in MenuUtils
         if (commandSender instanceof Player) {
             Player player = (Player) commandSender;
             if (FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId())) {