From 145022fd5c1ebaf61519c32f1900a56c9423c488 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 11:39:33 +0000 Subject: [PATCH 1/9] Initial plan From 739e22649a813d28eaf8fd96af4145db0789e4a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 11:41:08 +0000 Subject: [PATCH 2/9] Add Russian locale file (MiniMessage format) Agent-Logs-Url: https://github.com/BentoBoxWorld/TopBlock/sessions/8bbe2987-7542-416b-8201-ecded7380737 Co-authored-by: tastybento <4407265+tastybento@users.noreply.github.com> --- src/main/resources/locales/ru.yml | 66 +++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/main/resources/locales/ru.yml diff --git a/src/main/resources/locales/ru.yml b/src/main/resources/locales/ru.yml new file mode 100644 index 0000000..6ffcf76 --- /dev/null +++ b/src/main/resources/locales/ru.yml @@ -0,0 +1,66 @@ +# ######################################################################################## # +# Это YML файл. Будьте осторожны при редактировании. Проверяйте свои правки # +# в YAML валидаторе, например, на http://yaml-online-parser.appspot.com # +# ######################################################################################## # + +island: + topblock: + description: показать десять лучших по AOneBlock + gui-title: 'Десятка лучших' + gui-heading: '[name]: [rank]' + island-level: 'Количество [count]' + +topblock: + gui: + titles: + top: 'Топ островов' + detail-panel: 'Остров [name]' + value-panel: 'Ценность блоков' + buttons: + island: + empty: '[name]. место' + name: ' [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Текст, заменяющий [name], если у острова нет названия. + owners-island: 'Остров [player]' + # Текст для [owner] в описании. + owner: 'Владелец: [player]' + # Заголовок перед списком участников для [members] в описании. + members-title: 'Участники:' + # Список каждого участника под заголовком для [members] в описании. + member: ' - [player]' + # Имя неизвестного игрока. + unknown: неизвестно + # Секция для парсинга [place] + place: '[number]. место' + # Секция для парсинга [count] + count: 'Количество блоков: [number]' + # Секция для парсинга [lifetime] + lifetime: 'Количество за всё время: [number]' + # Кнопка, используемая в многостраничных GUI для возврата на предыдущую страницу. + previous: + name: 'Предыдущая страница' + description: 'Переключиться на страницу [number]' + # Кнопка, используемая в многостраничных GUI для перехода на следующую страницу. + next: + name: 'Следующая страница' + description: 'Переключиться на страницу [number]' + tips: + click-to-view: 'Нажмите для просмотра.' + click-to-previous: 'Нажмите для просмотра предыдущей страницы.' + click-to-next: 'Нажмите для просмотра следующей страницы.' + click-to-select: 'Нажмите для выбора.' + left-click-to-cycle-up: 'ЛКМ для перебора вверх.' + right-click-to-cycle-down: 'ПКМ для перебора вниз.' + left-click-to-change: 'ЛКМ для редактирования.' + right-click-to-clear: 'ПКМ для очистки.' + click-to-asc: 'Нажмите для сортировки по возрастанию.' + click-to-desc: 'Нажмите для сортировки по убыванию.' + click-to-warp: 'Нажмите для телепортации.' + click-to-visit: 'Нажмите для посещения.' + right-click-to-visit: 'ПКМ для посещения.' From c2bf4375a6bb8e322dd82a3571c14331af6813d5 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 25 Apr 2026 20:39:14 -0700 Subject: [PATCH 3/9] Fix top ten panel showing no head or stats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The @EventHandler on TopBlockManager.startMonitoring was declared private, so Bukkit silently skipped it. BentoBoxReadyEvent never fired the refresh task, the topTen list stayed empty, and the panel only ever rendered fallback (LIME_STAINED_GLASS_PANE) items — no player head, no stats. Renamed to public onBentoBoxReady. Also removed dead code surfaced while diffing against the Level addon: the large commented-out WARP/VISIT/VIEW block in TopLevelPanel (TopBlock has no warp/visit hooks), the unused ConversationUtils class, and the unused helpers in Utils (sendMessage, getNextValue, getPreviousValue, prettifyObject, prettifyDescription). Co-Authored-By: Claude Opus 4.7 --- .../bentobox/topblock/TopBlockManager.java | 2 +- .../topblock/panels/TopLevelPanel.java | 453 ++++-------------- .../topblock/util/ConversationUtils.java | 120 ----- .../world/bentobox/topblock/util/Utils.java | 197 +------- 4 files changed, 108 insertions(+), 664 deletions(-) delete mode 100644 src/main/java/world/bentobox/topblock/util/ConversationUtils.java diff --git a/src/main/java/world/bentobox/topblock/TopBlockManager.java b/src/main/java/world/bentobox/topblock/TopBlockManager.java index 1039a01..d951749 100644 --- a/src/main/java/world/bentobox/topblock/TopBlockManager.java +++ b/src/main/java/world/bentobox/topblock/TopBlockManager.java @@ -67,7 +67,7 @@ public TopBlockManager(TopBlock addon) { } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - private void startMonitoring(BentoBoxReadyEvent e) { + public void onBentoBoxReady(BentoBoxReadyEvent e) { // Load the top ten from AOneBlock every so often Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), () -> { // Update TopTen diff --git a/src/main/java/world/bentobox/topblock/panels/TopLevelPanel.java b/src/main/java/world/bentobox/topblock/panels/TopLevelPanel.java index dcaa634..1347f47 100644 --- a/src/main/java/world/bentobox/topblock/panels/TopLevelPanel.java +++ b/src/main/java/world/bentobox/topblock/panels/TopLevelPanel.java @@ -27,96 +27,84 @@ /** - * This panel opens top likes panel + * This panel opens the top ten panel for AOneBlock. */ -public class TopLevelPanel -{ - // --------------------------------------------------------------------- - // Section: Internal Constructor - // --------------------------------------------------------------------- +public class TopLevelPanel { + private static final String REFERENCE = "topblock.gui.buttons.island."; + private static final String PLAYER = "[player]"; - /** - * 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(TopBlock addon, User user, World world, String permissionPrefix) - { + private final TopBlock addon; + private final User user; + private final World world; + private final String iconPermission; + private final List topIslands; + + + private TopLevelPanel(TopBlock addon, User user, World world, String permissionPrefix) { this.addon = addon; this.user = user; this.world = world; - this.iconPermission = permissionPrefix + "topblock.icon"; - this.topIslands = this.addon.getManager().getTopTen(TopBlock.TEN); } /** - * Build method manages current panel opening. It uses BentoBox PanelAPI that is easy to use and users can get nice - * panels. + * Open the panel for a user. */ - public void build() - { - TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); + public static void openPanel(TopBlock addon, User user, World world, String permissionPrefix) { + new TopLevelPanel(addon, user, world, permissionPrefix).build(); + } + + private 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 - // --------------------------------------------------------------------- + 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); + } + TopTenData record = this.topIslands.size() < index ? null : this.topIslands.get(index - 1); - /** - * 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; + if (record == null) { + return this.createFallback(template.fallback(), index); } + return this.createIslandIcon(template, record, index); + } + + private PanelItem createFallback(ItemTemplateRecord template, long index) { + if (template == null) { + return null; + } PanelItemBuilder builder = new PanelItemBuilder(); - if (template.icon() != null) - { + if (template.icon() != null) { builder.icon(template.icon().clone()); } - if (template.title() != null) - { + if (template.title() != null) { builder.name(this.user.getTranslation(this.world, template.title(), TextVariables.NAME, String.valueOf(index))); - } - else - { + } else { builder.name(this.user.getTranslation(this.world, REFERENCE, TextVariables.NAME, String.valueOf(index))); } - if (template.description() != null) - { + if (template.description() != null) { builder.description(this.user.getTranslation(this.world, template.description(), TextVariables.NUMBER, String.valueOf(index))); } @@ -127,45 +115,10 @@ private PanelItem createFallback(ItemTemplateRecord template, long index) } - /** - * 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); + private PanelItem createIslandIcon(ItemTemplateRecord template, TopTenData record, int index) { + Island island = record.island(); - if (index < 1) - { - return this.createFallback(template.fallback(), index); - } - - TopTenData 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, TopTenData islandTopRecord, int index) - { - // Get player island. - Island island = islandTopRecord.island(); - - if (island == null) - { + if (island == null) { return this.createFallback(template.fallback(), index); } @@ -173,327 +126,109 @@ private PanelItem createIslandIcon(ItemTemplateRecord template, TopTenData islan this.populateIslandIcon(builder, template, island); this.populateIslandTitle(builder, template, island); - this.populateIslandDescription(builder, template, island, islandTopRecord, index); + this.populateIslandDescription(builder, template, island, record, index); builder.amount(index); - /* - // Get only possible actions, by removing all inactive ones. - List 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); - } - } - } - } - 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) - { - // Get Island Name + private void populateIslandTitle(PanelItemBuilder builder, ItemTemplateRecord template, Island island) { String nameText; - if (island.getName() == null || island.getName().isEmpty()) - { + 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 - { + 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()) - { + if (template.title() != null && !template.title().isBlank()) { builder.name(this.user.getTranslation(this.world, template.title(), TextVariables.NAME, nameText)); - } - else - { + } 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) - { + 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); + String permissionIcon = owner == null ? null + : Utils.getPermissionValue(owner, this.iconPermission, null); - Material material; + Material material = (permissionIcon != null && !permissionIcon.equals("*")) + ? Material.matchMaterial(permissionIcon) + : null; - if (permissionIcon != null && !permissionIcon.equals("*")) - { - material = Material.matchMaterial(permissionIcon); - } - else - { - material = null; - } - - if (material != null) - { - if (!material.equals(Material.PLAYER_HEAD)) - { + if (material != null) { + if (!material.equals(Material.PLAYER_HEAD)) { builder.icon(material); - } - else - { + } else { builder.icon(owner.getName()); } - } - else if (template.icon() != null) - { + } else if (template.icon() != null) { builder.icon(template.icon().clone()); - } - else if (owner != null) - { + } else if (owner != null) { builder.icon(owner.getName()); - } - else - { + } 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, - TopTenData islandTopRecord, - int index) - { - // Get Owner Name + private void populateIslandDescription(PanelItemBuilder builder, ItemTemplateRecord template, + Island island, TopTenData record, int index) { + String ownerText = this.user.getTranslation(REFERENCE + "owner", PLAYER, - island.getOwner() == null ? - this.user.getTranslation(REFERENCE + "unknown") : - this.addon.getPlayers().getName(island.getOwner())); + island.getOwner() == null + ? this.user.getTranslation(REFERENCE + "unknown") + : this.addon.getPlayers().getName(island.getOwner())); - // Get Members Text String memberText; - - if (island.getMemberSet().size() > 1) - { + if (island.getMemberSet().size() > 1) { StringBuilder memberBuilder = new StringBuilder( this.user.getTranslationOrNothing(REFERENCE + "members-title")); - - for (UUID uuid : island.getMemberSet()) - { + for (UUID uuid : island.getMemberSet()) { User u = User.getInstance(uuid); - - if (memberBuilder.length() > 0) - { + if (memberBuilder.length() > 0) { memberBuilder.append("\n"); } - - memberBuilder.append( - this.user.getTranslationOrNothing(REFERENCE + "member", - PLAYER, u.getName())); + memberBuilder.append(this.user.getTranslationOrNothing(REFERENCE + "member", + PLAYER, u.getName())); } - memberText = memberBuilder.toString(); - } - else - { + } else { memberText = ""; } String placeText = this.user.getTranslation(REFERENCE + "place", TextVariables.NUMBER, String.valueOf(index)); - String levelText = this.user.getTranslation(REFERENCE + "count", - TextVariables.NUMBER, this.addon.getManager().formatLevel((long)islandTopRecord.blockNumber())); + String countText = this.user.getTranslation(REFERENCE + "count", + TextVariables.NUMBER, this.addon.getManager().formatLevel((long) record.blockNumber())); String lifetimeText = this.user.getTranslation(REFERENCE + "lifetime", - TextVariables.NUMBER, this.addon.getManager().formatLevel(islandTopRecord.lifetime())); - - // 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, - "[count]", levelText, - "[lifetime]", lifetimeText, - "[place]", placeText). - replaceAll("(?m)^[ \\t]*\\r?\\n", ""). - replaceAll("(? topIslands; } diff --git a/src/main/java/world/bentobox/topblock/util/ConversationUtils.java b/src/main/java/world/bentobox/topblock/util/ConversationUtils.java deleted file mode 100644 index 26fba78..0000000 --- a/src/main/java/world/bentobox/topblock/util/ConversationUtils.java +++ /dev/null @@ -1,120 +0,0 @@ -// -// Created by BONNe -// Copyright - 2021 -// - - -package world.bentobox.topblock.util; - - -import org.bukkit.conversations.*; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import java.util.function.Consumer; - -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.api.user.User; - - -public class ConversationUtils -{ - private ConversationUtils() {} - // --------------------------------------------------------------------- - // Section: Conversation API implementation - // --------------------------------------------------------------------- - - - /** - * This method will close opened gui and writes question in chat. After players answers on question in chat, message - * will trigger consumer and gui will reopen. - * - * @param consumer Consumer that accepts player output text. - * @param question Message that will be displayed in chat when player triggers conversion. - * @param user User who is targeted with current confirmation. - */ - public static void createStringInput(Consumer consumer, - User user, - @NonNull String question, - @Nullable String successMessage) - { - // Text input message. - StringPrompt stringPrompt = new StringPrompt() - { - @Override - public @NonNull String getPromptText(@NonNull ConversationContext context) - { - user.closeInventory(); - return question; - } - - - @Override - public @NonNull Prompt acceptInput(@NonNull ConversationContext context, @Nullable String input) - { - consumer.accept(input); - return ConversationUtils.endMessagePrompt(successMessage); - } - }; - - new ConversationFactory(BentoBox.getInstance()). - withPrefix(context -> user.getTranslation("level.conversations.prefix")). - withFirstPrompt(stringPrompt). - // On cancel conversation will be closed. - withLocalEcho(false). - withTimeout(90). - withEscapeSequence(user.getTranslation("level.conversations.cancel-string")). - // Use null value in consumer to detect if user has abandoned conversation. - addConversationAbandonedListener(ConversationUtils.getAbandonListener(consumer, user)). - buildConversation(user.getPlayer()). - begin(); - } - - - /** - * This is just a simple end message prompt that displays requested message. - * - * @param message Message that will be displayed. - * @return MessagePrompt that displays given message and exists from conversation. - */ - private static MessagePrompt endMessagePrompt(@Nullable String message) - { - return new MessagePrompt() - { - @Override - public @NonNull String getPromptText(@NonNull ConversationContext context) - { - return message == null ? "" : message; - } - - - @Override - protected @Nullable Prompt getNextPrompt(@NonNull ConversationContext context) - { - return Prompt.END_OF_CONVERSATION; - } - }; - } - - - /** - * This method creates and returns abandon listener for every conversation. - * - * @param consumer Consumer which must return null value. - * @param user User who was using conversation. - * @return ConversationAbandonedListener instance. - */ - private static ConversationAbandonedListener getAbandonListener(Consumer consumer, User user) - { - return abandonedEvent -> - { - if (!abandonedEvent.gracefulExit()) - { - consumer.accept(null); - // send cancell message - abandonedEvent.getContext().getForWhom().sendRawMessage( - user.getTranslation("level.conversations.prefix") + - user.getTranslation("level.conversations.cancelled")); - } - }; - } -} diff --git a/src/main/java/world/bentobox/topblock/util/Utils.java b/src/main/java/world/bentobox/topblock/util/Utils.java index 7794f47..b8e7a2b 100644 --- a/src/main/java/world/bentobox/topblock/util/Utils.java +++ b/src/main/java/world/bentobox/topblock/util/Utils.java @@ -3,37 +3,19 @@ // Copyright - 2021 // - package world.bentobox.topblock.util; - -import org.bukkit.Material; -import org.bukkit.permissions.PermissionAttachmentInfo; import java.util.List; import java.util.stream.Collectors; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.hooks.LangUtilsHook; +import org.bukkit.permissions.PermissionAttachmentInfo; +import world.bentobox.bentobox.api.user.User; -public class Utils -{ - private static final String LEVEL_MATERIALS = "level.materials."; +public class Utils { private Utils() {} - /** - * This method sends a message to the user with appended "prefix" text before message. - * @param user User who receives message. - * @param translationText Translation text of the message. - * @param parameters Parameters for the translation text. - */ - public static void sendMessage(User user, String translationText, String... parameters) - { - user.sendMessage(user.getTranslation( "level.conversations.prefix") + - user.getTranslation( translationText, parameters)); - } - /** * This method gets string value of given permission prefix. If user does not have given permission or it have all @@ -44,34 +26,28 @@ public static void sendMessage(User user, String translationText, String... para * @param defaultValue Default value that will be returned if permission not found. * @return String value that follows permissionPrefix. */ - public static String getPermissionValue(User user, String permissionPrefix, String defaultValue) - { - if (user.isPlayer()) - { - if (permissionPrefix.endsWith(".")) - { + public static String getPermissionValue(User user, String permissionPrefix, String defaultValue) { + if (user.isPlayer()) { + if (permissionPrefix.endsWith(".")) { permissionPrefix = permissionPrefix.substring(0, permissionPrefix.length() - 1); } String permPrefix = permissionPrefix + "."; - List permissions = user.getEffectivePermissions().stream(). - map(PermissionAttachmentInfo::getPermission). - filter(permission -> permission.startsWith(permPrefix)). - collect(Collectors.toList()); + List permissions = user.getEffectivePermissions().stream() + .map(PermissionAttachmentInfo::getPermission) + .filter(permission -> permission.startsWith(permPrefix)) + .collect(Collectors.toList()); - for (String permission : permissions) - { - if (permission.contains(permPrefix + "*")) - { + 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) - { + if (parts.length > 1) { return parts[1]; } } @@ -79,151 +55,4 @@ public static String getPermissionValue(User user, String permissionPrefix, Stri return defaultValue; } - - - /** - * This method allows to get next value from array list after given value. - * - * @param values Array that should be searched for given value. - * @param currentValue Value which next element should be found. - * @param Instance of given object. - * @return Next value after currentValue in values array. - */ - public static T getNextValue(T[] values, T currentValue) - { - for (int i = 0; i < values.length; i++) - { - if (values[i].equals(currentValue)) - { - if (i + 1 == values.length) - { - return values[0]; - } - else - { - return values[i + 1]; - } - } - } - - return currentValue; - } - - - /** - * This method allows to get previous value from array list after given value. - * - * @param values Array that should be searched for given value. - * @param currentValue Value which previous element should be found. - * @param Instance of given object. - * @return Previous value before currentValue in values array. - */ - public static T getPreviousValue(T[] values, T currentValue) - { - for (int i = 0; i < values.length; i++) - { - if (values[i].equals(currentValue)) - { - if (i > 0) - { - return values[i - 1]; - } - else - { - return values[values.length - 1]; - } - } - } - - return currentValue; - } - - - /** - * Prettify Material object for user. - * @param object Object that must be pretty. - * @param user User who will see the object. - * @return Prettified string for Material. - */ - public static String prettifyObject(Material object, User user) - { - // Nothing to translate - if (object == null) - { - return ""; - } - - // Find addon structure with: - // [addon]: - // materials: - // [material]: - // name: [name] - String translation = user.getTranslationOrNothing(LEVEL_MATERIALS + object.name().toLowerCase() + ".name"); - - if (!translation.isEmpty()) - { - // We found our translation. - return translation; - } - - // Find addon structure with: - // [addon]: - // materials: - // [material]: [name] - - translation = user.getTranslationOrNothing(LEVEL_MATERIALS + object.name().toLowerCase()); - - if (!translation.isEmpty()) - { - // We found our translation. - return translation; - } - - // Find general structure with: - // materials: - // [material]: [name] - - translation = user.getTranslationOrNothing("materials." + object.name().toLowerCase()); - - if (!translation.isEmpty()) - { - // We found our translation. - return translation; - } - - // Use Lang Utils Hook to translate material - return LangUtilsHook.getMaterialName(object, user); - } - - - /** - * Prettify Material object description for user. - * @param object Object that must be pretty. - * @param user User who will see the object. - * @return Prettified description string for Material. - */ - public static String prettifyDescription(Material object, User user) - { - // Nothing to translate - if (object == null) - { - return ""; - } - - // Find addon structure with: - // [addon]: - // materials: - // [material]: - // description: [text] - String translation = user.getTranslationOrNothing(LEVEL_MATERIALS + object.name().toLowerCase() + ".description"); - - if (!translation.isEmpty()) - { - // We found our translation. - return translation; - } - - // No text to return. - return ""; - } } From 622eb32d213302f54026e5532949213deaee392b Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 25 Apr 2026 20:39:26 -0700 Subject: [PATCH 4/9] Modernise to Java 21, Paper 1.21.11, BentoBox 3.14.0 - Java 17 -> 21 - Spigot -> Paper 1.21.11-R0.1-SNAPSHOT - BentoBox 2.7.1-SNAPSHOT -> 3.14.0-SNAPSHOT - AOneBlock 1.12.3-SNAPSHOT -> 1.18.0 - Drop PowerMock; add JUnit 5 + MockBukkit + Mockito 5 deps - Maven plugins refreshed (compiler 3.15.0 with fork=true, surefire 3.5.2, shade 3.6.0, jar 3.4.2, javadoc 3.11.2, source 3.3.1, install/deploy 3.1.3, jacoco 0.8.12) - Add papermc, codemc-snapshots and jitpack repos; drop spigot repo - Bump build.version to 2.0.0 - TopBlockPladdon: cache the Addon instance instead of building a fresh one on every getAddon() call - Fix typo in plugin.yml api-version (`"1.21""` -> `"1.21"`) Co-Authored-By: Claude Opus 4.7 --- pom.xml | 158 ++++++++++-------- .../bentobox/topblock/TopBlockPladdon.java | 8 +- src/main/resources/plugin.yml | 2 +- 3 files changed, 96 insertions(+), 72 deletions(-) diff --git a/pom.xml b/pom.xml index 1e61b93..40b67a5 100644 --- a/pom.xml +++ b/pom.xml @@ -50,14 +50,16 @@ UTF-8 UTF-8 - 17 + 21 - 2.0.9 + 5.10.2 + 5.11.0 + v1.21-SNAPSHOT - 1.21.3-R0.1-SNAPSHOT - 2.7.1-SNAPSHOT + 1.21.11-R0.1-SNAPSHOT + 3.14.0-SNAPSHOT - 1.12.3-SNAPSHOT + 1.18.0 1.1.0 @@ -65,7 +67,7 @@ -LOCAL - 1.1.0 + 2.0.0 BentoBoxWorld_TopBlock bentobox-world https://sonarcloud.io @@ -75,7 +77,7 @@ - ci @@ -89,13 +91,13 @@ - - - - master @@ -122,8 +124,8 @@ - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots + papermc + https://repo.papermc.io/repository/maven-public/ bentoboxworld @@ -134,38 +136,23 @@ https://repo.codemc.org/repository/maven-public/ - codemc-public - https://repo.codemc.org/repository/maven-public/ + codemc + https://repo.codemc.org/repository/maven-snapshots/ + + + jitpack.io + https://jitpack.io - + - org.spigotmc - spigot-api - ${spigot.version} + io.papermc.paper + paper-api + ${paper.version} provided - - - org.mockito - mockito-core - 3.11.1 - test - - - org.powermock - powermock-module-junit4 - ${powermock.version} - test - - - org.powermock - powermock-api-mockito2 - ${powermock.version} - test - world.bentobox bentobox @@ -184,24 +171,57 @@ ${panelutils.version} - org.eclipse.jdt org.eclipse.jdt.annotation 2.2.600 + + + com.github.MockBukkit + MockBukkit + ${mock-bukkit.version} + test + + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + + org.mockito + mockito-core + ${mockito.version} + test + + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + - - ${project.name}-${revision}${build.number} @@ -221,52 +241,50 @@ org.apache.maven.plugins maven-clean-plugin - 3.1.0 + 3.4.1 org.apache.maven.plugins maven-resources-plugin - 3.1.0 + 3.3.1 org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + 3.15.0 ${java.version} + true org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M5 + 3.5.2 + + **/*Test.java + **/*Test?.java + **/*Test??.java + - ${argLine} --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED - --add-opens - java.base/java.util.stream=ALL-UNNAMED + --add-opens java.base/java.util.stream=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED - --add-opens - java.base/java.util.regex=ALL-UNNAMED - --add-opens - java.base/java.nio.channels.spi=ALL-UNNAMED + --add-opens java.base/java.util.regex=ALL-UNNAMED + --add-opens java.base/java.nio.channels.spi=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED - --add-opens - java.base/java.util.concurrent=ALL-UNNAMED + --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/sun.nio.fs=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/java.nio.file=ALL-UNNAMED - --add-opens - java.base/java.nio.charset=ALL-UNNAMED - --add-opens - java.base/java.lang.reflect=ALL-UNNAMED - --add-opens - java.logging/java.util.logging=ALL-UNNAMED + --add-opens java.base/java.nio.charset=ALL-UNNAMED + --add-opens java.base/java.lang.reflect=ALL-UNNAMED + --add-opens java.logging/java.util.logging=ALL-UNNAMED --add-opens java.base/java.lang.ref=ALL-UNNAMED --add-opens java.base/java.util.jar=ALL-UNNAMED --add-opens java.base/java.util.zip=ALL-UNNAMED @@ -276,17 +294,17 @@ org.apache.maven.plugins maven-jar-plugin - 3.1.0 + 3.4.2 org.apache.maven.plugins maven-javadoc-plugin - 3.0.1 + 3.11.2 false -Xdoclint:none ${java.home}/bin/javadoc - 16 + 21 @@ -300,7 +318,7 @@ org.apache.maven.plugins maven-source-plugin - 3.0.1 + 3.3.1 attach-sources @@ -313,17 +331,17 @@ org.apache.maven.plugins maven-install-plugin - 2.5.2 + 3.1.3 org.apache.maven.plugins maven-deploy-plugin - 2.8.2 + 3.1.3 org.apache.maven.plugins maven-shade-plugin - 3.3.1-SNAPSHOT + 3.6.0 true @@ -355,11 +373,11 @@ org.jacoco jacoco-maven-plugin - 0.8.10 + 0.8.12 true - **/*Names* diff --git a/src/main/java/world/bentobox/topblock/TopBlockPladdon.java b/src/main/java/world/bentobox/topblock/TopBlockPladdon.java index 013c4c2..8c16d90 100644 --- a/src/main/java/world/bentobox/topblock/TopBlockPladdon.java +++ b/src/main/java/world/bentobox/topblock/TopBlockPladdon.java @@ -10,8 +10,14 @@ * */ public class TopBlockPladdon extends Pladdon { + + private Addon addon; + @Override public Addon getAddon() { - return new TopBlock(); + if (addon == null) { + addon = new TopBlock(); + } + return addon; } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 73998fd..920e306 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: BentoBox-TopBlock main: world.bentobox.topblock.TopBlockPladdon version: ${project.version}${build.number} -api-version: "1.21"" +api-version: "1.21" authors: [tastybento] contributors: ["The BentoBoxWorld Community"] From 6ef414f75e8e14ea276dfd02eb8593bb14099924 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 25 Apr 2026 20:39:41 -0700 Subject: [PATCH 5/9] Migrate tests to JUnit 5 + MockBukkit Replaces the old JUnit 4 + PowerMock TopBlockManagerTest (which had been broken since the Java 17 migration with `Cannot redefine singleton Server` errors) with a JUnit 5 + MockBukkit suite following the CaveBlock pattern: - CommonTestSetup: shared base wiring up Bukkit, BentoBox singleton, IslandWorldManager / IslandsManager / PlayersManager / Locales / Placeholders / Notifier / Hooks mocks - WhiteBox: tiny reflective static-field setter for injecting the BentoBox singleton - TestWorldSettings: minimal WorldSettings impl - TopBlockTest: addon load / enable / disable / reload, with a synthesised in-memory addon.jar holding config.yml and panels/top_panel.yml - TopBlockManagerTest: data flow through getOneBlockData and formatLevel (covers shorthand k / M / G branches) - PlaceholderManagerTest: getMemberNames sort order and out-of-range rank handling - mocks/ServerMocks.java removed (replaced by MockBukkit) Total: 22 tests passing (TopBlock 6, TopBlockManager 13, PlaceholderManager 3). Also adds CLAUDE.md describing the addon's architecture and the gotchas that were time-consuming to rediscover (Pladdon split, private @EventHandler trap, locale resource filtering, surefire --add-opens requirement). Co-Authored-By: Claude Opus 4.7 --- CLAUDE.md | 60 +++ .../bentobox/topblock/CommonTestSetup.java | 221 +++++++++++ .../topblock/PlaceholderManagerTest.java | 96 +++++ .../bentobox/topblock/TestWorldSettings.java | 345 ++++++++++++++++++ .../topblock/TopBlockManagerTest.java | 266 +++++--------- .../world/bentobox/topblock/TopBlockTest.java | 151 ++++++++ .../world/bentobox/topblock/WhiteBox.java | 13 + .../bentobox/topblock/mocks/ServerMocks.java | 118 ------ 8 files changed, 982 insertions(+), 288 deletions(-) create mode 100644 CLAUDE.md create mode 100644 src/test/java/world/bentobox/topblock/CommonTestSetup.java create mode 100644 src/test/java/world/bentobox/topblock/PlaceholderManagerTest.java create mode 100644 src/test/java/world/bentobox/topblock/TestWorldSettings.java create mode 100644 src/test/java/world/bentobox/topblock/TopBlockTest.java create mode 100644 src/test/java/world/bentobox/topblock/WhiteBox.java delete mode 100644 src/test/java/world/bentobox/topblock/mocks/ServerMocks.java diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b9a34d7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,60 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project + +TopBlock is a BentoBox addon that produces a Top Ten ranking for the AOneBlock game mode based on how many magic blocks each island has mined. It is **not** standalone — it depends on the BentoBox plugin and the AOneBlock addon being present at runtime, and refuses to enable otherwise. + +## Build & Test + +Maven project, Java 21, Paper 1.21.11 API, BentoBox 3.14.0, AOneBlock 1.18.0. + +- Build (default goal is `clean package`): `mvn package` — produces a shaded jar in `target/` named `TopBlock-.jar`. The shade plugin bundles only `lv.id.bonne:panelutils`; everything else is `provided`. +- Run tests: `mvn test` +- Run a single test class: `mvn test -Dtest=TopBlockManagerTest` +- Run a single test method: `mvn test -Dtest=TopBlockManagerTest#testFormatLevelShorthandKilo` +- The Surefire config sets a long list of `--add-opens` JVM flags — required for Mockito + MockBukkit reflection on Java 21; do not remove them when tweaking the build. + +Version handling is driven by Maven properties: `build.version` is the human version (currently 1.1.0), `revision` resolves to `${build.version}-SNAPSHOT` locally and to `${build.version}` under the `master` profile (activated by `GIT_BRANCH=origin/master` on Jenkins). `build.number` is `-LOCAL` locally, `-b` on CI, empty on master. Don't hand-edit `` — bump `build.version`. + +## Runtime entry points (Pladdon pattern) + +There are **two** main classes and the distinction matters: + +- `TopBlockPladdon` (referenced by `plugin.yml`) is the Bukkit-facing `Pladdon`. Spigot loads this; its only job is `getAddon() → new TopBlock()`. +- `TopBlock` (referenced by `addon.yml`) is the BentoBox `Addon`. All real lifecycle (`onLoad`, `onEnable`, `onDisable`) lives here. + +`onEnable` looks up the AOneBlock addon via `getPlugin().getAddonsManager().getAddonByName("aoneblock")`; if missing or not a `GameModeAddon`, the addon disables itself. The `/ topblock` command is registered against AOneBlock's player command, not as a top-level command. + +## Data flow + +`TopBlockManager` is a `Listener` that reacts to `BentoBoxReadyEvent` (handler is `public void onBentoBoxReady` — Bukkit silently skips private @EventHandler methods, which is what broke the addon historically) to start a repeating Bukkit task. The task period is `settings.getRefreshTime() * 20L * 60` ticks (minutes → ticks). Each tick of the task: + +1. Calls `AOneBlock.getBlockListener().getAllIslands()` — this reads every island, so the refresh interval is intentionally coarse (default 5 min, min 1 min). +2. Builds a fresh `List` (record of island + blockNumber + lifetime + phaseName) — sorted at read time via `Comparator` on `lifetime` then `blockNumber`. +3. Updates `PlaceholderManager`'s cached snapshot. + +Placeholders are registered once via a `runTaskLater` 10-tick delay after the first ready event (so PAPI / BentoBox's `PlaceholdersManager` is up). Names follow `island__top_<1..10>` and are scoped to the AOneBlock `GameModeAddon`. The `TopBlock.TEN` constant is the source of truth for the list size. + +## Panel + +`TopLevelPanel` uses BentoBox's `TemplatedPanelBuilder`. The template file is shipped in `src/main/resources/panels/top_panel.yml` and copied to the data folder on load via `saveResource("panels/top_panel.yml", false)` — players' edits to the on-disk file persist across restarts. Localization keys live under `topblock.gui.buttons.island.*` in `src/main/resources/locales/en-US.yml`. The icon material can be overridden per-player via the `topblock.icon.` permission. + +The panel has no click actions (TopBlock doesn't bundle Warp/Visit hooks like Level does). The YAML still declares `warp`/`visit` actions with tooltips, but no click handler is registered — clicking does nothing. + +## Resource filtering + +`pom.xml` filters `src/main/resources` (so `${version}` etc. in `addon.yml` / `plugin.yml` get substituted) **except** `src/main/resources/locales`, which is copied verbatim to `./locales` to avoid Maven mangling YAML colons / placeholder syntax in translations. + +## Tests + +JUnit 5 + Mockito + MockBukkit. Test classes extend `CommonTestSetup` which: +- Mocks `Bukkit` statically and provides a real `MockBukkit.mock()` server (needed for Tag/Material initialisation). +- Injects the BentoBox singleton via `WhiteBox.setInternalState(BentoBox.class, "instance", plugin)`. +- Sets up the standard graph of mocks: `IslandWorldManager`, `IslandsManager`, `PlayersManager`, `LocalesManager`, `PlaceholdersManager`, `Notifier`, `HooksManager`, `BlueprintsManager`. +- Calls `User.setPlugin(plugin)` and pre-creates a `User` instance for `mockPlayer` (uuid `tastybento`). + +`TestWorldSettings` returns `"TopBlock"` for friendly name and `"topblock."` for permission prefix. The addon test (`TopBlockTest`) builds an in-memory `addon.jar` containing `config.yml` + `panels/top_panel.yml` because `Addon.saveResource` reads from a real JarFile. + +JaCoCo excludes `**/*Names*` to avoid synthetic-field issues on JavaBeans — keep that exclusion if adding similar classes. diff --git a/src/test/java/world/bentobox/topblock/CommonTestSetup.java b/src/test/java/world/bentobox/topblock/CommonTestSetup.java new file mode 100644 index 0000000..30c864b --- /dev/null +++ b/src/test/java/world/bentobox/topblock/CommonTestSetup.java @@ -0,0 +1,221 @@ +package world.bentobox.topblock; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.Optional; +import java.util.UUID; +import java.util.logging.Logger; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.entity.Player.Spigot; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.plugin.PluginManager; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.util.Vector; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockbukkit.mockbukkit.ServerMock; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; + +import com.google.common.collect.ImmutableSet; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.configuration.WorldSettings; +import world.bentobox.bentobox.api.user.Notifier; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.database.objects.Players; +import world.bentobox.bentobox.managers.BlueprintsManager; +import world.bentobox.bentobox.managers.FlagsManager; +import world.bentobox.bentobox.managers.HooksManager; +import world.bentobox.bentobox.managers.IslandWorldManager; +import world.bentobox.bentobox.managers.IslandsManager; +import world.bentobox.bentobox.managers.LocalesManager; +import world.bentobox.bentobox.managers.PlaceholdersManager; +import world.bentobox.bentobox.managers.PlayersManager; +import world.bentobox.bentobox.util.Util; + +/** + * Common test setup for TopBlock tests. Call super.setUp() in subclass @BeforeEach. + */ +public abstract class CommonTestSetup { + + protected UUID uuid = UUID.randomUUID(); + + @Mock + protected Player mockPlayer; + @Mock + protected PluginManager pim; + @Mock + protected ItemFactory itemFactory; + @Mock + protected Location location; + @Mock + protected World world; + @Mock + protected IslandWorldManager iwm; + @Mock + protected IslandsManager im; + @Mock + protected Island island; + @Mock + protected BentoBox plugin; + @Mock + protected PlayerInventory inv; + @Mock + protected Notifier notifier; + @Mock + protected FlagsManager fm; + @Mock + protected Spigot spigot; + @Mock + protected HooksManager hooksManager; + @Mock + protected BlueprintsManager bm; + @Mock + protected BukkitScheduler sch; + @Mock + protected LocalesManager lm; + @Mock + protected PlaceholdersManager phm; + + protected ServerMock server; + protected MockedStatic mockedBukkit; + protected MockedStatic mockedUtil; + protected AutoCloseable closeable; + + @BeforeEach + @SuppressWarnings("java:S1130") + public void setUp() throws Exception { + closeable = MockitoAnnotations.openMocks(this); + server = MockBukkit.mock(); + + // Inject BentoBox singleton + WhiteBox.setInternalState(BentoBox.class, "instance", plugin); + + // Force Tag static fields to initialise under the real server + @SuppressWarnings("unused") + var unusedTagRef = org.bukkit.Tag.LEAVES; + + // Static Bukkit mock + mockedBukkit = Mockito.mockStatic(Bukkit.class, Mockito.RETURNS_DEEP_STUBS); + mockedBukkit.when(Bukkit::getMinecraftVersion).thenReturn("1.21.10"); + mockedBukkit.when(Bukkit::getBukkitVersion).thenReturn(""); + mockedBukkit.when(Bukkit::getPluginManager).thenReturn(pim); + mockedBukkit.when(Bukkit::getItemFactory).thenReturn(itemFactory); + mockedBukkit.when(Bukkit::getServer).thenReturn(server); + mockedBukkit.when(Bukkit::getScheduler).thenReturn(sch); + + // Location + when(location.getWorld()).thenReturn(world); + when(location.getBlockX()).thenReturn(0); + when(location.getBlockY()).thenReturn(0); + when(location.getBlockZ()).thenReturn(0); + when(location.toVector()).thenReturn(new Vector(0, 0, 0)); + when(location.clone()).thenReturn(location); + + // PlayersManager + PlayersManager pm = mock(PlayersManager.class); + when(plugin.getPlayers()).thenReturn(pm); + Players players = mock(Players.class); + when(players.getMetaData()).thenReturn(Optional.empty()); + when(pm.getPlayer(any(UUID.class))).thenReturn(players); + + // Player + when(mockPlayer.getUniqueId()).thenReturn(uuid); + when(mockPlayer.getLocation()).thenReturn(location); + when(mockPlayer.getWorld()).thenReturn(world); + when(mockPlayer.getName()).thenReturn("tastybento"); + when(mockPlayer.getInventory()).thenReturn(inv); + when(mockPlayer.spigot()).thenReturn(spigot); + when(mockPlayer.getType()).thenReturn(EntityType.PLAYER); + + User.setPlugin(plugin); + User.clearUsers(); + User.getInstance(mockPlayer); + + // IWM + when(plugin.getIWM()).thenReturn(iwm); + when(iwm.inWorld(any(Location.class))).thenReturn(true); + when(iwm.inWorld(any(World.class))).thenReturn(true); + when(iwm.getFriendlyName(any())).thenReturn("TopBlock"); + when(iwm.getAddon(any())).thenReturn(Optional.empty()); + + // WorldSettings + WorldSettings worldSet = new TestWorldSettings(); + when(iwm.getWorldSettings(any())).thenReturn(worldSet); + + // IslandsManager + when(plugin.getIslands()).thenReturn(im); + when(im.getProtectedIslandAt(any())).thenReturn(Optional.of(island)); + when(island.isAllowed(any())).thenReturn(false); + when(island.isAllowed(any(User.class), any())).thenReturn(false); + when(island.getOwner()).thenReturn(uuid); + when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid)); + + // Locales & Placeholders + when(lm.get(any(), any())).thenAnswer((Answer) invocation -> invocation.getArgument(1, String.class)); + when(plugin.getPlaceholdersManager()).thenReturn(phm); + when(phm.replacePlaceholders(any(), any())).thenAnswer((Answer) invocation -> invocation.getArgument(1, String.class)); + when(plugin.getLocalesManager()).thenReturn(lm); + + // Notifier + when(plugin.getNotifier()).thenReturn(notifier); + + // Logger — Addon.getLogger() delegates to plugin.getLogger() + when(plugin.getLogger()).thenReturn(Logger.getLogger("TopBlock-test")); + + // BentoBox settings (fake players feature) + world.bentobox.bentobox.Settings settings = new world.bentobox.bentobox.Settings(); + when(plugin.getSettings()).thenReturn(settings); + + // Util static mock + mockedUtil = Mockito.mockStatic(Util.class, Mockito.CALLS_REAL_METHODS); + mockedUtil.when(() -> Util.getWorld(any())).thenReturn(mock(World.class)); + Util.setPlugin(plugin); + mockedUtil.when(() -> Util.findFirstMatchingEnum(any(), any())).thenCallRealMethod(); + + // Hooks + when(hooksManager.getHook(anyString())).thenReturn(Optional.empty()); + when(plugin.getHooks()).thenReturn(hooksManager); + + // BlueprintsManager + when(plugin.getBlueprintsManager()).thenReturn(bm); + } + + @AfterEach + public void tearDown() throws Exception { + mockedBukkit.closeOnDemand(); + mockedUtil.closeOnDemand(); + closeable.close(); + MockBukkit.unmock(); + User.clearUsers(); + Mockito.framework().clearInlineMocks(); + deleteAll(new File("database")); + deleteAll(new File("database_backup")); + } + + protected static void deleteAll(File file) throws IOException { + if (file.exists()) { + Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } + } +} diff --git a/src/test/java/world/bentobox/topblock/PlaceholderManagerTest.java b/src/test/java/world/bentobox/topblock/PlaceholderManagerTest.java new file mode 100644 index 0000000..8cd3249 --- /dev/null +++ b/src/test/java/world/bentobox/topblock/PlaceholderManagerTest.java @@ -0,0 +1,96 @@ +package world.bentobox.topblock; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import com.google.common.collect.ImmutableSet; + +import world.bentobox.aoneblock.AOneBlock; +import world.bentobox.aoneblock.dataobjects.OneBlockIslands; +import world.bentobox.aoneblock.listeners.BlockListener; +import world.bentobox.bentobox.managers.PlayersManager; +import world.bentobox.bentobox.managers.RanksManager; +import world.bentobox.topblock.config.ConfigSettings; + +class PlaceholderManagerTest extends CommonTestSetup { + + @Mock + private TopBlock addon; + @Mock + private AOneBlock aob; + @Mock + private BlockListener bl; + @Mock + private PlayersManager playersMgr; + + private TopBlockManager tbm; + private PlaceholderManager phMgr; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + + ConfigSettings settings = new ConfigSettings(); + when(addon.getPlugin()).thenReturn(plugin); + when(addon.getSettings()).thenReturn(settings); + when(addon.getaOneBlock()).thenReturn(aob); + when(addon.getIslands()).thenReturn(im); + when(addon.getPlayers()).thenReturn(playersMgr); + when(aob.getBlockListener()).thenReturn(bl); + when(im.getIslandById(anyString())).thenReturn(Optional.of(island)); + + // Single island in top ten + OneBlockIslands ob = new OneBlockIslands(UUID.randomUUID().toString()); + ob.setBlockNumber(80); + ob.setLifetime(250); + ob.setPhaseName("Underground"); + when(bl.getAllIslands()).thenReturn(List.of(ob)); + + tbm = new TopBlockManager(addon); + when(addon.getManager()).thenReturn(tbm); + tbm.getOneBlockData(); + + phMgr = new PlaceholderManager(addon); + phMgr.updateTopTen(); + } + + @Test + void testGetMemberNamesEmptyForSingleMemberIsland() { + when(island.getMembers()).thenReturn(java.util.Collections.emptyMap()); + + assertEquals("", phMgr.getMemberNames(1)); + } + + @Test + void testGetMemberNamesPastEndReturnsEmpty() { + // Rank 5 with only 1 island in the list → empty + assertEquals("", phMgr.getMemberNames(5)); + } + + @Test + void testGetMemberNamesJoinsMembers() { + UUID a = UUID.randomUUID(); + UUID b = UUID.randomUUID(); + when(island.getMembers()).thenReturn(java.util.Map.of( + a, RanksManager.MEMBER_RANK, + b, RanksManager.SUB_OWNER_RANK)); + when(island.getMemberSet()).thenReturn(ImmutableSet.of(a, b)); + when(playersMgr.getName(a)).thenReturn("Alice"); + when(playersMgr.getName(b)).thenReturn("Bob"); + + String names = phMgr.getMemberNames(1); + // SUB_OWNER_RANK > MEMBER_RANK, so Bob comes first + assertEquals("Bob,Alice", names); + } +} diff --git a/src/test/java/world/bentobox/topblock/TestWorldSettings.java b/src/test/java/world/bentobox/topblock/TestWorldSettings.java new file mode 100644 index 0000000..40c862a --- /dev/null +++ b/src/test/java/world/bentobox/topblock/TestWorldSettings.java @@ -0,0 +1,345 @@ +package world.bentobox.topblock; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bukkit.Difficulty; +import org.bukkit.GameMode; +import org.bukkit.entity.EntityType; +import org.eclipse.jdt.annotation.NonNull; + +import world.bentobox.bentobox.api.configuration.WorldSettings; +import world.bentobox.bentobox.api.flags.Flag; + +/** + * Minimal WorldSettings implementation for use in tests. + */ +public class TestWorldSettings implements WorldSettings { + + private long epoch; + + @Override + public GameMode getDefaultGameMode() { + return GameMode.SURVIVAL; + } + + @SuppressWarnings("removal") + @Override + public Map getDefaultIslandFlags() { + return Collections.emptyMap(); + } + + @SuppressWarnings("removal") + @Override + public Map getDefaultIslandSettings() { + return Collections.emptyMap(); + } + + @Override + public Difficulty getDifficulty() { + return Difficulty.NORMAL; + } + + @Override + public void setDifficulty(Difficulty difficulty) { + // unused + } + + @Override + public String getFriendlyName() { + return "TopBlock"; + } + + @Override + public int getIslandDistance() { + return 0; + } + + @Override + public int getIslandHeight() { + return 0; + } + + @Override + public int getIslandProtectionRange() { + return 0; + } + + @Override + public int getIslandStartX() { + return 0; + } + + @Override + public int getIslandStartZ() { + return 0; + } + + @Override + public int getIslandXOffset() { + return 0; + } + + @Override + public int getIslandZOffset() { + return 0; + } + + @Override + public List getIvSettings() { + return Collections.emptyList(); + } + + @Override + public int getMaxHomes() { + return 3; + } + + @Override + public int getMaxIslands() { + return 0; + } + + @Override + public int getMaxTeamSize() { + return 4; + } + + @Override + public int getNetherSpawnRadius() { + return 10; + } + + @Override + public String getPermissionPrefix() { + return "topblock."; + } + + @Override + public Set getRemoveMobsWhitelist() { + return Collections.emptySet(); + } + + @Override + public int getSeaHeight() { + return 0; + } + + @Override + public List getHiddenFlags() { + return Collections.emptyList(); + } + + @Override + public List getVisitorBannedCommands() { + return Collections.emptyList(); + } + + @Override + public Map getWorldFlags() { + return new HashMap<>(); + } + + @Override + public String getWorldName() { + return "topblock-world"; + } + + @Override + public boolean isDragonSpawn() { + return false; + } + + @Override + public boolean isEndGenerate() { + return true; + } + + @Override + public boolean isEndIslands() { + return true; + } + + @Override + public boolean isNetherGenerate() { + return true; + } + + @Override + public boolean isNetherIslands() { + return true; + } + + @Override + public boolean isOnJoinResetEnderChest() { + return false; + } + + @Override + public boolean isOnJoinResetInventory() { + return false; + } + + @Override + public boolean isOnJoinResetMoney() { + return false; + } + + @Override + public boolean isOnJoinResetHealth() { + return false; + } + + @Override + public boolean isOnJoinResetHunger() { + return false; + } + + @Override + public boolean isOnJoinResetXP() { + return false; + } + + @Override + public @NonNull List getOnJoinCommands() { + return Collections.emptyList(); + } + + @Override + public boolean isOnLeaveResetEnderChest() { + return false; + } + + @Override + public boolean isOnLeaveResetInventory() { + return false; + } + + @Override + public boolean isOnLeaveResetMoney() { + return false; + } + + @Override + public boolean isOnLeaveResetHealth() { + return false; + } + + @Override + public boolean isOnLeaveResetHunger() { + return false; + } + + @Override + public boolean isOnLeaveResetXP() { + return false; + } + + @Override + public @NonNull List getOnLeaveCommands() { + return Collections.emptyList(); + } + + @Override + public boolean isUseOwnGenerator() { + return false; + } + + @Override + public boolean isWaterUnsafe() { + return false; + } + + @Override + public List getGeoLimitSettings() { + return Collections.emptyList(); + } + + @Override + public int getResetLimit() { + return 0; + } + + @Override + public long getResetEpoch() { + return epoch; + } + + @Override + public void setResetEpoch(long timestamp) { + this.epoch = timestamp; + } + + @Override + public boolean isTeamJoinDeathReset() { + return false; + } + + @Override + public int getDeathsMax() { + return 0; + } + + @Override + public boolean isDeathsCounted() { + return true; + } + + @Override + public boolean isDeathsResetOnNewIsland() { + return true; + } + + @Override + public boolean isAllowSetHomeInNether() { + return false; + } + + @Override + public boolean isAllowSetHomeInTheEnd() { + return false; + } + + @Override + public boolean isRequireConfirmationToSetHomeInNether() { + return false; + } + + @Override + public boolean isRequireConfirmationToSetHomeInTheEnd() { + return false; + } + + @Override + public int getBanLimit() { + return 10; + } + + @Override + public boolean isLeaversLoseReset() { + return true; + } + + @Override + public boolean isKickedKeepInventory() { + return true; + } + + @Override + public boolean isCreateIslandOnFirstLoginEnabled() { + return false; + } + + @Override + public int getCreateIslandOnFirstLoginDelay() { + return 0; + } + + @Override + public boolean isCreateIslandOnFirstLoginAbortOnLogout() { + return false; + } +} diff --git a/src/test/java/world/bentobox/topblock/TopBlockManagerTest.java b/src/test/java/world/bentobox/topblock/TopBlockManagerTest.java index 708ba74..9f6a2ce 100644 --- a/src/test/java/world/bentobox/topblock/TopBlockManagerTest.java +++ b/src/test/java/world/bentobox/topblock/TopBlockManagerTest.java @@ -1,243 +1,169 @@ package world.bentobox.topblock; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.UUID; -import org.bukkit.Bukkit; -import org.bukkit.Server; -import org.eclipse.jdt.annotation.NonNull; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.mockito.Mockito; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; import world.bentobox.aoneblock.AOneBlock; import world.bentobox.aoneblock.dataobjects.OneBlockIslands; import world.bentobox.aoneblock.listeners.BlockListener; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.managers.IslandsManager; import world.bentobox.topblock.TopBlockManager.TopTenData; import world.bentobox.topblock.config.ConfigSettings; -import world.bentobox.topblock.mocks.ServerMocks; -/** - * @author tastybento - * - */ -@RunWith(PowerMockRunner.class) -@PrepareForTest(Bukkit.class) -public class TopBlockManagerTest { +class TopBlockManagerTest extends CommonTestSetup { @Mock private TopBlock addon; @Mock - private Island island; - - private TopBlockManager tbm; - @Mock private AOneBlock aob; - @Mock - private IslandsManager im; + private BlockListener bl; + private TopBlockManager tbm; + private ConfigSettings settings; - /** - * @throws java.lang.Exception - */ - @Before + @Override + @BeforeEach public void setUp() throws Exception { - Server server = ServerMocks.newServer(); - - PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); - when(Bukkit.getServer()).thenReturn(server); - - List list = new ArrayList<>(); - OneBlockIslands i = new OneBlockIslands(UUID.randomUUID().toString()); - i.setLifetime(100); - i.setBlockNumber(100); - i.setPhaseName("phasy"); - list.add(i); + super.setUp(); - // Island manager + settings = new ConfigSettings(); + when(addon.getPlugin()).thenReturn(plugin); + when(addon.getSettings()).thenReturn(settings); + when(addon.getaOneBlock()).thenReturn(aob); when(addon.getIslands()).thenReturn(im); - when(im.getIslandById(anyString())).thenReturn(Optional.of(island)); - // AOneBlock - BlockListener bl = mock(BlockListener.class); // This class uses static initializations so if it is mocked as a field, it will spark an issue - when(bl.getAllIslands()).thenReturn(list); when(aob.getBlockListener()).thenReturn(bl); - when(addon.getaOneBlock()).thenReturn(aob); + when(im.getIslandById(anyString())).thenReturn(Optional.of(island)); + tbm = new TopBlockManager(addon); } - @After - public void tearDown() { - ServerMocks.unsetBukkitServer(); - User.clearUsers(); - Mockito.framework().clearInlineMocks(); + private static OneBlockIslands ob(int blockNumber, long lifetime, String phase) { + OneBlockIslands i = new OneBlockIslands(UUID.randomUUID().toString()); + i.setBlockNumber(blockNumber); + i.setLifetime(lifetime); + i.setPhaseName(phase); + return i; } - /** - * Test method for {@link world.bentobox.topblock.TopBlockManager#TopBlockManager(world.bentobox.topblock.TopBlock)}. - */ @Test - public void testTopBlockManager() { - assertNotNull(tbm); + void testGetTopTenEmptyByDefault() { + assertTrue(tbm.getTopTen(10).isEmpty()); } - /** - * Test method for {@link world.bentobox.topblock.TopBlockManager.TopTenData}. - */ @Test - public void testTopTenDataSame() { - TopTenData ttd = new TopTenData(island, 0, 0, "phase one"); - TopTenData ttd2 = new TopTenData(island, 0, 0, "phase one"); - assertEquals(ttd, ttd2); + void testGetOneBlockDataPopulatesTopTen() { + when(bl.getAllIslands()).thenReturn(List.of( + ob(50, 100, "Plains"), + ob(80, 250, "Underground"))); + + tbm.getOneBlockData(); + + List top = tbm.getTopTen(10); + assertEquals(2, top.size()); + // Sorted descending by lifetime + assertEquals(250L, top.get(0).lifetime()); + assertEquals(100L, top.get(1).lifetime()); } - /** - * Test method for {@link world.bentobox.topblock.TopBlockManager.TopTenData}. - */ @Test - public void testTopTenDataBlockDifferent() { - TopTenData ttd = new TopTenData(island, 1000, 0, "phase one"); - TopTenData ttd2 = new TopTenData(island, 0, 0, "phase one"); - assertNotEquals(ttd, ttd2); + void testGetOneBlockDataFiltersZeroLifetime() { + when(bl.getAllIslands()).thenReturn(List.of( + ob(0, 0, "Plains"), + ob(80, 250, "Underground"))); + + tbm.getOneBlockData(); + + List top = tbm.getTopTen(10); + assertEquals(1, top.size()); + assertEquals(250L, top.get(0).lifetime()); } - /** - * Test method for {@link world.bentobox.topblock.TopBlockManager.TopTenData}. - */ @Test - public void testTopTenDataLifetimeDifferent() { - TopTenData ttd = new TopTenData(island, 0, 0, "phase one"); - TopTenData ttd2 = new TopTenData(island, 0, 10000, "phase one"); - assertNotEquals(ttd, ttd2); + void testGetOneBlockDataSkipsIslandsWithoutBentoBoxIsland() { + when(im.getIslandById(anyString())).thenReturn(Optional.empty()); + when(bl.getAllIslands()).thenReturn(List.of(ob(80, 250, "Underground"))); + + tbm.getOneBlockData(); + + assertTrue(tbm.getTopTen(10).isEmpty()); } - /** - * Test method for {@link world.bentobox.topblock.TopBlockManager.TopTenData}. - */ @Test - public void testTopTenDataPhaseDifferent() { - TopTenData ttd = new TopTenData(island, 0, 0, "phase one"); - TopTenData ttd2 = new TopTenData(island, 0, 0, "phase two"); - assertNotEquals(ttd, ttd2); + void testGetOneBlockDataReplacesPreviousResults() { + when(bl.getAllIslands()).thenReturn(List.of(ob(80, 250, "Underground"))); + tbm.getOneBlockData(); + assertEquals(1, tbm.getTopTen(10).size()); + + when(bl.getAllIslands()).thenReturn(List.of()); + tbm.getOneBlockData(); + assertTrue(tbm.getTopTen(10).isEmpty()); } - /** - * Test method for {@link world.bentobox.topblock.TopBlockManager.TopTenData}. - */ @Test - public void testTopTenDataGreater() { - TopTenData ttd = new TopTenData(island, 10000, 0, "phase fifty"); - TopTenData ttd2 = new TopTenData(island, 0, 0, "phase two"); - List list = new ArrayList<>(); - list.add(ttd); - list.add(ttd2); - list = list.stream().sorted(Collections.reverseOrder()).toList(); - assertEquals(ttd, list.get(0)); - assertEquals(ttd2, list.get(1)); + void testGetTopTenLimitsSize() { + when(bl.getAllIslands()).thenReturn(List.of( + ob(10, 10, "a"), + ob(20, 20, "b"), + ob(30, 30, "c"))); + tbm.getOneBlockData(); + + assertEquals(2, tbm.getTopTen(2).size()); } - /** - * Test method for {@link world.bentobox.topblock.TopBlockManager.TopTenData}. - */ @Test - public void testTopTenDataLess() { - TopTenData ttd = new TopTenData(island, 0, 0, "phase one"); - TopTenData ttd2 = new TopTenData(island, 10000, 0, "phase fifty"); - List list = new ArrayList<>(); - list.add(ttd); - list.add(ttd2); - list = list.stream().sorted(Collections.reverseOrder()).toList(); - assertEquals(ttd2, list.get(0)); - assertEquals(ttd, list.get(1)); + void testFormatLevelNullReturnsEmpty() { + assertEquals("", tbm.formatLevel(null)); } - /** - * Test method for {@link world.bentobox.topblock.TopBlockManager.TopTenData}. - */ @Test - public void testTopTenDataGreaterLifetime() { - TopTenData ttd = new TopTenData(island, 100, 10100, "phase fifty"); - TopTenData ttd2 = new TopTenData(island, 1000, 0, "phase two"); - List list = new ArrayList<>(); - list.add(ttd); - list.add(ttd2); - list = list.stream().sorted(Collections.reverseOrder()).toList(); - assertEquals(ttd, list.get(0)); - assertEquals(ttd2, list.get(1)); + void testFormatLevelNoShorthandReturnsRawString() { + settings.setShorthand(false); + assertEquals("104556", tbm.formatLevel(104556L)); } - /** - * Test method for {@link world.bentobox.topblock.TopBlockManager.TopTenData}. - */ @Test - public void testTopTenDataGreaterLifetime2() { - TopTenData ttd = new TopTenData(island, 100, 10100, "phase fifty"); - TopTenData ttd2 = new TopTenData(island, 100, 0, "phase two"); - List list = new ArrayList<>(); - list.add(ttd2); - list.add(ttd); - list = list.stream().sorted(Collections.reverseOrder()).toList(); - assertEquals(ttd, list.get(0)); - assertEquals(ttd2, list.get(1)); + void testFormatLevelShorthandUnderThousandUnchanged() { + settings.setShorthand(true); + assertEquals("999", tbm.formatLevel(999L)); } - - /** - * Test method for {@link world.bentobox.topblock.TopBlockManager#getOneBlockData()}. - */ @Test - public void testGetOneBlockData() { - this.tbm.getOneBlockData(); - @NonNull - List list = tbm.getTopTen(10); - TopTenData t = list.get(0); - assertEquals(100, t.lifetime()); - assertEquals(100, t.blockNumber()); - assertEquals("phasy", t.phaseName()); - + void testFormatLevelShorthandKilo() { + settings.setShorthand(true); + assertEquals("10.5k", tbm.formatLevel(10500L)); } - /** - * Test method for {@link world.bentobox.topblock.TopBlockManager#formatLevel(java.lang.Long)}. - */ @Test - public void testFormatLevel() { - ConfigSettings settings = new ConfigSettings(); + void testFormatLevelShorthandMega() { settings.setShorthand(true); - when(addon.getSettings()).thenReturn(settings); - assertEquals("12.3G", tbm.formatLevel(12345678349L)); - settings.setShorthand(false); - when(addon.getSettings()).thenReturn(settings); - assertEquals("12345678349", tbm.formatLevel(12345678349L)); + assertEquals("1.5M", tbm.formatLevel(1_527_314L)); } - /** - * Test method for {@link world.bentobox.topblock.TopBlockManager#getTopTen(int)}. - */ @Test - public void testGetTopTen() { - List list = tbm.getTopTen(10); - assertTrue(list.isEmpty()); + void testFormatLevelShorthandGiga() { + settings.setShorthand(true); + assertEquals("3.9G", tbm.formatLevel(3_874_130_021L)); } + @Test + void testTopTenDataRecordFields() { + TopTenData d = new TopTenData(island, 42, 1234L, "phasy"); + assertNotNull(d); + assertEquals(42, d.blockNumber()); + assertEquals(1234L, d.lifetime()); + assertEquals("phasy", d.phaseName()); + assertEquals(island, d.island()); + } } diff --git a/src/test/java/world/bentobox/topblock/TopBlockTest.java b/src/test/java/world/bentobox/topblock/TopBlockTest.java new file mode 100644 index 0000000..4f2c33e --- /dev/null +++ b/src/test/java/world/bentobox/topblock/TopBlockTest.java @@ -0,0 +1,151 @@ +package world.bentobox.topblock; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import world.bentobox.bentobox.api.addons.Addon.State; +import world.bentobox.bentobox.api.addons.AddonDescription; +import world.bentobox.bentobox.database.AbstractDatabaseHandler; +import world.bentobox.bentobox.database.DatabaseSetup; +import world.bentobox.bentobox.managers.AddonsManager; +import world.bentobox.bentobox.managers.CommandsManager; +import world.bentobox.topblock.config.ConfigSettings; + +class TopBlockTest extends CommonTestSetup { + + private static final String CONFIG_YML = + """ + refresh-time: 5 + shorthand: false + """; + + private static final String TOP_PANEL_YML = "top_panel:\n type: INVENTORY\n"; + + @Mock + private AddonsManager am; + + private TopBlock addon; + private MockedStatic mockDb; + + @SuppressWarnings("unchecked") + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + + // Database mock + AbstractDatabaseHandler h = mock(AbstractDatabaseHandler.class); + mockDb = Mockito.mockStatic(DatabaseSetup.class); + DatabaseSetup dbSetup = mock(DatabaseSetup.class); + mockDb.when(DatabaseSetup::getDatabase).thenReturn(dbSetup); + when(dbSetup.getHandler(any())).thenReturn(h); + when(h.saveObject(any())).thenReturn(CompletableFuture.completedFuture(true)); + + // CommandsManager + CommandsManager cm = mock(CommandsManager.class); + when(plugin.getCommandsManager()).thenReturn(cm); + + // AddonsManager — no aoneblock present + when(plugin.getAddonsManager()).thenReturn(am); + when(am.getGameModeAddons()).thenReturn(Collections.emptyList()); + when(am.getAddonByName("aoneblock")).thenReturn(Optional.empty()); + + // FlagsManager + when(plugin.getFlagsManager()).thenReturn(fm); + when(fm.getFlags()).thenReturn(Collections.emptyList()); + + // Build a JAR with config.yml + the panel resource that onLoad copies + addon = new TopBlock(); + File jFile = new File("addon.jar"); + try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(jFile))) { + addJarEntry(jos, "config.yml", CONFIG_YML); + addJarEntry(jos, "panels/top_panel.yml", TOP_PANEL_YML); + } + addon.setDataFolder(new File("addons/TopBlock")); + addon.setFile(jFile); + addon.setDescription(new AddonDescription.Builder("bentobox", "TopBlock", "1.0.0") + .description("test").authors("tastybento").build()); + } + + @Override + @AfterEach + public void tearDown() throws Exception { + if (mockDb != null) { + mockDb.closeOnDemand(); + } + super.tearDown(); + new File("addon.jar").delete(); + deleteAll(new File("addons")); + } + + private static void addJarEntry(JarOutputStream jos, String name, String content) throws Exception { + JarEntry entry = new JarEntry(name); + jos.putNextEntry(entry); + jos.write(content.getBytes(StandardCharsets.UTF_8)); + jos.closeEntry(); + } + + @Test + void testGetSettingsNullBeforeLoad() { + assertNull(addon.getSettings()); + } + + @Test + void testOnLoad() { + addon.onLoad(); + assertNotNull(addon.getSettings()); + } + + @Test + void testOnLoadSettingsDefaults() { + addon.onLoad(); + ConfigSettings s = addon.getSettings(); + assertNotNull(s); + // refresh-time clamps to >=1; default in YAML is 5 + org.junit.jupiter.api.Assertions.assertEquals(5, s.getRefreshTime()); + org.junit.jupiter.api.Assertions.assertFalse(s.isShorthand()); + } + + @Test + void testOnEnableWithoutAOneBlockDisables() { + addon.onLoad(); + addon.onEnable(); + // AOneBlock not present → addon disables itself + assertTrue(addon.getState() == State.DISABLED); + // Manager is still constructed before the AOneBlock lookup + assertNotNull(addon.getManager()); + } + + @Test + void testOnDisable() { + addon.onDisable(); + assertNotNull(addon); + } + + @Test + void testOnReload() { + addon.onLoad(); + addon.onReload(); + assertNotNull(addon.getSettings()); + } +} diff --git a/src/test/java/world/bentobox/topblock/WhiteBox.java b/src/test/java/world/bentobox/topblock/WhiteBox.java new file mode 100644 index 0000000..94eb230 --- /dev/null +++ b/src/test/java/world/bentobox/topblock/WhiteBox.java @@ -0,0 +1,13 @@ +package world.bentobox.topblock; + +public class WhiteBox { + public static void setInternalState(Class targetClass, String fieldName, Object value) { + try { + java.lang.reflect.Field field = targetClass.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(null, value); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("Failed to set static field '" + fieldName + "' on class " + targetClass.getName(), e); + } + } +} diff --git a/src/test/java/world/bentobox/topblock/mocks/ServerMocks.java b/src/test/java/world/bentobox/topblock/mocks/ServerMocks.java deleted file mode 100644 index 06ad3db..0000000 --- a/src/test/java/world/bentobox/topblock/mocks/ServerMocks.java +++ /dev/null @@ -1,118 +0,0 @@ -package world.bentobox.topblock.mocks; - -import static org.mockito.ArgumentMatchers.notNull; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.logging.Logger; - -import org.bukkit.Bukkit; -import org.bukkit.Keyed; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; -import org.bukkit.Server; -import org.bukkit.Tag; -import org.bukkit.UnsafeValues; -import org.eclipse.jdt.annotation.NonNull; - -public final class ServerMocks { - - public static @NonNull Server newServer() { - Server mock = mock(Server.class); - - Logger noOp = mock(Logger.class); - when(mock.getLogger()).thenReturn(noOp); - when(mock.isPrimaryThread()).thenReturn(true); - - // Unsafe - UnsafeValues unsafe = mock(UnsafeValues.class); - when(mock.getUnsafe()).thenReturn(unsafe); - - // Server must be available before tags can be mocked. - Bukkit.setServer(mock); - - // Bukkit has a lot of static constants referencing registry values. To initialize those, the - // registries must be able to be fetched before the classes are touched. - Map, Object> registers = new HashMap<>(); - - doAnswer(invocationGetRegistry -> registers.computeIfAbsent(invocationGetRegistry.getArgument(0), clazz -> { - Registry registry = mock(Registry.class); - Map cache = new HashMap<>(); - doAnswer(invocationGetEntry -> { - NamespacedKey key = invocationGetEntry.getArgument(0); - // Some classes (like BlockType and ItemType) have extra generics that will be - // erased during runtime calls. To ensure accurate typing, grab the constant's field. - // This approach also allows us to return null for unsupported keys. - Class constantClazz; - try { - //noinspection unchecked - constantClazz = (Class) clazz - .getField(key.getKey().toUpperCase(Locale.ROOT).replace('.', '_')).getType(); - } catch (ClassCastException e) { - throw new RuntimeException(e); - } catch (NoSuchFieldException e) { - return null; - } - - return cache.computeIfAbsent(key, key1 -> { - Keyed keyed = mock(constantClazz); - doReturn(key).when(keyed).getKey(); - return keyed; - }); - }).when(registry).get(notNull()); - return registry; - })).when(mock).getRegistry(notNull()); - - // Tags are dependent on registries, but use a different method. - // This will set up blank tags for each constant; all that needs to be done to render them - // functional is to re-mock Tag#getValues. - doAnswer(invocationGetTag -> { - Tag tag = mock(Tag.class); - doReturn(invocationGetTag.getArgument(1)).when(tag).getKey(); - doReturn(Set.of()).when(tag).getValues(); - doAnswer(invocationIsTagged -> { - Keyed keyed = invocationIsTagged.getArgument(0); - Class type = invocationGetTag.getArgument(2); - if (!type.isAssignableFrom(keyed.getClass())) { - return null; - } - // Since these are mocks, the exact instance might not be equal. Consider equal keys equal. - return tag.getValues().contains(keyed) - || tag.getValues().stream().anyMatch(value -> value.getKey().equals(keyed.getKey())); - }).when(tag).isTagged(notNull()); - return tag; - }).when(mock).getTag(notNull(), notNull(), notNull()); - - // Once the server is all set up, touch BlockType and ItemType to initialize. - // This prevents issues when trying to access dependent methods from a Material constant. - try { - Class.forName("org.bukkit.inventory.ItemType"); - Class.forName("org.bukkit.block.BlockType"); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - - return mock; - } - - public static void unsetBukkitServer() { - try { - Field server = Bukkit.class.getDeclaredField("server"); - server.setAccessible(true); - server.set(null, null); - } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - private ServerMocks() { - } - -} \ No newline at end of file From d459cba3acf9de7636e17a32269a8d57b7a25553 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 25 Apr 2026 20:49:00 -0700 Subject: [PATCH 6/9] Filter top ten by aoneblock.intopten permission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the permission with default: true so every player is included unless an admin removes it. Filtering is on the island owner only, applied at refresh time in getOneBlockData. Offline owners always pass — admins must remove the perm from a player who can actually log in. README updated to describe the behaviour. Co-Authored-By: Claude Opus 4.7 --- README.md | 9 ++- .../bentobox/topblock/TopBlockManager.java | 26 ++++++- src/main/resources/addon.yml | 5 +- .../bentobox/topblock/CommonTestSetup.java | 3 + .../topblock/TopBlockManagerTest.java | 68 ++++++++++++++++++- 5 files changed, 105 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2dc732f..22de907 100644 --- a/README.md +++ b/README.md @@ -18,15 +18,20 @@ Add-on for BentoBox to calculate island levels for AOneBlock specifically. Ranks `/ob topblock` - this shows the Top Ten ## Permissions -Permissions are given automatically to players as listed below. If your permissions plugin strips permissions then you may have to allocate these manually. Note that if a player doesn't have the `intopten` permission, they will not be listed in the top ten. +Permissions are given automatically to players as listed below. If your permissions plugin strips permissions then you may have to allocate these manually. ``` -permissions: +permissions: 'aoneblock.island.topblock': description: Player can use TopBlock command default: true + 'aoneblock.intopten': + description: Player's island will be listed in the top ten. Remove from admins or testers to hide them. + default: true ``` +If an island owner is **online** and does not have `aoneblock.intopten`, their island is excluded from the top ten and from placeholders. Offline owners are always included — to hide an admin or tester, remove the perm from the player who can actually log in. Removing the perm from an entire group (e.g. ops) excludes everyone in that group while online. + ## Placeholders ``` diff --git a/src/main/java/world/bentobox/topblock/TopBlockManager.java b/src/main/java/world/bentobox/topblock/TopBlockManager.java index d951749..1f9b159 100644 --- a/src/main/java/world/bentobox/topblock/TopBlockManager.java +++ b/src/main/java/world/bentobox/topblock/TopBlockManager.java @@ -9,8 +9,10 @@ import java.util.Map; import java.util.Objects; import java.util.TreeMap; +import java.util.UUID; import org.bukkit.Bukkit; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -83,11 +85,31 @@ void getOneBlockData() { AOneBlock ob = addon.getaOneBlock(); topTen.clear(); ob.getBlockListener().getAllIslands().stream().filter(i -> i.getLifetime() > 0).forEach(i -> - // Get player island. - addon.getIslands().getIslandById(i.getUniqueId()).ifPresent(island -> + addon.getIslands().getIslandById(i.getUniqueId()) + .filter(this::ownerInTopTen) + .ifPresent(island -> topTen.add(new TopTenData(island, i.getBlockNumber(), i.getLifetime(), i.getPhaseName())))); } + /** + * Returns true if the island's owner should be listed in the top ten. + * Offline owners always pass — admins must remove the perm from a player + * who can actually log in. An online owner without the {@code intopten} + * permission is excluded. + */ + private boolean ownerInTopTen(Island island) { + UUID owner = island.getOwner(); + if (owner == null) { + return false; + } + Player player = Bukkit.getPlayer(owner); + if (player == null) { + return true; + } + String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(island.getWorld()); + return player.hasPermission(permPrefix + "intopten"); + } + /** * Get the string representation of the level. May be converted to shorthand notation, e.g., 104556 = 10.5k * @param lvl - long value to represent diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml index 75a6bce..9180b5d 100755 --- a/src/main/resources/addon.yml +++ b/src/main/resources/addon.yml @@ -8,7 +8,10 @@ authors: tastybento depend: AOneBlock -permissions: +permissions: 'aoneblock.island.topblock': description: Player can use TopBlock command default: true + 'aoneblock.intopten': + description: Player's island will be listed in the top ten. Remove from admins or testers to hide them. + default: true diff --git a/src/test/java/world/bentobox/topblock/CommonTestSetup.java b/src/test/java/world/bentobox/topblock/CommonTestSetup.java index 30c864b..f68c679 100644 --- a/src/test/java/world/bentobox/topblock/CommonTestSetup.java +++ b/src/test/java/world/bentobox/topblock/CommonTestSetup.java @@ -123,6 +123,9 @@ public void setUp() throws Exception { mockedBukkit.when(Bukkit::getItemFactory).thenReturn(itemFactory); mockedBukkit.when(Bukkit::getServer).thenReturn(server); mockedBukkit.when(Bukkit::getScheduler).thenReturn(sch); + // By default treat island owners as offline so the intopten filter + // does not exclude them. Tests that need an online owner can override. + mockedBukkit.when(() -> Bukkit.getPlayer(any(UUID.class))).thenReturn(null); // Location when(location.getWorld()).thenReturn(world); diff --git a/src/test/java/world/bentobox/topblock/TopBlockManagerTest.java b/src/test/java/world/bentobox/topblock/TopBlockManagerTest.java index 9f6a2ce..dd7a94b 100644 --- a/src/test/java/world/bentobox/topblock/TopBlockManagerTest.java +++ b/src/test/java/world/bentobox/topblock/TopBlockManagerTest.java @@ -3,14 +3,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; @@ -45,6 +47,8 @@ public void setUp() throws Exception { when(addon.getIslands()).thenReturn(im); when(aob.getBlockListener()).thenReturn(bl); when(im.getIslandById(anyString())).thenReturn(Optional.of(island)); + when(island.getWorld()).thenReturn(world); + when(iwm.getPermissionPrefix(any())).thenReturn("aoneblock."); tbm = new TopBlockManager(addon); } @@ -157,6 +161,68 @@ void testFormatLevelShorthandGiga() { assertEquals("3.9G", tbm.formatLevel(3_874_130_021L)); } + @Test + void testGetOneBlockDataExcludesOnlineOwnerWithoutIntoptenPerm() { + Player p = org.mockito.Mockito.mock(Player.class); + when(p.hasPermission("aoneblock.intopten")).thenReturn(false); + mockedBukkit.when(() -> Bukkit.getPlayer(any(UUID.class))).thenReturn(p); + + when(bl.getAllIslands()).thenReturn(List.of( + new OneBlockIslands(UUID.randomUUID().toString()) {{ + setBlockNumber(80); + setLifetime(250); + setPhaseName("Underground"); + }})); + tbm.getOneBlockData(); + + assertTrue(tbm.getTopTen(10).isEmpty()); + } + + @Test + void testGetOneBlockDataIncludesOnlineOwnerWithIntoptenPerm() { + Player p = org.mockito.Mockito.mock(Player.class); + when(p.hasPermission("aoneblock.intopten")).thenReturn(true); + mockedBukkit.when(() -> Bukkit.getPlayer(any(UUID.class))).thenReturn(p); + + when(bl.getAllIslands()).thenReturn(List.of( + new OneBlockIslands(UUID.randomUUID().toString()) {{ + setBlockNumber(80); + setLifetime(250); + setPhaseName("Underground"); + }})); + tbm.getOneBlockData(); + + assertEquals(1, tbm.getTopTen(10).size()); + } + + @Test + void testGetOneBlockDataIncludesOfflineOwner() { + // CommonTestSetup already stubs Bukkit.getPlayer -> null + when(bl.getAllIslands()).thenReturn(List.of( + new OneBlockIslands(UUID.randomUUID().toString()) {{ + setBlockNumber(80); + setLifetime(250); + setPhaseName("Underground"); + }})); + tbm.getOneBlockData(); + + assertEquals(1, tbm.getTopTen(10).size()); + } + + @Test + void testGetOneBlockDataExcludesIslandWithoutOwner() { + when(island.getOwner()).thenReturn(null); + when(bl.getAllIslands()).thenReturn(List.of( + new OneBlockIslands(UUID.randomUUID().toString()) {{ + setBlockNumber(80); + setLifetime(250); + setPhaseName("Underground"); + }})); + tbm.getOneBlockData(); + + assertTrue(tbm.getTopTen(10).isEmpty()); + } + @Test void testTopTenDataRecordFields() { TopTenData d = new TopTenData(island, 42, 1234L, "phasy"); From 4f08fcae8c8fe16533b136feeec04cbaddffba9c Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 25 Apr 2026 21:09:10 -0700 Subject: [PATCH 7/9] Update CI to Java 21 and refresh README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GitHub Actions: bump checkout/setup-java/cache to v4, switch to the temurin distribution (adopt is deprecated), and target Java 21 instead of Java 17. Drop the redundant -Dsonar.projectKey CLI flag — it's already set in pom.xml. - README: fix the "level addon jar" copy/paste, document requirements (Paper 1.21, Java 21, BB 3.14.0, AOneBlock 1.18.0), add a config table for refresh-time and shorthand, document the /oneblock topblock alias, explain how a player gets into the top ten, document the topblock.icon. per-player override, and clarify placeholder behaviour past the available rank count. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/build.yml | 16 ++++----- README.md | 69 ++++++++++++++++++++++++++++--------- 2 files changed, 60 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 12bdefc..0091885 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,22 +11,22 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Set up JDK 17 - uses: actions/setup-java@v3 + - name: Set up JDK 21 + uses: actions/setup-java@v4 with: - distribution: 'adopt' - java-version: '17' + distribution: 'temurin' + java-version: '21' - name: Cache SonarCloud packages - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Cache Maven packages - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} @@ -35,4 +35,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \ No newline at end of file + run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar diff --git a/README.md b/README.md index 22de907..55c5d44 100644 --- a/README.md +++ b/README.md @@ -3,42 +3,77 @@ ## About -Add-on for BentoBox to calculate island levels for AOneBlock specifically. Ranks are determined by how many magic blocks have been mined - the count. +TopBlock is a [BentoBox](https://github.com/BentoBoxWorld/BentoBox) addon that produces a Top Ten ranking for the [AOneBlock](https://github.com/BentoBoxWorld/AOneBlock) game mode based on how many magic blocks each island has mined. + +## Requirements + +- Paper 1.21.x (Spigot is no longer supported) +- Java 21 +- BentoBox 3.14.0 or later +- AOneBlock 1.18.0 or later ## How to use -1. Place the level addon jar in the addons folder of the BentoBox plugin. Make sure you have AOneBlock installed too! -2. Restart the server -3. The addon will create a data folder and inside the folder will be a config.yml -4. Edit the config.yml how you want. -5. Restart the server if you make a change +1. Drop the TopBlock jar into your server's `plugins/BentoBox/addons/` folder. AOneBlock must already be installed there too. +2. Restart the server. TopBlock will create `addons/TopBlock/config.yml` and `addons/TopBlock/panels/top_panel.yml`. +3. Edit `config.yml` if you want to tune anything (see below) and restart the server again to apply. + +## Configuration + +`addons/TopBlock/config.yml`: + +| Option | Default | Description | +|----------------|---------|-------------------------------------------------------------------------------------------------------------------| +| `refresh-time` | `5` | How often the Top Ten is recalculated, in minutes. Minimum 1. Each refresh reads every island, so don't set it too low. | +| `shorthand` | `false` | If `true`, format large counts using units — `10,500` becomes `10.5k`, `1,527,314` becomes `1.5M`, etc. | + +The panel layout lives in `addons/TopBlock/panels/top_panel.yml`. Edits are preserved across restarts. ## Commands -`/ob topblock` - this shows the Top Ten +`/ob topblock` (alias: `/oneblock topblock`) — opens the Top Ten panel. + +To get into the top ten, a player just needs to mine at least one magic block on their AOneBlock island. The list refreshes every `refresh-time` minutes; a player who just started mining may need to wait that long before appearing. ## Permissions -Permissions are given automatically to players as listed below. If your permissions plugin strips permissions then you may have to allocate these manually. -``` +Permissions are given automatically to players. If your permissions plugin strips defaults, allocate them manually: + +```yaml permissions: 'aoneblock.island.topblock': - description: Player can use TopBlock command + description: Player can use the TopBlock command default: true 'aoneblock.intopten': description: Player's island will be listed in the top ten. Remove from admins or testers to hide them. default: true ``` -If an island owner is **online** and does not have `aoneblock.intopten`, their island is excluded from the top ten and from placeholders. Offline owners are always included — to hide an admin or tester, remove the perm from the player who can actually log in. Removing the perm from an entire group (e.g. ops) excludes everyone in that group while online. +If an island owner is **online** and lacks `aoneblock.intopten`, their island is excluded from the top ten panel and from placeholders. **Offline** owners are always included — to hide an admin or tester, remove the perm from the player who can actually log in. Removing the perm from an entire group (e.g. ops) excludes everyone in that group while online. + +The icon shown for each rank can be overridden per player by granting `aoneblock.topblock.icon.` (for example `aoneblock.topblock.icon.diamond_block`). Without an override, the rank icon is the player's head. ## Placeholders ``` -%aoneblock_island_player_name_top_RANK% where RANK is 1 to 10 - Island owner's name -%aoneblock_island_member_names_top_RANK% where RANK is 1 to 10 - Name of island team members -%aoneblock_island_phase_name_top_RANK% where RANK is 1 to 10 - Name of the phase they have reached -%aoneblock_island_phase_number_top_RANK% where RANK is 1 to 10 - Phase number, e.g. Plains is 1, Underground is 2, etc. -%aoneblock_island_count_top_RANK% where RANK is 1 to 10 - Block Count of magic blocks mined this round -%aoneblock_island_lifetime_top_RANK% where RANK is 1 to 10 - Lifetime count of magic blocks mined +%aoneblock_island_player_name_top_RANK% - Island owner's name +%aoneblock_island_member_names_top_RANK% - Comma-separated team members (highest rank first) +%aoneblock_island_phase_name_top_RANK% - Name of the phase the island has reached +%aoneblock_island_phase_number_top_RANK% - Phase number, e.g. Plains is 1, Underground is 2 +%aoneblock_island_count_top_RANK% - Block count of magic blocks mined this round +%aoneblock_island_lifetime_top_RANK% - Lifetime count of magic blocks mined +``` + +`RANK` is `1` to `10`. If fewer than `RANK` islands qualify for the top ten, the placeholder returns an empty string. + +## Building from source + +```bash +mvn clean package ``` + +Produces `target/TopBlock-.jar`. + +## License + +[EPL-2.0](LICENSE) From 46531109c72a6b5dc60d0be7f99c927e85667930 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 25 Apr 2026 21:58:10 -0700 Subject: [PATCH 8/9] Add translations for 21 languages Adds locale files matching all languages present in BentoBox core: cs, de, es, fr, hr, hu, id, it, ja, ko, lv, nl, pl, pt-BR, pt, ro, tr, uk, vi, zh-CN, zh-HK. All 35 keys translated per file. Color codes and placeholders ([name], [player], [number], [count], [lifetime]) preserved exactly. Co-Authored-By: Claude Sonnet 4.6 --- src/main/resources/locales/cs.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/de.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/es.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/fr.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/hr.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/hu.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/id.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/it.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/ja.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/ko.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/lv.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/nl.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/pl.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/pt-BR.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/pt.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/ro.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/tr.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/uk.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/vi.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/zh-CN.yml | 69 ++++++++++++++++++++++++++++ src/main/resources/locales/zh-HK.yml | 69 ++++++++++++++++++++++++++++ 21 files changed, 1449 insertions(+) create mode 100644 src/main/resources/locales/cs.yml create mode 100644 src/main/resources/locales/de.yml create mode 100644 src/main/resources/locales/es.yml create mode 100644 src/main/resources/locales/fr.yml create mode 100644 src/main/resources/locales/hr.yml create mode 100644 src/main/resources/locales/hu.yml create mode 100644 src/main/resources/locales/id.yml create mode 100644 src/main/resources/locales/it.yml create mode 100644 src/main/resources/locales/ja.yml create mode 100644 src/main/resources/locales/ko.yml create mode 100644 src/main/resources/locales/lv.yml create mode 100644 src/main/resources/locales/nl.yml create mode 100644 src/main/resources/locales/pl.yml create mode 100644 src/main/resources/locales/pt-BR.yml create mode 100644 src/main/resources/locales/pt.yml create mode 100644 src/main/resources/locales/ro.yml create mode 100644 src/main/resources/locales/tr.yml create mode 100644 src/main/resources/locales/uk.yml create mode 100644 src/main/resources/locales/vi.yml create mode 100644 src/main/resources/locales/zh-CN.yml create mode 100644 src/main/resources/locales/zh-HK.yml diff --git a/src/main/resources/locales/cs.yml b/src/main/resources/locales/cs.yml new file mode 100644 index 0000000..3fea00a --- /dev/null +++ b/src/main/resources/locales/cs.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "zobrazit Top Deset AOneBlock" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Počet [count]" + + +topblock: + gui: + titles: + top: "&0&l Nejlepší ostrovy" + detail-panel: "&0&l Ostrov hráče [name]" + value-panel: "&0&l Hodnoty bloků" + buttons: + island: + empty: '&f&l [name]. místo' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: 'Ostrov hráče [player]' + # Text for [owner] in description. + owner: '&7&l Vlastník: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Členové:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "neznámý" + # Section for parsing [place] + place: '&7&o [number]. &r&7 místo' + # Section for parsing [count] + count: '&7 Počet bloků: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Celkový počet bloků: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Předchozí strana' + description: |- + &7 Přejít na stránku [number] + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Další strana' + description: |- + &7 Přejít na stránku [number] + tips: + click-to-view: '&e Klikni &7 pro zobrazení.' + click-to-previous: '&e Klikni &7 pro zobrazení předchozí stránky.' + click-to-next: '&e Klikni &7 pro zobrazení další stránky.' + click-to-select: '&e Klikni &7 pro výběr.' + left-click-to-cycle-up: '&e Levý klik &7 pro posun nahoru.' + right-click-to-cycle-down: '&e Pravý klik &7 pro posun dolů.' + left-click-to-change: '&e Levý klik &7 pro úpravu.' + right-click-to-clear: '&e Pravý klik &7 pro vymazání.' + click-to-asc: '&e Klikni &7 pro řazení vzestupně.' + click-to-desc: '&e Klikni &7 pro řazení sestupně.' + click-to-warp: '&e Klikni &7 pro teleportaci.' + click-to-visit: '&e Klikni &7 pro návštěvu.' + right-click-to-visit: '&e Pravý klik &7 pro návštěvu.' diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml new file mode 100644 index 0000000..c666d72 --- /dev/null +++ b/src/main/resources/locales/de.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "AOneBlock Top Zehn anzeigen" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Anzahl [count]" + + +topblock: + gui: + titles: + top: "&0&l Top-Inseln" + detail-panel: "&0&l Insel von [name]" + value-panel: "&0&l Blockwerte" + buttons: + island: + empty: '&f&l [name]. Platz' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: 'Insel von [player]' + # Text for [owner] in description. + owner: '&7&l Besitzer: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Mitglieder:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "unbekannt" + # Section for parsing [place] + place: '&7&o [number]. &r&7 Platz' + # Section for parsing [count] + count: '&7 Blockanzahl: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Gesamtanzahl: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Vorherige Seite' + description: |- + &7 Zu Seite [number] wechseln + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Nächste Seite' + description: |- + &7 Zu Seite [number] wechseln + tips: + click-to-view: '&e Klicken &7 zum Ansehen.' + click-to-previous: '&e Klicken &7 für die vorherige Seite.' + click-to-next: '&e Klicken &7 für die nächste Seite.' + click-to-select: '&e Klicken &7 zum Auswählen.' + left-click-to-cycle-up: '&e Linksklick &7 zum Hochzählen.' + right-click-to-cycle-down: '&e Rechtsklick &7 zum Herunterzählen.' + left-click-to-change: '&e Linksklick &7 zum Bearbeiten.' + right-click-to-clear: '&e Rechtsklick &7 zum Löschen.' + click-to-asc: '&e Klicken &7 für aufsteigende Sortierung.' + click-to-desc: '&e Klicken &7 für absteigende Sortierung.' + click-to-warp: '&e Klicken &7 zum Warpen.' + click-to-visit: '&e Klicken &7 zum Besuchen.' + right-click-to-visit: '&e Rechtsklick &7 zum Besuchen.' diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml new file mode 100644 index 0000000..d793ef5 --- /dev/null +++ b/src/main/resources/locales/es.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "mostrar el Top Diez de AOneBlock" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Cuenta [count]" + + +topblock: + gui: + titles: + top: "&0&l Mejores Islas" + detail-panel: "&0&l Isla de [name]" + value-panel: "&0&l Valores de Bloques" + buttons: + island: + empty: '&f&l [name]. lugar' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: 'Isla de [player]' + # Text for [owner] in description. + owner: '&7&l Propietario: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Miembros:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "desconocido" + # Section for parsing [place] + place: '&7&o [number]. &r&7 lugar' + # Section for parsing [count] + count: '&7 Conteo de bloques: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Conteo de por vida: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Página Anterior' + description: |- + &7 Cambiar a la página [number] + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Página Siguiente' + description: |- + &7 Cambiar a la página [number] + tips: + click-to-view: '&e Clic &7 para ver.' + click-to-previous: '&e Clic &7 para ver la página anterior.' + click-to-next: '&e Clic &7 para ver la página siguiente.' + click-to-select: '&e Clic &7 para seleccionar.' + left-click-to-cycle-up: '&e Clic Izquierdo &7 para subir.' + right-click-to-cycle-down: '&e Clic Derecho &7 para bajar.' + left-click-to-change: '&e Clic Izquierdo &7 para editar.' + right-click-to-clear: '&e Clic Derecho &7 para limpiar.' + click-to-asc: '&e Clic &7 para ordenar de forma ascendente.' + click-to-desc: '&e Clic &7 para ordenar de forma descendente.' + click-to-warp: '&e Clic &7 para teletransportarse.' + click-to-visit: '&e Clic &7 para visitar.' + right-click-to-visit: '&e Clic Derecho &7 para visitar.' diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml new file mode 100644 index 0000000..29cbb8e --- /dev/null +++ b/src/main/resources/locales/fr.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "afficher le Top Dix de AOneBlock" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Compte [count]" + + +topblock: + gui: + titles: + top: "&0&l Meilleures Îles" + detail-panel: "&0&l Île de [name]" + value-panel: "&0&l Valeurs des Blocs" + buttons: + island: + empty: '&f&l [name]. place' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: 'Île de [player]' + # Text for [owner] in description. + owner: '&7&l Propriétaire: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Membres:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "inconnu" + # Section for parsing [place] + place: '&7&o [number]. &r&7 place' + # Section for parsing [count] + count: '&7 Nombre de blocs: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Compte total: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Page Précédente' + description: |- + &7 Aller à la page [number] + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Page Suivante' + description: |- + &7 Aller à la page [number] + tips: + click-to-view: '&e Cliquez &7 pour voir.' + click-to-previous: '&e Cliquez &7 pour voir la page précédente.' + click-to-next: '&e Cliquez &7 pour voir la page suivante.' + click-to-select: '&e Cliquez &7 pour sélectionner.' + left-click-to-cycle-up: '&e Clic Gauche &7 pour monter.' + right-click-to-cycle-down: '&e Clic Droit &7 pour descendre.' + left-click-to-change: '&e Clic Gauche &7 pour modifier.' + right-click-to-clear: '&e Clic Droit &7 pour effacer.' + click-to-asc: '&e Cliquez &7 pour trier par ordre croissant.' + click-to-desc: '&e Cliquez &7 pour trier par ordre décroissant.' + click-to-warp: '&e Cliquez &7 pour vous téléporter.' + click-to-visit: '&e Cliquez &7 pour visiter.' + right-click-to-visit: '&e Clic Droit &7 pour visiter.' diff --git a/src/main/resources/locales/hr.yml b/src/main/resources/locales/hr.yml new file mode 100644 index 0000000..40119a0 --- /dev/null +++ b/src/main/resources/locales/hr.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "prikazati AOneBlock Top Deset" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Broj [count]" + + +topblock: + gui: + titles: + top: "&0&l Vrhunski Otoci" + detail-panel: "&0&l Otok igrača [name]" + value-panel: "&0&l Vrijednosti blokova" + buttons: + island: + empty: '&f&l [name]. mjesto' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: 'Otok igrača [player]' + # Text for [owner] in description. + owner: '&7&l Vlasnik: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Članovi:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "nepoznat" + # Section for parsing [place] + place: '&7&o [number]. &r&7 mjesto' + # Section for parsing [count] + count: '&7 Broj blokova: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Ukupan broj blokova: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Prethodna Stranica' + description: |- + &7 Prebaci na stranicu [number] + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Sljedeća Stranica' + description: |- + &7 Prebaci na stranicu [number] + tips: + click-to-view: '&e Klikni &7 za pregled.' + click-to-previous: '&e Klikni &7 za prethodnu stranicu.' + click-to-next: '&e Klikni &7 za sljedeću stranicu.' + click-to-select: '&e Klikni &7 za odabir.' + left-click-to-cycle-up: '&e Lijevi klik &7 za pomicanje gore.' + right-click-to-cycle-down: '&e Desni klik &7 za pomicanje dolje.' + left-click-to-change: '&e Lijevi klik &7 za uređivanje.' + right-click-to-clear: '&e Desni klik &7 za brisanje.' + click-to-asc: '&e Klikni &7 za sortiranje uzlazno.' + click-to-desc: '&e Klikni &7 za sortiranje silazno.' + click-to-warp: '&e Klikni &7 za teleportaciju.' + click-to-visit: '&e Klikni &7 za posjetu.' + right-click-to-visit: '&e Desni klik &7 za posjetu.' diff --git a/src/main/resources/locales/hu.yml b/src/main/resources/locales/hu.yml new file mode 100644 index 0000000..17b830e --- /dev/null +++ b/src/main/resources/locales/hu.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "az AOneBlock Top Tíz megjelenítése" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Szám [count]" + + +topblock: + gui: + titles: + top: "&0&l Top Szigetek" + detail-panel: "&0&l [name] szigete" + value-panel: "&0&l Blokk értékek" + buttons: + island: + empty: '&f&l [name]. hely' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: '[player] szigete' + # Text for [owner] in description. + owner: '&7&l Tulajdonos: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Tagok:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "ismeretlen" + # Section for parsing [place] + place: '&7&o [number]. &r&7 hely' + # Section for parsing [count] + count: '&7 Blokk szám: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Élettartam számlálás: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Előző Oldal' + description: |- + &7 Ugrás a(z) [number]. oldalra + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Következő Oldal' + description: |- + &7 Ugrás a(z) [number]. oldalra + tips: + click-to-view: '&e Kattints &7 a megtekintéshez.' + click-to-previous: '&e Kattints &7 az előző oldal megtekintéséhez.' + click-to-next: '&e Kattints &7 a következő oldal megtekintéséhez.' + click-to-select: '&e Kattints &7 a kiválasztáshoz.' + left-click-to-cycle-up: '&e Bal kattintás &7 a felfelé léptetéshez.' + right-click-to-cycle-down: '&e Jobb kattintás &7 a lefelé léptetéshez.' + left-click-to-change: '&e Bal kattintás &7 a szerkesztéshez.' + right-click-to-clear: '&e Jobb kattintás &7 a törléshez.' + click-to-asc: '&e Kattints &7 a növekvő sorrendhez.' + click-to-desc: '&e Kattints &7 a csökkenő sorrendhez.' + click-to-warp: '&e Kattints &7 a teleportáláshoz.' + click-to-visit: '&e Kattints &7 a látogatáshoz.' + right-click-to-visit: '&e Jobb kattintás &7 a látogatáshoz.' diff --git a/src/main/resources/locales/id.yml b/src/main/resources/locales/id.yml new file mode 100644 index 0000000..6ea3c66 --- /dev/null +++ b/src/main/resources/locales/id.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "tampilkan Top Sepuluh AOneBlock" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Jumlah [count]" + + +topblock: + gui: + titles: + top: "&0&l Pulau Teratas" + detail-panel: "&0&l Pulau [name]" + value-panel: "&0&l Nilai Blok" + buttons: + island: + empty: '&f&l [name]. tempat' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: 'Pulau [player]' + # Text for [owner] in description. + owner: '&7&l Pemilik: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Anggota:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "tidak diketahui" + # Section for parsing [place] + place: '&7&o [number]. &r&7 tempat' + # Section for parsing [count] + count: '&7 Jumlah Blok: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Total Seumur Hidup: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Halaman Sebelumnya' + description: |- + &7 Beralih ke halaman [number] + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Halaman Berikutnya' + description: |- + &7 Beralih ke halaman [number] + tips: + click-to-view: '&e Klik &7 untuk melihat.' + click-to-previous: '&e Klik &7 untuk melihat halaman sebelumnya.' + click-to-next: '&e Klik &7 untuk melihat halaman berikutnya.' + click-to-select: '&e Klik &7 untuk memilih.' + left-click-to-cycle-up: '&e Klik Kiri &7 untuk naik.' + right-click-to-cycle-down: '&e Klik Kanan &7 untuk turun.' + left-click-to-change: '&e Klik Kiri &7 untuk mengedit.' + right-click-to-clear: '&e Klik Kanan &7 untuk menghapus.' + click-to-asc: '&e Klik &7 untuk mengurutkan secara menaik.' + click-to-desc: '&e Klik &7 untuk mengurutkan secara menurun.' + click-to-warp: '&e Klik &7 untuk warp.' + click-to-visit: '&e Klik &7 untuk mengunjungi.' + right-click-to-visit: '&e Klik Kanan &7 untuk mengunjungi.' diff --git a/src/main/resources/locales/it.yml b/src/main/resources/locales/it.yml new file mode 100644 index 0000000..2ad8c88 --- /dev/null +++ b/src/main/resources/locales/it.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "mostra la Top Dieci di AOneBlock" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Conteggio [count]" + + +topblock: + gui: + titles: + top: "&0&l Isole Migliori" + detail-panel: "&0&l Isola di [name]" + value-panel: "&0&l Valori dei Blocchi" + buttons: + island: + empty: '&f&l [name]. posto' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: 'Isola di [player]' + # Text for [owner] in description. + owner: '&7&l Proprietario: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Membri:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "sconosciuto" + # Section for parsing [place] + place: '&7&o [number]. &r&7 posto' + # Section for parsing [count] + count: '&7 Conteggio Blocchi: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Conteggio Totale: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Pagina Precedente' + description: |- + &7 Passa alla pagina [number] + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Pagina Successiva' + description: |- + &7 Passa alla pagina [number] + tips: + click-to-view: '&e Clicca &7 per visualizzare.' + click-to-previous: '&e Clicca &7 per la pagina precedente.' + click-to-next: '&e Clicca &7 per la pagina successiva.' + click-to-select: '&e Clicca &7 per selezionare.' + left-click-to-cycle-up: '&e Clic Sinistro &7 per scorrere su.' + right-click-to-cycle-down: '&e Clic Destro &7 per scorrere giù.' + left-click-to-change: '&e Clic Sinistro &7 per modificare.' + right-click-to-clear: '&e Clic Destro &7 per cancellare.' + click-to-asc: '&e Clicca &7 per ordinare in modo crescente.' + click-to-desc: '&e Clicca &7 per ordinare in modo decrescente.' + click-to-warp: '&e Clicca &7 per teletrasportarti.' + click-to-visit: '&e Clicca &7 per visitare.' + right-click-to-visit: '&e Clic Destro &7 per visitare.' diff --git a/src/main/resources/locales/ja.yml b/src/main/resources/locales/ja.yml new file mode 100644 index 0000000..4f839f3 --- /dev/null +++ b/src/main/resources/locales/ja.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "AOneBlockのトップテンを表示" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b カウント [count]" + + +topblock: + gui: + titles: + top: "&0&l トップアイランド" + detail-panel: "&0&l [name]の島" + value-panel: "&0&l ブロック値" + buttons: + island: + empty: '&f&l [name]. 位' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: '[player]の島' + # Text for [owner] in description. + owner: '&7&l オーナー: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l メンバー:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "不明" + # Section for parsing [place] + place: '&7&o [number]. &r&7 位' + # Section for parsing [count] + count: '&7 ブロック数: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 累計ブロック数: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l 前のページ' + description: |- + &7 [number]ページに移動 + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l 次のページ' + description: |- + &7 [number]ページに移動 + tips: + click-to-view: '&e クリック &7 して見る。' + click-to-previous: '&e クリック &7 して前のページを見る。' + click-to-next: '&e クリック &7 して次のページを見る。' + click-to-select: '&e クリック &7 して選択。' + left-click-to-cycle-up: '&e 左クリック &7 して上に移動。' + right-click-to-cycle-down: '&e 右クリック &7 して下に移動。' + left-click-to-change: '&e 左クリック &7 して編集。' + right-click-to-clear: '&e 右クリック &7 してクリア。' + click-to-asc: '&e クリック &7 して昇順に並べ替え。' + click-to-desc: '&e クリック &7 して降順に並べ替え。' + click-to-warp: '&e クリック &7 してワープ。' + click-to-visit: '&e クリック &7 して訪問。' + right-click-to-visit: '&e 右クリック &7 して訪問。' diff --git a/src/main/resources/locales/ko.yml b/src/main/resources/locales/ko.yml new file mode 100644 index 0000000..b684ff5 --- /dev/null +++ b/src/main/resources/locales/ko.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "AOneBlock 상위 10위 표시" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b 수 [count]" + + +topblock: + gui: + titles: + top: "&0&l 상위 섬" + detail-panel: "&0&l [name]의 섬" + value-panel: "&0&l 블록 값" + buttons: + island: + empty: '&f&l [name]. 위' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: '[player]의 섬' + # Text for [owner] in description. + owner: '&7&l 소유자: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l 멤버:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "알 수 없음" + # Section for parsing [place] + place: '&7&o [number]. &r&7 위' + # Section for parsing [count] + count: '&7 블록 수: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 누적 블록 수: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l 이전 페이지' + description: |- + &7 [number] 페이지로 이동 + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l 다음 페이지' + description: |- + &7 [number] 페이지로 이동 + tips: + click-to-view: '&e 클릭 &7 하여 보기.' + click-to-previous: '&e 클릭 &7 하여 이전 페이지 보기.' + click-to-next: '&e 클릭 &7 하여 다음 페이지 보기.' + click-to-select: '&e 클릭 &7 하여 선택.' + left-click-to-cycle-up: '&e 좌클릭 &7 하여 위로 이동.' + right-click-to-cycle-down: '&e 우클릭 &7 하여 아래로 이동.' + left-click-to-change: '&e 좌클릭 &7 하여 편집.' + right-click-to-clear: '&e 우클릭 &7 하여 지우기.' + click-to-asc: '&e 클릭 &7 하여 오름차순 정렬.' + click-to-desc: '&e 클릭 &7 하여 내림차순 정렬.' + click-to-warp: '&e 클릭 &7 하여 워프.' + click-to-visit: '&e 클릭 &7 하여 방문.' + right-click-to-visit: '&e 우클릭 &7 하여 방문.' diff --git a/src/main/resources/locales/lv.yml b/src/main/resources/locales/lv.yml new file mode 100644 index 0000000..24408f8 --- /dev/null +++ b/src/main/resources/locales/lv.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "parādīt AOneBlock Top Desmit" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Skaits [count]" + + +topblock: + gui: + titles: + top: "&0&l Labākie Salās" + detail-panel: "&0&l [name] sala" + value-panel: "&0&l Bloku vērtības" + buttons: + island: + empty: '&f&l [name]. vieta' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: '[player] sala' + # Text for [owner] in description. + owner: '&7&l Īpašnieks: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Dalībnieki:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "nezināms" + # Section for parsing [place] + place: '&7&o [number]. &r&7 vieta' + # Section for parsing [count] + count: '&7 Bloku skaits: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Kopējais bloku skaits: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Iepriekšējā Lapa' + description: |- + &7 Pārslēgties uz lapu [number] + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Nākamā Lapa' + description: |- + &7 Pārslēgties uz lapu [number] + tips: + click-to-view: '&e Noklikšķini &7 lai skatītu.' + click-to-previous: '&e Noklikšķini &7 lai skatītu iepriekšējo lapu.' + click-to-next: '&e Noklikšķini &7 lai skatītu nākamo lapu.' + click-to-select: '&e Noklikšķini &7 lai izvēlētos.' + left-click-to-cycle-up: '&e Kreisais klikšķis &7 lai ritinātu augšup.' + right-click-to-cycle-down: '&e Labais klikšķis &7 lai ritinātu lejup.' + left-click-to-change: '&e Kreisais klikšķis &7 lai rediģētu.' + right-click-to-clear: '&e Labais klikšķis &7 lai notīrītu.' + click-to-asc: '&e Noklikšķini &7 lai kārtotu augošā secībā.' + click-to-desc: '&e Noklikšķini &7 lai kārtotu dilstošā secībā.' + click-to-warp: '&e Noklikšķini &7 lai teleportētos.' + click-to-visit: '&e Noklikšķini &7 lai apmeklētu.' + right-click-to-visit: '&e Labais klikšķis &7 lai apmeklētu.' diff --git a/src/main/resources/locales/nl.yml b/src/main/resources/locales/nl.yml new file mode 100644 index 0000000..5062c7a --- /dev/null +++ b/src/main/resources/locales/nl.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "toon de AOneBlock Top Tien" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Aantal [count]" + + +topblock: + gui: + titles: + top: "&0&l Top Eilanden" + detail-panel: "&0&l Eiland van [name]" + value-panel: "&0&l Blokwaarden" + buttons: + island: + empty: '&f&l [name]. plaats' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: 'Eiland van [player]' + # Text for [owner] in description. + owner: '&7&l Eigenaar: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Leden:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "onbekend" + # Section for parsing [place] + place: '&7&o [number]. &r&7 plaats' + # Section for parsing [count] + count: '&7 Bloktelling: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Levenslange Telling: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Vorige Pagina' + description: |- + &7 Naar pagina [number] gaan + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Volgende Pagina' + description: |- + &7 Naar pagina [number] gaan + tips: + click-to-view: '&e Klik &7 om te bekijken.' + click-to-previous: '&e Klik &7 voor de vorige pagina.' + click-to-next: '&e Klik &7 voor de volgende pagina.' + click-to-select: '&e Klik &7 om te selecteren.' + left-click-to-cycle-up: '&e Linkermuisknop &7 om omhoog te bladeren.' + right-click-to-cycle-down: '&e Rechtermuisknop &7 om omlaag te bladeren.' + left-click-to-change: '&e Linkermuisknop &7 om te bewerken.' + right-click-to-clear: '&e Rechtermuisknop &7 om te wissen.' + click-to-asc: '&e Klik &7 om oplopend te sorteren.' + click-to-desc: '&e Klik &7 om aflopend te sorteren.' + click-to-warp: '&e Klik &7 om te warpen.' + click-to-visit: '&e Klik &7 om te bezoeken.' + right-click-to-visit: '&e Rechtermuisknop &7 om te bezoeken.' diff --git a/src/main/resources/locales/pl.yml b/src/main/resources/locales/pl.yml new file mode 100644 index 0000000..7d47e05 --- /dev/null +++ b/src/main/resources/locales/pl.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "pokaż Top Dziesięć AOneBlock" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Liczba [count]" + + +topblock: + gui: + titles: + top: "&0&l Najlepsze Wyspy" + detail-panel: "&0&l Wyspa gracza [name]" + value-panel: "&0&l Wartości bloków" + buttons: + island: + empty: '&f&l [name]. miejsce' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: 'Wyspa gracza [player]' + # Text for [owner] in description. + owner: '&7&l Właściciel: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Członkowie:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "nieznany" + # Section for parsing [place] + place: '&7&o [number]. &r&7 miejsce' + # Section for parsing [count] + count: '&7 Liczba bloków: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Łączna liczba bloków: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Poprzednia Strona' + description: |- + &7 Przełącz na stronę [number] + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Następna Strona' + description: |- + &7 Przełącz na stronę [number] + tips: + click-to-view: '&e Kliknij &7 aby zobaczyć.' + click-to-previous: '&e Kliknij &7 aby zobaczyć poprzednią stronę.' + click-to-next: '&e Kliknij &7 aby zobaczyć następną stronę.' + click-to-select: '&e Kliknij &7 aby wybrać.' + left-click-to-cycle-up: '&e Lewy klik &7 aby przewinąć w górę.' + right-click-to-cycle-down: '&e Prawy klik &7 aby przewinąć w dół.' + left-click-to-change: '&e Lewy klik &7 aby edytować.' + right-click-to-clear: '&e Prawy klik &7 aby wyczyścić.' + click-to-asc: '&e Kliknij &7 aby posortować rosnąco.' + click-to-desc: '&e Kliknij &7 aby posortować malejąco.' + click-to-warp: '&e Kliknij &7 aby warp.' + click-to-visit: '&e Kliknij &7 aby odwiedzić.' + right-click-to-visit: '&e Prawy klik &7 aby odwiedzić.' diff --git a/src/main/resources/locales/pt-BR.yml b/src/main/resources/locales/pt-BR.yml new file mode 100644 index 0000000..474a02d --- /dev/null +++ b/src/main/resources/locales/pt-BR.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "mostrar o Top Dez do AOneBlock" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Contagem [count]" + + +topblock: + gui: + titles: + top: "&0&l Melhores Ilhas" + detail-panel: "&0&l Ilha de [name]" + value-panel: "&0&l Valores de Blocos" + buttons: + island: + empty: '&f&l [name]. lugar' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: 'Ilha de [player]' + # Text for [owner] in description. + owner: '&7&l Proprietário: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Membros:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "desconhecido" + # Section for parsing [place] + place: '&7&o [number]. &r&7 lugar' + # Section for parsing [count] + count: '&7 Contagem de Blocos: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Contagem Total: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Página Anterior' + description: |- + &7 Mudar para a página [number] + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Próxima Página' + description: |- + &7 Mudar para a página [number] + tips: + click-to-view: '&e Clique &7 para ver.' + click-to-previous: '&e Clique &7 para ver a página anterior.' + click-to-next: '&e Clique &7 para ver a próxima página.' + click-to-select: '&e Clique &7 para selecionar.' + left-click-to-cycle-up: '&e Clique Esquerdo &7 para subir.' + right-click-to-cycle-down: '&e Clique Direito &7 para descer.' + left-click-to-change: '&e Clique Esquerdo &7 para editar.' + right-click-to-clear: '&e Clique Direito &7 para limpar.' + click-to-asc: '&e Clique &7 para ordenar em ordem crescente.' + click-to-desc: '&e Clique &7 para ordenar em ordem decrescente.' + click-to-warp: '&e Clique &7 para se teletransportar.' + click-to-visit: '&e Clique &7 para visitar.' + right-click-to-visit: '&e Clique Direito &7 para visitar.' diff --git a/src/main/resources/locales/pt.yml b/src/main/resources/locales/pt.yml new file mode 100644 index 0000000..474a02d --- /dev/null +++ b/src/main/resources/locales/pt.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "mostrar o Top Dez do AOneBlock" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Contagem [count]" + + +topblock: + gui: + titles: + top: "&0&l Melhores Ilhas" + detail-panel: "&0&l Ilha de [name]" + value-panel: "&0&l Valores de Blocos" + buttons: + island: + empty: '&f&l [name]. lugar' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: 'Ilha de [player]' + # Text for [owner] in description. + owner: '&7&l Proprietário: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Membros:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "desconhecido" + # Section for parsing [place] + place: '&7&o [number]. &r&7 lugar' + # Section for parsing [count] + count: '&7 Contagem de Blocos: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Contagem Total: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Página Anterior' + description: |- + &7 Mudar para a página [number] + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Próxima Página' + description: |- + &7 Mudar para a página [number] + tips: + click-to-view: '&e Clique &7 para ver.' + click-to-previous: '&e Clique &7 para ver a página anterior.' + click-to-next: '&e Clique &7 para ver a próxima página.' + click-to-select: '&e Clique &7 para selecionar.' + left-click-to-cycle-up: '&e Clique Esquerdo &7 para subir.' + right-click-to-cycle-down: '&e Clique Direito &7 para descer.' + left-click-to-change: '&e Clique Esquerdo &7 para editar.' + right-click-to-clear: '&e Clique Direito &7 para limpar.' + click-to-asc: '&e Clique &7 para ordenar em ordem crescente.' + click-to-desc: '&e Clique &7 para ordenar em ordem decrescente.' + click-to-warp: '&e Clique &7 para se teletransportar.' + click-to-visit: '&e Clique &7 para visitar.' + right-click-to-visit: '&e Clique Direito &7 para visitar.' diff --git a/src/main/resources/locales/ro.yml b/src/main/resources/locales/ro.yml new file mode 100644 index 0000000..5bc06b9 --- /dev/null +++ b/src/main/resources/locales/ro.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "afișează Top Zece AOneBlock" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Număr [count]" + + +topblock: + gui: + titles: + top: "&0&l Insule de Top" + detail-panel: "&0&l Insula lui [name]" + value-panel: "&0&l Valori Blocuri" + buttons: + island: + empty: '&f&l [name]. loc' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: 'Insula lui [player]' + # Text for [owner] in description. + owner: '&7&l Proprietar: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Membri:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "necunoscut" + # Section for parsing [place] + place: '&7&o [number]. &r&7 loc' + # Section for parsing [count] + count: '&7 Număr Blocuri: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Total Blocuri: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Pagina Anterioară' + description: |- + &7 Mergi la pagina [number] + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Pagina Următoare' + description: |- + &7 Mergi la pagina [number] + tips: + click-to-view: '&e Click &7 pentru a vizualiza.' + click-to-previous: '&e Click &7 pentru pagina anterioară.' + click-to-next: '&e Click &7 pentru pagina următoare.' + click-to-select: '&e Click &7 pentru a selecta.' + left-click-to-cycle-up: '&e Click Stânga &7 pentru sus.' + right-click-to-cycle-down: '&e Click Dreapta &7 pentru jos.' + left-click-to-change: '&e Click Stânga &7 pentru editare.' + right-click-to-clear: '&e Click Dreapta &7 pentru ștergere.' + click-to-asc: '&e Click &7 pentru sortare crescătoare.' + click-to-desc: '&e Click &7 pentru sortare descrescătoare.' + click-to-warp: '&e Click &7 pentru teleportare.' + click-to-visit: '&e Click &7 pentru a vizita.' + right-click-to-visit: '&e Click Dreapta &7 pentru a vizita.' diff --git a/src/main/resources/locales/tr.yml b/src/main/resources/locales/tr.yml new file mode 100644 index 0000000..59a59d9 --- /dev/null +++ b/src/main/resources/locales/tr.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "AOneBlock İlk Onunu göster" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Sayı [count]" + + +topblock: + gui: + titles: + top: "&0&l En İyi Adalar" + detail-panel: "&0&l [name] Adası" + value-panel: "&0&l Blok Değerleri" + buttons: + island: + empty: '&f&l [name]. sıra' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: '[player] Adası' + # Text for [owner] in description. + owner: '&7&l Sahip: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Üyeler:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "bilinmiyor" + # Section for parsing [place] + place: '&7&o [number]. &r&7 sıra' + # Section for parsing [count] + count: '&7 Blok Sayısı: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Toplam Sayı: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Önceki Sayfa' + description: |- + &7 [number]. sayfaya geç + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Sonraki Sayfa' + description: |- + &7 [number]. sayfaya geç + tips: + click-to-view: '&e Tıkla &7 görüntülemek için.' + click-to-previous: '&e Tıkla &7 önceki sayfayı görüntülemek için.' + click-to-next: '&e Tıkla &7 sonraki sayfayı görüntülemek için.' + click-to-select: '&e Tıkla &7 seçmek için.' + left-click-to-cycle-up: '&e Sol Tıklama &7 yukarı kaydırmak için.' + right-click-to-cycle-down: '&e Sağ Tıklama &7 aşağı kaydırmak için.' + left-click-to-change: '&e Sol Tıklama &7 düzenlemek için.' + right-click-to-clear: '&e Sağ Tıklama &7 temizlemek için.' + click-to-asc: '&e Tıkla &7 artan sıralamak için.' + click-to-desc: '&e Tıkla &7 azalan sıralamak için.' + click-to-warp: '&e Tıkla &7 ışınlanmak için.' + click-to-visit: '&e Tıkla &7 ziyaret etmek için.' + right-click-to-visit: '&e Sağ Tıklama &7 ziyaret etmek için.' diff --git a/src/main/resources/locales/uk.yml b/src/main/resources/locales/uk.yml new file mode 100644 index 0000000..964046c --- /dev/null +++ b/src/main/resources/locales/uk.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "показати ТОП Десять AOneBlock" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Кількість [count]" + + +topblock: + gui: + titles: + top: "&0&l Кращі острови" + detail-panel: "&0&l Острів [name]" + value-panel: "&0&l Цінності блоків" + buttons: + island: + empty: '&f&l [name]. місце' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: 'Острів [player]' + # Text for [owner] in description. + owner: '&7&l Власник: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Учасники:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "невідомо" + # Section for parsing [place] + place: '&7&o [number]. &r&7 місце' + # Section for parsing [count] + count: '&7 Кількість блоків: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Загальна кількість: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Попередня сторінка' + description: |- + &7 Перейти на сторінку [number] + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Наступна сторінка' + description: |- + &7 Перейти на сторінку [number] + tips: + click-to-view: '&e Клікніть &7 щоб переглянути.' + click-to-previous: '&e Клікніть &7 щоб переглянути попередню сторінку.' + click-to-next: '&e Клікніть &7 щоб переглянути наступну сторінку.' + click-to-select: '&e Клікніть &7 щоб вибрати.' + left-click-to-cycle-up: '&e Лівий клік &7 прокрутити вгору.' + right-click-to-cycle-down: '&e Правий клік &7 прокрутити вниз.' + left-click-to-change: '&e Лівий клік &7 для редагування.' + right-click-to-clear: '&e Правий клік &7 щоб очистити.' + click-to-asc: '&e Клікніть &7 щоб сортувати за зростанням.' + click-to-desc: '&e Клікніть &7 щоб сортувати за спаданням.' + click-to-warp: '&e Клікніть &7 для телепортації.' + click-to-visit: '&e Клікніть &7 щоб відвідати.' + right-click-to-visit: '&e Правий клік &7 щоб відвідати.' diff --git a/src/main/resources/locales/vi.yml b/src/main/resources/locales/vi.yml new file mode 100644 index 0000000..cea9754 --- /dev/null +++ b/src/main/resources/locales/vi.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "hiển thị Top Mười AOneBlock" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Đếm [count]" + + +topblock: + gui: + titles: + top: "&0&l Đảo Hàng Đầu" + detail-panel: "&0&l Đảo của [name]" + value-panel: "&0&l Giá trị Khối" + buttons: + island: + empty: '&f&l [name]. vị trí' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: 'Đảo của [player]' + # Text for [owner] in description. + owner: '&7&l Chủ: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l Thành viên:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "không rõ" + # Section for parsing [place] + place: '&7&o [number]. &r&7 vị trí' + # Section for parsing [count] + count: '&7 Số Khối: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 Tổng Số Khối: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l Trang Trước' + description: |- + &7 Chuyển đến trang [number] + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l Trang Tiếp' + description: |- + &7 Chuyển đến trang [number] + tips: + click-to-view: '&e Nhấp &7 để xem.' + click-to-previous: '&e Nhấp &7 để xem trang trước.' + click-to-next: '&e Nhấp &7 để xem trang tiếp theo.' + click-to-select: '&e Nhấp &7 để chọn.' + left-click-to-cycle-up: '&e Nhấp Trái &7 để cuộn lên.' + right-click-to-cycle-down: '&e Nhấp Phải &7 để cuộn xuống.' + left-click-to-change: '&e Nhấp Trái &7 để chỉnh sửa.' + right-click-to-clear: '&e Nhấp Phải &7 để xóa.' + click-to-asc: '&e Nhấp &7 để sắp xếp tăng dần.' + click-to-desc: '&e Nhấp &7 để sắp xếp giảm dần.' + click-to-warp: '&e Nhấp &7 để dịch chuyển.' + click-to-visit: '&e Nhấp &7 để thăm.' + right-click-to-visit: '&e Nhấp Phải &7 để thăm.' diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml new file mode 100644 index 0000000..5884449 --- /dev/null +++ b/src/main/resources/locales/zh-CN.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "显示AOneBlock前十名" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b 计数 [count]" + + +topblock: + gui: + titles: + top: "&0&l 顶级岛屿" + detail-panel: "&0&l [name]的岛屿" + value-panel: "&0&l 方块价值" + buttons: + island: + empty: '&f&l 第[name]名' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: '[player]的岛' + # Text for [owner] in description. + owner: '&7&l 拥有者: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l 成员:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "未知" + # Section for parsing [place] + place: '&7&o 第[number]名 &r&7' + # Section for parsing [count] + count: '&7 方块数: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 累计方块数: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l 上一页' + description: |- + &7 切换到第[number]页 + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l 下一页' + description: |- + &7 切换到第[number]页 + tips: + click-to-view: '&e 点击 &7 查看。' + click-to-previous: '&e 点击 &7 查看上一页。' + click-to-next: '&e 点击 &7 查看下一页。' + click-to-select: '&e 点击 &7 选择。' + left-click-to-cycle-up: '&e 左键点击 &7 向上翻。' + right-click-to-cycle-down: '&e 右键点击 &7 向下翻。' + left-click-to-change: '&e 左键点击 &7 编辑。' + right-click-to-clear: '&e 右键点击 &7 清除。' + click-to-asc: '&e 点击 &7 按升序排序。' + click-to-desc: '&e 点击 &7 按降序排序。' + click-to-warp: '&e 点击 &7 传送。' + click-to-visit: '&e 点击 &7 访问。' + right-click-to-visit: '&e 右键点击 &7 访问。' diff --git a/src/main/resources/locales/zh-HK.yml b/src/main/resources/locales/zh-HK.yml new file mode 100644 index 0000000..1cb9ee3 --- /dev/null +++ b/src/main/resources/locales/zh-HK.yml @@ -0,0 +1,69 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +island: + topblock: + description: "顯示AOneBlock前十名" + gui-title: "&a Top Ten" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b 計數 [count]" + + +topblock: + gui: + titles: + top: "&0&l 頂級島嶼" + detail-panel: "&0&l [name]的島嶼" + value-panel: "&0&l 方塊價值" + buttons: + island: + empty: '&f&l 第[name]名' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [count] + [lifetime] + # Text that is replacing [name] if island do not have a name + owners-island: '[player]的島' + # Text for [owner] in description. + owner: '&7&l 擁有者: &r&b [player]' + # Title before listing members for [members] in description + members-title: '&7&l 成員:' + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "未知" + # Section for parsing [place] + place: '&7&o 第[number]名 &r&7' + # Section for parsing [count] + count: '&7 方塊數: &o [number]' + # Section for parsing [lifetime] + lifetime: '&7 累計方塊數: &o [number]' + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: '&f&l 上一頁' + description: |- + &7 切換到第[number]頁 + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: '&f&l 下一頁' + description: |- + &7 切換到第[number]頁 + tips: + click-to-view: '&e 點擊 &7 查看。' + click-to-previous: '&e 點擊 &7 查看上一頁。' + click-to-next: '&e 點擊 &7 查看下一頁。' + click-to-select: '&e 點擊 &7 選擇。' + left-click-to-cycle-up: '&e 左鍵點擊 &7 向上翻。' + right-click-to-cycle-down: '&e 右鍵點擊 &7 向下翻。' + left-click-to-change: '&e 左鍵點擊 &7 編輯。' + right-click-to-clear: '&e 右鍵點擊 &7 清除。' + click-to-asc: '&e 點擊 &7 按升序排序。' + click-to-desc: '&e 點擊 &7 按降序排序。' + click-to-warp: '&e 點擊 &7 傳送。' + click-to-visit: '&e 點擊 &7 訪問。' + right-click-to-visit: '&e 右鍵點擊 &7 訪問。' From 915cfe59d0f46b0b7d6d07de5ce98108cd32affd Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 25 Apr 2026 22:25:16 -0700 Subject: [PATCH 9/9] Fix JaCoCo coverage not reaching SonarCloud Surefire's hardcoded block was replacing the ${argLine} property entirely, so JaCoCo's prepare-agent javaagent flag was discarded and jacoco.exec stayed empty. Prepend @{argLine} (late- binding syntax, evaluated after prepare-agent sets it) so both the JaCoCo agent and the --add-opens flags are active at test time. Co-Authored-By: Claude Sonnet 4.6 --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 40b67a5..2441281 100644 --- a/pom.xml +++ b/pom.xml @@ -268,6 +268,7 @@ **/*Test??.java + @{argLine} --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED