import { creatureTemplateReference } from "./azerothCoreReference"; import { ENEMY_DUAL_WIELD_FIRST_SWING_DELAY_FRACTION, ENEMY_DUAL_WIELD_SIMULTANEOUS_SWING_DELAY_MS, ENEMY_DUAL_WIELD_WHITE_MISS_PENALTY_PERCENT, } from "./enemyExtraAttacks"; /** AzerothCore CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK. */ export const CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK = 0x0000_0800; /** AzerothCore ITEM_CLASS_WEAPON, also stored in the client Item.dbc Class field. */ export const ENEMY_OFFHAND_ITEM_CLASS_WEAPON = 2; /** Pinned world row carrying creature_template.flags_extra. */ export const ENEMY_AUTOMATIC_OFFHAND_WORLD_SOURCE = Object.freeze({ repository: "https://github.com/azerothcore/azerothcore-wotlk", commit: "4d9d1d4b5723e819c5c390f6ca1177283dbfb8e3", table: "creature_template", path: "data/sql/base/db_world/creature_template.sql", sha256: "0066e4fd35b809986dd325487515087f5a95f4ffe50ea03846c8d8bf5196870a", }); /** Byte-identical local WotLK client/server Item.dbc files used for item Class lookups. */ export const ENEMY_AUTOMATIC_OFFHAND_ITEM_SOURCE = Object.freeze({ path: "../wow335a/Data/patch-A.MPQ/DBFilesClient/Item.dbc", sha256: "22061a811523790ddd2ee95b36ade2ac6e0c79a8b3121324d32a639acf50567f", classField: 1, }); export interface EnemyAutomaticOffhandContract { readonly entry: number; readonly name: string; readonly source: "creature-template-flag" | "auto-offhand-weapon"; /** `enabled` bypasses the equipment check; `auto` requires a valid weapon item. */ readonly dualWieldMode: "enabled" | "auto"; readonly requiresEquippedWeapon: boolean; readonly attackType: "offhand"; /** Creature::CalculateMinMaxDamage uses the creature-generated hand range and AP formula. */ readonly baseDamageFrom: "same-creature-template-offhand-range-and-ap-formula"; /** UNIT_MOD_DAMAGE_OFFHAND TOTAL_PCT starts at 0.5 in the pinned core source. */ readonly damageMultiplier: 0.5; /** Ordinary white swings add 19 points when an offhand channel is available. */ readonly whiteSwingMissPenaltyPercent: typeof ENEMY_DUAL_WIELD_WHITE_MISS_PENALTY_PERCENT; readonly attackTimeFrom: "creature-template-base-attack-time"; readonly independentSwingTimer: true; readonly firstSwingDelayFractionOnEngage: typeof ENEMY_DUAL_WIELD_FIRST_SWING_DELAY_FRACTION; readonly simultaneousSwingDelayMs: typeof ENEMY_DUAL_WIELD_SIMULTANEOUS_SWING_DELAY_MS; } function makeContract(input: { entry: number; name: string; source: EnemyAutomaticOffhandContract["source"]; dualWieldMode: EnemyAutomaticOffhandContract["dualWieldMode"]; requiresEquippedWeapon: boolean; }): EnemyAutomaticOffhandContract { return Object.freeze({ ...input, attackType: "offhand", baseDamageFrom: "same-creature-template-offhand-range-and-ap-formula", damageMultiplier: 0.5, whiteSwingMissPenaltyPercent: ENEMY_DUAL_WIELD_WHITE_MISS_PENALTY_PERCENT, attackTimeFrom: "creature-template-base-attack-time", independentSwingTimer: true, firstSwingDelayFractionOnEngage: ENEMY_DUAL_WIELD_FIRST_SWING_DELAY_FRACTION, simultaneousSwingDelayMs: ENEMY_DUAL_WIELD_SIMULTANEOUS_SWING_DELAY_MS, }); } /** * Resolves only the pinned creature-template USE_OFFHAND_ATTACK bit. This is * an intrinsic creature capability, set by Creature::InitEntry/UpdateEntry; * it survives combat, evade, and death state resets while the creature entry * remains the same. */ export function enemyFlagDrivenOffhandContract(entry: number): EnemyAutomaticOffhandContract | undefined { if (!Number.isSafeInteger(entry) || entry <= 0) return undefined; const template = creatureTemplateReference(entry); const flagsExtra = template?.flagsExtra; if (!template || !Number.isSafeInteger(flagsExtra) || flagsExtra! < 0 || flagsExtra! > 0xffff_ffff || (flagsExtra! & CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK) === 0) return undefined; return makeContract({ entry: template.entry, name: template.name, source: "creature-template-flag", dualWieldMode: "enabled", requiresEquippedWeapon: false, }); } /** * Resolves the default AUTO-mode offhand gate used by Creature::HasWeapon. * `itemClass` must come from a successfully resolved client/server item row; * class WEAPON (2) is the entire core gate after source virtual-item validation. * Callers must resolve the pinned spawn selector and equipment set; model or * display IDs and the selector itself are never treated as item IDs. */ export function enemyAutoEquippedOffhandContract(input: { readonly entry: number; readonly name?: string; readonly offhandItemId: number; readonly offhandItemClass: number | null | undefined; }): EnemyAutomaticOffhandContract | undefined { if (!Number.isSafeInteger(input.entry) || input.entry <= 0 || !Number.isSafeInteger(input.offhandItemId) || input.offhandItemId <= 0 || input.offhandItemClass !== ENEMY_OFFHAND_ITEM_CLASS_WEAPON) return undefined; const template = creatureTemplateReference(input.entry); if (!template) return undefined; return makeContract({ entry: input.entry, name: input.name?.trim() || template.name, source: "auto-offhand-weapon", dualWieldMode: "auto", requiresEquippedWeapon: true, }); }