diff --git a/pom.xml b/pom.xml
index a3bd1f0..17d1dd9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,7 +59,7 @@
2.0.9
1.16.5-R0.1-SNAPSHOT
- 1.16.5-SNAPSHOT
+ 1.20.0
${build.version}-SNAPSHOT
@@ -146,6 +146,11 @@
${spigot.version}
provided
+
+ org.spigotmc
+ plugin-annotations
+ 1.2.3-SNAPSHOT
+
org.mockito
diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java
index 79c7a5e..c91a291 100644
--- a/src/main/java/world/bentobox/level/Level.java
+++ b/src/main/java/world/bentobox/level/Level.java
@@ -79,6 +79,10 @@ public class Level extends Addon implements Listener {
private boolean loadSettings() {
// Load settings again to get worlds
settings = configObject.loadConfigObject();
+
+ // Save existing panels.
+ this.saveResource("panels/top_panel.yml", false);
+
return settings == null;
}
diff --git a/src/main/java/world/bentobox/level/LevelPladdon.java b/src/main/java/world/bentobox/level/LevelPladdon.java
index 7bebe0c..2e8032e 100644
--- a/src/main/java/world/bentobox/level/LevelPladdon.java
+++ b/src/main/java/world/bentobox/level/LevelPladdon.java
@@ -1,16 +1,23 @@
package world.bentobox.level;
+
+import org.bukkit.plugin.java.annotation.dependency.Dependency;
+import org.bukkit.plugin.java.annotation.plugin.ApiVersion;
+import org.bukkit.plugin.java.annotation.plugin.Plugin;
+
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.Pladdon;
+
/**
* @author tastybento
*
*/
+@Plugin(name="Pladdon", version="1.0")
+@ApiVersion(ApiVersion.Target.v1_16)
+@Dependency(value = "BentoBox")
public class LevelPladdon extends Pladdon {
-
@Override
public Addon getAddon() {
return new Level();
}
-
}
diff --git a/src/main/java/world/bentobox/level/commands/IslandTopCommand.java b/src/main/java/world/bentobox/level/commands/IslandTopCommand.java
index 7521b9c..5e35d3e 100644
--- a/src/main/java/world/bentobox/level/commands/IslandTopCommand.java
+++ b/src/main/java/world/bentobox/level/commands/IslandTopCommand.java
@@ -5,6 +5,8 @@ import java.util.List;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.level.Level;
+import world.bentobox.level.panels.TopLevelPanel;
+
public class IslandTopCommand extends CompositeCommand {
@@ -24,7 +26,7 @@ public class IslandTopCommand extends CompositeCommand {
@Override
public boolean execute(User user, String label, List list) {
- addon.getManager().getGUI(getWorld(), user);
+ TopLevelPanel.openPanel(this.addon, user, this.getWorld(), this.getPermissionPrefix());
return true;
}
}
diff --git a/src/main/java/world/bentobox/level/panels/TopLevelPanel.java b/src/main/java/world/bentobox/level/panels/TopLevelPanel.java
new file mode 100644
index 0000000..c9f5741
--- /dev/null
+++ b/src/main/java/world/bentobox/level/panels/TopLevelPanel.java
@@ -0,0 +1,532 @@
+///
+// Created by BONNe
+// Copyright - 2021
+///
+
+package world.bentobox.level.panels;
+
+
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.permissions.PermissionAttachmentInfo;
+import java.io.File;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+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;
+import world.bentobox.level.Level;
+
+
+/**
+ * This panel opens top likes panel
+ */
+public class TopLevelPanel
+{
+// ---------------------------------------------------------------------
+// Section: Internal Constructor
+// ---------------------------------------------------------------------
+
+
+ /**
+ * This is internal constructor. It is used internally in current class to avoid creating objects everywhere.
+ *
+ * @param addon Level object.
+ * @param user User who opens Panel.
+ * @param world World where gui is opened
+ * @param permissionPrefix Permission Prefix
+ */
+ private TopLevelPanel(Level addon, User user, World world, String permissionPrefix)
+ {
+ this.addon = addon;
+ this.user = user;
+ this.world = world;
+
+ this.iconPermission = permissionPrefix + "level.icon";
+
+ this.topIslands = this.addon.getManager().getTopTen(this.world, 10).entrySet().stream().
+ map(entry -> {
+ Island island = this.addon.getIslandsManager().getIsland(this.world, entry.getKey());
+ return new IslandTopRecord(island, entry.getValue());
+ }).
+ collect(Collectors.toList());
+ }
+
+
+ /**
+ * Build method manages current panel opening. It uses BentoBox PanelAPI that is easy to use and users can get nice
+ * panels.
+ */
+ public void build()
+ {
+ TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();
+
+ panelBuilder.user(this.user);
+ panelBuilder.world(this.world);
+
+ panelBuilder.template("top_panel", new File(this.addon.getDataFolder(), "panels"));
+
+ panelBuilder.registerTypeBuilder("VIEW", this::createViewerButton);
+ panelBuilder.registerTypeBuilder("TOP", this::createPlayerButton);
+
+ // Register unknown type builder.
+ panelBuilder.build();
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Methods
+// ---------------------------------------------------------------------
+
+
+ /**
+ * Creates fallback based on template.
+ * @param template Template record for fallback button.
+ * @param index Place of the fallback.
+ * @return Fallback panel item.
+ */
+ private PanelItem createFallback(ItemTemplateRecord template, long index)
+ {
+ if (template == null)
+ {
+ return null;
+ }
+
+ final String reference = "level.gui.buttons.island.";
+
+ PanelItemBuilder builder = new PanelItemBuilder();
+
+ if (template.icon() != null)
+ {
+ builder.icon(template.icon().clone());
+ }
+
+ if (template.title() != null)
+ {
+ builder.name(this.user.getTranslation(this.world, template.title(),
+ "[name]", String.valueOf(index)));
+ }
+ else
+ {
+ builder.name(this.user.getTranslation(this.world, reference,
+ "[name]", String.valueOf(index)));
+ }
+
+ if (template.description() != null)
+ {
+ builder.description(this.user.getTranslation(this.world, template.description(),
+ "[number]", String.valueOf(index)));
+ }
+
+ builder.amount(index != 0 ? (int) index : 1);
+
+ return builder.build();
+ }
+
+
+ /**
+ * This method creates player icon with warp functionality.
+ *
+ * @return PanelItem for PanelBuilder.
+ */
+ private PanelItem createPlayerButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot itemSlot)
+ {
+ int index = (int) template.dataMap().getOrDefault("index", 0);
+
+ if (index < 1)
+ {
+ return this.createFallback(template.fallback(), index);
+ }
+
+ IslandTopRecord islandTopRecord = this.topIslands.size() < index ? null : this.topIslands.get(index - 1);
+
+ if (islandTopRecord == null)
+ {
+ return this.createFallback(template.fallback(), index);
+ }
+
+ return this.createIslandIcon(template, islandTopRecord, index);
+ }
+
+
+ /**
+ * This method creates button from template for given island top record.
+ * @param template Icon Template.
+ * @param islandTopRecord Island Top Record.
+ * @param index Place Index.
+ * @return PanelItem for PanelBuilder.
+ */
+ private PanelItem createIslandIcon(ItemTemplateRecord template, IslandTopRecord islandTopRecord, int index)
+ {
+ // Get player island.
+ Island island = islandTopRecord.island();
+
+ if (island == null)
+ {
+ return this.createFallback(template.fallback(), index);
+ }
+
+ PanelItemBuilder builder = new PanelItemBuilder();
+
+ this.populateIslandIcon(builder, template, island);
+ this.populateIslandTitle(builder, template, island);
+ this.populateIslandDescription(builder, template, island, islandTopRecord, index);
+
+ builder.amount(index);
+
+ // Get only possible actions, by removing all inactive ones.
+ List activeActions = new ArrayList<>(template.actions());
+
+ activeActions.removeIf(action ->
+ "VIEW".equalsIgnoreCase(action.actionType()) && island.getOwner() == null &&
+ island.getMemberSet(RanksManager.MEMBER_RANK).
+ contains(this.user.getUniqueId()));
+
+ // Add Click handler
+ builder.clickHandler((panel, user, clickType, i) ->
+ {
+ for (ItemTemplateRecord.ActionRecords action : activeActions)
+ {
+ if (clickType == action.clickType() && "VIEW".equalsIgnoreCase(action.actionType()))
+ {
+ this.user.closeInventory();
+ // Open Detailed GUI.
+ }
+ }
+
+ return true;
+ });
+
+ // Collect tooltips.
+ List tooltips = activeActions.stream().
+ filter(action -> action.tooltip() != null).
+ map(action -> this.user.getTranslation(this.world, action.tooltip())).
+ filter(text -> !text.isBlank()).
+ collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
+
+ // Add tooltips.
+ if (!tooltips.isEmpty())
+ {
+ // Empty line and tooltips.
+ builder.description("");
+ builder.description(tooltips);
+ }
+
+ return builder.build();
+ }
+
+
+ /**
+ * Populate given panel item builder name with values from template and island objects.
+ *
+ * @param builder the builder
+ * @param template the template
+ * @param island the island
+ */
+ private void populateIslandTitle(PanelItemBuilder builder,
+ ItemTemplateRecord template,
+ Island island)
+ {
+ final String reference = "level.gui.buttons.island.";
+
+ // Get Island Name
+ String nameText;
+
+ if (island.getName() == null || island.getName().isEmpty())
+ {
+ nameText = this.user.getTranslation(reference + "owners-island",
+ "[player]",
+ island.getOwner() == null ?
+ this.user.getTranslation(reference + "unknown") :
+ this.addon.getPlayers().getName(island.getOwner()));
+ }
+ else
+ {
+ nameText = island.getName();
+ }
+
+ // Template specific title is always more important than custom one.
+ if (template.title() != null && !template.title().isBlank())
+ {
+ builder.name(this.user.getTranslation(this.world, template.title(),
+ "[name]", nameText));
+ }
+ else
+ {
+ builder.name(this.user.getTranslation(reference + "name", "[name]", nameText));
+ }
+ }
+
+
+ /**
+ * Populate given panel item builder icon with values from template and island objects.
+ *
+ * @param builder the builder
+ * @param template the template
+ * @param island the island
+ */
+ private void populateIslandIcon(PanelItemBuilder builder,
+ ItemTemplateRecord template,
+ Island island)
+ {
+ User owner = island.getOwner() == null ? null : User.getInstance(island.getOwner());
+
+ // Get permission or island icon
+ String permissionIcon = TopLevelPanel.getPermissionValue(owner, this.iconPermission);
+
+ Material material;
+
+ if (permissionIcon != null && !permissionIcon.equals("*"))
+ {
+ material = Material.matchMaterial(permissionIcon);
+ }
+ else
+ {
+ material = null;
+ }
+
+ if (material != null)
+ {
+ if (!material.equals(Material.PLAYER_HEAD))
+ {
+ builder.icon(material);
+ }
+ else
+ {
+ builder.icon(owner.getName());
+ }
+ }
+ else if (template.icon() != null)
+ {
+ builder.icon(template.icon().clone());
+ }
+ else if (owner != null)
+ {
+ builder.icon(owner.getName());
+ }
+ else
+ {
+ builder.icon(Material.PLAYER_HEAD);
+ }
+ }
+
+
+ /**
+ * Populate given panel item builder description with values from template and island objects.
+ *
+ * @param builder the builder
+ * @param template the template
+ * @param island the island
+ * @param islandTopRecord the top record object
+ * @param index place index.
+ */
+ private void populateIslandDescription(PanelItemBuilder builder,
+ ItemTemplateRecord template,
+ Island island,
+ IslandTopRecord islandTopRecord,
+ int index)
+ {
+ final String reference = "level.gui.buttons.island.";
+
+ // Get Owner Name
+ String ownerText = this.user.getTranslation(reference + "owner",
+ "[player]",
+ island.getOwner() == null ?
+ this.user.getTranslation(reference + "unknown") :
+ this.addon.getPlayers().getName(island.getOwner()));
+
+ // Get Members Text
+ String memberText;
+
+ if (island.getMemberSet().size() > 1)
+ {
+ StringBuilder memberBuilder = new StringBuilder(
+ this.user.getTranslationOrNothing(reference + "members-title"));
+
+ for (UUID uuid : island.getMemberSet())
+ {
+ User user = User.getInstance(uuid);
+
+ if (memberBuilder.length() > 0)
+ {
+ memberBuilder.append("\n");
+ }
+
+ memberBuilder.append(
+ this.user.getTranslationOrNothing(reference + "member",
+ "[player]", user.getName()));
+ }
+
+ memberText = memberBuilder.toString();
+ }
+ else
+ {
+ memberText = "";
+ }
+
+ String placeText = this.user.getTranslation(reference + "place",
+ "[number]", String.valueOf(index));
+
+ String levelText = this.user.getTranslation(reference + "level",
+ "[number]", String.valueOf(islandTopRecord.level()));
+
+ // Template specific description is always more important than custom one.
+ if (template.description() != null && !template.description().isBlank())
+ {
+ builder.description(this.user.getTranslation(this.world, template.description(),
+ "[owner]", ownerText,
+ "[members]", memberText,
+ "[level]", levelText,
+ "[place]", placeText).
+ replaceAll("(?m)^[ \\t]*\\r?\\n", "").
+ replaceAll("(? permissions = user.getEffectivePermissions().stream().
+ map(PermissionAttachmentInfo::getPermission).
+ filter(permission -> permission.startsWith(permPrefix)).
+ collect(Collectors.toList());
+
+ for (String permission : permissions)
+ {
+ if (permission.contains(permPrefix + "*"))
+ {
+ // * means all. So continue to search more specific.
+ continue;
+ }
+
+ String[] parts = permission.split(permPrefix);
+
+ if (parts.length > 1)
+ {
+ return parts[1];
+ }
+ }
+ }
+
+ return null;
+ }
+
+
+// ---------------------------------------------------------------------
+// Section: Record
+// ---------------------------------------------------------------------
+
+
+ /**
+ * This record is used internally. It converts user -> level to island -> level.
+ */
+ private record IslandTopRecord(Island island, Long level) {}
+
+
+// ---------------------------------------------------------------------
+// Section: Variables
+// ---------------------------------------------------------------------
+
+ /**
+ * This variable allows to access addon object.
+ */
+ private final Level addon;
+
+ /**
+ * This variable holds user who opens panel. Without it panel cannot be opened.
+ */
+ private final User user;
+
+ /**
+ * This variable holds a world to which gui referee.
+ */
+ private final World world;
+
+ /**
+ * Location to icon permission.
+ */
+ private final String iconPermission;
+
+ /**
+ * List of top 10 island records.
+ */
+ private final List topIslands;
+}
diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml
index 6b75387..144aee9 100755
--- a/src/main/resources/locales/en-US.yml
+++ b/src/main/resources/locales/en-US.yml
@@ -60,4 +60,34 @@ island:
success: "&7 The value of this block is: &e[value]"
success-underwater: "&7 The value of this block below sea-level: &e[value]"
empty-hand: "&c There are no blocks in your hand"
- no-value: "&c That item has no value."
\ No newline at end of file
+ no-value: "&c That item has no value."
+
+level:
+ gui:
+ titles:
+ top: "&0&l Top Islands"
+ buttons:
+ island:
+ empty: '&f&l [name]. place'
+ name: '&f&l [name]'
+ description: |-
+ [owner]
+ [members]
+ [place]
+ [level]
+ # Text that is replacing [name] if island do not have a name
+ owners-island: "[player]'s Island"
+ # Text for [owner] in description.
+ owner: "&7&l Owner: &r&b [player]"
+ # Title before listing members for [members] in description
+ members-title: "&7&l Members:"
+ # List each member under the title for [members] in description
+ member: "&b - [player]"
+ # Name of unknown player.
+ unknown: "unknown"
+ # Section for parsing [place]
+ place: "&7&o [number]. &r&7 place"
+ # Section for parsing [level]
+ level: "&7 Level: &o [number]"
+ tips:
+ click-to-view: "&e Click &7 to view."
diff --git a/src/main/resources/panels/top_panel.yml b/src/main/resources/panels/top_panel.yml
new file mode 100644
index 0000000..6f24683
--- /dev/null
+++ b/src/main/resources/panels/top_panel.yml
@@ -0,0 +1,124 @@
+top_panel:
+ title: level.gui.titles.top
+ type: INVENTORY
+ background:
+ icon: BLACK_STAINED_GLASS_PANE
+ title: "&b&r" # Empty text
+ border:
+ icon: BLACK_STAINED_GLASS_PANE
+ title: "&b&r" # Empty text
+ force-shown: [2,3,4,5]
+ content:
+ 2:
+ 5:
+ icon: PLAYER_HEAD
+ title: level.gui.buttons.island.name
+ description: level.gui.buttons.island.description
+ data:
+ type: TOP
+ index: 1
+ fallback:
+ icon: LIME_STAINED_GLASS_PANE
+ title: level.gui.buttons.island.empty
+ 3:
+ 4:
+ icon: PLAYER_HEAD
+ title: level.gui.buttons.island.name
+ description: level.gui.buttons.island.description
+ data:
+ type: TOP
+ index: 2
+ fallback:
+ icon: LIME_STAINED_GLASS_PANE
+ title: level.gui.buttons.island.empty
+ 6:
+ icon: PLAYER_HEAD
+ title: level.gui.buttons.island.name
+ description: level.gui.buttons.island.description
+ data:
+ type: TOP
+ index: 3
+ fallback:
+ icon: LIME_STAINED_GLASS_PANE
+ title: level.gui.buttons.island.empty
+ 4:
+ 2:
+ icon: PLAYER_HEAD
+ title: level.gui.buttons.island.name
+ description: level.gui.buttons.island.description
+ data:
+ type: TOP
+ index: 4
+ fallback:
+ icon: LIME_STAINED_GLASS_PANE
+ title: level.gui.buttons.island.empty
+ 3:
+ icon: PLAYER_HEAD
+ title: level.gui.buttons.island.name
+ description: level.gui.buttons.island.description
+ data:
+ type: TOP
+ index: 5
+ fallback:
+ icon: LIME_STAINED_GLASS_PANE
+ title: level.gui.buttons.island.empty
+ 4:
+ icon: PLAYER_HEAD
+ title: level.gui.buttons.island.name
+ description: level.gui.buttons.island.description
+ data:
+ type: TOP
+ index: 6
+ fallback:
+ icon: LIME_STAINED_GLASS_PANE
+ title: level.gui.buttons.island.empty
+ 5:
+ icon: PLAYER_HEAD
+ title: level.gui.buttons.island.name
+ description: level.gui.buttons.island.description
+ data:
+ type: TOP
+ index: 7
+ fallback:
+ icon: LIME_STAINED_GLASS_PANE
+ title: level.gui.buttons.island.empty
+ 6:
+ icon: PLAYER_HEAD
+ title: level.gui.buttons.island.name
+ description: level.gui.buttons.island.description
+ data:
+ type: TOP
+ index: 8
+ fallback:
+ icon: LIME_STAINED_GLASS_PANE
+ title: level.gui.buttons.island.empty
+ 7:
+ icon: PLAYER_HEAD
+ title: level.gui.buttons.island.name
+ description: level.gui.buttons.island.description
+ data:
+ type: TOP
+ index: 9
+ fallback:
+ icon: LIME_STAINED_GLASS_PANE
+ title: level.gui.buttons.island.empty
+ 8:
+ icon: PLAYER_HEAD
+ title: level.gui.buttons.island.name
+ description: level.gui.buttons.island.description
+ data:
+ type: TOP
+ index: 10
+ fallback:
+ icon: LIME_STAINED_GLASS_PANE
+ title: level.gui.buttons.island.empty
+ 6:
+ 5:
+ icon: PLAYER_HEAD
+ title: level.gui.buttons.island.name
+ description: level.gui.buttons.island.description
+ data:
+ type: VIEW
+ actions:
+ left:
+ tooltip: level.gui.tips.click-to-view
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
deleted file mode 100644
index c5bd022..0000000
--- a/src/main/resources/plugin.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-name: Pladdon
-main: world.bentobox.level.LevelPladdon
-version: ${version}
-api-version: "1.17"
-description: Level Addon
-author: tastybento
-depend:
- - BentoBox
-