diff --git a/src/game/entity_controller.cpp b/src/game/entity_controller.cpp index 2af369de..ff4f0d80 100644 --- a/src/game/entity_controller.cpp +++ b/src/game/entity_controller.cpp @@ -73,17 +73,24 @@ EntityController::EntityController(GameHandler& owner) : owner_(owner) { initTypeHandlers(); } void EntityController::registerOpcodes(DispatchTable& table) { - // World object updates - table[Opcode::SMSG_UPDATE_OBJECT] = [this](network::Packet& packet) { + // World object updates — accept during ENTERING_WORLD too so that entity + // packets arriving before SMSG_LOGIN_VERIFY_WORLD are parsed and queued + // rather than silently dropped (the budget system processes them later once + // the state transitions to IN_WORLD). + auto inWorldOrEntering = [this]() { + auto s = owner_.getState(); + return s == WorldState::IN_WORLD || s == WorldState::ENTERING_WORLD; + }; + table[Opcode::SMSG_UPDATE_OBJECT] = [this, inWorldOrEntering](network::Packet& packet) { LOG_DEBUG("Received SMSG_UPDATE_OBJECT, state=", static_cast(owner_.getState()), " size=", packet.getSize()); - if (owner_.getState() == WorldState::IN_WORLD) handleUpdateObject(packet); + if (inWorldOrEntering()) handleUpdateObject(packet); }; - table[Opcode::SMSG_COMPRESSED_UPDATE_OBJECT] = [this](network::Packet& packet) { + table[Opcode::SMSG_COMPRESSED_UPDATE_OBJECT] = [this, inWorldOrEntering](network::Packet& packet) { LOG_DEBUG("Received SMSG_COMPRESSED_UPDATE_OBJECT, state=", static_cast(owner_.getState()), " size=", packet.getSize()); - if (owner_.getState() == WorldState::IN_WORLD) handleCompressedUpdateObject(packet); + if (inWorldOrEntering()) handleCompressedUpdateObject(packet); }; - table[Opcode::SMSG_DESTROY_OBJECT] = [this](network::Packet& packet) { - if (owner_.getState() == WorldState::IN_WORLD) handleDestroyObject(packet); + table[Opcode::SMSG_DESTROY_OBJECT] = [this, inWorldOrEntering](network::Packet& packet) { + if (inWorldOrEntering()) handleDestroyObject(packet); }; // Entity queries diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index c4497b79..4270803d 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -1029,25 +1029,37 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock // Save position before WotLK spline header for fallback size_t beforeSplineHeader = packet.getReadPos(); + // AzerothCore MoveSplineFlag constants: + // CATMULLROM = 0x00080000 — uncompressed Catmull-Rom interpolation + // CYCLIC = 0x00100000 — cyclic path + // ENTER_CYCLE = 0x00200000 — entering cyclic path + // ANIMATION = 0x00400000 — animation spline with animType+effectStart + // PARABOLIC = 0x00000008 — vertical_acceleration+effectStartTime + constexpr uint32_t SF_PARABOLIC = 0x00000008; + constexpr uint32_t SF_CATMULLROM = 0x00080000; + constexpr uint32_t SF_CYCLIC = 0x00100000; + constexpr uint32_t SF_ENTER_CYCLE = 0x00200000; + constexpr uint32_t SF_ANIMATION = 0x00400000; + constexpr uint32_t SF_UNCOMPRESSED_MASK = SF_CATMULLROM | SF_CYCLIC | SF_ENTER_CYCLE; + // Try 1: WotLK format (durationMod+durationModNext+[ANIMATION]+vertAccel+effectStart+points) + // Some servers (ChromieCraft) always write vertAccel+effectStart unconditionally. bool splineParsed = false; if (bytesAvailable(8)) { /*float durationMod =*/ packet.readFloat(); /*float durationModNext =*/ packet.readFloat(); bool wotlkOk = true; - if (splineFlags & 0x00400000) { // SPLINEFLAG_ANIMATION + if (splineFlags & SF_ANIMATION) { if (!bytesAvailable(5)) { wotlkOk = false; } else { packet.readUInt8(); packet.readUInt32(); } } - // AzerothCore/ChromieCraft always writes verticalAcceleration(float) - // + effectStartTime(uint32) unconditionally -- NOT gated by PARABOLIC flag. + // Unconditional vertAccel+effectStart (ChromieCraft/some AzerothCore builds) if (wotlkOk) { if (!bytesAvailable(8)) { wotlkOk = false; } else { /*float vertAccel =*/ packet.readFloat(); /*uint32_t effectStart =*/ packet.readUInt32(); } } if (wotlkOk) { - // WotLK: compressed unless CYCLIC(0x80000) or ENTER_CYCLE(0x2000) set - bool useCompressed = (splineFlags & (0x00080000 | 0x00002000)) == 0; + bool useCompressed = (splineFlags & SF_UNCOMPRESSED_MASK) == 0; splineParsed = tryParseSplinePoints(useCompressed, "wotlk-compressed"); if (!splineParsed) { splineParsed = tryParseSplinePoints(false, "wotlk-uncompressed"); @@ -1055,29 +1067,72 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock } } - if (!splineParsed) { - // WotLK compressed+uncompressed both failed. Try without the parabolic - // fields (some cores don't send vertAccel+effectStart unconditionally). + // Try 2: ANIMATION present but vertAccel+effectStart gated by PARABOLIC + // (standard AzerothCore: only writes vertAccel+effectStart when PARABOLIC is set) + if (!splineParsed && (splineFlags & SF_ANIMATION)) { + packet.setReadPos(beforeSplineHeader); + if (bytesAvailable(8)) { + packet.readFloat(); // durationMod + packet.readFloat(); // durationModNext + bool ok = true; + if (!bytesAvailable(5)) { ok = false; } + else { packet.readUInt8(); packet.readUInt32(); } // animType + effectStart + if (ok && (splineFlags & SF_PARABOLIC)) { + if (!bytesAvailable(8)) { ok = false; } + else { packet.readFloat(); packet.readUInt32(); } + } + if (ok) { + bool useCompressed = (splineFlags & SF_UNCOMPRESSED_MASK) == 0; + splineParsed = tryParseSplinePoints(useCompressed, "wotlk-anim-conditional"); + if (!splineParsed) { + splineParsed = tryParseSplinePoints(false, "wotlk-anim-conditional-uncomp"); + } + } + } + } + + // Try 3: No ANIMATION — vertAccel+effectStart only when PARABOLIC set + if (!splineParsed) { + packet.setReadPos(beforeSplineHeader); + if (bytesAvailable(8)) { + packet.readFloat(); // durationMod + packet.readFloat(); // durationModNext + bool ok = true; + if (splineFlags & SF_PARABOLIC) { + if (!bytesAvailable(8)) { ok = false; } + else { packet.readFloat(); packet.readUInt32(); } + } + if (ok) { + bool useCompressed = (splineFlags & SF_UNCOMPRESSED_MASK) == 0; + splineParsed = tryParseSplinePoints(useCompressed, "wotlk-parabolic-gated"); + if (!splineParsed) { + splineParsed = tryParseSplinePoints(false, "wotlk-parabolic-gated-uncomp"); + } + } + } + } + + // Try 4: No header at all — just durationMod+durationModNext then points + if (!splineParsed) { packet.setReadPos(beforeSplineHeader); if (bytesAvailable(8)) { packet.readFloat(); // durationMod packet.readFloat(); // durationModNext - // Skip parabolic fields — try points directly splineParsed = tryParseSplinePoints(false, "wotlk-no-parabolic"); if (!splineParsed) { - bool useComp = (splineFlags & (0x00080000 | 0x00002000)) == 0; + bool useComp = (splineFlags & SF_UNCOMPRESSED_MASK) == 0; splineParsed = tryParseSplinePoints(useComp, "wotlk-no-parabolic-compressed"); } } } - // Try 3: bare points (no WotLK header at all — some spline types skip everything) + // Try 5: bare points (no WotLK header at all — some spline types skip everything) if (!splineParsed) { packet.setReadPos(beforeSplineHeader); splineParsed = tryParseSplinePoints(false, "bare-uncompressed"); if (!splineParsed) { packet.setReadPos(beforeSplineHeader); - bool useComp = (splineFlags & (0x00080000 | 0x00002000)) == 0; + bool useComp = (splineFlags & SF_UNCOMPRESSED_MASK) == 0; splineParsed = tryParseSplinePoints(useComp, "bare-compressed"); } } @@ -1090,8 +1145,8 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock d[di] = packet.readUInt32(); packet.setReadPos(beforeSplineHeader); LOG_WARNING("WotLK spline parse failed for guid=0x", std::hex, block.guid, std::dec, - " splineFlags=0x", splineFlags, - " remaining=", std::dec, packet.getRemainingSize(), + " splineFlags=0x", std::hex, splineFlags, std::dec, + " remaining=", packet.getRemainingSize(), " header=[0x", std::hex, d[0], " 0x", d[1], " 0x", d[2], " 0x", d[3], " 0x", d[4], "]", std::dec); return false; @@ -1424,10 +1479,12 @@ bool UpdateObjectParser::parse(network::Packet& packet, UpdateObjectData& data) UpdateBlock block; if (!parseUpdateBlock(packet, block)) { static int parseBlockErrors = 0; - if (++parseBlockErrors <= 5) { + const uint32_t lostBlocks = data.blockCount - i; + if (++parseBlockErrors <= 10) { LOG_ERROR("Failed to parse update block ", i + 1, " of ", data.blockCount, - " (", i, " blocks parsed successfully before failure)"); - if (parseBlockErrors == 5) + " (", i, " blocks parsed, ", lostBlocks, " blocks LOST", + ", remaining=", packet.getRemainingSize(), " bytes)"); + if (parseBlockErrors == 10) LOG_ERROR("(suppressing further update block parse errors)"); } // Cannot reliably re-sync to the next block after a parse failure,