feat(animation): 452 named constants, 30-phase character animation state machine

Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.

Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.

Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.

Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.

Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.

Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
This commit is contained in:
Paul 2026-04-04 23:02:53 +03:00
parent d54e262048
commit e58f9b4b40
59 changed files with 3903 additions and 483 deletions

View file

@ -123,6 +123,9 @@ void CombatHandler::registerOpcodes(DispatchTable& table) {
addCombatText(CombatTextEntry::ABSORB, static_cast<int32_t>(envAbs), 0, false, 0, 0, victimGuid);
if (envRes > 0)
addCombatText(CombatTextEntry::RESIST, static_cast<int32_t>(envRes), 0, false, 0, 0, victimGuid);
// Drowning damage → play DROWN one-shot on player
if (envType == 1 && dmg > 0 && owner_.emoteAnimCallback_)
owner_.emoteAnimCallback_(victimGuid, 131); // anim::DROWN
}
packet.skipAll();
};
@ -440,7 +443,7 @@ void CombatHandler::handleAttackerStateUpdate(network::Packet& packet) {
lastMeleeSwingMs_ = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count());
if (owner_.meleeSwingCallback_) owner_.meleeSwingCallback_();
if (owner_.meleeSwingCallback_) owner_.meleeSwingCallback_(0);
}
if (!isPlayerAttacker && owner_.npcSwingCallback_) {
owner_.npcSwingCallback_(data.attackerGuid);
@ -520,6 +523,17 @@ void CombatHandler::handleAttackerStateUpdate(network::Packet& packet) {
addCombatText(CombatTextEntry::RESIST, static_cast<int32_t>(totalResisted), 0, isPlayerAttacker, 0, data.attackerGuid, data.targetGuid);
}
// Fire hit reaction animation on the victim
if (owner_.hitReactionCallback_ && !data.isMiss()) {
using HR = GameHandler::HitReaction;
HR reaction = HR::WOUND;
if (data.victimState == 1) reaction = HR::DODGE;
else if (data.victimState == 2) reaction = HR::PARRY;
else if (data.victimState == 4) reaction = HR::BLOCK;
else if (data.isCrit()) reaction = HR::CRIT_WOUND;
owner_.hitReactionCallback_(data.targetGuid, reaction);
}
}
void CombatHandler::handleSpellDamageLog(network::Packet& packet) {

View file

@ -542,6 +542,7 @@ EntityController::UnitFieldIndices EntityController::UnitFieldIndices::resolve()
fieldIndex(UF::UNIT_FIELD_DISPLAYID),
fieldIndex(UF::UNIT_FIELD_MOUNTDISPLAYID),
fieldIndex(UF::UNIT_NPC_FLAGS),
fieldIndex(UF::UNIT_NPC_EMOTESTATE),
fieldIndex(UF::UNIT_FIELD_BYTES_0),
fieldIndex(UF::UNIT_FIELD_BYTES_1)
};
@ -697,6 +698,7 @@ bool EntityController::applyUnitFieldsOnCreate(const UpdateBlock& block,
}
}
else if (key == ufi.npcFlags) { unit->setNpcFlags(val); }
else if (key == ufi.npcEmoteState) { unit->setNpcEmoteState(val); }
else if (key == ufi.dynFlags) {
unit->setDynamicFlags(val);
if (block.objectType == ObjectType::UNIT &&
@ -795,7 +797,28 @@ EntityController::UnitFieldUpdateResult EntityController::applyUnitFieldsOnUpdat
if (!uid.empty())
pendingEvents_.emit("UNIT_DISPLAYPOWER", {uid});
}
} else if (key == ufi.flags) { unit->setUnitFlags(val); }
} else if (key == ufi.flags) {
uint32_t oldFlags = unit->getUnitFlags();
unit->setUnitFlags(val);
// Detect stun state change on local player
constexpr uint32_t UNIT_FLAG_STUNNED = 0x00040000;
if (block.guid == owner_.playerGuid && owner_.stunStateCallback_) {
bool wasStunned = (oldFlags & UNIT_FLAG_STUNNED) != 0;
bool nowStunned = (val & UNIT_FLAG_STUNNED) != 0;
if (wasStunned != nowStunned) {
owner_.stunStateCallback_(nowStunned);
}
}
// Detect stealth state change on local player
constexpr uint32_t UNIT_FLAG_SNEAKING = 0x02000000;
if (block.guid == owner_.playerGuid && owner_.stealthStateCallback_) {
bool wasStealth = (oldFlags & UNIT_FLAG_SNEAKING) != 0;
bool nowStealth = (val & UNIT_FLAG_SNEAKING) != 0;
if (wasStealth != nowStealth) {
owner_.stealthStateCallback_(nowStealth);
}
}
}
else if (ufi.bytes1 != 0xFFFF && key == ufi.bytes1 && block.guid == owner_.playerGuid) {
uint8_t newForm = static_cast<uint8_t>((val >> 24) & 0xFF);
if (newForm != owner_.shapeshiftFormId_) {
@ -863,6 +886,14 @@ EntityController::UnitFieldUpdateResult EntityController::applyUnitFieldsOnUpdat
}
unit->setMountDisplayId(val);
} else if (key == ufi.npcFlags) { unit->setNpcFlags(val); }
else if (key == ufi.npcEmoteState) {
uint32_t oldEmote = unit->getNpcEmoteState();
unit->setNpcEmoteState(val);
// Fire emote animation callback so entity_spawner can update the NPC's idle anim
if (val != oldEmote && owner_.emoteAnimCallback_) {
owner_.emoteAnimCallback_(block.guid, val);
}
}
// Power/maxpower range checks AFTER all specific fields
else if (key >= ufi.powerBase && key < ufi.powerBase + 7) {
unit->setPowerByType(static_cast<uint8_t>(key - ufi.powerBase), val);
@ -889,6 +920,11 @@ EntityController::UnitFieldUpdateResult EntityController::applyUnitFieldsOnUpdat
}
}
// Fire player health callback for wounded-idle animation
if (result.healthChanged && block.guid == owner_.playerGuid && owner_.playerHealthCallback_) {
owner_.playerHealthCallback_(unit->getHealth(), unit->getMaxHealth());
}
return result;
}
@ -1632,6 +1668,17 @@ void EntityController::onValuesUpdateGameObject(const UpdateBlock& block, std::s
entity->getZ(), entity->getOrientation());
}
}
// Detect GO state changes from GAMEOBJECT_BYTES_1 (packed: byte0=state, byte1=type, byte2=artKit, byte3=animProgress)
const uint16_t ufGoBytes1 = fieldIndex(UF::GAMEOBJECT_BYTES_1);
if (ufGoBytes1 != 0xFFFF) {
auto itB = block.fields.find(ufGoBytes1);
if (itB != block.fields.end()) {
uint8_t goState = static_cast<uint8_t>(itB->second & 0xFF);
if (owner_.gameObjectStateCallback_)
owner_.gameObjectStateCallback_(block.guid, goState);
}
}
}
// ============================================================

