From 863ea742f674874075bd6c5bc3d36052d6a9359a Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 10 Mar 2026 11:14:58 -0700 Subject: [PATCH] net/game: initialise entity swim/walk state from spawn packet and SPLINE_MOVE opcodes UpdateBlock now stores moveFlags from the LIVING movement block so the cold-join problem is fixed: entities already swimming or walking when the client joins get their animation state correctly initialised from the SMSG_UPDATE_OBJECT CREATE_OBJECT packet rather than waiting for the next MSG_MOVE_* heartbeat. Additionally, SMSG_SPLINE_MOVE_START_SWIM, SMSG_SPLINE_MOVE_STOP_SWIM, SMSG_SPLINE_MOVE_SET_WALK_MODE, SMSG_SPLINE_MOVE_SET_RUN_MODE, and SMSG_SPLINE_MOVE_SET_FLYING now fire unitMoveFlagsCallback_ with synthesised flags so explicit server-driven mode transitions update animation state immediately without waiting for a heartbeat. --- include/game/world_packets.hpp | 4 ++++ src/game/game_handler.cpp | 37 +++++++++++++++++++++++++++------- src/game/world_packets.cpp | 1 + 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/include/game/world_packets.hpp b/include/game/world_packets.hpp index 7f62b622..c13659c3 100644 --- a/include/game/world_packets.hpp +++ b/include/game/world_packets.hpp @@ -481,6 +481,10 @@ struct UpdateBlock { // Update flags from movement block (for detecting transports, etc.) uint16_t updateFlags = 0; + // Raw movement flags from LIVING block (SWIMMING=0x200000, WALKING=0x100, CAN_FLY=0x800000, FLYING=0x1000000) + // Used to initialise swim/walk/fly state on entity spawn (cold-join). + uint32_t moveFlags = 0; + // Transport data from LIVING movement block (MOVEMENTFLAG_ONTRANSPORT) bool onTransport = false; uint64_t transportGuid = 0; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 8825cb25..7bb5ec6a 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -2347,24 +2347,40 @@ void GameHandler::handlePacket(network::Packet& packet) { case Opcode::SMSG_MONSTER_MOVE_TRANSPORT: handleMonsterMoveTransport(packet); break; - case Opcode::SMSG_SPLINE_MOVE_SET_WALK_MODE: - case Opcode::SMSG_SPLINE_MOVE_SET_RUN_MODE: case Opcode::SMSG_SPLINE_MOVE_FEATHER_FALL: case Opcode::SMSG_SPLINE_MOVE_GRAVITY_DISABLE: case Opcode::SMSG_SPLINE_MOVE_GRAVITY_ENABLE: case Opcode::SMSG_SPLINE_MOVE_LAND_WALK: case Opcode::SMSG_SPLINE_MOVE_NORMAL_FALL: case Opcode::SMSG_SPLINE_MOVE_ROOT: - case Opcode::SMSG_SPLINE_MOVE_SET_FLYING: - case Opcode::SMSG_SPLINE_MOVE_SET_HOVER: - case Opcode::SMSG_SPLINE_MOVE_START_SWIM: - case Opcode::SMSG_SPLINE_MOVE_STOP_SWIM: { - // Minimal parse: PackedGuid only — entity state flag change. + case Opcode::SMSG_SPLINE_MOVE_SET_HOVER: { + // Minimal parse: PackedGuid only — no animation-relevant state change. if (packet.getSize() - packet.getReadPos() >= 1) { (void)UpdateObjectParser::readPackedGuid(packet); } break; } + case Opcode::SMSG_SPLINE_MOVE_SET_WALK_MODE: + case Opcode::SMSG_SPLINE_MOVE_SET_RUN_MODE: + case Opcode::SMSG_SPLINE_MOVE_SET_FLYING: + case Opcode::SMSG_SPLINE_MOVE_START_SWIM: + case Opcode::SMSG_SPLINE_MOVE_STOP_SWIM: { + // PackedGuid + synthesised move-flags → drives animation state in application layer. + // SWIMMING=0x00200000, WALKING=0x00000100, CAN_FLY=0x00800000, FLYING=0x01000000 + if (packet.getSize() - packet.getReadPos() < 1) break; + uint64_t guid = UpdateObjectParser::readPackedGuid(packet); + if (guid == 0 || guid == playerGuid || !unitMoveFlagsCallback_) break; + uint32_t synthFlags = 0; + if (*logicalOp == Opcode::SMSG_SPLINE_MOVE_START_SWIM) + synthFlags = 0x00200000u; // SWIMMING + else if (*logicalOp == Opcode::SMSG_SPLINE_MOVE_SET_WALK_MODE) + synthFlags = 0x00000100u; // WALKING + else if (*logicalOp == Opcode::SMSG_SPLINE_MOVE_SET_FLYING) + synthFlags = 0x01000000u | 0x00800000u; // FLYING | CAN_FLY + // STOP_SWIM and SET_RUN_MODE: synthFlags stays 0 → clears swim/walk + unitMoveFlagsCallback_(guid, synthFlags); + break; + } case Opcode::SMSG_SPLINE_SET_RUN_SPEED: case Opcode::SMSG_SPLINE_SET_RUN_BACK_SPEED: case Opcode::SMSG_SPLINE_SET_SWIM_SPEED: { @@ -7786,6 +7802,13 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { npcDeathCallback_(block.guid); } } + // Initialise swim/walk state from spawn-time movement flags (cold-join fix). + // Without this, an entity already swimming/walking when the client joins + // won't get its animation state set until the next MSG_MOVE_* heartbeat. + if (block.hasMovement && block.moveFlags != 0 && unitMoveFlagsCallback_ && + block.guid != playerGuid) { + unitMoveFlagsCallback_(block.guid, block.moveFlags); + } // Query quest giver status for NPCs with questgiver flag (0x02) if (block.objectType == ObjectType::UNIT && (unit->getNpcFlags() & 0x02) && socket) { network::Packet qsPkt(wireOpcode(Opcode::CMSG_QUESTGIVER_STATUS_QUERY)); diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index 694a61cd..b736391e 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -904,6 +904,7 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock /*float pitchRate =*/ packet.readFloat(); block.runSpeed = runSpeed; + block.moveFlags = moveFlags; // Spline data if (moveFlags & 0x08000000) { // MOVEMENTFLAG_SPLINE_ENABLED