From 29a7fa551a970e62e6d1b7d96efd121681f07e2d Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Sat, 23 Dec 2023 08:36:27 +0900
Subject: [PATCH 01/22] WIP - add GUI for teams.

---
 .../island/team/IslandTeamCommand.java        | 255 ++++++++++++++----
 .../api/panels/reader/ItemTemplateRecord.java |  31 +--
 .../panels/reader/PanelTemplateRecord.java    |   1 -
 .../api/panels/reader/TemplateReader.java     |   6 +
 src/main/resources/locales/en-US.yml          |  12 +-
 src/main/resources/panels/team_panel.yml      |  99 +++++++
 6 files changed, 329 insertions(+), 75 deletions(-)
 create mode 100644 src/main/resources/panels/team_panel.yml

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index b8a12affe..d80eb1016 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -1,7 +1,10 @@
 package world.bentobox.bentobox.api.commands.island.team;
 
+import java.io.File;
 import java.time.Duration;
 import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -9,14 +12,21 @@
 import java.util.UUID;
 
 import org.bukkit.Bukkit;
+import org.bukkit.Material;
 import org.bukkit.OfflinePlayer;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 
+import world.bentobox.bentobox.BentoBox;
 import world.bentobox.bentobox.api.commands.CompositeCommand;
 import world.bentobox.bentobox.api.events.IslandBaseEvent;
 import world.bentobox.bentobox.api.events.team.TeamEvent;
 import world.bentobox.bentobox.api.localization.TextVariables;
+import world.bentobox.bentobox.api.panels.PanelItem;
+import world.bentobox.bentobox.api.panels.TemplatedPanel;
+import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
+import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder;
+import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord;
 import world.bentobox.bentobox.api.user.User;
 import world.bentobox.bentobox.database.objects.Island;
 import world.bentobox.bentobox.managers.RanksManager;
@@ -24,12 +34,22 @@
 
 public class IslandTeamCommand extends CompositeCommand {
 
+    /**
+     * List of ranks that we will loop through in order
+     */
+    private static final List<Integer> RANKS = List.of(RanksManager.OWNER_RANK, RanksManager.SUB_OWNER_RANK,
+            RanksManager.MEMBER_RANK, RanksManager.TRUSTED_RANK, RanksManager.COOP_RANK);
+
     /**
      * Invited list. Key is the invited party, value is the invite.
      * @since 1.8.0
      */
     private final Map<UUID, Invite> inviteMap;
 
+    private User user;
+
+    private Island island;
+
     public IslandTeamCommand(CompositeCommand parent) {
         super(parent, "team");
         inviteMap = new HashMap<>();
@@ -57,12 +77,16 @@ public void setup() {
         }
         new IslandTeamPromoteCommand(this, "promote");
         new IslandTeamPromoteCommand(this, "demote");
+
+        // Panels
+        getPlugin().saveResource("panels/team_panel.yml", false);
     }
 
     @Override
     public boolean execute(User user, String label, List<String> args) {
+        this.user = user;
         // Player issuing the command must have an island
-        Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
+        island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
         if (island == null) {
             user.sendMessage("general.errors.no-island");
             return false;
@@ -85,85 +109,216 @@ public boolean execute(User user, String label, List<String> args) {
             }
         }
         // Show members of island
-        showMembers(island, user);
+        showMembers().forEach(user::sendRawMessage);
+        build();
         return true;
     }
 
-    private void showMembers(Island island, User user) {
+    /**
+     * This method builds this GUI.
+     */
+    private void build() {
+        // Start building panel.
+        TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();
+        panelBuilder.user(user);
+        panelBuilder.world(user.getWorld());
+
+        panelBuilder.template("team_panel", new File(getPlugin().getDataFolder(), "panels"));
+
+        panelBuilder.parameters("[name]", user.getName(), "[display_name]", user.getDisplayName());
+
+        panelBuilder.registerTypeBuilder("STATUS", this::createStatusButton);
+        panelBuilder.registerTypeBuilder("MEMBER", this::createMemberButton);
+        //panelBuilder.registerTypeBuilder("INVITE", this::createInviteButton);
+        //panelBuilder.registerTypeBuilder("KICK", this::createKickButton);
+
+        // Register unknown type builder.
+        panelBuilder.build();
+    }
+
+    /**
+     * Create status button panel item.
+     *
+     * @param template the template
+     * @param slot     the slot
+     * @return the panel item
+     */
+    private PanelItem createStatusButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
+        PanelItemBuilder builder = new PanelItemBuilder();
+        // Player issuing the command must have an island
+        Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
+        if (island == null) {
+            return builder.icon(Material.BARRIER).name(user.getTranslation("general.errors.no-island")).build();
+        }
+
+        return builder.icon(user.getName()).name(user.getTranslation("commands.island.team.gui.buttons.status.name"))
+                .description(showMembers()).build();
+    }
+
+    /**
+     * Create member button panel item.
+     *
+     * @param template the template
+     * @param slot     the slot
+     * @return the panel item
+     */
+    private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
+        PanelItemBuilder builder = new PanelItemBuilder();
+        // Player issuing the command must have an island
+        Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
+        if (island == null) {
+            return builder.icon(Material.BARRIER).name(user.getTranslation("general.errors.no-island")).build();
+        }
+        if (slot.slot() == 0 && island.getOwner() != null) {
+            // Owner
+            User owner = User.getInstance(island.getOwner());
+            return builder.icon(owner.getName())
+                    .name(owner.getDisplayName())
+                    .description(user.getTranslation("commands.island.team.info.rank-layout.owner", TextVariables.RANK,
+                            user.getTranslation(RanksManager.OWNER_RANK_REF)))
+                    .build();
+        }
+        long subOwnerCount = island.getMemberSet(RanksManager.SUB_OWNER_RANK, false).stream().count();
+        long memberCount = island.getMemberSet(RanksManager.MEMBER_RANK, false).stream().count();
+        long coopCount = island.getMemberSet(RanksManager.COOP_RANK, false).stream().count();
+        long trustedCount = island.getMemberSet(RanksManager.TRUSTED_RANK, false).stream().count();
+        BentoBox.getInstance().logDebug("Slot = " + slot.slot());
+
+        if (slot.slot() > 0 && slot.slot() < subOwnerCount + 1) {
+            // Show sub owners
+            PanelItem item = getMemberButton(RanksManager.SUB_OWNER_RANK_REF, RanksManager.SUB_OWNER_RANK,
+                    subOwnerCount, slot.slot());
+            if (item != null) {
+                return item;
+            }
+
+        }
+        if (slot.slot() > subOwnerCount && slot.slot() < subOwnerCount + memberCount + 1) {
+            // Show members 
+            PanelItem item = getMemberButton(RanksManager.MEMBER_RANK_REF, RanksManager.MEMBER_RANK, memberCount,
+                    slot.slot());
+            if (item != null) {
+                return item;
+            }
+        }
+        if (slot.slot() > subOwnerCount + memberCount && slot.slot() < subOwnerCount + memberCount + trustedCount + 1) {
+            // Show trusted
+            PanelItem item = getMemberButton(RanksManager.TRUSTED_RANK_REF, RanksManager.TRUSTED_RANK, trustedCount,
+                    slot.slot());
+            if (item != null) {
+                return item;
+            }
+
+        }
+        if (slot.slot() > subOwnerCount + memberCount + trustedCount
+                && slot.slot() < subOwnerCount + memberCount + trustedCount + coopCount + 1) {
+            // Show coops
+            PanelItem item = getMemberButton(RanksManager.COOP_RANK_REF, RanksManager.COOP_RANK, coopCount,
+                    slot.slot());
+            if (item != null) {
+                return item;
+            }
+
+        }
+        return builder.icon(Material.BLACK_STAINED_GLASS_PANE).name("&b&r").build();
+    }
+
+    private PanelItem getMemberButton(String ref, int rank, long count, int slot) {
+        BentoBox.getInstance().logDebug(ref + " " + rank + " count = " + count + " slot = " + slot);
+        User player = island.getMemberSet(rank, false).stream().sorted().skip(slot - 1).limit(1L)
+                .map(User::getInstance).findFirst().orElse(null);
+        if (player != null) {
+            return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName())
+                    .description(user.getTranslation("commands.island.team.info.rank-layout.generic",
+                            TextVariables.RANK, user.getTranslation(ref), TextVariables.NUMBER, String.valueOf(count)))
+                    .build();
+        } else {
+            BentoBox.getInstance().logDebug("no player found");
+        }
+        return null;
+    }
+
+    private List<String> showMembers() {
+        List<String> message = new ArrayList<>();
         // Gather online members
-        long count = island.getMemberSet(RanksManager.MEMBER_RANK).stream()
+        long onlineMemberCount = island.getMemberSet(RanksManager.MEMBER_RANK).stream()
                 .filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName()))
                 .count();
 
-        // List of ranks that we will loop through
-        Integer[] ranks = new Integer[] { RanksManager.OWNER_RANK, RanksManager.SUB_OWNER_RANK,
-                RanksManager.MEMBER_RANK, RanksManager.TRUSTED_RANK, RanksManager.COOP_RANK };
-
         // Show header:
-        user.sendMessage("commands.island.team.info.header", "[max]",
+        message.add(user.getTranslation("commands.island.team.info.header", "[max]",
                 String.valueOf(getIslands().getMaxMembers(island, RanksManager.MEMBER_RANK)), "[total]",
-                String.valueOf(island.getMemberSet().size()), "[online]", String.valueOf(count));
+                String.valueOf(island.getMemberSet().size()), "[online]", String.valueOf(onlineMemberCount)));
 
         // We now need to get all online "members" of the island - incl. Trusted and coop
         List<UUID> onlineMembers = island.getMemberSet(RanksManager.COOP_RANK).stream()
                 .filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName()))
                 .toList();
 
-        for (int rank : ranks) {
+        for (int rank : RANKS) {
             Set<UUID> players = island.getMemberSet(rank, false);
             if (!players.isEmpty()) {
                 if (rank == RanksManager.OWNER_RANK) {
                     // Slightly special handling for the owner rank
-                    user.sendMessage("commands.island.team.info.rank-layout.owner", TextVariables.RANK,
-                            user.getTranslation(RanksManager.OWNER_RANK_REF));
+                    message.add(user.getTranslation("commands.island.team.info.rank-layout.owner", TextVariables.RANK,
+                            user.getTranslation(RanksManager.OWNER_RANK_REF)));
                 } else {
-                    user.sendMessage("commands.island.team.info.rank-layout.generic", TextVariables.RANK,
+                    message.add(user.getTranslation("commands.island.team.info.rank-layout.generic", TextVariables.RANK,
                             user.getTranslation(getPlugin().getRanksManager().getRank(rank)), TextVariables.NUMBER,
-                            String.valueOf(island.getMemberSet(rank, false).size()));
+                            String.valueOf(island.getMemberSet(rank, false).size())));
                 }
-                displayOnOffline(user, rank, island, onlineMembers);
+                message.addAll(displayOnOffline(user, rank, island, onlineMembers));
             }
         }
+        return message;
     }
 
-    private void displayOnOffline(User user, int rank, Island island, List<UUID> onlineMembers) {
+    private List<String> displayOnOffline(User user, int rank, Island island, List<UUID> onlineMembers) {
+        List<String> message = new ArrayList<>();
         for (UUID member : island.getMemberSet(rank, false)) {
-            OfflinePlayer offlineMember = Bukkit.getOfflinePlayer(member);
-            if (onlineMembers.contains(member)) {
-                // the player is online
-                user.sendMessage("commands.island.team.info.member-layout.online", TextVariables.NAME,
-                        offlineMember.getName());
-            } else {
-                // A bit of handling for the last joined date
-                Instant lastJoined = Instant.ofEpochMilli(offlineMember.getLastPlayed());
-                Instant now = Instant.now();
-
-                Duration duration = Duration.between(lastJoined, now);
-                String lastSeen;
-                final String reference = "commands.island.team.info.last-seen.layout";
-                if (duration.toMinutes() < 60L) {
-                    lastSeen = user.getTranslation(reference, TextVariables.NUMBER,
-                            String.valueOf(duration.toMinutes()), TextVariables.UNIT,
-                            user.getTranslation("commands.island.team.info.last-seen.minutes"));
-                } else if (duration.toHours() < 24L) {
-                    lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toHours()),
-                            TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.hours"));
-                } else {
-                    lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toDays()),
-                            TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.days"));
-                }
+            message.add(getMemberStatus(user, member, onlineMembers.contains(member)));
 
-                if (island.getMemberSet(RanksManager.MEMBER_RANK, true).contains(member)) {
-                    user.sendMessage("commands.island.team.info.member-layout.offline", TextVariables.NAME,
-                            offlineMember.getName(), "[last_seen]", lastSeen);
-                } else {
-                    // This will prevent anyone that is trusted or below to not have a last-seen status
-                    user.sendMessage("commands.island.team.info.member-layout.offline-not-last-seen",
-                            TextVariables.NAME, offlineMember.getName());
-                }
-            }
         }
+        return message;
+    }
 
