Vanilla/Turtle WoW compatibility: fix UPDATE_OBJECT, chat, equipment, creatures

- Route SMSG_UPDATE_OBJECT through polymorphic parsers for correct
  vanilla format (uint8 updateFlags, 6 speeds vs WotLK uint16/9)
- Fix SMSG_DESTROY_OBJECT for vanilla (8 bytes, no isDeath field)
- Add MSG_MOVE_* handlers for other player movement relay
- Add ClassicPacketParsers::parseMessageChat with targetGuid read
  and monster-type name handling
- Resolve chat sender names from player name cache before display
- Fix CSV DBC field 0 always treated as numeric ID (fixes 16+ garbled
  Turtle CSVs including Map, AreaTable, Spell, CreatureDisplayInfo)
- Add CSV DBC validation: reject garbled CSVs (>80% zero IDs) and
  fall back to binary DBC files
- Fix ItemDisplayInfo texture component field index (14+ not 15+)
  for binary DBC with gender-aware suffix resolution
- Spawn other players as visible M2 models via creature callback
- Map name cache dedup prevents overwrites from duplicate CSV records
This commit is contained in:
Kelsi 2026-02-13 18:59:09 -08:00
parent 430c2bdcfa
commit fb0ae26fe6
13 changed files with 689 additions and 106 deletions

View file

@ -1101,15 +1101,20 @@ bool UpdateObjectParser::parse(network::Packet& packet, UpdateObjectData& data)
bool DestroyObjectParser::parse(network::Packet& packet, DestroyObjectData& data) {
// SMSG_DESTROY_OBJECT format:
// uint64 guid
// uint8 isDeath (0 = despawn, 1 = death)
// uint8 isDeath (0 = despawn, 1 = death) — WotLK only; vanilla/TBC omit this
if (packet.getSize() < 9) {
if (packet.getSize() < 8) {
LOG_ERROR("SMSG_DESTROY_OBJECT packet too small: ", packet.getSize(), " bytes");
return false;
}
data.guid = packet.readUInt64();
data.isDeath = (packet.readUInt8() != 0);
// WotLK adds isDeath byte; vanilla/TBC packets are exactly 8 bytes
if (packet.getReadPos() < packet.getSize()) {
data.isDeath = (packet.readUInt8() != 0);
} else {
data.isDeath = false;
}
LOG_INFO("Parsed SMSG_DESTROY_OBJECT:");
LOG_INFO(" GUID: 0x", std::hex, data.guid, std::dec);
@ -2102,12 +2107,24 @@ bool InitialSpellsParser::parse(network::Packet& packet, InitialSpellsData& data
data.talentSpec = packet.readUInt8();
uint16_t spellCount = packet.readUInt16();
LOG_INFO("SMSG_INITIAL_SPELLS: packetSize=", packetSize, " bytes, spellCount=", spellCount);
// Detect vanilla (uint16 spellId) vs WotLK (uint32 spellId) format
// Vanilla: 4 bytes/spell (uint16 id + uint16 slot), WotLK: 6 bytes/spell (uint32 id + uint16 unk)
size_t remainingAfterHeader = packetSize - 3; // subtract talentSpec(1) + spellCount(2)
bool vanillaFormat = remainingAfterHeader < static_cast<size_t>(spellCount) * 6 + 2;
LOG_INFO("SMSG_INITIAL_SPELLS: packetSize=", packetSize, " bytes, spellCount=", spellCount,
vanillaFormat ? " (vanilla uint16 format)" : " (WotLK uint32 format)");
data.spellIds.reserve(spellCount);
for (uint16_t i = 0; i < spellCount; ++i) {
uint32_t spellId = packet.readUInt32();
packet.readUInt16(); // unknown (always 0)
uint32_t spellId;
if (vanillaFormat) {
spellId = packet.readUInt16();
packet.readUInt16(); // slot
} else {
spellId = packet.readUInt32();
packet.readUInt16(); // unknown (always 0)
}
if (spellId != 0) {
data.spellIds.push_back(spellId);
}
@ -2117,7 +2134,11 @@ bool InitialSpellsParser::parse(network::Packet& packet, InitialSpellsData& data
data.cooldowns.reserve(cooldownCount);
for (uint16_t i = 0; i < cooldownCount; ++i) {
SpellCooldownEntry entry;
entry.spellId = packet.readUInt32();
if (vanillaFormat) {
entry.spellId = packet.readUInt16();
} else {
entry.spellId = packet.readUInt32();
}
entry.itemId = packet.readUInt16();
entry.categoryId = packet.readUInt16();
entry.cooldownMs = packet.readUInt32();