Update to BentoBox API 1.20.

Replace plugin.yml with spigot-annotations.

Implement customizable TopLevelPanel.
This commit is contained in:
BONNe 2022-03-12 12:52:44 +02:00
parent 0a79b7fa58
commit e16fad882e
8 changed files with 709 additions and 14 deletions

View File

@ -59,7 +59,7 @@
<powermock.version>2.0.9</powermock.version>
<!-- More visible way how to change dependency versions -->
<spigot.version>1.16.5-R0.1-SNAPSHOT</spigot.version>
<bentobox.version>1.16.5-SNAPSHOT</bentobox.version>
<bentobox.version>1.20.0</bentobox.version>
<!-- Revision variable removes warning about dynamic version -->
<revision>${build.version}-SNAPSHOT</revision>
<!-- Do not change unless you want different name for local builds. -->
@ -146,6 +146,11 @@
<version>${spigot.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>plugin-annotations</artifactId>
<version>1.2.3-SNAPSHOT</version>
</dependency>
<!-- Mockito (Unit testing) -->
<dependency>
<groupId>org.mockito</groupId>

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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<String> list) {
addon.getManager().getGUI(getWorld(), user);
TopLevelPanel.openPanel(this.addon, user, this.getWorld(), this.getPermissionPrefix());
return true;
}
}

View File

@ -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<ItemTemplateRecord.ActionRecords> 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<String> 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("(?<!\\\\)\\|", "\n").
replaceAll("\\\\\\|", "|"));
}
else
{
// Now combine everything.
String descriptionText = this.user.getTranslation(reference + "description",
"[owner]", ownerText,
"[members]", memberText,
"[level]", levelText,
"[place]", placeText);
builder.description(descriptionText.
replaceAll("(?m)^[ \\t]*\\r?\\n", "").
replaceAll("(?<!\\\\)\\|", "\n").
replaceAll("\\\\\\|", "|"));
}
}
/**
* Create viewer button panel item.
*
* @return PanelItem for PanelBuilder.
*/
private PanelItem createViewerButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot itemSlot)
{
Island island = this.addon.getIslands().getIsland(this.world, this.user);
if (island == null || island.getOwner() == null)
{
// Player do not have an island.
return null;
}
int place = this.addon.getManager().getRank(this.world, this.user.getUniqueId());
long level = this.addon.getIslandLevel(this.world, island.getOwner());
IslandTopRecord record = new IslandTopRecord(island, level);
return this.createIslandIcon(template, record, place);
}
/**
* This method is used to open UserPanel outside this class. It will be much easier to open panel with single method
* call then initializing new object.
*
* @param addon Level Addon object
* @param user User who opens panel
* @param world World where gui is opened
* @param permissionPrefix Permission Prefix
*/
public static void openPanel(Level addon, User user, World world, String permissionPrefix)
{
new TopLevelPanel(addon, user, world, permissionPrefix).build();
}
/**
* This method gets string value of given permission prefix. If user does not have given permission or it have all
* (*), then return default value.
*
* @param user User who's permission should be checked.
* @param permissionPrefix Prefix that need to be found.
* @return String value that follows permissionPrefix.
*/
private static String getPermissionValue(User user, String permissionPrefix)
{
if (user != null && user.isPlayer())
{
if (permissionPrefix.endsWith("."))
{
permissionPrefix = permissionPrefix.substring(0, permissionPrefix.length() - 1);
}
String permPrefix = permissionPrefix + ".";
List<String> 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<IslandTopRecord> topIslands;
}

View File

@ -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-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."

View File

@ -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

View File

@ -1,9 +0,0 @@
name: Pladdon
main: world.bentobox.level.LevelPladdon
version: ${version}
api-version: "1.17"
description: Level Addon
author: tastybento
depend:
- BentoBox