forked from phenom/healer-man
Fix named talent cooldown scopes (#20)
Preserve named cooldown targets from tooltip clauses and keep named damage and healing effects scoped to their described abilities. Broad spell cooldowns remain broad. Issue: phenom/healer-man#20 Coordinator: Root (/root), session 01a12399-cfab-7c72-8455-e0e4cb7e156c Worker: /root/combat_talents_luna, GPT-6 Luna xhigh Branch: fix/20-named-talent-scope
This commit is contained in:
1 parent
72a67ac1e3
commit
b3e0445e9c
2 files changed
+68
-2
No files matched your search
@@ -223,8 +223,12 @@ export function parseTalentModifiers(description: string): readonly ParsedModifi
|
||||
if (castTime) add({ kind: "cast-time-flat-ms", amount: -Number(castTime[1]) * 1000, unit: "milliseconds", scope: sentence });
|
||||
const cooldown = /\bcasts?\s+reduce(?:s)?\s+(?:the\s+)?cooldown\b/i.test(sentence)
|
||||
? null
|
||||
: sentence.match(/reduces? (?:the )?cooldown (?:of |on )?.*? by (\d+(?:\.\d+)?) sec/i);
|
||||
if (cooldown) add({ kind: "cooldown-flat-ms", amount: -Number(cooldown[1]) * 1000, unit: "milliseconds", scope: sentence });
|
||||
: sentence.match(/\breduces?\s+(?:the\s+)?cooldown(?:\s+(of|on)\s+(.+?))?\s+by\s+(\d+(?:\.\d+)?)\s+secs?\b/i);
|
||||
if (cooldown) {
|
||||
const target = cooldown[2]?.trim();
|
||||
const scope = target ? `cooldown ${cooldown[1]} ${target}` : "cooldown";
|
||||
add({ kind: "cooldown-flat-ms", amount: -Number(cooldown[3]) * 1000, unit: "milliseconds", scope });
|
||||
}
|
||||
}
|
||||
|
||||
return modifiers;
|
||||
@@ -445,6 +449,20 @@ export interface TalentModifierContext {
|
||||
readonly tags?: readonly string[];
|
||||
}
|
||||
|
||||
function namedModifierTarget(scope: string): string | undefined {
|
||||
const scopeWithoutAmount = scope.replace(/\s+by\s+-?\d+(?:\.\d+)?\s*(?:%|secs?)(?:\s*[.!?])?$/i, "");
|
||||
const target = scopeWithoutAmount.match(/\b(?:of|on|with|for|to)\s+(?:your\s+)?([^,.!?]+)$/)?.[1]?.trim();
|
||||
if (!target) return undefined;
|
||||
|
||||
// Generic descriptors describe a category of spells, effects, or targets.
|
||||
// Keep those modifiers broad; remaining words indicate a named ability.
|
||||
const remainingTarget = target.toLowerCase()
|
||||
.replace(/\b(?:a|an|the|your|all|any|each|every|both|other|next|additional|total|maximum|direct|periodic|instant|damaging|healing|helpful|harmful|melee|ranged|magic|physical|arcane|fire|frost|holy|nature|shadow|spell|spells|ability|abilities|attack|attacks|damage|cooldown|cost|cast|casting|time|effect|effects|critical|strike|chance|done|dealt|taken|suffered|received|target|targets|enemy|enemies|ally|allies|friendly|hostile|party|raid|members|you|self|both|and|or|of|on|with|for|to|by)\b/g, " ")
|
||||
.replace(/[^a-z0-9]+/g, " ")
|
||||
.trim();
|
||||
return remainingTarget ? target.toLowerCase() : undefined;
|
||||
}
|
||||
|
||||
function modifierApplies(modifier: TalentModifier, context: TalentModifierContext): boolean {
|
||||
const scope = modifier.scope.toLowerCase();
|
||||
const tags = new Set((context.tags ?? []).map((tag) => tag.toLowerCase()));
|
||||
@@ -469,6 +487,8 @@ function modifierApplies(modifier: TalentModifier, context: TalentModifierContex
|
||||
}
|
||||
if (context.abilityName) {
|
||||
const normalizedName = context.abilityName.toLowerCase();
|
||||
const namedTarget = namedModifierTarget(scope);
|
||||
if (namedTarget && !namedTarget.includes(normalizedName)) return false;
|
||||
const namedScope = /(?:damage|healing|cost|cooldown|cast(?:ing)? time|effect|critical (?:effect |strike )?chance|chance to critically).*?\b(?:by|of|with) your\b/.test(scope);
|
||||
const genericScope = /\byour (?:all |instant |periodic |healing |damage |helpful |harmful )*(?:spells?|abilities?|attacks?)\b/.test(scope)
|
||||
|| /\byour (?:arcane|fire|frost|holy|nature|shadow) spells?\b/.test(scope);
|
||||
|
||||
Reference in new issue
Block a user