forked from phenom/healer-man
Release Healer Man 0.1.49
This commit is contained in:
1 parent
39425f2494
commit
612170afd0
91 files changed
+2278
-263
No files matched your search
@@ -70,6 +70,13 @@ export interface AbilityAiHints {
|
||||
| "resurrection"
|
||||
| "control"
|
||||
| "debuff"
|
||||
| "buff"
|
||||
| "resource"
|
||||
| "summon"
|
||||
| "pet"
|
||||
| "movement"
|
||||
| "threat"
|
||||
| "area-damage"
|
||||
)[];
|
||||
readonly priority?: number;
|
||||
}
|
||||
@@ -293,6 +300,9 @@ export interface AbilityDefinition {
|
||||
/** The original client level before Healer Man's starter-kit remapping. */
|
||||
readonly sourceUnlockLevel?: number;
|
||||
readonly ranks?: readonly AbilityRankDefinition[];
|
||||
/** Runtime talent node that must be learned before this ability is available. */
|
||||
readonly talentNodeId?: string;
|
||||
/** Legacy CoA source identifier; `abilityTalentNodeId` normalizes it. */
|
||||
readonly talentEntryId?: number;
|
||||
}
|
||||
|
||||
@@ -953,10 +963,51 @@ export function abilityRankForLevel(
|
||||
?? ability.ranks[Math.min(ability.ranks.length, normalized) - 1]
|
||||
?? ability.ranks[0];
|
||||
}
|
||||
return [...ability.ranks]
|
||||
const eligible = [...ability.ranks]
|
||||
.filter((rank) => rank.level <= playerLevel)
|
||||
.sort((left, right) => right.rank - left.rank)[0]
|
||||
?? ability.ranks[0];
|
||||
.sort((left, right) => right.rank - left.rank);
|
||||
const highestRank = eligible[0]?.rank;
|
||||
if (highestRank === undefined) return ability.ranks[0];
|
||||
const candidates = eligible.filter((rank) => rank.rank === highestRank);
|
||||
if (candidates.length === 1) return candidates[0];
|
||||
// Several Wrath spell-name chains contain both the player-facing rank and a
|
||||
// same-rank triggered child (for example Lightning Shield's retaliation).
|
||||
// Preserve the structural effect family of the chain's root spell so a
|
||||
// triggered damage child cannot replace the executable aura at later ranks.
|
||||
const effectStructure = (effects: readonly AbilityEffect[]): string => effects.map((effect) => (
|
||||
effect.kind === "apply-aura"
|
||||
? [effect.kind, effect.sourceEffectType, effect.sourceAuraType].join(":")
|
||||
: effect.kind
|
||||
)).join("|");
|
||||
const rootStructure = effectStructure(ability.effects);
|
||||
return candidates.find((rank) => effectStructure(rank.effects) === rootStructure) ?? candidates[0];
|
||||
}
|
||||
|
||||
export function abilityTalentNodeId(ability: AbilityDefinition): string | null {
|
||||
return ability.talentNodeId
|
||||
?? (ability.talentEntryId === undefined ? null : `coa-entry-${ability.talentEntryId}`);
|
||||
}
|
||||
|
||||
export function learnedAbilityRank(
|
||||
ability: AbilityDefinition,
|
||||
talentRanks: Readonly<Record<string, number>>,
|
||||
): number | undefined {
|
||||
const nodeId = abilityTalentNodeId(ability);
|
||||
return nodeId ? Math.max(0, Math.trunc(talentRanks[nodeId] ?? 0)) : undefined;
|
||||
}
|
||||
|
||||
export function learnedAbilityForBuild(
|
||||
ability: AbilityDefinition,
|
||||
playerLevel: number,
|
||||
talentRanks: Readonly<Record<string, number>>,
|
||||
): AbilityDefinition | null {
|
||||
if (!isAbilityUnlocked(ability, playerLevel)) return null;
|
||||
const nodeId = abilityTalentNodeId(ability);
|
||||
const learnedRank = learnedAbilityRank(ability, talentRanks);
|
||||
if (nodeId && !learnedRank) return null;
|
||||
// CoA tree entries use the allocated talent rank as the spell rank. Classic
|
||||
// active talents continue to learn their later spell ranks by character level.
|
||||
return abilityAtLevel(ability, playerLevel, ability.talentEntryId === undefined ? undefined : learnedRank);
|
||||
}
|
||||
|
||||
export function abilityAtLevel(
|
||||
@@ -1040,7 +1091,7 @@ export function defaultActionBarForClass(classId: ClassId): readonly string[] {
|
||||
.map(({ ability }) => ability)
|
||||
.filter((ability) => (
|
||||
!ability.passive
|
||||
&& ability.source !== "coa-tree"
|
||||
&& !abilityTalentNodeId(ability)
|
||||
&& !ability.effects.every((effect) => effect.kind === "unsupported" || effect.kind === "scripted")
|
||||
))
|
||||
.map((ability) => ability.id);
|
||||
@@ -1065,6 +1116,7 @@ export function defaultActionBarForCharacter(classId: ClassId, secondaryClassId?
|
||||
.filter((ability) => (
|
||||
!ability.passive
|
||||
&& ability.unlockLevel <= level
|
||||
&& !abilityTalentNodeId(ability)
|
||||
&& !ability.effects.every((effect) => effect.kind === "unsupported" || effect.kind === "scripted")
|
||||
))
|
||||
.sort((left, right) => left.unlockLevel - right.unlockLevel || left.name.localeCompare(right.name))
|
||||
|
||||
Reference in new issue
Block a user