Release Healer Man 0.1.59
This commit is contained in:
1 parent
11934a3979
commit
ab1434a3b8
44 files changed
+1242
-103
No files matched your search
@@ -25,7 +25,7 @@ import {
|
||||
ROGUE_DEADLY_POISON_DOSE_TAG,
|
||||
ROGUE_ENVENOM_BUFF_TAG,
|
||||
} from "./roguePoison";
|
||||
import { talentNodeById } from "./talentCatalog";
|
||||
import { runtimeTalentNodeById } from "./talentRuntimeCatalog";
|
||||
import { baseResourcesFor } from "./wotlkStats";
|
||||
|
||||
export const BASE_CLASS_STARTER_SOURCE_LEVEL = 10;
|
||||
@@ -930,9 +930,7 @@ function classScriptEffects(
|
||||
case "priest:Mind Flay": return [{ kind: "damage", coefficient: 0.9 }];
|
||||
case "priest:Penance": return [
|
||||
{ kind: "heal", coefficient: 0.6 },
|
||||
{ kind: "hot", coefficient: 0.6, ticks: 2, intervalMs: 1_000 },
|
||||
{ kind: "damage", coefficient: 0.6 },
|
||||
{ kind: "dot", coefficient: 0.6, ticks: 2, intervalMs: 1_000, tag: "penance" },
|
||||
];
|
||||
case "priest:Divine Hymn": return [{
|
||||
kind: "heal", coefficient: 1, scope: "party", center: "caster", radius: 40, maximumTargets: 3,
|
||||
@@ -1478,7 +1476,16 @@ function rankDefinition(
|
||||
abilityName: string,
|
||||
): AbilityRankDefinition {
|
||||
const effects = effectsForRank(classId, rank, abilityName);
|
||||
const channeling = rank.channelDurationMs > 0;
|
||||
// Penance's learnable spell is a dummy wrapper; its triggered heal/damage
|
||||
// children own the channel in the client DBC.
|
||||
const penance = classId === "priest" && abilityName === "Penance";
|
||||
const channeling = rank.channelDurationMs > 0 || penance;
|
||||
const channelDurationMs = penance ? 2_000 : rank.channelDurationMs;
|
||||
const channelTickMs = penance ? 1_000 : rank.effects
|
||||
.map((effect) => effect.periodMs)
|
||||
.filter((period) => period > 0)
|
||||
.sort((left, right) => left - right)[0];
|
||||
const castTimeMs = channeling ? Math.max(rank.castTimeMs, channelDurationMs) : rank.castTimeMs;
|
||||
const rankResource = resourceForRank(resource, rank);
|
||||
return {
|
||||
rank: rank.rank || index + 1,
|
||||
@@ -1486,7 +1493,12 @@ function rankDefinition(
|
||||
level: gameplaySourceLevel(classId, rank.level),
|
||||
description: rank.description,
|
||||
castMode: channeling ? "channel" : rank.castTimeMs > 0 ? "cast" : "instant",
|
||||
castTimeMs: channeling ? Math.max(rank.castTimeMs, rank.channelDurationMs) : rank.castTimeMs,
|
||||
castTimeMs,
|
||||
...(channeling && channelTickMs ? { channelTickMs } : {}),
|
||||
...(channeling && channelTickMs
|
||||
? { channelPulses: Math.max(1, Math.round(castTimeMs / channelTickMs) + (penance ? 1 : 0)) }
|
||||
: {}),
|
||||
...(penance ? { channelStartsWithPulse: true } : {}),
|
||||
cooldownMs: rank.cooldownMs,
|
||||
gcdMs: rank.gcdMs,
|
||||
cost: {
|
||||
@@ -1517,17 +1529,41 @@ function abilityDefinition(classId: BaseClassId, chain: Wow335Chain): AbilityDef
|
||||
const sourceRanks = chain.ranks.some((rank) => rank.rank > 0)
|
||||
? chain.ranks.filter((rank) => rank.rank > 0)
|
||||
: chain.ranks;
|
||||
const channelRanks = sourceRanks[0]?.channelDurationMs
|
||||
? sourceRanks.filter((rank) => rank.channelDurationMs > 0)
|
||||
: sourceRanks;
|
||||
// The imported progression includes Hurricane's rank-five damage child
|
||||
// (48466), but omits its player-cast wrapper (48467). Preserve the previous
|
||||
// wrapper's channel/cost fields and point its periodic trigger at that child.
|
||||
const hurricaneRankFive = classId === "druid" && chain.name === "Hurricane"
|
||||
? sourceRanks.find((rank) => rank.rank === 5 && rank.spellId === 48466)
|
||||
: undefined;
|
||||
const previousHurricaneRank = channelRanks.at(-1);
|
||||
const playerRanks = hurricaneRankFive && previousHurricaneRank?.rank === 4
|
||||
? [...channelRanks, {
|
||||
...previousHurricaneRank,
|
||||
spellId: 48467,
|
||||
rank: 5,
|
||||
level: hurricaneRankFive.level,
|
||||
sourceLevel: hurricaneRankFive.sourceLevel,
|
||||
maxScalingLevel: hurricaneRankFive.maxScalingLevel,
|
||||
description: hurricaneRankFive.description,
|
||||
effects: previousHurricaneRank.effects.map((effect) => effect.auraType === 23
|
||||
? { ...effect, triggerSpellId: hurricaneRankFive.spellId }
|
||||
: effect),
|
||||
}]
|
||||
: channelRanks;
|
||||
// Holy Nova's DBC chain stores damage and healing as separate same-rank
|
||||
// spells. The damage spell is the cast (and pays mana); its healing partner
|
||||
// is a second payload of that same cast, not another selectable rank.
|
||||
const castRanks = classId === "priest" && chain.name === "Holy Nova"
|
||||
? sourceRanks.filter((rank) => rank.effects.some((effect) => effect.effectType === 2))
|
||||
? playerRanks.filter((rank) => rank.effects.some((effect) => effect.effectType === 2))
|
||||
.map((rank) => {
|
||||
const healing = sourceRanks.find((candidate) => candidate.rank === rank.rank
|
||||
&& candidate.effects.some((effect) => effect.effectType === 10));
|
||||
return healing ? { ...rank, effects: [...rank.effects, ...healing.effects] } : rank;
|
||||
})
|
||||
: sourceRanks;
|
||||
: playerRanks;
|
||||
const ranks = castRanks.map((rank, index) => rankDefinition(classId, resource, rank, index, chain.name));
|
||||
const first = ranks[0];
|
||||
const firstRaw = castRanks[0];
|
||||
@@ -1558,6 +1594,9 @@ function abilityDefinition(classId: BaseClassId, chain: Wow335Chain): AbilityDef
|
||||
...(radius > 0 ? { radius } : {}),
|
||||
castMode: first.castMode,
|
||||
castTimeMs: first.castTimeMs,
|
||||
channelTickMs: first.channelTickMs,
|
||||
channelPulses: first.channelPulses,
|
||||
channelStartsWithPulse: first.channelStartsWithPulse,
|
||||
cooldownMs: first.cooldownMs,
|
||||
...(classicCooldownGroup(classId, chain.name)
|
||||
? { cooldownGroup: classicCooldownGroup(classId, chain.name) }
|
||||
@@ -1572,7 +1611,7 @@ function abilityDefinition(classId: BaseClassId, chain: Wow335Chain): AbilityDef
|
||||
// Lash). They cannot be allocated by any legal local build, so expose
|
||||
// those retained class actions at their source level instead of making
|
||||
// them permanently unlearnable behind a dangling talent ID.
|
||||
talentNodeId: chain.talentNodeId && talentNodeById(chain.talentNodeId)
|
||||
talentNodeId: chain.talentNodeId && runtimeTalentNodeById(chain.talentNodeId)
|
||||
? chain.talentNodeId
|
||||
: undefined,
|
||||
ranks,
|
||||
@@ -1612,3 +1651,42 @@ export const WOW335_ABILITIES_BY_CLASS: Readonly<Record<BaseClassId, readonly Ab
|
||||
.filter((ability): ability is AbilityDefinition => Boolean(ability))
|
||||
.sort((left, right) => left.unlockLevel - right.unlockLevel || left.name.localeCompare(right.name)),
|
||||
]))) as unknown as Readonly<Record<BaseClassId, readonly AbilityDefinition[]>>;
|
||||
|
||||
// These ground channels store their actual damage in same-name triggered
|
||||
// child spells. Resolve the child once so each channel pulse can use its
|
||||
// rank-specific DBC amount rather than creating a delayed area effect.
|
||||
const GROUND_CHANNEL_CHILD_NAMES = new Set(["Blizzard", "Rain of Fire", "Hurricane"]);
|
||||
export const WOW335_CHANNEL_TRIGGERED_ABILITIES_BY_SPELL_ID: Readonly<Record<number, AbilityDefinition>> =
|
||||
Object.freeze(Object.fromEntries(BASE_CLASS_IDS.flatMap((classId) => data.classes[classId]
|
||||
.filter((chain) => GROUND_CHANNEL_CHILD_NAMES.has(chain.name))
|
||||
.flatMap((chain) => {
|
||||
const ranksById = new Map(chain.ranks.map((rank) => [rank.spellId, rank]));
|
||||
const childIds = new Set(chain.ranks.filter((rank) => rank.channelDurationMs === 0
|
||||
&& rank.effects.some((effect) => effect.effectType === 2))
|
||||
.map((rank) => rank.spellId));
|
||||
return [...childIds].flatMap((spellId): Array<[number, AbilityDefinition]> => {
|
||||
const child = ranksById.get(spellId);
|
||||
if (!child) return [];
|
||||
const effects = effectsForRank(classId, child, chain.name)
|
||||
.filter((effect) => effect.kind === "damage" || effect.kind === "heal");
|
||||
if (!effects.length) return [];
|
||||
return [[spellId, {
|
||||
id: `wow335-trigger-${spellId}`,
|
||||
classId,
|
||||
dbcSpellId: spellId,
|
||||
name: `${chain.name} pulse`,
|
||||
unlockLevel: 1,
|
||||
icon: chain.iconPath ?? `/assets/ui/spells/${chain.iconBasename}.png`,
|
||||
sigil: sigil(chain.name),
|
||||
description: child.description,
|
||||
target: "hostile",
|
||||
range: { min: 0, max: 40 },
|
||||
castMode: "instant",
|
||||
castTimeMs: 0,
|
||||
cooldownMs: 0,
|
||||
gcdMs: 0,
|
||||
cost: { resource: RESOURCE_BY_CLASS[classId], amount: 0 },
|
||||
effects,
|
||||
}]];
|
||||
});
|
||||
}))));
|
||||
Reference in new issue
Block a user