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,248 @@
|
||||
import type { AbilityDefinition } from "./abilityCatalog";
|
||||
import type { ActiveAura } from "./combatAuras";
|
||||
|
||||
export type EnemyControlImmunityMechanic = "fear" | "stun";
|
||||
|
||||
export interface EnemyControlImmunityPurgeProof {
|
||||
readonly sourceProfile: "wow335";
|
||||
readonly sourceSpellId: 18499 | 46924;
|
||||
/** Mechanic immunities proven by the exact selected source aura/effect. */
|
||||
readonly grantsMechanics: readonly EnemyControlImmunityMechanic[];
|
||||
/** Every named shout aura below has spell-level Mechanic=FEAR. */
|
||||
readonly purgesNamedShoutSpellMechanics: readonly "fear"[];
|
||||
/** Core cast-prevention bypasses proven for this selected spell. */
|
||||
readonly usableWhileControlledBy: readonly EnemyControlImmunityMechanic[];
|
||||
}
|
||||
|
||||
const IMMUNITY_PURGES_EFFECT = 0x00008000;
|
||||
const NAMED_SHOUT_AURA_IDS = new Set([
|
||||
"enemy-control:source-spell-5246",
|
||||
"enemy-control:source-spell-19134",
|
||||
"enemy-control:source-spell-20511",
|
||||
"enemy-control:source-spell-29544",
|
||||
]);
|
||||
const VERIFIED_PROOFS = new WeakSet<object>();
|
||||
|
||||
type ExactApplyAura = Extract<AbilityDefinition["effects"][number], { kind: "apply-aura" }>;
|
||||
|
||||
function isExactApplyAura(
|
||||
effect: AbilityDefinition["effects"][number] | undefined,
|
||||
expected: {
|
||||
readonly index: number;
|
||||
readonly spellId: 18499 | 46924;
|
||||
readonly auraType: number;
|
||||
readonly miscValue: number;
|
||||
readonly basePoints: number;
|
||||
readonly dieSides: number;
|
||||
readonly triggerSpellId?: number;
|
||||
},
|
||||
): effect is ExactApplyAura {
|
||||
return effect?.kind === "apply-aura"
|
||||
&& effect.sourceEffectType === 6
|
||||
&& effect.sourceAuraType === expected.auraType
|
||||
&& effect.sourceBasePoints === expected.basePoints
|
||||
&& effect.sourceDieSides === expected.dieSides
|
||||
&& effect.sourcePointsPerLevel === 0
|
||||
&& effect.miscValue === expected.miscValue
|
||||
&& effect.triggerSpellId === (expected.triggerSpellId ?? 0)
|
||||
&& effect.recipient === "caster"
|
||||
&& effect.aura.id === `wow335:${expected.spellId}:effect:${expected.index}:aura:${expected.auraType}`;
|
||||
}
|
||||
|
||||
function verifiedProof(
|
||||
proof: EnemyControlImmunityPurgeProof,
|
||||
): EnemyControlImmunityPurgeProof {
|
||||
const frozen = Object.freeze(proof);
|
||||
VERIFIED_PROOFS.add(frozen);
|
||||
return frozen;
|
||||
}
|
||||
|
||||
function defenseMatches(
|
||||
ability: AbilityDefinition,
|
||||
expected: {
|
||||
readonly spellId: 18499 | 46924;
|
||||
readonly attributes0: number;
|
||||
readonly attributes1: number;
|
||||
readonly attributes3: number;
|
||||
readonly attributes5: number;
|
||||
readonly damageClass: number;
|
||||
},
|
||||
): boolean {
|
||||
const defense = ability.wowSpellDefense;
|
||||
return ability.dbcSpellId === expected.spellId
|
||||
&& ability.classId === "warrior"
|
||||
&& ability.source === "wow335-progression"
|
||||
&& defense?.sourceProfile === "wow335"
|
||||
&& defense.damageClass === expected.damageClass
|
||||
&& defense.schoolMask === 1
|
||||
&& defense.attributes0 === expected.attributes0
|
||||
&& defense.attributes1 === expected.attributes1
|
||||
&& defense.attributes3 === expected.attributes3
|
||||
&& defense.attributes5 === expected.attributes5
|
||||
&& Number.isInteger(defense.attributes1)
|
||||
&& Number.isInteger(defense.attributes5)
|
||||
&& (defense.attributes1 & IMMUNITY_PURGES_EFFECT) !== 0;
|
||||
}
|
||||
|
||||
function berserkerRageProof(ability: AbilityDefinition): EnemyControlImmunityPurgeProof | undefined {
|
||||
if (ability.id !== "wow335-warrior-berserker-rage"
|
||||
|| ability.name !== "Berserker Rage"
|
||||
|| ability.target !== "self"
|
||||
|| ability.effects.length !== 3
|
||||
|| !defenseMatches(ability, {
|
||||
spellId: 18499,
|
||||
attributes0: 327696,
|
||||
attributes1: 32768,
|
||||
attributes3: 0,
|
||||
attributes5: 0,
|
||||
damageClass: 0,
|
||||
})) return undefined;
|
||||
|
||||
const [fear, sap, incapacitate] = ability.effects;
|
||||
if (!isExactApplyAura(fear, {
|
||||
index: 0, spellId: 18499, auraType: 77, miscValue: 5, basePoints: -1, dieSides: 1,
|
||||
}) || !fear.aura.controlImmunities?.includes("fear")
|
||||
|| !isExactApplyAura(sap, {
|
||||
index: 1, spellId: 18499, auraType: 77, miscValue: 14, basePoints: -1, dieSides: 1,
|
||||
})
|
||||
|| !isExactApplyAura(incapacitate, {
|
||||
index: 2, spellId: 18499, auraType: 77, miscValue: 30, basePoints: 0, dieSides: 0,
|
||||
})) return undefined;
|
||||
|
||||
return verifiedProof({
|
||||
sourceProfile: "wow335",
|
||||
sourceSpellId: 18499,
|
||||
grantsMechanics: Object.freeze(["fear"]),
|
||||
purgesNamedShoutSpellMechanics: Object.freeze(["fear"]),
|
||||
usableWhileControlledBy: Object.freeze(["fear"]),
|
||||
});
|
||||
}
|
||||
|
||||
function bladestormProof(ability: AbilityDefinition): EnemyControlImmunityPurgeProof | undefined {
|
||||
if (ability.id !== "wow335-warrior-bladestorm"
|
||||
|| ability.name !== "Bladestorm"
|
||||
|| ability.target !== "self"
|
||||
|| ability.effects.length !== 4
|
||||
|| !defenseMatches(ability, {
|
||||
spellId: 46924,
|
||||
attributes0: 327696,
|
||||
attributes1: 32784,
|
||||
attributes3: 1024,
|
||||
attributes5: 512,
|
||||
damageClass: 2,
|
||||
})) return undefined;
|
||||
|
||||
const sourceAuras = ability.effects.filter((effect) => effect.kind === "apply-aura");
|
||||
if (sourceAuras.length !== 3 || ability.effects.filter((effect) => effect.kind === "dot").length !== 1
|
||||
|| ability.effects.some((effect) => effect.kind !== "apply-aura" && effect.kind !== "dot")) return undefined;
|
||||
const [periodic, immunity, dummy] = sourceAuras;
|
||||
const immunityMechanics = immunity.kind === "apply-aura"
|
||||
? immunity.aura.controlImmunities ?? []
|
||||
: [];
|
||||
if (!isExactApplyAura(periodic, {
|
||||
index: 0, spellId: 46924, auraType: 23, miscValue: 0, basePoints: -1, dieSides: 1,
|
||||
triggerSpellId: 50622,
|
||||
})
|
||||
|| !isExactApplyAura(immunity, {
|
||||
index: 1, spellId: 46924, auraType: 147, miscValue: 1733, basePoints: -1, dieSides: 1,
|
||||
}) || !immunityMechanics.includes("fear")
|
||||
|| !immunityMechanics.includes("stun")
|
||||
|| !isExactApplyAura(dummy, {
|
||||
index: 2, spellId: 46924, auraType: 263, miscValue: 0, basePoints: -1, dieSides: 1,
|
||||
})) return undefined;
|
||||
|
||||
return verifiedProof({
|
||||
sourceProfile: "wow335",
|
||||
sourceSpellId: 46924,
|
||||
grantsMechanics: Object.freeze(["fear", "stun"]),
|
||||
purgesNamedShoutSpellMechanics: Object.freeze(["fear"]),
|
||||
usableWhileControlledBy: Object.freeze([]),
|
||||
});
|
||||
}
|
||||
|
||||
/** Returns a source-pinned purge/cast proof only for the two audited WoW 3.3.5a rows. */
|
||||
export function enemyControlImmunityPurgeProofForAbility(
|
||||
ability: AbilityDefinition | null | undefined,
|
||||
): EnemyControlImmunityPurgeProof | undefined {
|
||||
if (!ability) return undefined;
|
||||
if (ability.dbcSpellId === 18499) return berserkerRageProof(ability);
|
||||
if (ability.dbcSpellId === 46924) return bladestormProof(ability);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Whether the pinned source cast rules permit this selected spell during one control mechanic. */
|
||||
export function enemyControlAbilityUsableWhileControlled(
|
||||
ability: AbilityDefinition | null | undefined,
|
||||
mechanic: EnemyControlImmunityMechanic,
|
||||
): boolean {
|
||||
return enemyControlImmunityPurgeProofForAbility(ability)?.usableWhileControlledBy.includes(mechanic) ?? false;
|
||||
}
|
||||
|
||||
const NON_ACTION_BLOCKING_CONTROL_MECHANICS = new Set(["root", "slow", "interrupt"]);
|
||||
|
||||
/**
|
||||
* The Berserker Rage fear exception also covers the Fear-mechanic 20511 child
|
||||
* while it applies a stun effect. It does not cover a separate stun aura.
|
||||
*/
|
||||
export function enemyControlAbilityCanEscapeAuras(
|
||||
ability: AbilityDefinition | null | undefined,
|
||||
mechanic: EnemyControlImmunityMechanic,
|
||||
auras: readonly ActiveAura[],
|
||||
targetId: string,
|
||||
now: number,
|
||||
): boolean {
|
||||
const proof = enemyControlImmunityPurgeProofForAbility(ability);
|
||||
if (!proof || proof.sourceSpellId !== 18499 || !Number.isFinite(now)) return false;
|
||||
const activeControlAuras = auras.filter((aura) => aura.targetId === targetId
|
||||
&& (aura.expiresAt === null || aura.expiresAt > now)
|
||||
&& aura.definition.id.startsWith("enemy-control:"));
|
||||
if (!activeControlAuras.length) return false;
|
||||
|
||||
const allPreventingAurasAreCovered = activeControlAuras.every((aura) => {
|
||||
const id = aura.definition.id;
|
||||
if (NAMED_SHOUT_AURA_IDS.has(id)) return true;
|
||||
const suffix = id.slice("enemy-control:".length);
|
||||
if (suffix.startsWith("source-spell-")) return false;
|
||||
const sourceMechanic = suffix.split(":").at(-1);
|
||||
return sourceMechanic !== undefined
|
||||
&& NON_ACTION_BLOCKING_CONTROL_MECHANICS.has(sourceMechanic);
|
||||
});
|
||||
if (!allPreventingAurasAreCovered) return false;
|
||||
|
||||
if (mechanic === "fear") {
|
||||
return enemyControlAbilityUsableWhileControlled(ability, "fear")
|
||||
&& activeControlAuras.some((aura) => NAMED_SHOUT_AURA_IDS.has(aura.definition.id));
|
||||
}
|
||||
return mechanic === "stun"
|
||||
&& activeControlAuras.some((aura) => aura.definition.id === "enemy-control:source-spell-20511");
|
||||
}
|
||||
|
||||
export interface EnemyControlShoutAuraPurgeResult {
|
||||
readonly auras: readonly ActiveAura[];
|
||||
readonly removed: readonly ActiveAura[];
|
||||
}
|
||||
|
||||
/** Remove only active, same-target source shout auras covered by a proven FEAR purge. */
|
||||
export function purgeNamedEnemyControlShoutAuras(
|
||||
auras: readonly ActiveAura[],
|
||||
targetId: string,
|
||||
proof: EnemyControlImmunityPurgeProof | undefined,
|
||||
now: number,
|
||||
): EnemyControlShoutAuraPurgeResult {
|
||||
if (!proof || proof.sourceProfile !== "wow335"
|
||||
|| !VERIFIED_PROOFS.has(proof)
|
||||
|| !proof.purgesNamedShoutSpellMechanics.includes("fear")
|
||||
|| !Number.isFinite(now)) return { auras, removed: [] };
|
||||
|
||||
const removed: ActiveAura[] = [];
|
||||
const retained = auras.filter((aura) => {
|
||||
const isActive = aura.expiresAt === null || aura.expiresAt > now;
|
||||
const isNamedShout = NAMED_SHOUT_AURA_IDS.has(aura.definition.id);
|
||||
if (isActive && isNamedShout && aura.targetId === targetId) {
|
||||
removed.push(aura);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return { auras: retained, removed };
|
||||
}
|
||||
Reference in new issue
Block a user