View file

@ -31,6 +31,7 @@
#include "pipeline/asset_manager.hpp"
#include "pipeline/dbc_loader.hpp"
#include "core/logger.hpp"
#include "rendering/animation_ids.hpp"
#include <glm/gtx/quaternion.hpp>
#include <algorithm>
#include <cmath>
@ -1275,12 +1276,25 @@ void GameHandler::registerOpcodeHandlers() {
};
// Consume silently — opcodes we receive but don't need to act on
for (auto op : {
Opcode::SMSG_GAMEOBJECT_DESPAWN_ANIM, Opcode::SMSG_GAMEOBJECT_RESET_STATE,
Opcode::SMSG_FLIGHT_SPLINE_SYNC, Opcode::SMSG_FORCE_DISPLAY_UPDATE,
Opcode::SMSG_FORCE_SEND_QUEUED_PACKETS, Opcode::SMSG_FORCE_SET_VEHICLE_REC_ID,
Opcode::SMSG_CORPSE_MAP_POSITION_QUERY_RESPONSE, Opcode::SMSG_DAMAGE_CALC_LOG,
Opcode::SMSG_DYNAMIC_DROP_ROLL_RESULT, Opcode::SMSG_DESTRUCTIBLE_BUILDING_DAMAGE,
}) { registerSkipHandler(op); }
// Game object despawn animation — reset state to closed before actual despawn
dispatchTable_[Opcode::SMSG_GAMEOBJECT_DESPAWN_ANIM] = [this](network::Packet& packet) {
if (!packet.hasRemaining(8)) return;
uint64_t guid = packet.readUInt64();
// Trigger a CLOSE animation / freeze before the object is removed
if (gameObjectStateCallback_) gameObjectStateCallback_(guid, 0);
};
// Game object reset state — return to READY(closed) state
dispatchTable_[Opcode::SMSG_GAMEOBJECT_RESET_STATE] = [this](network::Packet& packet) {
if (!packet.hasRemaining(8)) return;
uint64_t guid = packet.readUInt64();
if (gameObjectStateCallback_) gameObjectStateCallback_(guid, 0);
};
dispatchTable_[Opcode::SMSG_FORCED_DEATH_UPDATE] = [this](network::Packet& packet) {
playerDead_ = true;
if (ghostStateCallback_) ghostStateCallback_(false);
@ -2124,10 +2138,15 @@ void GameHandler::registerOpcodeHandlers() {
if (packet.hasRemaining(1)) {
(void)packet.readPackedGuid(); // player guid (unused)
}
uint32_t newVehicleId = 0;
if (packet.hasRemaining(4)) {
vehicleId_ = packet.readUInt32();
} else {
vehicleId_ = 0;
newVehicleId = packet.readUInt32();
}
bool wasInVehicle = vehicleId_ != 0;
bool nowInVehicle = newVehicleId != 0;
vehicleId_ = newVehicleId;
if (wasInVehicle != nowInVehicle && vehicleStateCallback_) {
vehicleStateCallback_(nowInVehicle, newVehicleId);
}
};
// guid(8) + status(1): status 1 = NPC has available/new routes for this player
@ -2842,6 +2861,9 @@ void GameHandler::registerOpcodeHandlers() {
};
dispatchTable_[Opcode::SMSG_ON_CANCEL_EXPECTED_RIDE_VEHICLE_AURA] = [this](network::Packet& packet) {
vehicleId_ = 0; // Vehicle ride cancelled; clear UI
if (vehicleStateCallback_) {
vehicleStateCallback_(false, 0);
}
packet.skipAll();
};
// uint32 type (0=normal, 1=heavy, 2=tired/restricted) + uint32 minutes played
@ -6048,6 +6070,12 @@ void GameHandler::preloadDBCCaches() const {
loadMapNameCache(); // Map.dbc
loadLfgDungeonDbc(); // LFGDungeons.dbc
// Validate animation constants against AnimationData.dbc
if (auto* am = services_.assetManager) {
auto animDbc = am->loadDBC("AnimationData.dbc");
rendering::anim::validateAgainstDBC(animDbc);
}
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - t0).count();
LOG_INFO("DBC cache pre-load complete in ", elapsed, " ms");

View file

@ -679,6 +679,7 @@ void InventoryHandler::closeLoot() {
owner_.socket->send(packet);
}
lootWindowOpen_ = false;
if (owner_.lootWindowCallback_) owner_.lootWindowCallback_(false);
if (owner_.addonEventCallback_) owner_.addonEventCallback_("LOOT_CLOSED", {});
currentLoot_ = LootResponseData{};
}
@ -704,6 +705,7 @@ void InventoryHandler::handleLootResponse(network::Packet& packet) {
return;
}
lootWindowOpen_ = true;
if (owner_.lootWindowCallback_) owner_.lootWindowCallback_(true);
if (owner_.addonEventCallback_) {
owner_.addonEventCallback_("LOOT_OPENED", {});
owner_.addonEventCallback_("LOOT_READY", {});
@ -749,6 +751,7 @@ void InventoryHandler::handleLootReleaseResponse(network::Packet& packet) {
(void)packet;
localLootState_.erase(currentLoot_.lootGuid);
lootWindowOpen_ = false;
if (owner_.lootWindowCallback_) owner_.lootWindowCallback_(false);
if (owner_.addonEventCallback_) owner_.addonEventCallback_("LOOT_CLOSED", {});
currentLoot_ = LootResponseData{};
}

View file

@ -34,19 +34,19 @@ static float mergeCooldownSeconds(float current, float incoming) {
static CombatTextEntry::Type combatTextTypeFromSpellMissInfo(uint8_t missInfo) {
switch (missInfo) {
case 0: return CombatTextEntry::MISS;
case 1: return CombatTextEntry::DODGE;
case 2: return CombatTextEntry::PARRY;
case 3: return CombatTextEntry::BLOCK;
case 4: return CombatTextEntry::EVADE;
case 5: return CombatTextEntry::IMMUNE;
case 6: return CombatTextEntry::DEFLECT;
case 7: return CombatTextEntry::ABSORB;
case 8: return CombatTextEntry::RESIST;
case 9:
case 10:
case SpellMissInfo::MISS: return CombatTextEntry::MISS;
case SpellMissInfo::DODGE: return CombatTextEntry::DODGE;
case SpellMissInfo::PARRY: return CombatTextEntry::PARRY;
case SpellMissInfo::BLOCK: return CombatTextEntry::BLOCK;
case SpellMissInfo::EVADE: return CombatTextEntry::EVADE;
case SpellMissInfo::IMMUNE: return CombatTextEntry::IMMUNE;
case SpellMissInfo::DEFLECT: return CombatTextEntry::DEFLECT;
case SpellMissInfo::ABSORB: return CombatTextEntry::ABSORB;
case SpellMissInfo::RESIST: return CombatTextEntry::RESIST;
case SpellMissInfo::IMMUNE2:
case SpellMissInfo::IMMUNE3:
return CombatTextEntry::IMMUNE;
case 11: return CombatTextEntry::REFLECT;
case SpellMissInfo::REFLECT: return CombatTextEntry::REFLECT;
default: return CombatTextEntry::MISS;
}
}
@ -939,7 +939,7 @@ void SpellHandler::handleSpellGo(network::Packet& packet) {
}
}
if (isMeleeAbility) {
if (owner_.meleeSwingCallback_) owner_.meleeSwingCallback_();
if (owner_.meleeSwingCallback_) owner_.meleeSwingCallback_(sid);
if (auto* ac = owner_.services().audioCoordinator) {
if (auto* csm = ac->getCombatSoundManager()) {
csm->playWeaponSwing(audio::CombatSoundManager::WeaponSize::MEDIUM, false);
@ -951,6 +951,14 @@ void SpellHandler::handleSpellGo(network::Packet& packet) {
const bool wasInTimedCast = casting_ && (data.spellId == currentCastSpellId_);
// Instant spell cast animation — if this wasn't a timed cast and isn't a
// melee ability, play a brief spell cast animation (one-shot)
if (!wasInTimedCast && !isMeleeAbility && !owner_.isProfessionSpell(data.spellId)) {
if (owner_.spellCastAnimCallback_) {
owner_.spellCastAnimCallback_(owner_.playerGuid, true, false);
}
}
LOG_WARNING("[GO-DIAG] SPELL_GO: spellId=", data.spellId,
" casting=", casting_, " currentCast=", currentCastSpellId_,
" wasInTimedCast=", wasInTimedCast,
@ -991,6 +999,13 @@ void SpellHandler::handleSpellGo(network::Packet& packet) {
castSpell(nextSpell, nextTarget);
}
} else {
// For non-player casters: if no tracked cast state exists, this was an
// instant cast — play a brief one-shot spell animation before stopping
auto castIt = unitCastStates_.find(data.casterUnit);
bool wasTrackedCast = (castIt != unitCastStates_.end());
if (!wasTrackedCast && owner_.spellCastAnimCallback_) {
owner_.spellCastAnimCallback_(data.casterUnit, true, false);
}
if (owner_.spellCastAnimCallback_) {
owner_.spellCastAnimCallback_(data.casterUnit, false, false);
}
@ -1181,6 +1196,26 @@ void SpellHandler::handleAuraUpdate(network::Packet& packet, bool isAll) {
}
}
}
// Sprint aura detection — check if any sprint/dash speed buff is active
if (data.guid == owner_.playerGuid && owner_.sprintAuraCallback_) {
static const uint32_t sprintSpells[] = {
2983, 8696, 11305, // Rogue Sprint (ranks 1-3)
1850, 9821, 33357, // Druid Dash (ranks 1-3)
36554, // Shadowstep (speed component)
68992, 68991, // Darkflight (worgen racial)
58984, // Aspect of the Pack speed
};
bool hasSprint = false;
for (const auto& a : playerAuras_) {
if (a.isEmpty()) continue;
for (uint32_t sid : sprintSpells) {
if (a.spellId == sid) { hasSprint = true; break; }
}
if (hasSprint) break;
}
owner_.sprintAuraCallback_(hasSprint);
}
}
}
@ -2222,7 +2257,7 @@ void SpellHandler::handleSpellLogMiss(network::Packet& packet) {
// TBC: spellId(4) + uint64 caster + uint8 unk + uint32 count
// + count × (uint64 victim + uint8 missInfo)
// All expansions append uint32 reflectSpellId + uint8 reflectResult when
// missInfo==11 (REFLECT).
// missInfo==REFLECT (11).
const bool spellMissUsesFullGuid = isActiveExpansion("tbc");
auto readSpellMissGuid = [&]() -> uint64_t {
if (spellMissUsesFullGuid)
@ -2248,7 +2283,7 @@ void SpellHandler::handleSpellLogMiss(network::Packet& packet) {
struct SpellMissLogEntry {
uint64_t victimGuid = 0;
uint8_t missInfo = 0;
uint32_t reflectSpellId = 0; // Only valid when missInfo==11 (REFLECT)
uint32_t reflectSpellId = 0; // Only valid when missInfo==REFLECT
};
std::vector<SpellMissLogEntry> parsedMisses;
parsedMisses.reserve(storedLimit);
@ -2266,9 +2301,9 @@ void SpellHandler::handleSpellLogMiss(network::Packet& packet) {
return;
}
const uint8_t missInfo = packet.readUInt8();
// REFLECT (11): extra uint32 reflectSpellId + uint8 reflectResult
// REFLECT: extra uint32 reflectSpellId + uint8 reflectResult
uint32_t reflectSpellId = 0;
if (missInfo == 11) {
if (missInfo == SpellMissInfo::REFLECT) {
if (packet.hasRemaining(5)) {
reflectSpellId = packet.readUInt32();
/*uint8_t reflectResult =*/ packet.readUInt8();
@ -2912,7 +2947,7 @@ void SpellHandler::handleSpellLogExecute(network::Packet& packet) {
uint8_t effectType = packet.readUInt8();
uint32_t effectLogCount = packet.readUInt32();
effectLogCount = std::min(effectLogCount, 64u); // sanity
if (effectType == 10) {
if (effectType == SpellEffect::POWER_DRAIN) {
// SPELL_EFFECT_POWER_DRAIN: packed_guid target + uint32 amount + uint32 powerType + float multiplier
for (uint32_t li = 0; li < effectLogCount; ++li) {
if (!packet.hasRemaining(exeUsesFullGuid ? 8u : 1u)
@ -2950,7 +2985,7 @@ void SpellHandler::handleSpellLogExecute(network::Packet& packet) {
" power=", drainPower, " amount=", drainAmount,
" multiplier=", drainMult);
}
} else if (effectType == 11) {
} else if (effectType == SpellEffect::HEALTH_LEECH) {
// SPELL_EFFECT_HEALTH_LEECH: packed_guid target + uint32 amount + float multiplier
for (uint32_t li = 0; li < effectLogCount; ++li) {
if (!packet.hasRemaining(exeUsesFullGuid ? 8u : 1u)
@ -2983,7 +3018,7 @@ void SpellHandler::handleSpellLogExecute(network::Packet& packet) {
LOG_DEBUG("SMSG_SPELLLOGEXECUTE HEALTH_LEECH: spell=", exeSpellId,
" amount=", leechAmount, " multiplier=", leechMult);
}
} else if (effectType == 24 || effectType == 114) {
} else if (effectType == SpellEffect::CREATE_ITEM || effectType == SpellEffect::CREATE_ITEM2) {
// SPELL_EFFECT_CREATE_ITEM / CREATE_ITEM2: uint32 itemEntry per log entry
for (uint32_t li = 0; li < effectLogCount; ++li) {
if (!packet.hasRemaining(4)) break;
@ -3012,7 +3047,7 @@ void SpellHandler::handleSpellLogExecute(network::Packet& packet) {
}
}
}
} else if (effectType == 26) {
} else if (effectType == SpellEffect::INTERRUPT_CAST) {
// SPELL_EFFECT_INTERRUPT_CAST: packed_guid target + uint32 interrupted_spell_id
for (uint32_t li = 0; li < effectLogCount; ++li) {
if (!packet.hasRemaining(exeUsesFullGuid ? 8u : 1u)
@ -3033,7 +3068,7 @@ void SpellHandler::handleSpellLogExecute(network::Packet& packet) {
LOG_DEBUG("SMSG_SPELLLOGEXECUTE INTERRUPT_CAST: spell=", exeSpellId,
" interrupted=", icSpellId, " target=0x", std::hex, icTarget, std::dec);
}
} else if (effectType == 49) {
} else if (effectType == SpellEffect::FEED_PET) {
// SPELL_EFFECT_FEED_PET: uint32 itemEntry per log entry
for (uint32_t li = 0; li < effectLogCount; ++li) {
if (!packet.hasRemaining(4)) break;
@ -3182,6 +3217,12 @@ void SpellHandler::handleChannelStart(network::Packet& packet) {
}
LOG_DEBUG("MSG_CHANNEL_START: caster=0x", std::hex, chanCaster, std::dec,
" spell=", chanSpellId, " total=", chanTotalMs, "ms");
// Play channeling animation (looping)
if (owner_.spellCastAnimCallback_) {
owner_.spellCastAnimCallback_(chanCaster, true, true);
}
// Fire UNIT_SPELLCAST_CHANNEL_START for Lua addons
if (owner_.addonEventCallback_) {
auto unitId = owner_.guidToUnitId(chanCaster);
@ -3217,6 +3258,10 @@ void SpellHandler::handleChannelUpdate(network::Packet& packet) {
" remaining=", chanRemainMs, "ms");
// Fire UNIT_SPELLCAST_CHANNEL_STOP when channel ends
if (chanRemainMs == 0) {
// Stop channeling animation — return to idle
if (owner_.spellCastAnimCallback_) {
owner_.spellCastAnimCallback_(chanCaster2, false, true);
}
auto unitId = owner_.guidToUnitId(chanCaster2);
if (!unitId.empty())
owner_.fireAddonEvent("UNIT_SPELLCAST_CHANNEL_STOP", {unitId});

View file

@ -36,6 +36,7 @@ static const UFNameEntry kUFNames[] = {
{"UNIT_FIELD_AURAS", UF::UNIT_FIELD_AURAS},
{"UNIT_FIELD_AURAFLAGS", UF::UNIT_FIELD_AURAFLAGS},
{"UNIT_NPC_FLAGS", UF::UNIT_NPC_FLAGS},
{"UNIT_NPC_EMOTESTATE", UF::UNIT_NPC_EMOTESTATE},
{"UNIT_DYNAMIC_FLAGS", UF::UNIT_DYNAMIC_FLAGS},
{"UNIT_FIELD_RESISTANCES", UF::UNIT_FIELD_RESISTANCES},
{"UNIT_FIELD_STAT0", UF::UNIT_FIELD_STAT0},
@ -61,6 +62,7 @@ static const UFNameEntry kUFNames[] = {
{"PLAYER_SKILL_INFO_START", UF::PLAYER_SKILL_INFO_START},
{"PLAYER_EXPLORED_ZONES_START", UF::PLAYER_EXPLORED_ZONES_START},
{"GAMEOBJECT_DISPLAYID", UF::GAMEOBJECT_DISPLAYID},
{"GAMEOBJECT_BYTES_1", UF::GAMEOBJECT_BYTES_1},
{"ITEM_FIELD_STACK_COUNT", UF::ITEM_FIELD_STACK_COUNT},
{"ITEM_FIELD_DURABILITY", UF::ITEM_FIELD_DURABILITY},
{"ITEM_FIELD_MAXDURABILITY", UF::ITEM_FIELD_MAXDURABILITY},