119 lines
5.1 KiB
TypeScript
119 lines
5.1 KiB
TypeScript
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,
|
|
});
|
|
}
|