168 lines
7.5 KiB
TypeScript
168 lines
7.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { applyAura, expireAuras, resolveDamageValue, resolveStatValue } from "./combatAuras";
|
|
import {
|
|
ENEMY_STANCE_SOURCE_PINS,
|
|
enemyStanceContract,
|
|
removeEnemyStanceAura,
|
|
supportedEnemyStanceAuraDefinition,
|
|
} from "./enemyStances";
|
|
|
|
describe("pinned enemy warrior stance contracts", () => {
|
|
it("pins both client profiles and preserves the direct form, duration, and no-restriction rows", () => {
|
|
expect(ENEMY_STANCE_SOURCE_PINS.ascension.spellDbcSha256)
|
|
.toBe("2f77f18b91ffbae32a19568e4bc05ec78ee384b6ba8dd2a6e05f0e97abbb2dce");
|
|
expect(ENEMY_STANCE_SOURCE_PINS.epoch.spellDbcSha256)
|
|
.toBe("d5cce1a83550dcfa9eb2f0251dbb11fd24c272534b2b1a9b230924a44d817ab3");
|
|
expect(ENEMY_STANCE_SOURCE_PINS.epoch.durationDbcSha256)
|
|
.toBe("eeae44ef0185924f3d6ec57ab2701c66566db86e51ab0bae017a9449126eb3a0");
|
|
|
|
for (const profile of ["ascension", "epoch"] as const) {
|
|
expect(enemyStanceContract(7164, profile)).toMatchObject({
|
|
name: "Defensive Stance",
|
|
form: 18,
|
|
durationMs: 180_000,
|
|
target: "self",
|
|
signature: {
|
|
attributes0: 327_696,
|
|
dispel: 0,
|
|
durationIndex: 25,
|
|
durationMs: 180_000,
|
|
rangeIndex: 1,
|
|
schoolMask: 1,
|
|
damageClass: 0,
|
|
stackAmount: 0,
|
|
stanceMask: [0, 0],
|
|
stanceExcludeMask: [0, 0],
|
|
equippedItemClass: -1,
|
|
equippedItemSubclassMask: 0,
|
|
equippedItemInventoryMask: 0,
|
|
effects: [{ effectType: 6, auraType: 36, basePoints: 0, implicitTargetA: 1, implicitTargetB: 0, radiusIndex: 0, radius: 0, miscValue: 18, triggerSpellId: 0 }],
|
|
},
|
|
});
|
|
expect(enemyStanceContract(7165, profile)).toMatchObject({
|
|
name: "Battle Stance",
|
|
form: 17,
|
|
durationMs: null,
|
|
target: "self",
|
|
signature: {
|
|
attributes0: 327_696,
|
|
durationIndex: 21,
|
|
durationMs: -1,
|
|
damageClass: 2,
|
|
stanceMask: [0, 0],
|
|
stanceExcludeMask: [0, 0],
|
|
effects: [{ effectType: 6, auraType: 36, basePoints: 0, miscValue: 17, triggerSpellId: 0 }],
|
|
},
|
|
});
|
|
}
|
|
expect(enemyStanceContract(99999, "epoch")).toBeUndefined();
|
|
});
|
|
|
|
it("uses the server-linked child passives and applies only supported numeric effects", () => {
|
|
const ascDefensive = enemyStanceContract(7164, "ascension")!;
|
|
const epochDefensive = enemyStanceContract(7164, "epoch")!;
|
|
const ascBattle = enemyStanceContract(7165, "ascension")!;
|
|
const epochBattle = enemyStanceContract(7165, "epoch")!;
|
|
|
|
expect(ascDefensive).toMatchObject({
|
|
childPassiveSpellId: 7376,
|
|
childPassiveScalars: {
|
|
attributes0: 327_888,
|
|
dispel: 0,
|
|
durationIndex: 0,
|
|
durationMs: 0,
|
|
rangeIndex: 1,
|
|
schoolMask: 1,
|
|
damageClass: 0,
|
|
stackAmount: 0,
|
|
stanceMask: [0, 0],
|
|
stanceExcludeMask: [0, 0],
|
|
equippedItemClass: -1,
|
|
equippedItemSubclassMask: 0,
|
|
equippedItemInventoryMask: 0,
|
|
},
|
|
childPassiveEffects: [
|
|
{ effectType: 6, auraType: 87, basePoints: -10, miscValue: 127, triggerSpellId: 0 },
|
|
{ effectType: 6, auraType: 79, basePoints: -5, miscValue: 127, triggerSpellId: 0 },
|
|
{ effectType: 6, auraType: 4, basePoints: 42, miscValue: 127, triggerSpellId: 0 },
|
|
],
|
|
modifiers: [
|
|
{ kind: "damage", direction: "taken", value: -0.10, multiplicative: true },
|
|
{ kind: "damage", direction: "dealt", value: -0.05, multiplicative: true },
|
|
],
|
|
unsupportedEffects: [{ auraType: 4, basePoints: 42 }],
|
|
});
|
|
expect(epochDefensive.childPassiveEffects[2]).toMatchObject({ auraType: 10, basePoints: 45 });
|
|
expect(epochDefensive.modifiers).toContainEqual({
|
|
kind: "stat", stat: "threat", operation: "percent", value: 0.45, multiplicative: true,
|
|
});
|
|
expect(ascBattle).toMatchObject({
|
|
childPassiveSpellId: 21156,
|
|
childPassiveScalars: { attributes0: 327_888, durationIndex: 0, rangeIndex: 1, schoolMask: 1, damageClass: 0, stackAmount: 0 },
|
|
childPassiveEffects: [{ auraType: 10, basePoints: 0 }, { auraType: 280, basePoints: 0 }],
|
|
modifiers: [],
|
|
unsupportedEffects: [],
|
|
});
|
|
expect(epochBattle).toMatchObject({
|
|
childPassiveSpellId: 21156,
|
|
childPassiveScalars: { attributes0: 327_888, durationIndex: 0, rangeIndex: 1, schoolMask: 1, damageClass: 0, stackAmount: 0 },
|
|
childPassiveEffects: [{ auraType: 10, basePoints: -20, miscValue: 127 }],
|
|
modifiers: [{ kind: "stat", stat: "threat", operation: "percent", value: -0.20, multiplicative: true }],
|
|
unsupportedEffects: [],
|
|
});
|
|
|
|
const definition = supportedEnemyStanceAuraDefinition(ascDefensive);
|
|
const active = applyAura([], { definition, sourceId: "mob", targetId: "mob", now: 0 }).auras;
|
|
expect(resolveDamageValue(100, active, { direction: "taken", school: "fire", attackKind: "spell" })).toBe(90);
|
|
expect(resolveDamageValue(100, active, { direction: "dealt", school: "physical", attackKind: "melee" })).toBe(95);
|
|
const combinedMitigation = applyAura(active, {
|
|
definition: {
|
|
id: "another-source-damage-mitigation",
|
|
name: "Another sourced mitigation",
|
|
disposition: "buff",
|
|
durationMs: null,
|
|
modifiers: [{ kind: "damage", direction: "taken", operation: "percent", value: 0.20, multiplicative: true }],
|
|
},
|
|
sourceId: "second-source",
|
|
targetId: "mob",
|
|
now: 1,
|
|
}).auras;
|
|
expect(resolveDamageValue(100, combinedMitigation, { direction: "taken", school: "physical", attackKind: "melee" }))
|
|
.toBeCloseTo(108);
|
|
expect(resolveStatValue(1, applyAura([], {
|
|
definition: supportedEnemyStanceAuraDefinition(epochDefensive), sourceId: "epoch-mob", targetId: "epoch-mob", now: 0,
|
|
}).auras, "threat")).toBeCloseTo(1.45);
|
|
expect(resolveStatValue(1, applyAura([], {
|
|
definition: supportedEnemyStanceAuraDefinition(epochBattle), sourceId: "epoch-mob", targetId: "epoch-mob", now: 0,
|
|
}).auras, "threat")).toBeCloseTo(0.80);
|
|
});
|
|
|
|
it("expires Defensive Stance, switches exclusively into metadata-only Battle Stance, then removes it on reset", () => {
|
|
const defensive = supportedEnemyStanceAuraDefinition(enemyStanceContract(7164, "ascension")!);
|
|
const battle = supportedEnemyStanceAuraDefinition(enemyStanceContract(7165, "ascension")!);
|
|
const first = applyAura([], { definition: defensive, sourceId: "mob", targetId: "mob", now: 10 }).auras;
|
|
expect(expireAuras(first, 180_009).active).toHaveLength(1);
|
|
expect(expireAuras(first, 180_010)).toMatchObject({ active: [], expired: first });
|
|
|
|
const switchedResult = applyAura(first, { definition: battle, sourceId: "mob", targetId: "mob", now: 20 });
|
|
const switched = switchedResult.auras;
|
|
|
|
expect(defensive.exclusiveGroup).toBe("enemy-warrior-stance");
|
|
expect(battle.exclusiveGroup).toBe("enemy-warrior-stance");
|
|
expect(switched).toHaveLength(1);
|
|
expect(switched[0]).toMatchObject({
|
|
definition: {
|
|
id: "enemy-source-spell-7165",
|
|
modifiers: [],
|
|
utilityTags: ["enemy-warrior-stance", "enemy-warrior-form:17", "enemy-passive-spell:21156"],
|
|
},
|
|
appliedAt: 20,
|
|
expiresAt: null,
|
|
});
|
|
expect(switchedResult.replaced?.definition.id).toBe("enemy-source-spell-7164");
|
|
expect(resolveDamageValue(100, switched, { direction: "taken", school: "physical", attackKind: "melee" })).toBe(100);
|
|
expect(removeEnemyStanceAura(switched, "another-mob")).toEqual(switched);
|
|
expect(removeEnemyStanceAura(switched, "mob")).toEqual([]);
|
|
});
|
|
});
|