diff --git a/src/main/java/kamkeel/npcs/command/AbilityCommand.java b/src/main/java/kamkeel/npcs/command/AbilityCommand.java index f40c266cb..982a975b3 100644 --- a/src/main/java/kamkeel/npcs/command/AbilityCommand.java +++ b/src/main/java/kamkeel/npcs/command/AbilityCommand.java @@ -231,12 +231,12 @@ public class AbilityCommand extends CommandKamkeelBase { } Ability ability = AbilityController.Instance.resolveAbility(abilityKey); - String canonicalKey = abilityKey; - String displayName = abilityKey; - if (ability != null) { - canonicalKey = ability.getId() != null ? ability.getId() : abilityKey; - displayName = ability.getDisplayName(); + if (ability == null) { + sendError(sender, "Ability not found: " + abilityKey); + return; } + String canonicalKey = ability.getId() != null ? ability.getId() : abilityKey; + String displayName = ability.getDisplayName(); PlayerData data = PlayerDataController.Instance.getPlayerData(player); if (!data.abilityData.hasUnlockedAbility(canonicalKey)) { @@ -376,8 +376,8 @@ public class AbilityCommand extends CommandKamkeelBase { // Chained ability String chainName = key.substring(PlayerAbilityData.CHAIN_PREFIX.length()); ChainedAbility chain = AbilityController.Instance.resolveChainedAbility(chainName); - String displayName = chain != null ? chain.getDisplayName() : chainName; - sendResult(sender, prefix + "\u00A7d[Chain] \u00A7b" + displayName); + String display = chain != null ? chain.getDisplayName() : chainName; + sendResult(sender, prefix + "\u00A7d[Chain] \u00A7b" + display); } else { // Regular ability Ability ability = AbilityController.Instance.resolveAbility(key); diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index 7f7dac1c3..44445da04 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -823,6 +823,35 @@ public class AbilityController implements IAbilityHandler { return null; } + /** + * Resolve a chained ability by key and return the live (non-copied) reference. + * For validation/permission checks only — do NOT expose or modify the returned chain. + * Uses the same lookup chain as {@link #resolveChainedAbility(String)}. + */ + public ChainedAbility peekChainedAbility(String key) { + if (key == null || key.isEmpty()) return null; + + ChainedAbility byId = chainedAbilitiesById.get(key); + if (byId != null) return byId; + + ChainedAbility chain = chainedAbilities.get(key); + if (chain != null) return chain; + + for (Map.Entry entry : chainedAbilities.entrySet()) { + if (entry.getKey().equalsIgnoreCase(key)) { + return entry.getValue(); + } + } + + String sanitizedKey = FileNameHelper.sanitizeTextInput(key); + if (!sanitizedKey.equals(key)) { + ChainedAbility sanitized = chainedAbilities.get(sanitizedKey); + if (sanitized != null) return sanitized; + } + + return null; + } + public boolean canResolveChainedAbility(String key) { if (key == null || key.isEmpty()) return false; if (chainedAbilitiesById.containsKey(key)) return true; @@ -1014,19 +1043,37 @@ public class AbilityController implements IAbilityHandler { public Ability peekAbility(String key) { if (key == null || key.isEmpty()) return null; + // Custom: UUID lookup Ability byUuid = customAbilitiesById.get(key); if (byUuid != null) return byUuid; + // Built-in: exact key Ability builtIn = builtAbilities.get(key); if (builtIn != null) return builtIn; - for (Ability ability : builtAbilities.values()) { - if (key.equals(ability.getId())) return ability; + // Built-in: case-insensitive key + for (Map.Entry entry : builtAbilities.entrySet()) { + if (entry.getKey().equalsIgnoreCase(key)) { + return entry.getValue(); + } } + // Built-in: by ID (case-insensitive) + for (Ability ability : builtAbilities.values()) { + if (key.equalsIgnoreCase(ability.getId())) return ability; + } + + // Custom: exact name Ability custom = customAbilities.get(key); if (custom != null) return custom; + // Custom: case-insensitive name + for (Map.Entry entry : customAbilities.entrySet()) { + if (entry.getKey().equalsIgnoreCase(key)) { + return entry.getValue(); + } + } + return null; }