Skip to content

Commit 9f71ece

Browse files
authored
Merge pull request #394 from BentoBoxWorld/391_block_count_limit_placeholder
Add block limit count placeholder
2 parents 5a4e264 + cc9fda1 commit 9f71ece

File tree

2 files changed

+136
-53
lines changed

2 files changed

+136
-53
lines changed

src/main/java/world/bentobox/level/PlaceholderManager.java

Lines changed: 129 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,21 @@
77
import java.util.UUID;
88
import java.util.stream.Collectors;
99

10-
import org.bukkit.Keyed;
10+
import org.bukkit.Bukkit;
11+
import org.bukkit.Material;
1112
import org.bukkit.NamespacedKey;
1213
import org.bukkit.Registry;
1314
import org.bukkit.World;
14-
import org.bukkit.Material;
1515
import org.bukkit.block.Block;
1616
import org.bukkit.block.BlockState;
1717
import org.bukkit.block.CreatureSpawner;
1818
import org.bukkit.entity.EntityType;
1919
import org.bukkit.inventory.ItemStack;
2020
import org.bukkit.inventory.meta.BlockStateMeta;
2121
import org.bukkit.inventory.meta.ItemMeta;
22-
import org.eclipse.jdt.annotation.Nullable;
23-
import org.bukkit.Bukkit;
2422
import org.bukkit.persistence.PersistentDataContainer;
2523
import org.bukkit.persistence.PersistentDataType;
24+
import org.eclipse.jdt.annotation.Nullable;
2625

2726
import world.bentobox.bentobox.BentoBox;
2827
import world.bentobox.bentobox.api.addons.GameModeAddon;
@@ -35,9 +34,12 @@
3534
import world.bentobox.level.objects.TopTenData;
3635

