UpdateBlock::fields and Entity::fields were both std::map<uint16_t,
uint32_t>. Every UPDATE_OBJECT packet allocated one red-black-tree
node per parsed field (typically ~20 fields × dozens of blocks per
packet), trashing the allocator and producing pointer-chained
iteration on every consumer scan.
Introduce FlatFieldMap (include/game/flat_field_map.hpp): a sorted
std::vector<std::pair<uint16_t, uint32_t>> wrapper with the same
iterator-based API the existing 50+ call sites use — find(), end(),
operator[], range-for, ->first/->second on iterators. The parser
walks the bitmask low-to-high so append_sorted() is O(1) amortized;
Entity::setField falls back to insert_or_assign which is O(log N)
search + O(N) shift but N is small (~60 fields per entity).
Switched every signature on the path:
- UpdateBlock::fields, Entity::fields storage
- extractContainerFields / detectInventorySlotBases /
applyInventoryFields / updateOtherPlayerVisibleItems
- detectPlayerMountChange / maybeDetectCoinageIndex /
applyPlayerStatFields / extractPlayerAppearance
- extractSkillFields / extractExploredZoneFields /
applyQuestStateFromFields
- lastPlayerFields_ snapshot
Icon==6 and text=="GOSSIP_OPTION_BANKER" both sent BANKER_ACTIVATE
independently. Banking NPCs match both, so the packet was sent twice —
some servers toggle the bank window open then closed. Added sentBanker
guard so only one packet is sent.
Also extracts classifyGossipQuests() from two identical 30-line blocks
in handleGossipMessage and handleQuestgiverQuestList. The icon→status
mapping (5/6/10=completable, 3/4=incomplete, 2/7/8=available) is now
in one place with a why-comment explaining these are protocol-defined.
Extract domain-specific logic from the monolithic GameHandler into
dedicated handler classes, each owning its own opcode registration,
state, and packet parsing:
- CombatHandler: combat, XP, kill, PvP, loot roll (~26 methods)
- SpellHandler: spells, auras, pet stable, talent (~3+ methods)
- SocialHandler: friends, guild, groups, BG, RAF, PvP AFK (~14+ methods)
- ChatHandler: chat messages, channels, GM tickets, server messages,
defense/area-trigger messages (~7+ methods)
- InventoryHandler: items, trade, loot, mail, vendor, equipment sets,
read item (~3+ methods)
- QuestHandler: gossip, quests, completed quest response (~5+ methods)
- MovementHandler: movement, follow, transport (~2 methods)
- WardenHandler: Warden anti-cheat module
Each handler registers its own dispatch table entries via
registerOpcodes(DispatchTable&), called from
GameHandler::registerOpcodeHandlers(). GameHandler retains core
orchestration: auth/session handshake, update-object parsing,
opcode routing, and cross-handler coordination.
game_handler.cpp reduced from ~10,188 to ~9,432 lines.
Also add a POST_BUILD CMake step to symlink Data/ next to the
executable so expansion profiles and opcode tables are found at
runtime when running from build/bin/.