From e16fad882ee897eed9f48d959a39a130c4221342 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sat, 12 Mar 2022 12:52:44 +0200 Subject: [PATCH] Update to BentoBox API 1.20. Replace plugin.yml with spigot-annotations. Implement customizable TopLevelPanel. --- pom.xml | 7 +- src/main/java/world/bentobox/level/Level.java | 4 + .../world/bentobox/level/LevelPladdon.java | 11 +- .../level/commands/IslandTopCommand.java | 4 +- .../bentobox/level/panels/TopLevelPanel.java | 532 ++++++++++++++++++ src/main/resources/locales/en-US.yml | 32 +- src/main/resources/panels/top_panel.yml | 124 ++++ src/main/resources/plugin.yml | 9 - 8 files changed, 709 insertions(+), 14 deletions(-) create mode 100644 src/main/java/world/bentobox/level/panels/TopLevelPanel.java create mode 100644 src/main/resources/panels/top_panel.yml delete mode 100644 src/main/resources/plugin.yml 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 -