Update Healer Man server
This commit is contained in:
1 parent
69809f7228
commit
ff20ab8779
101 files changed
+16713
-945
No files matched your search
@@ -4,6 +4,7 @@ import {
|
||||
type EnemyNpcSpellDefenseMetadata,
|
||||
type EnemyNpcSpellSourceProfile,
|
||||
} from "./enemyBlocking";
|
||||
import type { AbilityDefinition, AbilityEffect } from "./abilityCatalog";
|
||||
|
||||
export type EnemyUtilitySpellCasterKind = "player" | "party" | "enemy-npc" | "rom-native" | "unknown";
|
||||
export type EnemyUtilitySpellTargetKind = "ordinary-npc" | "character" | "native-rom" | "summon" | "unknown";
|
||||
@@ -11,54 +12,173 @@ export type EnemyUtilitySpellHitOutcome = "hit" | "crit" | "miss" | "dodge" | "p
|
||||
export type EnemyUtilitySpellBlockDisposition = "apply" | "blocked" | "avoided" | "fallback";
|
||||
|
||||
export interface EnemyUtilitySpellBlockSourceContract {
|
||||
readonly spellId: 72;
|
||||
readonly spellId: 72 | 1766 | 6552;
|
||||
readonly sourceProfile: "wow335";
|
||||
readonly abilityId: string;
|
||||
/** Raw imported chain ID, accepted only with the same exact source projection. */
|
||||
readonly sourceAbilityId?: string;
|
||||
readonly classId: "warrior" | "rogue";
|
||||
readonly abilityName: "Shield Bash" | "Kick" | "Pummel";
|
||||
readonly spellDefense: EnemyNpcSpellDefenseMetadata;
|
||||
/** Relevant fields from the two exact Shield Bash Spell.dbc effect slots. */
|
||||
readonly dbcEffects: readonly [
|
||||
{ readonly index: 0; readonly effectType: 68; readonly mechanic: 26; readonly targetA: 6; readonly triggerSpellId: 0 },
|
||||
{ readonly index: 1; readonly effectType: 64; readonly mechanic: 0; readonly targetA: 6; readonly triggerSpellId: 29703 },
|
||||
];
|
||||
/** Relevant source fields from the exact selected Spell.dbc rank. */
|
||||
readonly dbcEffects: readonly EnemyUtilitySpellBlockDbcEffect[];
|
||||
/** Exact projection produced by the playable catalog for this selected rank. */
|
||||
readonly projectedEffects: readonly AbilityEffect[];
|
||||
}
|
||||
|
||||
const SHIELD_BASH_72_EFFECTS = Object.freeze([
|
||||
Object.freeze({ index: 0 as const, effectType: 68 as const, mechanic: 26 as const, targetA: 6 as const, triggerSpellId: 0 as const }),
|
||||
Object.freeze({ index: 1 as const, effectType: 64 as const, mechanic: 0 as const, targetA: 6 as const, triggerSpellId: 29703 as const }),
|
||||
]) as EnemyUtilitySpellBlockSourceContract["dbcEffects"];
|
||||
interface EnemyUtilitySpellBlockDbcEffect {
|
||||
readonly index: number;
|
||||
readonly effectType: number;
|
||||
readonly auraType: number;
|
||||
readonly mechanic: number;
|
||||
readonly targetA: number;
|
||||
readonly targetB: number;
|
||||
readonly basePoints: number;
|
||||
readonly triggerSpellId: number;
|
||||
}
|
||||
|
||||
interface EnemyUtilitySpellBlockRankContract {
|
||||
readonly spellId: EnemyUtilitySpellBlockSourceContract["spellId"];
|
||||
readonly abilityId: string;
|
||||
readonly sourceAbilityId?: string;
|
||||
readonly classId: "warrior" | "rogue";
|
||||
readonly abilityName: EnemyUtilitySpellBlockSourceContract["abilityName"];
|
||||
readonly dbcEffects: readonly EnemyUtilitySpellBlockDbcEffect[];
|
||||
readonly projectedEffects: readonly AbilityEffect[];
|
||||
}
|
||||
|
||||
const UTILITY_SPELL_BLOCK_RANKS: readonly EnemyUtilitySpellBlockRankContract[] = Object.freeze([
|
||||
Object.freeze({
|
||||
spellId: 72,
|
||||
abilityId: "wow335-warrior-shield-bash",
|
||||
classId: "warrior",
|
||||
abilityName: "Shield Bash",
|
||||
dbcEffects: Object.freeze([
|
||||
Object.freeze({ index: 0, effectType: 68, auraType: 0, mechanic: 26, targetA: 6, targetB: 0, basePoints: 0, triggerSpellId: 0 }),
|
||||
Object.freeze({ index: 1, effectType: 64, auraType: 0, mechanic: 0, targetA: 6, targetB: 0, basePoints: 0, triggerSpellId: 29703 }),
|
||||
]),
|
||||
projectedEffects: Object.freeze([
|
||||
Object.freeze({ kind: "interrupt", lockoutMs: 6_000 }),
|
||||
Object.freeze({ kind: "trigger-spell", spellId: 29_703 }),
|
||||
]),
|
||||
}),
|
||||
Object.freeze({
|
||||
spellId: 1766,
|
||||
abilityId: "rogue-kick",
|
||||
sourceAbilityId: "wow335-rogue-kick",
|
||||
classId: "rogue",
|
||||
abilityName: "Kick",
|
||||
dbcEffects: Object.freeze([
|
||||
Object.freeze({ index: 0, effectType: 68, auraType: 0, mechanic: 26, targetA: 6, targetB: 0, basePoints: 1, triggerSpellId: 0 }),
|
||||
Object.freeze({ index: 1, effectType: 0, auraType: 0, mechanic: 0, targetA: 0, targetB: 0, basePoints: 0, triggerSpellId: 0 }),
|
||||
]),
|
||||
projectedEffects: Object.freeze([
|
||||
Object.freeze({ kind: "interrupt", lockoutMs: 5_000 }),
|
||||
]),
|
||||
}),
|
||||
Object.freeze({
|
||||
spellId: 6552,
|
||||
abilityId: "wow335-warrior-pummel",
|
||||
classId: "warrior",
|
||||
abilityName: "Pummel",
|
||||
dbcEffects: Object.freeze([
|
||||
Object.freeze({ index: 0, effectType: 68, auraType: 0, mechanic: 26, targetA: 6, targetB: 0, basePoints: 1, triggerSpellId: 0 }),
|
||||
Object.freeze({ index: 1, effectType: 0, auraType: 0, mechanic: 0, targetA: 0, targetB: 0, basePoints: 0, triggerSpellId: 0 }),
|
||||
]),
|
||||
projectedEffects: Object.freeze([
|
||||
Object.freeze({ kind: "interrupt", lockoutMs: 4_000 }),
|
||||
]),
|
||||
}),
|
||||
]);
|
||||
|
||||
/**
|
||||
* Returns the only audited utility full-hit-block spell contract. The rank and
|
||||
* profile are intentionally exact; the DBC effect signature is interrupt plus
|
||||
* its triggered aura, with no direct-damage effect.
|
||||
* Returns an audited utility full-block contract for an exact WoW 3.3.5a
|
||||
* selected rank. Other ranks and source profiles remain on their current path.
|
||||
*/
|
||||
export function enemyUtilitySpellBlockSourceContract(
|
||||
spellId: number | undefined,
|
||||
sourceProfile: EnemyNpcSpellSourceProfile | undefined,
|
||||
): EnemyUtilitySpellBlockSourceContract | undefined {
|
||||
if (spellId !== 72 || sourceProfile !== "wow335") return undefined;
|
||||
const spellDefense = enemyNpcSourceSpellBlockMetadata(72, "wow335");
|
||||
if (sourceProfile !== "wow335") return undefined;
|
||||
const rank = UTILITY_SPELL_BLOCK_RANKS.find((candidate) => candidate.spellId === spellId);
|
||||
if (!rank) return undefined;
|
||||
const spellDefense = enemyNpcSourceSpellBlockMetadata(rank.spellId, "wow335");
|
||||
if (!spellDefense) return undefined;
|
||||
return Object.freeze({
|
||||
spellId: 72,
|
||||
spellId: rank.spellId,
|
||||
sourceProfile: "wow335",
|
||||
abilityId: rank.abilityId,
|
||||
...(rank.sourceAbilityId ? { sourceAbilityId: rank.sourceAbilityId } : {}),
|
||||
classId: rank.classId,
|
||||
abilityName: rank.abilityName,
|
||||
spellDefense,
|
||||
dbcEffects: SHIELD_BASH_72_EFFECTS,
|
||||
dbcEffects: rank.dbcEffects,
|
||||
projectedEffects: rank.projectedEffects,
|
||||
});
|
||||
}
|
||||
|
||||
function matchesShieldBashBlockMetadata(
|
||||
function matchesUtilityBlockMetadata(
|
||||
metadata: EnemyNpcSpellDefenseMetadata | undefined,
|
||||
contract: EnemyUtilitySpellBlockSourceContract,
|
||||
reconstructSourceProof = false,
|
||||
): boolean {
|
||||
if (!metadata) return false;
|
||||
const expected = contract.spellDefense;
|
||||
return metadata.damageClass === expected.damageClass
|
||||
&& metadata.schoolMask === expected.schoolMask
|
||||
&& metadata.attributes0 === expected.attributes0
|
||||
&& metadata.attributes3 === expected.attributes3
|
||||
&& metadata.directDamageFromEffects === expected.directDamageFromEffects
|
||||
&& metadata.sourceProfile === expected.sourceProfile
|
||||
&& classifyEnemyNpcSpellBlockPhase(metadata) === "hit-table";
|
||||
if (metadata.damageClass !== expected.damageClass
|
||||
|| metadata.schoolMask !== expected.schoolMask
|
||||
|| metadata.attributes0 !== expected.attributes0
|
||||
|| metadata.attributes3 !== expected.attributes3
|
||||
|| metadata.directDamageFromEffects !== expected.directDamageFromEffects
|
||||
|| metadata.sourceProfile !== expected.sourceProfile) return false;
|
||||
|
||||
const knownCustom = expected.customAttributeEvidence;
|
||||
if (knownCustom) {
|
||||
if (metadata.customAttributes !== undefined
|
||||
&& ((metadata.customAttributes & knownCustom.knownMask) >>> 0) !== knownCustom.value) return false;
|
||||
const evidence = metadata.customAttributeEvidence;
|
||||
if (evidence && ((evidence.knownMask & knownCustom.knownMask) !== knownCustom.knownMask
|
||||
|| ((evidence.value & knownCustom.knownMask) >>> 0) !== knownCustom.value)) return false;
|
||||
}
|
||||
const withSourceProof = reconstructSourceProof
|
||||
&& metadata.customAttributes === undefined
|
||||
&& metadata.customAttributeEvidence === undefined && knownCustom
|
||||
? { ...metadata, customAttributeEvidence: knownCustom }
|
||||
: metadata;
|
||||
return classifyEnemyNpcSpellBlockPhase(withSourceProof) === "hit-table";
|
||||
}
|
||||
|
||||
function stableSignature(value: unknown): string {
|
||||
if (Array.isArray(value)) return `[${value.map(stableSignature).join(",")}]`;
|
||||
if (value && typeof value === "object") {
|
||||
const entries = Object.entries(value as Record<string, unknown>)
|
||||
.sort(([left], [right]) => left.localeCompare(right));
|
||||
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableSignature(entry)}`).join(",")}}`;
|
||||
}
|
||||
return JSON.stringify(value) ?? "undefined";
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the spell-block contract to the actual selected playable catalog row.
|
||||
* Matching a spell ID alone cannot authorize another class, source, wrapper,
|
||||
* or modified effect list to borrow this source proof.
|
||||
*/
|
||||
export function enemyUtilitySpellBlockAbilityContract(
|
||||
ability: AbilityDefinition | undefined,
|
||||
): EnemyUtilitySpellBlockSourceContract | undefined {
|
||||
if (!ability) return undefined;
|
||||
const contract = enemyUtilitySpellBlockSourceContract(
|
||||
ability.dbcSpellId,
|
||||
ability.wowSpellDefense?.sourceProfile,
|
||||
);
|
||||
if (!contract
|
||||
|| (ability.id !== contract.abilityId && ability.id !== contract.sourceAbilityId)
|
||||
|| ability.classId !== contract.classId
|
||||
|| ability.name !== contract.abilityName
|
||||
|| ability.source !== "wow335-progression"
|
||||
|| ability.dbcSpellId !== contract.spellId
|
||||
|| !matchesUtilityBlockMetadata(ability.wowSpellDefense, contract, true)
|
||||
|| stableSignature(ability.effects) !== stableSignature(contract.projectedEffects)) return undefined;
|
||||
return contract;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +198,7 @@ export function enemyUtilitySpellBlockDisposition(input: {
|
||||
if (!contract
|
||||
|| input.casterKind !== "player" && input.casterKind !== "party"
|
||||
|| input.targetKind !== "ordinary-npc"
|
||||
|| !matchesShieldBashBlockMetadata(input.spellDefense, contract)) return "fallback";
|
||||
|| !matchesUtilityBlockMetadata(input.spellDefense, contract)) return "fallback";
|
||||
|
||||
if (input.outcome === "block") return "blocked";
|
||||
if (input.outcome === "miss" || input.outcome === "dodge"
|
||||
|
||||
Reference in new issue
Block a user