forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
1 parent
6114a2295f
commit
69809f7228
593 files changed
+903643
-40774
No files matched your search
@@ -0,0 +1,223 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { applyAura, type ActiveAura } from "./combatAuras";
|
||||
import { EMPTY_ITEM_STATS } from "./itemStats";
|
||||
import { WOW335_ABILITIES_BY_CLASS } from "./wow335AbilityCatalog";
|
||||
import { enemyWeaponDebuffAuraDefinition, enemyWeaponDebuffContract } from "./enemyWeaponDebuffs";
|
||||
import {
|
||||
activeEnemyDisarmChannels,
|
||||
enemyDisarmAurasForWeaponChannel,
|
||||
enemyDisarmAbilityWeaponChannel,
|
||||
enemyDisarmAdjustedAbilityWeapon,
|
||||
enemyDisarmAdjustedWeaponStats,
|
||||
enemyDisarmBlocksAbilityCast,
|
||||
enemyDisarmBlocksNpcWeaponAbility,
|
||||
enemyDisarmItemDependentAuraSuppressionIds,
|
||||
enemyDisarmInvalidatingProfile,
|
||||
enemyDisarmRequirementCandidates,
|
||||
enemyDisarmTargetWeaponsFromLoadout,
|
||||
} from "./enemyDisarmRuntime";
|
||||
|
||||
const weapon = (weaponType: string, inventorySlot = "one-hand") => ({
|
||||
weaponType,
|
||||
inventorySlot,
|
||||
stats: { ...EMPTY_ITEM_STATS, meleeWeapon: { damageMin: 10, damageMax: 20, damageSchool: "physical" as const, speedMs: 2_000 } },
|
||||
});
|
||||
|
||||
function disarmAuras(profile: "ascension" | "epoch", targetId = "actor"): ActiveAura[] {
|
||||
const definition = enemyWeaponDebuffAuraDefinition(enemyWeaponDebuffContract(6713, profile)!);
|
||||
return [...applyAura([], { definition, sourceId: "mob", targetId, now: 100 }).auras];
|
||||
}
|
||||
|
||||
describe("source-bound character Disarm runtime predicates", () => {
|
||||
it("projects the corrected WotLK DBC equipped weapon requirement onto ranks", () => {
|
||||
const sinisterStrike = WOW335_ABILITIES_BY_CLASS.rogue.find((ability) => ability.name === "Sinister Strike");
|
||||
expect(sinisterStrike?.ranks?.[0]).toMatchObject({
|
||||
spellId: 1752,
|
||||
wowWeaponRequirements: {
|
||||
equippedItemClass: 2,
|
||||
equippedItemSubclassMask: 173_555,
|
||||
equippedItemInventoryMask: 0,
|
||||
},
|
||||
});
|
||||
expect(WOW335_ABILITIES_BY_CLASS.rogue.find((ability) => ability.name === "Eviscerate")?.ranks?.[0]
|
||||
?.wowWeaponRequirements).toMatchObject({ equippedItemClass: 2 });
|
||||
});
|
||||
|
||||
it("derives active channels by target and expiration without recognizing unrelated/native tags", () => {
|
||||
const ascension = disarmAuras("ascension");
|
||||
expect([...activeEnemyDisarmChannels(ascension, "actor", 101)].sort()).toEqual(["main-hand", "ranged"]);
|
||||
expect([...activeEnemyDisarmChannels(ascension, "actor", 5_100)]).toEqual([]);
|
||||
expect([...activeEnemyDisarmChannels(ascension, "other", 101)]).toEqual([]);
|
||||
const native = applyAura([], {
|
||||
definition: { id: "native-disarm", name: "Native", disposition: "debuff", durationMs: 10_000,
|
||||
utilityTags: ["rom-disarm", "enemy-disarm:unknown:main-hand"], modifiers: [] },
|
||||
sourceId: "mob", targetId: "actor", now: 100,
|
||||
}).auras;
|
||||
expect([...activeEnemyDisarmChannels(native, "actor", 101)]).toEqual([]);
|
||||
});
|
||||
|
||||
it("projects only source item-dependent passive auras with no usable alternate weapon", () => {
|
||||
const itemAura = { id: "weapon-passive-aura", name: "Weapon passive", disposition: "buff" as const,
|
||||
durationMs: null, modifiers: [{ kind: "stat" as const, stat: "criticalStrikeRating",
|
||||
operation: "flat" as const, value: 20 }] };
|
||||
const active = applyAura([], { definition: itemAura, sourceId: "actor", targetId: "ally", now: 100 }).auras;
|
||||
const otherCaster = applyAura(active, { definition: itemAura, sourceId: "other", targetId: "ally", now: 100 }).auras;
|
||||
const disarmed = [...otherCaster, ...disarmAuras("ascension")];
|
||||
const actorTargetedAura = applyAura(disarmed, {
|
||||
definition: itemAura, sourceId: "other", targetId: "actor", now: 100,
|
||||
}).auras;
|
||||
const passiveRequirement = [{ auraDefinitionId: itemAura.id, equippedItemClass: 2,
|
||||
equippedItemSubclassMask: 1 << 15, passive: true }];
|
||||
|
||||
const mainHandOnly = enemyDisarmItemDependentAuraSuppressionIds(
|
||||
disarmed, "actor", 101, passiveRequirement, { "main-hand": weapon("dagger") },
|
||||
);
|
||||
expect([...mainHandOnly]).toEqual(["ally:weapon-passive-aura:actor"]);
|
||||
expect(mainHandOnly.has("ally:weapon-passive-aura:other")).toBe(false);
|
||||
expect(enemyDisarmAurasForWeaponChannel(
|
||||
actorTargetedAura, "actor", 101, "main-hand", passiveRequirement, { "main-hand": weapon("dagger") },
|
||||
).filter((aura) => aura.targetId === "actor" && aura.definition.id === itemAura.id)).toEqual([]);
|
||||
expect(enemyDisarmAurasForWeaponChannel(
|
||||
actorTargetedAura, "actor", 101, "off-hand", passiveRequirement,
|
||||
{ "main-hand": weapon("dagger"), "off-hand": weapon("dagger", "off-hand") },
|
||||
).filter((aura) => aura.targetId === "actor" && aura.definition.id === itemAura.id)).toHaveLength(1);
|
||||
|
||||
expect(enemyDisarmItemDependentAuraSuppressionIds(
|
||||
disarmed, "actor", 101, passiveRequirement,
|
||||
{ "main-hand": weapon("dagger"), "off-hand": weapon("dagger", "off-hand") },
|
||||
).size).toBe(0);
|
||||
expect(enemyDisarmItemDependentAuraSuppressionIds(
|
||||
disarmed, "actor", 101,
|
||||
[{ ...passiveRequirement[0], passive: false }],
|
||||
{ "main-hand": weapon("dagger") },
|
||||
).size).toBe(0);
|
||||
expect(enemyDisarmItemDependentAuraSuppressionIds(
|
||||
disarmed, "actor", 101, passiveRequirement, { "main-hand": weapon("dagger") }, true,
|
||||
).size).toBe(0);
|
||||
expect(enemyDisarmAurasForWeaponChannel(
|
||||
actorTargetedAura, "actor", 101, "main-hand", passiveRequirement,
|
||||
{ "main-hand": weapon("dagger") }, true,
|
||||
).filter((aura) => aura.targetId === "actor" && aura.definition.id === itemAura.id)).toHaveLength(1);
|
||||
expect(enemyDisarmItemDependentAuraSuppressionIds(
|
||||
disarmed, "actor", 5_100, passiveRequirement, { "main-hand": weapon("dagger") },
|
||||
).size).toBe(0);
|
||||
});
|
||||
|
||||
it("derives target usability and only suppresses disarmed weapon fields in an ephemeral stats view", () => {
|
||||
const main = weapon("dagger");
|
||||
const off = weapon("mace");
|
||||
const ranged = { weaponType: "bow", inventorySlot: "ranged",
|
||||
stats: { ...EMPTY_ITEM_STATS, rangedWeapon: { damageMin: 8, damageMax: 12, damageSchool: "physical" as const, speedMs: 2_500 } } };
|
||||
expect(enemyDisarmTargetWeaponsFromLoadout({ "main-hand": main, "off-hand": off, ranged })).toEqual({
|
||||
mainHand: true, offHand: true, ranged: true, feralForm: false,
|
||||
});
|
||||
expect(enemyDisarmTargetWeaponsFromLoadout({ "main-hand": main }, true).feralForm).toBe(true);
|
||||
|
||||
const channels = activeEnemyDisarmChannels(disarmAuras("ascension"), "actor", 101);
|
||||
const adjusted = enemyDisarmAdjustedWeaponStats({ ...EMPTY_ITEM_STATS, meleeWeapon: main.stats.meleeWeapon,
|
||||
rangedWeapon: ranged.stats.rangedWeapon, strength: 14 }, channels);
|
||||
expect(adjusted).toMatchObject({
|
||||
meleeWeapon: { damageMin: 1, damageMax: 2, damageSchool: "physical", speedMs: 2_000 },
|
||||
rangedWeapon: null,
|
||||
strength: 14,
|
||||
});
|
||||
expect(enemyDisarmAdjustedWeaponStats(
|
||||
{ ...EMPTY_ITEM_STATS, meleeWeapon: main.stats.meleeWeapon }, channels, true,
|
||||
).meleeWeapon).toEqual(main.stats.meleeWeapon);
|
||||
expect(EMPTY_ITEM_STATS.meleeWeapon).toBeNull();
|
||||
});
|
||||
|
||||
it("blocks weapon-required casts only when all matching weapon slots are disabled", () => {
|
||||
const candidates = enemyDisarmRequirementCandidates({ "main-hand": weapon("dagger") });
|
||||
const dagger = { equippedItemClass: 2, equippedItemSubclassMask: 1 << 15 };
|
||||
expect(enemyDisarmInvalidatingProfile(disarmAuras("ascension"), "actor", 101, dagger, candidates)).toBe("ascension");
|
||||
expect(enemyDisarmInvalidatingProfile(disarmAuras("epoch"), "actor", 101, dagger, candidates)).toBe("epoch");
|
||||
const dualWieldCandidates = enemyDisarmRequirementCandidates({
|
||||
"main-hand": weapon("dagger"), "off-hand": weapon("dagger"),
|
||||
});
|
||||
expect(enemyDisarmInvalidatingProfile(disarmAuras("ascension"), "actor", 101, dagger, dualWieldCandidates)).toBeUndefined();
|
||||
expect(enemyDisarmInvalidatingProfile(disarmAuras("ascension"), "actor", 101,
|
||||
{ ...dagger, equippedItemInventoryMask: 1 << 21 }, candidates)).toBeUndefined();
|
||||
expect(enemyDisarmInvalidatingProfile(disarmAuras("ascension"), "actor", 101,
|
||||
{ equippedItemClass: -1, equippedItemSubclassMask: 0 }, candidates)).toBeUndefined();
|
||||
expect(enemyDisarmInvalidatingProfile(disarmAuras("ascension"), "actor", 101,
|
||||
{ ...dagger, equippedItemSubclassMask: 1 << 4 }, candidates)).toBeUndefined();
|
||||
expect(enemyDisarmBlocksAbilityCast(disarmAuras("ascension"), "actor", 101, {
|
||||
...dagger, createsAura: true,
|
||||
}, candidates)).toBe(false);
|
||||
expect(enemyDisarmBlocksAbilityCast(disarmAuras("ascension"), "actor", 101, {
|
||||
...dagger, createsAura: true, attributes3: 0x00000400,
|
||||
}, candidates)).toBe(true);
|
||||
expect(enemyDisarmBlocksAbilityCast(disarmAuras("ascension"), "actor", 101, {
|
||||
...dagger, createsAura: true, attributes3: 0x01000000,
|
||||
}, candidates)).toBe(false);
|
||||
expect(enemyDisarmBlocksAbilityCast(disarmAuras("ascension"), "actor", 101, {
|
||||
...dagger, passive: true,
|
||||
}, candidates)).toBe(true);
|
||||
expect(enemyDisarmBlocksAbilityCast(disarmAuras("ascension"), "actor", 101, {
|
||||
...dagger, attributes3: 0x00000400 | 0x01000000,
|
||||
}, [
|
||||
...candidates,
|
||||
{ channel: "off-hand", itemClass: 2, subclass: 15 },
|
||||
])).toBe(true);
|
||||
|
||||
const offHandOnlyAura = applyAura([], {
|
||||
definition: {
|
||||
id: "source-offhand-test",
|
||||
name: "Synthetic off-hand channel test",
|
||||
disposition: "debuff",
|
||||
durationMs: 5_000,
|
||||
utilityTags: ["enemy-disarm:epoch:off-hand"],
|
||||
modifiers: [],
|
||||
},
|
||||
sourceId: "mob",
|
||||
targetId: "actor",
|
||||
now: 100,
|
||||
}).auras;
|
||||
expect(enemyDisarmBlocksAbilityCast(offHandOnlyAura, "actor", 101, {
|
||||
...dagger, attributes3: 0x00000400 | 0x01000000,
|
||||
}, [
|
||||
...candidates,
|
||||
{ channel: "off-hand", itemClass: 2, subclass: 15 },
|
||||
])).toBe(true);
|
||||
});
|
||||
|
||||
it("selects the DBC attack channel and changes only that ability's weapon contribution", () => {
|
||||
const sourceHeroicStrike = WOW335_ABILITIES_BY_CLASS.warrior
|
||||
.find((ability) => ability.name === "Heroic Strike")!.ranks![0].wowSpellDefense!;
|
||||
const sourceSteadyShot = WOW335_ABILITIES_BY_CLASS.hunter
|
||||
.find((ability) => ability.name === "Steady Shot")!.ranks![0].wowSpellDefense!;
|
||||
expect(enemyDisarmAbilityWeaponChannel({ ...sourceHeroicStrike, damageClass: 2 })).toBe("main-hand");
|
||||
expect(enemyDisarmAbilityWeaponChannel({ ...sourceSteadyShot, damageClass: 3,
|
||||
equippedItemSubclassMask: 1 << 2 })).toBe("ranged");
|
||||
expect(enemyDisarmAbilityWeaponChannel({ damageClass: 2, attributes3: 0x01000000 })).toBe("off-hand");
|
||||
expect(enemyDisarmAbilityWeaponChannel({ damageClass: 3, attributes0: 0x00000002 })).toBe("ranged");
|
||||
expect(enemyDisarmAbilityWeaponChannel({ damageClass: 3, isRangedWeaponSpell: false })).toBe("main-hand");
|
||||
expect(enemyDisarmAbilityWeaponChannel({ damageClass: 1 })).toBeUndefined();
|
||||
|
||||
const weaponStats = weapon("dagger").stats!.meleeWeapon!;
|
||||
const ascensionChannels = activeEnemyDisarmChannels(disarmAuras("ascension"), "actor", 101);
|
||||
expect(enemyDisarmAdjustedAbilityWeapon(weaponStats, "main-hand", ascensionChannels)).toEqual({
|
||||
damageMin: 1, damageMax: 2, damageSchool: "physical", speedMs: 2_000,
|
||||
});
|
||||
expect(enemyDisarmAdjustedAbilityWeapon(weaponStats, "off-hand", ascensionChannels)).toEqual(weaponStats);
|
||||
expect(enemyDisarmAdjustedAbilityWeapon(weaponStats, "off-hand",
|
||||
new Set(["off-hand"]))).toBeNull();
|
||||
expect(enemyDisarmAdjustedAbilityWeapon(weaponStats, "ranged", ascensionChannels)).toBeNull();
|
||||
expect(enemyDisarmAdjustedAbilityWeapon(weaponStats, "main-hand", ascensionChannels, true)).toEqual(weaponStats);
|
||||
});
|
||||
|
||||
it("applies the creature cast gate only to the selected disarmed weapon channel", () => {
|
||||
const ascension = disarmAuras("ascension");
|
||||
expect(enemyDisarmBlocksNpcWeaponAbility(ascension, "actor", 101,
|
||||
{ damageClass: 2 })).toBe(true);
|
||||
expect(enemyDisarmBlocksNpcWeaponAbility(ascension, "actor", 101,
|
||||
{ damageClass: 2, attributes3: 0x01000000 })).toBe(false);
|
||||
expect(enemyDisarmBlocksNpcWeaponAbility(ascension, "actor", 101,
|
||||
{ damageClass: 3, attributes0: 0x00000002 })).toBe(true);
|
||||
expect(enemyDisarmBlocksNpcWeaponAbility(ascension, "actor", 101,
|
||||
{ damageClass: 1 })).toBe(false);
|
||||
expect(enemyDisarmBlocksNpcWeaponAbility(disarmAuras("epoch"), "actor", 101,
|
||||
{ damageClass: 3, isRangedWeaponSpell: true })).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user