Update Healer Man server
This commit is contained in:
1 parent
69809f7228
commit
ff20ab8779
101 files changed
+16713
-945
No files matched your search
@@ -40,6 +40,8 @@ import {
|
||||
} from "./wow335WarlockMetamorphosisRuntime";
|
||||
import { WOW335_WARLOCK_SOUL_LINK_TAG } from "./wow335WarlockSoulLinkRuntime";
|
||||
import { WOW335_TALENT_NODES } from "./wow335Talents.generated";
|
||||
import { WOW335_HUNTER_DETERRENCE_PARENT_AURA_EFFECTS } from "./wow335DeterrenceRuntime";
|
||||
import { WOW335_DETERRENCE_SOURCE_SIGNATURES } from "./wow335Deterrence";
|
||||
|
||||
export const BASE_CLASS_STARTER_SOURCE_LEVEL = 10;
|
||||
|
||||
@@ -50,6 +52,7 @@ interface Wow335Effect {
|
||||
readonly basePoints: number;
|
||||
readonly dieSides: number;
|
||||
readonly pointsPerLevel: number;
|
||||
readonly mechanic?: number;
|
||||
readonly implicitTargetA: number;
|
||||
readonly implicitTargetB: number;
|
||||
readonly radius: number;
|
||||
@@ -100,6 +103,126 @@ const data = generatedData as {
|
||||
readonly classes: Readonly<Record<BaseClassId, readonly Wow335Chain[]>>;
|
||||
};
|
||||
|
||||
const WOW335_HUNTER_DETERRENCE_CHAIN: Wow335Chain = Object.freeze({
|
||||
id: "wow335-hunter-deterrence",
|
||||
name: "Deterrence",
|
||||
iconBasename: "ability_whirlwind",
|
||||
iconPath: "/assets/ui/spells/ability_whirlwind.png",
|
||||
sourceUnlockLevel: 60,
|
||||
ranks: Object.freeze([Object.freeze({
|
||||
spellId: 19263,
|
||||
rank: 0,
|
||||
level: 60,
|
||||
sourceLevel: 60,
|
||||
maxScalingLevel: 0,
|
||||
description: "When activated, increases parry chance by 100% and grants a 100% chance to deflect spells. While Deterrence is active, you cannot attack. Lasts 5 sec.",
|
||||
castTimeMs: 0,
|
||||
cooldownMs: 90_000,
|
||||
gcdMs: 0,
|
||||
durationMs: 5_000,
|
||||
channelDurationMs: 0,
|
||||
powerType: 0,
|
||||
powerCost: 0,
|
||||
powerCostPercentage: 0,
|
||||
rangeIndex: 1,
|
||||
rangeMin: 0,
|
||||
rangeMax: 0,
|
||||
equippedItemClass: 2,
|
||||
equippedItemSubclassMask: 173_555,
|
||||
equippedItemInventoryMask: 0,
|
||||
wowSpellDefense: Object.freeze({
|
||||
damageClass: 0,
|
||||
schoolMask: 1,
|
||||
attributes0: 327_696,
|
||||
attributes1: 16,
|
||||
attributes3: 0,
|
||||
attributes5: 0,
|
||||
directDamageFromEffects: false,
|
||||
sourceProfile: "wow335" as const,
|
||||
}),
|
||||
effects: Object.freeze(WOW335_DETERRENCE_SOURCE_SIGNATURES.selectedParent.effects.map((effect) => ({
|
||||
...effect,
|
||||
coefficient: [1, 1, 0][effect.index] ?? 0,
|
||||
}))),
|
||||
})]),
|
||||
});
|
||||
|
||||
const WOW335_HUNTER_DETERRENCE_DESCRIPTION = WOW335_HUNTER_DETERRENCE_CHAIN.ranks[0]!.description;
|
||||
|
||||
function isExactHunterDeterrenceChain(chain: Wow335Chain): boolean {
|
||||
const [rank] = chain.ranks;
|
||||
const source = WOW335_DETERRENCE_SOURCE_SIGNATURES.selectedParent;
|
||||
if (chain.id !== "wow335-hunter-deterrence"
|
||||
|| chain.name !== "Deterrence"
|
||||
|| chain.iconBasename !== "ability_whirlwind"
|
||||
|| chain.iconPath !== "/assets/ui/spells/ability_whirlwind.png"
|
||||
|| chain.sourceUnlockLevel !== 60
|
||||
|| chain.ranks.length !== 1
|
||||
|| !rank
|
||||
|| rank.spellId !== source.spellId
|
||||
|| rank.rank !== 0
|
||||
|| rank.level !== 60
|
||||
|| rank.sourceLevel !== 60
|
||||
|| rank.maxScalingLevel !== 0
|
||||
|| rank.description !== WOW335_HUNTER_DETERRENCE_DESCRIPTION
|
||||
|| rank.castTimeMs !== 0
|
||||
|| rank.cooldownMs !== 90_000
|
||||
|| rank.gcdMs !== 0
|
||||
|| rank.durationMs !== 5_000
|
||||
|| rank.channelDurationMs !== 0
|
||||
|| rank.powerType !== 0
|
||||
|| rank.powerCost !== 0
|
||||
|| rank.powerCostPercentage !== 0
|
||||
|| rank.rangeIndex !== 1
|
||||
|| rank.rangeMin !== 0
|
||||
|| rank.rangeMax !== 0
|
||||
|| rank.equippedItemClass !== 2
|
||||
|| rank.equippedItemSubclassMask !== 173_555
|
||||
|| rank.equippedItemInventoryMask !== 0) return false;
|
||||
|
||||
const defense = rank.wowSpellDefense;
|
||||
if (!defense
|
||||
|| defense.damageClass !== 0
|
||||
|| defense.schoolMask !== 1
|
||||
|| defense.attributes0 !== 327_696
|
||||
|| defense.attributes1 !== 16
|
||||
|| defense.attributes3 !== 0
|
||||
|| defense.attributes5 !== 0
|
||||
|| defense.directDamageFromEffects !== false
|
||||
|| defense.sourceProfile !== "wow335") return false;
|
||||
|
||||
return rank.effects.length === source.effects.length
|
||||
&& rank.effects.every((effect, index) => {
|
||||
const expected = source.effects[index];
|
||||
return expected !== undefined
|
||||
&& effect.index === expected.index
|
||||
&& effect.effectType === expected.effectType
|
||||
&& effect.auraType === expected.auraType
|
||||
&& effect.basePoints === expected.basePoints
|
||||
&& effect.dieSides === expected.dieSides
|
||||
&& effect.pointsPerLevel === expected.pointsPerLevel
|
||||
&& (effect.mechanic ?? 0) === expected.mechanic
|
||||
&& effect.implicitTargetA === expected.implicitTargetA
|
||||
&& effect.implicitTargetB === expected.implicitTargetB
|
||||
&& effect.radius === expected.radius
|
||||
&& effect.periodMs === expected.periodMs
|
||||
&& effect.miscValue === expected.miscValue
|
||||
&& effect.triggerSpellId === expected.triggerSpellId
|
||||
&& effect.coefficient === [1, 1, 0][index];
|
||||
});
|
||||
}
|
||||
|
||||
function hunterChainsWithSelectedDeterrence(): readonly Wow335Chain[] {
|
||||
const chains = data.classes.hunter;
|
||||
const candidates = chains.filter((chain) => chain.name === "Deterrence"
|
||||
|| chain.ranks.some((rank) => [19_263, 65_871, 67_801].includes(rank.spellId)));
|
||||
if (candidates.length === 0) return [...chains, WOW335_HUNTER_DETERRENCE_CHAIN];
|
||||
if (candidates.length === 1 && isExactHunterDeterrenceChain(candidates[0]!)) return chains;
|
||||
// A conflicting same-name/linked-ID import is source drift. Drop the whole
|
||||
// candidate group and fail closed rather than substitute 65871 or 67801.
|
||||
return chains.filter((chain) => !candidates.includes(chain));
|
||||
}
|
||||
|
||||
const RESOURCE_BY_CLASS: Readonly<Record<BaseClassId, ResourceType>> = {
|
||||
warrior: "rage",
|
||||
paladin: "mana",
|
||||
@@ -2386,7 +2509,15 @@ function abilityDefinition(classId: BaseClassId, chain: Wow335Chain): AbilityDef
|
||||
?? auraMasteryTalentTooltip;
|
||||
return talentTooltip ? { ...rank, description: talentTooltip } : rank;
|
||||
});
|
||||
const first = ranks[0];
|
||||
const selectedDeterrence = classId === "hunter"
|
||||
&& chain.id === "wow335-hunter-deterrence"
|
||||
&& chain.name === "Deterrence"
|
||||
&& castRanks.length === 1
|
||||
&& castRanks[0]?.spellId === 19_263;
|
||||
const mappedRanks = selectedDeterrence
|
||||
? ranks.map((rank) => ({ ...rank, effects: WOW335_HUNTER_DETERRENCE_PARENT_AURA_EFFECTS }))
|
||||
: ranks;
|
||||
const first = mappedRanks[0];
|
||||
const firstRaw = castRanks[0];
|
||||
if (!first || !firstRaw) return null;
|
||||
const effectiveSourceUnlockLevel = classId === "warrior" && chain.name === "Sweeping Strikes"
|
||||
@@ -2458,7 +2589,7 @@ function abilityDefinition(classId: BaseClassId, chain: Wow335Chain): AbilityDef
|
||||
talentNodeId: chain.talentNodeId && runtimeTalentNodeById(chain.talentNodeId)
|
||||
? chain.talentNodeId
|
||||
: undefined,
|
||||
ranks,
|
||||
ranks: mappedRanks,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2499,7 +2630,8 @@ function classicCooldownGroup(classId: BaseClassId, abilityName: string): string
|
||||
|
||||
export const WOW335_ABILITIES_BY_CLASS: Readonly<Record<BaseClassId, readonly AbilityDefinition[]>> =
|
||||
Object.freeze(Object.fromEntries(BASE_CLASS_IDS.map((classId) => {
|
||||
const generated = data.classes[classId]
|
||||
const sourceChains = classId === "hunter" ? hunterChainsWithSelectedDeterrence() : data.classes[classId];
|
||||
const generated = sourceChains
|
||||
.map((chain) => abilityDefinition(classId, chain))
|
||||
.filter((ability): ability is AbilityDefinition => Boolean(ability));
|
||||
const specialized = classId === "warlock"
|
||||
@@ -2514,6 +2646,82 @@ export const WOW335_ABILITIES_BY_CLASS: Readonly<Record<BaseClassId, readonly Ab
|
||||
))];
|
||||
}))) as unknown as Readonly<Record<BaseClassId, readonly AbilityDefinition[]>>;
|
||||
|
||||
export type Wow335HunterPreventionType = 0 | 1 | 2;
|
||||
|
||||
// Spell.dbc field 214 in the pinned 234-field layout (field 220 in the
|
||||
// selected 239-field archive) is kept locally until the generator has a
|
||||
// source-profile-aware way to carry this cast gate. Every value below was
|
||||
// read from the pinned Spell.dbc row; missing IDs intentionally remain absent.
|
||||
const WOW335_HUNTER_PREVENTION_SPELL_IDS_BY_TYPE: Readonly<Record<Wow335HunterPreventionType, readonly number[]>> = Object.freeze({
|
||||
0: Object.freeze([
|
||||
23_989, 1_494, 1_130, 14_323, 14_324, 14_325, 53_338, 883, 2_641, 6_991,
|
||||
1_515, 19_883, 19_884, 781, 1_462, 19_885, 3_045, 19_880, 5_384,
|
||||
19_878, 19_574, 19_882, 24_131, 24_134, 24_135, 27_069, 49_009, 49_010,
|
||||
34_471, 19_879, 62_757, 19_263,
|
||||
]),
|
||||
1: Object.freeze([
|
||||
13_163, 13_165, 14_318, 14_319, 14_320, 14_321, 14_322, 25_296, 27_044,
|
||||
982, 136, 3_111, 3_661, 3_662, 13_542, 13_543, 13_544, 27_046, 48_989,
|
||||
48_990, 6_197, 1_002, 1_513, 14_326, 14_327, 5_118, 13_795, 13_797, 14_298,
|
||||
14_302, 14_299, 14_303, 14_300, 14_304, 14_301, 14_305, 27_023, 27_024,
|
||||
49_053, 49_055, 49_054, 49_056, 34_074, 1_499, 14_310, 14_311, 13_809,
|
||||
13_161, 19_577, 1_543, 13_813, 14_316, 14_317, 27_025, 49_066, 49_067,
|
||||
13_159, 19_506, 20_043, 20_190, 27_045,
|
||||
49_071, 34_026, 34_600, 61_846, 61_847, 60_192,
|
||||
]),
|
||||
2: Object.freeze([
|
||||
75, 2_973, 14_260, 14_261, 14_262, 14_263, 14_264, 14_265, 14_266, 27_014,
|
||||
48_995, 48_996, 1_978, 13_549, 13_550, 13_551, 13_552, 13_553, 13_554,
|
||||
13_555, 25_295, 27_016, 49_000, 49_001, 3_044, 14_281, 14_282, 14_283,
|
||||
14_284, 14_285, 14_286, 14_287, 27_019, 49_044, 49_045, 5_116, 20_736,
|
||||
2_974, 19_503, 1_495, 14_269, 14_270, 14_271, 36_916, 53_339, 2_643,
|
||||
14_288, 14_289, 14_290, 25_294, 27_021, 49_047, 49_048, 19_434, 27_065,
|
||||
49_049, 49_050, 3_043, 19_306, 20_909, 20_910, 27_067, 48_998, 48_999,
|
||||
34_490, 3_034, 1_510, 14_294, 14_295, 27_022, 19_386, 24_132, 24_133,
|
||||
27_068, 49_011, 49_012, 3_674, 63_668, 63_669, 63_670, 63_671, 63_672,
|
||||
56_641, 49_051, 49_052, 53_209, 53_301, 19_801, 34_477, 61_005, 61_006,
|
||||
]),
|
||||
});
|
||||
|
||||
const hunterPreventionTypesBySpellId: Record<number, Wow335HunterPreventionType> = {};
|
||||
for (const type of [0, 1, 2] as const) {
|
||||
for (const spellId of WOW335_HUNTER_PREVENTION_SPELL_IDS_BY_TYPE[type]) {
|
||||
hunterPreventionTypesBySpellId[spellId] = type;
|
||||
}
|
||||
}
|
||||
export const WOW335_HUNTER_PREVENTION_TYPES_BY_SPELL_ID: Readonly<Record<number, Wow335HunterPreventionType>> =
|
||||
Object.freeze(hunterPreventionTypesBySpellId);
|
||||
|
||||
const HUNTER_SOURCE_RANK_IDENTITIES = new Map<number, { readonly id: string; readonly name: string }>();
|
||||
const DUPLICATE_HUNTER_SOURCE_RANK_IDS = new Set<number>();
|
||||
for (const ability of WOW335_ABILITIES_BY_CLASS.hunter) {
|
||||
for (const rank of ability.ranks ?? []) {
|
||||
if (HUNTER_SOURCE_RANK_IDENTITIES.has(rank.spellId)) {
|
||||
HUNTER_SOURCE_RANK_IDENTITIES.delete(rank.spellId);
|
||||
DUPLICATE_HUNTER_SOURCE_RANK_IDS.add(rank.spellId);
|
||||
continue;
|
||||
}
|
||||
if (!DUPLICATE_HUNTER_SOURCE_RANK_IDS.has(rank.spellId)) {
|
||||
HUNTER_SOURCE_RANK_IDENTITIES.set(rank.spellId, { id: ability.id, name: ability.name });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the pinned source PreventionType only for a selected Hunter row. */
|
||||
export function wow335HunterPreventionTypeForAbility(
|
||||
ability: Pick<AbilityDefinition, "id" | "classId" | "source" | "name" | "dbcSpellId">,
|
||||
): Wow335HunterPreventionType | undefined {
|
||||
if (ability.classId !== "hunter" || ability.source !== "wow335-progression") return undefined;
|
||||
const preventionType = WOW335_HUNTER_PREVENTION_TYPES_BY_SPELL_ID[ability.dbcSpellId];
|
||||
const identity = HUNTER_SOURCE_RANK_IDENTITIES.get(ability.dbcSpellId);
|
||||
if (preventionType === undefined || !identity || DUPLICATE_HUNTER_SOURCE_RANK_IDS.has(ability.dbcSpellId)) {
|
||||
return undefined;
|
||||
}
|
||||
const publicAlias = identity.id.startsWith("wow335-") ? identity.id.slice("wow335-".length) : undefined;
|
||||
if (ability.name !== identity.name || (ability.id !== identity.id && ability.id !== publicAlias)) return undefined;
|
||||
return preventionType;
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
Reference in new issue
Block a user