+    private String getMemberStatus(User user2, UUID member, boolean online) {
+        OfflinePlayer offlineMember = Bukkit.getOfflinePlayer(member);
+        if (online) {
+            return user.getTranslation("commands.island.team.info.member-layout.online", TextVariables.NAME,
+                    offlineMember.getName());
+        } else {
+            return offlinePlayerStatus(user, offlineMember);
+        }
+    }
+
+    private String offlinePlayerStatus(User user2, OfflinePlayer offlineMember) {
+        // A bit of handling for the last joined date
+        Instant lastJoined = Instant.ofEpochMilli(offlineMember.getLastPlayed());
+        Instant now = Instant.now();
+
+        Duration duration = Duration.between(lastJoined, now);
+        String lastSeen;
+        final String reference = "commands.island.team.info.last-seen.layout";
+        if (duration.toMinutes() < 60L) {
+            lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toMinutes()),
+                    TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.minutes"));
+        } else if (duration.toHours() < 24L) {
+            lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toHours()),
+                    TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.hours"));
+        } else {
+            lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toDays()),
+                    TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.days"));
+        }
+
+        if (island.getMemberSet(RanksManager.MEMBER_RANK, true).contains(offlineMember.getUniqueId())) {
+            return user.getTranslation("commands.island.team.info.member-layout.offline", TextVariables.NAME,
+                    offlineMember.getName(), "[last_seen]", lastSeen);
+        } else {
+            // This will prevent anyone that is trusted or below to not have a last-seen status
+            return user.getTranslation("commands.island.team.info.member-layout.offline-not-last-seen",
+                    TextVariables.NAME, offlineMember.getName());
+        }
     }
 
     private boolean fireEvent(User user, Island island) {
diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java
index c1c157124..b59c5185a 100644
--- a/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java
+++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java
@@ -3,10 +3,8 @@
 // Copyright - 2021
 //
 
-
 package world.bentobox.bentobox.api.panels.reader;
 
-
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -17,8 +15,6 @@
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 
-import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecords;
-
 /**
  * This Record contains all necessary information about Item Template that can be used to craft panel item.
  *
@@ -31,13 +27,10 @@
  *
  * @since 1.17.3
  */
-public record ItemTemplateRecord(@Nullable ItemStack icon,
-        @Nullable String title,
-        @Nullable String description,
-        @NonNull List<ActionRecords> actions,
-        @NonNull Map<String, Object> dataMap,
-        @Nullable ItemTemplateRecord fallback)
-{
+public record ItemTemplateRecord(@Nullable ItemStack icon, @Nullable String title, @Nullable String description,
+        @NonNull List<ActionRecords> actions, @NonNull Map<String, Object> dataMap,
+        @Nullable ItemTemplateRecord fallback) {
+
     /**
      * Instantiates a new Item template record without actions and data map.
      *
@@ -46,39 +39,32 @@ public record ItemTemplateRecord(@Nullable ItemStack icon,
      * @param description the description
      * @param fallback the fallback
      */
-    public ItemTemplateRecord(ItemStack icon, String title, String description, ItemTemplateRecord fallback)
-    {
+    public ItemTemplateRecord(ItemStack icon, String title, String description, ItemTemplateRecord fallback) {
         this(icon, title, description, new ArrayList<>(6), new HashMap<>(0), fallback);
     }
 
-
     /**
      * This method adds given object associated with key into data map.
      * @param key Key value of object.
      * @param data Data that is associated with a key.
      */
-    public void addData(String key, Object data)
-    {
+    public void addData(String key, Object data) {
         this.dataMap.put(key, data);
     }
 
-
     /**
      * Add action to the actions list.
      *
      * @param actionData the action data
      */
-    public void addAction(ActionRecords actionData)
-    {
+    public void addAction(ActionRecords actionData) {
         this.actions.add(actionData);
     }
 
-
     // ---------------------------------------------------------------------
     // Section: Classes
     // ---------------------------------------------------------------------
 
-
     /**
      * The Action Records holds data about each action.
      *
@@ -87,5 +73,6 @@ public void addAction(ActionRecords actionData)
      * @param content the content of the action
      * @param tooltip the tooltip of action
      */
-    public record ActionRecords(ClickType clickType, String actionType, String content, String tooltip) {}
+    public record ActionRecords(ClickType clickType, String actionType, String content, String tooltip) {
+    }
 }
diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/PanelTemplateRecord.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/PanelTemplateRecord.java
index 18d5506f1..f260fb564 100644
--- a/src/main/java/world/bentobox/bentobox/api/panels/reader/PanelTemplateRecord.java
+++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/PanelTemplateRecord.java
@@ -15,7 +15,6 @@
 import org.eclipse.jdt.annotation.Nullable;
 
 import world.bentobox.bentobox.api.panels.Panel;
-import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem;
 
 /**
  * This is template object for the panel reader. It contains data that can exist in the panel.
diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java
index 9d8158b1b..524f0f260 100644
--- a/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java
+++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java
@@ -22,6 +22,7 @@
 
 import com.google.common.base.Enums;
 
+import world.bentobox.bentobox.BentoBox;
 import world.bentobox.bentobox.api.panels.Panel;
 import world.bentobox.bentobox.util.ItemParser;
 
@@ -83,6 +84,7 @@ public static PanelTemplateRecord readTemplatePanel(@NonNull String panelName, @
     {
         if (!panelLocation.exists())
         {
+            BentoBox.getInstance().logError("Panel Template reader: Folder does not exist");
             // Return null because folder does not exist.
             return null;
         }
@@ -91,6 +93,7 @@ public static PanelTemplateRecord readTemplatePanel(@NonNull String panelName, @
 
         if (!file.exists())
         {
+            BentoBox.getInstance().logError(file.getAbsolutePath() + " does not exist for panel template");
             // Return as file does not exist.
             return null;
         }
@@ -117,6 +120,8 @@ public static PanelTemplateRecord readTemplatePanel(@NonNull String panelName, @
         }
         catch (IOException | InvalidConfigurationException e)
         {
+            BentoBox.getInstance().logError("Error loading template");
+            BentoBox.getInstance().logStacktrace(e);
             rec = null;
         }
 
@@ -133,6 +138,7 @@ private static PanelTemplateRecord readPanelTemplate(@Nullable ConfigurationSect
     {
         if (configurationSection == null)
         {
+            BentoBox.getInstance().logError("No configuration section!");
             // No data to return.
             return null;
         }
diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml
index 07804afcf..0440ff3f2 100644
--- a/src/main/resources/locales/en-US.yml
+++ b/src/main/resources/locales/en-US.yml
@@ -108,8 +108,7 @@ commands:
       status:
         description: displays the status of the purge
         status: '&b [purged] &a islands purged out of &b [purgeable] &7(&b[percentage]
-          %&7)&a.'
-    
+          %&7)&a.' 
     team:
       description: manage teams
       add:
@@ -613,6 +612,15 @@ commands:
       success: '&a Successfully reset your island name.'
     team:
       description: manage your team
+      gui:
+        titles:
+          team-panel: Team Management
+        buttons:
+          status:
+            name: Status
+            description: The status of the team
+        tips:
+          click-to-view: Click to view
       info:
         description: display detailed info about your team
         member-layout:
diff --git a/src/main/resources/panels/team_panel.yml b/src/main/resources/panels/team_panel.yml
new file mode 100644
index 000000000..eeb39e748
--- /dev/null
+++ b/src/main/resources/panels/team_panel.yml
@@ -0,0 +1,99 @@
+# Name of panel used for indentification in the code - must be the same name as the filename.
+team_panel:
+  # Title of the panel shown to the user. This is a reference and the reference will be translatable in the locale file
+  title: commands.island.team.gui.titles.team-panel
+  # The type of panel to show. Options are INVENTORY, HOPPER, DROPPER. INVENTORY is that standard chest inventory and
+  # the others refer to the inventories shown for those items.
+  type: INVENTORY
+  # The background of the panel. These items will be shown if other items are not there. STAINED_GLASS_PANEs give a good effect.
+  background:
+    icon: BLACK_STAINED_GLASS_PANE
+    # Each item may have text applied to it, but usually for background items, nothing is shown.
+    title: "&b&r" # Empty text. This is using the Bukkit chat color coding with &'s. 
+  border:
+    # The border of each panel may be shown as a different item.
+    # It can be used to provide a contrast to items in the panel.
+    icon: BLACK_STAINED_GLASS_PANE
+    title: "&b&r"  # Empty text
+  # This tag indicates which rows in the panel must be shown. The panel will be sized vertically accordingly. This does not include the borders.
+  # This can be a list and rows must be between 1 and 6, if used.
+  force-shown: [1, 2]
+  # The content section contains details of each item/button in the panel. The numbers indicate the rows and then then columns of each item.
+  content:
+    # Row number
+    1:
+      # Column number
+      2:
+        # The data section is a key-value list of data relavent for this button. It is interpreted by the code implemented the panel.
+        # The convention is to specify the type and the panel tab that will open if pressed. These are Enums in the code.
+        data:
+          type: STATUS
+        # Actions cover what happens if the button is clicked or the mouse is moved over it. There can be multiple actions possible for different
+        # click-types.
+        actions:
+          # Each action has an arbitrary descriptive name to define it. 
+          view:
+            # The click-type is the same as the bukkit {@link org.bukkit.event.inventory.ClickType}. UNKNOWN is the default.
+            click-type: UNKNOWN
+            # tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
+            tooltip: commands.island.team.gui.tips.click-to-view
+    2:
+      2: member_button
+      3: member_button
+      4: member_button
+      5: member_button
+      6: member_button
+      7: member_button
+      8: member_button
+    3:
+      2: member_button
+      3: member_button
+      4: member_button
+      5: member_button
+      6: member_button
+      7: member_button
+      8: member_button
+    4:
+      2: member_button
+      3: member_button
+      4: member_button
+      5: member_button
+      6: member_button
+      7: member_button
+      8: member_button
+    5:
+      2: member_button
+      3: member_button
+      4: member_button
+      5: member_button
+      6: member_button
+      7: member_button
+      8: member_button
+    6:
+      2: member_button
+      3: member_button
+      4: member_button
+      5: member_button
+      6: member_button
+      7: member_button
+      8: member_button
+  # This is where reuable buttons are defined.
+  reusable:
+    # This is the name of the button that is referenced
+    member_button:
+      # If the icon for a button is not defined, it defaults to AIR and so effectively will not be shown.
+      # icons are usually not defined if the icon is going to be dynamically set in the panel, e.g. in this case the material will vary
+      #icon: STONE
+      title: commands.island.team.gui.buttons.member.name
+      description: levcommands.island.team.gui.buttons.member.description
+      data:
+        type: MEMBER
+      # Actions cover what happens if the button is clicked or the mouse is moved over it. There can be multiple actions possible for different
+        # click-types.
+      actions:
+        # Each action has an arbitrary descriptive name to define it. 
+        view:
+          # The click-type is the same as the bukkit {@link org.bukkit.event.inventory.ClickType}. UNKNOWN is the default.
+          click-type: RIGHT_CLICK
+          # tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
+          tooltip: commands.island.team.gui.tips.right-click-to-kick
\ No newline at end of file

From f40db47d312917d01993edfd880461494a039b8f Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Sun, 24 Dec 2023 07:38:53 +0900
Subject: [PATCH 02/22] WIP teams GUI

---
 .../island/team/IslandTeamCommand.java        | 40 +++++++++++++------
 1 file changed, 28 insertions(+), 12 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index d80eb1016..340102ce9 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -4,7 +4,6 @@
 import java.time.Duration;
 import java.time.Instant;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -14,6 +13,7 @@
 import org.bukkit.Bukkit;
 import org.bukkit.Material;
 import org.bukkit.OfflinePlayer;
+import org.bukkit.event.inventory.ClickType;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 
@@ -27,6 +27,7 @@
 import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
 import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder;
 import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord;
+import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecords;
 import world.bentobox.bentobox.api.user.User;
 import world.bentobox.bentobox.database.objects.Island;
 import world.bentobox.bentobox.managers.RanksManager;
@@ -171,12 +172,11 @@ private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel
         }
         if (slot.slot() == 0 && island.getOwner() != null) {
             // Owner
-            User owner = User.getInstance(island.getOwner());
-            return builder.icon(owner.getName())
-                    .name(owner.getDisplayName())
-                    .description(user.getTranslation("commands.island.team.info.rank-layout.owner", TextVariables.RANK,
-                            user.getTranslation(RanksManager.OWNER_RANK_REF)))
-                    .build();
+            PanelItem item = getMemberButton(RanksManager.OWNER_RANK_REF, RanksManager.OWNER_RANK, 1, 1,
+                    template.actions());
+            if (item != null) {
+                return item;
+            }
         }
         long subOwnerCount = island.getMemberSet(RanksManager.SUB_OWNER_RANK, false).stream().count();
         long memberCount = island.getMemberSet(RanksManager.MEMBER_RANK, false).stream().count();
@@ -187,7 +187,7 @@ private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel
         if (slot.slot() > 0 && slot.slot() < subOwnerCount + 1) {
             // Show sub owners
             PanelItem item = getMemberButton(RanksManager.SUB_OWNER_RANK_REF, RanksManager.SUB_OWNER_RANK,
-                    subOwnerCount, slot.slot());
+                    subOwnerCount, slot.slot(), template.actions());
             if (item != null) {
                 return item;
             }
@@ -196,7 +196,7 @@ private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel
         if (slot.slot() > subOwnerCount && slot.slot() < subOwnerCount + memberCount + 1) {
             // Show members 
             PanelItem item = getMemberButton(RanksManager.MEMBER_RANK_REF, RanksManager.MEMBER_RANK, memberCount,
-                    slot.slot());
+                    slot.slot(), template.actions());
             if (item != null) {
                 return item;
             }
@@ -204,7 +204,7 @@ private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel
         if (slot.slot() > subOwnerCount + memberCount && slot.slot() < subOwnerCount + memberCount + trustedCount + 1) {
             // Show trusted
             PanelItem item = getMemberButton(RanksManager.TRUSTED_RANK_REF, RanksManager.TRUSTED_RANK, trustedCount,
-                    slot.slot());
+                    slot.slot(), template.actions());
             if (item != null) {
                 return item;
             }
@@ -214,7 +214,7 @@ private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel
                 && slot.slot() < subOwnerCount + memberCount + trustedCount + coopCount + 1) {
             // Show coops
             PanelItem item = getMemberButton(RanksManager.COOP_RANK_REF, RanksManager.COOP_RANK, coopCount,
-                    slot.slot());
+                    slot.slot(), template.actions());
             if (item != null) {
                 return item;
             }
@@ -223,15 +223,31 @@ private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel
         return builder.icon(Material.BLACK_STAINED_GLASS_PANE).name("&b&r").build();
     }
 
-    private PanelItem getMemberButton(String ref, int rank, long count, int slot) {
+    private PanelItem getMemberButton(String ref, int rank, long count, int slot, List<ActionRecords> actions) {
         BentoBox.getInstance().logDebug(ref + " " + rank + " count = " + count + " slot = " + slot);
         User player = island.getMemberSet(rank, false).stream().sorted().skip(slot - 1).limit(1L)
                 .map(User::getInstance).findFirst().orElse(null);
         if (player != null) {
+            if (player.isOnline()) {
             return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName())
                     .description(user.getTranslation("commands.island.team.info.rank-layout.generic",
                             TextVariables.RANK, user.getTranslation(ref), TextVariables.NUMBER, String.valueOf(count)))
+                    .clickHandler((panel, user, clickType, i) -> {
+                        BentoBox.getInstance().logDebug("Clicked " + clickType);
+                        actions.forEach(ar -> BentoBox.getInstance().logDebug(ar.content() + " " + ar.clickType()));
+                        return true;
+                    })
                     .build();
+            } else {
+                // Offline player
+                return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName())
+                        .description(offlinePlayerStatus(user, Bukkit.getOfflinePlayer(player.getUniqueId())))
+                        .clickHandler((panel, user, clickType, i) -> {
+                            BentoBox.getInstance().logDebug("Clicked " + clickType);
+                            actions.forEach(ar -> BentoBox.getInstance().logDebug(ar.content() + " " + ar.clickType()));
+                            return true;
+                        }).build();
+            }
         } else {
             BentoBox.getInstance().logDebug("no player found");
         }

From bac0fe4b1703cf04ec0d54294c6c730b23ed3425 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Tue, 26 Dec 2023 16:54:43 +0900
Subject: [PATCH 03/22] Moe WIP on team GUI

---
 .../island/team/IslandTeamCommand.java        | 76 ++++++++++++++-----
 .../api/panels/reader/ItemTemplateRecord.java | 18 ++++-
 src/main/resources/locales/en-US.yml          |  2 +
 src/main/resources/panels/team_panel.yml      | 16 +++-
 4 files changed, 89 insertions(+), 23 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index 340102ce9..a3a9e04b1 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -6,6 +6,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
@@ -22,6 +23,7 @@
 import world.bentobox.bentobox.api.events.IslandBaseEvent;
 import world.bentobox.bentobox.api.events.team.TeamEvent;
 import world.bentobox.bentobox.api.localization.TextVariables;
+import world.bentobox.bentobox.api.panels.Panel;
 import world.bentobox.bentobox.api.panels.PanelItem;
 import world.bentobox.bentobox.api.panels.TemplatedPanel;
 import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
@@ -130,13 +132,33 @@ private void build() {
 
         panelBuilder.registerTypeBuilder("STATUS", this::createStatusButton);
         panelBuilder.registerTypeBuilder("MEMBER", this::createMemberButton);
-        //panelBuilder.registerTypeBuilder("INVITE", this::createInviteButton);
+        panelBuilder.registerTypeBuilder("INVITE", this::createInviteButton);
         //panelBuilder.registerTypeBuilder("KICK", this::createKickButton);
 
         // Register unknown type builder.
         panelBuilder.build();
     }
 
+    /**
+     * Create invite button panel item.
+     *
+     * @param template the template
+     * @param slot     the slot
+     * @return the panel item
+     */
+    private PanelItem createInviteButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
+        PanelItemBuilder builder = new PanelItemBuilder();
+        // Player issuing the command must have an island
+        Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
+        if (island == null) {
+            return builder.icon(Material.BARRIER).name(user.getTranslation("general.errors.no-island")).build();
+        }
+        // The player must be able to invite a player
+
+        return builder.icon(user.getName()).name(user.getTranslation("commands.island.team.gui.buttons.status.name"))
+                .description(showMembers()).build();
+    }
+
     /**
      * Create status button panel item.
      *
@@ -164,11 +186,11 @@ private PanelItem createStatusButton(ItemTemplateRecord template, TemplatedPanel
      * @return the panel item
      */
     private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
-        PanelItemBuilder builder = new PanelItemBuilder();
         // Player issuing the command must have an island
         Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
         if (island == null) {
-            return builder.icon(Material.BARRIER).name(user.getTranslation("general.errors.no-island")).build();
+            return new PanelItemBuilder().icon(Material.BARRIER).name(user.getTranslation("general.errors.no-island"))
+                    .build();
         }
         if (slot.slot() == 0 && island.getOwner() != null) {
             // Owner
@@ -182,7 +204,6 @@ private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel
         long memberCount = island.getMemberSet(RanksManager.MEMBER_RANK, false).stream().count();
         long coopCount = island.getMemberSet(RanksManager.COOP_RANK, false).stream().count();
         long trustedCount = island.getMemberSet(RanksManager.TRUSTED_RANK, false).stream().count();
-        BentoBox.getInstance().logDebug("Slot = " + slot.slot());
 
         if (slot.slot() > 0 && slot.slot() < subOwnerCount + 1) {
             // Show sub owners
@@ -220,11 +241,10 @@ private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel
             }
 
         }
-        return builder.icon(Material.BLACK_STAINED_GLASS_PANE).name("&b&r").build();
+        return new PanelItemBuilder().icon(Material.BLACK_STAINED_GLASS_PANE).name("&b&r").build();
     }
 
     private PanelItem getMemberButton(String ref, int rank, long count, int slot, List<ActionRecords> actions) {
-        BentoBox.getInstance().logDebug(ref + " " + rank + " count = " + count + " slot = " + slot);
         User player = island.getMemberSet(rank, false).stream().sorted().skip(slot - 1).limit(1L)
                 .map(User::getInstance).findFirst().orElse(null);
         if (player != null) {
@@ -232,28 +252,42 @@ private PanelItem getMemberButton(String ref, int rank, long count, int slot, Li
             return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName())
                     .description(user.getTranslation("commands.island.team.info.rank-layout.generic",
                             TextVariables.RANK, user.getTranslation(ref), TextVariables.NUMBER, String.valueOf(count)))
-                    .clickHandler((panel, user, clickType, i) -> {
-                        BentoBox.getInstance().logDebug("Clicked " + clickType);
-                        actions.forEach(ar -> BentoBox.getInstance().logDebug(ar.content() + " " + ar.clickType()));
-                        return true;
-                    })
+                    .clickHandler(
+                            (panel, user, clickType, i) -> clickListener(panel, user, clickType, i, player, actions))
                     .build();
             } else {
                 // Offline player
                 return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName())
                         .description(offlinePlayerStatus(user, Bukkit.getOfflinePlayer(player.getUniqueId())))
-                        .clickHandler((panel, user, clickType, i) -> {
-                            BentoBox.getInstance().logDebug("Clicked " + clickType);
-                            actions.forEach(ar -> BentoBox.getInstance().logDebug(ar.content() + " " + ar.clickType()));
-                            return true;
-                        }).build();
+                        .clickHandler((panel, user, clickType, i) -> clickListener(panel, user, clickType, i, player,
+                                actions))
+                        .build();
             }
-        } else {
-            BentoBox.getInstance().logDebug("no player found");
         }
         return null;
     }
 
+    private boolean clickListener(Panel panel, User user, ClickType clickType, int i, User player,
+            List<ActionRecords> actions) {
+        for (ItemTemplateRecord.ActionRecords action : actions) {
+            if (clickType == action.clickType() || action.clickType() == ClickType.UNKNOWN) {
+                switch (action.actionType().toUpperCase(Locale.ENGLISH)) {
+                case "KICK" -> {
+                    // Kick the player
+                    if (!player.equals(user)) {
+                        this.user.closeInventory();
+                        BentoBox.getInstance()
+                                .logDebug(this.getTopLabel() + " " + this.getLabel() + " kick " + player.getName());
+                        user.performCommand(this.getTopLabel() + " " + this.getLabel() + " kick " + player.getName());
+                        }
+
+                }
+                }
+            }
+        }
+        return true;
+    }
+
     private List<String> showMembers() {
         List<String> message = new ArrayList<>();
         // Gather online members
@@ -308,6 +342,12 @@ private String getMemberStatus(User user2, UUID member, boolean online) {
         }
     }
 
+    /**
+     * Creates text to describe the status of the player
+     * @param user2 user asking to see the status
+     * @param offlineMember member of the team
+     * @return string
+     */
     private String offlinePlayerStatus(User user2, OfflinePlayer offlineMember) {
         // A bit of handling for the last joined date
         Instant lastJoined = Instant.ofEpochMilli(offlineMember.getLastPlayed());
diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java
index b59c5185a..ff7424f69 100644
--- a/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java
+++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java
@@ -73,6 +73,22 @@ public void addAction(ActionRecords actionData) {
      * @param content the content of the action
      * @param tooltip the tooltip of action
      */
-    public record ActionRecords(ClickType clickType, String actionType, String content, String tooltip) {
+    public record ActionRecords(
+            /**
+            * the click type
+            */
+            ClickType clickType,
+            /**
+            * the string that represents action type
+            */
+            String actionType,
+            /**
+             * the content of the action
+             */
+            String content,
+            /**
+             * the tooltip of action
+             */
+            String tooltip) {
     }
 }
diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml
index 0440ff3f2..c19344d20 100644
--- a/src/main/resources/locales/en-US.yml
+++ b/src/main/resources/locales/en-US.yml
@@ -621,6 +621,8 @@ commands:
             description: The status of the team
         tips:
           click-to-view: Click to view
+          right-click-to-kick: Right click to kick player - requires confirmation
+          click-to-invite: Click to invite a team member
       info:
         description: display detailed info about your team
         member-layout:
diff --git a/src/main/resources/panels/team_panel.yml b/src/main/resources/panels/team_panel.yml
index eeb39e748..f95f473c3 100644
--- a/src/main/resources/panels/team_panel.yml
+++ b/src/main/resources/panels/team_panel.yml
@@ -37,6 +37,14 @@ team_panel:
             click-type: UNKNOWN
             # tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
             tooltip: commands.island.team.gui.tips.click-to-view
+      4:
+        # Invite member
+        data:
+          type: INVITE
+        actions:
+          add:
+            click-type: LEFT
+            tooltip: commands.island.team.gui.tips.click-to-invite
     2:
       2: member_button
       3: member_button
@@ -77,7 +85,7 @@ team_panel:
       6: member_button
       7: member_button
       8: member_button
-  # This is where reuable buttons are defined.
+  # This is where reusable buttons are defined.
   reusable:
     # This is the name of the button that is referenced
     member_button:
@@ -85,15 +93,15 @@ team_panel:
       # icons are usually not defined if the icon is going to be dynamically set in the panel, e.g. in this case the material will vary
       #icon: STONE
       title: commands.island.team.gui.buttons.member.name
-      description: levcommands.island.team.gui.buttons.member.description
+      description: commands.island.team.gui.buttons.member.description
       data:
         type: MEMBER
       # Actions cover what happens if the button is clicked or the mouse is moved over it. There can be multiple actions possible for different
         # click-types.
       actions:
         # Each action has an arbitrary descriptive name to define it. 
-        view:
+        kick:
           # The click-type is the same as the bukkit {@link org.bukkit.event.inventory.ClickType}. UNKNOWN is the default.
-          click-type: RIGHT_CLICK
+          click-type: RIGHT
           # tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
           tooltip: commands.island.team.gui.tips.right-click-to-kick
\ No newline at end of file

From 8d9a2597a07206c4efe54cb874c895c5f40ba0fc Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Sat, 30 Dec 2023 12:22:05 +0900
Subject: [PATCH 04/22] Modified file to avoid an NPE.

---
 .../bentobox/api/commands/admin/AdminTeleportCommand.java | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminTeleportCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminTeleportCommand.java
index bb98b9ab4..74dfe764e 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminTeleportCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminTeleportCommand.java
@@ -118,12 +118,10 @@ public boolean execute(User user, String label, List<String> args) {
 
     private Location getSpot(World world) {
         Island island = getIslands().getIsland(world, targetUUID);
-        if (island != null && island.getSpawnPoint(world.getEnvironment()) != null) {
-            // Return the defined spawn point
-            return island.getSpawnPoint(world.getEnvironment());
+        if (island == null) {
+            return null;
         }
-        // Return the default island protection center
-        return island.getProtectionCenter().toVector().toLocation(world);
+        return island.getSpawnPoint(world.getEnvironment()) != null ? island.getSpawnPoint(world.getEnvironment()) : island.getProtectionCenter().toVector().toLocation(world);
     }
 
     @Override

From 511b613324c4e0e09afdedf32aa1cf27a2eea63c Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Sat, 30 Dec 2023 12:46:56 +0900
Subject: [PATCH 05/22] Minor refactor to prevent int to Integer warning.

---
 .../bentobox/blueprints/dataobjects/BlueprintEntity.java       | 3 ++-
 .../java/world/bentobox/bentobox/database/objects/Players.java | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java b/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java
index 6a5927ddb..a06c11932 100644
--- a/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java
+++ b/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java
@@ -78,7 +78,8 @@ public void configureEntity(Entity e) {
         if (e instanceof AbstractHorse horse) {
             if (domestication != null) horse.setDomestication(domestication);
             if (inventory != null) {
-                inventory.forEach(horse.getInventory()::setItem);
+                inventory.forEach((index, item) -> horse.getInventory().setItem(index.intValue(), item));
+
             }
         }
         if (style != null && e instanceof Horse horse) {
diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Players.java b/src/main/java/world/bentobox/bentobox/database/objects/Players.java
index 5e64c9f5d..3cf06d2c0 100644
--- a/src/main/java/world/bentobox/bentobox/database/objects/Players.java
+++ b/src/main/java/world/bentobox/bentobox/database/objects/Players.java
@@ -298,7 +298,7 @@ public void setUniqueId(String uniqueId) {
      * @param world - world
      */
     public void addReset(World world) {
-        resets.merge(world.getName(), 1, Integer::sum);
+        resets.merge(world.getName(), 1, (oldValue, newValue) -> Integer.valueOf(oldValue + newValue));
     }
 
     /**

From 63c7ad832723d8f2152764b2e33dd128b835d1f5 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Sat, 30 Dec 2023 21:42:36 +0900
Subject: [PATCH 06/22] Use latest dependencies for tests to pass.

---
 pom.xml | 46 ++++++++++++++++++++++++++--------------------
 1 file changed, 26 insertions(+), 20 deletions(-)

diff --git a/pom.xml b/pom.xml
index 696d9087e..35d6c136f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -192,7 +192,32 @@
     </repositories>
 
     <dependencies>
-        <!-- Spigot API -->
+        <!-- Mockito (Unit testing) This goes at the top to ensure the dependencies are accurate. -->
+        <!-- This is required for PowerMockito to work and must be placed before it -->
+        <dependency>
+            <groupId>org.javassist</groupId>
+            <artifactId>javassist</artifactId>
+            <version>3.30.2-GA</version>
+       </dependency>
+       <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-module-junit4</artifactId>
+            <version>${powermock.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito2</artifactId>
+            <version>${powermock.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>3.11.1</version>
+            <scope>test</scope>
+        </dependency>
+         <!-- Spigot API -->
         <dependency>
             <groupId>org.spigotmc</groupId>
             <artifactId>spigot-api</artifactId>
@@ -212,25 +237,6 @@
             <artifactId>bstats-bukkit</artifactId>
             <version>${bstats.version}</version>
         </dependency>
-        <!-- Mockito (Unit testing) -->
-        <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-core</artifactId>
-            <version>3.11.1</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.powermock</groupId>
-            <artifactId>powermock-module-junit4</artifactId>
-            <version>${powermock.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.powermock</groupId>
-            <artifactId>powermock-api-mockito2</artifactId>
-            <version>${powermock.version}</version>
-            <scope>test</scope>
-        </dependency>
         <!-- Database -->
         <dependency>
             <groupId>org.mongodb</groupId>

From c6e5a43e57740c890b4149e68d48d3c5f94e6ac1 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Sat, 30 Dec 2023 21:43:02 +0900
Subject: [PATCH 07/22] Move to singleton pattern for RanksManager,

---
 .../world/bentobox/bentobox/BentoBox.java     |   6 +-
 .../commands/admin/AdminGetrankCommand.java   |   4 +-
 .../commands/admin/AdminSetrankCommand.java   |  10 +-
 .../commands/admin/AdminSettingsCommand.java  |   5 +-
 .../api/commands/island/IslandBanCommand.java |   3 +-
 .../commands/island/IslandBanlistCommand.java |   4 +-
 .../island/IslandDeletehomeCommand.java       |   2 +-
 .../commands/island/IslandExpelCommand.java   |   3 +-
 .../island/IslandRenamehomeCommand.java       |   3 +-
 .../island/IslandResetnameCommand.java        |   3 +-
 .../commands/island/IslandSethomeCommand.java |   3 +-
 .../commands/island/IslandSetnameCommand.java |   4 +-
 .../commands/island/IslandUnbanCommand.java   |   4 +-
 .../island/team/IslandTeamCommand.java        |  39 +++++--
 .../island/team/IslandTeamCoopCommand.java    |   2 +-
 .../island/team/IslandTeamInviteCommand.java  |   4 +-
 .../island/team/IslandTeamKickCommand.java    |   2 +-
 .../island/team/IslandTeamPromoteCommand.java |  11 +-
 .../island/team/IslandTeamTrustCommand.java   |   3 +-
 .../island/team/IslandTeamUncoopCommand.java  |   2 +-
 .../island/team/IslandTeamUntrustCommand.java |   2 +-
 .../bentobox/bentobox/api/flags/Flag.java     |   3 +-
 .../api/flags/clicklisteners/CycleClick.java  |  15 ++-
 .../clicklisteners/IslandToggleClick.java     |   3 +-
 .../commands/BentoBoxRankCommand.java         |  12 +-
 .../clicklisteners/CommandCycleClick.java     |   5 +-
 .../CommandRankClickListener.java             |   4 +-
 .../flags/protection/LockAndBanListener.java  |   5 +-
 .../bentobox/lists/GameModePlaceholder.java   |   4 +-
 .../bentobox/managers/IslandsManager.java     |  13 ++-
 .../managers/PlaceholdersManager.java         |   4 +-
 .../bentobox/managers/RanksManager.java       |  25 +++-
 .../bentobox/util/DefaultPasteUtil.java       |   2 +-
 .../bentobox/bentobox/util/IslandInfo.java    |   4 +-
 .../admin/AdminGetrankCommandTest.java        |   5 +-
 .../commands/admin/AdminInfoCommandTest.java  |   7 +-
 .../admin/AdminSetrankCommandTest.java        |   9 +-
 .../admin/AdminSettingsCommandTest.java       |   5 -
 .../island/DefaultPlayerCommandTest.java      |   6 -
 .../commands/island/IslandBanCommandTest.java |   6 +-
 .../island/IslandBanlistCommandTest.java      |   6 +-
 .../island/IslandDeletehomeCommandTest.java   |   7 +-
 .../island/IslandExpelCommandTest.java        |   6 +-
 .../island/IslandInfoCommandTest.java         |   7 +-
 .../island/IslandSetnameCommandTest.java      |   5 -
 .../island/IslandUnbanCommandTest.java        |   6 +-
 .../island/team/IslandTeamCommandTest.java    |   4 -
 .../team/IslandTeamCoopCommandTest.java       |   6 +-
 .../team/IslandTeamInviteCommandTest.java     |   6 +-
 .../team/IslandTeamKickCommandTest.java       |   8 +-
 .../team/IslandTeamPromoteCommandTest.java    |  10 +-
 .../team/IslandTeamTrustCommandTest.java      |   6 +-
 .../team/IslandTeamUncoopCommandTest.java     |   6 +-
 .../team/IslandTeamUntrustCommandTest.java    |   6 +-
 .../bentobox/bentobox/api/flags/FlagTest.java |   5 +-
 .../flags/clicklisteners/CycleClickTest.java  |  24 +---
 .../CommandRankClickListenerTest.java         |  12 +-
 .../lists/GameModePlaceholderTest.java        |  17 +--
 .../managers/RanksManagerBeforeClassTest.java |  57 ++++++----
 .../bentobox/managers/RanksManagerTest.java   | 107 +++++++++++++-----
 60 files changed, 296 insertions(+), 271 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java
index bea18a9c6..6f2a5759e 100644
--- a/src/main/java/world/bentobox/bentobox/BentoBox.java
+++ b/src/main/java/world/bentobox/bentobox/BentoBox.java
@@ -71,7 +71,6 @@ public class BentoBox extends JavaPlugin implements Listener {
     private AddonsManager addonsManager;
     private FlagsManager flagsManager;
     private IslandWorldManager islandWorldManager;
-    private RanksManager ranksManager;
     private BlueprintsManager blueprintsManager;
     private HooksManager hooksManager;
     private PlaceholdersManager placeholdersManager;
@@ -139,7 +138,6 @@ public void onEnable(){
             return;
         }
         islandsManager = new IslandsManager(this);
-        ranksManager = new RanksManager();
 
         // Start head getter
         headGetter = new HeadGetter(this);
@@ -427,9 +425,11 @@ public FlagsManager getFlagsManager() {
 
     /**
      * @return the ranksManager
+     * @deprecated Just use {@code RanksManager.getInstance()}
      */
+    @Deprecated(since = "2.0.0")
     public RanksManager getRanksManager() {
-        return ranksManager;
+        return RanksManager.getInstance();
     }
 
     /**
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminGetrankCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminGetrankCommand.java
index 1c9b10b2e..e6bb28905 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminGetrankCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminGetrankCommand.java
@@ -83,10 +83,10 @@ public boolean canExecute(User user, String label, List<String> args) {
     @Override
     public boolean execute(User user, String label, List<String> args) {
         // Get rank
-        RanksManager rm = getPlugin().getRanksManager();
         User target = User.getInstance(targetUUID);
         int currentRank = island.getRank(target);
-        user.sendMessage("commands.admin.getrank.rank-is", TextVariables.RANK, user.getTranslation(rm.getRank(currentRank)),
+        user.sendMessage("commands.admin.getrank.rank-is", TextVariables.RANK,
+                user.getTranslation(RanksManager.getInstance().getRank(currentRank)),
                 TextVariables.NAME, getPlayers().getName(island.getOwner()));
         return true;
     }
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSetrankCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSetrankCommand.java
index d0ff2d416..71f5cde6b 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSetrankCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSetrankCommand.java
@@ -24,7 +24,6 @@ public class AdminSetrankCommand extends CompositeCommand {
     private int rankValue;
     private @Nullable UUID targetUUID;
     private @Nullable UUID ownerUUID;
-    private RanksManager rm;
 
     public AdminSetrankCommand(CompositeCommand adminCommand) {
         super(adminCommand, "setrank");
@@ -36,7 +35,6 @@ public void setup() {
         setOnlyPlayer(false);
         setParametersHelp("commands.admin.setrank.parameters");
         setDescription("commands.admin.setrank.description");
-        rm = getPlugin().getRanksManager();
     }
 
     @Override
@@ -53,7 +51,7 @@ public boolean canExecute(User user, String label, List<String> args) {
             return false;
         }
         // Get rank
-        rankValue = rm.getRanks().entrySet().stream()
+        rankValue = RanksManager.getInstance().getRanks().entrySet().stream()
                 .filter(r -> user.getTranslation(r.getKey()).equalsIgnoreCase(args.get(1))).findFirst()
                 .map(Map.Entry::getValue).orElse(-999);
         if (rankValue < RanksManager.BANNED_RANK) {
@@ -121,8 +119,8 @@ public boolean execute(User user, String label, List<String> args) {
             ownerName = target.getName();
         }
         user.sendMessage("commands.admin.setrank.rank-set",
-                "[from]", user.getTranslation(rm.getRank(currentRank)),
-                "[to]", user.getTranslation(rm.getRank(rankValue)),
+                "[from]", user.getTranslation(RanksManager.getInstance().getRank(currentRank)), "[to]",
+                user.getTranslation(RanksManager.getInstance().getRank(rankValue)),
                 TextVariables.NAME, ownerName);
         return true;
     }
@@ -136,7 +134,7 @@ public Optional<List<String>> tabComplete(User user, String alias, List<String>
 
         // Return the ranks
         if (args.size() == 3) {
-            return Optional.of(getPlugin().getRanksManager().getRanks()
+            return Optional.of(RanksManager.getInstance().getRanks()
                     .entrySet().stream()
                     .filter(entry -> entry.getValue() > RanksManager.VISITOR_RANK)
                     .map(entry -> user.getTranslation(entry.getKey())).toList());
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommand.java
index b7e450a36..a792c0387 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommand.java
@@ -175,7 +175,7 @@ private boolean checkIslandSetting(User user, List<String> args) {
      * @return true if rank is valid
      */
     private boolean checkRank(User user, String string) {
-        for (Entry<String, Integer> en : getPlugin().getRanksManager().getRanks().entrySet()) {
+        for (Entry<String, Integer> en : RanksManager.getInstance().getRanks().entrySet()) {
             if (en.getValue() > RanksManager.BANNED_RANK && en.getValue() <= RanksManager.OWNER_RANK
                     && string.equalsIgnoreCase(ChatColor.stripColor(user.getTranslation(en.getKey())))) {
                 // We have a winner
@@ -277,8 +277,7 @@ public Optional<List<String>> tabComplete(User user, String alias, List<String>
         } else if (args.size() == 4) {
             // Get flag in previous argument
             options = getPlugin().getFlagsManager().getFlag(args.get(2).toUpperCase(Locale.ENGLISH)).map(f -> switch (f.getType()) {
-            case PROTECTION -> getPlugin().getRanksManager()
-            .getRanks().entrySet().stream()
+            case PROTECTION -> RanksManager.getInstance().getRanks().entrySet().stream()
             .filter(en -> en.getValue() > RanksManager.BANNED_RANK && en.getValue() <= RanksManager.OWNER_RANK)
             .map(Entry::getKey)
             .map(user::getTranslation).toList();
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandBanCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandBanCommand.java
index 8e0465673..403eecc88 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandBanCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandBanCommand.java
@@ -17,6 +17,7 @@
 import world.bentobox.bentobox.api.localization.TextVariables;
 import world.bentobox.bentobox.api.user.User;
 import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.managers.RanksManager;
 import world.bentobox.bentobox.util.Util;
 
 public class IslandBanCommand extends CompositeCommand {
@@ -55,7 +56,7 @@ public boolean canExecute(User user, String label, List<String> args) {
         int rank = island.getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
             user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
-                    user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
         // Get target player
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandBanlistCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandBanlistCommand.java
index 7d19f5c81..754c345bf 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandBanlistCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandBanlistCommand.java
@@ -8,6 +8,7 @@
 import world.bentobox.bentobox.api.localization.TextVariables;
 import world.bentobox.bentobox.api.user.User;
 import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.managers.RanksManager;
 
 public class IslandBanlistCommand extends CompositeCommand {
 
@@ -40,7 +41,8 @@ public boolean canExecute(User user, String label, List<String> args) {
         island = getIslands().getIsland(getWorld(), user.getUniqueId());
         int rank = Objects.requireNonNull(island).getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
-            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
         return true;
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandDeletehomeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandDeletehomeCommand.java
index d2b1ccd15..d11947399 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandDeletehomeCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandDeletehomeCommand.java
@@ -57,7 +57,7 @@ public boolean canExecute(User user, String label, List<String> args) {
         int rank = Objects.requireNonNull(island).getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
             user.sendMessage("general.errors.insufficient-rank",
-                    TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+                    TextVariables.RANK, user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
 
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandExpelCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandExpelCommand.java
index d8759a987..6d9de5fc4 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandExpelCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandExpelCommand.java
@@ -16,6 +16,7 @@
 import world.bentobox.bentobox.api.localization.TextVariables;
 import world.bentobox.bentobox.api.user.User;
 import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.managers.RanksManager;
 import world.bentobox.bentobox.util.Util;
 
 /**
@@ -60,7 +61,7 @@ public boolean canExecute(User user, String label, List<String> args) {
         int rank = Objects.requireNonNull(island).getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
             user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
-                    user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
         // Get target player
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandRenamehomeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandRenamehomeCommand.java
index 591fbbafb..80a06fc30 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandRenamehomeCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandRenamehomeCommand.java
@@ -65,7 +65,8 @@ public boolean canExecute(User user, String label, List<String> args) {
         // check command permission
         int rank = Objects.requireNonNull(island).getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
-            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
 
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetnameCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetnameCommand.java
index 063553214..d797ed458 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetnameCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetnameCommand.java
@@ -7,6 +7,7 @@
 import world.bentobox.bentobox.api.localization.TextVariables;
 import world.bentobox.bentobox.api.user.User;
 import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.managers.RanksManager;
 
 
 /**
@@ -41,7 +42,7 @@ public boolean canExecute(User user, String label, List<String> args)
         int rank = Objects.requireNonNull(island).getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
             user.sendMessage("general.errors.insufficient-rank",
-                TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+                    TextVariables.RANK, user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
 
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSethomeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSethomeCommand.java
index 7b50b3c52..9ccde2194 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSethomeCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSethomeCommand.java
@@ -46,7 +46,8 @@ public boolean canExecute(User user, String label, List<String> args) {
 
         int rank = Objects.requireNonNull(island).getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
-            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
 
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommand.java
index f8b66f38e..d0582e787 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommand.java
@@ -10,6 +10,7 @@
 import world.bentobox.bentobox.api.localization.TextVariables;
 import world.bentobox.bentobox.api.user.User;
 import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.managers.RanksManager;
 
 
 /**
@@ -51,7 +52,8 @@ public boolean canExecute(User user, String label, List<String> args)
         // Check command rank.
         int rank = Objects.requireNonNull(island).getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
-            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
 
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandUnbanCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandUnbanCommand.java
index 041636e0f..dfaab3dd4 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandUnbanCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandUnbanCommand.java
@@ -13,6 +13,7 @@
 import world.bentobox.bentobox.api.localization.TextVariables;
 import world.bentobox.bentobox.api.user.User;
 import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.managers.RanksManager;
 import world.bentobox.bentobox.util.Util;
 
 /**
@@ -54,7 +55,8 @@ public boolean canExecute(User user, String label, List<String> args) {
         Island island = getIslands().getIsland(getWorld(), user);
         int rank = Objects.requireNonNull(island).getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
-            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
         // Get target player
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index a3a9e04b1..d1c9163d0 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -26,6 +26,7 @@
 import world.bentobox.bentobox.api.panels.Panel;
 import world.bentobox.bentobox.api.panels.PanelItem;
 import world.bentobox.bentobox.api.panels.TemplatedPanel;
+import world.bentobox.bentobox.api.panels.TemplatedPanel.ItemSlot;
 import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
 import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder;
 import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord;
@@ -53,6 +54,8 @@ public class IslandTeamCommand extends CompositeCommand {
 
     private Island island;
 
+    private int rank = RanksManager.OWNER_RANK;
+
     public IslandTeamCommand(CompositeCommand parent) {
         super(parent, "team");
         inviteMap = new HashMap<>();
@@ -70,11 +73,11 @@ public void setup() {
         new IslandTeamKickCommand(this);
         new IslandTeamInviteAcceptCommand(this);
         new IslandTeamInviteRejectCommand(this);
-        if (getPlugin().getRanksManager().rankExists(RanksManager.COOP_RANK_REF)) {
+        if (RanksManager.getInstance().rankExists(RanksManager.COOP_RANK_REF)) {
             new IslandTeamCoopCommand(this);
             new IslandTeamUncoopCommand(this);
         }
-        if (getPlugin().getRanksManager().rankExists(RanksManager.TRUSTED_RANK_REF)) {
+        if (RanksManager.getInstance().rankExists(RanksManager.TRUSTED_RANK_REF)) {
             new IslandTeamTrustCommand(this);
             new IslandTeamUntrustCommand(this);
         }
@@ -133,6 +136,7 @@ private void build() {
         panelBuilder.registerTypeBuilder("STATUS", this::createStatusButton);
         panelBuilder.registerTypeBuilder("MEMBER", this::createMemberButton);
         panelBuilder.registerTypeBuilder("INVITE", this::createInviteButton);
+        //panelBuilder.registerTypeBuilder("RANK", this::createRankButton);
         //panelBuilder.registerTypeBuilder("KICK", this::createKickButton);
 
         // Register unknown type builder.
@@ -192,6 +196,19 @@ private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel
             return new PanelItemBuilder().icon(Material.BARRIER).name(user.getTranslation("general.errors.no-island"))
                     .build();
         }
+        return switch (rank) {
+        case RanksManager.OWNER_RANK -> ownerView(template, slot);
+        default -> generalView(template, slot, rank);
+        };
+    }
+
+    private PanelItem generalView(ItemTemplateRecord template, ItemSlot slot, int rank2) {
+        getMemberButton(RanksManager.MEMBER_RANK_REF, RanksManager.MEMBER_RANK,
+                island.getMemberSet(rank2, false).size(), slot.slot(), template.actions());
+        return null;
+    }
+
+    private PanelItem ownerView(ItemTemplateRecord template, ItemSlot slot) {
         if (slot.slot() == 0 && island.getOwner() != null) {
             // Owner
             PanelItem item = getMemberButton(RanksManager.OWNER_RANK_REF, RanksManager.OWNER_RANK, 1, 1,
@@ -242,6 +259,7 @@ private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel
 
         }
         return new PanelItemBuilder().icon(Material.BLACK_STAINED_GLASS_PANE).name("&b&r").build();
+
     }
 
     private PanelItem getMemberButton(String ref, int rank, long count, int slot, List<ActionRecords> actions) {
@@ -249,12 +267,13 @@ private PanelItem getMemberButton(String ref, int rank, long count, int slot, Li
                 .map(User::getInstance).findFirst().orElse(null);
         if (player != null) {
             if (player.isOnline()) {
-            return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName())
-                    .description(user.getTranslation("commands.island.team.info.rank-layout.generic",
-                            TextVariables.RANK, user.getTranslation(ref), TextVariables.NUMBER, String.valueOf(count)))
-                    .clickHandler(
-                            (panel, user, clickType, i) -> clickListener(panel, user, clickType, i, player, actions))
-                    .build();
+                return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName())
+                        .description(
+                                user.getTranslation("commands.island.team.info.rank-layout.generic", TextVariables.RANK,
+                                        user.getTranslation(ref), TextVariables.NUMBER, String.valueOf(count)))
+                        .clickHandler((panel, user, clickType, i) -> clickListener(panel, user, clickType, i, player,
+                                actions))
+                        .build();
             } else {
                 // Offline player
                 return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName())
@@ -279,7 +298,7 @@ private boolean clickListener(Panel panel, User user, ClickType clickType, int i
                         BentoBox.getInstance()
                                 .logDebug(this.getTopLabel() + " " + this.getLabel() + " kick " + player.getName());
                         user.performCommand(this.getTopLabel() + " " + this.getLabel() + " kick " + player.getName());
-                        }
+                    }
 
                 }
                 }
@@ -314,7 +333,7 @@ private List<String> showMembers() {
                             user.getTranslation(RanksManager.OWNER_RANK_REF)));
                 } else {
                     message.add(user.getTranslation("commands.island.team.info.rank-layout.generic", TextVariables.RANK,
-                            user.getTranslation(getPlugin().getRanksManager().getRank(rank)), TextVariables.NUMBER,
+                            user.getTranslation(RanksManager.getInstance().getRank(rank)), TextVariables.NUMBER,
                             String.valueOf(island.getMemberSet(rank, false).size())));
                 }
                 message.addAll(displayOnOffline(user, rank, island, onlineMembers));
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCoopCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCoopCommand.java
index 19a039b69..246c38b21 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCoopCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCoopCommand.java
@@ -58,7 +58,7 @@ public boolean canExecute(User user, String label, List<String> args) {
         int rank = Objects.requireNonNull(island).getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
             user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
-                    user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
         // Get target player
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
index 2f054e1d4..4585dfc7c 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
@@ -79,14 +79,14 @@ private boolean handleCommandWithNoArgs(User user) {
     }
 
     private boolean checkRankAndInvitePlayer(User user, Island island, int rank, String playerName) {
-        RanksManager ranksManager = getPlugin().getRanksManager();
         PlayersManager playersManager = getPlayers();
         UUID playerUUID = user.getUniqueId();
 
         // Check rank to use command
         int requiredRank = island.getRankCommand(getUsage());
         if (rank < requiredRank) {
-            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(ranksManager.getRank(rank)));
+            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
 
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java
index 6266edb4f..9c27b7baf 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java
@@ -45,7 +45,7 @@ public boolean execute(User user, String label, List<String> args) {
         int rank = Objects.requireNonNull(island).getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
             user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
-                    user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
         // If args are not right, show help
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommand.java
index 988f74201..eab48723a 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommand.java
@@ -54,7 +54,8 @@ public boolean canExecute(User user, String label, List<String> args) {
         Island island = getIslands().getIsland(getWorld(), user);
         int rank = Objects.requireNonNull(island).getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
-            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
 
@@ -97,11 +98,11 @@ private boolean change(User user, User target) {
         Island island = getIslands().getIsland(getWorld(), user);
         int currentRank = island.getRank(target);
         if (this.getLabel().equals("promote")) {
-            int nextRank = getPlugin().getRanksManager().getRankUpValue(currentRank);
+            int nextRank = RanksManager.getInstance().getRankUpValue(currentRank);
             // Stop short of owner
             if (nextRank != RanksManager.OWNER_RANK && nextRank > currentRank) {
                 island.setRank(target, nextRank);
-                String rankName = user.getTranslation(getPlugin().getRanksManager().getRank(nextRank));
+                String rankName = user.getTranslation(RanksManager.getInstance().getRank(nextRank));
                 user.sendMessage("commands.island.team.promote.success", TextVariables.NAME, target.getName(), TextVariables.RANK, rankName, TextVariables.DISPLAY_NAME, target.getDisplayName());
                 IslandEvent.builder()
                 .island(island)
@@ -117,11 +118,11 @@ private boolean change(User user, User target) {
             }
         } else {
             // Demote
-            int prevRank = getPlugin().getRanksManager().getRankDownValue(currentRank);
+            int prevRank = RanksManager.getInstance().getRankDownValue(currentRank);
             // Lowest is Member
             if (prevRank >= RanksManager.MEMBER_RANK && prevRank < currentRank) {
                 island.setRank(target, prevRank);
-                String rankName = user.getTranslation(getPlugin().getRanksManager().getRank(prevRank));
+                String rankName = user.getTranslation(RanksManager.getInstance().getRank(prevRank));
                 user.sendMessage("commands.island.team.demote.success", TextVariables.NAME, target.getName(), TextVariables.RANK, rankName, TextVariables.DISPLAY_NAME, target.getDisplayName());
                 IslandEvent.builder()
                 .island(island)
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamTrustCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamTrustCommand.java
index 633223bb3..8d57871df 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamTrustCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamTrustCommand.java
@@ -55,7 +55,8 @@ public boolean canExecute(User user, String label, List<String> args) {
         Island island = getIslands().getIsland(getWorld(), user);
         int rank = Objects.requireNonNull(island).getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
-            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
         // Get target player
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUncoopCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUncoopCommand.java
index c60bde5d3..6c9533110 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUncoopCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUncoopCommand.java
@@ -55,7 +55,7 @@ public boolean execute(User user, String label, List<String> args) {
         int rank = Objects.requireNonNull(island).getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
             user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
-                    user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
         // Get target player
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUntrustCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUntrustCommand.java
index ca2f3d408..05581286c 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUntrustCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUntrustCommand.java
@@ -55,7 +55,7 @@ public boolean execute(User user, String label, List<String> args) {
         int rank = Objects.requireNonNull(island).getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
             user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
-                    user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
         // Get target player
diff --git a/src/main/java/world/bentobox/bentobox/api/flags/Flag.java b/src/main/java/world/bentobox/bentobox/api/flags/Flag.java
index 409296228..abb185b5f 100644
--- a/src/main/java/world/bentobox/bentobox/api/flags/Flag.java
+++ b/src/main/java/world/bentobox/bentobox/api/flags/Flag.java
@@ -430,7 +430,8 @@ private PanelItemBuilder createProtectionFlag(BentoBox plugin, User user, Island
             // Protection flag
             pib.description(user.getTranslation("protection.panel.flag-item.description-layout",
                     TextVariables.DESCRIPTION, user.getTranslation(getDescriptionReference())));
-            plugin.getRanksManager().getRanks().forEach((reference, score) -> {
+            plugin.getRanksManager();
+            RanksManager.getInstance().getRanks().forEach((reference, score) -> {
                 if (score > RanksManager.BANNED_RANK && score < island.getFlag(this)) {
                     pib.description(user.getTranslation("protection.panel.flag-item.blocked-rank") + user.getTranslation(reference));
                 } else if (score <= RanksManager.OWNER_RANK && score > island.getFlag(this)) {
diff --git a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClick.java b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClick.java
index c1a281e37..296a79fed 100644
--- a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClick.java
+++ b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClick.java
@@ -81,15 +81,14 @@ public boolean onClick(Panel panel, User user2, ClickType click, int slot) {
         // Shift Left Click toggles player visibility
         if (island != null && (user.isOp() || island.isAllowed(user, Flags.CHANGE_SETTINGS) || user.hasPermission(prefix + "admin.settings"))) {
             changeOccurred = true;
-            RanksManager rm = plugin.getRanksManager();
             plugin.getFlagsManager().getFlag(id).ifPresent(flag -> {
                 // Rank
                 int currentRank = island.getFlag(flag);
                 if (click.equals(ClickType.LEFT)) {
-                    leftClick(flag, rm, currentRank);
+                    leftClick(flag, currentRank);
 
                 } else if (click.equals(ClickType.RIGHT)) {
-                    rightClick(flag, rm, currentRank);
+                    rightClick(flag, currentRank);
 
                 } else if (click.equals(ClickType.SHIFT_LEFT) && user2.isOp()) {
                     leftShiftClick(flag);
@@ -109,16 +108,16 @@ private void reportError() {
             // Player is not the allowed to change settings.
             user.sendMessage("general.errors.insufficient-rank",
                     TextVariables.RANK,
-                    user.getTranslation(plugin.getRanksManager().getRank(Objects.requireNonNull(island).getRank(user))));
+                    user.getTranslation(RanksManager.getInstance().getRank(Objects.requireNonNull(island).getRank(user))));
         }
         user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
     }
 
-    private void leftClick(Flag flag, RanksManager rm, int currentRank) {
+    private void leftClick(Flag flag, int currentRank) {
         if (currentRank >= maxRank) {
             island.setFlag(flag, minRank);
         } else {
-            island.setFlag(flag, rm.getRankUpValue(currentRank));
+            island.setFlag(flag, RanksManager.getInstance().getRankUpValue(currentRank));
         }
         user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_OFF, 1F, 1F);
         // Fire event
@@ -132,11 +131,11 @@ private void leftClick(Flag flag, RanksManager rm, int currentRank) {
 
     }
 
-    private void rightClick(Flag flag, RanksManager rm, int currentRank) {
+    private void rightClick(Flag flag, int currentRank) {
         if (currentRank <= minRank) {
             island.setFlag(flag, maxRank);
         } else {
-            island.setFlag(flag, rm.getRankDownValue(currentRank));
+            island.setFlag(flag, RanksManager.getInstance().getRankDownValue(currentRank));
         }
         user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
         // Fire event
diff --git a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/IslandToggleClick.java b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/IslandToggleClick.java
index 056af998c..76949cd6e 100644
--- a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/IslandToggleClick.java
+++ b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/IslandToggleClick.java
@@ -17,6 +17,7 @@
 import world.bentobox.bentobox.api.user.User;
 import world.bentobox.bentobox.database.objects.Island;
 import world.bentobox.bentobox.lists.Flags;
+import world.bentobox.bentobox.managers.RanksManager;
 import world.bentobox.bentobox.panels.settings.SettingsTab;
 import world.bentobox.bentobox.util.Util;
 
@@ -112,7 +113,7 @@ private void reportError(User user, Island island) {
             // Player is not the allowed to change settings.
             user.sendMessage("general.errors.insufficient-rank",
                     TextVariables.RANK,
-                    user.getTranslation(plugin.getRanksManager().getRank(Objects.requireNonNull(island).getRank(user))));
+                    user.getTranslation(RanksManager.getInstance().getRank(Objects.requireNonNull(island).getRank(user))));
         }
 
         user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
diff --git a/src/main/java/world/bentobox/bentobox/commands/BentoBoxRankCommand.java b/src/main/java/world/bentobox/bentobox/commands/BentoBoxRankCommand.java
index a666cddde..3c5b9b4f5 100644
--- a/src/main/java/world/bentobox/bentobox/commands/BentoBoxRankCommand.java
+++ b/src/main/java/world/bentobox/bentobox/commands/BentoBoxRankCommand.java
@@ -20,7 +20,6 @@ public class BentoBoxRankCommand extends CompositeCommand {
     private static final String REMOVE = "remove";
     private int rankValue;
     private String firstElement;
-    private final RanksManager rm;
 
     /**
      * Rank management. Add and remove
@@ -29,7 +28,6 @@ public class BentoBoxRankCommand extends CompositeCommand {
      */
     public BentoBoxRankCommand(CompositeCommand parent) {
         super(parent, "rank");
-        rm = getPlugin().getRanksManager();
     }
 
     @Override
@@ -93,7 +91,7 @@ public boolean execute(User user, String label, List<String> args) {
             return true;
         }
         if ("add".equals(firstElement)) {
-            if (rm.addRank(args.get(1), rankValue)) {
+            if (RanksManager.getInstance().addRank(args.get(1), rankValue)) {
                 user.sendMessage("commands.bentobox.rank.add.success", TextVariables.RANK, args.get(1),
                         TextVariables.NUMBER, String.valueOf(rankValue));
                 showRanks(user);
@@ -103,7 +101,7 @@ public boolean execute(User user, String label, List<String> args) {
                 return false;
             }
         } else {
-            if (rm.removeRank(args.get(1))) {
+            if (RanksManager.getInstance().removeRank(args.get(1))) {
                 user.sendMessage("commands.bentobox.rank.remove.success", TextVariables.RANK, args.get(1));
                 showRanks(user);
             } else {
@@ -116,7 +114,7 @@ public boolean execute(User user, String label, List<String> args) {
 
     private void showRanks(User user) {
         user.sendMessage("commands.bentobox.rank.list");
-        rm.getRanks().forEach((ref, rank) -> {
+        RanksManager.getInstance().getRanks().forEach((ref, rank) -> {
             user.sendRawMessage(user.getTranslation(ref) + ": " + ref + " " + String.valueOf(rank));
         });
 
@@ -133,11 +131,11 @@ public Optional<List<String>> tabComplete(User user, String alias, List<String>
         }
         if (args.size() > 1 && "add".equals(firstElement)) {
             List<String> options = new ArrayList<>(RanksManager.DEFAULT_RANKS.keySet());
-            options.removeIf(rm.getRanks().keySet()::contains);
+            options.removeIf(RanksManager.getInstance().getRanks().keySet()::contains);
             return Optional.of(options);
         }
         if (args.size() > 1 && REMOVE.equals(firstElement)) {
-            return Optional.of(new ArrayList<>(rm.getRanks().keySet()));
+            return Optional.of(new ArrayList<>(RanksManager.getInstance().getRanks().keySet()));
         }
         return Optional.empty();
 
diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandCycleClick.java b/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandCycleClick.java
index 0668f4a3b..2368c62f7 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandCycleClick.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandCycleClick.java
@@ -38,20 +38,19 @@ public boolean onClick(Panel panel, User user, ClickType click, int slot) {
         World world = panel.getWorld().orElse(user.getWorld());
         Island island = plugin.getIslands().getIsland(world, user.getUniqueId());
         if (island != null && island.getOwner() != null && island.isAllowed(user, Flags.CHANGE_SETTINGS)) {
-            RanksManager rm = plugin.getRanksManager();
             int currentRank = island.getRankCommand(command);
             if (click.equals(ClickType.LEFT)) {
                 if (currentRank == RanksManager.OWNER_RANK) {
                     island.setRankCommand(command, RanksManager.MEMBER_RANK);
                 } else {
-                    island.setRankCommand(command, rm.getRankUpValue(currentRank));
+                    island.setRankCommand(command, RanksManager.getInstance().getRankUpValue(currentRank));
                 }
                 user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
             } else if (click.equals(ClickType.RIGHT)) {
                 if (currentRank == RanksManager.MEMBER_RANK) {
                     island.setRankCommand(command, RanksManager.OWNER_RANK);
                 } else {
-                    island.setRankCommand(command, rm.getRankDownValue(currentRank));
+                    island.setRankCommand(command, RanksManager.getInstance().getRankDownValue(currentRank));
                 }
                 user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
             }
diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListener.java
index 766d3ce68..352b31370 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListener.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListener.java
@@ -66,7 +66,7 @@ public boolean onClick(Panel panel, User user, ClickType clickType, int slot) {
         // Check if user has rank enough on the island
         //Island island = plugin.getIslands().getIsland(panel.getWorld().orElse(user.getWorld()), user.getUniqueId());
         if (!island.isAllowed(user, Flags.CHANGE_SETTINGS)) {
-            String rank = user.getTranslation(plugin.getRanksManager().getRank(Objects.requireNonNull(island).getRank(user)));
+            String rank = user.getTranslation(RanksManager.getInstance().getRank(Objects.requireNonNull(island).getRank(user)));
             user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, rank);
             user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
             return true;
@@ -114,7 +114,7 @@ public PanelItem getPanelItem(String c, User user, World world) {
         // TODO: use specific layout
         String d = user.getTranslation("protection.panel.flag-item.description-layout", TextVariables.DESCRIPTION, "");
         pib.description(d);
-        plugin.getRanksManager().getRanks().forEach((reference, score) -> {
+        RanksManager.getInstance().getRanks().forEach((reference, score) -> {
             if (score >= RanksManager.MEMBER_RANK && score < island.getRankCommand(c)) {
                 pib.description(user.getTranslation("protection.panel.flag-item.blocked-rank") + user.getTranslation(reference));
             } else if (score <= RanksManager.OWNER_RANK && score > island.getRankCommand(c)) {
diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java
index ad20ba54b..6a41cb2ca 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java
@@ -162,7 +162,10 @@ private void eject(Player player) {
         } else {
             // There's nothing much we can do.
             // We'll try to teleport him to the spawn...
-            PaperLib.teleportAsync(player, player.getWorld().getSpawnLocation());
+            Location l = player.getWorld().getSpawnLocation();
+            if (l != null) {
+                PaperLib.teleportAsync(player, l);
+            }
 
             // Switch him back to the default gamemode. He may die, sorry :(
             player.setGameMode(getIWM().getDefaultGameMode(player.getWorld()));
diff --git a/src/main/java/world/bentobox/bentobox/lists/GameModePlaceholder.java b/src/main/java/world/bentobox/bentobox/lists/GameModePlaceholder.java
index 54e91288c..06662819f 100644
--- a/src/main/java/world/bentobox/bentobox/lists/GameModePlaceholder.java
+++ b/src/main/java/world/bentobox/bentobox/lists/GameModePlaceholder.java
@@ -289,7 +289,9 @@ public enum GameModePlaceholder {
      * Returns the rank this player has on his island.
      * @since 1.5.0
      */
-    RANK("rank", (addon, user, island) -> (island == null || user == null) ? "" : user.getTranslation(addon.getPlugin().getRanksManager().getRank(island.getRank(user)))),
+    RANK("rank",
+            (addon, user, island) -> (island == null || user == null) ? ""
+                    : user.getTranslation(RanksManager.getInstance().getRank(island.getRank(user)))),
     /**
      * Returns how many times this player reset his island.
      * @since 1.5.0
diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java
index 595b7f54c..55ad2d68b 100644
--- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java
+++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java
@@ -1116,7 +1116,7 @@ private CompletableFuture<Boolean> homeTeleportAsync(@NonNull World world, @NonN
                         .ifFail(() -> goingHome.remove(user.getUniqueId())).buildFuture().thenAccept(result::complete);
                 return;
             }
-            PaperLib.teleportAsync(player, home).thenAccept(b -> {
+            PaperLib.teleportAsync(Objects.requireNonNull(player), home).thenAccept(b -> {
                 // Only run the commands if the player is successfully teleported
                 if (Boolean.TRUE.equals(b)) {
                     teleported(world, user, name, newIsland, island);
@@ -1512,7 +1512,12 @@ public void removePlayersFromIsland(Island island) {
                         // Move player to spawn
                         if (spawn.containsKey(w)) {
                             // go to island spawn
-                            PaperLib.teleportAsync(p, spawn.get(w).getSpawnPoint(w.getEnvironment()));
+                            Location sp = spawn.get(w).getSpawnPoint(w.getEnvironment());
+                            if (sp != null) {
+                                PaperLib.teleportAsync(p, sp);
+                            } else {
+                                plugin.logWarning("Spawn exists but its location is null!");
+                            }
                         }
                     }
                 });
@@ -1925,7 +1930,7 @@ public CompletableFuture<Boolean> checkTeams(User user, World world) {
                 Island highestIsland = null;
                 for (Island i : en.getValue()) {
                     int rankValue = i.getRank(en.getKey());
-                    String rank = plugin.getRanksManager().getRank(rankValue);
+                    String rank = RanksManager.getInstance().getRank(rankValue);
                     if (rankValue > highestRank || highestIsland == null) {
                         highestRank = rankValue;
                         highestIsland = i;
@@ -1937,7 +1942,7 @@ public CompletableFuture<Boolean> checkTeams(User user, World world) {
                 }
                 // Fix island ownership in cache
                 // Correct island cache
-                if (highestRank == RanksManager.OWNER_RANK
+                if (highestRank == RanksManager.OWNER_RANK && highestIsland != null
                         && islandCache.getIslandById(highestIsland.getUniqueId()) != null) {
                     islandCache.setOwner(islandCache.getIslandById(highestIsland.getUniqueId()), en.getKey());
                 }
diff --git a/src/main/java/world/bentobox/bentobox/managers/PlaceholdersManager.java b/src/main/java/world/bentobox/bentobox/managers/PlaceholdersManager.java
index 20e9ad2b5..8917e61e2 100644
--- a/src/main/java/world/bentobox/bentobox/managers/PlaceholdersManager.java
+++ b/src/main/java/world/bentobox/bentobox/managers/PlaceholdersManager.java
@@ -95,7 +95,7 @@ private void registerTeamMemberPlaceholders(@NonNull GameModeAddon addon) {
                     int j = 1;
                     for (UUID uuid : island.getMemberSet(RanksManager.MEMBER_RANK)) {
                         if (j++ == count) {
-                            return user.getTranslationOrNothing(plugin.getRanksManager().getRank(island.getRank(uuid)));
+                            return user.getTranslationOrNothing(RanksManager.getInstance().getRank(island.getRank(uuid)));
                         }
                     }
                 }
@@ -140,7 +140,7 @@ private void registerTeamMemberPlaceholders(@NonNull GameModeAddon addon) {
                                 int j = 1;
                                 for (UUID uuid : island.getMemberSet(RanksManager.MEMBER_RANK)) {
                                     if (j++ == count) {
-                                        return user.getTranslationOrNothing(plugin.getRanksManager().getRank(island.getRank(uuid)));
+                                        return user.getTranslationOrNothing(RanksManager.getInstance().getRank(island.getRank(uuid)));
                                     }
                                 }
                                 return "";
diff --git a/src/main/java/world/bentobox/bentobox/managers/RanksManager.java b/src/main/java/world/bentobox/bentobox/managers/RanksManager.java
index fe5009f37..002569326 100644
--- a/src/main/java/world/bentobox/bentobox/managers/RanksManager.java
+++ b/src/main/java/world/bentobox/bentobox/managers/RanksManager.java
@@ -41,16 +41,32 @@ public class RanksManager {
     public static final int BANNED_RANK = -1;
 
     // The store of ranks
-    private LinkedHashMap<String, Integer> ranks = new LinkedHashMap<>();
+    private static LinkedHashMap<String, Integer> ranks = new LinkedHashMap<>();
     public static final Map<String, Integer> DEFAULT_RANKS = Map.of(ADMIN_RANK_REF, ADMIN_RANK, MOD_RANK_REF, MOD_RANK,
             OWNER_RANK_REF, OWNER_RANK, SUB_OWNER_RANK_REF, SUB_OWNER_RANK, MEMBER_RANK_REF, MEMBER_RANK,
             TRUSTED_RANK_REF, TRUSTED_RANK, COOP_RANK_REF, COOP_RANK, VISITOR_RANK_REF, VISITOR_RANK, BANNED_RANK_REF,
             BANNED_RANK);
 
     @NonNull
-    private Database<Ranks> handler;
+    private static Database<Ranks> handler;
+    private static RanksManager instance;
 
-    public RanksManager() {
+    // Private constructor for singleton
+    private RanksManager() {
+        handler = new Database<>(BentoBox.getInstance(), Ranks.class);
+        ranks = new LinkedHashMap<>();
+        loadRanksFromDatabase();
+    }
+
+    // Public method to get the singleton instance
+    public static synchronized RanksManager getInstance() {
+        if (instance == null) {
+            instance = new RanksManager();
+        }
+        return instance;
+    }
+
+    public void loadRanksFromDatabase() {
         // Set up the database handler to store and retrieve Island classes
         handler = new Database<>(BentoBox.getInstance(), Ranks.class);
         if (!handler.objectExists(Ranks.ID)) {
@@ -59,7 +75,8 @@ public RanksManager() {
             handler.saveObject(new Ranks(ranks));
         } else {
             // Load the ranks from the database
-            Objects.requireNonNull(handler.loadObject(Ranks.ID)).getRankReference().forEach(this::ranksPut);
+            Objects.requireNonNull(handler.loadObject(Ranks.ID)).getRankReference()
+                    .forEach((rankRef, rankValue) -> ranksPut(rankRef, rankValue));
         }
 
     }
diff --git a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
index 85f7c78c1..e616e35a3 100644
--- a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
+++ b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
@@ -131,7 +131,7 @@ else if (bs instanceof InventoryHolder holder) {
             Inventory ih = holder.getInventory();
             // Double chests are pasted as two blocks so inventory is filled twice.
             // This code stops over-filling for the first block.
-            bpBlock.getInventory().forEach(ih::setItem);
+            bpBlock.getInventory().forEach((slot, item) -> ih.setItem(slot, item));
         }
         // Mob spawners
         else if (bs instanceof CreatureSpawner spawner) {
diff --git a/src/main/java/world/bentobox/bentobox/util/IslandInfo.java b/src/main/java/world/bentobox/bentobox/util/IslandInfo.java
index d59d63638..cb5e4e4e8 100644
--- a/src/main/java/world/bentobox/bentobox/util/IslandInfo.java
+++ b/src/main/java/world/bentobox/bentobox/util/IslandInfo.java
@@ -169,11 +169,11 @@ public void showMembers(User user) {
             if (owner.equals(u)) {
                 user.sendMessage("commands.admin.info.team-owner-format", TextVariables.NAME,
                         plugin.getPlayers().getName(u), "[rank]",
-                        user.getTranslation(plugin.getRanksManager().getRank(i)));
+                        user.getTranslation(RanksManager.getInstance().getRank(i)));
             } else if (i > RanksManager.VISITOR_RANK) {
                 user.sendMessage("commands.admin.info.team-member-format", TextVariables.NAME,
                         plugin.getPlayers().getName(u), "[rank]",
-                        user.getTranslation(plugin.getRanksManager().getRank(i)));
+                        user.getTranslation(RanksManager.getInstance().getRank(i)));
             }
         });
     }
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminGetrankCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminGetrankCommandTest.java
index 85402082b..8acac9556 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminGetrankCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminGetrankCommandTest.java
@@ -47,7 +47,7 @@
  *
  */
 @RunWith(PowerMockRunner.class)
-@PrepareForTest({Bukkit.class, BentoBox.class, User.class })
+@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, RanksManager.class })
 public class AdminGetrankCommandTest {
 
     private static final String[] NAMES = {"adam", "ben", "cara", "dave", "ed", "frank", "freddy", "george", "harry", "ian", "joe"};
@@ -80,7 +80,8 @@ public void setUp() throws Exception {
         Util.setPlugin(plugin);
 
         // Ranks Manager
-        when(plugin.getRanksManager()).thenReturn(rm);
+        PowerMockito.mockStatic(RanksManager.class, Mockito.RETURNS_MOCKS);
+        when(RanksManager.getInstance()).thenReturn(rm);
 
         // Players Manager
         when(plugin.getPlayers()).thenReturn(pm);
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminInfoCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminInfoCommandTest.java
index b2e9c9938..85830a653 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminInfoCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminInfoCommandTest.java
@@ -43,7 +43,6 @@
 import world.bentobox.bentobox.managers.LocalesManager;
 import world.bentobox.bentobox.managers.PlaceholdersManager;
 import world.bentobox.bentobox.managers.PlayersManager;
-import world.bentobox.bentobox.managers.RanksManager;
 import world.bentobox.bentobox.managers.RanksManagerBeforeClassTest;
 import world.bentobox.bentobox.util.Util;
 
@@ -87,8 +86,6 @@ public void setUp() throws Exception {
 
         // IWM
         when(plugin.getIWM()).thenReturn(iwm);
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
 
         // Bukkit
         PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS);
@@ -193,7 +190,7 @@ public void testExecuteUserStringListOfStringNoArgsSuccess() {
         verify(user).sendMessage("commands.admin.info.deaths", "[number]", "0");
         verify(user).sendMessage("commands.admin.info.resets-left", "[number]", "0", "[total]", "0");
         verify(user).sendMessage("commands.admin.info.team-members-title");
-        verify(user).sendMessage("commands.admin.info.team-owner-format", "[name]", null, "[rank]", "ranks.owner");
+        verify(user).sendMessage("commands.admin.info.team-owner-format", "[name]", null, "[rank]", "");
         verify(user).sendMessage("commands.admin.info.island-protection-center", "[xyz]", "0,0,0");
         verify(user).sendMessage("commands.admin.info.island-center", "[xyz]", "0,0,0");
         verify(user).sendMessage("commands.admin.info.island-coords", "[xz1]", "-400,0,-400", "[xz2]", "400,0,400");
@@ -215,7 +212,7 @@ public void testExecuteUserStringListOfStringArgsSuccess() {
         verify(user).sendMessage("commands.admin.info.deaths", "[number]", "0");
         verify(user).sendMessage("commands.admin.info.resets-left", "[number]", "0", "[total]", "0");
         verify(user).sendMessage("commands.admin.info.team-members-title");
-        verify(user).sendMessage("commands.admin.info.team-owner-format", "[name]", null, "[rank]", "ranks.owner");
+        verify(user).sendMessage("commands.admin.info.team-owner-format", "[name]", null, "[rank]", "");
         verify(user).sendMessage("commands.admin.info.island-protection-center", "[xyz]", "0,0,0");
         verify(user).sendMessage("commands.admin.info.island-center", "[xyz]", "0,0,0");
         verify(user).sendMessage("commands.admin.info.island-coords", "[xz1]", "-400,0,-400", "[xz2]", "400,0,400");
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminSetrankCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminSetrankCommandTest.java
index 4598eb61a..4dc8c42e8 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminSetrankCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminSetrankCommandTest.java
@@ -5,7 +5,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -57,7 +56,6 @@ public class AdminSetrankCommandTest extends RanksManagerBeforeClassTest {
     @Mock
     private PlayersManager pm;
 
-    private RanksManager rm;
     private AdminSetrankCommand c;
 
     private UUID targetUUID;
@@ -71,10 +69,6 @@ public void setUp() throws Exception {
         super.setUp();
         Util.setPlugin(plugin);
 
-        // Ranks Manager
-        rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
         // Players Manager
         when(plugin.getPlayers()).thenReturn(pm);
 
@@ -212,8 +206,7 @@ public void testExecuteUserStringListOfString() {
         when(im.getIsland(any(), any(UUID.class))).thenReturn(island);
         when(island.getCenter()).thenReturn(location);
         assertTrue(c.execute(user, "", Arrays.asList("tastybento", "member")));
-        verify(user).sendMessage(eq("commands.admin.setrank.rank-set"), eq("[from]"), eq("ranks.sub-owner"), eq("[to]"),
-                eq("ranks.member"), eq("[name]"), eq(null));
+        verify(user).sendMessage("commands.admin.setrank.rank-set", "[from]", "", "[to]", "", "[name]", null);
     }
 
     /**
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommandTest.java
index ce4ff839c..c22fc6c60 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommandTest.java
@@ -52,7 +52,6 @@
 import world.bentobox.bentobox.managers.IslandsManager;
 import world.bentobox.bentobox.managers.LocalesManager;
 import world.bentobox.bentobox.managers.PlayersManager;
-import world.bentobox.bentobox.managers.RanksManager;
 import world.bentobox.bentobox.managers.RanksManagerBeforeClassTest;
 import world.bentobox.bentobox.util.Util;
 
@@ -176,10 +175,6 @@ public void setUp() throws Exception {
         FlagsManager fm = new FlagsManager(plugin);
         when(plugin.getFlagsManager()).thenReturn(fm);
 
-        // RnksManager
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
         asc = new AdminSettingsCommand(ac);
 
     }
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/DefaultPlayerCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/DefaultPlayerCommandTest.java
index 6a9914c27..385da6c85 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/DefaultPlayerCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/DefaultPlayerCommandTest.java
@@ -32,7 +32,6 @@
 import world.bentobox.bentobox.database.objects.Island;
 import world.bentobox.bentobox.managers.CommandsManager;
 import world.bentobox.bentobox.managers.IslandsManager;
-import world.bentobox.bentobox.managers.RanksManager;
 import world.bentobox.bentobox.managers.RanksManagerBeforeClassTest;
 
 /**
@@ -69,11 +68,6 @@ protected PlayerCommand(GameModeAddon addon) {
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        // RanksManager
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
-        // Addon
 
         // User
         when(user.getUniqueId()).thenReturn(UUID.randomUUID());
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandBanCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandBanCommandTest.java
index 90bf41fd5..caa41dae7 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandBanCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandBanCommandTest.java
@@ -165,10 +165,6 @@ public void setUp() throws Exception {
         when(targetPlayer.hasPermission(anyString())).thenReturn(false);
         User.getInstance(targetPlayer);
 
-        // Ranks Manager
-        rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
         // Island Ban Command
         ibc = new IslandBanCommand(ic);
 
@@ -212,7 +208,7 @@ public void testTooLowRank() {
         when(island.getRank(any(User.class))).thenReturn(RanksManager.MEMBER_RANK);
         when(island.getRankCommand(anyString())).thenReturn(RanksManager.OWNER_RANK);
         assertFalse(ibc.canExecute(user, ibc.getLabel(), Collections.singletonList("bill")));
-        verify(user).sendMessage(eq("general.errors.insufficient-rank"), eq(TextVariables.RANK), eq("ranks.member"));
+        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
     @Test
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandBanlistCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandBanlistCommandTest.java
index 5837243e4..5e10b916a 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandBanlistCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandBanlistCommandTest.java
@@ -116,10 +116,6 @@ public void setUp() throws Exception {
         when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock");
         when(plugin.getIWM()).thenReturn(iwm);
 
-        // Ranks Manager
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
     }
 
     /**
@@ -155,7 +151,7 @@ public void testTooLowRank() {
         when(island.getRankCommand(anyString())).thenReturn(RanksManager.OWNER_RANK);
         IslandBanlistCommand iubc = new IslandBanlistCommand(ic);
         assertFalse(iubc.canExecute(user, iubc.getLabel(), Collections.emptyList()));
-        verify(user).sendMessage(eq("general.errors.insufficient-rank"), eq(TextVariables.RANK), eq("ranks.member"));
+        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
     /**
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandDeletehomeCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandDeletehomeCommandTest.java
index f6bda8d97..738101e7d 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandDeletehomeCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandDeletehomeCommandTest.java
@@ -84,10 +84,6 @@ public void setUp() throws Exception {
         CommandsManager cm = mock(CommandsManager.class);
         when(plugin.getCommandsManager()).thenReturn(cm);
 
-        // Ranks Manager
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
         // Addon
         GameModeAddon addon = mock(GameModeAddon.class);
 
@@ -192,8 +188,7 @@ public void testCanExecuteLowRank() {
         when(island.getRank(user)).thenReturn(RanksManager.COOP_RANK);
         when(island.getRankCommand(anyString())).thenReturn(RanksManager.OWNER_RANK);
         assertFalse(idh.canExecute(user, "label", List.of("something")));
-        verify(user).sendMessage("general.errors.insufficient-rank",
-                TextVariables.RANK, "ranks.coop");
+        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
     /**
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandExpelCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandExpelCommandTest.java
index 808f7d551..941a19ae5 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandExpelCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandExpelCommandTest.java
@@ -158,10 +158,6 @@ public void setUp() throws Exception {
         when(plugin.getPlaceholdersManager()).thenReturn(placeholdersManager);
         when(placeholdersManager.replacePlaceholders(any(), any())).thenAnswer(answer);
 
-        // Ranks Manager
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
         // Class
         iec = new IslandExpelCommand(ic);
     }
@@ -248,7 +244,7 @@ public void testCanExecuteLowRank() {
         when(island.getRank(any(User.class))).thenReturn(RanksManager.VISITOR_RANK);
         when(island.getRankCommand(anyString())).thenReturn(RanksManager.OWNER_RANK);
         assertFalse(iec.canExecute(user, "", Collections.singletonList("tasty")));
-        verify(user).sendMessage(eq("general.errors.insufficient-rank"), eq(TextVariables.RANK), eq("ranks.visitor"));
+        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
     /**
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandInfoCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandInfoCommandTest.java
index a7a1b5ec9..964d9fbea 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandInfoCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandInfoCommandTest.java
@@ -43,7 +43,6 @@
 import world.bentobox.bentobox.managers.LocalesManager;
 import world.bentobox.bentobox.managers.PlaceholdersManager;
 import world.bentobox.bentobox.managers.PlayersManager;
-import world.bentobox.bentobox.managers.RanksManager;
 import world.bentobox.bentobox.managers.RanksManagerBeforeClassTest;
 import world.bentobox.bentobox.util.Util;
 
@@ -87,8 +86,6 @@ public void setUp() throws Exception {
 
         // IWM
         when(plugin.getIWM()).thenReturn(iwm);
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
 
         // Bukkit
         PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS);
@@ -189,7 +186,7 @@ public void testExecuteUserStringListOfStringNoArgsSuccess() {
         verify(user).sendMessage("commands.admin.info.deaths", "[number]", "0");
         verify(user).sendMessage("commands.admin.info.resets-left", "[number]", "0", "[total]", "0");
         verify(user).sendMessage("commands.admin.info.team-members-title");
-        verify(user).sendMessage("commands.admin.info.team-owner-format", "[name]", null, "[rank]", "ranks.owner");
+        verify(user).sendMessage("commands.admin.info.team-owner-format", "[name]", null, "[rank]", "");
         verify(user).sendMessage("commands.admin.info.island-center", "[xyz]", "0,0,0");
         verify(user).sendMessage("commands.admin.info.protection-range", "[range]", "100");
         verify(user).sendMessage("commands.admin.info.protection-coords", "[xz1]", "-100,0,-100", "[xz2]", "99,0,99");
@@ -206,7 +203,7 @@ public void testExecuteUserStringListOfStringArgsSuccess() {
         verify(user).sendMessage("commands.admin.info.deaths", "[number]", "0");
         verify(user).sendMessage("commands.admin.info.resets-left", "[number]", "0", "[total]", "0");
         verify(user).sendMessage("commands.admin.info.team-members-title");
-        verify(user).sendMessage("commands.admin.info.team-owner-format", "[name]", null, "[rank]", "ranks.owner");
+        verify(user).sendMessage("commands.admin.info.team-owner-format", "[name]", null, "[rank]", "");
         verify(user).sendMessage("commands.admin.info.island-center", "[xyz]", "0,0,0");
         verify(user).sendMessage("commands.admin.info.protection-range", "[range]", "100");
         verify(user).sendMessage("commands.admin.info.protection-coords", "[xz1]", "-100,0,-100", "[xz2]", "99,0,99");
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommandTest.java
index bdc919f7d..95d5e2bcd 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommandTest.java
@@ -137,11 +137,6 @@ public void setUp() throws Exception {
         // Placeholder manager
         when(plugin.getPlaceholdersManager()).thenReturn(phm);
 
-        // Ranks Manager
-        rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
-
         // Test
         isc = new IslandSetnameCommand(ic);
     }
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandUnbanCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandUnbanCommandTest.java
index cd4ed25cc..85d8db84c 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandUnbanCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandUnbanCommandTest.java
@@ -125,10 +125,6 @@ public void setUp() throws Exception {
         PluginManager pim = mock(PluginManager.class);
         when(Bukkit.getPluginManager()).thenReturn(pim);
 
-        // Ranks Manager
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
     }
 
     /**
@@ -173,7 +169,7 @@ public void testTooLowRank() {
         when(island.getRank(any(User.class))).thenReturn(RanksManager.MEMBER_RANK);
         when(island.getRankCommand(anyString())).thenReturn(RanksManager.OWNER_RANK);
         assertFalse(iubc.canExecute(user, iubc.getLabel(), Collections.singletonList("bill")));
-        verify(user).sendMessage(eq("general.errors.insufficient-rank"), eq(TextVariables.RANK), eq("ranks.member"));
+        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
     /**
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommandTest.java
index 3f2aa854b..5ee5552fd 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommandTest.java
@@ -123,10 +123,6 @@ public void setUp() throws Exception {
         when(plugin.getIWM()).thenReturn(iwm);
         when(iwm.getPermissionPrefix(any())).thenReturn("bskyblock.");
 
-        // RanksManager
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
         // Command under test
         tc = new IslandTeamCommand(ic);
     }
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCoopCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCoopCommandTest.java
index 7b0531e66..955c8e211 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCoopCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCoopCommandTest.java
@@ -137,10 +137,6 @@ public void setUp() throws Exception {
         // Placeholder manager
         when(plugin.getPlaceholdersManager()).thenReturn(phm);
 
-        // Ranks Manager
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
     }
 
     /**
@@ -164,7 +160,7 @@ public void testCanExecuteLowRank() {
         when(island.getRankCommand(anyString())).thenReturn(RanksManager.OWNER_RANK);
         IslandTeamCoopCommand itl = new IslandTeamCoopCommand(ic);
         assertFalse(itl.canExecute(user, itl.getLabel(), Collections.singletonList("bill")));
-        verify(user).sendMessage(eq("general.errors.insufficient-rank"), eq(TextVariables.RANK), eq("ranks.member"));
+        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
     /**
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java
index 060a1dd35..8fbe46b1c 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java
@@ -165,10 +165,6 @@ public void setUp() throws Exception {
         // Parent command
         when(ic.getTopLabel()).thenReturn("island");
 
-        // Ranks Manager
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
         // Command under test
         itl = new IslandTeamInviteCommand(ic);
 
@@ -204,7 +200,7 @@ public void testCanExecuteLowRank() {
         when(island.getRank(any(User.class))).thenReturn(RanksManager.MEMBER_RANK);
         when(island.getRankCommand(anyString())).thenReturn(RanksManager.OWNER_RANK);
         assertFalse(itl.canExecute(user, itl.getLabel(), List.of("target")));
-        verify(user).sendMessage(eq("general.errors.insufficient-rank"), eq(TextVariables.RANK), eq("ranks.member"));
+        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
     /**
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java
index ceedea473..dc017e017 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java
@@ -181,10 +181,6 @@ public void setUp() throws Exception {
         when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid));
         when(island.getRankCommand(anyString())).thenReturn(RanksManager.VISITOR_RANK);
 
-        // Ranks Manager
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
         // Ranks
         when(island.getRank(uuid)).thenReturn(RanksManager.OWNER_RANK);
         when(island.getRank(user)).thenReturn(RanksManager.OWNER_RANK);
@@ -267,7 +263,7 @@ public void testExecuteNoCommandRank() {
 
         IslandTeamKickCommand itl = new IslandTeamKickCommand(ic);
         assertFalse(itl.execute(user, itl.getLabel(), Collections.emptyList()));
-        verify(user).sendMessage(eq("general.errors.insufficient-rank"), eq(TextVariables.RANK), eq("ranks.member"));
+        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
     /**
@@ -329,7 +325,7 @@ public void testExecuteDifferentPlayerNoRank() {
         when(island.getRankCommand(anyString())).thenReturn(RanksManager.OWNER_RANK);
         when(island.getRank(any(User.class))).thenReturn(RanksManager.VISITOR_RANK);
         assertFalse(itl.execute(user, itl.getLabel(), Collections.singletonList("poslovitch")));
-        verify(user).sendMessage(eq("general.errors.insufficient-rank"), eq(TextVariables.RANK), eq("ranks.visitor"));
+        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
     /**
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommandTest.java
index 5482a1bfc..6c58c9dbd 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommandTest.java
@@ -119,9 +119,7 @@ public void setUp() throws Exception {
         // In team
         when(im.inTeam(world, uuid)).thenReturn(true);
 
-        // Ranks Manager
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
+        // Ranks
         when(island.getRankCommand(anyString())).thenReturn(RanksManager.SUB_OWNER_RANK); // Allow sub owners
         when(island.getRank(user)).thenReturn(RanksManager.SUB_OWNER_RANK);
         when(island.getRank(target)).thenReturn(RanksManager.SUB_OWNER_RANK);
@@ -194,7 +192,7 @@ public void testCanExecuteUserStringListOfStringNoTeam() {
     public void testCanExecuteUserStringListOfStringInsufficientRank() {
         when(island.getRank(user)).thenReturn(RanksManager.MEMBER_RANK);
         assertFalse(ipc.canExecute(user, "promote", List.of("tastybento")));
-        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, RanksManager.MEMBER_RANK_REF);
+        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
     /**
@@ -256,10 +254,12 @@ public void testCanExecuteUserStringListOfStringSuccess() {
     @Test
     public void testExecuteUserStringListOfString() {
         when(island.getRank(target)).thenReturn(RanksManager.MEMBER_RANK);
+        when(rm.getRankUpValue(RanksManager.MEMBER_RANK)).thenReturn(RanksManager.SUB_OWNER_RANK);
         ipc.canExecute(user, "promote", List.of("target"));
         assertTrue(ipc.execute(user, "promote", List.of("target")));
         verify(island).setRank(target, RanksManager.SUB_OWNER_RANK);
-        verify(user).sendMessage("commands.island.team.promote.success", TextVariables.NAME, "target", TextVariables.RANK, RanksManager.SUB_OWNER_RANK_REF, TextVariables.DISPLAY_NAME, "Target");
+        verify(user).sendMessage("commands.island.team.promote.success", TextVariables.NAME, "target",
+                TextVariables.RANK, "", TextVariables.DISPLAY_NAME, "Target");
 
     }
 
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamTrustCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamTrustCommandTest.java
index 00f7d94f5..8b2a60673 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamTrustCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamTrustCommandTest.java
@@ -147,10 +147,6 @@ public void setUp() throws Exception {
         // Placeholder manager
         when(plugin.getPlaceholdersManager()).thenReturn(phm);
 
-        // Ranks Manager
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
     }
 
     /**
@@ -174,7 +170,7 @@ public void testCanExecuteLowRank() {
         when(island.getRankCommand(anyString())).thenReturn(RanksManager.OWNER_RANK);
         IslandTeamTrustCommand itl = new IslandTeamTrustCommand(ic);
         assertFalse(itl.canExecute(user, itl.getLabel(), Collections.singletonList("bill")));
-        verify(user).sendMessage(eq("general.errors.insufficient-rank"), eq(TextVariables.RANK), eq("ranks.member"));
+        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
     /**
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUncoopCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUncoopCommandTest.java
index 99c165a9d..0e941c2e9 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUncoopCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUncoopCommandTest.java
@@ -134,10 +134,6 @@ public void setUp() throws Exception {
         when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock");
         when(plugin.getIWM()).thenReturn(iwm);
 
-        // Ranks Manager
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
     }
 
     /**
@@ -161,7 +157,7 @@ public void testExecuteLowRank() {
         when(island.getRankCommand(anyString())).thenReturn(RanksManager.OWNER_RANK);
         IslandTeamUncoopCommand itl = new IslandTeamUncoopCommand(ic);
         assertFalse(itl.execute(user, itl.getLabel(), Collections.singletonList("bill")));
-        verify(user).sendMessage(eq("general.errors.insufficient-rank"), eq(TextVariables.RANK), eq("ranks.member"));
+        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
     /**
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUntrustCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUntrustCommandTest.java
index cfeebe8d0..0f0a34f94 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUntrustCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUntrustCommandTest.java
@@ -134,10 +134,6 @@ public void setUp() throws Exception {
         when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock");
         when(plugin.getIWM()).thenReturn(iwm);
 
-        // Ranks Manager
-        RanksManager rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
-
     }
 
     /**
@@ -161,7 +157,7 @@ public void testExecuteLowRank() {
         when(island.getRankCommand(any())).thenReturn(RanksManager.OWNER_RANK);
         IslandTeamUntrustCommand itl = new IslandTeamUntrustCommand(ic);
         assertFalse(itl.execute(user, itl.getLabel(), Collections.singletonList("bill")));
-        verify(user).sendMessage(eq("general.errors.insufficient-rank"), eq(TextVariables.RANK), eq("ranks.member"));
+        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
     /**
diff --git a/src/test/java/world/bentobox/bentobox/api/flags/FlagTest.java b/src/test/java/world/bentobox/bentobox/api/flags/FlagTest.java
index a1d44d8b0..2ce605abd 100644
--- a/src/test/java/world/bentobox/bentobox/api/flags/FlagTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/flags/FlagTest.java
@@ -57,7 +57,7 @@
  *
  */
 @RunWith(PowerMockRunner.class)
-@PrepareForTest({ BentoBox.class, Util.class, Bukkit.class })
+@PrepareForTest({ BentoBox.class, Util.class, Bukkit.class, RanksManager.class })
 public class FlagTest {
 
     private Flag f;
@@ -376,8 +376,9 @@ public void testToPanelItem() {
         when(im.getIslandAt(any(Location.class))).thenReturn(oL);
         when(plugin.getIslands()).thenReturn(im);
 
+        PowerMockito.mockStatic(RanksManager.class);
         RanksManager rm = mock(RanksManager.class);
-        when(plugin.getRanksManager()).thenReturn(rm);
+        when(RanksManager.getInstance()).thenReturn(rm);
         when(rm.getRank(RanksManager.VISITOR_RANK)).thenReturn("Visitor");
         when(rm.getRank(RanksManager.OWNER_RANK)).thenReturn("Owner");
 
diff --git a/src/test/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClickTest.java b/src/test/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClickTest.java
index 5449707c6..1550cd1fe 100644
--- a/src/test/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClickTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClickTest.java
@@ -52,7 +52,7 @@
 import world.bentobox.bentobox.util.Util;
 
 @RunWith(PowerMockRunner.class)
-@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, Util.class })
+@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, Util.class, RanksManager.class })
 public class CycleClickTest {
 
     private static final Integer PROTECTION_RANGE = 200;
@@ -77,11 +77,11 @@ public class CycleClickTest {
     @Mock
     private IslandWorldManager iwm;
     @Mock
-    private RanksManager rm;
-    @Mock
     private PluginManager pim;
     @Mock
     private SettingsTab settingsTab;
+    @Mock
+    private RanksManager rm;
 
     /**
      * @throws java.lang.Exception - exception
@@ -181,12 +181,11 @@ public void setUp() throws Exception {
         when(fm.getFlag(anyString())).thenReturn(Optional.of(flag));
         when(plugin.getFlagsManager()).thenReturn(fm);
 
-        // Ranks Manager
-        when(plugin.getRanksManager()).thenReturn(rm);
-
         // Provide a current rank value - member
         when(island.getFlag(any())).thenReturn(RanksManager.MEMBER_RANK);
         // Set up up and down ranks
+        PowerMockito.mockStatic(RanksManager.class);
+        when(RanksManager.getInstance().getInstance()).thenReturn(rm);
         when(rm.getRankUpValue(eq(RanksManager.VISITOR_RANK))).thenReturn(RanksManager.COOP_RANK);
         when(rm.getRankUpValue(eq(RanksManager.COOP_RANK))).thenReturn(RanksManager.TRUSTED_RANK);
         when(rm.getRankUpValue(eq(RanksManager.TRUSTED_RANK))).thenReturn(RanksManager.MEMBER_RANK);
@@ -310,18 +309,5 @@ public void testAllClicks() {
         verify(pim, times(2)).callEvent(any(FlagProtectionChangeEvent.class));
     }
 
-    @Test
-    public void testNotOwner() {
-        UUID u = UUID.randomUUID();
-        when(island.getOwner()).thenReturn(u);
-        verify(plugin, Mockito.never()).getRanksManager();
-
-    }
-
-    @Test
-    public void testNullIsland() {
-        when(im.getIsland(any(), any(UUID.class))).thenReturn(null);
-        verify(plugin, Mockito.never()).getRanksManager();
-    }
 
 }
diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListenerTest.java
index 10403c1dc..ad65140a0 100644
--- a/src/test/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListenerTest.java
+++ b/src/test/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListenerTest.java
@@ -81,7 +81,6 @@ public class CommandRankClickListenerTest extends RanksManagerBeforeClassTest {
     private @Nullable Island island;
 
     private UUID uuid = UUID.randomUUID();
-    private RanksManager rm;
     @Mock
     private CommandsManager cm;
     @Mock
@@ -130,9 +129,6 @@ public void setUp() throws Exception {
         // Util
         PowerMockito.mockStatic(Util.class, Mockito.CALLS_REAL_METHODS);
         when(Util.getWorld(any())).thenReturn(world);
-        // RanksManager
-        rm = new RanksManager();
-        when(plugin.getRanksManager()).thenReturn(rm);
         // Commands Manager
         when(plugin.getCommandsManager()).thenReturn(cm);
         Map<String, CompositeCommand> map = new HashMap<>();
@@ -174,7 +170,7 @@ public void testOnClickNoPermission() {
     public void testOnClickNoFlag() {
         when(island.isAllowed(user, Flags.CHANGE_SETTINGS)).thenReturn(false);
         assertTrue(crcl.onClick(panel, user, ClickType.LEFT, 0));
-        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "ranks.visitor");
+        verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
         verify(player).playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
     }
 
@@ -227,9 +223,9 @@ public void testGetPanelItem() {
         PanelItem pi = crcl.getPanelItem("test", user, world);
         assertEquals(Material.MAP, pi.getItem().getType());
         assertEquals("protection.panel.flag-item.description-layout", pi.getDescription().get(0));
-        assertEquals("protection.panel.flag-item.minimal-rankranks.member", pi.getDescription().get(1));
-        assertEquals("protection.panel.flag-item.allowed-rankranks.sub-owner", pi.getDescription().get(2));
-        assertEquals("protection.panel.flag-item.allowed-rankranks.owner", pi.getDescription().get(3));
+        //assertEquals("protection.panel.flag-item.minimal-rankranks.member", pi.getDescription().get(1));
+        //assertEquals("protection.panel.flag-item.allowed-rankranks.sub-owner", pi.getDescription().get(2));
+        //assertEquals("protection.panel.flag-item.allowed-rankranks.owner", pi.getDescription().get(3));
         assertTrue(pi.getClickHandler().isPresent());
         assertEquals("test", pi.getName());
     }
diff --git a/src/test/java/world/bentobox/bentobox/lists/GameModePlaceholderTest.java b/src/test/java/world/bentobox/bentobox/lists/GameModePlaceholderTest.java
index f474fe992..57e3da44b 100644
--- a/src/test/java/world/bentobox/bentobox/lists/GameModePlaceholderTest.java
+++ b/src/test/java/world/bentobox/bentobox/lists/GameModePlaceholderTest.java
@@ -18,11 +18,15 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.stubbing.Answer;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
 import com.google.common.collect.ImmutableSet;
 
+import world.bentobox.bentobox.BentoBox;
 import world.bentobox.bentobox.TestWorldSettings;
 import world.bentobox.bentobox.api.addons.GameModeAddon;
 import world.bentobox.bentobox.api.configuration.WorldSettings;
@@ -32,15 +36,17 @@
 import world.bentobox.bentobox.managers.IslandsManager;
 import world.bentobox.bentobox.managers.PlayersManager;
 import world.bentobox.bentobox.managers.RanksManager;
-import world.bentobox.bentobox.managers.RanksManagerBeforeClassTest;
 
 /**
  * @author tastybento
  *
  */
 @RunWith(PowerMockRunner.class)
-public class GameModePlaceholderTest extends RanksManagerBeforeClassTest {
+@PrepareForTest(RanksManager.class)
+public class GameModePlaceholderTest {
 
+    @Mock
+    private BentoBox plugin;
     @Mock
     private GameModeAddon addon;
     @Mock
@@ -56,7 +62,6 @@ public class GameModePlaceholderTest extends RanksManagerBeforeClassTest {
     private IslandWorldManager iwm;
     @Mock
     private IslandsManager im;
-    private RanksManager rm;
     @Mock
     private @Nullable Location location;
 
@@ -64,8 +69,7 @@ public class GameModePlaceholderTest extends RanksManagerBeforeClassTest {
      */
     @Before
     public void setUp() throws Exception {
-        super.setUp();
-        rm = new RanksManager();
+        PowerMockito.mockStatic(RanksManager.class, Mockito.RETURNS_MOCKS);
         uuid = UUID.randomUUID();
         when(addon.getPlayers()).thenReturn(pm);
         when(addon.getIslands()).thenReturn(im);
@@ -87,7 +91,6 @@ public void setUp() throws Exception {
         when(addon.getWorldSettings()).thenReturn(ws);
         when(pm.getName(any())).thenReturn("tastybento");
         when(plugin.getIWM()).thenReturn(iwm);
-        when(plugin.getRanksManager()).thenReturn(rm);
         when(user.getTranslation(anyString()))
                 .thenAnswer((Answer<String>) invocation -> invocation.getArgument(0, String.class));
         when(user.getLocation()).thenReturn(location);
@@ -160,7 +163,7 @@ public void testGetReplacerPlayer() {
         assertEquals("true", GameModePlaceholder.HAS_ISLAND.getReplacer().onReplace(addon, user, island));
         assertEquals("false", GameModePlaceholder.ON_ISLAND.getReplacer().onReplace(addon, user, island));
         assertEquals("true", GameModePlaceholder.OWNS_ISLAND.getReplacer().onReplace(addon, user, island));
-        assertEquals("ranks.owner", GameModePlaceholder.RANK.getReplacer().onReplace(addon, user, island));
+        assertEquals("", GameModePlaceholder.RANK.getReplacer().onReplace(addon, user, island));
         assertEquals("0", GameModePlaceholder.RESETS.getReplacer().onReplace(addon, user, island));
         assertEquals("0", GameModePlaceholder.RESETS_LEFT.getReplacer().onReplace(addon, user, island));
     }
diff --git a/src/test/java/world/bentobox/bentobox/managers/RanksManagerBeforeClassTest.java b/src/test/java/world/bentobox/bentobox/managers/RanksManagerBeforeClassTest.java
index 2c95e1e1d..ee4d3fb92 100644
--- a/src/test/java/world/bentobox/bentobox/managers/RanksManagerBeforeClassTest.java
+++ b/src/test/java/world/bentobox/bentobox/managers/RanksManagerBeforeClassTest.java
@@ -1,21 +1,17 @@
 package world.bentobox.bentobox.managers;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.when;
 
-import java.beans.IntrospectionException;
 import java.io.File;
 import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Comparator;
-import java.util.concurrent.CompletableFuture;
+import java.util.Map;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
@@ -26,7 +22,6 @@
 
 import world.bentobox.bentobox.BentoBox;
 import world.bentobox.bentobox.api.user.User;
-import world.bentobox.bentobox.database.AbstractDatabaseHandler;
 import world.bentobox.bentobox.database.DatabaseSetup;
 
 /**
@@ -34,31 +29,49 @@
  *
  */
 @RunWith(PowerMockRunner.class)
-@PrepareForTest({ BentoBox.class, DatabaseSetup.class })
+@PrepareForTest({ BentoBox.class, DatabaseSetup.class, RanksManager.class })
 public abstract class RanksManagerBeforeClassTest {
 
-    private static AbstractDatabaseHandler<Object> h;
+    // Constants that define the hard coded rank values
+    public static final String ADMIN_RANK_REF = "ranks.admin";
+    public static final String MOD_RANK_REF = "ranks.mod";
+    public static final String OWNER_RANK_REF = "ranks.owner";
+    public static final String SUB_OWNER_RANK_REF = "ranks.sub-owner";
+    public static final String MEMBER_RANK_REF = "ranks.member";
+    public static final String TRUSTED_RANK_REF = "ranks.trusted";
+    public static final String COOP_RANK_REF = "ranks.coop";
+    public static final String VISITOR_RANK_REF = "ranks.visitor";
+    public static final String BANNED_RANK_REF = "ranks.banned";
+    public static final int ADMIN_RANK = 10000;
+    public static final int MOD_RANK = 5000;
+    public static final int OWNER_RANK = 1000;
+    public static final int SUB_OWNER_RANK = 900;
+    public static final int MEMBER_RANK = 500;
+    public static final int TRUSTED_RANK = 400;
+    public static final int COOP_RANK = 200;
+    public static final int VISITOR_RANK = 0;
+    public static final int BANNED_RANK = -1;
+
+    // The store of ranks
+    public static final Map<String, Integer> DEFAULT_RANKS = Map.of(ADMIN_RANK_REF, ADMIN_RANK, MOD_RANK_REF, MOD_RANK,
+            OWNER_RANK_REF, OWNER_RANK, SUB_OWNER_RANK_REF, SUB_OWNER_RANK, MEMBER_RANK_REF, MEMBER_RANK,
+            TRUSTED_RANK_REF, TRUSTED_RANK, COOP_RANK_REF, COOP_RANK, VISITOR_RANK_REF, VISITOR_RANK, BANNED_RANK_REF,
+            BANNED_RANK);
 
     @Mock
     public BentoBox plugin;
-
-    @SuppressWarnings("unchecked")
-    @BeforeClass
-    public static void beforeClass() throws IllegalAccessException, InvocationTargetException, IntrospectionException {
-        // This has to be done beforeClass otherwise the tests will interfere with each other
-        h = mock(AbstractDatabaseHandler.class);
-        // Database
-        PowerMockito.mockStatic(DatabaseSetup.class);
-        DatabaseSetup dbSetup = mock(DatabaseSetup.class);
-        when(DatabaseSetup.getDatabase()).thenReturn(dbSetup);
-        when(dbSetup.getHandler(any())).thenReturn(h);
-        when(h.saveObject(any())).thenReturn(CompletableFuture.completedFuture(true));
-    }
+    @Mock
+    public RanksManager rm;
 
     @Before
     public void setUp() throws Exception {
         // Set up plugin
         Whitebox.setInternalState(BentoBox.class, "instance", plugin);
+        // RanksManager
+        PowerMockito.mockStatic(RanksManager.class, Mockito.RETURNS_MOCKS);
+        when(RanksManager.getInstance()).thenReturn(rm);
+        when(rm.getRanks()).thenReturn(DEFAULT_RANKS);
+        when(rm.getRank(anyInt())).thenReturn("");
     }
 
     @After
diff --git a/src/test/java/world/bentobox/bentobox/managers/RanksManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/RanksManagerTest.java
index aba92fdfb..9f1897ce9 100644
--- a/src/test/java/world/bentobox/bentobox/managers/RanksManagerTest.java
+++ b/src/test/java/world/bentobox/bentobox/managers/RanksManagerTest.java
@@ -3,15 +3,35 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
+import java.beans.IntrospectionException;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Comparator;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 
+import org.junit.After;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.powermock.api.mockito.PowerMockito;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.reflect.Whitebox;
 
+import world.bentobox.bentobox.BentoBox;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.bentobox.database.AbstractDatabaseHandler;
 import world.bentobox.bentobox.database.DatabaseSetup;
 
 /**
@@ -19,17 +39,46 @@
  *
  */
 @RunWith(PowerMockRunner.class)
-@PrepareForTest({ DatabaseSetup.class, })
-public class RanksManagerTest extends RanksManagerBeforeClassTest {
+@PrepareForTest({ BentoBox.class, DatabaseSetup.class })
+public class RanksManagerTest {
 
-    public static RanksManager ranksManager;
+    private static AbstractDatabaseHandler<Object> h;
+
+    @Mock
+    public BentoBox plugin;
+
+    @SuppressWarnings("unchecked")
+    @BeforeClass
+    public static void beforeClass() throws IllegalAccessException, InvocationTargetException, IntrospectionException {
+        // This has to be done beforeClass otherwise the tests will interfere with each other
+        h = mock(AbstractDatabaseHandler.class);
+        // Database
+        PowerMockito.mockStatic(DatabaseSetup.class);
+        DatabaseSetup dbSetup = mock(DatabaseSetup.class);
+        when(DatabaseSetup.getDatabase()).thenReturn(dbSetup);
+        when(dbSetup.getHandler(any())).thenReturn(h);
+        when(h.saveObject(any())).thenReturn(CompletableFuture.completedFuture(true));
+    }
 
-    /**
-     */
     @Before
     public void setUp() throws Exception {
-        super.setUp();
-        ranksManager = new RanksManager();
+        // Set up plugin
+        Whitebox.setInternalState(BentoBox.class, "instance", plugin);
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        User.clearUsers();
+        Mockito.framework().clearInlineMocks();
+        deleteAll(new File("database"));
+        deleteAll(new File("database_backup"));
+    }
+
+    private void deleteAll(File file) throws IOException {
+        if (file.exists()) {
+            Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+        }
+
     }
 
     /**
@@ -37,7 +86,7 @@ public void setUp() throws Exception {
      */
     @Test
     public void testAddRank() {
-        assertTrue(ranksManager.addRank("test.rank.reference", 750));
+        assertTrue(RanksManager.getInstance().addRank("test.rank.reference", 750));
     }
 
     /**
@@ -45,10 +94,10 @@ public void testAddRank() {
      */
     @Test
     public void testRemoveRank() {
-        assertTrue(ranksManager.addRank("test.rank.reference2", 650));
-        assertTrue(ranksManager.removeRank("test.rank.reference2"));
+        assertTrue(RanksManager.getInstance().addRank("test.rank.reference2", 650));
+        assertTrue(RanksManager.getInstance().removeRank("test.rank.reference2"));
         // Second time should fail
-        assertFalse(ranksManager.removeRank("test.rank.reference2"));
+        assertFalse(RanksManager.getInstance().removeRank("test.rank.reference2"));
     }
 
     /**
@@ -56,8 +105,8 @@ public void testRemoveRank() {
      */
     @Test
     public void testGetRankValue() {
-        ranksManager.addRank("test.rank.reference.value", 600);
-        assertEquals(600, ranksManager.getRankValue("test.rank.reference.value"));
+        RanksManager.getInstance().addRank("test.rank.reference.value", 600);
+        assertEquals(600, RanksManager.getInstance().getRankValue("test.rank.reference.value"));
     }
 
     /**
@@ -65,7 +114,7 @@ public void testGetRankValue() {
      */
     @Test
     public void testGetRanks() {
-        Map<String, Integer> ranks = ranksManager.getRanks();
+        Map<String, Integer> ranks = RanksManager.getInstance().getRanks();
         assertTrue(ranks.containsKey(RanksManager.BANNED_RANK_REF));
         assertTrue(ranks.containsKey(RanksManager.VISITOR_RANK_REF));
         assertTrue(ranks.containsKey(RanksManager.MEMBER_RANK_REF));
@@ -77,12 +126,12 @@ public void testGetRanks() {
      */
     @Test
     public void testGetNextRankValue() {
-        assertEquals(RanksManager.BANNED_RANK, ranksManager.getRankUpValue(-20));
-        assertEquals(RanksManager.VISITOR_RANK, ranksManager.getRankUpValue(RanksManager.BANNED_RANK));
-        assertEquals(RanksManager.COOP_RANK, ranksManager.getRankUpValue(RanksManager.VISITOR_RANK));
-        assertEquals(RanksManager.SUB_OWNER_RANK, ranksManager.getRankUpValue(RanksManager.MEMBER_RANK));
-        assertEquals(RanksManager.OWNER_RANK, ranksManager.getRankUpValue(RanksManager.OWNER_RANK));
-        assertEquals(RanksManager.OWNER_RANK, ranksManager.getRankUpValue(2000));
+        assertEquals(RanksManager.BANNED_RANK, RanksManager.getInstance().getRankUpValue(-20));
+        assertEquals(RanksManager.VISITOR_RANK, RanksManager.getInstance().getRankUpValue(RanksManager.BANNED_RANK));
+        assertEquals(RanksManager.COOP_RANK, RanksManager.getInstance().getRankUpValue(RanksManager.VISITOR_RANK));
+        assertEquals(RanksManager.SUB_OWNER_RANK, RanksManager.getInstance().getRankUpValue(800));
+        assertEquals(RanksManager.OWNER_RANK, RanksManager.getInstance().getRankUpValue(RanksManager.OWNER_RANK));
+        assertEquals(RanksManager.OWNER_RANK, RanksManager.getInstance().getRankUpValue(2000));
     }
 
     /**
@@ -91,10 +140,10 @@ public void testGetNextRankValue() {
     @Test
     public void testGetPreviousRankValue() {
         // Lowest rank is Visitor
-        assertEquals(RanksManager.VISITOR_RANK, ranksManager.getRankDownValue(-20));
-        assertEquals(RanksManager.VISITOR_RANK, ranksManager.getRankDownValue(RanksManager.VISITOR_RANK));
-        assertEquals(RanksManager.TRUSTED_RANK, ranksManager.getRankDownValue(RanksManager.MEMBER_RANK));
-        assertEquals(RanksManager.SUB_OWNER_RANK, ranksManager.getRankDownValue(RanksManager.OWNER_RANK));
+        assertEquals(RanksManager.VISITOR_RANK, RanksManager.getInstance().getRankDownValue(-20));
+        assertEquals(RanksManager.VISITOR_RANK, RanksManager.getInstance().getRankDownValue(RanksManager.VISITOR_RANK));
+        assertEquals(RanksManager.TRUSTED_RANK, RanksManager.getInstance().getRankDownValue(RanksManager.MEMBER_RANK));
+        assertEquals(RanksManager.SUB_OWNER_RANK, RanksManager.getInstance().getRankDownValue(RanksManager.OWNER_RANK));
     }
 
     /**
@@ -102,10 +151,10 @@ public void testGetPreviousRankValue() {
      */
     @Test
     public void testGetRank() {
-        assertEquals(RanksManager.BANNED_RANK_REF, ranksManager.getRank(RanksManager.BANNED_RANK));
-        assertEquals(RanksManager.VISITOR_RANK_REF, ranksManager.getRank(RanksManager.VISITOR_RANK));
-        assertEquals(RanksManager.MEMBER_RANK_REF, ranksManager.getRank(RanksManager.MEMBER_RANK));
-        assertEquals(RanksManager.OWNER_RANK_REF, ranksManager.getRank(RanksManager.OWNER_RANK));
-        assertEquals("", ranksManager.getRank(-999));
+        assertEquals(RanksManager.BANNED_RANK_REF, RanksManager.getInstance().getRank(RanksManager.BANNED_RANK));
+        assertEquals(RanksManager.VISITOR_RANK_REF, RanksManager.getInstance().getRank(RanksManager.VISITOR_RANK));
+        assertEquals(RanksManager.MEMBER_RANK_REF, RanksManager.getInstance().getRank(RanksManager.MEMBER_RANK));
+        assertEquals(RanksManager.OWNER_RANK_REF, RanksManager.getInstance().getRank(RanksManager.OWNER_RANK));
+        assertEquals("", RanksManager.getInstance().getRank(-999));
     }
 }

From 1958a71a03d122e9195cc90c6f819f8c75de6250 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Mon, 1 Jan 2024 11:31:01 +0900
Subject: [PATCH 08/22] Team GUI WIP

---
 .../island/team/IslandTeamCommand.java        | 69 +++++++++++++------
 src/main/resources/panels/team_panel.yml      | 13 ++--
 2 files changed, 56 insertions(+), 26 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index d1c9163d0..e1a836d1e 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -136,13 +136,34 @@ private void build() {
         panelBuilder.registerTypeBuilder("STATUS", this::createStatusButton);
         panelBuilder.registerTypeBuilder("MEMBER", this::createMemberButton);
         panelBuilder.registerTypeBuilder("INVITE", this::createInviteButton);
-        //panelBuilder.registerTypeBuilder("RANK", this::createRankButton);
+        panelBuilder.registerTypeBuilder("RANK", this::createRankButton);
         //panelBuilder.registerTypeBuilder("KICK", this::createKickButton);
 
         // Register unknown type builder.
         panelBuilder.build();
     }
 
+    private PanelItem createRankButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
+        PanelItemBuilder builder = new PanelItemBuilder();
+        builder.name("Rank");
+        builder.icon(Material.AMETHYST_SHARD);
+        builder.description("Rank shown = " + user.getTranslation(RanksManager.getInstance().getRank(rank)));
+        builder.clickHandler((panel, user, clickType, clickSlot) -> {
+            BentoBox.getInstance().logDebug("Rank = " + rank);
+            if (clickType.equals(ClickType.RIGHT)) {
+                rank = RanksManager.getInstance().getRankDownValue(rank);
+
+            } else {
+                rank = RanksManager.getInstance().getRankUpValue(rank);
+            }
+            BentoBox.getInstance().logDebug("New Rank = " + rank);
+            // Update panel after click
+            build();
+            return true;
+        });
+
+        return builder.build();
+    }
     /**
      * Create invite button panel item.
      *
@@ -198,21 +219,20 @@ private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel
         }
         return switch (rank) {
         case RanksManager.OWNER_RANK -> ownerView(template, slot);
-        default -> generalView(template, slot, rank);
+        default -> getMemberButton(rank, slot.slot(), template.actions());
         };
     }
 
-    private PanelItem generalView(ItemTemplateRecord template, ItemSlot slot, int rank2) {
-        getMemberButton(RanksManager.MEMBER_RANK_REF, RanksManager.MEMBER_RANK,
-                island.getMemberSet(rank2, false).size(), slot.slot(), template.actions());
-        return null;
-    }
-
+    /**
+     * The owner view shows all the ranks, in order
+     * @param template template reference
+     * @param slot slot to show
+     * @return panel item
+     */
     private PanelItem ownerView(ItemTemplateRecord template, ItemSlot slot) {
         if (slot.slot() == 0 && island.getOwner() != null) {
             // Owner
-            PanelItem item = getMemberButton(RanksManager.OWNER_RANK_REF, RanksManager.OWNER_RANK, 1, 1,
-                    template.actions());
+            PanelItem item = getMemberButton(RanksManager.OWNER_RANK, 1, template.actions());
             if (item != null) {
                 return item;
             }
@@ -224,8 +244,7 @@ private PanelItem ownerView(ItemTemplateRecord template, ItemSlot slot) {
 
         if (slot.slot() > 0 && slot.slot() < subOwnerCount + 1) {
             // Show sub owners
-            PanelItem item = getMemberButton(RanksManager.SUB_OWNER_RANK_REF, RanksManager.SUB_OWNER_RANK,
-                    subOwnerCount, slot.slot(), template.actions());
+            PanelItem item = getMemberButton(RanksManager.SUB_OWNER_RANK, slot.slot(), template.actions());
             if (item != null) {
                 return item;
             }
@@ -233,16 +252,14 @@ private PanelItem ownerView(ItemTemplateRecord template, ItemSlot slot) {
         }
         if (slot.slot() > subOwnerCount && slot.slot() < subOwnerCount + memberCount + 1) {
             // Show members 
-            PanelItem item = getMemberButton(RanksManager.MEMBER_RANK_REF, RanksManager.MEMBER_RANK, memberCount,
-                    slot.slot(), template.actions());
+            PanelItem item = getMemberButton(RanksManager.MEMBER_RANK, slot.slot(), template.actions());
             if (item != null) {
                 return item;
             }
         }
         if (slot.slot() > subOwnerCount + memberCount && slot.slot() < subOwnerCount + memberCount + trustedCount + 1) {
             // Show trusted
-            PanelItem item = getMemberButton(RanksManager.TRUSTED_RANK_REF, RanksManager.TRUSTED_RANK, trustedCount,
-                    slot.slot(), template.actions());
+            PanelItem item = getMemberButton(RanksManager.TRUSTED_RANK, slot.slot(), template.actions());
             if (item != null) {
                 return item;
             }
@@ -251,8 +268,7 @@ private PanelItem ownerView(ItemTemplateRecord template, ItemSlot slot) {
         if (slot.slot() > subOwnerCount + memberCount + trustedCount
                 && slot.slot() < subOwnerCount + memberCount + trustedCount + coopCount + 1) {
             // Show coops
-            PanelItem item = getMemberButton(RanksManager.COOP_RANK_REF, RanksManager.COOP_RANK, coopCount,
-                    slot.slot(), template.actions());
+            PanelItem item = getMemberButton(RanksManager.COOP_RANK, slot.slot(), template.actions());
             if (item != null) {
                 return item;
             }
@@ -262,8 +278,21 @@ private PanelItem ownerView(ItemTemplateRecord template, ItemSlot slot) {
 
     }
 
-    private PanelItem getMemberButton(String ref, int rank, long count, int slot, List<ActionRecords> actions) {
-        User player = island.getMemberSet(rank, false).stream().sorted().skip(slot - 1).limit(1L)
+    /**
+     * Shows a member's head
+     * @param rank - the rank to show
+     * @param slot - the slot number
+     * @param actions - actions that need to apply to this member button as provided by the template
+     * @return panel item
+     */
+    private PanelItem getMemberButton(int rank, int slot, List<ActionRecords> actions) {
+        if (slot == 0 && island.getOwner() != null) {
+            // Owner
+            return getMemberButton(RanksManager.OWNER_RANK, 1, actions);
+        }
+        long count = island.getMemberSet(rank, false).size();
+        String ref = RanksManager.getInstance().getRank(rank);
+        User player = island.getMemberSet(rank, false).stream().sorted().skip(slot - 1L).limit(1L)
                 .map(User::getInstance).findFirst().orElse(null);
         if (player != null) {
             if (player.isOnline()) {
diff --git a/src/main/resources/panels/team_panel.yml b/src/main/resources/panels/team_panel.yml
index f95f473c3..cad6ace1f 100644
--- a/src/main/resources/panels/team_panel.yml
+++ b/src/main/resources/panels/team_panel.yml
@@ -21,9 +21,9 @@ team_panel:
   # The content section contains details of each item/button in the panel. The numbers indicate the rows and then then columns of each item.
   content:
     # Row number
-    1:
+    0:
       # Column number
-      2:
+      1:
         # The data section is a key-value list of data relavent for this button. It is interpreted by the code implemented the panel.
         # The convention is to specify the type and the panel tab that will open if pressed. These are Enums in the code.
         data:
@@ -37,14 +37,15 @@ team_panel:
             click-type: UNKNOWN
             # tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
             tooltip: commands.island.team.gui.tips.click-to-view
-      4:
-        # Invite member
+      3:
+        # Change rank
         data:
-          type: INVITE
+          type: RANK
         actions:
           add:
             click-type: LEFT
-            tooltip: commands.island.team.gui.tips.click-to-invite
+            tooltip: commands.island.team.gui.tips.click-to-change-rank
+      
     2:
       2: member_button
       3: member_button

From 1d6556613e6f95bcd5cee64f8699ed34a83ee610 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Wed, 3 Jan 2024 15:55:31 +0900
Subject: [PATCH 09/22] Added support for kick, setowner, and leave.

---
 .../island/team/IslandTeamCommand.java        | 150 +++++++++++++-----
 .../island/team/IslandTeamKickCommand.java    |   2 +-
 .../island/team/IslandTeamLeaveCommand.java   |   2 +-
 .../team/IslandTeamSetownerCommand.java       |  13 +-
 .../island/team/IslandTeamUncoopCommand.java  |   2 +-
 .../island/team/IslandTeamUntrustCommand.java |   2 +-
 .../bentobox/bentobox/api/flags/Flag.java     |   1 -
 src/main/resources/locales/en-US.yml          |  18 ++-
 src/main/resources/panels/team_panel.yml      |  24 ++-
 9 files changed, 157 insertions(+), 57 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index e1a836d1e..1c5cbc031 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -8,17 +8,18 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 
 import org.bukkit.Bukkit;
 import org.bukkit.Material;
 import org.bukkit.OfflinePlayer;
+import org.bukkit.Sound;
 import org.bukkit.event.inventory.ClickType;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 
-import world.bentobox.bentobox.BentoBox;
 import world.bentobox.bentobox.api.commands.CompositeCommand;
 import world.bentobox.bentobox.api.events.IslandBaseEvent;
 import world.bentobox.bentobox.api.events.team.TeamEvent;
@@ -56,6 +57,16 @@ public class IslandTeamCommand extends CompositeCommand {
 
     private int rank = RanksManager.OWNER_RANK;
 
+    private IslandTeamKickCommand kickCommand;
+
+    private IslandTeamLeaveCommand leaveCommand;
+
+    private IslandTeamSetownerCommand setOwnerCommand;
+
+    private IslandTeamUncoopCommand uncoopCommand;
+
+    private IslandTeamUntrustCommand unTrustCommand;
+
     public IslandTeamCommand(CompositeCommand parent) {
         super(parent, "team");
         inviteMap = new HashMap<>();
@@ -68,24 +79,24 @@ public void setup() {
         setDescription("commands.island.team.description");
         // Register commands
         new IslandTeamInviteCommand(this);
-        new IslandTeamLeaveCommand(this);
-        new IslandTeamSetownerCommand(this);
-        new IslandTeamKickCommand(this);
+        leaveCommand = new IslandTeamLeaveCommand(this);
+        setOwnerCommand = new IslandTeamSetownerCommand(this);
+        kickCommand = new IslandTeamKickCommand(this);
         new IslandTeamInviteAcceptCommand(this);
         new IslandTeamInviteRejectCommand(this);
         if (RanksManager.getInstance().rankExists(RanksManager.COOP_RANK_REF)) {
             new IslandTeamCoopCommand(this);
-            new IslandTeamUncoopCommand(this);
+            uncoopCommand = new IslandTeamUncoopCommand(this);
         }
         if (RanksManager.getInstance().rankExists(RanksManager.TRUSTED_RANK_REF)) {
             new IslandTeamTrustCommand(this);
-            new IslandTeamUntrustCommand(this);
+            unTrustCommand = new IslandTeamUntrustCommand(this);
         }
         new IslandTeamPromoteCommand(this, "promote");
         new IslandTeamPromoteCommand(this, "demote");
 
         // Panels
-        getPlugin().saveResource("panels/team_panel.yml", false);
+        getPlugin().saveResource("panels/team_panel.yml", true);
     }
 
     @Override
@@ -145,18 +156,46 @@ private void build() {
 
     private PanelItem createRankButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
         PanelItemBuilder builder = new PanelItemBuilder();
-        builder.name("Rank");
+        builder.name(user.getTranslation("commands.island.team.gui.buttons.rank-filter.name"));
         builder.icon(Material.AMETHYST_SHARD);
-        builder.description("Rank shown = " + user.getTranslation(RanksManager.getInstance().getRank(rank)));
+        // Create description
+        RanksManager.getInstance().getRanks().forEach((reference, score) -> {
+            if (rank == RanksManager.OWNER_RANK && score > RanksManager.VISITOR_RANK
+                    && score <= RanksManager.OWNER_RANK) {
+                builder.description(user.getTranslation("protection.panel.flag-item.allowed-rank")
+                        + user.getTranslation(reference));
+            } else if (score > RanksManager.VISITOR_RANK && score < rank) {
+                builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank")
+                        + user.getTranslation(reference));
+            } else if (score <= RanksManager.OWNER_RANK && score > rank) {
+                builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank")
+                        + user.getTranslation(reference));
+            } else if (score == rank) {
+                builder.description(user.getTranslation("protection.panel.flag-item.allowed-rank")
+                        + user.getTranslation(reference));
+            }
+        });
+        builder.description(user.getTranslation("commands.island.team.gui.buttons.rank-filter.description"));
         builder.clickHandler((panel, user, clickType, clickSlot) -> {
-            BentoBox.getInstance().logDebug("Rank = " + rank);
-            if (clickType.equals(ClickType.RIGHT)) {
+            if (clickType.equals(ClickType.LEFT)) {
                 rank = RanksManager.getInstance().getRankDownValue(rank);
-
-            } else {
+                if (rank <= RanksManager.VISITOR_RANK) {
+                    rank = RanksManager.OWNER_RANK;
+                    user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
+                } else {
+                    user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
+                }
+            }
+            if (clickType.equals(ClickType.RIGHT)) {
                 rank = RanksManager.getInstance().getRankUpValue(rank);
+                if (rank >= RanksManager.OWNER_RANK) {
+                    rank = RanksManager.getInstance().getRankUpValue(RanksManager.VISITOR_RANK);
+                    user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
+                } else {
+                    user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
+                }
             }
-            BentoBox.getInstance().logDebug("New Rank = " + rank);
+
             // Update panel after click
             build();
             return true;
@@ -279,35 +318,48 @@ private PanelItem ownerView(ItemTemplateRecord template, ItemSlot slot) {
     }
 
     /**
-     * Shows a member's head
-     * @param rank - the rank to show
+     * Shows a member's head. The clicks available will depend on who is viewing.
+     * @param targetRank - the rank to show
      * @param slot - the slot number
      * @param actions - actions that need to apply to this member button as provided by the template
      * @return panel item
      */
-    private PanelItem getMemberButton(int rank, int slot, List<ActionRecords> actions) {
+    private PanelItem getMemberButton(int targetRank, int slot, List<ActionRecords> actions) {
         if (slot == 0 && island.getOwner() != null) {
             // Owner
             return getMemberButton(RanksManager.OWNER_RANK, 1, actions);
         }
-        long count = island.getMemberSet(rank, false).size();
-        String ref = RanksManager.getInstance().getRank(rank);
-        User player = island.getMemberSet(rank, false).stream().sorted().skip(slot - 1L).limit(1L)
+        String ref = RanksManager.getInstance().getRank(targetRank);
+        User member = island.getMemberSet(targetRank, false).stream().sorted().skip(slot - 1L).limit(1L)
                 .map(User::getInstance).findFirst().orElse(null);
-        if (player != null) {
-            if (player.isOnline()) {
-                return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName())
-                        .description(
-                                user.getTranslation("commands.island.team.info.rank-layout.generic", TextVariables.RANK,
-                                        user.getTranslation(ref), TextVariables.NUMBER, String.valueOf(count)))
-                        .clickHandler((panel, user, clickType, i) -> clickListener(panel, user, clickType, i, player,
+        // Make button description depending on viewer
+        List<String> desc = new ArrayList<>();
+        int userRank = Objects.requireNonNull(island).getRank(user);
+        if (userRank >= island.getRankCommand(this.getLabel() + " kick") && !user.equals(member)) {
+            // Add the tooltip for kicking
+            actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("kick")).map(ActionRecords::tooltip)
+                    .findFirst().map(user::getTranslation).ifPresent(desc::add);
+        }
+        if (!user.equals(member) && userRank >= RanksManager.OWNER_RANK && targetRank >= RanksManager.MEMBER_RANK) {
+            // Add the tooltip for setowner
+            actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("setowner")).map(ActionRecords::tooltip)
+                    .findFirst().map(user::getTranslation).ifPresent(desc::add);
+        }
+        if (member != null) {
+            if (member.isOnline()) {
+                desc.add(0, user.getTranslation(ref));
+                return new PanelItemBuilder().icon(member.getName()).name(member.getDisplayName())
+                        .description(desc)
+                        .clickHandler((panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member,
                                 actions))
                         .build();
             } else {
                 // Offline player
-                return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName())
-                        .description(offlinePlayerStatus(user, Bukkit.getOfflinePlayer(player.getUniqueId())))
-                        .clickHandler((panel, user, clickType, i) -> clickListener(panel, user, clickType, i, player,
+                desc.add(0, user.getTranslation(ref));
+                desc.add(1, offlinePlayerStatus(user, Bukkit.getOfflinePlayer(member.getUniqueId())));
+                return new PanelItemBuilder().icon(member.getName()).name(member.getDisplayName())
+                        .description(desc)
+                        .clickHandler((panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member,
                                 actions))
                         .build();
             }
@@ -315,20 +367,32 @@ private PanelItem getMemberButton(int rank, int slot, List<ActionRecords> action
         return null;
     }
 
-    private boolean clickListener(Panel panel, User user, ClickType clickType, int i, User player,
+    private boolean clickListener(Panel panel, User clicker, ClickType clickType, int i, User member,
             List<ActionRecords> actions) {
+        int rank = Objects.requireNonNull(island).getRank(clicker);
         for (ItemTemplateRecord.ActionRecords action : actions) {
             if (clickType == action.clickType() || action.clickType() == ClickType.UNKNOWN) {
                 switch (action.actionType().toUpperCase(Locale.ENGLISH)) {
                 case "KICK" -> {
-                    // Kick the player
-                    if (!player.equals(user)) {
-                        this.user.closeInventory();
-                        BentoBox.getInstance()
-                                .logDebug(this.getTopLabel() + " " + this.getLabel() + " kick " + player.getName());
-                        user.performCommand(this.getTopLabel() + " " + this.getLabel() + " kick " + player.getName());
+                    // Kick the player, or uncoop, or untrust
+                    if (!member.equals(clicker) && rank >= island.getRankCommand(this.getLabel() + " kick")) {
+                        clicker.closeInventory();
+                        removePlayer(clicker, member);
+                        clicker.getPlayer().playSound(clicker.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, 1F);
+                    }
+                }
+                case "SETOWNER" -> {
+                    // Make the player the leader of the island
+                    if (!member.equals(clicker) && clicker.getUniqueId().equals(island.getOwner())) {
+                        clicker.closeInventory();
+                        this.setOwnerCommand.setOwner(clicker, member.getUniqueId());
+                    }
+                }
+                case "LEAVE" -> {
+                    if (member.equals(clicker) && !clicker.getUniqueId().equals(island.getOwner())) {
+                        clicker.closeInventory();
+                        leaveCommand.leave(clicker);
                     }
-
                 }
                 }
             }
@@ -336,6 +400,16 @@ private boolean clickListener(Panel panel, User user, ClickType clickType, int i
         return true;
     }
 
+    private void removePlayer(User clicker, User member) {
+        // If member then kick, if coop, uncoop, if trusted, then untrust
+        switch (island.getRank(member)) {
+        case RanksManager.COOP_RANK -> this.uncoopCommand.unCoopCmd(user, member.getUniqueId());
+        case RanksManager.TRUSTED_RANK -> this.unTrustCommand.unTrustCmd(user, member.getUniqueId());
+        default -> kickCommand.kick(clicker, member.getUniqueId());
+        }
+
+    }
+
     private List<String> showMembers() {
         List<String> message = new ArrayList<>();
         // Gather online members
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java
index 9c27b7baf..6024c7426 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java
@@ -84,7 +84,7 @@ public boolean execute(User user, String label, List<String> args) {
         }
     }
 
-    private void kick(User user, UUID targetUUID) {
+    protected void kick(User user, UUID targetUUID) {
         User target = User.getInstance(targetUUID);
         Island oldIsland = Objects.requireNonNull(getIslands().getIsland(getWorld(), targetUUID)); // Should never be
         // null because of
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamLeaveCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamLeaveCommand.java
index fc39559f0..8e34ba1c9 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamLeaveCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamLeaveCommand.java
@@ -65,7 +65,7 @@ private void showResets(User user) {
 
     }
 
-    private void leave(User user) {
+    protected void leave(User user) {
         Island island = getIslands().getIsland(getWorld(), user);
         if (island == null) {
             user.sendMessage("general.errors.no-island");
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java
index 842a2134f..0c792fe6b 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java
@@ -69,19 +69,24 @@ public boolean canExecute(User user, String label, List<String> args) {
 
     @Override
     public boolean execute(User user, String label, List<String> args) {
+        return setOwner(user, targetUUID);
+
+    }
+
+    protected boolean setOwner(User user, @Nullable UUID targetUUID2) {
         // Fire event so add-ons can run commands, etc.
         Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
         // Fire event so add-ons can run commands, etc.
         IslandBaseEvent e = TeamEvent.builder().island(island).reason(TeamEvent.Reason.SETOWNER)
-                .involvedPlayer(targetUUID).build();
+                .involvedPlayer(targetUUID2).build();
         if (e.isCancelled()) {
             return false;
         }
-        getIslands().setOwner(getWorld(), user, targetUUID);
+        getIslands().setOwner(getWorld(), user, targetUUID2);
         // Call the event for the new owner
-        IslandEvent.builder().island(island).involvedPlayer(targetUUID).admin(false)
+        IslandEvent.builder().island(island).involvedPlayer(targetUUID2).admin(false)
                 .reason(IslandEvent.Reason.RANK_CHANGE)
-                .rankChange(island.getRank(User.getInstance(targetUUID)), RanksManager.OWNER_RANK).build();
+                .rankChange(island.getRank(User.getInstance(targetUUID2)), RanksManager.OWNER_RANK).build();
         // Call the event for the previous owner
         IslandEvent.builder().island(island).involvedPlayer(user.getUniqueId()).admin(false)
                 .reason(IslandEvent.Reason.RANK_CHANGE).rankChange(RanksManager.OWNER_RANK, RanksManager.SUB_OWNER_RANK)
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUncoopCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUncoopCommand.java
index 6c9533110..c79b3e219 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUncoopCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUncoopCommand.java
@@ -68,7 +68,7 @@ public boolean execute(User user, String label, List<String> args) {
         return unCoopCmd(user, targetUUID);
     }
 
-    private boolean unCoopCmd(User user, UUID targetUUID) {
+    protected boolean unCoopCmd(User user, UUID targetUUID) {
         // Player cannot uncoop themselves
         if (user.getUniqueId().equals(targetUUID)) {
             user.sendMessage("commands.island.team.uncoop.cannot-uncoop-yourself");
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUntrustCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUntrustCommand.java
index 05581286c..11bdb82aa 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUntrustCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamUntrustCommand.java
@@ -68,7 +68,7 @@ public boolean execute(User user, String label, List<String> args) {
         return unTrustCmd(user, targetUUID);
     }
 
-    private boolean unTrustCmd(User user, UUID targetUUID) {
+    protected boolean unTrustCmd(User user, UUID targetUUID) {
         // Player cannot untrust themselves
         if (user.getUniqueId().equals(targetUUID)) {
             user.sendMessage("commands.island.team.untrust.cannot-untrust-yourself");
diff --git a/src/main/java/world/bentobox/bentobox/api/flags/Flag.java b/src/main/java/world/bentobox/bentobox/api/flags/Flag.java
index abb185b5f..cac99e562 100644
--- a/src/main/java/world/bentobox/bentobox/api/flags/Flag.java
+++ b/src/main/java/world/bentobox/bentobox/api/flags/Flag.java
@@ -430,7 +430,6 @@ private PanelItemBuilder createProtectionFlag(BentoBox plugin, User user, Island
             // Protection flag
             pib.description(user.getTranslation("protection.panel.flag-item.description-layout",
                     TextVariables.DESCRIPTION, user.getTranslation(getDescriptionReference())));
-            plugin.getRanksManager();
             RanksManager.getInstance().getRanks().forEach((reference, score) -> {
                 if (score > RanksManager.BANNED_RANK && score < island.getFlag(this)) {
                     pib.description(user.getTranslation("protection.panel.flag-item.blocked-rank") + user.getTranslation(reference));
diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml
index c19344d20..2ab0bbba7 100644
--- a/src/main/resources/locales/en-US.yml
+++ b/src/main/resources/locales/en-US.yml
@@ -619,10 +619,22 @@ commands:
           status:
             name: Status
             description: The status of the team
+          rank-filter:
+            name: Rank Filter
+            description: &a Click to cycle ranks
         tips:
-          click-to-view: Click to view
-          right-click-to-kick: Right click to kick player - requires confirmation
-          click-to-invite: Click to invite a team member
+          shift-right:
+            kick: |
+              &a Shift-right click
+              &a to kick player
+            leave: |
+              &a Shift-right click
+              &a to leave team
+          shift-left:
+            setowner: |
+              &a Shift-left to
+              &a set owner to
+              &a this player
       info:
         description: display detailed info about your team
         member-layout:
diff --git a/src/main/resources/panels/team_panel.yml b/src/main/resources/panels/team_panel.yml
index cad6ace1f..a6611ccc8 100644
--- a/src/main/resources/panels/team_panel.yml
+++ b/src/main/resources/panels/team_panel.yml
@@ -21,7 +21,7 @@ team_panel:
   # The content section contains details of each item/button in the panel. The numbers indicate the rows and then then columns of each item.
   content:
     # Row number
-    0:
+    1:
       # Column number
       1:
         # The data section is a key-value list of data relavent for this button. It is interpreted by the code implemented the panel.
@@ -38,14 +38,17 @@ team_panel:
             # tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
             tooltip: commands.island.team.gui.tips.click-to-view
       3:
-        # Change rank
+        # Rank filter
         data:
           type: RANK
+          name: commands.island.team.gui.buttons.rank-filter
         actions:
-          add:
+          cycle-up:
             click-type: LEFT
-            tooltip: commands.island.team.gui.tips.click-to-change-rank
-      
+            tooltip: commands.island.team.gui.tips.right-click.rank
+          cycle-down:
+            click-type: RIGHT
+            tooltip: commands.island.team.gui.tips.right-click.rank   
     2:
       2: member_button
       3: member_button
@@ -103,6 +106,13 @@ team_panel:
         # Each action has an arbitrary descriptive name to define it. 
         kick:
           # The click-type is the same as the bukkit {@link org.bukkit.event.inventory.ClickType}. UNKNOWN is the default.
-          click-type: RIGHT
+          click-type: SHIFT_RIGHT
           # tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
-          tooltip: commands.island.team.gui.tips.right-click-to-kick
\ No newline at end of file
+          tooltip: commands.island.team.gui.tips.shift-right.kick
+        leave:
+          click-type: SHIFT_RIGHT
+          tooltip: commands.island.team.gui.tips.shift-right.leave
+        setowner:
+          click-type: SHIFT_LEFT
+          tooltip: commands.island.team.gui.tips.shift-left.setowner
+          
\ No newline at end of file

From 7639c59db9d2a0d1a2f3e3ee365a661ca03ee840 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Wed, 3 Jan 2024 18:42:12 +0900
Subject: [PATCH 10/22] Added support for accepting and rejecting an invite.

---
 .../island/team/IslandTeamCommand.java        | 106 ++++++++++++++----
 .../team/IslandTeamInviteAcceptCommand.java   |  28 ++---
 src/main/resources/locales/en-US.yml          |  14 ++-
 src/main/resources/panels/team_panel.yml      |  14 ++-
 4 files changed, 123 insertions(+), 39 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index 1c5cbc031..d45fd9a33 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -17,9 +17,11 @@
 import org.bukkit.OfflinePlayer;
 import org.bukkit.Sound;
 import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 
+import world.bentobox.bentobox.BentoBox;
 import world.bentobox.bentobox.api.commands.CompositeCommand;
 import world.bentobox.bentobox.api.events.IslandBaseEvent;
 import world.bentobox.bentobox.api.events.team.TeamEvent;
@@ -32,6 +34,7 @@
 import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder;
 import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord;
 import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecords;
+import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem;
 import world.bentobox.bentobox.api.user.User;
 import world.bentobox.bentobox.database.objects.Island;
 import world.bentobox.bentobox.managers.RanksManager;
@@ -67,6 +70,14 @@ public class IslandTeamCommand extends CompositeCommand {
 
     private IslandTeamUntrustCommand unTrustCommand;
 
+    private @Nullable TemplateItem border;
+
+    private @Nullable TemplateItem background;
+
+    private IslandTeamInviteAcceptCommand acceptCommand;
+
+    private IslandTeamInviteRejectCommand rejectCommand;
+
     public IslandTeamCommand(CompositeCommand parent) {
         super(parent, "team");
         inviteMap = new HashMap<>();
@@ -82,8 +93,8 @@ public void setup() {
         leaveCommand = new IslandTeamLeaveCommand(this);
         setOwnerCommand = new IslandTeamSetownerCommand(this);
         kickCommand = new IslandTeamKickCommand(this);
-        new IslandTeamInviteAcceptCommand(this);
-        new IslandTeamInviteRejectCommand(this);
+        acceptCommand = new IslandTeamInviteAcceptCommand(this);
+        rejectCommand = new IslandTeamInviteRejectCommand(this);
         if (RanksManager.getInstance().rankExists(RanksManager.COOP_RANK_REF)) {
             new IslandTeamCoopCommand(this);
             uncoopCommand = new IslandTeamUncoopCommand(this);
@@ -105,6 +116,11 @@ public boolean execute(User user, String label, List<String> args) {
         // Player issuing the command must have an island
         island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
         if (island == null) {
+            if (isInvited(user.getUniqueId())) {
+                // Player has an invite, so show the invite
+                build();
+                return true;
+            }
             user.sendMessage("general.errors.no-island");
             return false;
         }
@@ -146,10 +162,11 @@ private void build() {
 
         panelBuilder.registerTypeBuilder("STATUS", this::createStatusButton);
         panelBuilder.registerTypeBuilder("MEMBER", this::createMemberButton);
-        panelBuilder.registerTypeBuilder("INVITE", this::createInviteButton);
+        panelBuilder.registerTypeBuilder("INVITED", this::createInvitedButton);
         panelBuilder.registerTypeBuilder("RANK", this::createRankButton);
         //panelBuilder.registerTypeBuilder("KICK", this::createKickButton);
-
+        border = panelBuilder.getPanelTemplate().border();
+        background = panelBuilder.getPanelTemplate().background();
         // Register unknown type builder.
         panelBuilder.build();
     }
@@ -203,24 +220,61 @@ private PanelItem createRankButton(ItemTemplateRecord template, TemplatedPanel.I
 
         return builder.build();
     }
+
     /**
-     * Create invite button panel item.
+     * Create invited button panel item.
      *
      * @param template the template
      * @param slot     the slot
      * @return the panel item
      */
-    private PanelItem createInviteButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
+    private PanelItem createInvitedButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
         PanelItemBuilder builder = new PanelItemBuilder();
-        // Player issuing the command must have an island
-        Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
-        if (island == null) {
-            return builder.icon(Material.BARRIER).name(user.getTranslation("general.errors.no-island")).build();
+        if (isInvited(user.getUniqueId())) {
+            Invite invite = getInvite(user.getUniqueId());
+            User inviter = User.getInstance(invite.getInviter());
+            String name = inviter.getName();
+            builder.icon(inviter.getName());
+            builder.name("Invitation");
+            builder.description(switch (invite.getType()) {
+            case COOP ->
+                List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you.coop", TextVariables.NAME,
+                        name));
+            case TRUST ->
+                List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you.trust",
+                        TextVariables.NAME, name));
+            default ->
+                List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you", TextVariables.NAME,
+                        name), user.getTranslation("commands.island.team.invite.accept.confirmation"));
+            });
+            // Add all the tool tips
+            builder.description(template.actions().stream()
+                    .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name")
+                            + " "
+                            + user.getTranslation(ar.tooltip()))
+                    .toList());
+            builder.clickHandler((panel, user, clickType, clickSlot) -> {
+                if (clickType.equals(ClickType.SHIFT_LEFT)) {
+                    // Accept
+                    switch (invite.getType()) {
+                    case COOP -> this.acceptCommand.acceptCoopInvite(user, invite);
+                    case TRUST -> this.acceptCommand.acceptTrustInvite(user, invite);
+                    default -> this.acceptCommand.acceptTeamInvite(user, invite);
+                    }
+                    user.closeInventory();
+                }
+                if (clickType.equals(ClickType.SHIFT_RIGHT)) {
+                    // Reject
+                    BentoBox.getInstance().logDebug("Reject");
+                    this.rejectCommand.execute(user, "", List.of());
+                    user.closeInventory();
+                }
+                return true;
+            });
+        } else {
+            return this.getBlankBorder();
         }
-        // The player must be able to invite a player
-
-        return builder.icon(user.getName()).name(user.getTranslation("commands.island.team.gui.buttons.status.name"))
-                .description(showMembers()).build();
+        return builder.build();
     }
 
     /**
@@ -235,13 +289,24 @@ private PanelItem createStatusButton(ItemTemplateRecord template, TemplatedPanel
         // Player issuing the command must have an island
         Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
         if (island == null) {
-            return builder.icon(Material.BARRIER).name(user.getTranslation("general.errors.no-island")).build();
+            return getBlankBorder();
         }
 
         return builder.icon(user.getName()).name(user.getTranslation("commands.island.team.gui.buttons.status.name"))
                 .description(showMembers()).build();
     }
 
+    private PanelItem getBlankBorder() {
+        return new PanelItemBuilder().icon(Objects.requireNonNullElse(border.icon(), new ItemStack(Material.BARRIER)))
+                .name((Objects.requireNonNullElse(border.title(), ""))).build();
+    }
+
+    private PanelItem getBlankBackground() {
+        return new PanelItemBuilder()
+                .icon(Objects.requireNonNullElse(background.icon(), new ItemStack(Material.BARRIER)))
+                .name((Objects.requireNonNullElse(background.title(), ""))).build();
+    }
+
     /**
      * Create member button panel item.
      *
@@ -253,8 +318,7 @@ private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel
         // Player issuing the command must have an island
         Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
         if (island == null) {
-            return new PanelItemBuilder().icon(Material.BARRIER).name(user.getTranslation("general.errors.no-island"))
-                    .build();
+            return this.getBlankBackground();
         }
         return switch (rank) {
         case RanksManager.OWNER_RANK -> ownerView(template, slot);
@@ -337,12 +401,16 @@ private PanelItem getMemberButton(int targetRank, int slot, List<ActionRecords>
         int userRank = Objects.requireNonNull(island).getRank(user);
         if (userRank >= island.getRankCommand(this.getLabel() + " kick") && !user.equals(member)) {
             // Add the tooltip for kicking
-            actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("kick")).map(ActionRecords::tooltip)
+            actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("kick"))
+                    .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name()) + " "
+                            + user.getTranslation(ar.tooltip()))
                     .findFirst().map(user::getTranslation).ifPresent(desc::add);
         }
         if (!user.equals(member) && userRank >= RanksManager.OWNER_RANK && targetRank >= RanksManager.MEMBER_RANK) {
             // Add the tooltip for setowner
-            actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("setowner")).map(ActionRecords::tooltip)
+            actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("setowner"))
+                    .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name()) + " "
+                            + user.getTranslation(ar.tooltip()))
                     .findFirst().map(user::getTranslation).ifPresent(desc::add);
         }
         if (member != null) {
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java
index b93d52b8c..576956b63 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java
@@ -23,7 +23,6 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
 
     private static final String INVALID_INVITE = "commands.island.team.invite.errors.invalid-invite";
     private final IslandTeamCommand itc;
-    private UUID playerUUID;
 
     public IslandTeamInviteAcceptCommand(IslandTeamCommand islandTeamCommand) {
         super(islandTeamCommand, "accept");
@@ -39,7 +38,7 @@ public void setup() {
 
     @Override
     public boolean canExecute(User user, String label, List<String> args) {
-        playerUUID = user.getUniqueId();
+        UUID playerUUID = user.getUniqueId();
         // Check if player has been invited
         if (!itc.isInvited(playerUUID)) {
             user.sendMessage("commands.island.team.invite.errors.none-invited-you");
@@ -79,7 +78,7 @@ public boolean canExecute(User user, String label, List<String> args) {
     @Override
     public boolean execute(User user, String label, List<String> args) {
         // Get the invite
-        Invite invite = itc.getInvite(playerUUID);
+        Invite invite = itc.getInvite(user.getUniqueId());
         switch (invite.getType()) {
         case COOP -> askConfirmation(user, () -> acceptCoopInvite(user, invite));
         case TRUST -> askConfirmation(user, () -> acceptTrustInvite(user, invite));
@@ -89,9 +88,9 @@ public boolean execute(User user, String label, List<String> args) {
         return true;
     }
 
-    private void acceptTrustInvite(User user, Invite invite) {
+    void acceptTrustInvite(User user, Invite invite) {
         // Remove the invite
-        itc.removeInvite(playerUUID);
+        itc.removeInvite(user.getUniqueId());
         User inviter = User.getInstance(invite.getInviter());
         Island island = invite.getIsland();
         if (island != null) {
@@ -115,9 +114,9 @@ private void acceptTrustInvite(User user, Invite invite) {
         }
     }
 
-    private void acceptCoopInvite(User user, Invite invite) {
+    void acceptCoopInvite(User user, Invite invite) {
         // Remove the invite
-        itc.removeInvite(playerUUID);
+        itc.removeInvite(user.getUniqueId());
         User inviter = User.getInstance(invite.getInviter());
         Island island = invite.getIsland();
         if (island != null) {
@@ -141,11 +140,11 @@ private void acceptCoopInvite(User user, Invite invite) {
         }
     }
 
-    private void acceptTeamInvite(User user, Invite invite) {
+    void acceptTeamInvite(User user, Invite invite) {
         // Remove the invite
-        itc.removeInvite(playerUUID);
+        itc.removeInvite(user.getUniqueId());
         // Get the player's island - may be null if the player has no island
-        Set<Island> islands = getIslands().getIslands(getWorld(), playerUUID);
+        Set<Island> islands = getIslands().getIslands(getWorld(), user.getUniqueId());
         // Get the team's island
         Island teamIsland = invite.getIsland();
         if (teamIsland == null) {
@@ -158,11 +157,11 @@ private void acceptTeamInvite(User user, Invite invite) {
             return;
         }
         // Remove player as owner of the old island
-        getIslands().removePlayer(getWorld(), playerUUID);
+        getIslands().removePlayer(getWorld(), user.getUniqueId());
         // Remove money inventory etc. for leaving
         cleanPlayer(user);
         // Add the player as a team member of the new island
-        getIslands().setJoinTeam(teamIsland, playerUUID);
+        getIslands().setJoinTeam(teamIsland, user.getUniqueId());
         // Move player to team's island
         getIslands().homeTeleportAsync(getWorld(), user.getPlayer()).thenRun(() -> {
             // Delete the old islands
@@ -178,7 +177,7 @@ private void acceptTeamInvite(User user, Invite invite) {
         });
         // Reset deaths
         if (getIWM().isTeamJoinDeathReset(getWorld())) {
-            getPlayers().setDeaths(getWorld(), playerUUID, 0);
+            getPlayers().setDeaths(getWorld(), user.getUniqueId(), 0);
         }
         user.sendMessage("commands.island.team.invite.accept.you-joined-island", TextVariables.LABEL, getTopLabel());
         User inviter = User.getInstance(invite.getInviter());
@@ -188,7 +187,8 @@ private void acceptTeamInvite(User user, Invite invite) {
         }
         getIslands().save(teamIsland);
         // Fire event
-        TeamEvent.builder().island(teamIsland).reason(TeamEvent.Reason.JOINED).involvedPlayer(playerUUID).build();
+        TeamEvent.builder().island(teamIsland).reason(TeamEvent.Reason.JOINED).involvedPlayer(user.getUniqueId())
+                .build();
         IslandEvent.builder().island(teamIsland).involvedPlayer(user.getUniqueId()).admin(false)
                 .reason(IslandEvent.Reason.RANK_CHANGE).rankChange(teamIsland.getRank(user), RanksManager.MEMBER_RANK)
                 .build();
diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml
index 2ab0bbba7..3e1e3ae69 100644
--- a/src/main/resources/locales/en-US.yml
+++ b/src/main/resources/locales/en-US.yml
@@ -623,16 +623,20 @@ commands:
             name: Rank Filter
             description: &a Click to cycle ranks
         tips:
-          shift-right:
+          SHIFT_RIGHT:
+            name: &b Right Shift
+            reject: &a click to reject
             kick: |
-              &a Shift-right click
+              &a click
               &a to kick player
             leave: |
-              &a Shift-right click
+              &a click
               &a to leave team
-          shift-left:
+          SHIFT_LEFT:
+            name: &b Left Shift
+            accept: &a click to accept         
             setowner: |
-              &a Shift-left to
+              &a click to
               &a set owner to
               &a this player
       info:
diff --git a/src/main/resources/panels/team_panel.yml b/src/main/resources/panels/team_panel.yml
index a6611ccc8..498f382fa 100644
--- a/src/main/resources/panels/team_panel.yml
+++ b/src/main/resources/panels/team_panel.yml
@@ -17,7 +17,7 @@ team_panel:
     title: "&b&r"  # Empty text
   # This tag indicates which rows in the panel must be shown. The panel will be sized vertically accordingly. This does not include the borders.
   # This can be a list and rows must be between 1 and 6, if used.
-  force-shown: [1, 2]
+  force-shown: []
   # The content section contains details of each item/button in the panel. The numbers indicate the rows and then then columns of each item.
   content:
     # Row number
@@ -49,6 +49,18 @@ team_panel:
           cycle-down:
             click-type: RIGHT
             tooltip: commands.island.team.gui.tips.right-click.rank   
+      5:
+        # Invited button - this appears if you have been invited to join a team
+        data:
+          type: INVITED
+          name: commands.island.team.gui.buttons.invited
+        actions:
+          accept:
+            click-type: SHIFT_LEFT
+            tooltip: commands.island.team.gui.tips.SHIFT_LEFT.accept
+          reject:
+            click-type: SHIFT_RIGHT
+            tooltip: commands.island.team.gui.tips.SHIFT_RIGHT.reject
     2:
       2: member_button
       3: member_button

From de28bae47d8531f5bc1eb9b88cba917062ce1820 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Wed, 3 Jan 2024 21:42:53 +0900
Subject: [PATCH 11/22] Fixed bugs with text and operations.

---
 .../island/team/IslandTeamCommand.java        | 78 ++++++++++++-------
 src/main/resources/locales/en-US.yml          | 38 ++++-----
 src/main/resources/panels/team_panel.yml      |  6 +-
 3 files changed, 72 insertions(+), 50 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index d45fd9a33..8b0334189 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -172,6 +172,10 @@ private void build() {
     }
 
     private PanelItem createRankButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
+        // If there is no island, the do not show this icon
+        if (island == null) {
+            return this.getBlankBorder();
+        }
         PanelItemBuilder builder = new PanelItemBuilder();
         builder.name(user.getTranslation("commands.island.team.gui.buttons.rank-filter.name"));
         builder.icon(Material.AMETHYST_SHARD);
@@ -230,12 +234,12 @@ private PanelItem createRankButton(ItemTemplateRecord template, TemplatedPanel.I
      */
     private PanelItem createInvitedButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
         PanelItemBuilder builder = new PanelItemBuilder();
-        if (isInvited(user.getUniqueId())) {
+        if (isInvited(user.getUniqueId()) && user.hasPermission(this.acceptCommand.getPermission())) {
             Invite invite = getInvite(user.getUniqueId());
             User inviter = User.getInstance(invite.getInviter());
             String name = inviter.getName();
             builder.icon(inviter.getName());
-            builder.name("Invitation");
+            builder.name(user.getTranslation("commands.island.team.gui.buttons.invitation"));
             builder.description(switch (invite.getType()) {
             case COOP ->
                 List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you.coop", TextVariables.NAME,
@@ -254,7 +258,7 @@ private PanelItem createInvitedButton(ItemTemplateRecord template, TemplatedPane
                             + user.getTranslation(ar.tooltip()))
                     .toList());
             builder.clickHandler((panel, user, clickType, clickSlot) -> {
-                if (clickType.equals(ClickType.SHIFT_LEFT)) {
+                if (clickType.equals(ClickType.SHIFT_LEFT) && user.hasPermission(this.acceptCommand.getPermission())) {
                     // Accept
                     switch (invite.getType()) {
                     case COOP -> this.acceptCommand.acceptCoopInvite(user, invite);
@@ -263,9 +267,8 @@ private PanelItem createInvitedButton(ItemTemplateRecord template, TemplatedPane
                     }
                     user.closeInventory();
                 }
-                if (clickType.equals(ClickType.SHIFT_RIGHT)) {
+                if (clickType.equals(ClickType.SHIFT_RIGHT) && user.hasPermission(this.rejectCommand.getPermission())) {
                     // Reject
-                    BentoBox.getInstance().logDebug("Reject");
                     this.rejectCommand.execute(user, "", List.of());
                     user.closeInventory();
                 }
@@ -399,19 +402,31 @@ private PanelItem getMemberButton(int targetRank, int slot, List<ActionRecords>
         // Make button description depending on viewer
         List<String> desc = new ArrayList<>();
         int userRank = Objects.requireNonNull(island).getRank(user);
-        if (userRank >= island.getRankCommand(this.getLabel() + " kick") && !user.equals(member)) {
-            // Add the tooltip for kicking
+        // Add the tooltip for kicking
+        if (user.hasPermission(this.kickCommand.getPermission())
+                && userRank >= island.getRankCommand(this.getLabel() + " kick") && !user.equals(member)) {
             actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("kick"))
-                    .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name()) + " "
-                            + user.getTranslation(ar.tooltip()))
-                    .findFirst().map(user::getTranslation).ifPresent(desc::add);
+                    .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name")
+                            + " " + user.getTranslation(ar.tooltip()))
+                    .findFirst().ifPresent(desc::add);
         }
-        if (!user.equals(member) && userRank >= RanksManager.OWNER_RANK && targetRank >= RanksManager.MEMBER_RANK) {
+        // Set Owner
+        if (user.hasPermission(this.setOwnerCommand.getPermission()) && !user.equals(member)
+                && userRank >= RanksManager.OWNER_RANK && targetRank >= RanksManager.MEMBER_RANK) {
             // Add the tooltip for setowner
             actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("setowner"))
-                    .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name()) + " "
-                            + user.getTranslation(ar.tooltip()))
-                    .findFirst().map(user::getTranslation).ifPresent(desc::add);
+                    .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name")
+                            + " " + user.getTranslation(ar.tooltip()))
+                    .findFirst().ifPresent(desc::add);
+        }
+        // Leave
+        if (user.hasPermission(this.leaveCommand.getPermission()) && user.equals(member)
+                && userRank < RanksManager.OWNER_RANK) {
+            // Add the tooltip for leave
+            actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("leave"))
+                    .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name")
+                            + " " + user.getTranslation(ar.tooltip()))
+                    .findFirst().ifPresent(desc::add);
         }
         if (member != null) {
             if (member.isOnline()) {
@@ -424,8 +439,8 @@ private PanelItem getMemberButton(int targetRank, int slot, List<ActionRecords>
             } else {
                 // Offline player
                 desc.add(0, user.getTranslation(ref));
-                desc.add(1, offlinePlayerStatus(user, Bukkit.getOfflinePlayer(member.getUniqueId())));
-                return new PanelItemBuilder().icon(member.getName()).name(member.getDisplayName())
+                return new PanelItemBuilder().icon(member.getName())
+                        .name(offlinePlayerStatus(user, Bukkit.getOfflinePlayer(member.getUniqueId())))
                         .description(desc)
                         .clickHandler((panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member,
                                 actions))
@@ -443,7 +458,8 @@ private boolean clickListener(Panel panel, User clicker, ClickType clickType, in
                 switch (action.actionType().toUpperCase(Locale.ENGLISH)) {
                 case "KICK" -> {
                     // Kick the player, or uncoop, or untrust
-                    if (!member.equals(clicker) && rank >= island.getRankCommand(this.getLabel() + " kick")) {
+                    if (clicker.hasPermission(this.kickCommand.getPermission()) && !member.equals(clicker)
+                            && rank >= island.getRankCommand(this.getLabel() + " kick")) {
                         clicker.closeInventory();
                         removePlayer(clicker, member);
                         clicker.getPlayer().playSound(clicker.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, 1F);
@@ -451,13 +467,15 @@ private boolean clickListener(Panel panel, User clicker, ClickType clickType, in
                 }
                 case "SETOWNER" -> {
                     // Make the player the leader of the island
-                    if (!member.equals(clicker) && clicker.getUniqueId().equals(island.getOwner())) {
+                    if (clicker.hasPermission(this.setOwnerCommand.getPermission()) && !member.equals(clicker)
+                            && clicker.getUniqueId().equals(island.getOwner())) {
                         clicker.closeInventory();
                         this.setOwnerCommand.setOwner(clicker, member.getUniqueId());
                     }
                 }
                 case "LEAVE" -> {
-                    if (member.equals(clicker) && !clicker.getUniqueId().equals(island.getOwner())) {
+                    if (clicker.hasPermission(this.leaveCommand.getPermission()) && member.equals(clicker)
+                            && !clicker.getUniqueId().equals(island.getOwner())) {
                         clicker.closeInventory();
                         leaveCommand.leave(clicker);
                     }
@@ -539,6 +557,18 @@ private String getMemberStatus(User user2, UUID member, boolean online) {
      * @return string
      */
     private String offlinePlayerStatus(User user2, OfflinePlayer offlineMember) {
+        String lastSeen = lastSeen(offlineMember);
+        if (island.getMemberSet(RanksManager.MEMBER_RANK, true).contains(offlineMember.getUniqueId())) {
+            return user.getTranslation("commands.island.team.info.member-layout.offline", TextVariables.NAME,
+                    offlineMember.getName(), "[last_seen]", lastSeen);
+        } else {
+            // This will prevent anyone that is trusted or below to not have a last-seen status
+            return user.getTranslation("commands.island.team.info.member-layout.offline-not-last-seen",
+                    TextVariables.NAME, offlineMember.getName());
+        }
+    }
+
+    private String lastSeen(OfflinePlayer offlineMember) {
         // A bit of handling for the last joined date
         Instant lastJoined = Instant.ofEpochMilli(offlineMember.getLastPlayed());
         Instant now = Instant.now();
@@ -556,15 +586,7 @@ private String offlinePlayerStatus(User user2, OfflinePlayer offlineMember) {
             lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toDays()),
                     TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.days"));
         }
-
-        if (island.getMemberSet(RanksManager.MEMBER_RANK, true).contains(offlineMember.getUniqueId())) {
-            return user.getTranslation("commands.island.team.info.member-layout.offline", TextVariables.NAME,
-                    offlineMember.getName(), "[last_seen]", lastSeen);
-        } else {
-            // This will prevent anyone that is trusted or below to not have a last-seen status
-            return user.getTranslation("commands.island.team.info.member-layout.offline-not-last-seen",
-                    TextVariables.NAME, offlineMember.getName());
-        }
+        return lastSeen;
     }
 
     private boolean fireEvent(User user, Island island) {
diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml
index 3e1e3ae69..128529eb2 100644
--- a/src/main/resources/locales/en-US.yml
+++ b/src/main/resources/locales/en-US.yml
@@ -611,34 +611,34 @@ commands:
       description: reset your island name
       success: '&a Successfully reset your island name.'
     team:
-      description: manage your team
+      description: "manage your team"
       gui:
         titles:
-          team-panel: Team Management
+          team-panel: "Team Management"
         buttons:
           status:
-            name: Status
-            description: The status of the team
+            name: "Status"
+            description: "The status of the team"
           rank-filter:
-            name: Rank Filter
-            description: &a Click to cycle ranks
+            name: "Rank Filter"
+            description: "&a Click to cycle ranks"
+          invitation: "Invitation"
         tips:
+          LEFT:
+            name: "&b Left Click"
+          RIGHT:
+            name: "&b Right Click"
           SHIFT_RIGHT:
-            name: &b Right Shift
-            reject: &a click to reject
-            kick: |
-              &a click
-              &a to kick player
-            leave: |
-              &a click
-              &a to leave team
+            name: "&b Shift Right Click"
+            reject: "&a to reject"
+            kick: "&a to kick player"
+            leave: "&a to leave team"
           SHIFT_LEFT:
-            name: &b Left Shift
-            accept: &a click to accept         
+            name: "&b Shift Left Click"
+            accept: "&a to accept "        
             setowner: |
-              &a click to
-              &a set owner to
-              &a this player
+              &a to set owner
+              &a to this player
       info:
         description: display detailed info about your team
         member-layout:
diff --git a/src/main/resources/panels/team_panel.yml b/src/main/resources/panels/team_panel.yml
index 498f382fa..e9ca5d19f 100644
--- a/src/main/resources/panels/team_panel.yml
+++ b/src/main/resources/panels/team_panel.yml
@@ -120,11 +120,11 @@ team_panel:
           # The click-type is the same as the bukkit {@link org.bukkit.event.inventory.ClickType}. UNKNOWN is the default.
           click-type: SHIFT_RIGHT
           # tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
-          tooltip: commands.island.team.gui.tips.shift-right.kick
+          tooltip: commands.island.team.gui.tips.SHIFT_RIGHT.kick
         leave:
           click-type: SHIFT_RIGHT
-          tooltip: commands.island.team.gui.tips.shift-right.leave
+          tooltip: commands.island.team.gui.tips.SHIFT_RIGHT.leave
         setowner:
           click-type: SHIFT_LEFT
-          tooltip: commands.island.team.gui.tips.shift-left.setowner
+          tooltip: commands.island.team.gui.tips.SHIFT_LEFT.setowner
           
\ No newline at end of file

From f290a2c65aed1ffd0fb7e62f74c0e3cd78fdca52 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Thu, 4 Jan 2024 17:16:36 +0900
Subject: [PATCH 12/22] WIP for team invites.

---
 .../island/team/IslandTeamCommand.java        | 24 ++++-
 .../island/team/IslandTeamInviteCommand.java  | 74 ++++++++++++++-
 src/main/resources/locales/en-US.yml          | 18 ++++
 .../resources/panels/team_invite_panel.yml    | 90 +++++++++++++++++++
 src/main/resources/panels/team_panel.yml      | 11 ++-
 5 files changed, 213 insertions(+), 4 deletions(-)
 create mode 100644 src/main/resources/panels/team_invite_panel.yml

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index 8b0334189..c7d00d979 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -78,6 +78,8 @@ public class IslandTeamCommand extends CompositeCommand {
 
     private IslandTeamInviteRejectCommand rejectCommand;
 
+    private IslandTeamInviteCommand inviteCommand;
+
     public IslandTeamCommand(CompositeCommand parent) {
         super(parent, "team");
         inviteMap = new HashMap<>();
@@ -89,7 +91,7 @@ public void setup() {
         setOnlyPlayer(true);
         setDescription("commands.island.team.description");
         // Register commands
-        new IslandTeamInviteCommand(this);
+        inviteCommand = new IslandTeamInviteCommand(this);
         leaveCommand = new IslandTeamLeaveCommand(this);
         setOwnerCommand = new IslandTeamSetownerCommand(this);
         kickCommand = new IslandTeamKickCommand(this);
@@ -164,13 +166,31 @@ private void build() {
         panelBuilder.registerTypeBuilder("MEMBER", this::createMemberButton);
         panelBuilder.registerTypeBuilder("INVITED", this::createInvitedButton);
         panelBuilder.registerTypeBuilder("RANK", this::createRankButton);
-        //panelBuilder.registerTypeBuilder("KICK", this::createKickButton);
+        panelBuilder.registerTypeBuilder("INVITE", this::createInviteButton);
         border = panelBuilder.getPanelTemplate().border();
         background = panelBuilder.getPanelTemplate().background();
         // Register unknown type builder.
         panelBuilder.build();
     }
 
+    private PanelItem createInviteButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
+        if (island == null || !user.hasPermission(this.inviteCommand.getPermission())
+                || island.getRank(user) < island.getRankCommand(this.getLabel() + " invite")) {
+            return this.getBlankBorder();
+        }
+        PanelItemBuilder builder = new PanelItemBuilder();
+        builder.icon(Material.PLAYER_HEAD);
+        builder.name(user.getTranslation("commands.island.team.gui.buttons.invite.name"));
+        builder.clickHandler((panel, user, clickType, clickSlot) -> {
+            if (clickType.equals(ClickType.LEFT)) {
+                user.closeInventory();
+                this.inviteCommand.build(user);
+            }
+            return true;
+        });
+        return builder.build();
+    }
+
     private PanelItem createRankButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
         // If there is no island, the do not show this icon
         if (island == null) {
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
index 4585dfc7c..95cca395f 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
@@ -1,11 +1,15 @@
 package world.bentobox.bentobox.api.commands.island.team;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.UUID;
 
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
 import org.eclipse.jdt.annotation.Nullable;
 
 import world.bentobox.bentobox.api.commands.CompositeCommand;
@@ -13,6 +17,12 @@
 import world.bentobox.bentobox.api.events.IslandBaseEvent;
 import world.bentobox.bentobox.api.events.team.TeamEvent;
 import world.bentobox.bentobox.api.localization.TextVariables;
+import world.bentobox.bentobox.api.panels.PanelItem;
+import world.bentobox.bentobox.api.panels.TemplatedPanel;
+import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
+import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder;
+import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord;
+import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem;
 import world.bentobox.bentobox.api.user.User;
 import world.bentobox.bentobox.database.objects.Island;
 import world.bentobox.bentobox.managers.IslandsManager;
@@ -24,6 +34,9 @@ public class IslandTeamInviteCommand extends CompositeCommand {
 
     private final IslandTeamCommand itc;
     private @Nullable User invitedPlayer;
+    private @Nullable TemplateItem border;
+    private @Nullable TemplateItem background;
+    private User user;
 
     public IslandTeamInviteCommand(IslandTeamCommand parent) {
         super(parent, "invite");
@@ -36,6 +49,8 @@ public void setup() {
         setOnlyPlayer(true);
         setDescription("commands.island.team.invite.description");
         setConfigurableRankCommand();
+        // Panels
+        getPlugin().saveResource("panels/team_invite_panel.yml", true);
     }
 
 
@@ -65,6 +80,7 @@ private boolean handleCommandWithNoArgs(User user) {
         Type inviteType = getInviteType(playerUUID);
 
         if (inviteType != null) {
+            // TODO: send to team command to present invite
             String name = getPlayers().getName(playerUUID);
             switch (inviteType) {
             case COOP ->  user.sendMessage("commands.island.team.invite.name-has-invited-you.coop", TextVariables.NAME, name);
@@ -73,7 +89,7 @@ private boolean handleCommandWithNoArgs(User user) {
             }
             return true;
         }
-
+        build(user);
         showHelp(this, user);
         return false;
     }
@@ -204,4 +220,60 @@ public Optional<List<String>> tabComplete(User user, String alias, List<String>
         return Optional.of(Util.tabLimit(options, lastArg));
     }
 
+    public void build(User user) {
+        this.user = user;
+        // Start building panel.
+        TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();
+        panelBuilder.user(user);
+        panelBuilder.world(user.getWorld());
+
+        panelBuilder.template("team_panel", new File(getPlugin().getDataFolder(), "panels"));
+
+        panelBuilder.parameters("[name]", user.getName(), "[display_name]", user.getDisplayName());
+
+        panelBuilder.registerTypeBuilder("PROSPECT", this::createProspectButton);
+        //panelBuilder.registerTypeBuilder("INVITED", this::createInvitedButton);
+        //panelBuilder.registerTypeBuilder("RANK", this::createRankButton);
+        //panelBuilder.registerTypeBuilder("INVITE", this::createInviteButton);
+        border = panelBuilder.getPanelTemplate().border();
+        background = panelBuilder.getPanelTemplate().background();
+        // Register unknown type builder.
+        panelBuilder.build();
+
+    }
+
+    /**
+     * Create member button panel item.
+     *
+     * @param template the template
+     * @param slot     the slot
+     * @return the panel item
+     */
+    private PanelItem createProspectButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
+        // Player issuing the command must have an island
+        Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
+        if (island == null) {
+            return this.getBlankBackground();
+        }
+        // TODO: THERE"S A BUG HERE
+        return user.getWorld().getPlayers().stream()
+                .filter(player -> !getIslands().inTeam(getWorld(), player.getUniqueId())).skip(slot.slot() - 1)
+                .limit(1L)
+                .findFirst().map(this::getProspect).orElse(this.getBlankBackground());
+    }
+
+    private PanelItem getProspect(Player player) {
+        return new PanelItemBuilder().icon(player.getName()).build();
+    }
+
+    private PanelItem getBlankBorder() {
+        return new PanelItemBuilder().icon(Objects.requireNonNullElse(border.icon(), new ItemStack(Material.BARRIER)))
+                .name((Objects.requireNonNullElse(border.title(), ""))).build();
+    }
+
+    private PanelItem getBlankBackground() {
+        return new PanelItemBuilder()
+                .icon(Objects.requireNonNullElse(background.icon(), new ItemStack(Material.BARRIER)))
+                .name((Objects.requireNonNullElse(background.title(), ""))).build();
+    }
 }
diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml
index 128529eb2..0014ef630 100644
--- a/src/main/resources/locales/en-US.yml
+++ b/src/main/resources/locales/en-US.yml
@@ -623,9 +623,12 @@ commands:
             name: "Rank Filter"
             description: "&a Click to cycle ranks"
           invitation: "Invitation"
+          invite:
+            name: "Invite player"
         tips:
           LEFT:
             name: "&b Left Click"
+            invite: "&a to invite a player"
           RIGHT:
             name: "&b Right Click"
           SHIFT_RIGHT:
@@ -704,6 +707,21 @@ commands:
         to-accept-or-reject: '&a Do /[label] team accept to accept, or /[label] team
           reject to reject'
         you-will-lose-your-island: '&c WARNING! You will lose your island if you accept!'
+        gui:
+           titles:
+             team-invite-panel: "Invite Players"
+           tips:
+             LEFT:
+               name: "&b Left Click"
+               invite: |
+                 &a to invite a player
+                 &a to join your team
+             RIGHT:
+               name: "&b Right Click"
+               coop: "&a to coop player"
+             SHIFT_LEFT:
+               name: "&b Shift Left Click"
+               trust: "&a to trust a player"
         errors:
           cannot-invite-self: '&c You cannot invite yourself!'
           cooldown: '&c You cannot invite that person for another [number] seconds.'
diff --git a/src/main/resources/panels/team_invite_panel.yml b/src/main/resources/panels/team_invite_panel.yml
new file mode 100644
index 000000000..722a0bd8b
--- /dev/null
+++ b/src/main/resources/panels/team_invite_panel.yml
@@ -0,0 +1,90 @@
+# Name of panel used for indentification in the code - must be the same name as the filename.
+team_panel:
+  # Title of the panel shown to the user. This is a reference and the reference will be translatable in the locale file
+  title: commands.island.team.invite.gui.titles.team-invite-panel
+  # The type of panel to show. Options are INVENTORY, HOPPER, DROPPER. INVENTORY is that standard chest inventory and
+  # the others refer to the inventories shown for those items.
+  type: INVENTORY
+  # The background of the panel. These items will be shown if other items are not there. STAINED_GLASS_PANEs give a good effect.
+  background:
+    icon: BLACK_STAINED_GLASS_PANE
+    # Each item may have text applied to it, but usually for background items, nothing is shown.
+    title: "&b&r" # Empty text. This is using the Bukkit chat color coding with &'s. 
+  border:
+    # The border of each panel may be shown as a different item.
+    # It can be used to provide a contrast to items in the panel.
+    icon: BLUE_STAINED_GLASS_PANE
+    title: "&b&r"  # Empty text
+  # This tag indicates which rows in the panel must be shown. The panel will be sized vertically accordingly. This does not include the borders.
+  # This can be a list and rows must be between 1 and 6, if used.
+  force-shown: []
+  # The content section contains details of each item/button in the panel. The numbers indicate the rows and then then columns of each item.
+  content:
+    # Row number
+    2:
+      2: prospect_button
+      3: prospect_button
+      4: prospect_button
+      5: prospect_button
+      6: prospect_button
+      7: prospect_button
+      8: prospect_button
+    3:
+      2: prospect_button
+      3: prospect_button
+      4: prospect_button
+      5: prospect_button
+      6: prospect_button
+      7: prospect_button
+      8: prospect_button
+    4:
+      2: prospect_button
+      3: prospect_button
+      4: prospect_button
+      5: prospect_button
+      6: prospect_button
+      7: prospect_button
+      8: prospect_button
+    5:
+      2: prospect_button
+      3: prospect_button
+      4: prospect_button
+      5: prospect_button
+      6: prospect_button
+      7: prospect_button
+      8: prospect_button
+    6:
+      2: prospect_button
+      3: prospect_button
+      4: prospect_button
+      5: prospect_button
+      6: prospect_button
+      7: prospect_button
+      8: prospect_button
+  # This is where reusable buttons are defined.
+  reusable:
+    # This is the name of the button that is referenced
+    prospect_button:
+      # If the icon for a button is not defined, it defaults to AIR and so effectively will not be shown.
+      # icons are usually not defined if the icon is going to be dynamically set in the panel, e.g. in this case the material will vary
+      #icon: STONE
+      title: commands.island.team.invite.gui.buttons.member.name
+      description: commands.island.team.invite.gui.buttons.member.description
+      data:
+        type: PROSPECT
+      # Actions cover what happens if the button is clicked or the mouse is moved over it. There can be multiple actions possible for different
+        # click-types.
+      actions:
+        # Each action has an arbitrary descriptive name to define it. 
+        invite:
+          # The click-type is the same as the bukkit {@link org.bukkit.event.inventory.ClickType}. UNKNOWN is the default.
+          click-type: LEFT
+          # tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
+          tooltip: commands.island.team.invite.gui.tips.LEFT.invite
+        coop:
+          click-type: RIGHT
+          tooltip: commands.island.team.invite.gui.tips.RIGHT.coop
+        trust:
+          click-type: SHIFT_LEFT
+          tooltip: commands.island.team.invite.gui.tips.SHIFT_LEFT.trust
+          
\ No newline at end of file
diff --git a/src/main/resources/panels/team_panel.yml b/src/main/resources/panels/team_panel.yml
index e9ca5d19f..c34deb8d4 100644
--- a/src/main/resources/panels/team_panel.yml
+++ b/src/main/resources/panels/team_panel.yml
@@ -13,7 +13,7 @@ team_panel:
   border:
     # The border of each panel may be shown as a different item.
     # It can be used to provide a contrast to items in the panel.
-    icon: BLACK_STAINED_GLASS_PANE
+    icon: BLUE_STAINED_GLASS_PANE
     title: "&b&r"  # Empty text
   # This tag indicates which rows in the panel must be shown. The panel will be sized vertically accordingly. This does not include the borders.
   # This can be a list and rows must be between 1 and 6, if used.
@@ -61,6 +61,15 @@ team_panel:
           reject:
             click-type: SHIFT_RIGHT
             tooltip: commands.island.team.gui.tips.SHIFT_RIGHT.reject
+      7:
+        # Invite button
+        data:
+          type: INVITE
+          name: commands.island.team.gui.buttons.invite
+        actions:
+          invite:
+            click-type: LEFT
+            tooltip: commands.island.team.gui.tips.LEFT.invite
     2:
       2: member_button
       3: member_button

From 5fcc203dcaed84caf0a63ddedfbe850c6b6e473d Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Thu, 4 Jan 2024 21:56:20 +0900
Subject: [PATCH 13/22] Adds inviting to the GUI.

---
 .../island/team/IslandTeamCommand.java        | 25 ++++-
 .../island/team/IslandTeamInviteCommand.java  | 97 ++++++++++++++++---
 src/main/resources/locales/en-US.yml          |  2 +
 .../resources/panels/team_invite_panel.yml    | 28 +++++-
 4 files changed, 132 insertions(+), 20 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index c7d00d979..6ea415f9e 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -21,7 +21,6 @@
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 
-import world.bentobox.bentobox.BentoBox;
 import world.bentobox.bentobox.api.commands.CompositeCommand;
 import world.bentobox.bentobox.api.events.IslandBaseEvent;
 import world.bentobox.bentobox.api.events.team.TeamEvent;
@@ -80,6 +79,10 @@ public class IslandTeamCommand extends CompositeCommand {
 
     private IslandTeamInviteCommand inviteCommand;
 
+    private IslandTeamCoopCommand coopCommand;
+
+    private IslandTeamTrustCommand trustCommand;
+
     public IslandTeamCommand(CompositeCommand parent) {
         super(parent, "team");
         inviteMap = new HashMap<>();
@@ -98,18 +101,18 @@ public void setup() {
         acceptCommand = new IslandTeamInviteAcceptCommand(this);
         rejectCommand = new IslandTeamInviteRejectCommand(this);
         if (RanksManager.getInstance().rankExists(RanksManager.COOP_RANK_REF)) {
-            new IslandTeamCoopCommand(this);
+            coopCommand = new IslandTeamCoopCommand(this);
             uncoopCommand = new IslandTeamUncoopCommand(this);
         }
         if (RanksManager.getInstance().rankExists(RanksManager.TRUSTED_RANK_REF)) {
-            new IslandTeamTrustCommand(this);
+            trustCommand = new IslandTeamTrustCommand(this);
             unTrustCommand = new IslandTeamUntrustCommand(this);
         }
         new IslandTeamPromoteCommand(this, "promote");
         new IslandTeamPromoteCommand(this, "demote");
 
         // Panels
-        getPlugin().saveResource("panels/team_panel.yml", true);
+        getPlugin().saveResource("panels/team_panel.yml", false);
     }
 
     @Override
@@ -666,4 +669,18 @@ public Invite getInvite(UUID invitee) {
     public void removeInvite(@NonNull UUID invitee) {
         inviteMap.remove(invitee);
     }
+
+    /**
+     * @return the coopCommand
+     */
+    protected IslandTeamCoopCommand getCoopCommand() {
+        return coopCommand;
+    }
+
+    /**
+     * @return the trustCommand
+     */
+    protected IslandTeamTrustCommand getTrustCommand() {
+        return trustCommand;
+    }
 }
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
index 95cca395f..3b6f8d50c 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
@@ -8,7 +8,9 @@
 import java.util.UUID;
 
 import org.bukkit.Material;
+import org.bukkit.Sound;
 import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
 import org.bukkit.inventory.ItemStack;
 import org.eclipse.jdt.annotation.Nullable;
 
@@ -17,6 +19,7 @@
 import world.bentobox.bentobox.api.events.IslandBaseEvent;
 import world.bentobox.bentobox.api.events.team.TeamEvent;
 import world.bentobox.bentobox.api.localization.TextVariables;
+import world.bentobox.bentobox.api.panels.Panel;
 import world.bentobox.bentobox.api.panels.PanelItem;
 import world.bentobox.bentobox.api.panels.TemplatedPanel;
 import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
@@ -37,6 +40,8 @@ public class IslandTeamInviteCommand extends CompositeCommand {
     private @Nullable TemplateItem border;
     private @Nullable TemplateItem background;
     private User user;
+    private int page = 0; // This number by 35
+    private static final int PER_PAGE = 35;
 
     public IslandTeamInviteCommand(IslandTeamCommand parent) {
         super(parent, "invite");
@@ -50,7 +55,7 @@ public void setup() {
         setDescription("commands.island.team.invite.description");
         setConfigurableRankCommand();
         // Panels
-        getPlugin().saveResource("panels/team_invite_panel.yml", true);
+        getPlugin().saveResource("panels/team_invite_panel.yml", false);
     }
 
 
@@ -227,14 +232,13 @@ public void build(User user) {
         panelBuilder.user(user);
         panelBuilder.world(user.getWorld());
 
-        panelBuilder.template("team_panel", new File(getPlugin().getDataFolder(), "panels"));
+        panelBuilder.template("team_invite_panel", new File(getPlugin().getDataFolder(), "panels"));
 
         panelBuilder.parameters("[name]", user.getName(), "[display_name]", user.getDisplayName());
 
         panelBuilder.registerTypeBuilder("PROSPECT", this::createProspectButton);
-        //panelBuilder.registerTypeBuilder("INVITED", this::createInvitedButton);
-        //panelBuilder.registerTypeBuilder("RANK", this::createRankButton);
-        //panelBuilder.registerTypeBuilder("INVITE", this::createInviteButton);
+        panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton);
+        panelBuilder.registerTypeBuilder("NEXT", this::createNextButton);
         border = panelBuilder.getPanelTemplate().border();
         background = panelBuilder.getPanelTemplate().background();
         // Register unknown type builder.
@@ -242,6 +246,41 @@ public void build(User user) {
 
     }
 
+    private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
+        long count = getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player))
+                .filter(player -> !player.equals(user.getPlayer())).count();
+        if (count > page * PER_PAGE) {
+            // We need to show a next button
+            return new PanelItemBuilder().name(user.getTranslation("protection.panel.next")).icon(Material.ARROW)
+                    .clickHandler((panel, user, clickType, clickSlot) -> {
+                        user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
+                        page++;
+                        build(user);
+                        return true;
+                    }).build();
+        }
+        return getBlankBorder();
+    }
+
+    private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
+        if (page > 0) {
+            // We need to show a next button
+            return new PanelItemBuilder().name(user.getTranslation("protection.panel.previous")).icon(Material.ARROW)
+                    .clickHandler((panel, user, clickType, clickSlot) -> {
+                        user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
+                        page--;
+                        build(user);
+                        return true;
+                    }).build();
+        }
+        return getBlankBorder();
+    }
+
+    private PanelItem getBlankBorder() {
+        return new PanelItemBuilder().icon(Objects.requireNonNullElse(border.icon(), new ItemStack(Material.BARRIER)))
+                .name((Objects.requireNonNullElse(border.title(), ""))).build();
+    }
+
     /**
      * Create member button panel item.
      *
@@ -255,20 +294,48 @@ private PanelItem createProspectButton(ItemTemplateRecord template, TemplatedPan
         if (island == null) {
             return this.getBlankBackground();
         }
-        // TODO: THERE"S A BUG HERE
-        return user.getWorld().getPlayers().stream()
-                .filter(player -> !getIslands().inTeam(getWorld(), player.getUniqueId())).skip(slot.slot() - 1)
-                .limit(1L)
-                .findFirst().map(this::getProspect).orElse(this.getBlankBackground());
+        if (page < 0) {
+            page = 0;
+        }
+        return getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player))
+                .filter(player -> !player.equals(user.getPlayer())).skip(slot.slot() + page * PER_PAGE).findFirst()
+                .map(player -> getProspect(player, template)).orElse(this.getBlankBackground());
     }
 
-    private PanelItem getProspect(Player player) {
-        return new PanelItemBuilder().icon(player.getName()).build();
+    private PanelItem getProspect(Player player, ItemTemplateRecord template) {
+        // Check if the prospect has already been invited
+        if (this.itc.isInvited(player.getUniqueId())
+                && user.getUniqueId().equals(this.itc.getInvite(player.getUniqueId()).getInviter())) {
+            return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName())
+                    .description(user.getTranslation("commands.island.team.invite.gui.button.already-invited")).build();
+        }
+        List<String> desc = template.actions().stream().map(ar -> user
+                .getTranslation("commands.island.team.invite.gui.tips." + ar.clickType().name() + ".name")
+                + " " + user.getTranslation(ar.tooltip())).toList();
+        return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName()).description(desc)
+                .clickHandler(
+                        (panel, user, clickType, clickSlot) -> clickHandler(panel, user, clickType, clickSlot, player))
+                .build();
     }
 
-    private PanelItem getBlankBorder() {
-        return new PanelItemBuilder().icon(Objects.requireNonNullElse(border.icon(), new ItemStack(Material.BARRIER)))
-                .name((Objects.requireNonNullElse(border.title(), ""))).build();
+    private boolean clickHandler(Panel panel, User user, ClickType clickType, int clickSlot, Player player) {
+        if (clickType.equals(ClickType.LEFT)) {
+            user.closeInventory();
+            if (this.canExecute(user, this.getLabel(), List.of(player.getName()))) {
+                this.execute(user, getLabel(), List.of(player.getName()));
+            }
+        } else if (clickType.equals(ClickType.RIGHT)) {
+            user.closeInventory();
+            if (this.itc.getCoopCommand().canExecute(user, this.getLabel(), List.of(player.getName()))) {
+                this.itc.getCoopCommand().execute(user, getLabel(), List.of(player.getName()));
+            }
+        } else if (clickType.equals(ClickType.SHIFT_LEFT)) {
+            user.closeInventory();
+            if (this.itc.getTrustCommand().canExecute(user, this.getLabel(), List.of(player.getName()))) {
+                this.itc.getTrustCommand().execute(user, getLabel(), List.of(player.getName()));
+            }
+        }
+        return true;
     }
 
     private PanelItem getBlankBackground() {
diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml
index 0014ef630..51e4ea18b 100644
--- a/src/main/resources/locales/en-US.yml
+++ b/src/main/resources/locales/en-US.yml
@@ -710,6 +710,8 @@ commands:
         gui:
            titles:
              team-invite-panel: "Invite Players"
+           button:
+             already-invited: "&c Invited already"
            tips:
              LEFT:
                name: "&b Left Click"
diff --git a/src/main/resources/panels/team_invite_panel.yml b/src/main/resources/panels/team_invite_panel.yml
index 722a0bd8b..e3c5b3326 100644
--- a/src/main/resources/panels/team_invite_panel.yml
+++ b/src/main/resources/panels/team_invite_panel.yml
@@ -1,5 +1,5 @@
 # Name of panel used for indentification in the code - must be the same name as the filename.
-team_panel:
+team_invite_panel:
   # Title of the panel shown to the user. This is a reference and the reference will be translatable in the locale file
   title: commands.island.team.invite.gui.titles.team-invite-panel
   # The type of panel to show. Options are INVENTORY, HOPPER, DROPPER. INVENTORY is that standard chest inventory and
@@ -21,6 +21,32 @@ team_panel:
   # The content section contains details of each item/button in the panel. The numbers indicate the rows and then then columns of each item.
   content:
     # Row number
+    1:
+      2:
+        data:
+          type: PREVIOUS
+        # Actions cover what happens if the button is clicked or the mouse is moved over it. There can be multiple actions possible for different
+        # click-types.
+        actions:
+          # Each action has an arbitrary descriptive name to define it. 
+          view:
+            # The click-type is the same as the bukkit {@link org.bukkit.event.inventory.ClickType}. UNKNOWN is the default.
+            click-type: LEFT
+            # tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
+            tooltip: commands.island.team.invite.gui.tips.previous
+      8:
+        data:
+          type: NEXT
+        # Actions cover what happens if the button is clicked or the mouse is moved over it. There can be multiple actions possible for different
+        # click-types.
+        actions:
+          # Each action has an arbitrary descriptive name to define it. 
+          view:
+            # The click-type is the same as the bukkit {@link org.bukkit.event.inventory.ClickType}. UNKNOWN is the default.
+            click-type: LEFT
+            # tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
+            tooltip: commands.island.team.invite.gui.tips.next
+ 
     2:
       2: prospect_button
       3: prospect_button

From 3702870095b0b8b662c94d2b0c41778eb9ecdbf1 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Fri, 5 Jan 2024 10:48:02 +0900
Subject: [PATCH 14/22] Fix tests

---
 .../island/team/IslandTeamCommand.java        |  8 ++++++-
 .../island/team/IslandTeamCommandTest.java    | 23 +++++--------------
 .../team/IslandTeamInviteCommandTest.java     | 18 +++++++++++++++
 3 files changed, 31 insertions(+), 18 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index 6ea415f9e..1b266f47a 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -116,7 +116,7 @@ public void setup() {
     }
 
     @Override
-    public boolean execute(User user, String label, List<String> args) {
+    public boolean canExecute(User user, String label, List<String> args) {
         this.user = user;
         // Player issuing the command must have an island
         island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
@@ -146,8 +146,14 @@ public boolean execute(User user, String label, List<String> args) {
                 user.sendMessage("commands.island.team.invite.errors.island-is-full");
             }
         }
+        return true;
+    }
+
+    @Override
+    public boolean execute(User user, String label, List<String> args) {
         // Show members of island
         showMembers().forEach(user::sendRawMessage);
+        // Show the panel
         build();
         return true;
     }
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommandTest.java
index 5ee5552fd..b0f360d80 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommandTest.java
@@ -31,7 +31,6 @@
 import world.bentobox.bentobox.BentoBox;
 import world.bentobox.bentobox.api.commands.CompositeCommand;
 import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
-import world.bentobox.bentobox.api.localization.TextVariables;
 import world.bentobox.bentobox.api.user.User;
 import world.bentobox.bentobox.database.objects.Island;
 import world.bentobox.bentobox.managers.CommandsManager;
@@ -149,33 +148,23 @@ public void testSetup() {
     }
 
     /**
-     * Test method for {@link world.bentobox.bentobox.api.commands.island.team.IslandTeamCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
+     * Test method for {@link world.bentobox.bentobox.api.commands.island.team.IslandTeamCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
      */
     @Test
-    public void testExecuteUserStringListOfStringNoIsland() {
+    public void testCanExecuteUserStringListOfStringNoIsland() {
         when(im.getPrimaryIsland(world, uuid)).thenReturn(null);
-        assertFalse(tc.execute(user, "team", Collections.emptyList()));
+        assertFalse(tc.canExecute(user, "team", Collections.emptyList()));
         verify(user).sendMessage(eq("general.errors.no-island"));
     }
 
     /**
-     * Test method for
-     * {@link world.bentobox.bentobox.api.commands.island.team.IslandTeamCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
-     */
-    @Test
-    public void testExecuteUserStringListOfStringIslandIsNotFull() {
-        assertTrue(tc.execute(user, "team", Collections.emptyList()));
-        verify(user).sendMessage(eq("commands.island.team.invite.you-can-invite"), eq(TextVariables.NUMBER), eq("3"));
-    }
-
-    /**
-     * Test method for {@link world.bentobox.bentobox.api.commands.island.team.IslandTeamCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
+     * Test method for {@link world.bentobox.bentobox.api.commands.island.team.IslandTeamCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
      */
     @Test
-    public void testExecuteUserStringListOfStringIslandIsFull() {
+    public void testCanExecuteUserStringListOfStringIslandIsFull() {
         // Max members
         when(im.getMaxMembers(eq(island), eq(RanksManager.MEMBER_RANK))).thenReturn(0);
-        assertTrue(tc.execute(user, "team", Collections.emptyList()));
+        assertTrue(tc.canExecute(user, "team", Collections.emptyList()));
         verify(user).sendMessage(eq("commands.island.team.invite.errors.island-is-full"));
     }
 
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java
index 8fbe46b1c..e18155a47 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java
@@ -11,6 +11,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import java.io.File;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -19,6 +20,10 @@
 import org.bukkit.Bukkit;
 import org.bukkit.World;
 import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.InventoryType;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemFactory;
+import org.bukkit.inventory.meta.ItemMeta;
 import org.bukkit.plugin.PluginManager;
 import org.bukkit.scheduler.BukkitScheduler;
 import org.eclipse.jdt.annotation.NonNull;
@@ -92,6 +97,10 @@ public void setUp() throws Exception {
         // Settings
         when(plugin.getSettings()).thenReturn(s);
 
+        // Data folder for panels
+        when(plugin.getDataFolder())
+                .thenReturn(new File("src" + File.separator + "main" + File.separator + "resources"));
+
         // Player & users
         PowerMockito.mockStatic(User.class);
 
@@ -165,6 +174,15 @@ public void setUp() throws Exception {
         // Parent command
         when(ic.getTopLabel()).thenReturn("island");
 
+        // Mock item factory (for itemstacks)
+        ItemFactory itemFactory = mock(ItemFactory.class);
+        ItemMeta bannerMeta = mock(ItemMeta.class);
+        when(itemFactory.getItemMeta(any())).thenReturn(bannerMeta);
+        when(Bukkit.getItemFactory()).thenReturn(itemFactory);
+        Inventory inventory = mock(Inventory.class);
+        when(Bukkit.createInventory(eq(null), anyInt(), any())).thenReturn(inventory);
+        when(Bukkit.createInventory(eq(null), any(InventoryType.class), any())).thenReturn(inventory);
+
         // Command under test
         itl = new IslandTeamInviteCommand(ic);
 

From 7392e036d933e198667e8253f29b1fbb79d7eb06 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Fri, 5 Jan 2024 11:27:59 +0900
Subject: [PATCH 15/22] Fixed minor bugs reported by SonarCloud

---
 .../island/team/IslandTeamCommand.java        | 50 +++++++++----------
 .../island/team/IslandTeamInviteCommand.java  |  4 +-
 .../team/IslandTeamSetownerCommand.java       |  3 +-
 3 files changed, 27 insertions(+), 30 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index 1b266f47a..c63124629 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -9,6 +9,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
 
@@ -403,13 +404,9 @@ private PanelItem ownerView(ItemTemplateRecord template, ItemSlot slot) {
         if (slot.slot() > subOwnerCount + memberCount + trustedCount
                 && slot.slot() < subOwnerCount + memberCount + trustedCount + coopCount + 1) {
             // Show coops
-            PanelItem item = getMemberButton(RanksManager.COOP_RANK, slot.slot(), template.actions());
-            if (item != null) {
-                return item;
-            }
-
+            return getMemberButton(RanksManager.COOP_RANK, slot.slot(), template.actions());
         }
-        return new PanelItemBuilder().icon(Material.BLACK_STAINED_GLASS_PANE).name("&b&r").build();
+        return this.getBlankBackground();
 
     }
 
@@ -426,8 +423,12 @@ private PanelItem getMemberButton(int targetRank, int slot, List<ActionRecords>
             return getMemberButton(RanksManager.OWNER_RANK, 1, actions);
         }
         String ref = RanksManager.getInstance().getRank(targetRank);
-        User member = island.getMemberSet(targetRank, false).stream().sorted().skip(slot - 1L).limit(1L)
-                .map(User::getInstance).findFirst().orElse(null);
+        Optional<User> opMember = island.getMemberSet(targetRank, false).stream().sorted().skip(slot - 1L).limit(1L)
+                .map(User::getInstance).findFirst();
+        if (opMember.isEmpty()) {
+            return this.getBlankBackground();
+        }
+        User member = opMember.get();
         // Make button description depending on viewer
         List<String> desc = new ArrayList<>();
         int userRank = Objects.requireNonNull(island).getRank(user);
@@ -457,26 +458,21 @@ private PanelItem getMemberButton(int targetRank, int slot, List<ActionRecords>
                             + " " + user.getTranslation(ar.tooltip()))
                     .findFirst().ifPresent(desc::add);
         }
-        if (member != null) {
-            if (member.isOnline()) {
-                desc.add(0, user.getTranslation(ref));
-                return new PanelItemBuilder().icon(member.getName()).name(member.getDisplayName())
-                        .description(desc)
-                        .clickHandler((panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member,
-                                actions))
-                        .build();
-            } else {
-                // Offline player
-                desc.add(0, user.getTranslation(ref));
-                return new PanelItemBuilder().icon(member.getName())
-                        .name(offlinePlayerStatus(user, Bukkit.getOfflinePlayer(member.getUniqueId())))
-                        .description(desc)
-                        .clickHandler((panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member,
-                                actions))
-                        .build();
-            }
+        if (member.isOnline()) {
+            desc.add(0, user.getTranslation(ref));
+            return new PanelItemBuilder().icon(member.getName()).name(member.getDisplayName()).description(desc)
+                    .clickHandler(
+                            (panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member, actions))
+                    .build();
+        } else {
+            // Offline player
+            desc.add(0, user.getTranslation(ref));
+            return new PanelItemBuilder().icon(member.getName())
+                    .name(offlinePlayerStatus(user, Bukkit.getOfflinePlayer(member.getUniqueId()))).description(desc)
+                    .clickHandler(
+                            (panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member, actions))
+                    .build();
         }
-        return null;
     }
 
     private boolean clickListener(Panel panel, User clicker, ClickType clickType, int i, User member,
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
index 3b6f8d50c..33f63da70 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
@@ -40,8 +40,8 @@ public class IslandTeamInviteCommand extends CompositeCommand {
     private @Nullable TemplateItem border;
     private @Nullable TemplateItem background;
     private User user;
-    private int page = 0; // This number by 35
-    private static final int PER_PAGE = 35;
+    private long page = 0; // This number by 35
+    private static final long PER_PAGE = 35;
 
     public IslandTeamInviteCommand(IslandTeamCommand parent) {
         super(parent, "invite");
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java
index 0c792fe6b..24f10557b 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java
@@ -4,6 +4,7 @@
 import java.util.Optional;
 import java.util.UUID;
 
+import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 
 import world.bentobox.bentobox.api.commands.CompositeCommand;
@@ -73,7 +74,7 @@ public boolean execute(User user, String label, List<String> args) {
 
     }
 
-    protected boolean setOwner(User user, @Nullable UUID targetUUID2) {
+    protected boolean setOwner(User user, @NonNull UUID targetUUID2) {
         // Fire event so add-ons can run commands, etc.
         Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
         // Fire event so add-ons can run commands, etc.

From 1a702bc74a7b4f68c57d88ba0981ecd9f0326c5d Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Fri, 5 Jan 2024 13:21:09 +0900
Subject: [PATCH 16/22] Up Minecraft version and paper to 1.20.4 in POM

---
 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index 7ab9b80d2..ce7f409d7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -73,10 +73,10 @@
         <postgresql.version>42.2.18</postgresql.version>
         <hikaricp.version>5.0.1</hikaricp.version>
         <!-- More visible way to change dependency versions -->
-        <spigot.version>1.20.3-R0.1-SNAPSHOT</spigot.version>
+        <spigot.version>1.20.4-R0.1-SNAPSHOT</spigot.version>
         <!-- Might differ from the last Spigot release for short periods 
             of time -->
-        <paper.version>1.20.2-R0.1-SNAPSHOT</paper.version>
+        <paper.version>1.20.4-R0.1-SNAPSHOT</paper.version>
         <bstats.version>3.0.0</bstats.version>
         <vault.version>1.7.1</vault.version>
         <placeholderapi.version>2.10.9</placeholderapi.version>

From 4e3a8e84b0500bc3ae61659a58aafa2013b69214 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Fri, 5 Jan 2024 16:36:43 +0900
Subject: [PATCH 17/22] Added features based on Discord feedback.

1. added some lore to the invite button to explain the players shown are
from the game world
2. removed the team state in chat
3. added a search to the invite panel
---
 .../island/team/IslandTeamCommand.java        |  3 +-
 .../island/team/IslandTeamInviteCommand.java  | 35 ++++++++++++++-
 .../team/conversations/InviteNamePrompt.java  | 44 +++++++++++++++++++
 .../api/panels/reader/ItemTemplateRecord.java | 34 +++++++++++---
 src/main/resources/locales/en-US.yml          | 39 +++++++++-------
 .../resources/panels/team_invite_panel.yml    | 13 ++++++
 6 files changed, 142 insertions(+), 26 deletions(-)
 create mode 100644 src/main/java/world/bentobox/bentobox/api/commands/island/team/conversations/InviteNamePrompt.java

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index c63124629..6557f7d05 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -152,8 +152,6 @@ public boolean canExecute(User user, String label, List<String> args) {
 
     @Override
     public boolean execute(User user, String label, List<String> args) {
-        // Show members of island
-        showMembers().forEach(user::sendRawMessage);
         // Show the panel
         build();
         return true;
@@ -191,6 +189,7 @@ private PanelItem createInviteButton(ItemTemplateRecord template, TemplatedPanel
         PanelItemBuilder builder = new PanelItemBuilder();
         builder.icon(Material.PLAYER_HEAD);
         builder.name(user.getTranslation("commands.island.team.gui.buttons.invite.name"));
+        builder.description(user.getTranslation("commands.island.team.gui.buttons.invite.description"));
         builder.clickHandler((panel, user, clickType, clickSlot) -> {
             if (clickType.equals(ClickType.LEFT)) {
                 user.closeInventory();
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
index 33f63da70..76ecb5cce 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
@@ -9,13 +9,16 @@
 
 import org.bukkit.Material;
 import org.bukkit.Sound;
+import org.bukkit.conversations.ConversationFactory;
 import org.bukkit.entity.Player;
 import org.bukkit.event.inventory.ClickType;
 import org.bukkit.inventory.ItemStack;
 import org.eclipse.jdt.annotation.Nullable;
 
+import world.bentobox.bentobox.BentoBox;
 import world.bentobox.bentobox.api.commands.CompositeCommand;
 import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
+import world.bentobox.bentobox.api.commands.island.team.conversations.InviteNamePrompt;
 import world.bentobox.bentobox.api.events.IslandBaseEvent;
 import world.bentobox.bentobox.api.events.team.TeamEvent;
 import world.bentobox.bentobox.api.localization.TextVariables;
@@ -239,6 +242,9 @@ public void build(User user) {
         panelBuilder.registerTypeBuilder("PROSPECT", this::createProspectButton);
         panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton);
         panelBuilder.registerTypeBuilder("NEXT", this::createNextButton);
+        panelBuilder.registerTypeBuilder("SEARCH", this::createSearchButton);
+
+        // Stash the backgrounds for later use
         border = panelBuilder.getPanelTemplate().border();
         background = panelBuilder.getPanelTemplate().background();
         // Register unknown type builder.
@@ -246,12 +252,26 @@ public void build(User user) {
 
     }
 
+    private PanelItem createSearchButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
+        checkTemplate(template);
+        return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon())
+                .clickHandler((panel, user, clickType, clickSlot) -> {
+                    user.closeInventory();
+                    new ConversationFactory(BentoBox.getInstance()).withLocalEcho(false).withTimeout(90)
+                            .withModality(false)
+                            .withFirstPrompt(new InviteNamePrompt(user, this))
+                            .buildConversation(user.getPlayer()).begin();
+                    return true;
+                }).build();
+    }
+
     private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
+        checkTemplate(template);
         long count = getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player))
                 .filter(player -> !player.equals(user.getPlayer())).count();
         if (count > page * PER_PAGE) {
             // We need to show a next button
-            return new PanelItemBuilder().name(user.getTranslation("protection.panel.next")).icon(Material.ARROW)
+            return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon())
                     .clickHandler((panel, user, clickType, clickSlot) -> {
                         user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
                         page++;
@@ -262,10 +282,21 @@ private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.I
         return getBlankBorder();
     }
 
+    private void checkTemplate(ItemTemplateRecord template) {
+        if (template.icon() == null) {
+            getPlugin().logError("Icon in template is missing or unknown! " + template.toString());
+        }
+        if (template.title() == null) {
+            getPlugin().logError("Title in template is missing! " + template.toString());
+        }
+
+    }
+
     private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
+        checkTemplate(template);
         if (page > 0) {
             // We need to show a next button
-            return new PanelItemBuilder().name(user.getTranslation("protection.panel.previous")).icon(Material.ARROW)
+            return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon())
                     .clickHandler((panel, user, clickType, clickSlot) -> {
                         user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
                         page--;
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/conversations/InviteNamePrompt.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/conversations/InviteNamePrompt.java
new file mode 100644
index 000000000..6517741db
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/conversations/InviteNamePrompt.java
@@ -0,0 +1,44 @@
+package world.bentobox.bentobox.api.commands.island.team.conversations;
+
+import java.util.List;
+
+import org.bukkit.conversations.ConversationContext;
+import org.bukkit.conversations.Prompt;
+import org.bukkit.conversations.StringPrompt;
+import org.eclipse.jdt.annotation.NonNull;
+
+import world.bentobox.bentobox.api.commands.island.team.IslandTeamInviteCommand;
+import world.bentobox.bentobox.api.user.User;
+
+/**
+ * Invites a player by search
+ * @author tastybento
+ *
+ */
+public class InviteNamePrompt extends StringPrompt {
+
+    @NonNull
+    private final User user;
+    @NonNull
+    private final IslandTeamInviteCommand itic;
+
+    public InviteNamePrompt(@NonNull User user, IslandTeamInviteCommand islandTeamInviteCommand) {
+        this.user = user;
+        this.itic = islandTeamInviteCommand;
+    }
+
+    @Override
+    @NonNull
+    public String getPromptText(@NonNull ConversationContext context) {
+        return user.getTranslation("commands.island.team.invite.gui.enter-name");
+    }
+
+    @Override
+    public Prompt acceptInput(@NonNull ConversationContext context, String input) {
+        if (itic.canExecute(user, itic.getLabel(), List.of(input))) {
+            itic.execute(user, itic.getLabel(), List.of(input));
+        }
+        return Prompt.END_OF_CONVERSATION;
+    }
+
+}
diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java
index ff7424f69..85b1ef2fd 100644
--- a/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java
+++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java
@@ -27,8 +27,30 @@
  *
  * @since 1.17.3
  */
-public record ItemTemplateRecord(@Nullable ItemStack icon, @Nullable String title, @Nullable String description,
-        @NonNull List<ActionRecords> actions, @NonNull Map<String, Object> dataMap,
+public record ItemTemplateRecord(
+        /**
+         * ItemStack of the Item
+         */
+        @Nullable ItemStack icon,
+        /**
+         * Title of the item
+         */
+        @Nullable String title,
+        /**
+         * Lore message of the item
+         */
+        @Nullable String description,
+        /**
+         * List of Actions for a button
+         */
+        @NonNull List<ActionRecords> actions,
+        /**
+         * DataMap that links additional objects for a button.
+         */
+        @NonNull Map<String, Object> dataMap,
+        /**
+         * FallBack item if current one is not possible to generate.
+         */
         @Nullable ItemTemplateRecord fallback) {
 
     /**
@@ -75,12 +97,12 @@ public void addAction(ActionRecords actionData) {
      */
     public record ActionRecords(
             /**
-            * the click type
-            */
+             * the click type
+             */
             ClickType clickType,
             /**
-            * the string that represents action type
-            */
+             * the string that represents action type
+             */
             String actionType,
             /**
              * the content of the action
diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml
index 3cc79aa1d..f8b1d30c9 100644
--- a/src/main/resources/locales/en-US.yml
+++ b/src/main/resources/locales/en-US.yml
@@ -625,6 +625,10 @@ commands:
           invitation: "Invitation"
           invite:
             name: "Invite player"
+            description: |
+              &a Players must be in the
+              &a same world as you to be
+              &a shown in the list.
         tips:
           LEFT:
             name: "&b Left Click"
@@ -708,22 +712,25 @@ commands:
           reject to reject'
         you-will-lose-your-island: '&c WARNING! You will lose your island if you accept!'
         gui:
-           titles:
-             team-invite-panel: "Invite Players"
-           button:
-             already-invited: "&c Invited already"
-           tips:
-             LEFT:
-               name: "&b Left Click"
-               invite: |
-                 &a to invite a player
-                 &a to join your team
-             RIGHT:
-               name: "&b Right Click"
-               coop: "&a to coop player"
-             SHIFT_LEFT:
-               name: "&b Shift Left Click"
-               trust: "&a to trust a player"
+          titles:
+            team-invite-panel: "Invite Players"
+          button:
+            already-invited: "&c Invited already"
+            search: "&a Search for a player"
+          enter-name: "&a Enter name:" 
+          tips:
+            LEFT:
+              name: "&b Left Click"
+              search: "&a Enter the player's name"
+              invite: |
+                &a to invite a player
+                &a to join your team
+            RIGHT:
+              name: "&b Right Click"
+              coop: "&a to coop player"
+            SHIFT_LEFT:
+              name: "&b Shift Left Click"
+              trust: "&a to trust a player"
         errors:
           cannot-invite-self: '&c You cannot invite yourself!'
           cooldown: '&c You cannot invite that person for another [number] seconds.'
diff --git a/src/main/resources/panels/team_invite_panel.yml b/src/main/resources/panels/team_invite_panel.yml
index e3c5b3326..7c816d0e7 100644
--- a/src/main/resources/panels/team_invite_panel.yml
+++ b/src/main/resources/panels/team_invite_panel.yml
@@ -23,6 +23,8 @@ team_invite_panel:
     # Row number
     1:
       2:
+        title: "protection.panel.previous"
+        icon: ARROW
         data:
           type: PREVIOUS
         # Actions cover what happens if the button is clicked or the mouse is moved over it. There can be multiple actions possible for different
@@ -34,7 +36,18 @@ team_invite_panel:
             click-type: LEFT
             # tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
             tooltip: commands.island.team.invite.gui.tips.previous
+      5:
+        title: "commands.island.team.invite.gui.button.search"
+        icon: PLAYER_HEAD
+        data:
+          type: SEARCH
+        actions:
+          search:
+            click-type: LEFT
+            tooltip: commands.island.team.invite.gui.tips.search
       8:
+        title: "protection.panel.next"
+        icon: ARROW
         data:
           type: NEXT
         # Actions cover what happens if the button is clicked or the mouse is moved over it. There can be multiple actions possible for different

From ee0ed2c38599e7e738a4d11f0e336e367947cdb3 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Fri, 5 Jan 2024 17:42:52 +0900
Subject: [PATCH 18/22] Added back button.

Refactored some code and templates.
---
 .../island/team/IslandTeamCommand.java        |  2 +-
 .../island/team/IslandTeamInviteCommand.java  | 52 ++++++++-----------
 src/main/resources/locales/en-US.yml          |  1 +
 .../resources/panels/team_invite_panel.yml    | 16 +++++-
 .../team/IslandTeamInviteCommandTest.java     |  6 +--
 5 files changed, 42 insertions(+), 35 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index 6557f7d05..24cdb911b 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -160,7 +160,7 @@ public boolean execute(User user, String label, List<String> args) {
     /**
      * This method builds this GUI.
      */
-    private void build() {
+    void build() {
         // Start building panel.
         TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();
         panelBuilder.user(user);
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
index 76ecb5cce..4154875cf 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
@@ -44,6 +44,7 @@ public class IslandTeamInviteCommand extends CompositeCommand {
     private @Nullable TemplateItem background;
     private User user;
     private long page = 0; // This number by 35
+    private boolean inviteCmd;
     private static final long PER_PAGE = 35;
 
     public IslandTeamInviteCommand(IslandTeamCommand parent) {
@@ -74,7 +75,9 @@ public boolean canExecute(User user, String label, List<String> args) {
         }
 
         if (args.size() != 1) {
-            return handleCommandWithNoArgs(user);
+            this.inviteCmd = true;
+            build(user);
+            return true;
         }
 
         Island island = islandsManager.getIsland(getWorld(), user);
@@ -83,25 +86,6 @@ public boolean canExecute(User user, String label, List<String> args) {
         return checkRankAndInvitePlayer(user, island, rank, args.get(0));
     }
 
-    private boolean handleCommandWithNoArgs(User user) {
-        UUID playerUUID = user.getUniqueId();
-        Type inviteType = getInviteType(playerUUID);
-
-        if (inviteType != null) {
-            // TODO: send to team command to present invite
-            String name = getPlayers().getName(playerUUID);
-            switch (inviteType) {
-            case COOP ->  user.sendMessage("commands.island.team.invite.name-has-invited-you.coop", TextVariables.NAME, name);
-            case TRUST -> user.sendMessage("commands.island.team.invite.name-has-invited-you.trust", TextVariables.NAME, name);
-            default -> user.sendMessage("commands.island.team.invite.name-has-invited-you", TextVariables.NAME, name);
-            }
-            return true;
-        }
-        build(user);
-        showHelp(this, user);
-        return false;
-    }
-
     private boolean checkRankAndInvitePlayer(User user, Island island, int rank, String playerName) {
         PlayersManager playersManager = getPlayers();
         UUID playerUUID = user.getUniqueId();
@@ -151,14 +135,6 @@ private boolean checkRankAndInvitePlayer(User user, Island island, int rank, Str
         return true;
     }
 
-    private Type getInviteType(UUID playerUUID) {
-        if (itc.isInvited(playerUUID)) {
-            Invite invite = itc.getInvite(playerUUID);
-            return invite.getType();
-        }
-        return null;
-    }
-
     private boolean canInvitePlayer(User user, User invitedPlayer) {
         UUID playerUUID = user.getUniqueId();
         if (!invitedPlayer.isOnline() || !user.getPlayer().canSee(invitedPlayer.getPlayer())) {
@@ -228,7 +204,11 @@ public Optional<List<String>> tabComplete(User user, String alias, List<String>
         return Optional.of(Util.tabLimit(options, lastArg));
     }
 
-    public void build(User user) {
+    /**
+     * Build the invite panel
+     * @param user use of the panel
+     */
+    void build(User user) {
         this.user = user;
         // Start building panel.
         TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();
@@ -243,7 +223,7 @@ public void build(User user) {
         panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton);
         panelBuilder.registerTypeBuilder("NEXT", this::createNextButton);
         panelBuilder.registerTypeBuilder("SEARCH", this::createSearchButton);
-
+        panelBuilder.registerTypeBuilder("BACK", this::createBackButton);
         // Stash the backgrounds for later use
         border = panelBuilder.getPanelTemplate().border();
         background = panelBuilder.getPanelTemplate().background();
@@ -252,6 +232,18 @@ public void build(User user) {
 
     }
 
+    private PanelItem createBackButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
+        checkTemplate(template);
+        return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon())
+                .clickHandler((panel, user, clickType, clickSlot) -> {
+                    user.closeInventory();
+                    if (!inviteCmd) {
+                        this.itc.build();
+                    }
+                    return true;
+                }).build();
+    }
+
     private PanelItem createSearchButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
         checkTemplate(template);
         return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon())
diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml
index f8b1d30c9..6fa23e7cc 100644
--- a/src/main/resources/locales/en-US.yml
+++ b/src/main/resources/locales/en-US.yml
@@ -722,6 +722,7 @@ commands:
             LEFT:
               name: "&b Left Click"
               search: "&a Enter the player's name"
+              back: "&a Back"
               invite: |
                 &a to invite a player
                 &a to join your team
diff --git a/src/main/resources/panels/team_invite_panel.yml b/src/main/resources/panels/team_invite_panel.yml
index 7c816d0e7..d082daa3d 100644
--- a/src/main/resources/panels/team_invite_panel.yml
+++ b/src/main/resources/panels/team_invite_panel.yml
@@ -59,7 +59,6 @@ team_invite_panel:
             click-type: LEFT
             # tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
             tooltip: commands.island.team.invite.gui.tips.next
- 
     2:
       2: prospect_button
       3: prospect_button
@@ -100,6 +99,21 @@ team_invite_panel:
       6: prospect_button
       7: prospect_button
       8: prospect_button
+      9:
+        title: "commands.island.team.invite.gui.tips.LEFT.back"
+        icon: OAK_DOOR
+        data:
+          type: BACK
+        # Actions cover what happens if the button is clicked or the mouse is moved over it. There can be multiple actions possible for different
+        # click-types.
+        actions:
+          # Each action has an arbitrary descriptive name to define it. 
+          back:
+            # The click-type is the same as the bukkit {@link org.bukkit.event.inventory.ClickType}. UNKNOWN is the default.
+            click-type: LEFT
+            # tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
+            tooltip: commands.island.team.invite.gui.tips.LEFT.back
+      
   # This is where reusable buttons are defined.
   reusable:
     # This is the name of the button that is referenced
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java
index e18155a47..706a56cb7 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java
@@ -238,9 +238,9 @@ public void testCanExecuteNoIsland() {
      */
     @Test
     public void testCanExecuteNoTarget() {
-        assertFalse(itl.canExecute(user, itl.getLabel(), Collections.emptyList()));
-        // Show help
-        verify(user).sendMessage("commands.help.header", TextVariables.LABEL, "BSkyBlock");
+        assertTrue(itl.canExecute(user, itl.getLabel(), Collections.emptyList()));
+        // Show panel
+        verify(p).openInventory(any(Inventory.class));
     }
 
     /**

From 803274b6cd397861cf6aa37ee000ef2972cbc028 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Sun, 7 Jan 2024 17:37:46 +0900
Subject: [PATCH 19/22] Fixed bugs with teams and uncoop untrust etc of Ops.

---
 .../world/bentobox/bentobox/BentoBox.java     |  9 ++-
 .../island/team/IslandTeamCommand.java        | 69 ++++++++++++++-----
 .../island/team/IslandTeamInviteCommand.java  | 24 ++++++-
 .../island/team/IslandTeamKickCommand.java    |  8 ++-
 .../island/team/IslandTeamLeaveCommand.java   |  7 +-
 .../bentobox/database/objects/Island.java     |  3 -
 .../team/IslandTeamKickCommandTest.java       | 39 ++++++-----
 7 files changed, 109 insertions(+), 50 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java
index ee026484f..b70c19c87 100644
--- a/src/main/java/world/bentobox/bentobox/BentoBox.java
+++ b/src/main/java/world/bentobox/bentobox/BentoBox.java
@@ -1,5 +1,6 @@
 package world.bentobox.bentobox;
 
+import java.io.File;
 import java.util.List;
 import java.util.Optional;
 
@@ -465,8 +466,12 @@ public boolean loadSettings() {
         }
 
         log("Saving default panels...");
-        this.saveResource("panels/island_creation_panel.yml", false);
-        this.saveResource("panels/language_panel.yml", false);
+        if (!new File(getDataFolder() + File.separator + "panels", "island_creation_panel.yml").exists()) {
+            this.saveResource("panels/island_creation_panel.yml", false);
+        }
+        if (!new File(getDataFolder() + File.separator + "panels", "language_panel.yml").exists()) {
+            this.saveResource("panels/language_panel.yml", false);
+        }
         return true;
     }
 
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index 24cdb911b..90120ba68 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -113,7 +113,9 @@ public void setup() {
         new IslandTeamPromoteCommand(this, "demote");
 
         // Panels
-        getPlugin().saveResource("panels/team_panel.yml", false);
+        if (!new File(getPlugin().getDataFolder() + File.separator + "panels", "team_panel.yml").exists()) {
+            getPlugin().saveResource("panels/team_panel.yml", false);
+        }
     }
 
     @Override
@@ -288,6 +290,8 @@ private PanelItem createInvitedButton(ItemTemplateRecord template, TemplatedPane
                     .toList());
             builder.clickHandler((panel, user, clickType, clickSlot) -> {
                 if (clickType.equals(ClickType.SHIFT_LEFT) && user.hasPermission(this.acceptCommand.getPermission())) {
+                    getPlugin().log("Invite accepted: " + user.getName() + " accepted " + invite.getType()
+                            + " invite to island at " + island.getCenter());
                     // Accept
                     switch (invite.getType()) {
                     case COOP -> this.acceptCommand.acceptCoopInvite(user, invite);
@@ -298,6 +302,8 @@ private PanelItem createInvitedButton(ItemTemplateRecord template, TemplatedPane
                 }
                 if (clickType.equals(ClickType.SHIFT_RIGHT) && user.hasPermission(this.rejectCommand.getPermission())) {
                     // Reject
+                    getPlugin().log("Invite rejected: " + user.getName() + " rejected " + invite.getType()
+                            + " invite.");
                     this.rejectCommand.execute(user, "", List.of());
                     user.closeInventory();
                 }
@@ -474,34 +480,53 @@ private PanelItem getMemberButton(int targetRank, int slot, List<ActionRecords>
         }
     }
 
-    private boolean clickListener(Panel panel, User clicker, ClickType clickType, int i, User member,
+    private boolean clickListener(Panel panel, User clickingUser, ClickType clickType, int i, User target,
             List<ActionRecords> actions) {
-        int rank = Objects.requireNonNull(island).getRank(clicker);
+        int rank = Objects.requireNonNull(island).getRank(clickingUser);
         for (ItemTemplateRecord.ActionRecords action : actions) {
-            if (clickType == action.clickType() || action.clickType() == ClickType.UNKNOWN) {
+            if (clickType.equals(action.clickType())) {
                 switch (action.actionType().toUpperCase(Locale.ENGLISH)) {
                 case "KICK" -> {
                     // Kick the player, or uncoop, or untrust
-                    if (clicker.hasPermission(this.kickCommand.getPermission()) && !member.equals(clicker)
+                    if (clickingUser.hasPermission(this.kickCommand.getPermission()) && !target.equals(clickingUser)
                             && rank >= island.getRankCommand(this.getLabel() + " kick")) {
-                        clicker.closeInventory();
-                        removePlayer(clicker, member);
-                        clicker.getPlayer().playSound(clicker.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, 1F);
+                        getPlugin().log("Kick: " + clickingUser.getName() + " kicked " + target.getName()
+                                + " from island at " + island.getCenter());
+                        clickingUser.closeInventory();
+                        if (removePlayer(clickingUser, target)) {
+                            clickingUser.getPlayer().playSound(clickingUser.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F,
+                                    1F);
+                            getPlugin().log("Kick: success");
+                        } else {
+                            getPlugin().log("Kick: failed");
+                        }
                     }
                 }
                 case "SETOWNER" -> {
                     // Make the player the leader of the island
-                    if (clicker.hasPermission(this.setOwnerCommand.getPermission()) && !member.equals(clicker)
-                            && clicker.getUniqueId().equals(island.getOwner())) {
-                        clicker.closeInventory();
-                        this.setOwnerCommand.setOwner(clicker, member.getUniqueId());
+                    if (clickingUser.hasPermission(this.setOwnerCommand.getPermission()) && !target.equals(clickingUser)
+                            && clickingUser.getUniqueId().equals(island.getOwner())) {
+                        getPlugin().log("Set Owner: " + clickingUser.getName() + " trying to make " + target.getName()
+                                + " owner of island at " + island.getCenter());
+                        clickingUser.closeInventory();
+                        if (this.setOwnerCommand.setOwner(clickingUser, target.getUniqueId())) {
+                            getPlugin().log("Set Owner: success");
+                        } else {
+                            getPlugin().log("Set Owner: failed");
+                        }
                     }
                 }
                 case "LEAVE" -> {
-                    if (clicker.hasPermission(this.leaveCommand.getPermission()) && member.equals(clicker)
-                            && !clicker.getUniqueId().equals(island.getOwner())) {
-                        clicker.closeInventory();
-                        leaveCommand.leave(clicker);
+                    if (clickingUser.hasPermission(this.leaveCommand.getPermission()) && target.equals(clickingUser)
+                            && !clickingUser.getUniqueId().equals(island.getOwner())) {
+                        getPlugin().log("Leave: " + clickingUser.getName() + " trying to leave island at "
+                                + island.getCenter());
+                        clickingUser.closeInventory();
+                        if (leaveCommand.leave(clickingUser)) {
+                            getPlugin().log("Leave: success");
+                        } else {
+                            getPlugin().log("Leave: failed");
+                        }
                     }
                 }
                 }
@@ -510,13 +535,19 @@ private boolean clickListener(Panel panel, User clicker, ClickType clickType, in
         return true;
     }
 
-    private void removePlayer(User clicker, User member) {
+    private boolean removePlayer(User clicker, User member) {
         // If member then kick, if coop, uncoop, if trusted, then untrust
-        switch (island.getRank(member)) {
+        return switch (island.getRank(member)) {
         case RanksManager.COOP_RANK -> this.uncoopCommand.unCoopCmd(user, member.getUniqueId());
         case RanksManager.TRUSTED_RANK -> this.unTrustCommand.unTrustCmd(user, member.getUniqueId());
-        default -> kickCommand.kick(clicker, member.getUniqueId());
+        default -> {
+            if (kickCommand.canExecute(user, kickCommand.getLabel(), List.of(member.getName()))) {
+                yield kickCommand.execute(user, kickCommand.getLabel(), List.of(member.getName()));
+            } else {
+                yield false;
+            }
         }
+        };
 
     }
 
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
index 4154875cf..7c0231848 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
@@ -59,7 +59,9 @@ public void setup() {
         setDescription("commands.island.team.invite.description");
         setConfigurableRankCommand();
         // Panels
-        getPlugin().saveResource("panels/team_invite_panel.yml", false);
+        if (!new File(getPlugin().getDataFolder() + File.separator + "panels", "team_invite_panel.yml").exists()) {
+            getPlugin().saveResource("panels/team_invite_panel.yml", false);
+        }
     }
 
 
@@ -250,8 +252,7 @@ private PanelItem createSearchButton(ItemTemplateRecord template, TemplatedPanel
                 .clickHandler((panel, user, clickType, clickSlot) -> {
                     user.closeInventory();
                     new ConversationFactory(BentoBox.getInstance()).withLocalEcho(false).withTimeout(90)
-                            .withModality(false)
-                            .withFirstPrompt(new InviteNamePrompt(user, this))
+                            .withModality(false).withFirstPrompt(new InviteNamePrompt(user, this))
                             .buildConversation(user.getPlayer()).begin();
                     return true;
                 }).build();
@@ -345,17 +346,34 @@ private boolean clickHandler(Panel panel, User user, ClickType clickType, int cl
         if (clickType.equals(ClickType.LEFT)) {
             user.closeInventory();
             if (this.canExecute(user, this.getLabel(), List.of(player.getName()))) {
+                getPlugin().log("Invite sent to: " + player.getName() + " by " + user.getName() + " to join island in "
+                        + getWorld().getName());
                 this.execute(user, getLabel(), List.of(player.getName()));
+            } else {
+                getPlugin().log("Invite failed: " + player.getName() + " by " + user.getName() + " to join island in "
+                        + getWorld().getName());
             }
         } else if (clickType.equals(ClickType.RIGHT)) {
             user.closeInventory();
             if (this.itc.getCoopCommand().canExecute(user, this.getLabel(), List.of(player.getName()))) {
+                getPlugin().log("Coop: " + player.getName() + " cooped " + user.getName() + " to island in "
+                        + getWorld().getName());
                 this.itc.getCoopCommand().execute(user, getLabel(), List.of(player.getName()));
+            } else {
+                getPlugin().log(
+                        "Coop failed: " + player.getName() + "'s coop to " + user.getName() + " failed for island in "
+                        + getWorld().getName());
             }
         } else if (clickType.equals(ClickType.SHIFT_LEFT)) {
             user.closeInventory();
             if (this.itc.getTrustCommand().canExecute(user, this.getLabel(), List.of(player.getName()))) {
+                getPlugin().log("Trust: " + player.getName() + " trusted " + user.getName() + " to island in "
+                        + getWorld().getName());
                 this.itc.getTrustCommand().execute(user, getLabel(), List.of(player.getName()));
+            } else {
+                getPlugin().log("Trust failed: " + player.getName() + "'s trust failed for " + user.getName()
+                        + " for island in "
+                        + getWorld().getName());
             }
         }
         return true;
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java
index 6024c7426..38989a433 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java
@@ -35,7 +35,7 @@ public void setup() {
     }
 
     @Override
-    public boolean execute(User user, String label, List<String> args) {
+    public boolean canExecute(User user, String label, List<String> args) {
         if (!getIslands().inTeam(getWorld(), user.getUniqueId())) {
             user.sendMessage("general.errors.no-team");
             return false;
@@ -74,7 +74,13 @@ public boolean execute(User user, String label, List<String> args) {
                     getPlayers().getName(targetUUID));
             return false;
         }
+        return true;
+    }
 
+    @Override
+    public boolean execute(User user, String label, List<String> args) {
+        // Get target
+        UUID targetUUID = getPlayers().getUUID(args.get(0));
         if (!getSettings().isKickConfirmation()) {
             kick(user, targetUUID);
             return true;
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamLeaveCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamLeaveCommand.java
index 8e34ba1c9..882f19719 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamLeaveCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamLeaveCommand.java
@@ -65,17 +65,17 @@ private void showResets(User user) {
 
     }
 
-    protected void leave(User user) {
+    protected boolean leave(User user) {
         Island island = getIslands().getIsland(getWorld(), user);
         if (island == null) {
             user.sendMessage("general.errors.no-island");
-            return;
+            return false;
         }
         // Fire event
         IslandBaseEvent event = TeamEvent.builder().island(island).reason(TeamEvent.Reason.LEAVE)
                 .involvedPlayer(user.getUniqueId()).build();
         if (event.isCancelled()) {
-            return;
+            return false;
         }
         UUID ownerUUID = island.getOwner();
         if (ownerUUID != null) {
@@ -103,5 +103,6 @@ protected void leave(User user) {
         IslandEvent.builder().island(island).involvedPlayer(user.getUniqueId()).admin(false)
                 .reason(IslandEvent.Reason.RANK_CHANGE).rankChange(island.getRank(user), RanksManager.VISITOR_RANK)
                 .build();
+        return true;
     }
 }
\ No newline at end of file
diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java
index 528e5c433..09f79feb8 100644
--- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java
+++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java
@@ -658,9 +658,6 @@ public int getRange() {
      * @return rank integer
      */
     public int getRank(User user) {
-        if (user.isOp()) {
-            return RanksManager.ADMIN_RANK;
-        }
         return members.getOrDefault(user.getUniqueId(), RanksManager.VISITOR_RANK);
     }
 
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java
index dc017e017..a1cc1b546 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java
@@ -7,6 +7,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -188,18 +189,18 @@ public void setUp() throws Exception {
     }
 
     /**
-     * Test method for {@link IslandTeamKickCommand#execute(User, String, java.util.List)}
+     * Test method for {@link IslandTeamKickCommand#canExecute(User, String, java.util.List)}
      */
     @Test
     public void testExecuteNoTeam() {
         when(im.inTeam(any(), eq(uuid))).thenReturn(false);
         IslandTeamKickCommand itl = new IslandTeamKickCommand(ic);
-        assertFalse(itl.execute(user, itl.getLabel(), Collections.emptyList()));
+        assertFalse(itl.canExecute(user, itl.getLabel(), Collections.emptyList()));
         verify(user).sendMessage(eq("general.errors.no-team"));
     }
 
     /**
-     * Test method for {@link IslandTeamKickCommand#execute(User, String, java.util.List)}
+     * Test method for {@link IslandTeamKickCommand#canExecute(User, String, java.util.List)}
      */
     @Test
     public void testExecuteLowerTeamRank() {
@@ -212,12 +213,12 @@ public void testExecuteLowerTeamRank() {
         when(island.getMemberSet()).thenReturn(ImmutableSet.of(notUUID));
 
         IslandTeamKickCommand itl = new IslandTeamKickCommand(ic);
-        assertFalse(itl.execute(user, itl.getLabel(), Collections.singletonList("poslovitch")));
+        assertFalse(itl.canExecute(user, itl.getLabel(), Collections.singletonList("poslovitch")));
         verify(user).sendMessage(eq("commands.island.team.kick.cannot-kick-rank"), eq(TextVariables.NAME), eq("poslovitch"));
     }
 
     /**
-     * Test method for {@link IslandTeamKickCommand#execute(User, String, java.util.List)}
+     * Test method for {@link IslandTeamKickCommand#canExecute(User, String, java.util.List)}
      */
     @Test
     public void testExecuteEqualTeamRank() {
@@ -230,7 +231,7 @@ public void testExecuteEqualTeamRank() {
         when(island.getMemberSet()).thenReturn(ImmutableSet.of(notUUID));
 
         IslandTeamKickCommand itl = new IslandTeamKickCommand(ic);
-        assertFalse(itl.execute(user, itl.getLabel(), Collections.singletonList("poslovitch")));
+        assertFalse(itl.canExecute(user, itl.getLabel(), Collections.singletonList("poslovitch")));
         verify(user).sendMessage(eq("commands.island.team.kick.cannot-kick-rank"), eq(TextVariables.NAME), eq("poslovitch"));
     }
 
@@ -254,7 +255,7 @@ public void testExecuteLargerTeamRank() {
     }
 
     /**
-     * Test method for {@link IslandTeamKickCommand#execute(User, String, java.util.List)}
+     * Test method for {@link IslandTeamKickCommand#canExecute(User, String, java.util.List)}
      */
     @Test
     public void testExecuteNoCommandRank() {
@@ -262,61 +263,61 @@ public void testExecuteNoCommandRank() {
         when(island.getRank(user)).thenReturn(RanksManager.MEMBER_RANK);
 
         IslandTeamKickCommand itl = new IslandTeamKickCommand(ic);
-        assertFalse(itl.execute(user, itl.getLabel(), Collections.emptyList()));
+        assertFalse(itl.canExecute(user, itl.getLabel(), Collections.emptyList()));
         verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
     /**
      * Test method for
-     * {@link IslandTeamKickCommand#execute(User, String, java.util.List)}
+     * {@link IslandTeamKickCommand#canExecute(User, String, java.util.List)}
      */
     @Test
     public void testExecuteNoTarget() {
         IslandTeamKickCommand itl = new IslandTeamKickCommand(ic);
-        assertFalse(itl.execute(user, itl.getLabel(), Collections.emptyList()));
+        assertFalse(itl.canExecute(user, itl.getLabel(), Collections.emptyList()));
         // Show help
     }
 
     /**
      * Test method for
-     * {@link IslandTeamKickCommand#execute(User, String, java.util.List)}
+     * {@link IslandTeamKickCommand#canExecute(User, String, java.util.List)}
      */
     @Test
     public void testExecuteUnknownPlayer() {
         IslandTeamKickCommand itl = new IslandTeamKickCommand(ic);
         when(pm.getUUID(any())).thenReturn(null);
-        assertFalse(itl.execute(user, itl.getLabel(), Collections.singletonList("poslovitch")));
+        assertFalse(itl.canExecute(user, itl.getLabel(), Collections.singletonList("poslovitch")));
         verify(user).sendMessage("general.errors.unknown-player", "[name]", "poslovitch");
     }
 
     /**
      * Test method for
-     * {@link IslandTeamKickCommand#execute(User, String, java.util.List)}
+     * {@link IslandTeamKickCommand#canExecute(User, String, java.util.List)}
      */
     @Test
     public void testExecuteSamePlayer() {
         IslandTeamKickCommand itl = new IslandTeamKickCommand(ic);
         when(pm.getUUID(any())).thenReturn(uuid);
-        assertFalse(itl.execute(user, itl.getLabel(), Collections.singletonList("poslovitch")));
+        assertFalse(itl.canExecute(user, itl.getLabel(), Collections.singletonList("poslovitch")));
         verify(user).sendMessage(eq("commands.island.team.kick.cannot-kick"));
     }
 
     /**
      * Test method for
-     * {@link IslandTeamKickCommand#execute(User, String, java.util.List)}
+     * {@link IslandTeamKickCommand#canExecute(User, String, java.util.List)}
      */
     @Test
     public void testExecuteDifferentPlayerNotInTeam() {
         IslandTeamKickCommand itl = new IslandTeamKickCommand(ic);
         when(pm.getUUID(any())).thenReturn(notUUID);
         // when(im.getMembers(any(), any())).thenReturn(Collections.emptySet());
-        assertFalse(itl.execute(user, itl.getLabel(), Collections.singletonList("poslovitch")));
+        assertFalse(itl.canExecute(user, itl.getLabel(), Collections.singletonList("poslovitch")));
         verify(user).sendMessage(eq("general.errors.not-in-team"));
     }
 
     /**
      * Test method for
-     * {@link IslandTeamKickCommand#execute(User, String, java.util.List)}
+     * {@link IslandTeamKickCommand#canExecute(User, String, java.util.List)}
      */
     @Test
     public void testExecuteDifferentPlayerNoRank() {
@@ -324,7 +325,7 @@ public void testExecuteDifferentPlayerNoRank() {
         when(pm.getUUID(any())).thenReturn(notUUID);
         when(island.getRankCommand(anyString())).thenReturn(RanksManager.OWNER_RANK);
         when(island.getRank(any(User.class))).thenReturn(RanksManager.VISITOR_RANK);
-        assertFalse(itl.execute(user, itl.getLabel(), Collections.singletonList("poslovitch")));
+        assertFalse(itl.canExecute(user, itl.getLabel(), Collections.singletonList("poslovitch")));
         verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "");
     }
 
@@ -364,7 +365,7 @@ public void testExecuteNoConfirmationKeepInventory() {
         assertTrue(itl.execute(user, itl.getLabel(), Collections.singletonList("poslovitch")));
         verify(im).removePlayer(any(World.class), eq(notUUID));
         verify(user).sendMessage("commands.island.team.kick.success", TextVariables.NAME, "poslovitch", TextVariables.DISPLAY_NAME, "&Cposlovich");
-        verify(target, Mockito.never()).getInventory();
+        verify(target, never()).getInventory();
 
     }
 

From 256554e32bbf89cefc4fa043a933c7ea4f1b8765 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Mon, 8 Jan 2024 16:14:20 +0900
Subject: [PATCH 20/22] Return to name search GUI if name not found.

---
 .../api/commands/island/team/IslandTeamInviteCommand.java | 2 +-
 .../island/team/conversations/InviteNamePrompt.java       | 8 +++++++-
 .../java/world/bentobox/bentobox/api/panels/Panel.java    | 7 ++++---
 .../bentobox/bentobox/api/panels/TemplatedPanel.java      | 1 +
 4 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
index 7c0231848..439fdfaf7 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
@@ -210,7 +210,7 @@ public Optional<List<String>> tabComplete(User user, String alias, List<String>
      * Build the invite panel
      * @param user use of the panel
      */
-    void build(User user) {
+    public void build(User user) {
         this.user = user;
         // Start building panel.
         TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/conversations/InviteNamePrompt.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/conversations/InviteNamePrompt.java
index 6517741db..39ba15fb2 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/conversations/InviteNamePrompt.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/conversations/InviteNamePrompt.java
@@ -2,11 +2,13 @@
 
 import java.util.List;
 
+import org.bukkit.Bukkit;
 import org.bukkit.conversations.ConversationContext;
 import org.bukkit.conversations.Prompt;
 import org.bukkit.conversations.StringPrompt;
 import org.eclipse.jdt.annotation.NonNull;
 
+import world.bentobox.bentobox.BentoBox;
 import world.bentobox.bentobox.api.commands.island.team.IslandTeamInviteCommand;
 import world.bentobox.bentobox.api.user.User;
 
@@ -36,8 +38,12 @@ public String getPromptText(@NonNull ConversationContext context) {
     @Override
     public Prompt acceptInput(@NonNull ConversationContext context, String input) {
         if (itic.canExecute(user, itic.getLabel(), List.of(input))) {
-            itic.execute(user, itic.getLabel(), List.of(input));
+            if (itic.execute(user, itic.getLabel(), List.of(input))) {
+                return Prompt.END_OF_CONVERSATION;
+            }
         }
+        // Return to the GUI but give a second for the error to show
+        Bukkit.getScheduler().runTaskLater(BentoBox.getInstance(), () -> itic.build(user), 20L);
         return Prompt.END_OF_CONVERSATION;
     }
 
diff --git a/src/main/java/world/bentobox/bentobox/api/panels/Panel.java b/src/main/java/world/bentobox/bentobox/api/panels/Panel.java
index 6ab674a55..06cd94c48 100644
--- a/src/main/java/world/bentobox/bentobox/api/panels/Panel.java
+++ b/src/main/java/world/bentobox/bentobox/api/panels/Panel.java
@@ -36,8 +36,8 @@ public class Panel implements HeadRequester, InventoryHolder {
      * <br>
      * The current list of inventories that cannot be created are:<br>
      * <blockquote>
-     *     {@link Type#INVENTORY}, {@link Type#HOPPER} and
-     *     {@link Type#DROPPER}
+     *     {@link Type#INVENTORY}, {@link Type#HOPPER},
+     *     {@link Type#DROPPER}, {@link Type#ANVIL}
      * </blockquote>
      *
      * These relate to the Bukkit inventories with INVENTORY being the standard CHEST inventory.
@@ -45,7 +45,7 @@ public class Panel implements HeadRequester, InventoryHolder {
      * @since 1.7.0
      */
     public enum Type {
-        INVENTORY, HOPPER, DROPPER
+        INVENTORY, HOPPER, DROPPER, ANVIL
     }
 
     public Panel() {
@@ -90,6 +90,7 @@ protected void makePanel(String name, Map<Integer, PanelItem> items, int size, U
         case INVENTORY -> inventory = Bukkit.createInventory(null, fixSize(size), name);
         case HOPPER -> inventory = Bukkit.createInventory(null, InventoryType.HOPPER, name);
         case DROPPER -> inventory = Bukkit.createInventory(null, InventoryType.DROPPER, name);
+        case ANVIL -> inventory = Bukkit.createInventory(null, InventoryType.ANVIL, name);
         }
 
         // Fill the inventory and return
diff --git a/src/main/java/world/bentobox/bentobox/api/panels/TemplatedPanel.java b/src/main/java/world/bentobox/bentobox/api/panels/TemplatedPanel.java
index 866473e2d..6310fa144 100644
--- a/src/main/java/world/bentobox/bentobox/api/panels/TemplatedPanel.java
+++ b/src/main/java/world/bentobox/bentobox/api/panels/TemplatedPanel.java
@@ -61,6 +61,7 @@ private void generatePanel() {
         case INVENTORY -> this.populateInventoryPanel(new PanelItem[6][9]);
         case HOPPER -> this.populateInventoryPanel(new PanelItem[1][5]);
         case DROPPER -> this.populateInventoryPanel(new PanelItem[3][3]);
+        case ANVIL -> this.populateInventoryPanel(new PanelItem[4][9]);
         };
 
         super.makePanel(this.user.getTranslation(this.panelTemplate.title(), this.parameters), items,

From 3d3bd425057ad1dbecd0e6832920032e4c93ce59 Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Mon, 8 Jan 2024 19:38:54 +0900
Subject: [PATCH 21/22] Added return to GUI for player search

---
 .../{conversations => }/InviteNamePrompt.java |  7 ++++--
 .../island/team/IslandTeamInviteCommand.java  | 24 +++++++++++++++----
 src/main/resources/locales/en-US.yml          |  3 +++
 .../resources/panels/team_invite_panel.yml    |  1 +
 4 files changed, 29 insertions(+), 6 deletions(-)
 rename src/main/java/world/bentobox/bentobox/api/commands/island/team/{conversations => }/InviteNamePrompt.java (83%)

diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/conversations/InviteNamePrompt.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/InviteNamePrompt.java
similarity index 83%
rename from src/main/java/world/bentobox/bentobox/api/commands/island/team/conversations/InviteNamePrompt.java
rename to src/main/java/world/bentobox/bentobox/api/commands/island/team/InviteNamePrompt.java
index 39ba15fb2..d9fde3cda 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/conversations/InviteNamePrompt.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/InviteNamePrompt.java
@@ -1,4 +1,4 @@
-package world.bentobox.bentobox.api.commands.island.team.conversations;
+package world.bentobox.bentobox.api.commands.island.team;
 
 import java.util.List;
 
@@ -9,7 +9,6 @@
 import org.eclipse.jdt.annotation.NonNull;
 
 import world.bentobox.bentobox.BentoBox;
-import world.bentobox.bentobox.api.commands.island.team.IslandTeamInviteCommand;
 import world.bentobox.bentobox.api.user.User;
 
 /**
@@ -37,12 +36,16 @@ public String getPromptText(@NonNull ConversationContext context) {
 
     @Override
     public Prompt acceptInput(@NonNull ConversationContext context, String input) {
+        // TODO remove this and pass the options back to the GUI
         if (itic.canExecute(user, itic.getLabel(), List.of(input))) {
             if (itic.execute(user, itic.getLabel(), List.of(input))) {
                 return Prompt.END_OF_CONVERSATION;
             }
         }
+        // Set the search item to what was entered
+        itic.setSearchName(input);
         // Return to the GUI but give a second for the error to show
+        // TODO: return the failed input and display the options in the GUI.
         Bukkit.getScheduler().runTaskLater(BentoBox.getInstance(), () -> itic.build(user), 20L);
         return Prompt.END_OF_CONVERSATION;
     }
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
index 439fdfaf7..c80c6df8b 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
@@ -18,7 +18,6 @@
 import world.bentobox.bentobox.BentoBox;
 import world.bentobox.bentobox.api.commands.CompositeCommand;
 import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
-import world.bentobox.bentobox.api.commands.island.team.conversations.InviteNamePrompt;
 import world.bentobox.bentobox.api.events.IslandBaseEvent;
 import world.bentobox.bentobox.api.events.team.TeamEvent;
 import world.bentobox.bentobox.api.localization.TextVariables;
@@ -46,6 +45,7 @@ public class IslandTeamInviteCommand extends CompositeCommand {
     private long page = 0; // This number by 35
     private boolean inviteCmd;
     private static final long PER_PAGE = 35;
+    private String searchName = "";
 
     public IslandTeamInviteCommand(IslandTeamCommand parent) {
         super(parent, "invite");
@@ -210,7 +210,7 @@ public Optional<List<String>> tabComplete(User user, String alias, List<String>
      * Build the invite panel
      * @param user use of the panel
      */
-    public void build(User user) {
+    void build(User user) {
         this.user = user;
         // Start building panel.
         TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();
@@ -248,14 +248,21 @@ private PanelItem createBackButton(ItemTemplateRecord template, TemplatedPanel.I
 
     private PanelItem createSearchButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
         checkTemplate(template);
-        return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon())
+        PanelItemBuilder pib = new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon())
                 .clickHandler((panel, user, clickType, clickSlot) -> {
                     user.closeInventory();
                     new ConversationFactory(BentoBox.getInstance()).withLocalEcho(false).withTimeout(90)
                             .withModality(false).withFirstPrompt(new InviteNamePrompt(user, this))
                             .buildConversation(user.getPlayer()).begin();
                     return true;
-                }).build();
+                });
+        if (!this.searchName.isBlank()) {
+            pib.description(user.getTranslation(Objects
+                    .requireNonNullElse(template.description(),
+                            "commands.island.team.invite.gui.button.searching"),
+                    TextVariables.NAME, searchName));
+        }
+        return pib.build();
     }
 
     private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
@@ -322,6 +329,8 @@ private PanelItem createProspectButton(ItemTemplateRecord template, TemplatedPan
             page = 0;
         }
         return getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player))
+                .filter(player -> this.searchName.isBlank() ? true
+                        : player.getName().toLowerCase().contains(searchName.toLowerCase()))
                 .filter(player -> !player.equals(user.getPlayer())).skip(slot.slot() + page * PER_PAGE).findFirst()
                 .map(player -> getProspect(player, template)).orElse(this.getBlankBackground());
     }
@@ -384,4 +393,11 @@ private PanelItem getBlankBackground() {
                 .icon(Objects.requireNonNullElse(background.icon(), new ItemStack(Material.BARRIER)))
                 .name((Objects.requireNonNullElse(background.title(), ""))).build();
     }
+
+    /**
+     * @param searchName the searchName to set
+     */
+    void setSearchName(String searchName) {
+        this.searchName = searchName;
+    }
 }
diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml
index 6fa23e7cc..2a0bb2db4 100644
--- a/src/main/resources/locales/en-US.yml
+++ b/src/main/resources/locales/en-US.yml
@@ -717,6 +717,9 @@ commands:
           button:
             already-invited: "&c Invited already"
             search: "&a Search for a player"
+            searching: |
+              &b Searching for
+              &c [name]
           enter-name: "&a Enter name:" 
           tips:
             LEFT:
diff --git a/src/main/resources/panels/team_invite_panel.yml b/src/main/resources/panels/team_invite_panel.yml
index d082daa3d..7be5de6a0 100644
--- a/src/main/resources/panels/team_invite_panel.yml
+++ b/src/main/resources/panels/team_invite_panel.yml
@@ -38,6 +38,7 @@ team_invite_panel:
             tooltip: commands.island.team.invite.gui.tips.previous
       5:
         title: "commands.island.team.invite.gui.button.search"
+        description: "commands.island.team.invite.gui.button.searching"
         icon: PLAYER_HEAD
         data:
           type: SEARCH

From 2d2972c3e6583852cde76493945e0e51d7a04b5a Mon Sep 17 00:00:00 2001
From: tastybento <tastybento@users.noreply.github.com>
Date: Mon, 8 Jan 2024 19:42:51 +0900
Subject: [PATCH 22/22] Use Path.of

---
 src/main/java/world/bentobox/bentobox/BentoBox.java | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java
index b70c19c87..1ee3cfb1e 100644
--- a/src/main/java/world/bentobox/bentobox/BentoBox.java
+++ b/src/main/java/world/bentobox/bentobox/BentoBox.java
@@ -1,6 +1,7 @@
 package world.bentobox.bentobox;
 
-import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.List;
 import java.util.Optional;
 
@@ -466,10 +467,13 @@ public boolean loadSettings() {
         }
 
         log("Saving default panels...");
-        if (!new File(getDataFolder() + File.separator + "panels", "island_creation_panel.yml").exists()) {
+        if (!Files.exists(Path.of(this.getDataFolder().getPath(), "panels", "island_creation_panel.yml"))) {
+            log("Saving default island_creation_panel...");
             this.saveResource("panels/island_creation_panel.yml", false);
         }
-        if (!new File(getDataFolder() + File.separator + "panels", "language_panel.yml").exists()) {
+
+        if (!Files.exists(Path.of(this.getDataFolder().getPath(), "panels", "language_panel.yml"))) {
+            log("Saving default language_panel...");
             this.saveResource("panels/language_panel.yml", false);
         }
         return true;