diff --git a/README.md b/README.md index be52184..0d30e89 100644 --- a/README.md +++ b/README.md @@ -1,96 +1,106 @@ -# Level +# 🌟 Level Add-on for BentoBox [![Build Status](https://ci.codemc.org/buildStatus/icon?job=BentoBoxWorld/Level)](https://ci.codemc.org/job/BentoBoxWorld/job/Level/)[ ![Bugs](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_Level&metric=bugs)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_Level) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_Level&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_Level) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_Level&metric=ncloc)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_Level) -## About +## 🔍 What is Level? -Add-on for BentoBox to calculate island levels for BentoBox game modes like BSkyBlock and AcidIsland. It counts blocks and assigns a value to them. -Players gain levels by accumulating points and can lose levels too if their points go down. This add-on will work -for game modes listed in the config.yml. +**Level** is the ultimate competition booster for your BentoBox server! Designed for game modes like **BSkyBlock**, **AcidIsland**, and more, this powerful add-on turns your island into a battleground of **block-by-block domination**. -Full documentation for Level can be found at [docs.bentobox.world](https://docs.bentobox.world/en/latest/addons/Level/). +Every block you place counts toward your **island level**—and every block you lose could cost you your spot in the rankings. Whether you're aiming for the top ten or just flexing your creative builds, **Level** adds stakes, strategy, and excitement to your sky-high journey. -Official download releases are at [download.bentobox.world](download.bentobox.world). +📘 [Full Documentation](https://docs.bentobox.world/en/latest/addons/Level/) +📦 [Official Downloads](https://download.bentobox.world) -## How to use +--- -1. Place the level addon jar in the addons folder of the BentoBox plugin -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. The config specifies how much blocks are worth (see below) -5. Restart the server if you make a change +## 🚀 Getting Started -## Upgrading +Ready to level up? Here's how to launch **Level** on your server: -1. Read the release notes carefully, but you may have to delete the old config.yml to use a new one. +1. Drop the **Level** add-on `.jar` into your BentoBox `addons` folder. +2. Restart your server and let the magic happen. +3. A new `Level` data folder and `config.yml` will be created. +4. Open `config.yml` and customize block values, settings, and behavior to suit your game mode. +5. Restart your server again to apply changes. -## Permissions -Permissions are given automatically to players as listed below for BSkyBlock, AcidIsland and CaveBlock. 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. +Now you’re all set—go build something worth leveling for! 🏗️ + +--- + +## 🔄 Upgrading + +When updating, always read the **release notes**! +Some updates might require a fresh `config.yml`, so make backups and review changes carefully. + +--- + +## 🛡️ Permissions + +**Level** integrates directly with your permissions plugin, giving players the tools to compete while letting admins keep control. + +Default permissions for **BSkyBlock**, **AcidIsland**, and **CaveBlock**: ``` permissions: - bskyblock.intopten: - description: Player is in the top ten. + bskyblock.intopten: # Show up in top 10 default: true - bskyblock.island.level: - description: Player can use level command + bskyblock.island.level: # Use /is level default: true - bskyblock.island.top: - description: Player can use top ten command + bskyblock.island.top: # Use /is top default: true - bskyblock.island.value: - description: Player can use value command + bskyblock.island.value: # Use /is value default: true - bskyblock.admin.level: - description: Player can use admin level command + bskyblock.admin.level: # Admin access to /is level default: true - bskyblock.admin.topten: - description: Player can use admin top ten command + bskyblock.admin.topten: # Admin access to /is topten default: true ``` -## Config.yml +⚠️ Players need `intopten` to appear in the leaderboard! -The config.yml has the following sections: +--- -* Game Mode Addon configuration -* General settings -* Limits -* Block values -* Per-world block values +## ⚙️ Configuration: Make It Yours -### Game Mode Addon configuration +The `config.yml` file gives you total control over how leveling works. Here's a breakdown of what you can tweak: -This section allows you to list which game mode add-ons Level should hook into. Use BentoBox's version command to list the official add-on name. +### 🎮 Game Mode Hook +Tell Level which BentoBox game modes it should connect to. -### General Settings +### ⚙️ General Settings +- **Underwater Block Multiplier** – Give bonus points for blocks below sea level. +- **Level Cost** – Set how many points are needed to gain 1 island level. +- **Level Wait** – Add a cooldown between level scans. +- **Death Penalty** – Punish deaths with level loss. +- **Sum Team Deaths** – Choose whether to track team deaths or just the leader's. +- **Reset on Island Reset / Team Join** – Wipe the death count when teams change or islands are reset. -This section defines a number of overall settings for the add-on. +### 🚫 Block Limits +Cap the number of specific blocks that count toward level (e.g., only 200 DIAMOND_BLOCKs count). + +Format: +``` +DIAMOND_BLOCK: 200 +``` -* Underwater block multiplier - default value = 1. If this value is > 1 then blocks below sea level will have a greater value. -* Level cost - Value of one island level. Default 100. Minimum value is 1. -* Level wait - Cool down between level requests in seconds -* Death penalty - How many block values a player will lose per death. Default value of 100 means that for every death, the player will lose 1 level (if levelcost is 100) -* Sum Team Deaths - if true, all the team member deaths are summed. If false, only the leader's deaths counts. -* Max deaths - If player dies more than this, it doesn't count anymore. -* Reset deaths on island reset -* Reset deaths on team join +### 💎 Block Values +Assign point values to blocks to reward rare or hard-to-get materials. -### Limits -This section lists the limits for any particular block. Blocks over this amount are not counted. This limit applies to all game modes and is not world-specific. -Format is MATERIAL: value +Format: +``` +STONE: 1 +DIAMOND_BLOCK: 100 +``` -### Block values -This section lists the value of a block in all game modes (worlds). To specific world-specific values, use the next section. Value must be an integer. Any blocks not listed will have a value of 0. AIR is always zero. -Format is MATERIAL: value. +Blocks not listed are worth **0**. `AIR` is always ignored. -### World-specific block values -List any blocks that have a different value in a specific world. If a block is not listed, the default value will be used from the blocks section. -Prefix with world name. The values will apply to the associated nether and the end if they exist. Example: +### 🌍 World-Specific Values +Customize block values for individual worlds or game modes. -``` +Example: +```yaml worlds: AcidIsland_world: SAND: 0 @@ -98,4 +108,16 @@ worlds: ICE: 0 ``` -In this example, AcidIsland will use the same values as BSkyBlock for all blocks except for sand, sandstone and ice. +In this setup, **AcidIsland** disables points for sand-based blocks while using default values for everything else. + +--- + +## 🏁 Final Words + +**Level** isn’t just a numbers game—it’s a **challenge**, a **competition**, and a **celebration** of creativity. +Whether you're climbing the ranks or just making your mark, Level brings out the best in your builds. + +💡 Need help or want to contribute? Join the community at [bentobox.world](https://bentobox.world) and show us what your island is made of! + +Now go get that top spot. 🌌 +— The BentoBox Team diff --git a/pom.xml b/pom.xml index cebdfea..bd86a15 100644 --- a/pom.xml +++ b/pom.xml @@ -54,8 +54,8 @@ 2.0.9 - 1.21.3-R0.1-SNAPSHOT - 3.3.1-SNAPSHOT + 1.21.5-R0.1-SNAPSHOT + 3.3.1 1.12.0 @@ -67,7 +67,7 @@ -LOCAL - 2.20.0 + 2.21.0 BentoBoxWorld_Level bentobox-world https://sonarcloud.io @@ -247,14 +247,14 @@ com.craftaro UltimateStacker-API - 1.0.0-20240329.173606-35 + 1.0.0-SNAPSHOT provided dev.lone api-itemsadder - 4.0.2-beta-release-11 + 4.0.10 provided diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 0ba0c1b..2851348 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -26,6 +26,7 @@ import world.bentobox.level.commands.AdminSetInitialLevelCommand; import world.bentobox.level.commands.AdminStatsCommand; import world.bentobox.level.commands.AdminTopCommand; +import world.bentobox.level.commands.IslandDetailCommand; import world.bentobox.level.commands.IslandLevelCommand; import world.bentobox.level.commands.IslandTopCommand; import world.bentobox.level.commands.IslandValueCommand; @@ -34,7 +35,6 @@ import world.bentobox.level.listeners.IslandActivitiesListeners; import world.bentobox.level.listeners.JoinLeaveListener; import world.bentobox.level.listeners.MigrationListener; -import world.bentobox.level.objects.LevelsData; import world.bentobox.level.requests.LevelRequestHandler; import world.bentobox.level.requests.TopTenRequestHandler; import world.bentobox.visit.VisitAddon; @@ -249,6 +249,7 @@ private void registerCommands(GameModeAddon gm) { new IslandLevelCommand(this, playerCmd); new IslandTopCommand(this, playerCmd); new IslandValueCommand(this, playerCmd); + new IslandDetailCommand(this, playerCmd); }); } @@ -482,7 +483,7 @@ public Warp getWarpHook() { } public boolean isItemsAdder() { - return getPlugin().getHooks().getHook("ItemsAdder").isPresent(); + return !getSettings().isDisableItemsAdder() && getPlugin().getHooks().getHook("ItemsAdder").isPresent(); } } diff --git a/src/main/java/world/bentobox/level/PlaceholderManager.java b/src/main/java/world/bentobox/level/PlaceholderManager.java index ee56c4c..668969c 100644 --- a/src/main/java/world/bentobox/level/PlaceholderManager.java +++ b/src/main/java/world/bentobox/level/PlaceholderManager.java @@ -7,15 +7,28 @@ import java.util.UUID; import java.util.stream.Collectors; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; import org.bukkit.World; import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.bukkit.inventory.meta.ItemMeta; import org.eclipse.jdt.annotation.Nullable; import org.bukkit.Bukkit; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.hooks.ItemsAdderHook; import world.bentobox.bentobox.managers.PlaceholdersManager; import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.level.objects.IslandLevels; @@ -101,81 +114,77 @@ protected void registerPlaceholders(GameModeAddon gm) { u -> getRankValue(gm.getOverWorld(), u)); // Register mainhand placeholders - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_island_value_mainhand", + bpm.registerPlaceholder(addon,gm.getDescription().getName().toLowerCase() + "_island_value_mainhand", user -> { - if (user.getPlayer() == null || !user.getPlayer().getInventory().getItemInMainHand().getType().isBlock()) { - return "0"; - } - String blockName = user.getPlayer().getInventory().getItemInMainHand().getType().getKey().getKey(); + if (user.getPlayer() == null) return "0"; + ItemStack itemInHand = user.getPlayer().getInventory().getItemInMainHand(); + Object identifier = getItemIdentifier(itemInHand); // Get EntityType, Material, String, or null + if (identifier == null) return "0"; + // BlockConfig.getValue handles EntityType, Material, String correctly return String.valueOf(Objects.requireNonNullElse( - addon.getBlockConfig().getValue(gm.getOverWorld(), blockName), - 0 - )); + addon.getBlockConfig().getValue(gm.getOverWorld(), identifier), 0)); } ); - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_island_count_mainhand", + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_count_mainhand", user -> { - if (user.getPlayer() == null || !user.getPlayer().getInventory().getItemInMainHand().getType().isBlock()) { - return "0"; - } - Material material = user.getPlayer().getInventory().getItemInMainHand().getType(); - return getBlockCount(gm, user, material); + if (user.getPlayer() == null) return "0"; + ItemStack itemInHand = user.getPlayer().getInventory().getItemInMainHand(); + Object identifier = getItemIdentifier(itemInHand); // Get EntityType, Material, String, or null + if (identifier == null) return "0"; + // Pass the actual object identifier to getBlockCount + return getBlockCount(gm, user, identifier); } ); // Register looking at block placeholders - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_island_value_looking", + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_value_looking", user -> { if (user.getPlayer() == null) return "0"; - var targetBlock = user.getPlayer().getTargetBlock(null, 5); - if (targetBlock != null && !targetBlock.getType().isAir()) { - String blockName = targetBlock.getType().getKey().getKey(); + Block targetBlock = user.getPlayer().getTargetBlockExact(5); + Object identifier = getBlockIdentifier(targetBlock); // Get EntityType, Material, String, or null + if (identifier == null) return "0"; + // BlockConfig.getValue handles EntityType, Material, String correctly return String.valueOf(Objects.requireNonNullElse( - addon.getBlockConfig().getValue(gm.getOverWorld(), blockName), - 0 - )); - } - return "0"; + addon.getBlockConfig().getValue(gm.getOverWorld(), identifier), 0)); } ); - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_island_count_looking", + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_count_looking", user -> { if (user.getPlayer() == null) return "0"; - var targetBlock = user.getPlayer().getTargetBlock(null, 5); - if (targetBlock != null && !targetBlock.getType().isAir()) { - return getBlockCount(gm, user, targetBlock.getType()); - } - return "0"; + Block targetBlock = user.getPlayer().getTargetBlockExact(5); + Object identifier = getBlockIdentifier(targetBlock); // Get EntityType, Material, String, or null + if (identifier == null) return "0"; + // Pass the actual object identifier to getBlockCount + return getBlockCount(gm, user, identifier); } ); - // Register placeholders for all block materials + // Register placeholders for all block materials/types from config if (Bukkit.getServer() != null) { - // Get all materials from the block config - addon.getBlockConfig().getBlockValues().keySet().forEach(blockName -> { - String formattedName = blockName.replace(':', '_').toLowerCase(); + // Iterate over the String keys defined in the block config's baseline values + addon.getBlockConfig().getBlockValues().keySet().forEach(configKey -> { + // configKey is a String like "minecraft:stone", "pig_spawner", "itemsadder:my_custom_block" + + // Format the key for the placeholder name (e.g., minecraft_stone, pig_spawner) + String placeholderSuffix = configKey.replace(':', '_').replace('.', '_').toLowerCase(); // Register value placeholder - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_island_value_" + formattedName, + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_value_" + placeholderSuffix, user -> String.valueOf(Objects.requireNonNullElse( - addon.getBlockConfig().getValue(gm.getOverWorld(), blockName), - 0 - )) + // Use the configKey directly, getValue handles String keys + addon.getBlockConfig().getValue(gm.getOverWorld(), configKey), 0)) ); // Register count placeholder - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_island_count_" + formattedName, + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_count_" + placeholderSuffix, user -> { - Material material = Material.valueOf(blockName.toUpperCase()); - return getBlockCount(gm, user, material); + // Convert the String configKey back to the expected Object type (EntityType, Material, String) + // for IslandLevels lookup. + Object identifier = getObjectFromConfigKey(configKey); + if (identifier == null) return "0"; + return getBlockCount(gm, user, identifier); } ); }); @@ -304,41 +313,211 @@ String getVisitedIslandLevel(GameModeAddon gm, User user) { } /** - * Gets the block count for a specific material in a user's island + * Gets the most specific identifier object for a block. + * NOTE: Does not currently support getting custom block IDs (e.g., ItemsAdder) + * directly from the Block object due to hook limitations. + * @param block The block + * @return EntityType, Material, or null if air/invalid. + */ + @Nullable + private Object getBlockIdentifier(@Nullable Block block) { + if (block == null || block.getType().isAir()) return null; + + Material type = block.getType(); + + // Handle Spawners + if (type == Material.SPAWNER) { + BlockState state = block.getState(); + if (state instanceof CreatureSpawner) { + CreatureSpawner spawner = (CreatureSpawner) state; + EntityType spawnedType = spawner.getSpawnedType(); + if (spawnedType != null) { + return spawnedType; // Return EntityType + } + return Material.SPAWNER; // Return generic spawner material if type unknown + } + return Material.SPAWNER; // Return generic spawner material if state invalid + } + + // Fallback to the Material for regular blocks + return type; + } + + /** + * Gets the most specific identifier object for an ItemStack. + * Prioritizes standard Bukkit methods for spawners. + * Adds support for reading "spawnermeta:type" NBT tag via PDC. + * Returns null for spawners if the specific type cannot be determined. + * Supports ItemsAdder items. + * @param itemStack The ItemStack + * @return EntityType, Material (for standard blocks), String (for custom items), + * or null (if air, invalid, or unidentified spawner). + */ + @Nullable + private Object getItemIdentifier(@Nullable ItemStack itemStack) { + if (itemStack == null || itemStack.getType().isAir()) { + return null; // Invalid item + } + + Material type = itemStack.getType(); + + // 1. Handle Spawners + if (type == Material.SPAWNER) { + if (itemStack.hasItemMeta()) { + ItemMeta meta = itemStack.getItemMeta(); + EntityType specificType = null; // Variable to store the result + + // 1a. Try standard BlockStateMeta method FIRST + if (meta instanceof BlockStateMeta) { + BlockStateMeta blockStateMeta = (BlockStateMeta) meta; + if (blockStateMeta.hasBlockState()) { + BlockState blockState = blockStateMeta.getBlockState(); + if (blockState instanceof CreatureSpawner) { + CreatureSpawner spawner = (CreatureSpawner) blockState; + // Get type if standard method works + specificType = spawner.getSpawnedType(); + } + } + } + + // 1b. If standard method failed (specificType is still null), try reading PDC tag + if (specificType == null && meta != null) { // Check meta != null again just in case + PersistentDataContainer pdc = meta.getPersistentDataContainer(); + // Define the key used by SpawnerMeta (and potentially others) + NamespacedKey spawnerMetaTypeKey = new NamespacedKey("spawnermeta", "type"); + + if (pdc.has(spawnerMetaTypeKey, PersistentDataType.STRING)) { + String entityName = pdc.get(spawnerMetaTypeKey, PersistentDataType.STRING); + if (entityName != null && !entityName.isEmpty()) { + try { + // Convert the name (e.g., "ZOMBIE") to EntityType + // Use uppercase as EntityType names are uppercase enums + specificType = EntityType.valueOf(entityName.toUpperCase()); + } catch (IllegalArgumentException e) { + // Keep specificType as null + } + } + } + } + + // Return the identified type (from standard or PDC), or null if neither worked + return specificType; + + } else { + // No ItemMeta at all for the spawner item + return null; + } + } // End of Spawner handling + + // 2. Handle potential custom items (e.g., ItemsAdder) + if (addon.isItemsAdder()) { + Optional customId = ItemsAdderHook.getNamespacedId(itemStack); + if (customId.isPresent()) { + return customId.get(); // Return the String ID from ItemsAdder + } + } + + // 3. Fallback to Material for regular items that represent blocks + return type.isBlock() ? type : null; + } + + /** + * Helper method to convert a String key from the config (e.g., "pig_spawner", "minecraft:stone") + * back into the corresponding Object (EntityType, Material, String) used by IslandLevels. + * @param configKey The key string from block config. + * @return EntityType, Material, String identifier, or null if not resolvable. + */ + @Nullable + private Object getObjectFromConfigKey(String configKey) { + if (configKey == null || configKey.isBlank()) { + return null; + } + + String lowerCaseKey = configKey.toLowerCase(); // Normalize for checks + + // Check if it's a spawner key (e.g., "pig_spawner") + // Ensure it's not the generic "minecraft:spawner" or just "spawner" + if (lowerCaseKey.endsWith("_spawner") && !lowerCaseKey.equals(Material.SPAWNER.getKey().toString()) && !lowerCaseKey.equals("spawner")) { + String entityTypeName = lowerCaseKey.substring(0, lowerCaseKey.length() - "_spawner".length()); + // Entity types require namespace in modern MC. Assume minecraft if none provided. + // This might need adjustment if config uses non-namespaced keys for entities. + NamespacedKey entityKey = NamespacedKey.fromString(entityTypeName); // Allow full key like "minecraft:pig" + if (entityKey == null && !entityTypeName.contains(":")) { // If no namespace, assume minecraft + entityKey = NamespacedKey.minecraft(entityTypeName); + } + + if (entityKey != null) { + EntityType entityType = Registry.ENTITY_TYPE.get(entityKey); + if (entityType != null) { + return entityType; + } + } + return null; // Cannot resolve + } + + // Check if it's a standard Material key (namespaced) + NamespacedKey matKey = NamespacedKey.fromString(lowerCaseKey); + Material material = null; + if (matKey != null) { + material = Registry.MATERIAL.get(matKey); + } + // Add check for non-namespaced legacy material names? Might conflict with custom keys. Risky. + // Example: Material legacyMat = Material.matchMaterial(configKey); + + if (material != null) { + return material; + } + + // Assume it's a custom String key (e.g., ItemsAdder) if not resolved yet + if (addon.isItemsAdder() && ItemsAdderHook.isInRegistry(configKey)) { // Use original case key for lookup? + return configKey; + } + + // Final check: maybe it's the generic "spawner" key from config? + if(lowerCaseKey.equals("spawner")) { + return Material.SPAWNER; + } + return null; + } + + /** + * Gets the block count for a specific identifier object in a user's island. * @param gm GameModeAddon * @param user User requesting the count - * @param material Material to count - * @return String representation of the count + * @param identifier The identifier object (EntityType, Material, String) + * @return String representation of the count. */ - private String getBlockCount(GameModeAddon gm, User user, Object material) { - if (user == null) { + private String getBlockCount(GameModeAddon gm, User user, @Nullable Object identifier) { + if (user == null || identifier == null) { return "0"; } - return getBlockCountForUser(gm, user, material); + return getBlockCountForUser(gm, user, identifier); } /** - * Gets the block count for a specific material in a user's island + * Gets the block count for a specific identifier object from IslandLevels. + * This now correctly uses EntityType or Material as keys based on `DetailsPanel`'s logic. * @param gm GameModeAddon * @param user User to get count for - * @param material Material to count - * @return String representation of the count + * @param identifier The identifier object (EntityType, Material, String) + * @return String representation of the count. */ - private String getBlockCountForUser(GameModeAddon gm, User user, Object material) { - // Get the island for the user + private String getBlockCountForUser(GameModeAddon gm, User user, Object identifier) { Island island = addon.getIslands().getIsland(gm.getOverWorld(), user); if (island == null) { return "0"; } - // Get the level data for the island IslandLevels data = addon.getManager().getLevelsData(island); if (data == null) { return "0"; } - // Get the total count from both above sea level and underwater - int count = data.getMdCount().getOrDefault(material, 0) + data.getUwCount().getOrDefault(material, 0); + // Get the count based on the type of the identifier + // Assumes IslandLevels uses EntityType for specific spawners, Material for blocks, + // and potentially String for custom items, based on DetailsPanel and BlockConfig analysis. + int count = data.getMdCount().getOrDefault(identifier, 0) + data.getUwCount().getOrDefault(identifier, 0); + return String.valueOf(count); } diff --git a/src/main/java/world/bentobox/level/commands/IslandDetailCommand.java b/src/main/java/world/bentobox/level/commands/IslandDetailCommand.java new file mode 100644 index 0000000..a24f632 --- /dev/null +++ b/src/main/java/world/bentobox/level/commands/IslandDetailCommand.java @@ -0,0 +1,39 @@ +package world.bentobox.level.commands; + +import java.util.List; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.level.Level; +import world.bentobox.level.panels.DetailsPanel; + + +public class IslandDetailCommand extends CompositeCommand { + + private final Level addon; + + public IslandDetailCommand(Level addon, CompositeCommand parent) { + super(parent, "detail"); + this.addon = addon; + } + + @Override + public void setup() { + setPermission("island.detail"); + setDescription("island.detail.description"); + setOnlyPlayer(true); + } + + @Override + public boolean execute(User user, String label, List list) { + Island island = getIslands().getIsland(getWorld(), user); + if (island == null) { + user.sendMessage("general.errors.player-has-no-island"); + return false; + + } + DetailsPanel.openPanel(this.addon, getWorld(), user); + return true; + } +} diff --git a/src/main/java/world/bentobox/level/config/ConfigSettings.java b/src/main/java/world/bentobox/level/config/ConfigSettings.java index e865f47..f5b16b3 100644 --- a/src/main/java/world/bentobox/level/config/ConfigSettings.java +++ b/src/main/java/world/bentobox/level/config/ConfigSettings.java @@ -21,6 +21,12 @@ public class ConfigSettings implements ConfigObject { @ConfigEntry(path = "disabled-game-modes") private List gameModes = Collections.emptyList(); + @ConfigComment("") + @ConfigComment("Disable ItemsAdder support") + @ConfigComment("This will ignore ItemsAdder even if it is installed and not use it. Do not set this to true unless you know what you are doing.") + @ConfigEntry(path = "disabled-itemsadder") + private boolean disableItemsAdder = false; + @ConfigComment("") @ConfigComment("When executing level command from console, should a report be shown?") @ConfigEntry(path = "log-report-to-console") @@ -487,4 +493,18 @@ public String getTera() { public void setTera(String tera) { this.tera = tera; } + + /** + * @return the disableItemsAdder + */ + public boolean isDisableItemsAdder() { + return disableItemsAdder; + } + + /** + * @param disableItemsAdder the disableItemsAdder to set + */ + public void setDisableItemsAdder(boolean disableItemsAdder) { + this.disableItemsAdder = disableItemsAdder; + } } diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml index c8b35ec..ac03c0b 100755 --- a/src/main/resources/addon.yml +++ b/src/main/resources/addon.yml @@ -2,7 +2,7 @@ name: Level main: world.bentobox.level.Level version: ${version}${build.number} icon: DIAMOND -api-version: 3.3.0 +api-version: 2.4.0 authors: tastybento @@ -13,6 +13,9 @@ permissions: '[gamemode].island.level': description: Player can use level command default: true + '[gamemode].island.detail': + description: Player can use island detail command + default: true '[gamemode].island.top': description: Player can use top ten command default: true diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 4b98666..11aacec 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -5,6 +5,10 @@ # Level will NOT hook into these game mode addons. disabled-game-modes: [] # +# Disable ItemsAdder support +# This will ignore ItemsAdder even if it is installed and not use it. Do not set this to true unless you know what you are doing. +disabled-itemsadder: false +# # When executing level command from console, should a report be shown? log-report-to-console: true # @@ -86,4 +90,4 @@ include-shulkers-in-chest: false # # Disables hooking with other plugins. # Example: disabled-plugin-hooks: [UltimateStacker, RoseStacker] -disabled-plugin-hooks: [] \ No newline at end of file +disabled-plugin-hooks: [] diff --git a/src/main/resources/locales/cs.yml b/src/main/resources/locales/cs.yml index ea66a57..cb9bcef 100644 --- a/src/main/resources/locales/cs.yml +++ b/src/main/resources/locales/cs.yml @@ -1,42 +1,194 @@ -########################################################################################### -# 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 # -# # -# Translation by: CZghost # -########################################################################################### - admin: level: - parameters: "" - description: "vypočítat úroveň ostrova hráče" + parameters: + description: vypočítat úroveň ostrova hráče + sethandicap: + parameters: [+/-] + description: | + Nastavte nebo změňte ostrov *handicap* + např. +10 odstraní 10 úrovní, + 30 nastaví handicap na 30, + -20 přidá 20 úrovní + changed: '&a Počáteční ostrovní handicap se změnil z [number] na [new_number].' + invalid-level: '&c Neplatný handicap. Použijte celé číslo.' + levelstatus: + description: Ukažte, kolik ostrovů je ve frontě pro skenování + islands-in-queue: '&a Ostrovy ve frontě: [number]' top: - description: "ukázat seznam TOP 10" - unknown-world: "&cNeznámý svět!" - display: "&f[rank]. &a[name] &7- &b[level]" + description: ukázat seznam TOP 10 + unknown-world: '&cNeznámý svět!' + display: '&f[rank]. &a[name] &7- &b[level]' remove: - description: "odstranit hráče z TOP 10" - parameters: "" - + description: odstranit hráče z TOP 10 + parameters: + stats: + description: Zobrazit statistiky na ostrovech na tomto serveru + title: Statistiky serveru + world: '&a [name]' + no-data: '&c Žádná data pro zpracování.' + average-level: 'Průměrná úroveň ostrova: [number]' + median-level: 'Střední úroveň ostrova: [number]' + mode-level: 'Úroveň ostrova režimu: [number]' + highest-level: 'Nejvyšší úroveň ostrova: [number]' + lowest-level: 'Nejnižší úroveň ostrova: [number]' + distribution: 'Distribuce úrovně ostrova:' + islands: ostrovy island: - level: - parameters: "[player]" - description: "spočítat úroveň tvého ostrova nebo ostrova hráče [player]" - calculating: "&aPočítám úroveň..." - island-level-is: "&aÚroveň ostrova je &b[level]" - required-points-to-next-level: "&a[points] vyžadováno do další úrovně" - deaths: "&c([number] smrtí)" - cooldown: "&cMusíš čekat &b[time] &csekund, než můžeš příkaz znovu použít" - + level: + parameters: '[player]' + description: spočítat úroveň tvého ostrova nebo ostrova hráče [player] + calculating: '&aPočítám úroveň...' + estimated-wait: '&a Odhadované čekání: [number] sekundy' + in-queue: '&a Jste číslo [number] ve frontě' + island-level-is: '&aÚroveň ostrova je &b[level]' + required-points-to-next-level: '&a[points] vyžadováno do další úrovně' + deaths: '&c([number] smrtí)' + cooldown: '&cMusíš čekat &b[time] &csekund, než můžeš příkaz znovu použít' + in-progress: '&6 Probíhá výpočet úrovně ostrova ...' + time-out: '&c Výpočet úrovně trval příliš dlouho. Zkuste to prosím znovu později.' top: - description: "ukázat TOP 10" - gui-title: "&aTOP 10" - gui-heading: "&6[name]: &B[rank]" - island-level: "&BÚroveň [level]" - warp-to: "&AWarp na ostrov [name]" - - value: - description: "ukáže hodnotu jakéhokoliv bloku" - success: "&7Hodnota tohoto bloku je: &e[value]" - success-underwater: "&7Hodnota tohoto bloku pod úrovní moře: &e[value]" - empty-hand: "&cNemáš v ruce žádný blok" - no-value: "&cTento předmět nemá žádnou hodnotu." \ No newline at end of file + description: ukázat TOP 10 + gui-title: '&aTOP 10' + gui-heading: '&6[name]: &B[rank]' + island-level: '&BÚroveň [level]' + warp-to: '&AWarp na ostrov [name]' + level-details: + above-sea-level-blocks: Nad bloky hladiny moře + spawners: Spawners + underwater-blocks: Podvodové bloky + all-blocks: Všechny bloky + no-island: '&c Žádný ostrov!' + names-island: '[name]''s Island' + syntax: '[name] x [number]' + hint: '&c Spustit úroveň a zobrazit zprávu bloku' +level: + commands: + value: + parameters: '[hand |]' + description: >- + ukazuje hodnotu bloků. Na konci přidejte „ruku“ a zobrazíte hodnotu pro + položku v ruce. + gui: + titles: + top: '&0&l Nejlepší ostrovy' + detail-panel: '&0&l [name]''s Island' + value-panel: '&0&l Blokové hodnoty' + buttons: + island: + empty: '&f&l [name]. místo' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [level] + owners-island: '[player]''s Island' + owner: '&7&l Majitel: &r&b [player]' + members-title: '&7&l Členové:' + member: '&b - [player]' + unknown: neznámý + place: '&7&o [number]. &r&7 místo' + level: '&7 Úroveň:&o [number]' + material: + name: '&f&l [number] x [material]' + description: |- + [description] + [count] + [value] + [calculated] + [limit] + [id] + id: '&7 Blok ID: &e [id]' + value: '&7 Hodnota bloku:&e [number]' + limit: '&7 Limit bloku:&e [number]' + count: '&7 Počet bloků:&e [number]' + calculated: '&7 Vypočítaná hodnota:&e [number]' + value_blocks: + name: '&f&l Všechny bloky s hodnotou' + description: |- + &7 Zobrazit všechny bloky + &7 s hodnotou na ostrově. + all_blocks: + name: '&f&l Všechny bloky' + description: |- + &7 Zobrazit všechny bloky + &7 na ostrově. + above_sea_level: + name: '&f&l Bloky nad hladinou moře' + description: |- + &7 Zobrazit pouze bloky + &7 které jsou nad mořem + &7 úroveň. + underwater: + name: '&f&l Blocks Under Sea level' + description: |- + &7 Zobrazit pouze bloky + &7 to je níže moře + &7 úroveň. + spawner: + name: '&f&l Spawners' + description: '&7 Displej pouze tření.' + block-name: '&b Spawner' + filters: + name: + name: '&f&l Sort by Name' + description: '&7 Sort all blocks by name.' + value: + name: '&f&l Sort by Value' + description: '&7 Sort all blocks by their value.' + count: + name: '&f&l Sort by Count' + description: '&7 Sort all blocks by their amount.' + value: + name: '&f&l [material]' + description: |- + [description] + [value] + [underwater] + [limit] + [id] + id: '&7 Block id: &e [id]' + value: '&7 Hodnota bloku: &e [number]' + underwater: '&7 Hladina moře Bellow: &e [number]' + limit: '&7 Limit bloku: &e [number]' + previous: + name: Předchozí stránka + description: '&7 Přepněte na stránku [number]' + next: + name: Další stránka + description: '&7 Přepněte na stránku [number]' + search: + name: Vyhledávání + description: |- + &7 Hledejte konkrétní + &7 hodnota. + search: '&b Hodnota: [value]' + tips: + click-to-view: '&e Kliknutím zobrazíte.' + click-to-previous: '&e Kliknutím zobrazíte předchozí stránku.' + click-to-next: '&e Kliknutím zobrazíte další stránku.' + click-to-select: '&e Kliknutím vyberte.' + left-click-to-cycle-up: '&e Levé kliknutí na cyklus nahoru.' + right-click-to-cycle-down: '&e Kliknutím pravým tlačítkem je cyklujte dolů.' + left-click-to-change: '&e Levý kliknutí upravte.' + right-click-to-clear: '&e Kliknutím pravým tlačítkem vymažte.' + click-to-asc: '&e Kliknutím třídíte v zvyšování pořadí.' + click-to-desc: '&e Kliknutím třídíte v klesajícím pořadí.' + click-to-warp: '&e Klikněte na warp.' + click-to-visit: '&e Kliknutím navštívíte.' + right-click-to-visit: '&e Kliknutím na návštěvu.' + conversations: + prefix: '&l&6 [BentoBox]: &r' + no-data: '&c Spusťte úroveň a zobrazí se zpráva o bloku.' + cancel-string: zrušit + exit-string: zrušit, ukončit, přestat + write-search: '&e Zadejte hodnotu vyhledávání. (Napište „zrušit“ do ukončení)' + search-updated: Aktualizována hodnota vyhledávání. + cancelled: '&c Konverzace zrušena!' + no-value: '&c Tato položka nemá žádnou hodnotu.' + unknown-item: '&c „[material]“ ve hře neexistuje.' + value: '&7 Hodnota „[material]“ je: &e [value]' + value-underwater: '&7 Hodnota [material] ''pod hladinou moře: &e [value]' + empty-hand: '&c V ruce nejsou žádné bloky' + you-have: '&7 Máte [number] na posledním počtu.' + you-can-place: '&7 Můžete umístit až [number] a nechat je počítat' diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index d11e474..b1bc380 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -51,7 +51,8 @@ island: cooldown: "&c You must wait &b[time] &c seconds until you can do that again" in-progress: "&6 Island level calculation is in progress..." time-out: "&c The level calculation took too long. Please try again later." - + detail: + description: "shows detail of your island blocks" top: description: "show the Top Ten" gui-title: "&a Top Ten" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 5a3b5e3..976e928 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: BentoBox-Level main: world.bentobox.level.LevelPladdon version: ${project.version}${build.number} -api-version: "1.21.4" +api-version: "1.20" authors: [tastybento] contributors: ["The BentoBoxWorld Community"] diff --git a/src/test/java/world/bentobox/level/LevelTest.java b/src/test/java/world/bentobox/level/LevelTest.java index 7fd0f9a..2dd0e87 100644 --- a/src/test/java/world/bentobox/level/LevelTest.java +++ b/src/test/java/world/bentobox/level/LevelTest.java @@ -287,7 +287,7 @@ private static void deleteAll(File file) throws IOException { public void testAllLoaded() { addon.allLoaded(); verify(plugin).log("[Level] Level hooking into BSkyBlock"); - verify(cmd, times(3)).getAddon(); // 3 commands + verify(cmd, times(4)).getAddon(); // 4 commands verify(adminCmd, times(5)).getAddon(); // Five commands // Placeholders verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_island_level"), any());