Files
healer-man/docs/ENEMY_AUTOMATIC_OFFHAND_2026-10-05.md
2026-10-06 22:25:34 -04:00

7.1 KiB

Automatic enemy offhands — 2026-10-05

Current source-table recovery supersedes the missing equipment-map limitation recorded below. See the 2026-10-06 equipment mapping audit for the pinned SQL, Item.dbc validation, importer output, and runtime helper.

Result

Added narrow helpers for the two historical AzerothCore automatic offhand gates: the creature-template USE_OFFHAND_ATTACK flag and the default AUTO mode's resolved offhand weapon check. The flag helper binds only the five matching creatures present in HealerMan's imported template-reference subset. The item helper requires a nonzero item ID and an already resolved Item.dbc class of WEAPON (2). It does not map an equipment_id to an item because that table is absent from the pinned world import.

The helpers are in enemyAutomaticOffhand.ts. They do not schedule attacks or mutate combat state. Runtime should treat enemyFlagDrivenOffhandContract(entry) as an intrinsic creature capability and feed it into the existing ordinary WoW offhand path. Keep this source flag separate from the simulator's temporary spell-granted capability: core sets the flag mode in creature initialization and entry updates, so evade, death, and encounter reset must not clear it while the creature entry remains the same. The simulator's existing reset policy can continue to clear spell-granted Dual Wield state.

enemyAutoEquippedOffhandContract({ entry, name?, offhandItemId, offhandItemClass }) resolves the default AUTO item gate only after a caller has loaded the actual offhand virtual item and looked up its class. An absent, zero, unresolved, or non-weapon item returns no contract. Do not use a model, creature appearance, or equipment_id alone as a substitute for the missing slot-to-item mapping. Keep both helpers on the source WoW creature path; the existing RuneWaker, summon, and character-defender exclusions remain in force.

World source and coverage

The imported AzerothCore world source is commit 4d9d1d4b5723e819c5c390f6ca1177283dbfb8e3. Its lock pins creature.sql at SHA-256 f54ddfe7ee00080b4b0bc970a809d6f947f6fb2627dbcd67e92b402fb34cb701 and creature_template.sql at SHA-256 0066e4fd35b809986dd325487515087f5a95f4ffe50ea03846c8d8bf5196870a. creature_template.sql has 29,947 rows, 43 of which set flags_extra & 0x00000800. The imported runtime reference contains 1,671 creature templates; five carry that bit:

Entry Creature flags_extra
4540 Scarlet Monk 2048
10478 Splintered Skeleton 2048
10488 Risen Construct 2048
16593 Shattered Hand Brawler 134219776
16808 Warchief Kargath Bladefist 2048

The source lock includes creature.sql rows with an equipment_id field, but does not include creature_equip_template.sql. The pinned spawn table has 149,879 rows; 51,248 have a nonzero equipment_id value (the distinct values are -1, 1, and 2). Those values select equipment-set IDs; they do not identify an offhand item by themselves.

I found creature_equip_template.sql in two local AzerothCore backup trees: ../wow-pvt-server-backups-2/azerothcore-wotlk/data/sql/base/db_world/creature_equip_template.sql and ../WoW-Pvt-Server-Backups/azerothcore-wotlk/data/sql/base/db_world/creature_equip_template.sql. The copies are byte-identical at SHA-256 D95423028E4D9CD7068E317BDE66ED847DC8238F4A6AA952D9EA40EF36109A1A, and their rows include Stockade mappings such as 1696/1 -> 5305, 5305, 1711/1 -> 2184, 2184, and 1716/1 -> 2179, 1897. These are useful leads, but they are not bound to this import: the adjacent archived creature_template.sql and creature.sql hashes are respectively e20e921613a02f8a86e997fd0d1d959dbe868bd6886151c8ca5f6c187a549356 and 135c78413da1bbbce0c65ee4ee833c0f19343933b3a0e30f82ff023f37aa6164, which do not match the pinned inputs. The equipment-table path is also absent from the pinned Git tree. The importer therefore does not bind those mappings.

The local WotLK client ../wow335a/Data/patch-A.MPQ/DBFilesClient/Item.dbc and AzerothCore server ../wow-pvt-server-backups-2/azerothcore-wotlk/data/Item.dbc have the same SHA-256: 22061a811523790ddd2ee95b36ade2ac6e0c79a8b3121324d32a639acf50567f. ItemEntry::ClassID is field 1 in the core DBC structure and the client DBC uses the matching Class field. Item class 2 is ITEM_CLASS_WEAPON. The core Creature::HasWeapon(OFF_ATTACK) checks only that the offhand virtual item resolves and its class is weapon; it does not check subclass or inventory type. This pins the gate logic and the local DBC bytes, but does not supply the missing equipment-set-to-item mapping for the locked world rows.

Core behavior reference

The behavior audit uses the local read-only AzerothCore checkout at HEAD e822d718322289dd036fb2daf8867ba41283bf9a. This is a core-behavior reference, not the world-data commit above.

  • CreatureData.h:57 defines CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK = 0x800.
  • Creature::InitEntry and Creature::UpdateEntry set DualWieldMode::ENABLED when the flag is present (Creature.cpp:565-566, 614-615).
  • Creature::HasWeapon(OFF_ATTACK) returns true in ENABLED mode. In AUTO mode it resolves virtual item slot 1 and accepts only a valid item whose ClassID == ITEM_CLASS_WEAPON (Creature.cpp:3299-3317). The core enum is in Unit.h:587-590; ITEM_CLASS_WEAPON is 2 at ItemTemplate.h:293.
  • Creature::SelectLevel initializes both base and offhand from the same generated creature range (Creature.cpp:1540-1549). Creature::CalculateMinMaxDamage uses the same range, template variance, attack-power contribution, and damage modifier for either hand (StatSystem.cpp:1119-1167). UNIT_MOD_DAMAGE_OFFHAND applies the default 0.5 total damage factor (Unit.cpp:401).
  • Both swing timers use CreatureTemplate.BaseAttackTime (Creature.cpp:617-618). Ordinary white swings add 19 percentage points to miss chance when an offhand is available (Unit.cpp:15288-15290). The existing Dual Wield source contract supplies the half-swing initial offset and 200 ms simultaneous-swing presentation stagger.

Relevant paths in the reference checkout are src/server/game/Entities/Creature/CreatureData.h, src/server/game/Entities/Creature/Creature.cpp, src/server/game/Entities/Unit/StatSystem.cpp, src/server/game/Entities/Unit/Unit.cpp, src/server/game/Entities/Unit/Unit.h, src/server/game/Entities/Item/ItemTemplate.h, and src/server/shared/DataStores/DBCStructure.h.

Validation and limits

npm test -- --run src/game/enemyAutomaticOffhand.test.ts passes four focused tests. npm's pretest hook also ran the existing loot-catalog generator; it reported the existing 9-boss/23-item catalog, with no loot-file change.

The flag path is implemented from the locked creature-template data. The AUTO path is implemented as a strict, reusable item-class gate, but no locked world equipment mapping currently supplies it with item IDs. Exact automatic offhand availability for the imported equipment_id and template-default cases remains unresolved until a matching equipment-template source is pinned or imported.