diff options
Diffstat (limited to 'src/server/game/Handlers/MovementHandler.cpp')
-rw-r--r-- | src/server/game/Handlers/MovementHandler.cpp | 157 |
1 files changed, 101 insertions, 56 deletions
diff --git a/src/server/game/Handlers/MovementHandler.cpp b/src/server/game/Handlers/MovementHandler.cpp index 3705afd2f4a..ddfd2468a4d 100644 --- a/src/server/game/Handlers/MovementHandler.cpp +++ b/src/server/game/Handlers/MovementHandler.cpp @@ -25,6 +25,7 @@ #include "MapManager.h" #include "MotionMaster.h" #include "MovementGenerator.h" +#include "MovementPacketSender.h" #include "MoveSpline.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" @@ -450,78 +451,105 @@ void WorldSession::HandleForceSpeedChangeAck(WorldPacket &recvData) { /* extract packet */ ObjectGuid guid; - uint32 unk1; - float newspeed; - recvData >> guid.ReadAsPacked(); - if (!IsRightUnitBeingMoved(guid)) + GameClient* client = GetGameClient(); + + // ACK handlers should call GameClient::IsAllowedToMove instead of WorldSession::IsRightUnitBeingMoved + // because the ACK could be coming from a unit that is under the control of that client but is not the 'Active Mover' unit. + // Example: Get a speed buff on yourself, then mount a vehicle before the end of the buff. When the buff expires, + // a force message will be sent to the client regarding the player and the client is required to respond with an ACK. + // But the vehicle will be the active mover unit at that time. + if (!client->IsAllowedToMove(guid)) { recvData.rfinish(); // prevent warnings spam + TC_LOG_DEBUG("entities.unit", "Ignoring ACK. Bad or outdated movement data by Player %s", _player->GetName().c_str()); return; } - GameClient* client = GetGameClient(); - Unit* mover = client->GetActivelyMovedUnit(); - - // continue parse packet + Unit* mover = ObjectAccessor::GetUnit(*_player, guid); - recvData >> unk1; // counter or moveEvent + UnitMoveType move_type; + switch (recvData.GetOpcode()) + { + case CMSG_FORCE_WALK_SPEED_CHANGE_ACK: move_type = MOVE_WALK; break; + case CMSG_FORCE_RUN_SPEED_CHANGE_ACK: move_type = MOVE_RUN; break; + case CMSG_FORCE_RUN_BACK_SPEED_CHANGE_ACK: move_type = MOVE_RUN_BACK; break; + case CMSG_FORCE_SWIM_SPEED_CHANGE_ACK: move_type = MOVE_SWIM; break; + case CMSG_FORCE_SWIM_BACK_SPEED_CHANGE_ACK: move_type = MOVE_SWIM_BACK; break; + case CMSG_FORCE_TURN_RATE_CHANGE_ACK: move_type = MOVE_TURN_RATE; break; + case CMSG_FORCE_FLIGHT_SPEED_CHANGE_ACK: move_type = MOVE_FLIGHT; break; + case CMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE_ACK: move_type = MOVE_FLIGHT_BACK; break; + case CMSG_FORCE_PITCH_RATE_CHANGE_ACK: move_type = MOVE_PITCH_RATE; break; + default: + TC_LOG_ERROR("network", "WorldSession::HandleForceSpeedChangeAck: Unknown move type opcode: %s", GetOpcodeNameForLogging(static_cast<OpcodeClient>(recvData.GetOpcode())).c_str()); + return; + } + uint32 movementCounter; + float speedReceived; MovementInfo movementInfo; movementInfo.guid = guid; - ReadMovementInfo(recvData, &movementInfo); - recvData >> newspeed; - /*----------------*/ - - // client ACK send one packet for mounted/run case and need skip all except last from its - // in other cases anti-cheat check can be fail in false case - UnitMoveType move_type; - UnitMoveType force_move_type; - - static char const* move_type_name[MAX_MOVE_TYPE] = { "Walk", "Run", "RunBack", "Swim", "SwimBack", "TurnRate", "Flight", "FlightBack", "PitchRate" }; + recvData >> movementCounter; + ReadMovementInfo(recvData, &movementInfo); + recvData >> speedReceived; - switch (recvData.GetOpcode()) + // verify that indeed the client is replying with the changes that were send to him + if (!mover->HasPendingMovementChange() || mover->PeakFirstPendingMovementChange().movementCounter > movementCounter) { - case CMSG_FORCE_WALK_SPEED_CHANGE_ACK: move_type = MOVE_WALK; force_move_type = MOVE_WALK; break; - case CMSG_FORCE_RUN_SPEED_CHANGE_ACK: move_type = MOVE_RUN; force_move_type = MOVE_RUN; break; - case CMSG_FORCE_RUN_BACK_SPEED_CHANGE_ACK: move_type = MOVE_RUN_BACK; force_move_type = MOVE_RUN_BACK; break; - case CMSG_FORCE_SWIM_SPEED_CHANGE_ACK: move_type = MOVE_SWIM; force_move_type = MOVE_SWIM; break; - case CMSG_FORCE_SWIM_BACK_SPEED_CHANGE_ACK: move_type = MOVE_SWIM_BACK; force_move_type = MOVE_SWIM_BACK; break; - case CMSG_FORCE_TURN_RATE_CHANGE_ACK: move_type = MOVE_TURN_RATE; force_move_type = MOVE_TURN_RATE; break; - case CMSG_FORCE_FLIGHT_SPEED_CHANGE_ACK: move_type = MOVE_FLIGHT; force_move_type = MOVE_FLIGHT; break; - case CMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE_ACK: move_type = MOVE_FLIGHT_BACK; force_move_type = MOVE_FLIGHT_BACK; break; - case CMSG_FORCE_PITCH_RATE_CHANGE_ACK: move_type = MOVE_PITCH_RATE; force_move_type = MOVE_PITCH_RATE; break; + TC_LOG_DEBUG("entities.unit", "Ignoring ACK. Bad or outdated movement data by Player %s", _player->GetName().c_str()); + return; + } + + PlayerMovementPendingChange pendingChange = mover->PopPendingMovementChange(); + float speedSent = pendingChange.newValue; + MovementChangeType changeType = pendingChange.movementChangeType; + UnitMoveType moveTypeSent; + switch (changeType) + { + case MovementChangeType::SPEED_CHANGE_WALK: moveTypeSent = MOVE_WALK; break; + case MovementChangeType::SPEED_CHANGE_RUN: moveTypeSent = MOVE_RUN; break; + case MovementChangeType::SPEED_CHANGE_RUN_BACK: moveTypeSent = MOVE_RUN_BACK; break; + case MovementChangeType::SPEED_CHANGE_SWIM: moveTypeSent = MOVE_SWIM; break; + case MovementChangeType::SPEED_CHANGE_SWIM_BACK: moveTypeSent = MOVE_SWIM_BACK; break; + case MovementChangeType::RATE_CHANGE_TURN: moveTypeSent = MOVE_TURN_RATE; break; + case MovementChangeType::SPEED_CHANGE_FLIGHT_SPEED: moveTypeSent = MOVE_FLIGHT; break; + case MovementChangeType::SPEED_CHANGE_FLIGHT_BACK_SPEED: moveTypeSent = MOVE_FLIGHT_BACK; break; + case MovementChangeType::RATE_CHANGE_PITCH: moveTypeSent = MOVE_PITCH_RATE; break; default: - TC_LOG_ERROR("network", "WorldSession::HandleForceSpeedChangeAck: Unknown move type opcode: %u", recvData.GetOpcode()); + TC_LOG_INFO("cheat", "WorldSession::HandleForceSpeedChangeAck: Player %s from account id %u kicked for incorrect data returned in an ack", + _player->GetName().c_str(), _player->GetSession()->GetAccountId()); + _player->GetSession()->KickPlayer("incorrect data returned in an ack"); return; } - // skip all forced speed changes except last and unexpected - // in run/mounted case used one ACK and it must be skipped.m_forced_speed_changes[MOVE_RUN} store both. - if (mover->m_forced_speed_changes[force_move_type] > 0) + if (pendingChange.movementCounter != movementCounter || std::fabs(speedSent - speedReceived) > 0.01f || moveTypeSent!= move_type) { - --mover->m_forced_speed_changes[force_move_type]; - if (mover->m_forced_speed_changes[force_move_type] > 0) - return; + TC_LOG_INFO("cheat", "WorldSession::HandleForceSpeedChangeAck: Player %s from account id %u kicked for incorrect data returned in an ack", + _player->GetName().c_str(), _player->GetSession()->GetAccountId()); + _player->GetSession()->KickPlayer("incorrect data returned in an ack"); + return; } - if (!mover->GetTransport() && std::fabs(mover->GetSpeed(move_type) - newspeed) > 0.01f) + /* the client data has been verified. let's do the actual change now */ + int64 movementTime = (int64)movementInfo.time + _timeSyncClockDelta; + if (_timeSyncClockDelta == 0 || movementTime < 0 || movementTime > 0xFFFFFFFF) { - if (mover->GetSpeed(move_type) > newspeed) // must be greater - just correct - { - TC_LOG_ERROR("network", "%sSpeedChange player %s is NOT correct (must be %f instead %f), force set to correct value", - move_type_name[move_type], _player->GetName().c_str(), mover->GetSpeed(move_type), newspeed); - mover->SetSpeedRate(move_type, mover->GetSpeedRate(move_type)); - } - else // must be lesser - cheating - { - TC_LOG_DEBUG("misc", "Player %s from account id %u kicked for incorrect speed (must be %f instead %f)", - _player->GetName().c_str(), _player->GetSession()->GetAccountId(), mover->GetSpeed(move_type), newspeed); - _player->GetSession()->KickPlayer("WorldSession::HandleForceSpeedChangeAck Incorrect speed"); - } + TC_LOG_WARN("misc", "The computed movement time using clockDelta is erronous. Using fallback instead"); + movementInfo.time = GameTime::GetGameTimeMS(); + } + else + { + movementInfo.time = (uint32)movementTime; } + + mover->m_movementInfo = movementInfo; + mover->UpdatePosition(movementInfo.pos); + + float newSpeedRate = speedSent / (mover->IsControlledByPlayer() ? playerBaseMoveSpeed[move_type] : baseMoveSpeed[move_type]); + mover->SetSpeedRateReal(move_type, newSpeedRate); + MovementPacketSender::SendSpeedChangeToObservers(mover, move_type, speedSent); } void WorldSession::HandleSetActiveMoverOpcode(WorldPacket &recvData) @@ -561,8 +589,7 @@ void WorldSession::HandleMoveNotActiveMover(WorldPacket &recvData) if (client->GetActivelyMovedUnit() == nullptr || client->GetActivelyMovedUnit()->GetGUID() != old_mover_guid) { - // this shouldn't never happen in theory - TC_LOG_WARN("entities.unit", "unset active mover FAILED for client of player %s. GUID %s.", _player->GetName().c_str(), old_mover_guid.ToString().c_str()); + TC_LOG_DEBUG("entities.unit", "unset active mover FAILED for client of player %s. GUID %s.", _player->GetName().c_str(), old_mover_guid.ToString().c_str()); return; } @@ -585,25 +612,43 @@ void WorldSession::HandleMoveKnockBackAck(WorldPacket& recvData) ObjectGuid guid; recvData >> guid.ReadAsPacked(); - if (!IsRightUnitBeingMoved(guid)) + GameClient* client = GetGameClient(); + + // ACK handlers should call GameClient::IsAllowedToMove instead of WorldSession::IsRightUnitBeingMoved + // because the ACK could be coming from a unit that is under the control of that client but is not the 'Active Mover' unit. + // Example: Get a speed buff on yourself, then mount a vehicle before the end of the buff. When the buff expires, + // a force message will be sent to the client regarding the player and the client is required to respond with an ACK. + // But the vehicle will be the active mover unit at that time. + if (!client->IsAllowedToMove(guid)) { recvData.rfinish(); // prevent warnings spam return; } + Unit* mover = ObjectAccessor::GetUnit(*_player, guid); + recvData.read_skip<uint32>(); // unk MovementInfo movementInfo; + movementInfo.guid = guid; ReadMovementInfo(recvData, &movementInfo); - GameClient* client = GetGameClient(); - Unit* mover = client->GetActivelyMovedUnit(); + int64 movementTime = (int64)movementInfo.time + _timeSyncClockDelta; + if (_timeSyncClockDelta == 0 || movementTime < 0 || movementTime > 0xFFFFFFFF) + { + TC_LOG_WARN("misc", "The computed movement time using clockDelta is erronous. Using fallback instead"); + movementInfo.time = GameTime::GetGameTimeMS(); + } + else + { + movementInfo.time = (uint32)movementTime; + } mover->m_movementInfo = movementInfo; + mover->UpdatePosition(movementInfo.pos); WorldPacket data(MSG_MOVE_KNOCK_BACK, 66); - data << guid.WriteAsPacked(); - mover->BuildMovementPacket(&data); + WriteMovementInfo(&data, &movementInfo); // knockback specific info data << movementInfo.jump.sinAngle; |