forked from phenom/healer-man
302 lines
12 KiB
TypeScript
302 lines
12 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { enemySourceAuraContract } from "./enemySourceAuras";
|
|
import {
|
|
canEnemyNpcBlock,
|
|
canEnemyNpcBlockSpell,
|
|
classifyEnemyNpcSpellBlockPhase,
|
|
CREATURE_FLAG_EXTRA_NO_BLOCK,
|
|
enemyNpcSourceSpellBlockMetadata,
|
|
enemyNpcBlockChanceAfterSkill,
|
|
enemyNpcSpellBlockChanceAfterSkill,
|
|
resolveEnemyNpcBlockStats,
|
|
SPELL_ATTR0_CU_DIRECT_DAMAGE,
|
|
SPELL_ATTR0_CU_BLOCKING_KNOWN_MASK,
|
|
SPELL_ATTR0_CU_REQ_CASTER_BEHIND_TARGET,
|
|
SPELL_ATTR0_NO_ACTIVE_DEFENSE,
|
|
SPELL_ATTR3_ALWAYS_HIT,
|
|
SPELL_ATTR3_COMPLETELY_BLOCKED,
|
|
SPELL_DAMAGE_CLASS_MAGIC,
|
|
SPELL_DAMAGE_CLASS_MELEE,
|
|
SPELL_DAMAGE_CLASS_RANGED,
|
|
SPELL_SCHOOL_MASK_NORMAL,
|
|
} from "./enemyBlocking";
|
|
|
|
const baseWeaponInput = {
|
|
attack: "melee-auto" as const,
|
|
blockPercent: 5,
|
|
flagsExtra: 0,
|
|
isTotem: false,
|
|
attackerLevel: 80,
|
|
defenderLevel: 80,
|
|
attackerIsBehind: false,
|
|
ignoresHitDirection: false,
|
|
isCastingNonMeleeSpell: false,
|
|
isControlled: false,
|
|
isEvadingAttacks: false,
|
|
};
|
|
|
|
const physicalDirectSpell = {
|
|
damageClass: SPELL_DAMAGE_CLASS_MELEE,
|
|
schoolMask: SPELL_SCHOOL_MASK_NORMAL,
|
|
attributes0: 0,
|
|
attributes3: 0,
|
|
directDamageFromEffects: true,
|
|
sourceProfile: "wow335" as const,
|
|
};
|
|
|
|
const baseSpellInput = {
|
|
spell: physicalDirectSpell,
|
|
blockPercent: 5,
|
|
flagsExtra: 0,
|
|
isTotem: false,
|
|
attackerWeaponSkill: 400,
|
|
defenderLevel: 80,
|
|
attackerIsBehind: false,
|
|
ignoresHitDirection: false,
|
|
isCastingNonMeleeSpell: false,
|
|
isControlled: false,
|
|
};
|
|
|
|
describe("source-backed enemy NPC blocking", () => {
|
|
it("uses the ordinary-creature base chance and inherent level-only block value", () => {
|
|
expect(resolveEnemyNpcBlockStats({
|
|
level: 79,
|
|
blockChanceAuraPercent: 0,
|
|
isTotem: false,
|
|
})).toEqual({ blockPercent: 5, blockValue: 39 });
|
|
});
|
|
|
|
it("adds Improved Blocking's sourced 75 percentage points in both profiles", () => {
|
|
for (const profile of ["ascension", "epoch"] as const) {
|
|
const contract = enemySourceAuraContract(3419, profile);
|
|
expect(contract?.durationMs).toBe(6_000);
|
|
expect(contract?.signature.effects).toContainEqual(expect.objectContaining({
|
|
auraType: 51,
|
|
basePoints: 75,
|
|
}));
|
|
expect(resolveEnemyNpcBlockStats({
|
|
level: 60,
|
|
blockChanceAuraPercent: contract!.signature.effects[0]!.basePoints,
|
|
isTotem: false,
|
|
})).toEqual({ blockPercent: 80, blockValue: 30 });
|
|
}
|
|
});
|
|
|
|
it("keeps totems at zero block chance and clamps negative aura totals", () => {
|
|
expect(resolveEnemyNpcBlockStats({
|
|
level: 60,
|
|
blockChanceAuraPercent: 75,
|
|
isTotem: true,
|
|
})).toEqual({ blockPercent: 0, blockValue: 30 });
|
|
expect(resolveEnemyNpcBlockStats({
|
|
level: 60,
|
|
blockChanceAuraPercent: -10,
|
|
isTotem: false,
|
|
})?.blockPercent).toBe(0);
|
|
});
|
|
|
|
it("fails closed outside the simulator level cap or for unsourced numeric inputs", () => {
|
|
expect(resolveEnemyNpcBlockStats({
|
|
level: 81,
|
|
blockChanceAuraPercent: 75,
|
|
isTotem: false,
|
|
})).toBeNull();
|
|
expect(resolveEnemyNpcBlockStats({
|
|
level: 60,
|
|
blockChanceAuraPercent: Number.NaN,
|
|
isTotem: false,
|
|
})).toBeNull();
|
|
expect(enemyNpcBlockChanceAfterSkill({ blockPercent: 5, attackerLevel: 81, defenderLevel: 80 })).toBeNull();
|
|
});
|
|
|
|
it("gates creature no-block, rear, casting, controlled, evading, and nonpositive skill-adjusted chances", () => {
|
|
expect(canEnemyNpcBlock({ ...baseWeaponInput, flagsExtra: CREATURE_FLAG_EXTRA_NO_BLOCK })).toBe(false);
|
|
expect(canEnemyNpcBlock({ ...baseWeaponInput, attackerIsBehind: true })).toBe(false);
|
|
expect(canEnemyNpcBlock({ ...baseWeaponInput, isCastingNonMeleeSpell: true })).toBe(false);
|
|
expect(canEnemyNpcBlock({ ...baseWeaponInput, isControlled: true })).toBe(false);
|
|
expect(canEnemyNpcBlock({ ...baseWeaponInput, isEvadingAttacks: true })).toBe(false);
|
|
expect(canEnemyNpcBlock({ ...baseWeaponInput, attackerLevel: 81 })).toBe(false);
|
|
expect(canEnemyNpcBlock({ ...baseWeaponInput, isTotem: true })).toBe(false);
|
|
});
|
|
|
|
it("allows ranged weapon blocks and applies the sourced four-hundredths skill adjustment", () => {
|
|
expect(canEnemyNpcBlock({
|
|
...baseWeaponInput,
|
|
attack: "ranged-auto",
|
|
})).toBe(true);
|
|
expect(enemyNpcBlockChanceAfterSkill({ blockPercent: 80, attackerLevel: 70, defenderLevel: 80 })).toBe(82);
|
|
expect(enemyNpcBlockChanceAfterSkill({ blockPercent: 80, attackerLevel: 80, defenderLevel: 70 })).toBe(78);
|
|
expect(enemyNpcBlockChanceAfterSkill({ blockPercent: 5, attackerLevel: 80, defenderLevel: 55 })).toBe(0);
|
|
expect(enemyNpcBlockChanceAfterSkill({ blockPercent: 0, attackerLevel: 1, defenderLevel: 80 })).toBe(0);
|
|
});
|
|
|
|
it("fails closed for spell and other packets until spell attributes are imported", () => {
|
|
expect(canEnemyNpcBlock({ ...baseWeaponInput, attack: "spell" })).toBe(false);
|
|
expect(canEnemyNpcBlock({ ...baseWeaponInput, attack: "other" })).toBe(false);
|
|
});
|
|
|
|
it("classifies source-backed physical direct-damage melee and ranged spells for post-hit blocking", () => {
|
|
expect(classifyEnemyNpcSpellBlockPhase(physicalDirectSpell)).toBe("direct-damage");
|
|
expect(classifyEnemyNpcSpellBlockPhase({
|
|
...physicalDirectSpell,
|
|
damageClass: SPELL_DAMAGE_CLASS_RANGED,
|
|
sourceProfile: "ascension",
|
|
})).toBe("direct-damage");
|
|
expect(classifyEnemyNpcSpellBlockPhase({
|
|
...physicalDirectSpell,
|
|
damageClass: SPELL_DAMAGE_CLASS_MAGIC,
|
|
})).toBe("none");
|
|
expect(classifyEnemyNpcSpellBlockPhase({
|
|
...physicalDirectSpell,
|
|
schoolMask: 0x10,
|
|
})).toBe("none");
|
|
expect(classifyEnemyNpcSpellBlockPhase({
|
|
...physicalDirectSpell,
|
|
attributes0: SPELL_ATTR0_NO_ACTIVE_DEFENSE,
|
|
})).toBe("none");
|
|
expect(classifyEnemyNpcSpellBlockPhase({
|
|
...physicalDirectSpell,
|
|
attributes3: SPELL_ATTR3_ALWAYS_HIT,
|
|
})).toBe("none");
|
|
});
|
|
|
|
it("requires explicit AzerothCore custom attributes before enabling a hit-table full block", () => {
|
|
const nonDirect = {
|
|
...physicalDirectSpell,
|
|
directDamageFromEffects: false,
|
|
attributes3: SPELL_ATTR3_COMPLETELY_BLOCKED,
|
|
};
|
|
expect(classifyEnemyNpcSpellBlockPhase(nonDirect)).toBe("none");
|
|
expect(classifyEnemyNpcSpellBlockPhase({ ...nonDirect, customAttributes: 0 })).toBe("hit-table");
|
|
expect(classifyEnemyNpcSpellBlockPhase({
|
|
...nonDirect,
|
|
damageClass: SPELL_DAMAGE_CLASS_MAGIC,
|
|
customAttributes: 0,
|
|
})).toBe("none");
|
|
expect(classifyEnemyNpcSpellBlockPhase({
|
|
...nonDirect,
|
|
attributes0: SPELL_ATTR0_NO_ACTIVE_DEFENSE,
|
|
customAttributes: 0,
|
|
})).toBe("none");
|
|
expect(classifyEnemyNpcSpellBlockPhase({
|
|
...nonDirect,
|
|
attributes3: SPELL_ATTR3_COMPLETELY_BLOCKED | SPELL_ATTR3_ALWAYS_HIT,
|
|
customAttributes: 0,
|
|
})).toBe("none");
|
|
expect(classifyEnemyNpcSpellBlockPhase({
|
|
...nonDirect,
|
|
customAttributes: SPELL_ATTR0_CU_DIRECT_DAMAGE,
|
|
})).toBe("direct-damage");
|
|
expect(classifyEnemyNpcSpellBlockPhase({ ...nonDirect, customAttributes: -1 })).toBeNull();
|
|
expect(classifyEnemyNpcSpellBlockPhase({ ...physicalDirectSpell, sourceProfile: "epoch" as never })).toBeNull();
|
|
});
|
|
|
|
it("uses only the two source-proven custom bits for the three exact utility ranks", () => {
|
|
const shieldBash = enemyNpcSourceSpellBlockMetadata(72, "wow335");
|
|
expect(shieldBash).toMatchObject({
|
|
damageClass: SPELL_DAMAGE_CLASS_MELEE,
|
|
schoolMask: SPELL_SCHOOL_MASK_NORMAL,
|
|
attributes0: 327696,
|
|
attributes3: SPELL_ATTR3_COMPLETELY_BLOCKED,
|
|
directDamageFromEffects: false,
|
|
sourceProfile: "wow335",
|
|
customAttributeEvidence: { knownMask: SPELL_ATTR0_CU_BLOCKING_KNOWN_MASK, value: 0 },
|
|
});
|
|
expect(classifyEnemyNpcSpellBlockPhase(shieldBash!)).toBe("hit-table");
|
|
expect(canEnemyNpcBlockSpell({
|
|
...baseSpellInput,
|
|
spell: shieldBash!,
|
|
attackerIsBehind: true,
|
|
ignoresHitDirection: true,
|
|
})).toBe(true);
|
|
expect(enemyNpcSourceSpellBlockMetadata(72, "ascension")).toBeUndefined();
|
|
expect(enemyNpcSourceSpellBlockMetadata(1766, "wow335")).toEqual(shieldBash);
|
|
expect(enemyNpcSourceSpellBlockMetadata(6552, "wow335")).toEqual(shieldBash);
|
|
expect(enemyNpcSourceSpellBlockMetadata(5246, "wow335")).toBeUndefined();
|
|
|
|
const unknownBehindBit = {
|
|
...shieldBash!,
|
|
customAttributeEvidence: { knownMask: SPELL_ATTR0_CU_DIRECT_DAMAGE, value: 0 },
|
|
};
|
|
expect(classifyEnemyNpcSpellBlockPhase(unknownBehindBit)).toBe("hit-table");
|
|
expect(canEnemyNpcBlockSpell({
|
|
...baseSpellInput,
|
|
spell: unknownBehindBit,
|
|
attackerIsBehind: true,
|
|
ignoresHitDirection: true,
|
|
})).toBe(false);
|
|
expect(classifyEnemyNpcSpellBlockPhase({
|
|
...shieldBash!,
|
|
customAttributes: 0,
|
|
customAttributeEvidence: { knownMask: SPELL_ATTR0_CU_BLOCKING_KNOWN_MASK, value: 0 },
|
|
})).toBe("hit-table");
|
|
expect(classifyEnemyNpcSpellBlockPhase({
|
|
...shieldBash!,
|
|
customAttributes: SPELL_ATTR0_CU_DIRECT_DAMAGE,
|
|
customAttributeEvidence: { knownMask: SPELL_ATTR0_CU_BLOCKING_KNOWN_MASK, value: 0 },
|
|
})).toBeNull();
|
|
});
|
|
|
|
it("uses the different source skill signs for hit-table and post-hit spell blocks", () => {
|
|
expect(enemyNpcSpellBlockChanceAfterSkill({
|
|
phase: "direct-damage", blockPercent: 5, attackerWeaponSkill: 450, defenderLevel: 80,
|
|
})).toBe(7);
|
|
expect(enemyNpcSpellBlockChanceAfterSkill({
|
|
phase: "hit-table", blockPercent: 5, attackerWeaponSkill: 450, defenderLevel: 80,
|
|
})).toBe(3);
|
|
expect(enemyNpcSpellBlockChanceAfterSkill({
|
|
phase: "direct-damage", blockPercent: 5, attackerWeaponSkill: 200, defenderLevel: 80,
|
|
})).toBe(0);
|
|
expect(enemyNpcSpellBlockChanceAfterSkill({
|
|
phase: "hit-table", blockPercent: 5, attackerWeaponSkill: 600, defenderLevel: 80,
|
|
})).toBe(0);
|
|
expect(enemyNpcSpellBlockChanceAfterSkill({
|
|
phase: "direct-damage", blockPercent: 0, attackerWeaponSkill: 450, defenderLevel: 80,
|
|
})).toBe(2);
|
|
expect(enemyNpcSpellBlockChanceAfterSkill({
|
|
phase: "hit-table", blockPercent: 0, attackerWeaponSkill: 350, defenderLevel: 80,
|
|
})).toBe(2);
|
|
});
|
|
|
|
it("applies direct-spell no-block, direction, cast, control, and skill gates", () => {
|
|
expect(canEnemyNpcBlockSpell(baseSpellInput)).toBe(true);
|
|
expect(canEnemyNpcBlockSpell({ ...baseSpellInput, flagsExtra: CREATURE_FLAG_EXTRA_NO_BLOCK })).toBe(false);
|
|
expect(canEnemyNpcBlockSpell({ ...baseSpellInput, isTotem: true })).toBe(false);
|
|
expect(canEnemyNpcBlockSpell({ ...baseSpellInput, attackerIsBehind: true })).toBe(false);
|
|
expect(canEnemyNpcBlockSpell({ ...baseSpellInput, attackerIsBehind: true, ignoresHitDirection: true })).toBe(true);
|
|
expect(canEnemyNpcBlockSpell({ ...baseSpellInput, isCastingNonMeleeSpell: true })).toBe(false);
|
|
expect(canEnemyNpcBlockSpell({ ...baseSpellInput, isControlled: true })).toBe(false);
|
|
expect(canEnemyNpcBlockSpell({ ...baseSpellInput, attackerWeaponSkill: 200 })).toBe(false);
|
|
expect(canEnemyNpcBlockSpell({
|
|
...baseSpellInput,
|
|
blockPercent: 0,
|
|
attackerWeaponSkill: 450,
|
|
})).toBe(true);
|
|
});
|
|
|
|
it("keeps hit-table spell direction exceptions and requires-behind attribute semantics", () => {
|
|
const hitTableSpell = {
|
|
damageClass: SPELL_DAMAGE_CLASS_MELEE,
|
|
schoolMask: SPELL_SCHOOL_MASK_NORMAL,
|
|
attributes0: 0,
|
|
attributes3: SPELL_ATTR3_COMPLETELY_BLOCKED,
|
|
directDamageFromEffects: false,
|
|
sourceProfile: "wow335" as const,
|
|
customAttributes: 0,
|
|
};
|
|
expect(canEnemyNpcBlockSpell({ ...baseSpellInput, spell: hitTableSpell })).toBe(true);
|
|
expect(canEnemyNpcBlockSpell({
|
|
...baseSpellInput,
|
|
spell: hitTableSpell,
|
|
attackerIsBehind: true,
|
|
ignoresHitDirection: true,
|
|
})).toBe(true);
|
|
expect(canEnemyNpcBlockSpell({
|
|
...baseSpellInput,
|
|
spell: { ...hitTableSpell, customAttributes: SPELL_ATTR0_CU_REQ_CASTER_BEHIND_TARGET },
|
|
attackerIsBehind: true,
|
|
ignoresHitDirection: true,
|
|
})).toBe(false);
|
|
});
|
|
});
|