Skip to content

Commit ecfce43

Browse files
authored
Merge pull request #364 from Zarkness/develop
2 parents 6f7343d + 1f190e6 commit ecfce43

File tree

1 file changed

+239
-60
lines changed

1 file changed

+239
-60
lines changed

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

Lines changed: 239 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,28 @@
77
import java.util.UUID;
88
import java.util.stream.Collectors;
99

10+
import org.bukkit.Keyed;
11+
import org.bukkit.NamespacedKey;
12+
import org.bukkit.Registry;
1013
import org.bukkit.World;
1114
import org.bukkit.Material;
15+
import org.bukkit.block.Block;
16+
import org.bukkit.block.BlockState;
17+
import org.bukkit.block.CreatureSpawner;
18+
import org.bukkit.entity.EntityType;
19+
import org.bukkit.inventory.ItemStack;
20+
import org.bukkit.inventory.meta.BlockStateMeta;
21+
import org.bukkit.inventory.meta.ItemMeta;
1222
import org.eclipse.jdt.annotation.Nullable;
1323
import org.bukkit.Bukkit;
24+
import org.bukkit.persistence.PersistentDataContainer;
25+
import org.bukkit.persistence.PersistentDataType;
1426

1527
import world.bentobox.bentobox.BentoBox;
1628
import world.bentobox.bentobox.api.addons.GameModeAddon;
1729
import world.bentobox.bentobox.api.user.User;
1830
import world.bentobox.bentobox.database.objects.Island;
31+
import world.bentobox.bentobox.hooks.ItemsAdderHook;
1932
import world.bentobox.bentobox.managers.PlaceholdersManager;
2033
import world.bentobox.bentobox.managers.RanksManager;
2134
import world.bentobox.level.objects.IslandLevels;
@@ -101,81 +114,77 @@ protected void registerPlaceholders(GameModeAddon gm) {
101114
u -> getRankValue(gm.getOverWorld(), u));
102115

103116
// Register mainhand placeholders
104-
bpm.registerPlaceholder(addon,
105-
gm.getDescription().getName().toLowerCase() + "_island_value_mainhand",
117+
bpm.registerPlaceholder(addon,gm.getDescription().getName().toLowerCase() + "_island_value_mainhand",
106118
user -> {
107-
if (user.getPlayer() == null || !user.getPlayer().getInventory().getItemInMainHand().getType().isBlock()) {
108-
return "0";
109-
}
110-
String blockName = user.getPlayer().getInventory().getItemInMainHand().getType().getKey().getKey();
119+
if (user.getPlayer() == null) return "0";
120+
ItemStack itemInHand = user.getPlayer().getInventory().getItemInMainHand();
121+
Object identifier = getItemIdentifier(itemInHand); // Get EntityType, Material, String, or null
122+
if (identifier == null) return "0";
123+
// BlockConfig.getValue handles EntityType, Material, String correctly
111124
return String.valueOf(Objects.requireNonNullElse(
112-
addon.getBlockConfig().getValue(gm.getOverWorld(), blockName),
113-
0
114-
));
125+
addon.getBlockConfig().getValue(gm.getOverWorld(), identifier), 0));
115126
}
116127
);
117128

118-
bpm.registerPlaceholder(addon,
119-
gm.getDescription().getName().toLowerCase() + "_island_count_mainhand",
129+
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_count_mainhand",
120130
user -> {
121-
if (user.getPlayer() == null || !user.getPlayer().getInventory().getItemInMainHand().getType().isBlock()) {
122-
return "0";
123-
}
124-
Material material = user.getPlayer().getInventory().getItemInMainHand().getType();
125-
return getBlockCount(gm, user, material);
131+
if (user.getPlayer() == null) return "0";
132+
ItemStack itemInHand = user.getPlayer().getInventory().getItemInMainHand();
133+
Object identifier = getItemIdentifier(itemInHand); // Get EntityType, Material, String, or null
134+
if (identifier == null) return "0";
135+
// Pass the actual object identifier to getBlockCount
136+
return getBlockCount(gm, user, identifier);
126137
}
127138
);
128139

