addon-level/src/main/java/world/bentobox/level/panels/TopLevelPanel.java

548 lines
18 KiB
Java
Raw Normal View History

Version 2.10.0 (#283) * Version 2.7.1 * Version 2.7.2 * Use Java 9's takeWhile * Added placeholder %Level_[gamemode]_rank_value Fixes https://github.com/BentoBoxWorld/Level/issues/228 * No save on disable (#231) * Release 2.6.4 * Remove saving to database on disable. https://github.com/BentoBoxWorld/Level/issues/229 First, the top ten tables are never actually used or loaded. They are created in memory by loading the island levels. So there is no reason to keep saving them. Second, the island level data is saved every time it is changed, so there is no need to save all of the cache on exit. * Fixes tests * Rosestacker (#232) * Add support for RoseStacker 1.3.0 * Made plugin a Pladdon. * Version 2.8.0 * Added new placeholders %Level_%gamemode%_top_island_name_%rank% - lists the island name %Level_%gamemode%_top_island_members_%rank% - a comma separated list of team members https://github.com/BentoBoxWorld/Level/issues/224 https://github.com/BentoBoxWorld/Level/issues/211 https://github.com/BentoBoxWorld/Level/issues/132 https://github.com/BentoBoxWorld/Level/issues/107 https://github.com/BentoBoxWorld/Level/issues/105 * Update to BentoBox API 1.18 * Open up modules for testing access. * Back support for BentoBox 1.16.5. * Version 2.8.1 * Speeds up level calculation by doing more chunk scans async. If chests are scanned, then it will take longer because these have to be done sync. https://github.com/BentoBoxWorld/Level/issues/243 * add Vietnamese (#240) * Raw island level placeholder (#241) * Changed IslandLevelCalculator minHeight to world minHeight for negative blocks height support since 1.18. (#246) * Version 2.9.0 * Chinese Translation (#249) * Translate zh-CN.yml via GitLocalize * Translate zh-CN.yml via GitLocalize Co-authored-by: mt-gitlocalize <mt@gitlocalize.com> Co-authored-by: 织梦 <493733933@qq.com> * Translate id.yml via GitLocalize (#250) Co-authored-by: Nathan Adhitya <nathanadhitya@outlook.com> * Translate fr.yml via GitLocalize (#251) Co-authored-by: organizatsiya <organizatsiya.wildguns@gmail.com> * Korean translation (#252) * Translate ko.yml via GitLocalize * Translate ko.yml via GitLocalize Co-authored-by: chickiyeah <ruddls030@naver.com> Co-authored-by: mt-gitlocalize <mt@gitlocalize.com> * German Translation (#253) * Translate de.yml via GitLocalize * Update de.yml Co-authored-by: Rikamo045 <rik.amos.krajinovic@gmail.com> Co-authored-by: tastybento <tastybento@users.noreply.github.com> * Translate hu.yml via GitLocalize (#254) Co-authored-by: András Marczinkó <marczinkoandris@gmail.com> * Version 2.9.1 * Attempt to handle WildStacker spawners * Fix error lon loading id locale * Avoid async chunk snapshotting. Fixes https://github.com/BentoBoxWorld/Level/issues/256 * Update to BentoBox API 1.20. Replace plugin.yml with spigot-annotations. Implement customizable TopLevelPanel. * Fixes some small issues with TopLevelPanel Add Utils class that contains some useful things. * Implement customizable DetailsPanel. Remove old DetailsGUITab due to new implementation. * Fix failing test. * Remove blank file * Added repo for maven plugin snapshots * Implement feature that allows to sort items in detail panel. (#259) Apparently, because it is 2 years old request, it got in a state -> implement or drop. Fixes #192 * Implement calculated value for blocks. (#260) It is ~ value, as calculation formula cannot be applied per block. At least I think so. Part of #192 * Update es.yml (#261) * Implement customizable Values GUI. (#262) This GUI shows value to all items in game. It also shows max limit of blocks, if it is set. Fixes of #192 * Support for AdvancedChests was updated. (#266) * Implements visit/warp actions in top gui Add 2 new actions for island buttons in TOP GUI: - Visit -> allows to visit island, but it requires Visit Addon - Warp -> allows to warp to island, but it requires Warp Addon Requested via Discord. * Fixes a Level addon crash on startup. Level addon crashed at the startup if Visit or Warps addon were not installed. It happened because Level addon main class were implementing Listener interface. To avoid it and fix the crash, I moved migration listener to a separate class. Fixes #2012 * Translate pl.yml via GitLocalize (#269) Co-authored-by: wiktorm12 <wiktorm12@gmail.com> * Translate fr.yml via GitLocalize (#272) Co-authored-by: organizatsiya <organizatsiya.wildguns@gmail.com> * Update to Java 17 * Update Github workflow to Java 17 * Adds %Level_[gamemode]_island_level_max% placeholder This records the lifetime maximum level the island has ever had. Addresses #271 * Only shows Members or higher in the top members placeholder Fixes #267 * Add natural log to level-calc formula parsing Relates to #274 * feat: add island total points + placeholder (#264) * feat: add island total points + placeholder * Update IslandLevels.java * Fix JavaDoc * Translate zh-CN.yml via GitLocalize (#276) Co-authored-by: dawnTak <lanlongxiaode@outlook.com> * Translate nl.yml via GitLocalize (#277) Co-authored-by: DevSolaris <solaris.dev.2002@gmail.com> * Add ${argLine} to get jacoco coverage * Updated Jacoco POM * Add shulker to in chest count (#275) * Sonar Cloud code smell clean up (#278) * Refactor placeholders (#279) * Update ReadMe * Fix Jacoco * Remove unused imports * Remove placeholders from main class Created a separate class for cleaner code and added a test class. * Remove dependency * Add UltimateStacker hook for stacked blocks (#281) * Create plugin.yml (#282) * Create plugin.yml The annotations do not provide the option to define the version dynamically from maven. This should fix that. * Remove Spigot Plugin Annotations * Remove plugin-annotation repo * Updated dependencies --------- Co-authored-by: Huynh Tien <huynhqtienvtag@gmail.com> Co-authored-by: Rubén <44579213+Rubenicos@users.noreply.github.com> Co-authored-by: Pierre Dedrie <Pirgosth74@gmail.com> Co-authored-by: gitlocalize-app[bot] <55277160+gitlocalize-app[bot]@users.noreply.github.com> Co-authored-by: mt-gitlocalize <mt@gitlocalize.com> Co-authored-by: 织梦 <493733933@qq.com> Co-authored-by: Nathan Adhitya <nathanadhitya@outlook.com> Co-authored-by: organizatsiya <organizatsiya.wildguns@gmail.com> Co-authored-by: chickiyeah <ruddls030@naver.com> Co-authored-by: Rikamo045 <rik.amos.krajinovic@gmail.com> Co-authored-by: András Marczinkó <marczinkoandris@gmail.com> Co-authored-by: BONNe <bonne@bonne.id.lv> Co-authored-by: KrazyxWolf <68208993+KrazyxWolf@users.noreply.github.com> Co-authored-by: DeadSilenceIV <Barreto-h2@hotmail.com> Co-authored-by: wiktorm12 <wiktorm12@gmail.com> Co-authored-by: evlad <emmanuelvlad@gmail.com> Co-authored-by: dawnTak <lanlongxiaode@outlook.com> Co-authored-by: DevSolaris <solaris.dev.2002@gmail.com> Co-authored-by: DevSolaris <105156235+DevSolaris@users.noreply.github.com> Co-authored-by: ceze88 <dev.ceze@gmail.com>
2023-04-16 01:44:33 +02:00
///
// Created by BONNe
// Copyright - 2021
///
package world.bentobox.level.panels;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import world.bentobox.bentobox.api.addons.GameModeAddon;
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;
import world.bentobox.level.Level;
import world.bentobox.level.util.Utils;
/**
* 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;
}
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(),
TextVariables.NAME, String.valueOf(index)));
}
else
{
builder.name(this.user.getTranslation(this.world, REFERENCE,
TextVariables.NAME, String.valueOf(index)));
}
if (template.description() != null)
{
builder.description(this.user.getTranslation(this.world, template.description(),
TextVariables.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 ->
{
switch (action.actionType().toUpperCase())
{
case "WARP" -> {
return island.getOwner() == null ||
this.addon.getWarpHook() == null ||
!this.addon.getWarpHook().getWarpSignsManager().hasWarp(this.world, island.getOwner());
}
case "VISIT" -> {
return island.getOwner() == null ||
this.addon.getVisitHook() == null ||
!this.addon.getVisitHook().getAddonManager().preprocessTeleportation(this.user, island);
}
case "VIEW" -> {
return island.getOwner() == null ||
!island.getMemberSet(RanksManager.MEMBER_RANK).contains(this.user.getUniqueId());
}
default -> {
return false;
}
}
});
// Add Click handler
builder.clickHandler((panel, user, clickType, i) ->
{
for (ItemTemplateRecord.ActionRecords action : activeActions)
{
if (clickType == action.clickType() || action.clickType() == ClickType.UNKNOWN)
{
switch (action.actionType().toUpperCase())
{
case "WARP" -> {
this.user.closeInventory();
this.addon.getWarpHook().getWarpSignsManager().warpPlayer(this.world, this.user, island.getOwner());
}
case "VISIT" ->
// The command call implementation solves necessity to check for all visits options,
// like cool down, confirmation and preprocess in single go. Would it be better to write
// all logic here?
this.addon.getPlugin().getIWM().getAddon(this.world).
flatMap(GameModeAddon::getPlayerCommand).ifPresent(command ->
{
String mainCommand =
this.addon.getVisitHook().getSettings().getPlayerMainCommand();
if (!mainCommand.isBlank())
{
this.user.closeInventory();
this.user.performCommand(command.getTopLabel() + " " + mainCommand + " " + island.getOwner());
}
});
case "VIEW" -> {
this.user.closeInventory();
// Open Detailed GUI.
DetailsPanel.openPanel(this.addon, this.world, this.user);
}
// Catch default
default -> {
this.user.closeInventory();
addon.logError("Unknown action type " + action.actionType().toUpperCase());
}
}
}
}
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)
{
// 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(),
TextVariables.NAME, nameText));
}
else
{
builder.name(this.user.getTranslation(REFERENCE + "name", TextVariables.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 = owner == null ? null :
Utils.getPermissionValue(owner, this.iconPermission, null);
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)
{
// 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 member = User.getInstance(uuid);
if (memberBuilder.length() > 0)
{
memberBuilder.append("\n");
}
memberBuilder.append(
this.user.getTranslationOrNothing(REFERENCE + "member",
PLAYER, member.getName()));
}
memberText = memberBuilder.toString();
}
else
{
memberText = "";
}
String placeText = this.user.getTranslation(REFERENCE + "place",
TextVariables.NUMBER, String.valueOf(index));
String levelText = this.user.getTranslation(REFERENCE + "level",
TextVariables.NUMBER, this.addon.getManager().formatLevel(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").
replace("\\\\\\|", "|")); // Not a regex - replace is more efficient
}
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").
replace("\\\\\\|", "|")); // Not a regex - replace is more efficient
}
}
/**
* 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 topRecord = new IslandTopRecord(island, level);
return this.createIslandIcon(template, topRecord, 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();
}
// ---------------------------------------------------------------------
// Section: Constants
// ---------------------------------------------------------------------
private static final String REFERENCE = "level.gui.buttons.island.";
private static final String PLAYER = "[player]";
// ---------------------------------------------------------------------
// 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;
}