import type { ActiveAura, AuraDefinition, AuraModifier } from "./combatAuras"; /** Spell.dbc profile selected by the dungeon recipe. */ export type EnemyStanceSourceProfile = "ascension" | "epoch"; export type EnemyStanceSpellId = 7164 | 7165; export type EnemyStanceForm = 17 | 18; export interface EnemyStanceEffectSignature { readonly effectType: number; readonly auraType: number; readonly basePoints: number; readonly mechanic: number; readonly implicitTargetA: number; readonly implicitTargetB: number; readonly radiusIndex: number; readonly radius: number; readonly miscValue: number; readonly periodMs: number; readonly triggerSpellId: number; } export interface EnemyStanceSpellScalars { readonly attributes0: number; readonly dispel: number; readonly durationIndex: number; readonly durationMs: number; readonly rangeIndex: number; readonly schoolMask: number; readonly damageClass: number; readonly stackAmount: number; readonly stanceMask: readonly [number, number]; readonly stanceExcludeMask: readonly [number, number]; readonly equippedItemClass: number; readonly equippedItemSubclassMask: number; readonly equippedItemInventoryMask: number; } export interface EnemyStanceSpellSignature extends EnemyStanceSpellScalars { readonly effects: readonly EnemyStanceEffectSignature[]; } export interface UnsupportedEnemyStanceEffect extends EnemyStanceEffectSignature { readonly reason: string; } export interface EnemyStanceContract { readonly spellId: EnemyStanceSpellId; readonly name: "Defensive Stance" | "Battle Stance"; readonly profile: EnemyStanceSourceProfile; readonly form: EnemyStanceForm; /** Runtime duration; null means permanent until replaced or explicitly removed. */ readonly durationMs: number | null; readonly target: "self"; readonly exclusiveGroup: "enemy-warrior-stance"; readonly signature: EnemyStanceSpellSignature; /** AzerothCore's form handler applies and removes this passive by form ID. */ readonly childPassiveSpellId: number; /** Attribute 0 identifies the linked Spell.dbc row as passive in both clients. */ readonly childPassiveScalars: EnemyStanceSpellScalars; /** Passive Spell.dbc row applied by the historical server's stance handler. */ readonly childPassiveEffects: readonly EnemyStanceEffectSignature[]; /** Modifier subset supported by ActiveAura's current stat and damage resolver. */ readonly modifiers: readonly AuraModifier[]; /** Child passive effects whose behavior is outside the current runtime. */ readonly unsupportedEffects: readonly UnsupportedEnemyStanceEffect[]; } export const ENEMY_STANCE_SOURCE_PINS = Object.freeze({ ascension: Object.freeze({ spellDbcPath: "../LadiksMPQEditor/client-current/area-52/patch-D/DBFilesClient/Spell.dbc", spellDbcSha256: "2f77f18b91ffbae32a19568e4bc05ec78ee384b6ba8dd2a6e05f0e97abbb2dce", durationSource: "shared patch-S SpellDuration.dbc fallback", durationDbcSha256: "eeae44ef0185924f3d6ec57ab2701c66566db86e51ab0bae017a9449126eb3a0", }), epoch: Object.freeze({ spellDbcPath: "../HealerMan-Storage/pipeline-work/dungeons/epoch-client/DBFilesClient/Spell.dbc", spellDbcSha256: "d5cce1a83550dcfa9eb2f0251dbb11fd24c272534b2b1a9b230924a44d817ab3", durationSource: "shared patch-S SpellDuration.dbc fallback", durationDbcSha256: "eeae44ef0185924f3d6ec57ab2701c66566db86e51ab0bae017a9449126eb3a0", }), }); const effect = ( effectType: number, auraType: number, basePoints: number, miscValue = 0, triggerSpellId = 0, ): EnemyStanceEffectSignature => Object.freeze({ effectType, auraType, basePoints, mechanic: 0, implicitTargetA: 1, implicitTargetB: 0, radiusIndex: 0, radius: 0, miscValue, periodMs: 0, triggerSpellId, }); const spellScalars = (input: { attributes0: number; durationIndex: number; durationMs: number; damageClass: number; }): EnemyStanceSpellScalars => Object.freeze({ ...input, dispel: 0, rangeIndex: 1, schoolMask: 1, stackAmount: 0, stanceMask: Object.freeze([0, 0]) as readonly [number, number], stanceExcludeMask: Object.freeze([0, 0]) as readonly [number, number], equippedItemClass: -1, equippedItemSubclassMask: 0, equippedItemInventoryMask: 0, }); const stanceSignature = ( durationMs: number, form: EnemyStanceForm, durationIndex: number, damageClass: number, ): EnemyStanceSpellSignature => Object.freeze({ attributes0: 327_696, dispel: 0, durationIndex, durationMs, rangeIndex: 1, schoolMask: 1, damageClass, stackAmount: 0, stanceMask: Object.freeze([0, 0]) as readonly [number, number], stanceExcludeMask: Object.freeze([0, 0]) as readonly [number, number], equippedItemClass: -1, equippedItemSubclassMask: 0, equippedItemInventoryMask: 0, effects: Object.freeze([effect(6, 36, 0, form)]), }); const unsupported = ( sourceEffect: EnemyStanceEffectSignature, reason: string, ): UnsupportedEnemyStanceEffect => Object.freeze({ ...sourceEffect, reason }); const allSchoolDamageModifier = ( direction: "dealt" | "taken", value: number, ): AuraModifier => Object.freeze({ kind: "damage", direction, operation: "percent", value, multiplicative: true, }); const threatModifier = (value: number): AuraModifier => Object.freeze({ kind: "stat", stat: "threat", operation: "percent", value, multiplicative: true, }); const contracts: Readonly>>> = Object.freeze({ ascension: Object.freeze({ 7164: Object.freeze({ spellId: 7164, name: "Defensive Stance", profile: "ascension", form: 18, durationMs: 180_000, target: "self", exclusiveGroup: "enemy-warrior-stance", signature: stanceSignature(180_000, 18, 25, 0), childPassiveSpellId: 7376, childPassiveScalars: spellScalars({ attributes0: 327_888, durationIndex: 0, durationMs: 0, damageClass: 0 }), childPassiveEffects: Object.freeze([ effect(6, 87, -10, 127), effect(6, 79, -5, 127), effect(6, 4, 42, 127), ]), modifiers: Object.freeze([ allSchoolDamageModifier("taken", -0.10), allSchoolDamageModifier("dealt", -0.05), ]), unsupportedEffects: Object.freeze([ unsupported(effect(6, 4, 42, 127), "Aura 4 is DUMMY; this row has no sourced trigger spell or supported runtime behavior."), ]), }), 7165: Object.freeze({ spellId: 7165, name: "Battle Stance", profile: "ascension", form: 17, durationMs: null, target: "self", exclusiveGroup: "enemy-warrior-stance", signature: stanceSignature(-1, 17, 21, 2), childPassiveSpellId: 21156, childPassiveScalars: spellScalars({ attributes0: 327_888, durationIndex: 0, durationMs: 0, damageClass: 0 }), childPassiveEffects: Object.freeze([ effect(6, 10, 0, 127), effect(6, 280, 0, 0), ]), modifiers: Object.freeze([]), unsupportedEffects: Object.freeze([]), }), }), epoch: Object.freeze({ 7164: Object.freeze({ spellId: 7164, name: "Defensive Stance", profile: "epoch", form: 18, durationMs: 180_000, target: "self", exclusiveGroup: "enemy-warrior-stance", signature: stanceSignature(180_000, 18, 25, 0), childPassiveSpellId: 7376, childPassiveScalars: spellScalars({ attributes0: 327_888, durationIndex: 0, durationMs: 0, damageClass: 0 }), childPassiveEffects: Object.freeze([ effect(6, 87, -10, 127), effect(6, 79, -5, 127), effect(6, 10, 45, 127), ]), modifiers: Object.freeze([ allSchoolDamageModifier("taken", -0.10), allSchoolDamageModifier("dealt", -0.05), threatModifier(0.45), ]), unsupportedEffects: Object.freeze([]), }), 7165: Object.freeze({ spellId: 7165, name: "Battle Stance", profile: "epoch", form: 17, durationMs: null, target: "self", exclusiveGroup: "enemy-warrior-stance", signature: stanceSignature(-1, 17, 21, 2), childPassiveSpellId: 21156, childPassiveScalars: spellScalars({ attributes0: 327_888, durationIndex: 0, durationMs: 0, damageClass: 0 }), childPassiveEffects: Object.freeze([ effect(6, 10, -20, 127), ]), modifiers: Object.freeze([threatModifier(-0.20)]), unsupportedEffects: Object.freeze([]), }), }), }); /** Return a pinned form spell and its profile-specific server-linked passive row. */ export function enemyStanceContract( spellId: number, profile: EnemyStanceSourceProfile, ): EnemyStanceContract | undefined { if (spellId !== 7164 && spellId !== 7165) return undefined; return contracts[profile][spellId]; } /** Build a state aura, retaining stance identity even where no modifier is supported. */ export function supportedEnemyStanceAuraDefinition( contract: EnemyStanceContract, ): AuraDefinition { return { id: `enemy-source-spell-${contract.spellId}`, name: contract.name, disposition: "buff", durationMs: contract.durationMs, stackBehavior: "replace", exclusiveGroup: contract.exclusiveGroup, utilityTags: [ "enemy-warrior-stance", `enemy-warrior-form:${contract.form}`, `enemy-passive-spell:${contract.childPassiveSpellId}`, ], modifiers: contract.modifiers, }; } /** Remove the actor's active source stance during an explicit form/reset transition. */ export function removeEnemyStanceAura( current: readonly ActiveAura[], actorId: string, ): readonly ActiveAura[] { return current.filter((aura) => !( aura.targetId === actorId && aura.sourceId === actorId && aura.definition.exclusiveGroup === "enemy-warrior-stance" )); }