3736
/**
38-
* Handles Level placeholders
39-
*
40-
* @author tastybento
37+
* Handles registration and resolution of Level placeholders for the Level addon.
38+
*
39+
* The class implements:
40+
* - registering placeholders via the BentoBox PlaceholdersManager
41+
* - resolving top-ten and per-island level values
42+
* - mapping blocks/items/spawners to the identifier used by IslandLevels
4143
*
4244
*/
4345
public class PlaceholderManager {
@@ -50,6 +52,23 @@ public PlaceholderManager(Level addon) {
5052
this.plugin = addon.getPlugin();
5153
}
5254

55+
/**
56+
* Register placeholders for a given GameModeAddon.
57+
*
58+
* This method registers a number of placeholders with BentoBox's PlaceholdersManager:
59+
* - island level placeholders (formatted, raw, owner-only)
60+
* - points / points-to-next-level placeholders
61+
* - top-ten placeholders (name, island name, members, level) for ranks 1..10
62+
* - visited island placeholder
63+
* - mainhand & looking placeholders (value and count)
64+
* - dynamic placeholders for each configured block key from the BlockConfig
65+
*
66+
* The registered placeholders call into the Level manager and IslandLevels to fetch
67+
* values. Safety checks are performed so that missing players, islands or data return "0"
68+
* or empty strings rather than throwing exceptions.
69+
*
70+
* @param gm the GameModeAddon for which placeholders are being registered
71+
*/
5372
protected void registerPlaceholders(GameModeAddon gm) {
5473
if (plugin.getPlaceholdersManager() == null)
5574
return;
@@ -170,15 +189,17 @@ protected void registerPlaceholders(GameModeAddon gm) {
170189
// Format the key for the placeholder name (e.g., minecraft_stone, pig_spawner)
171190
String placeholderSuffix = configKey.replace(':', '_').replace('.', '_').toLowerCase();
172191

173-
// Register value placeholder
174-
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_value_" + placeholderSuffix,
192+
// Register value placeholders
193+
String placeholder = gm.getDescription().getName().toLowerCase() + "_island_value_" + placeholderSuffix;
194+
bpm.registerPlaceholder(addon, placeholder,
175195
user -> String.valueOf(Objects.requireNonNullElse(
176196
// Use the configKey directly, getValue handles String keys
177197
addon.getBlockConfig().getValue(gm.getOverWorld(), configKey), 0))
178198
);
179199

180-
// Register count placeholder
181-
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_count_" + placeholderSuffix,
200+
// Register count placeholders
201+
placeholder = gm.getDescription().getName().toLowerCase() + "_island_count_" + placeholderSuffix;
202+
bpm.registerPlaceholder(addon, placeholder,
182203
user -> {
183204
// Convert the String configKey back to the expected Object type (EntityType, Material, String)
184205
// for IslandLevels lookup.
@@ -189,15 +210,27 @@ protected void registerPlaceholders(GameModeAddon gm) {
189210
);
190211
});
191212
}
213+
// Register limit placeholders
214+
addon.getBlockConfig().getBlockLimits().forEach((configKey, configValue) -> {
215+
// Format the key for the placeholder name (e.g., minecraft_stone, pig_spawner)
216+
String placeholderSuffix = configKey.replace(':', '_').replace('.', '_').toLowerCase();
217+
String placeholder = gm.getDescription().getName().toLowerCase() + "_island_limit_" + placeholderSuffix;
218+
bpm.registerPlaceholder(addon, placeholder, user -> String.valueOf(configValue));
219+
});
192220
}
193221

194222
/**
195223
* Get the name of the owner of the island who holds the rank in this world.
196-
*
197-
* @param world world
198-
* @param rank rank 1 to 10
199-
* @param weighted if true, then the weighted rank name is returned
200-
* @return rank name
224+
*
225+
* Behavior / notes:
226+
* - rank is clamped between 1 and Level.TEN
227+
* - when weighted == true, the weighted top-ten is used; otherwise the plain top-ten is used
228+
* - returns an empty string if a rank is not available or owner is null
229+
*
230+
* @param world world to look up the ranking in
231+
* @param rank 1-based rank (will be clamped)
232+
* @param weighted whether to use the weighted top-ten
233+
* @return owner name or empty string
201234
*/
202235
String getRankName(World world, int rank, boolean weighted) {
203236
// Ensure rank is within bounds
@@ -216,12 +249,14 @@ String getRankName(World world, int rank, boolean weighted) {
216249
}
217250

218251
/**
219-
* Get the island name for this rank
220-
*
221-
* @param world world
222-
* @param rank rank 1 to 10
223-
* @param weighted if true, then the weighted rank name is returned
224-
* @return name of island or nothing if there isn't one
252+
* Get the island name for this rank.
253+
*
254+
* Similar behavior to getRankName, but returns the island's name (or empty string).
255+
*
256+
* @param world world to look up the island in
257+
* @param rank 1-based rank (clamped)
258+
* @param weighted whether to use the weighted list
259+
* @return name of island or empty string
225260
*/
226261
String getRankIslandName(World world, int rank, boolean weighted) {
227262
// Ensure rank is within bounds
@@ -237,12 +272,16 @@ String getRankIslandName(World world, int rank, boolean weighted) {
237272
}
238273

239274
/**
240-
* Gets a comma separated string of island member names
241-
*
242-
* @param world world
243-
* @param rank rank to request
244-
* @param weighted if true, then the weighted rank name is returned
245-
* @return comma separated string of island member names
275+
* Gets a comma separated string of island member names for a given ranked island.
276+
*
277+
* - Members are filtered to those at or above RanksManager.MEMBER_RANK.
278+
* - Members are sorted by rank descending for consistent ordering.
279+
* - If the island is missing or has no members, returns an empty string.
280+
*
281+
* @param world world to look up
282+
* @param rank rank in the top-ten (1..10)
283+
* @param weighted whether to use weighted top-ten
284+
* @return comma-separated member names, or empty string
246285
*/
247286
String getRankMembers(World world, int rank, boolean weighted) {
248287
// Ensure rank is within bounds
@@ -269,12 +308,15 @@ String getRankMembers(World world, int rank, boolean weighted) {
269308
}
270309

271310
/**
272-
* Get the level for the rank requested
273-
*
274-
* @param world world
275-
* @param rank rank wanted
276-
* @param weighted true if weighted (level/number of team members)
277-
* @return level for the rank requested
311+
* Get the level for the rank requested.
312+
*
313+
* - Returns a formatted level string using the manager's formatLevel helper.
314+
* - If a value is missing, manager.formatLevel receives null which should handle the fallback.
315+
*
316+
* @param world world to query
317+
* @param rank rank 1..10 (clamped)
318+
* @param weighted whether to fetch weighted level
319+
* @return string representation of the level for the rank
278320
*/
279321
String getRankLevel(World world, int rank, boolean weighted) {
280322
// Ensure rank is within bounds
@@ -288,11 +330,11 @@ String getRankLevel(World world, int rank, boolean weighted) {
288330
}
289331

290332
/**
291-
* Return the rank of the player in a world
292-
*
333+
* Return the rank of the player in a world.
334+
*
293335
* @param world world
294336
* @param user player
295-
* @return rank where 1 is the top rank.
337+
* @return rank where 1 is the top rank as a String; returns empty string for null user
296338
*/
297339
private String getRankValue(World world, User user) {
298340
if (user == null) {
@@ -304,6 +346,13 @@ private String getRankValue(World world, User user) {
304346
.values().stream().filter(l -> l > level).count() + 1);
305347
}
306348

349+
/**
350+
* Return the level for the island the user is currently visiting (if any).
351+
*
352+
* @param gm the GameModeAddon (used to map to the overworld)
353+
* @param user the user to check
354+
* @return island level string for the visited island, or empty/ "0" when not applicable
355+
*/
307356
String getVisitedIslandLevel(GameModeAddon gm, User user) {
308357
if (user == null || !gm.inWorld(user.getWorld()))
309358
return "";
@@ -314,10 +363,16 @@ String getVisitedIslandLevel(GameModeAddon gm, User user) {
314363

315364
/**
316365
* Gets the most specific identifier object for a block.
317-
* NOTE: Does not currently support getting custom block IDs (e.g., ItemsAdder)
318-
* directly from the Block object due to hook limitations.
319-
* @param block The block
320-
* @return EntityType, Material, or null if air/invalid.
366+
*
367+
* The identifier is one of:
368+
* - EntityType for mob spawners (when the spawner block contains a specific spawned type)
369+
* - Material for regular blocks
370+
* - null for air or unknown/invalid blocks
371+
*
372+
* This is used to map the block to the same identifier the BlockConfig and IslandLevels use.
373+
*
374+
* @param block The block to inspect, null-safe
375+
* @return an EntityType or Material, or null for air/unknown
321376
*/
322377
@Nullable
323378
private Object getBlockIdentifier(@Nullable Block block) {
@@ -345,13 +400,22 @@ private Object getBlockIdentifier(@Nullable Block block) {
345400

346401
/**
347402
* Gets the most specific identifier object for an ItemStack.
348-
* Prioritizes standard Bukkit methods for spawners.
349-
* Adds support for reading "spawnermeta:type" NBT tag via PDC.
350-
* Returns null for spawners if the specific type cannot be determined.
351-
* Supports ItemsAdder items.
352-
* @param itemStack The ItemStack
353-
* @return EntityType, Material (for standard blocks), String (for custom items),
354-
* or null (if air, invalid, or unidentified spawner).
403+
*
404+
* This method attempts to:
405+
* 1) Resolve a specific EntityType for spawner items via BlockStateMeta or a PersistentDataContainer key.
406+
* If the exact spawned mob cannot be determined, it returns null for spawner items so counts
407+
* are not incorrectly attributed.
408+
* 2) If ItemsAdder is present, check for custom item Namespaced ID and return it (String).
409+
* 3) Fallback to returning the Material for block-like items, otherwise null for non-blocks.
410+
*
411+
* The return type is one of:
412+
* - EntityType (specific spawner type)
413+
* - Material (normal block-type items)
414+
* - String (custom items IDs like ItemsAdder)
415+
* - null (air, invalid item, or unidentified spawner item)
416+
*
417+
* @param itemStack the item to inspect (may be null)
418+
* @return EntityType, Material, String, or null
355419
*/
356420
@Nullable
357421
private Object getItemIdentifier(@Nullable ItemStack itemStack) {
@@ -422,8 +486,14 @@ private Object getItemIdentifier(@Nullable ItemStack itemStack) {
422486
}
423487

424488
/**
425-
* Helper method to convert a String key from the config (e.g., "pig_spawner", "minecraft:stone")
426-
* back into the corresponding Object (EntityType, Material, String) used by IslandLevels.
489+
* Convert a configuration key string (from the block config) into the identifier object
490+
* used by IslandLevels.
491+
*
492+
* - Handles "pig_spawner" style keys and resolves them to EntityType where possible.
493+
* - Resolves namespaced Material keys using Bukkit's Registry.
494+
* - Returns the original string for custom items (ItemsAdder) when present in registry.
495+
* - Returns Material.SPAWNER for generic "spawner" key, otherwise null if unresolvable.
496+
*
427497
* @param configKey The key string from block config.
428498
* @return EntityType, Material, String identifier, or null if not resolvable.
429499
*/
@@ -482,10 +552,12 @@ private Object getObjectFromConfigKey(String configKey) {
482552

483553
/**
484554
* Gets the block count for a specific identifier object in a user's island.
555+
* This is a thin wrapper that validates inputs and returns "0" when missing.
556+
*
485557
* @param gm GameModeAddon
486558
* @param user User requesting the count
487559
* @param identifier The identifier object (EntityType, Material, String)
488-
* @return String representation of the count.
560+
* @return String representation of the count (zero when not available)
489561
*/
490562
private String getBlockCount(GameModeAddon gm, User user, @Nullable Object identifier) {
491563
if (user == null || identifier == null) {
@@ -496,7 +568,12 @@ private String getBlockCount(GameModeAddon gm, User user, @Nullable Object ident
496568

497569
/**
498570
* Gets the block count for a specific identifier object from IslandLevels.
499-
* This now correctly uses EntityType or Material as keys based on `DetailsPanel`'s logic.
571+
*
572+
* - Fetches the Island for the user and then the IslandLevels data.
573+
* - IslandLevels stores counts in two maps (mdCount and uwCount) depending on how values
574+
* are classified; we add both to provide the complete count.
575+
* - Returns "0" if island or data is unavailable.
576+
*
500577
* @param gm GameModeAddon
501578
* @param user User to get count for
502579
* @param identifier The identifier object (EntityType, Material, String)
@@ -520,5 +597,4 @@ private String getBlockCountForUser(GameModeAddon gm, User user, Object identifi
520597

521598
return String.valueOf(count);
522599
}
523-
524600
}

src/main/java/world/bentobox/level/config/BlockConfig.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,4 +289,11 @@ public Map<World, Map<String, Integer>> getWorldBlockValues() {
289289
return worldBlockValues;
290290
}
291291

292+
/**
293+
* @return the blockLimits
294+
*/
295+
public Map<String, Integer> getBlockLimits() {
296+
return blockLimits;
297+
}
298+
292299
}

0 commit comments

Comments
 (0)