129140
// Register looking at block placeholders
130-
bpm.registerPlaceholder(addon,
131-
gm.getDescription().getName().toLowerCase() + "_island_value_looking",
141+
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_value_looking",
132142
user -> {
133143
if (user.getPlayer() == null) return "0";
134-
var targetBlock = user.getPlayer().getTargetBlock(null, 5);
135-
if (targetBlock != null && !targetBlock.getType().isAir()) {
136-
String blockName = targetBlock.getType().getKey().getKey();
144+
Block targetBlock = user.getPlayer().getTargetBlockExact(5);
145+
Object identifier = getBlockIdentifier(targetBlock); // Get EntityType, Material, String, or null
146+
if (identifier == null) return "0";
147+
// BlockConfig.getValue handles EntityType, Material, String correctly
137148
return String.valueOf(Objects.requireNonNullElse(
138-
addon.getBlockConfig().getValue(gm.getOverWorld(), blockName),
139-
0
140-
));
141-
}
142-
return "0";
149+
addon.getBlockConfig().getValue(gm.getOverWorld(), identifier), 0));
143150
}
144151
);
145152

146-
bpm.registerPlaceholder(addon,
147-
gm.getDescription().getName().toLowerCase() + "_island_count_looking",
153+
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_count_looking",
148154
user -> {
149155
if (user.getPlayer() == null) return "0";
150-
var targetBlock = user.getPlayer().getTargetBlock(null, 5);
151-
if (targetBlock != null && !targetBlock.getType().isAir()) {
152-
return getBlockCount(gm, user, targetBlock.getType());
153-
}
154-
return "0";
156+
Block targetBlock = user.getPlayer().getTargetBlockExact(5);
157+
Object identifier = getBlockIdentifier(targetBlock); // Get EntityType, Material, String, or null
158+
if (identifier == null) return "0";
159+
// Pass the actual object identifier to getBlockCount
160+
return getBlockCount(gm, user, identifier);
155161
}
156162
);
157163

