package world.bentobox.level.panels; import java.io.File; import java.util.ArrayList; import java.util.Comparator; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.ItemStack; import com.google.common.base.Enums; import lv.id.bonne.panelutils.PanelUtils; 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.util.Pair; import world.bentobox.level.Level; import world.bentobox.level.objects.IslandLevels; import world.bentobox.level.util.Utils; /** * This class opens GUI that shows generator view for user. */ public class DetailsPanel { // --------------------------------------------------------------------- // Section: Internal Constructor // --------------------------------------------------------------------- /** * This is internal constructor. It is used internally in current class to avoid creating objects everywhere. * * @param addon Level object * @param world World where user is operating * @param user User who opens panel */ private DetailsPanel(Level addon, World world, User user) { this.addon = addon; this.world = world; this.user = user; this.island = this.addon.getIslands().getIsland(world, user); if (this.island != null) { this.levelsData = this.addon.getManager().getLevelsData(this.island); } else { this.levelsData = null; } // By default no-filters are active. this.activeTab = Tab.ALL_BLOCKS; this.activeFilter = Filter.NAME; this.materialCountList = new ArrayList<>(Material.values().length); this.updateFilters(); } /** * This method builds this GUI. */ private void build() { if (this.island == null || this.levelsData == null) { // Nothing to see. Utils.sendMessage(this.user, this.user.getTranslation("general.errors.no-island")); return; } if (this.levelsData.getMdCount().isEmpty() && this.levelsData.getUwCount().isEmpty()) { // Nothing to see. Utils.sendMessage(this.user, this.user.getTranslation("level.conversations.no-data")); return; } // Start building panel. TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); panelBuilder.user(this.user); panelBuilder.world(this.user.getWorld()); panelBuilder.template("detail_panel", new File(this.addon.getDataFolder(), "panels")); panelBuilder.parameters("[name]", this.user.getName()); panelBuilder.registerTypeBuilder("NEXT", this::createNextButton); panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton); panelBuilder.registerTypeBuilder("BLOCK", this::createMaterialButton); panelBuilder.registerTypeBuilder("FILTER", this::createFilterButton); // Register tabs panelBuilder.registerTypeBuilder("TAB", this::createTabButton); // Register unknown type builder. panelBuilder.build(); } /** * This method updates filter of elements based on tabs. */ private void updateFilters() { this.materialCountList.clear(); switch (this.activeTab) { case ALL_BLOCKS -> { Map materialCountMap = new EnumMap<>(Material.class); materialCountMap.putAll(this.levelsData.getMdCount()); // Add underwater blocks. this.levelsData.getUwCount().forEach((material, count) -> materialCountMap.put(material, materialCountMap.computeIfAbsent(material, key -> 0) + count)); materialCountMap.entrySet().stream().sorted((Map.Entry.comparingByKey())). forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); } case ABOVE_SEA_LEVEL -> this.levelsData.getMdCount().entrySet().stream().sorted((Map.Entry.comparingByKey())) .forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); case UNDERWATER -> this.levelsData.getUwCount().entrySet().stream().sorted((Map.Entry.comparingByKey())) .forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); case SPAWNER -> { int aboveWater = this.levelsData.getMdCount().getOrDefault(Material.SPAWNER, 0); int underWater = this.levelsData.getUwCount().getOrDefault(Material.SPAWNER, 0); // TODO: spawners need some touch... this.materialCountList.add(new Pair<>(Material.SPAWNER, underWater + aboveWater)); } } Comparator> sorter; switch (this.activeFilter) { case COUNT -> { sorter = (o1, o2) -> { if (o1.getValue().equals(o2.getValue())) { String o1Name = Utils.prettifyObject(o1.getKey(), this.user); String o2Name = Utils.prettifyObject(o2.getKey(), this.user); return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); } else { return Integer.compare(o2.getValue(), o1.getValue()); } }; } case VALUE -> { sorter = (o1, o2) -> { int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o1.getKey(), 0); int o1Count = blockLimit > 0 ? Math.min(o1.getValue(), blockLimit) : o1.getValue(); blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o2.getKey(), 0); int o2Count = blockLimit > 0 ? Math.min(o2.getValue(), blockLimit) : o2.getValue(); long o1Value = (long) o1Count * this.addon.getBlockConfig().getBlockValues().getOrDefault(o1.getKey(), 0); long o2Value = (long) o2Count * this.addon.getBlockConfig().getBlockValues().getOrDefault(o2.getKey(), 0); if (o1Value == o2Value) { String o1Name = Utils.prettifyObject(o1.getKey(), this.user); String o2Name = Utils.prettifyObject(o2.getKey(), this.user); return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); } else { return Long.compare(o2Value, o1Value); } }; } default -> { sorter = (o1, o2) -> { String o1Name = Utils.prettifyObject(o1.getKey(), this.user); String o2Name = Utils.prettifyObject(o2.getKey(), this.user); return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); }; } } this.materialCountList.sort(sorter); this.pageIndex = 0; } // --------------------------------------------------------------------- // Section: Tab Button Type // --------------------------------------------------------------------- /** * Create tab button panel item. * * @param template the template * @param slot the slot * @return the panel item */ private PanelItem createTabButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { PanelItemBuilder builder = new PanelItemBuilder(); if (template.icon() != null) { // Set icon builder.icon(template.icon().clone()); } if (template.title() != null) { // Set title builder.name(this.user.getTranslation(this.world, template.title())); } if (template.description() != null) { // Set description builder.description(this.user.getTranslation(this.world, template.description())); } Tab tab = Enums.getIfPresent(Tab.class, String.valueOf(template.dataMap().get("tab"))).or(Tab.ALL_BLOCKS); // Get only possible actions, by removing all inactive ones. List activeActions = new ArrayList<>(template.actions()); activeActions.removeIf(action -> "VIEW".equalsIgnoreCase(action.actionType()) && this.activeTab == tab); // Add Click handler builder.clickHandler((panel, user, clickType, i) -> { for (ItemTemplateRecord.ActionRecords action : activeActions) { if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) && "VIEW".equalsIgnoreCase(action.actionType())) { this.activeTab = tab; // Update filters. this.updateFilters(); this.build(); } } 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); } builder.glow(this.activeTab == tab); return builder.build(); } /** * Create next button panel item. * * @param template the template * @param slot the slot * @return the panel item */ private PanelItem createFilterButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { PanelItemBuilder builder = new PanelItemBuilder(); if (template.icon() != null) { // Set icon builder.icon(template.icon().clone()); } Filter filter; if (slot.amountMap().getOrDefault("FILTER", 0) > 1) { filter = Enums.getIfPresent(Filter.class, String.valueOf(template.dataMap().get("filter"))).or(Filter.NAME); } else { filter = this.activeFilter; } final String reference = "level.gui.buttons.filters."; if (template.title() != null) { // Set title builder.name(this.user.getTranslation(this.world, template.title().replace("[filter]", filter.name().toLowerCase()))); } else { builder.name(this.user.getTranslation(this.world, reference + filter.name().toLowerCase() + ".name")); } if (template.description() != null) { // Set description builder.description(this.user.getTranslation(this.world, template.description().replace("[filter]", filter.name().toLowerCase()))); } else { builder.name(this.user.getTranslation(this.world, reference + filter.name().toLowerCase() + ".description")); } // Get only possible actions, by removing all inactive ones. List activeActions = new ArrayList<>(template.actions()); // Add Click handler builder.clickHandler((panel, user, clickType, i) -> { for (ItemTemplateRecord.ActionRecords action : activeActions) { if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) { if ("UP".equalsIgnoreCase(action.actionType())) { this.activeFilter = Utils.getNextValue(Filter.values(), filter); // Update filters. this.updateFilters(); this.build(); } else if ("DOWN".equalsIgnoreCase(action.actionType())) { this.activeFilter = Utils.getPreviousValue(Filter.values(), filter); // Update filters. this.updateFilters(); this.build(); } else if ("SELECT".equalsIgnoreCase(action.actionType())) { this.activeFilter = filter; // Update filters. this.updateFilters(); this.build(); } } } 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); } builder.glow(this.activeFilter == filter); return builder.build(); } // --------------------------------------------------------------------- // Section: Create common buttons // --------------------------------------------------------------------- /** * Create next button panel item. * * @param template the template * @param slot the slot * @return the panel item */ private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { long size = this.materialCountList.size(); if (size <= slot.amountMap().getOrDefault("BLOCK", 1) || 1.0 * size / slot.amountMap().getOrDefault("BLOCK", 1) <= this.pageIndex + 1) { // There are no next elements return null; } int nextPageIndex = this.pageIndex + 2; PanelItemBuilder builder = new PanelItemBuilder(); if (template.icon() != null) { ItemStack clone = template.icon().clone(); if (Boolean.TRUE.equals(template.dataMap().getOrDefault("indexing", false))) { clone.setAmount(nextPageIndex); } builder.icon(clone); } if (template.title() != null) { builder.name(this.user.getTranslation(this.world, template.title())); } if (template.description() != null) { builder.description(this.user.getTranslation(this.world, template.description(), TextVariables.NUMBER, String.valueOf(nextPageIndex))); } // Add ClickHandler builder.clickHandler((panel, user, clickType, i) -> { for (ItemTemplateRecord.ActionRecords action : template.actions()) { if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) && "NEXT".equalsIgnoreCase(action.actionType())) { this.pageIndex++; this.build(); } } // Always return true. return true; }); // Collect tooltips. List tooltips = template.actions().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(); } /** * Create previous button panel item. * * @param template the template * @param slot the slot * @return the panel item */ private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { if (this.pageIndex == 0) { // There are no next elements return null; } int previousPageIndex = this.pageIndex; PanelItemBuilder builder = new PanelItemBuilder(); if (template.icon() != null) { ItemStack clone = template.icon().clone(); if (Boolean.TRUE.equals(template.dataMap().getOrDefault("indexing", false))) { clone.setAmount(previousPageIndex); } builder.icon(clone); } if (template.title() != null) { builder.name(this.user.getTranslation(this.world, template.title())); } if (template.description() != null) { builder.description(this.user.getTranslation(this.world, template.description(), TextVariables.NUMBER, String.valueOf(previousPageIndex))); } // Add ClickHandler builder.clickHandler((panel, user, clickType, i) -> { for (ItemTemplateRecord.ActionRecords action : template.actions()) { if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) && "PREVIOUS".equalsIgnoreCase(action.actionType())) { this.pageIndex--; this.build(); } } // Always return true. return true; }); // Collect tooltips. List tooltips = template.actions().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(); } // --------------------------------------------------------------------- // Section: Create Material Button // --------------------------------------------------------------------- /** * Create material button panel item. * * @param template the template * @param slot the slot * @return the panel item */ private PanelItem createMaterialButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { if (this.materialCountList.isEmpty()) { // Does not contain any generators. return null; } int index = this.pageIndex * slot.amountMap().getOrDefault("BLOCK", 1) + slot.slot(); if (index >= this.materialCountList.size()) { // Out of index. return null; } return this.createMaterialButton(template, this.materialCountList.get(index)); } /** * This method creates button for material. * * @param template the template of the button * @param materialCount materialCount which button must be created. * @return PanelItem for generator tier. */ private PanelItem createMaterialButton(ItemTemplateRecord template, Pair materialCount) { PanelItemBuilder builder = new PanelItemBuilder(); if (template.icon() != null) { builder.icon(template.icon().clone()); } else { builder.icon(PanelUtils.getMaterialItem(materialCount.getKey())); } if (materialCount.getValue() < 64) { builder.amount(materialCount.getValue()); } if (template.title() != null) { builder.name(this.user.getTranslation(this.world, template.title(), TextVariables.NUMBER, String.valueOf(materialCount.getValue()), "[material]", Utils.prettifyObject(materialCount.getKey(), this.user))); } String description = Utils.prettifyDescription(materialCount.getKey(), this.user); final String reference = "level.gui.buttons.material."; String blockId = this.user.getTranslationOrNothing(reference + "id", "[id]", materialCount.getKey().name()); int blockValue = this.addon.getBlockConfig().getBlockValues().getOrDefault(materialCount.getKey(), 0); String value = blockValue > 0 ? this.user.getTranslationOrNothing(reference + "value", TextVariables.NUMBER, String.valueOf(blockValue)) : ""; int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(materialCount.getKey(), 0); String limit = blockLimit > 0 ? this.user.getTranslationOrNothing(reference + "limit", TextVariables.NUMBER, String.valueOf(blockLimit)) : ""; String count = this.user.getTranslationOrNothing(reference + "count", TextVariables.NUMBER, String.valueOf(materialCount.getValue())); long calculatedValue = (long) Math.min(blockLimit > 0 ? blockLimit : Integer.MAX_VALUE, materialCount.getValue()) * blockValue; String valueText = calculatedValue > 0 ? this.user.getTranslationOrNothing(reference + "calculated", TextVariables.NUMBER, String.valueOf(calculatedValue)) : ""; if (template.description() != null) { builder.description(this.user.getTranslation(this.world, template.description(), "[description]", description, "[id]", blockId, "[value]", value, "[calculated]", valueText, "[limit]", limit, "[count]", count). replaceAll("(?m)^[ \\t]*\\r?\\n", ""). replaceAll("(?> materialCountList; /** * This variable holds current pageIndex for multi-page generator choosing. */ private int pageIndex; /** * This variable stores which tab currently is active. */ private Tab activeTab; /** * This variable stores active filter for items. */ private Filter activeFilter; }