133 lines
7.1 KiB
Markdown
133 lines
7.1 KiB
Markdown
# 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](ENEMY_EQUIPMENT_MAPPING_2026-10-06.md)
|
|
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](../src/game/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.
|