245 lines
10 KiB
TypeScript
245 lines
10 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { applyAura, type ActiveAura, type AuraDefinition } from "./combatAuras";
|
|
import {
|
|
ENEMY_BLOCK_AURA_SOURCE_PINS,
|
|
enemyBlockAuraContract,
|
|
enemyBlockAuraInputDefinition,
|
|
ordinaryCreatureSelfTargetCriticalBlockAuraDefinition,
|
|
type EnemyNpcCriticalBlockImportProof,
|
|
enemyNpcBlockValueAfterCritical,
|
|
enemyNpcCriticalBlockChanceForRoll,
|
|
resolveEnemyNpcBlockAuraInputs,
|
|
supportedEnemyBlockAuraDefinition,
|
|
SPELL_AURA_IGNORE_HIT_DIRECTION,
|
|
SPELL_AURA_MOD_BLOCK_CRIT_CHANCE,
|
|
SPELL_AURA_MOD_BLOCK_PERCENT,
|
|
} from "./enemyBlockAuras";
|
|
|
|
function addAura(
|
|
auras: readonly ActiveAura[],
|
|
definition: AuraDefinition,
|
|
targetId: string,
|
|
now: number,
|
|
): readonly ActiveAura[] {
|
|
return applyAura(auras, { definition, sourceId: "source", targetId, now }).auras;
|
|
}
|
|
|
|
function ordinaryCreatureSelfTargetProof(id = "ordinary-creature-47294"): EnemyNpcCriticalBlockImportProof {
|
|
return {
|
|
defenderId: id,
|
|
casterId: id,
|
|
targetId: id,
|
|
defenderKind: "ordinary-creature",
|
|
isNativeDefender: false,
|
|
isSummonedDefender: false,
|
|
isCharacterDefender: false,
|
|
aura107Applicability: "azerothcore-e822d718-default-spellmod-path",
|
|
};
|
|
}
|
|
|
|
describe("source-bound enemy NPC block auras", () => {
|
|
it("pins the exact profiles and preserves their aura types, amounts, and durations", () => {
|
|
expect(ENEMY_BLOCK_AURA_SOURCE_PINS.wow335.spellDbcSha256)
|
|
.toBe("57011d528fd66cf39f5ab6fb168e389ab9e4cc1b8050f7201a54239d489b415c");
|
|
expect(ENEMY_BLOCK_AURA_SOURCE_PINS.ascension.spellDbcSha256)
|
|
.toBe("2f77f18b91ffbae32a19568e4bc05ec78ee384b6ba8dd2a6e05f0e97abbb2dce");
|
|
expect(ENEMY_BLOCK_AURA_SOURCE_PINS.epoch.spellDbcSha256)
|
|
.toBe("d5cce1a83550dcfa9eb2f0251dbb11fd24c272534b2b1a9b230924a44d817ab3");
|
|
|
|
for (const profile of ["wow335", "ascension", "epoch"] as const) {
|
|
expect(enemyBlockAuraContract(3419, profile)).toMatchObject({
|
|
durationMs: 6_000,
|
|
effects: [{ effectType: 6, auraType: SPELL_AURA_MOD_BLOCK_PERCENT, basePoints: 75 }],
|
|
blockInputs: { blockChanceAuraPercent: 75 },
|
|
});
|
|
}
|
|
|
|
for (const [profile, chance] of [["wow335", 20], ["epoch", 20], ["ascension", 100]] as const) {
|
|
const criticalBlock = enemyBlockAuraContract(47294, profile)!;
|
|
expect(criticalBlock).toMatchObject({
|
|
durationMs: null,
|
|
signature: { durationMs: -1 },
|
|
blockInputs: { criticalBlockChancePercent: chance },
|
|
});
|
|
expect(criticalBlock.effects.map(({ auraType, basePoints, miscValue }) => (
|
|
[auraType, basePoints, miscValue]
|
|
))).toEqual(profile === "ascension"
|
|
? [[SPELL_AURA_MOD_BLOCK_CRIT_CHANCE, 100, 0], [107, 4, 7], [107, 8, 3]]
|
|
: [[SPELL_AURA_MOD_BLOCK_CRIT_CHANCE, chance, 0], [107, 5, 7]]);
|
|
expect(criticalBlock.unsupportedEffects.map(({ auraType, basePoints, miscValue }) => (
|
|
[auraType, basePoints, miscValue]
|
|
))).toEqual(profile === "ascension" ? [[107, 4, 7], [107, 8, 3]] : [[107, 5, 7]]);
|
|
}
|
|
|
|
const wowDeterrence = enemyBlockAuraContract(67801, "wow335")!;
|
|
expect(wowDeterrence).toMatchObject({
|
|
durationMs: 5_000,
|
|
blockInputs: { ignoresHitDirection: true },
|
|
});
|
|
expect(wowDeterrence.effects.map(({ auraType, basePoints }) => [auraType, basePoints]))
|
|
.toEqual([[SPELL_AURA_IGNORE_HIT_DIRECTION, 100], [185, -100]]);
|
|
expect(enemyBlockAuraContract(67801, "ascension")?.durationMs).toBe(60_000);
|
|
expect(enemyBlockAuraContract(667801, "ascension")?.durationMs).toBe(5_000);
|
|
expect(enemyBlockAuraContract(1167801, "ascension")?.effects[0]).toMatchObject({
|
|
auraType: SPELL_AURA_IGNORE_HIT_DIRECTION,
|
|
basePoints: 100,
|
|
});
|
|
expect(enemyBlockAuraContract(47929, "wow335")).toBeUndefined();
|
|
});
|
|
|
|
it("only imports complete aura definitions and keeps input-only siblings explicit", () => {
|
|
const improvedBlocking = enemyBlockAuraContract(3419, "epoch")!;
|
|
expect(supportedEnemyBlockAuraDefinition(improvedBlocking)).toMatchObject({
|
|
id: "enemy-source-spell-3419",
|
|
utilityTags: ["enemy-npc-block-aura:51:75"],
|
|
});
|
|
for (const profile of ["wow335", "ascension", "epoch"] as const) {
|
|
expect(supportedEnemyBlockAuraDefinition(enemyBlockAuraContract(47294, profile)!)).toBeUndefined();
|
|
}
|
|
expect(supportedEnemyBlockAuraDefinition(enemyBlockAuraContract(67801, "wow335")!)).toBeUndefined();
|
|
|
|
const definition = enemyBlockAuraInputDefinition(enemyBlockAuraContract(47295, "epoch")!);
|
|
expect(definition).toMatchObject({
|
|
id: "enemy-source-spell-47295",
|
|
name: "Critical Block",
|
|
durationMs: null,
|
|
disposition: "buff",
|
|
utilityTags: ["enemy-npc-block-aura:253:40"],
|
|
});
|
|
expect(enemyBlockAuraInputDefinition(enemyBlockAuraContract(67801, "epoch")!).utilityTags)
|
|
.toContain("enemy-npc-block-aura:288:0");
|
|
});
|
|
|
|
it("imports reviewed Critical Block rows only for an ordinary Creature self-target proof", () => {
|
|
const expectedChances = {
|
|
wow335: [20, 40, 60],
|
|
ascension: [100, 100, 100],
|
|
epoch: [20, 40, 60],
|
|
} as const;
|
|
|
|
for (const profile of ["wow335", "ascension", "epoch"] as const) {
|
|
for (const [rankIndex, spellId] of [47294, 47295, 47296].entries()) {
|
|
const contract = enemyBlockAuraContract(spellId, profile)!;
|
|
const imported = ordinaryCreatureSelfTargetCriticalBlockAuraDefinition(
|
|
contract,
|
|
ordinaryCreatureSelfTargetProof(),
|
|
);
|
|
expect(imported).toMatchObject({
|
|
id: `enemy-source-spell-${spellId}`,
|
|
name: "Critical Block",
|
|
durationMs: null,
|
|
disposition: "buff",
|
|
utilityTags: [`enemy-npc-block-aura:${SPELL_AURA_MOD_BLOCK_CRIT_CHANCE}:${expectedChances[profile][rankIndex]}`],
|
|
});
|
|
// The generic complete-spell importer remains closed for these rows.
|
|
expect(supportedEnemyBlockAuraDefinition(contract)).toBeUndefined();
|
|
}
|
|
}
|
|
|
|
const proof = ordinaryCreatureSelfTargetProof();
|
|
const validContract = enemyBlockAuraContract(47294, "wow335")!;
|
|
const invalidProofs: readonly unknown[] = [
|
|
null,
|
|
{},
|
|
{ ...proof, defenderKind: "player" },
|
|
{ ...proof, isNativeDefender: true },
|
|
{ ...proof, isSummonedDefender: true },
|
|
{ ...proof, isCharacterDefender: true },
|
|
{ ...proof, aura107Applicability: "custom-server-script" },
|
|
{ ...proof, defenderId: "" },
|
|
{ ...proof, casterId: "different-caster" },
|
|
{ ...proof, targetId: "different-target" },
|
|
];
|
|
for (const invalidProof of invalidProofs) {
|
|
expect(ordinaryCreatureSelfTargetCriticalBlockAuraDefinition(
|
|
validContract,
|
|
invalidProof as EnemyNpcCriticalBlockImportProof,
|
|
)).toBeUndefined();
|
|
}
|
|
|
|
expect(ordinaryCreatureSelfTargetCriticalBlockAuraDefinition(undefined, proof)).toBeUndefined();
|
|
expect(ordinaryCreatureSelfTargetCriticalBlockAuraDefinition(null, proof)).toBeUndefined();
|
|
const invalidContracts: readonly unknown[] = [
|
|
{},
|
|
{ ...validContract, profile: "unreviewed-profile" },
|
|
{ ...validContract, blockInputs: null },
|
|
{ ...validContract, signature: null },
|
|
];
|
|
for (const invalidContract of invalidContracts) {
|
|
expect(ordinaryCreatureSelfTargetCriticalBlockAuraDefinition(
|
|
invalidContract as typeof validContract,
|
|
proof,
|
|
)).toBeUndefined();
|
|
}
|
|
expect(ordinaryCreatureSelfTargetCriticalBlockAuraDefinition(
|
|
enemyBlockAuraContract(67801, "wow335"),
|
|
proof,
|
|
)).toBeUndefined();
|
|
expect(ordinaryCreatureSelfTargetCriticalBlockAuraDefinition(
|
|
enemyBlockAuraContract(3419, "wow335"),
|
|
proof,
|
|
)).toBeUndefined();
|
|
});
|
|
|
|
it("fails closed when a Critical Block raw source signature drifts", () => {
|
|
const contract = enemyBlockAuraContract(47294, "ascension")!;
|
|
const proof = ordinaryCreatureSelfTargetProof();
|
|
const changedRawSignature = {
|
|
...contract,
|
|
signature: {
|
|
...contract.signature,
|
|
effects: contract.signature.effects.map((sourceEffect, index) => (
|
|
index === 2 ? { ...sourceEffect, miscValue: 7 } : sourceEffect
|
|
)),
|
|
},
|
|
};
|
|
expect(ordinaryCreatureSelfTargetCriticalBlockAuraDefinition(
|
|
changedRawSignature,
|
|
proof,
|
|
)).toBeUndefined();
|
|
|
|
const changedContractEffects = {
|
|
...contract,
|
|
effects: contract.effects.map((sourceEffect, index) => (
|
|
index === 1 ? { ...sourceEffect, basePoints: 99 } : sourceEffect
|
|
)),
|
|
};
|
|
expect(ordinaryCreatureSelfTargetCriticalBlockAuraDefinition(
|
|
changedContractEffects,
|
|
proof,
|
|
)).toBeUndefined();
|
|
});
|
|
|
|
it("sums target-local active aura inputs and ignores expired or unrelated auras", () => {
|
|
let auras: readonly ActiveAura[] = [];
|
|
auras = addAura(auras, enemyBlockAuraInputDefinition(enemyBlockAuraContract(3419, "epoch")!), "mob-a", 100);
|
|
auras = addAura(auras, enemyBlockAuraInputDefinition(enemyBlockAuraContract(47294, "epoch")!), "mob-a", 100);
|
|
auras = addAura(auras, enemyBlockAuraInputDefinition(enemyBlockAuraContract(67801, "epoch")!), "mob-a", 100);
|
|
auras = addAura(auras, enemyBlockAuraInputDefinition(enemyBlockAuraContract(47296, "epoch")!), "mob-b", 100);
|
|
|
|
const expiry = enemyBlockAuraInputDefinition({
|
|
...enemyBlockAuraContract(47295, "epoch")!,
|
|
durationMs: 10,
|
|
});
|
|
auras = addAura(auras, expiry, "mob-a", 100);
|
|
|
|
expect(resolveEnemyNpcBlockAuraInputs({ auras, targetId: "mob-a", now: 111 })).toEqual({
|
|
blockChanceAuraPercent: 75,
|
|
criticalBlockChancePercent: 20,
|
|
ignoresHitDirection: true,
|
|
});
|
|
expect(resolveEnemyNpcBlockAuraInputs({ auras, targetId: "mob-a", now: 109 })?.criticalBlockChancePercent)
|
|
.toBe(60);
|
|
});
|
|
|
|
it("adapts critical-block chance to integer roll_chance_i and doubles only a successful block value", () => {
|
|
expect(enemyNpcCriticalBlockChanceForRoll(19.9)).toBe(19);
|
|
expect(enemyNpcCriticalBlockChanceForRoll(-2.7)).toBe(-2);
|
|
expect(enemyNpcCriticalBlockChanceForRoll(Number.NaN)).toBeNull();
|
|
expect(enemyNpcBlockValueAfterCritical(39, false)).toBe(39);
|
|
expect(enemyNpcBlockValueAfterCritical(39, true)).toBe(78);
|
|
expect(enemyNpcBlockValueAfterCritical(Number.MAX_SAFE_INTEGER, true)).toBeNull();
|
|
expect(enemyNpcBlockValueAfterCritical(-1, true)).toBeNull();
|
|
});
|
|
});
|