Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
299 changes: 239 additions & 60 deletions src/main/java/world/bentobox/level/PlaceholderManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
);
});
Expand Down Expand Up @@ -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<String> 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);
}

Expand Down
Loading