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:57definesCREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK = 0x800.Creature::InitEntryandCreature::UpdateEntrysetDualWieldMode::ENABLEDwhen the flag is present (Creature.cpp:565-566, 614-615).Creature::HasWeapon(OFF_ATTACK)returns true inENABLEDmode. InAUTOmode it resolves virtual item slot 1 and accepts only a valid item whoseClassID == ITEM_CLASS_WEAPON(Creature.cpp:3299-3317). The core enum is inUnit.h:587-590;ITEM_CLASS_WEAPONis 2 atItemTemplate.h:293.Creature::SelectLevelinitializes both base and offhand from the same generated creature range (Creature.cpp:1540-1549).Creature::CalculateMinMaxDamageuses the same range, template variance, attack-power contribution, and damage modifier for either hand (StatSystem.cpp:1119-1167).UNIT_MOD_DAMAGE_OFFHANDapplies 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.