158-
// Register placeholders for all block materials
164+
// Register placeholders for all block materials/types from config
159165
if (Bukkit.getServer() != null) {
160-
// Get all materials from the block config
161-
addon.getBlockConfig().getBlockValues().keySet().forEach(blockName -> {
162-
String formattedName = blockName.replace(':', '_').toLowerCase();
166+
// Iterate over the String keys defined in the block config's baseline values
167+
addon.getBlockConfig().getBlockValues().keySet().forEach(configKey -> {
168+
// configKey is a String like "minecraft:stone", "pig_spawner", "itemsadder:my_custom_block"
169+
170+
// Format the key for the placeholder name (e.g., minecraft_stone, pig_spawner)
171+
String placeholderSuffix = configKey.replace(':', '_').replace('.', '_').toLowerCase();
163172

164173
// Register value placeholder
165-
bpm.registerPlaceholder(addon,
166-
gm.getDescription().getName().toLowerCase() + "_island_value_" + formattedName,
174+
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_value_" + placeholderSuffix,
167175
user -> String.valueOf(Objects.requireNonNullElse(
168-
addon.getBlockConfig().getValue(gm.getOverWorld(), blockName),
169-
0
170-
))
176+
// Use the configKey directly, getValue handles String keys
177+
addon.getBlockConfig().getValue(gm.getOverWorld(), configKey), 0))
171178
);
172179

173180
// Register count placeholder
174-
bpm.registerPlaceholder(addon,
175-
gm.getDescription().getName().toLowerCase() + "_island_count_" + formattedName,
181+
bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_count_" + placeholderSuffix,
176182
user -> {
177-
Material material = Material.valueOf(blockName.toUpperCase());
178-
return getBlockCount(gm, user, material);
183+
// Convert the String configKey back to the expected Object type (EntityType, Material, String)
184+
// for IslandLevels lookup.
185+
Object identifier = getObjectFromConfigKey(configKey);
186+
if (identifier == null) return "0";
187+
return getBlockCount(gm, user, identifier);
179188
}
180189
);
181190
});
@@ -304,41 +313,211 @@ String getVisitedIslandLevel(GameModeAddon gm, User user) {
304313
}
305314

306315
/**
307-
* Gets the block count for a specific material in a user's island
316+
* 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.
321+
*/
322+
@Nullable
323+
private Object getBlockIdentifier(@Nullable Block block) {
324+
if (block == null || block.getType().isAir()) return null;
325+
326+
Material type = block.getType();
327+
328+
// Handle Spawners
329+
if (type == Material.SPAWNER) {
330+
BlockState state = block.getState();
331+
if (state instanceof CreatureSpawner) {
332+
CreatureSpawner spawner = (CreatureSpawner) state;
333+
EntityType spawnedType = spawner.getSpawnedType();
334+
if (spawnedType != null) {
335+
return spawnedType; // Return EntityType
336+
}
337+
return Material.SPAWNER; // Return generic spawner material if type unknown
338+
}
339+
return Material.SPAWNER; // Return generic spawner material if state invalid
340+
}
341+
342+
// Fallback to the Material for regular blocks
343+
return type;
344+
}
345+
346+
/**
347+
* 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).
355+
*/
356+
@Nullable
357+
private Object getItemIdentifier(@Nullable ItemStack itemStack) {
358+
if (itemStack == null || itemStack.getType().isAir()) {
359+
return null; // Invalid item
360+
}
361+
362+
Material type = itemStack.getType();
363+
364+
// 1. Handle Spawners
365+
if (type == Material.SPAWNER) {
366+
if (itemStack.hasItemMeta()) {
367+
ItemMeta meta = itemStack.getItemMeta();
368+
EntityType specificType = null; // Variable to store the result
369+
370+
// 1a. Try standard BlockStateMeta method FIRST
371+
if (meta instanceof BlockStateMeta) {
372+
BlockStateMeta blockStateMeta = (BlockStateMeta) meta;
373+
if (blockStateMeta.hasBlockState()) {
374+
BlockState blockState = blockStateMeta.getBlockState();
375+
if (blockState instanceof CreatureSpawner) {
376+
CreatureSpawner spawner = (CreatureSpawner) blockState;
377+
// Get type if standard method works
378+
specificType = spawner.getSpawnedType();
379+
}
380+
}
381+
}
382+
383+
// 1b. If standard method failed (specificType is still null), try reading PDC tag
384+
if (specificType == null && meta != null) { // Check meta != null again just in case
385+
PersistentDataContainer pdc = meta.getPersistentDataContainer();
386+
// Define the key used by SpawnerMeta (and potentially others)
387+
NamespacedKey spawnerMetaTypeKey = new NamespacedKey("spawnermeta", "type");
388+
389+
if (pdc.has(spawnerMetaTypeKey, PersistentDataType.STRING)) {
390+
String entityName = pdc.get(spawnerMetaTypeKey, PersistentDataType.STRING);
391+
if (entityName != null && !entityName.isEmpty()) {
392+
try {
393+
// Convert the name (e.g., "ZOMBIE") to EntityType
394+
// Use uppercase as EntityType names are uppercase enums
395+
specificType = EntityType.valueOf(entityName.toUpperCase());
396+
} catch (IllegalArgumentException e) {
397+
// Keep specificType as null
398+
}
399+
}
400+
}
401+
}
402+
403+
// Return the identified type (from standard or PDC), or null if neither worked
404+
return specificType;
405+
406+
} else {
407+
// No ItemMeta at all for the spawner item
408+
return null;
409+
}
410+
} // End of Spawner handling
411+
412+
// 2. Handle potential custom items (e.g., ItemsAdder)
413+
if (addon.isItemsAdder()) {
414+
Optional<String> customId = ItemsAdderHook.getNamespacedId(itemStack);
415+
if (customId.isPresent()) {
416+
return customId.get(); // Return the String ID from ItemsAdder
417+
}
418+
}
419+
420+
// 3. Fallback to Material for regular items that represent blocks
421+
return type.isBlock() ? type : null;
422+
}
423+
424+
/**
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.
427+
* @param configKey The key string from block config.
428+
* @return EntityType, Material, String identifier, or null if not resolvable.
429+
*/
430+
@Nullable
431+
private Object getObjectFromConfigKey(String configKey) {
432+
if (configKey == null || configKey.isBlank()) {
433+
return null;
434+
}
435+
436+
String lowerCaseKey = configKey.toLowerCase(); // Normalize for checks
437+
438+
// Check if it's a spawner key (e.g., "pig_spawner")
439+
// Ensure it's not the generic "minecraft:spawner" or just "spawner"
440+
if (lowerCaseKey.endsWith("_spawner") && !lowerCaseKey.equals(Material.SPAWNER.getKey().toString()) && !lowerCaseKey.equals("spawner")) {
441+
String entityTypeName = lowerCaseKey.substring(0, lowerCaseKey.length() - "_spawner".length());
442+
// Entity types require namespace in modern MC. Assume minecraft if none provided.
443+
// This might need adjustment if config uses non-namespaced keys for entities.
444+
NamespacedKey entityKey = NamespacedKey.fromString(entityTypeName); // Allow full key like "minecraft:pig"
445+
if (entityKey == null && !entityTypeName.contains(":")) { // If no namespace, assume minecraft
446+
entityKey = NamespacedKey.minecraft(entityTypeName);
447+
}
448+
449+
if (entityKey != null) {
450+
EntityType entityType = Registry.ENTITY_TYPE.get(entityKey);
451+
if (entityType != null) {
452+
return entityType;
453+
}
454+
}
455+
return null; // Cannot resolve
456+
}
457+
458+
// Check if it's a standard Material key (namespaced)
459+
NamespacedKey matKey = NamespacedKey.fromString(lowerCaseKey);
460+
Material material = null;
461+
if (matKey != null) {
462+
material = Registry.MATERIAL.get(matKey);
463+
}
464+
// Add check for non-namespaced legacy material names? Might conflict with custom keys. Risky.
465+
// Example: Material legacyMat = Material.matchMaterial(configKey);
466+
467+
if (material != null) {
468+
return material;
469+
}
470+
471+
// Assume it's a custom String key (e.g., ItemsAdder) if not resolved yet
472+
if (addon.isItemsAdder() && ItemsAdderHook.isInRegistry(configKey)) { // Use original case key for lookup?
473+
return configKey;
474+
}
475+
476+
// Final check: maybe it's the generic "spawner" key from config?
477+
if(lowerCaseKey.equals("spawner")) {
478+
return Material.SPAWNER;
479+
}
480+
return null;
481+
}
482+
483+
/**
484+
* Gets the block count for a specific identifier object in a user's island.
308485
* @param gm GameModeAddon
309486
* @param user User requesting the count
310-
* @param material Material to count
311-
* @return String representation of the count
487+
* @param identifier The identifier object (EntityType, Material, String)
488+
* @return String representation of the count.
312489
*/
313-
private String getBlockCount(GameModeAddon gm, User user, Object material) {
314-
if (user == null) {
490+
private String getBlockCount(GameModeAddon gm, User user, @Nullable Object identifier) {
491+
if (user == null || identifier == null) {
315492
return "0";
316493
}
317-
return getBlockCountForUser(gm, user, material);
494+
return getBlockCountForUser(gm, user, identifier);
318495
}
319496

320497
/**
321-
* Gets the block count for a specific material in a user's island
498+
* 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.
322500
* @param gm GameModeAddon
323501
* @param user User to get count for
324-
* @param material Material to count
325-
* @return String representation of the count
502+
* @param identifier The identifier object (EntityType, Material, String)
503+
* @return String representation of the count.
326504
*/
327-
private String getBlockCountForUser(GameModeAddon gm, User user, Object material) {
328-
// Get the island for the user
505+
private String getBlockCountForUser(GameModeAddon gm, User user, Object identifier) {
329506
Island island = addon.getIslands().getIsland(gm.getOverWorld(), user);
330507
if (island == null) {
331508
return "0";
332509
}
333510

334-
// Get the level data for the island
335511
IslandLevels data = addon.getManager().getLevelsData(island);
336512
if (data == null) {
337513
return "0";
338514
}
339515

340-
// Get the total count from both above sea level and underwater
341-
int count = data.getMdCount().getOrDefault(material, 0) + data.getUwCount().getOrDefault(material, 0);
516+
// Get the count based on the type of the identifier
517+
// Assumes IslandLevels uses EntityType for specific spawners, Material for blocks,
518+
// and potentially String for custom items, based on DetailsPanel and BlockConfig analysis.
519+
int count = data.getMdCount().getOrDefault(identifier, 0) + data.getUwCount().getOrDefault(identifier, 0);
520+
342521
return String.valueOf(count);
343522
}
344523

0 commit comments

Comments
 (0)