diff options
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 5 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/Unit.cpp | 209 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/Unit.h | 81 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/UnitDefines.h | 51 | ||||
-rw-r--r-- | src/server/game/Handlers/MovementHandler.cpp | 157 | ||||
-rw-r--r-- | src/server/game/Movement/MovementPacketSender.cpp | 106 | ||||
-rw-r--r-- | src/server/game/Movement/MovementPacketSender.h | 132 | ||||
-rw-r--r-- | src/server/game/Server/GameClient.cpp | 4 | ||||
-rw-r--r-- | src/server/game/Server/GameClient.h | 1 | ||||
-rw-r--r-- | src/server/game/Server/WorldSession.cpp | 16 | ||||
-rw-r--r-- | src/server/game/Server/WorldSession.h | 2 | ||||
-rw-r--r-- | src/server/game/World/World.cpp | 3 | ||||
-rw-r--r-- | src/server/game/World/World.h | 1 | ||||
-rw-r--r-- | src/server/worldserver/worldserver.conf.dist | 19 |
14 files changed, 619 insertions, 168 deletions
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index afbde151123..3407da8dbdf 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -1806,6 +1806,9 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati //remove auras before removing from map... RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_CHANGE_MAP | AURA_INTERRUPT_FLAG_MOVE | AURA_INTERRUPT_FLAG_TURNING); + // players on mount will be dismounted. the speed and height change should not require an ACK and should be applied directly + PurgeAndApplyPendingMovementChanges(false); + if (!GetSession()->PlayerLogout()) { // send transfer packets @@ -22723,7 +22726,7 @@ void Player::SendInitialPacketsBeforeAddToMap() /// SMSG_RESYNC_RUNES ResyncRunes(); - GetSession()->GetGameClient()->AddAllowedMover(this); + GetGameClient()->SetMovedUnit(this, true); } void Player::SendInitialPacketsAfterAddToMap() diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 1d1b1ebe126..eef6eac817a 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -52,6 +52,7 @@ #include "ObjectMgr.h" #include "Opcodes.h" #include "OutdoorPvP.h" +#include "MovementPacketSender.h" #include "PassiveAI.h" #include "PetAI.h" #include "Pet.h" @@ -316,6 +317,8 @@ Unit::Unit(bool isWorldObject) : m_extraAttacks = 0; m_canDualWield = false; + m_movementCounter = 0; + m_rootTimes = 0; m_state = 0; @@ -368,9 +371,6 @@ Unit::Unit(bool isWorldObject) : for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i) m_speed_rate[i] = 1.0f; - for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i) - m_forced_speed_changes[i] = 0; - m_charmInfo = nullptr; _gameClientMovingMe = nullptr; @@ -432,6 +432,8 @@ void Unit::Update(uint32 p_time) // Or else we may have some SPELL_STATE_FINISHED spells stalled in pointers, that is bad. m_Events.Update(p_time); + CheckPendingMovementAcks(); + if (!IsInWorld()) return; @@ -8614,69 +8616,37 @@ void Unit::SetSpeedRate(UnitMoveType mtype, float rate) rate = 0.0f; // Update speed only on change - if (m_speed_rate[mtype] == rate) + MovementChangeType changeType = MovementPacketSender::GetChangeTypeByMoveType(mtype); + if (m_speed_rate[mtype] == rate && !HasPendingMovementChange(changeType)) //todo: is the "!HasPendingMovementChange" part necessary here? return; - m_speed_rate[mtype] = rate; - - PropagateSpeedChange(); - - // Spline packets are for units controlled by AI. "Force speed change" (wrongly named opcodes) and "move set speed" packets are for units controlled by a player. - static Opcodes const moveTypeToOpcode[MAX_MOVE_TYPE][3] = - { - {SMSG_SPLINE_SET_WALK_SPEED, SMSG_FORCE_WALK_SPEED_CHANGE, MSG_MOVE_SET_WALK_SPEED }, - {SMSG_SPLINE_SET_RUN_SPEED, SMSG_FORCE_RUN_SPEED_CHANGE, MSG_MOVE_SET_RUN_SPEED }, - {SMSG_SPLINE_SET_RUN_BACK_SPEED, SMSG_FORCE_RUN_BACK_SPEED_CHANGE, MSG_MOVE_SET_RUN_BACK_SPEED }, - {SMSG_SPLINE_SET_SWIM_SPEED, SMSG_FORCE_SWIM_SPEED_CHANGE, MSG_MOVE_SET_SWIM_SPEED }, - {SMSG_SPLINE_SET_SWIM_BACK_SPEED, SMSG_FORCE_SWIM_BACK_SPEED_CHANGE, MSG_MOVE_SET_SWIM_BACK_SPEED }, - {SMSG_SPLINE_SET_TURN_RATE, SMSG_FORCE_TURN_RATE_CHANGE, MSG_MOVE_SET_TURN_RATE }, - {SMSG_SPLINE_SET_FLIGHT_SPEED, SMSG_FORCE_FLIGHT_SPEED_CHANGE, MSG_MOVE_SET_FLIGHT_SPEED }, - {SMSG_SPLINE_SET_FLIGHT_BACK_SPEED, SMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE, MSG_MOVE_SET_FLIGHT_BACK_SPEED }, - {SMSG_SPLINE_SET_PITCH_RATE, SMSG_FORCE_PITCH_RATE_CHANGE, MSG_MOVE_SET_PITCH_RATE }, - }; - - if (GetTypeId() == TYPEID_PLAYER) + float newSpeedFlat = rate * (IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype]); + if (IsMovedByClient() && IsInWorld()) + MovementPacketSender::SendSpeedChangeToMover(this, mtype, newSpeedFlat); + else if (IsMovedByClient() && !IsInWorld()) // (1) + SetSpeedRateReal(mtype, rate); + else // <=> if(!IsMovedByPlayer()) { - // register forced speed changes for WorldSession::HandleForceSpeedChangeAck - // and do it only for real sent packets and use run for run/mounted as client expected - ++m_forced_speed_changes[mtype]; - - if (!IsInCombat()) - if (Pet* pet = ToPlayer()->GetPet()) - pet->SetSpeedRate(mtype, m_speed_rate[mtype]); + SetSpeedRateReal(mtype, rate); + MovementPacketSender::SendSpeedChangeToAll(this, mtype, newSpeedFlat); } - if (IsMovedByClient()) // unit controlled by a player. - { - Player* playerMover = GetGameClientMovingMe()->GetBasePlayer(); + // explaination of (1): + // If the player is not in the world yet, it won't reply to the packets requiring an ack. And once the player is in the world, next time a movement + // packet which requires an ack is sent to the client (change of speed for example), the client is kicked from the + // server on the ground that it should have replied to the first packet first. That line is a hacky fix + // in the sense that it doesn't work like that in retail since buffs are applied only after the player has been + // initialized in the world. cf description of PR #18771 +} - // Send notification to self. this packet is only sent to one client (the client of the player concerned by the change). - WorldPacket self; - self.Initialize(moveTypeToOpcode[mtype][1], mtype != MOVE_RUN ? 8 + 4 + 4 : 8 + 4 + 1 + 4); - self << GetPackGUID(); - self << (uint32)0; // Movement counter. Unimplemented at the moment! NUM_PMOVE_EVTS = 0x39Z. - if (mtype == MOVE_RUN) - self << uint8(1); // unknown byte added in 2.1.0 - self << float(GetSpeed(mtype)); - playerMover->SendDirectMessage(&self); - - // Send notification to other players. sent to every clients (if in range) except one: the client of the player concerned by the change. - WorldPacket data; - data.Initialize(moveTypeToOpcode[mtype][2], 8 + 30 + 4); - data << GetPackGUID(); - BuildMovementPacket(&data); - data << float(GetSpeed(mtype)); - playerMover->SendMessageToSet(&data, false); - } - else // unit controlled by AI. - { - // send notification to every clients. - WorldPacket data; - data.Initialize(moveTypeToOpcode[mtype][0], 8 + 4); - data << GetPackGUID(); - data << float(GetSpeed(mtype)); - SendMessageToSet(&data, false); - } +void Unit::SetSpeedRateReal(UnitMoveType mtype, float rate) +{ + if (!IsInCombat() && ToPlayer()) + if (Pet* pet = ToPlayer()->GetPet()) + pet->SetSpeedRate(mtype, rate); + + m_speed_rate[mtype] = rate; + PropagateSpeedChange(); } void Unit::RemoveAllFollowers() @@ -11408,6 +11378,10 @@ void Unit::SetFeared(bool apply) { if (apply) { + // block control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, false); + SetTarget(ObjectGuid::Empty); Unit* caster = nullptr; @@ -11428,17 +11402,21 @@ void Unit::SetFeared(bool apply) if (!IsPlayer() && !IsInCombat()) GetMotionMaster()->MoveTargetedHome(); } - } - // block / allow control to real player in control (eg charmer) - if (GetCharmerOrSelfPlayer()) - GetCharmerOrSelfPlayer()->SetClientControl(this, !apply); + // allow control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, true); + } } void Unit::SetConfused(bool apply) { if (apply) { + // block control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, false); + SetTarget(ObjectGuid::Empty); GetMotionMaster()->MoveConfused(); } @@ -11450,11 +11428,11 @@ void Unit::SetConfused(bool apply) if (GetVictim()) SetTarget(EnsureVictim()->GetGUID()); } - } - // block / allow control to real player in control (eg charmer) - if (GetCharmerOrSelfPlayer()) - GetCharmerOrSelfPlayer()->SetClientControl(this, !apply); + // allow control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, true); + } } bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* aurApp) @@ -12908,6 +12886,101 @@ void Unit::UpdateHeight(float newZ) GetVehicleKit()->RelocatePassengers(); } +PlayerMovementPendingChange& Unit::PeakFirstPendingMovementChange() +{ + return m_pendingMovementChanges.front(); +} + +PlayerMovementPendingChange Unit::PopPendingMovementChange() +{ + PlayerMovementPendingChange result = m_pendingMovementChanges.front(); + m_pendingMovementChanges.pop_front(); + return result; +} + +void Unit::PushPendingMovementChange(PlayerMovementPendingChange newChange) +{ + m_pendingMovementChanges.emplace_back(std::move(newChange)); +} + +bool Unit::HasPendingMovementChange(MovementChangeType changeType) const +{ + return std::find_if(m_pendingMovementChanges.begin(), m_pendingMovementChanges.end(), + [changeType](PlayerMovementPendingChange const& pendingChange) + { + return pendingChange.movementChangeType == changeType; + }) != m_pendingMovementChanges.end(); +} + +void Unit::CheckPendingMovementAcks() +{ + if (sWorld->getIntConfig(CONFIG_PENDING_MOVE_CHANGES_TIMEOUT) == 0) + return; + + if (!HasPendingMovementChange()) + return; + + PlayerMovementPendingChange const& oldestChangeToAck = m_pendingMovementChanges.front(); + if (GameTime::GetGameTimeMS() > oldestChangeToAck.time + sWorld->getIntConfig(CONFIG_PENDING_MOVE_CHANGES_TIMEOUT)) + { + /* + when players are teleported from one corner of a map to an other (example: from Dragonblight to the entrance of Naxxramas, both in the same map: Northend), + is it done through what is called a 'near' teleport. A near teleport always involve teleporting a player from one point to an other in the same map, even if + the distance is huge. When that distance is big enough, a loading screen appears on the client side. During that time, the client loads the surrounding zone + of the new location (and everything it contains). The problem is that, as long as the client hasn't finished loading the new zone, it will NOT ack the near + teleport. So if the server sends a near teleport order at a certain time and the client takes 20s to load the new zone (let's imagine a very slow computer), + even with zero latency, the server will receive an ack from the client only after 20s. + + For this reason and because the current implementation is simple (you dear reader, feel free to improve it if you can), we will just ignore checking for + near teleport acks (for now. @todo). + */ + if (oldestChangeToAck.movementChangeType == MovementChangeType::TELEPORT) + return; + + GameClient* controller = GetGameClientMovingMe(); + controller->GetWorldSession()->KickPlayer("Took too long to ack a movement change"); + TC_LOG_INFO("cheat", "Unit::CheckPendingMovementAcks: Player GUID: %s took too long to acknowledge a movement change. He was therefore kicked.", controller->GetBasePlayer()->GetGUID().ToString().c_str()); + } +} + +void Unit::PurgeAndApplyPendingMovementChanges(bool informObservers /* = true */) +{ + for (auto pendingChange = m_pendingMovementChanges.cbegin(); pendingChange != m_pendingMovementChanges.cend(); ++pendingChange) + { + float speedFlat = pendingChange->newValue; + MovementChangeType changeType = pendingChange->movementChangeType; + UnitMoveType moveType; + switch (changeType) + { + case MovementChangeType::SPEED_CHANGE_WALK: moveType = MOVE_WALK; break; + case MovementChangeType::SPEED_CHANGE_RUN: moveType = MOVE_RUN; break; + case MovementChangeType::SPEED_CHANGE_RUN_BACK: moveType = MOVE_RUN_BACK; break; + case MovementChangeType::SPEED_CHANGE_SWIM: moveType = MOVE_SWIM; break; + case MovementChangeType::SPEED_CHANGE_SWIM_BACK: moveType = MOVE_SWIM_BACK; break; + case MovementChangeType::RATE_CHANGE_TURN: moveType = MOVE_TURN_RATE; break; + case MovementChangeType::SPEED_CHANGE_FLIGHT_SPEED: moveType = MOVE_FLIGHT; break; + case MovementChangeType::SPEED_CHANGE_FLIGHT_BACK_SPEED: moveType = MOVE_FLIGHT_BACK; break; + case MovementChangeType::RATE_CHANGE_PITCH: moveType = MOVE_PITCH_RATE; break; + default: + ASSERT(false); + return; + } + + float newSpeedRate = speedFlat / (IsControlledByPlayer() ? playerBaseMoveSpeed[moveType] : baseMoveSpeed[moveType]); + SetSpeedRateReal(moveType, newSpeedRate); + + if (informObservers) + MovementPacketSender::SendSpeedChangeToObservers(this, moveType, speedFlat); + } + + m_pendingMovementChanges.clear(); +} + +PlayerMovementPendingChange::PlayerMovementPendingChange() +{ + time = GameTime::GetGameTimeMS(); +} + void Unit::RewardRage(uint32 damage, uint32 weaponSpeedHitFactor, bool attacker) { float addRage; diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 87c2e6d1e87..16a8bea871d 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -28,6 +28,7 @@ #include <map> #include <memory> #include <stack> +#include <queue> #define VISUAL_WAYPOINT 1 // Creature Entry ID used for waypoints show, visible only for GMs #define WORLD_TRIGGER 12999 @@ -260,23 +261,54 @@ enum UnitState : uint32 UNIT_STATE_ALL_STATE = 0xffffffff }; -enum UnitMoveType +TC_GAME_API extern float baseMoveSpeed[MAX_MOVE_TYPE]; +TC_GAME_API extern float playerBaseMoveSpeed[MAX_MOVE_TYPE]; + +enum class MovementChangeType : uint8 { - MOVE_WALK = 0, - MOVE_RUN = 1, - MOVE_RUN_BACK = 2, - MOVE_SWIM = 3, - MOVE_SWIM_BACK = 4, - MOVE_TURN_RATE = 5, - MOVE_FLIGHT = 6, - MOVE_FLIGHT_BACK = 7, - MOVE_PITCH_RATE = 8 + INVALID, + + ROOT, + WATER_WALK, + SET_HOVER, + SET_CAN_FLY, + SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY, + FEATHER_FALL, + GRAVITY_DISABLE, + + SPEED_CHANGE_WALK, + SPEED_CHANGE_RUN, + SPEED_CHANGE_RUN_BACK, + SPEED_CHANGE_SWIM, + SPEED_CHANGE_SWIM_BACK, + RATE_CHANGE_TURN, + SPEED_CHANGE_FLIGHT_SPEED, + SPEED_CHANGE_FLIGHT_BACK_SPEED, + RATE_CHANGE_PITCH, + + SET_COLLISION_HGT, + TELEPORT, + KNOCK_BACK }; -#define MAX_MOVE_TYPE 9 +struct PlayerMovementPendingChange +{ + PlayerMovementPendingChange(); + + uint32 movementCounter = 0; + MovementChangeType movementChangeType = MovementChangeType::INVALID; + uint32 time; -TC_GAME_API extern float baseMoveSpeed[MAX_MOVE_TYPE]; -TC_GAME_API extern float playerBaseMoveSpeed[MAX_MOVE_TYPE]; + float newValue = 0.0f; // used if speed or height change + bool apply = false; // used if movement flag change + struct KnockbackInfo + { + float vcos = 0.0f; + float vsin = 0.0f; + float speedXY = 0.0f; + float speedZ = 0.0f; + } knockbackInfo; // used if knockback +}; enum CombatRating { @@ -734,6 +766,7 @@ struct PositionUpdateInfo class TC_GAME_API Unit : public WorldObject { + friend class WorldSession; public: typedef std::set<Unit*> AttackerSet; typedef std::set<Unit*> ControlList; @@ -1546,7 +1579,10 @@ class TC_GAME_API Unit : public WorldObject float GetSpeedRate(UnitMoveType mtype) const { return m_speed_rate[mtype]; } void SetSpeed(UnitMoveType mtype, float newValue); void SetSpeedRate(UnitMoveType mtype, float rate); + private: + void SetSpeedRateReal(UnitMoveType mtype, float rate); + public: float CalculateSpellpowerCoefficientLevelPenalty(SpellInfo const* spellInfo) const; void FollowerAdded(AbstractFollower* f) { m_followingMe.insert(f); } @@ -1654,6 +1690,14 @@ class TC_GAME_API Unit : public WorldObject return HasUnitMovementFlag(MOVEMENTFLAG_HOVER) ? GetFloatValue(UNIT_FIELD_HOVERHEIGHT) : 0.0f; } + uint32 GetMovementCounterAndInc() { return m_movementCounter++; } + PlayerMovementPendingChange& PeakFirstPendingMovementChange(); + PlayerMovementPendingChange PopPendingMovementChange(); + void PushPendingMovementChange(PlayerMovementPendingChange newChange); + bool HasPendingMovementChange() const { return !m_pendingMovementChanges.empty(); } + bool HasPendingMovementChange(MovementChangeType changeType) const; + void PurgeAndApplyPendingMovementChanges(bool informObservers = true); + void RewardRage(uint32 damage, uint32 weaponSpeedHitFactor, bool attacker); virtual float GetFollowAngle() const { return static_cast<float>(M_PI/2); } @@ -1680,8 +1724,6 @@ class TC_GAME_API Unit : public WorldObject // Movement info Movement::MoveSpline * movespline; - uint8 m_forced_speed_changes[MAX_MOVE_TYPE]; - int32 GetHighestExclusiveSameEffectSpellGroupValue(AuraEffect const* aurEff, AuraType auraType, bool checkMiscValue = false, int32 miscValue = 0) const; bool IsHighestExclusiveAura(Aura const* aura, bool removeOtherAuraApplications = false); bool IsHighestExclusiveAuraEffect(SpellInfo const* spellInfo, AuraType auraType, int32 effectAmount, uint8 auraEffectMask, bool removeOtherAuraApplications = false); @@ -1797,6 +1839,7 @@ class TC_GAME_API Unit : public WorldObject void UpdateSplineMovement(uint32 t_diff); void UpdateSplinePosition(); void InterruptMovementBasedAuras(); + void CheckPendingMovementAcks(); // player or player's pet float GetCombatRatingReduction(CombatRating cr) const; @@ -1851,6 +1894,14 @@ class TC_GAME_API Unit : public WorldObject PositionUpdateInfo _positionUpdateInfo; bool _isIgnoringCombat; + + /* Player Movement fields START*/ + + // when a player controls this unit, and when change is made to this unit which requires an ack from the client to be acted (change of speed for example), this movementCounter is incremented + uint32 m_movementCounter; + std::deque<PlayerMovementPendingChange> m_pendingMovementChanges; + + /* Player Movement fields END*/ }; namespace Trinity diff --git a/src/server/game/Entities/Unit/UnitDefines.h b/src/server/game/Entities/Unit/UnitDefines.h index 57a2b91cf80..6cdcdd1b437 100644 --- a/src/server/game/Entities/Unit/UnitDefines.h +++ b/src/server/game/Entities/Unit/UnitDefines.h @@ -237,7 +237,7 @@ enum MovementFlags : uint32 MOVEMENTFLAG_PENDING_STRAFE_RIGHT = 0x00080000, MOVEMENTFLAG_PENDING_ROOT = 0x00100000, MOVEMENTFLAG_SWIMMING = 0x00200000, // appears with fly flag also - MOVEMENTFLAG_ASCENDING = 0x00400000, // press "space" when flying + MOVEMENTFLAG_ASCENDING = 0x00400000, // press "space" when flying or swimming MOVEMENTFLAG_DESCENDING = 0x00800000, MOVEMENTFLAG_CAN_FLY = 0x01000000, // Appears when unit can fly AND also walk MOVEMENTFLAG_FLYING = 0x02000000, // unit is actually flying. pretty sure this is only used for players. creatures use disable_gravity @@ -269,25 +269,40 @@ enum MovementFlags : uint32 enum MovementFlags2 : uint32 { - MOVEMENTFLAG2_NONE = 0x00000000, - MOVEMENTFLAG2_NO_STRAFE = 0x00000001, - MOVEMENTFLAG2_NO_JUMPING = 0x00000002, - MOVEMENTFLAG2_UNK3 = 0x00000004, // Overrides various clientside checks - MOVEMENTFLAG2_FULL_SPEED_TURNING = 0x00000008, - MOVEMENTFLAG2_FULL_SPEED_PITCHING = 0x00000010, - MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING = 0x00000020, - MOVEMENTFLAG2_UNK7 = 0x00000040, - MOVEMENTFLAG2_UNK8 = 0x00000080, - MOVEMENTFLAG2_UNK9 = 0x00000100, - MOVEMENTFLAG2_UNK10 = 0x00000200, - MOVEMENTFLAG2_INTERPOLATED_MOVEMENT = 0x00000400, - MOVEMENTFLAG2_INTERPOLATED_TURNING = 0x00000800, - MOVEMENTFLAG2_INTERPOLATED_PITCHING = 0x00001000, - MOVEMENTFLAG2_UNK14 = 0x00002000, - MOVEMENTFLAG2_UNK15 = 0x00004000, - MOVEMENTFLAG2_UNK16 = 0x00008000 + MOVEMENTFLAG2_NONE = 0x00000000, + MOVEMENTFLAG2_NO_STRAFE = 0x00000001, + MOVEMENTFLAG2_NO_JUMPING = 0x00000002, + MOVEMENTFLAG2_UNK3 = 0x00000004, // Overrides various clientside checks + MOVEMENTFLAG2_FULL_SPEED_TURNING = 0x00000008, + MOVEMENTFLAG2_FULL_SPEED_PITCHING = 0x00000010, + MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING = 0x00000020, + MOVEMENTFLAG2_UNK7 = 0x00000040, + MOVEMENTFLAG2_UNK8 = 0x00000080, + MOVEMENTFLAG2_UNK9 = 0x00000100, + MOVEMENTFLAG2_UNK10 = 0x00000200, + MOVEMENTFLAG2_INTERPOLATED_MOVEMENT = 0x00000400, + MOVEMENTFLAG2_INTERPOLATED_TURNING = 0x00000800, + MOVEMENTFLAG2_INTERPOLATED_PITCHING = 0x00001000, + MOVEMENTFLAG2_UNK14 = 0x00002000, + MOVEMENTFLAG2_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY = 0x00004000, + MOVEMENTFLAG2_UNK16 = 0x00008000 }; +enum UnitMoveType +{ + MOVE_WALK = 0, + MOVE_RUN = 1, + MOVE_RUN_BACK = 2, + MOVE_SWIM = 3, + MOVE_SWIM_BACK = 4, + MOVE_TURN_RATE = 5, + MOVE_FLIGHT = 6, + MOVE_FLIGHT_BACK = 7, + MOVE_PITCH_RATE = 8 +}; + +#define MAX_MOVE_TYPE 9 + enum HitInfo { HITINFO_NORMALSWING = 0x00000000, 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; diff --git a/src/server/game/Movement/MovementPacketSender.cpp b/src/server/game/Movement/MovementPacketSender.cpp new file mode 100644 index 00000000000..df0ff5e7bbf --- /dev/null +++ b/src/server/game/Movement/MovementPacketSender.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2008-2017 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "GameClient.h" +#include "MovementPacketSender.h" +#include "Player.h" +#include "WorldPacket.h" +#include "WorldSession.h" + +// SMSG_SPLINE_SET_* messages are for units controlled by the server. +// SMSG_FORCE_*_SPEED_CHANGE messages are for units controlled by a player and require the client to respond with an ACK message. +// MSG_MOVE_SET_*_SPEED is used to broadcast changes to observers of units controlled by a player +Opcodes const MovementPacketSender::moveTypeToOpcode[MAX_MOVE_TYPE][3] = +{ + { SMSG_SPLINE_SET_WALK_SPEED, SMSG_FORCE_WALK_SPEED_CHANGE, MSG_MOVE_SET_WALK_SPEED }, + { SMSG_SPLINE_SET_RUN_SPEED, SMSG_FORCE_RUN_SPEED_CHANGE, MSG_MOVE_SET_RUN_SPEED }, + { SMSG_SPLINE_SET_RUN_BACK_SPEED, SMSG_FORCE_RUN_BACK_SPEED_CHANGE, MSG_MOVE_SET_RUN_BACK_SPEED }, + { SMSG_SPLINE_SET_SWIM_SPEED, SMSG_FORCE_SWIM_SPEED_CHANGE, MSG_MOVE_SET_SWIM_SPEED }, + { SMSG_SPLINE_SET_SWIM_BACK_SPEED, SMSG_FORCE_SWIM_BACK_SPEED_CHANGE, MSG_MOVE_SET_SWIM_BACK_SPEED }, + { SMSG_SPLINE_SET_TURN_RATE, SMSG_FORCE_TURN_RATE_CHANGE, MSG_MOVE_SET_TURN_RATE }, + { SMSG_SPLINE_SET_FLIGHT_SPEED, SMSG_FORCE_FLIGHT_SPEED_CHANGE, MSG_MOVE_SET_FLIGHT_SPEED }, + { SMSG_SPLINE_SET_FLIGHT_BACK_SPEED, SMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE, MSG_MOVE_SET_FLIGHT_BACK_SPEED }, + { SMSG_SPLINE_SET_PITCH_RATE, SMSG_FORCE_PITCH_RATE_CHANGE, MSG_MOVE_SET_PITCH_RATE }, +}; + +MovementChangeType MovementPacketSender::GetChangeTypeByMoveType(UnitMoveType moveType) +{ + switch (moveType) + { + case MOVE_WALK: return MovementChangeType::SPEED_CHANGE_WALK; + case MOVE_RUN: return MovementChangeType::SPEED_CHANGE_RUN; + case MOVE_RUN_BACK: return MovementChangeType::SPEED_CHANGE_RUN_BACK; + case MOVE_SWIM: return MovementChangeType::SPEED_CHANGE_SWIM; + case MOVE_SWIM_BACK: return MovementChangeType::SPEED_CHANGE_SWIM_BACK; + case MOVE_TURN_RATE: return MovementChangeType::RATE_CHANGE_TURN; + case MOVE_FLIGHT: return MovementChangeType::SPEED_CHANGE_FLIGHT_SPEED; + case MOVE_FLIGHT_BACK: return MovementChangeType::SPEED_CHANGE_FLIGHT_BACK_SPEED; + case MOVE_PITCH_RATE: return MovementChangeType::RATE_CHANGE_PITCH; + default: + ASSERT(false, "MovementPacketSender::SendSpeedChangeToMover Unsupported UnitMoveType"); + } +} + +void MovementPacketSender::SendSpeedChangeToMover(Unit* unit, UnitMoveType mtype, float newSpeedFlat) +{ + GameClient* controller = unit->GetGameClientMovingMe(); + + ASSERT(controller); + + uint32 mCounter = unit->GetMovementCounterAndInc(); + PlayerMovementPendingChange pendingChange; + pendingChange.movementCounter = mCounter; + pendingChange.newValue = newSpeedFlat; + pendingChange.movementChangeType = MovementPacketSender::GetChangeTypeByMoveType(mtype); + unit->PushPendingMovementChange(pendingChange); + + WorldPacket data; + data.Initialize(moveTypeToOpcode[mtype][1], mtype != MOVE_RUN ? 8 + 4 + 4 : 8 + 4 + 1 + 4); + data << unit->GetPackGUID(); + data << mCounter; + if (mtype == MOVE_RUN) + data << uint8(1); // unknown byte added in 2.1.0 + data << newSpeedFlat; + controller->SendDirectMessage(&data); +} + +void MovementPacketSender::SendSpeedChangeToObservers(Unit* unit, UnitMoveType mtype, float newSpeedFlat) +{ + GameClient* controller = unit->GetGameClientMovingMe(); + + ASSERT(controller); + + WorldPacket data; + data.Initialize(moveTypeToOpcode[mtype][2], 8 + 30 + 4); + WorldSession::WriteMovementInfo(&data, &unit->m_movementInfo); + data << newSpeedFlat; + unit->SendMessageToSet(&data, controller->GetBasePlayer()); +} + +void MovementPacketSender::SendSpeedChangeToAll(Unit* unit, UnitMoveType mtype, float newSpeedFlat) +{ + GameClient* controller = unit->GetGameClientMovingMe(); + + ASSERT(!controller); + + WorldPacket data; + data.Initialize(moveTypeToOpcode[mtype][0], 8 + 4); + data << unit->GetPackGUID(); + data << newSpeedFlat; + unit->SendMessageToSet(&data, true); +} + diff --git a/src/server/game/Movement/MovementPacketSender.h b/src/server/game/Movement/MovementPacketSender.h new file mode 100644 index 00000000000..7afd46494e6 --- /dev/null +++ b/src/server/game/Movement/MovementPacketSender.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2008-2017 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MOVEMENT_SENDER_H +#define MOVEMENT_SENDER_H + +#include "UnitDefines.h" + +class Player; +class Unit; +struct MovementInfo; +enum class MovementChangeType : uint8; + +//! +//! movement flag changes for player controlled units: +//! >>> APPLY +//! { +//! // Step1 Sent by the server to the mover's client // Step2 Sent back by the mover's client to the server // Step3 Sent to observers (all of these should be renamed to SMSG! Confirmed by sniff analysis) +//! { SMSG_FORCE_MOVE_ROOT, CMSG_FORCE_MOVE_ROOT_ACK, MSG_MOVE_ROOT }, +//! { SMSG_MOVE_WATER_WALK, CMSG_MOVE_WATER_WALK_ACK, MSG_MOVE_WATER_WALK }, +//! { SMSG_MOVE_SET_HOVER, CMSG_MOVE_HOVER_ACK, MSG_MOVE_HOVER }, +//! { SMSG_MOVE_SET_CAN_FLY, CMSG_MOVE_SET_CAN_FLY_ACK, MSG_MOVE_UPDATE_CAN_FLY }, +//! { SMSG_MOVE_SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY, CMSG_MOVE_SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY_ACK, MSG_MOVE_UPDATE_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY }, +//! { SMSG_MOVE_FEATHER_FALL, CMSG_MOVE_FEATHER_FALL_ACK, MSG_MOVE_FEATHER_FALL }, +//! { SMSG_MOVE_GRAVITY_DISABLE, CMSG_MOVE_GRAVITY_DISABLE_ACK, MSG_MOVE_GRAVITY_CHNG } +//! }, +//! >>> UNAPPLY +//! { +//! { SMSG_FORCE_MOVE_UNROOT, CMSG_FORCE_MOVE_UNROOT_ACK, MSG_MOVE_UNROOT }, +//! { SMSG_MOVE_LAND_WALK, CMSG_MOVE_WATER_WALK_ACK, MSG_MOVE_WATER_WALK }, +//! { SMSG_MOVE_UNSET_HOVER, CMSG_MOVE_HOVER_ACK, MSG_MOVE_HOVER }, +//! { SMSG_MOVE_UNSET_CAN_FLY, CMSG_MOVE_SET_CAN_FLY_ACK, MSG_MOVE_UPDATE_CAN_FLY }, +//! { SMSG_MOVE_UNSET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY, CMSG_MOVE_SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY_ACK, MSG_MOVE_UPDATE_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY }, +//! { SMSG_MOVE_NORMAL_FALL, CMSG_MOVE_FEATHER_FALL_ACK, MSG_MOVE_FEATHER_FALL }, +//! { SMSG_MOVE_GRAVITY_ENABLE, CMSG_MOVE_GRAVITY_ENABLE_ACK, MSG_MOVE_GRAVITY_CHNG } +//! } +//! +//! ---------------------- +//! Speed changes on player controlled units: +//! // Step1 Sent by the server to the mover's client // Step2 Sent back by the mover's client to the server // Step3 Sent to observers +//! { SMSG_FORCE_WALK_SPEED_CHANGE, CMSG_FORCE_WALK_SPEED_CHANGE_ACK, MSG_MOVE_SET_WALK_SPEED }, +//! { SMSG_FORCE_RUN_SPEED_CHANGE, CMSG_FORCE_RUN_SPEED_CHANGE_ACK, MSG_MOVE_SET_RUN_SPEED }, +//! { SMSG_FORCE_RUN_BACK_SPEED_CHANGE, CMSG_FORCE_RUN_BACK_SPEED_CHANGE_ACK, MSG_MOVE_SET_RUN_BACK_SPEED }, +//! { SMSG_FORCE_SWIM_SPEED_CHANGE, CMSG_FORCE_SWIM_SPEED_CHANGE_ACK, MSG_MOVE_SET_SWIM_SPEED }, +//! { SMSG_FORCE_SWIM_BACK_SPEED_CHANGE, CMSG_FORCE_SWIM_BACK_SPEED_CHANGE_ACK, MSG_MOVE_SET_SWIM_BACK_SPEED }, +//! { SMSG_FORCE_TURN_RATE_CHANGE, CMSG_FORCE_TURN_RATE_CHANGE_ACK, MSG_MOVE_SET_TURN_RATE }, +//! { SMSG_FORCE_FLIGHT_SPEED_CHANGE, CMSG_FORCE_FLIGHT_SPEED_CHANGE_ACK, MSG_MOVE_SET_FLIGHT_SPEED }, +//! { SMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE, CMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE_ACK, MSG_MOVE_SET_FLIGHT_BACK_SPEED }, +//! { SMSG_FORCE_PITCH_RATE_CHANGE, CMSG_FORCE_PITCH_RATE_CHANGE_ACK, MSG_MOVE_SET_PITCH_RATE }, +//! +//! ---------------------- +//! Other type of changes +//! // Step1 Sent by the server to the mover's client // Step2 Sent back by the mover's client to the server // Step3 Sent to observers +//! { SMSG_MOVE_SET_COLLISION_HGT, CMSG_MOVE_SET_COLLISION_HGT_ACK, MSG_MOVE_SET_COLLISION_HGT }, +//! { MSG_MOVE_TELEPORT_ACK, MSG_MOVE_TELEPORT_ACK, MSG_MOVE_TELEPORT }, +//! { SMSG_MOVE_KNOCK_BACK, CMSG_MOVE_KNOCK_BACK_ACK, MSG_MOVE_KNOCK_BACK }, +//! +//! ---------------------- +//! movement flag changes for server controlled units: (these player movement flags have no equivalent in spline: SET_CAN_FLY and SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY) +//! apply +//! { +//! SMSG_SPLINE_MOVE_GRAVITY_DISABLE, +//! SMSG_SPLINE_MOVE_ROOT, +//! SMSG_SPLINE_MOVE_WATER_WALK, +//! SMSG_SPLINE_MOVE_FEATHER_FALL, +//! SMSG_SPLINE_MOVE_SET_HOVER, +//! } +//! unapply +//! { +//! SMSG_SPLINE_MOVE_GRAVITY_ENABLE, +//! SMSG_SPLINE_MOVE_UNROOT, +//! SMSG_SPLINE_MOVE_LAND_WALK, +//! SMSG_SPLINE_MOVE_NORMAL_FALL, +//! SMSG_SPLINE_MOVE_UNSET_HOVER, +//! } + +/* +xxxxxToMover() and xxxxxToObservers() methods should be only used on a unit controlled & moved by a player (as in direct client control: possess, vehicule,..). +ToMover() to send a packet to the client (asking for confirmation before acting the change) and ToObservers once the change has been acted and should be broadcasted to the other players around (the observers). + +xxxxxToAll() method should be used on a unit controlled & moved by the server (@todo note to self: does a player moved unit under the control of a temporary disorient (Scatter Shot eg) or fear fall into this category? EDIT: by looking at the effects of Psychic Scream (10890), the answer is yes) +*/ +class MovementPacketSender +{ + public: + ///* height change */ + //static void SendHeightChangeToMover(Unit* unit, float newRate); + //static void SendHeightChangeToObservers(Unit* unit, float newRate); + + ///* teleport */ + //static void SendTeleportAckPacket(Unit* unit, MovementInfo const& movementInfo); // rename to SendTeleportToMover? + //static void SendTeleportPacket(Unit* unit, MovementInfo const& movementInfo); // rename to SendTeleportToobservers? + + /* speed change */ + static void SendSpeedChangeToMover(Unit* unit, UnitMoveType mtype, float newRate); + static void SendSpeedChangeToObservers(Unit* unit, UnitMoveType mtype, float newRate); + static void SendSpeedChangeToAll(Unit* unit, UnitMoveType mtype, float newRate); + + ///* knocback */ + //static void SendKnockBackToMover(Unit* unit, float vcos, float vsin, float speedXY, float speedZ); + //static void SendKnockBackToObservers(Unit* unit, float vcos, float vsin, float speedXY, float speedZ); + + ///* movement flag change */ + //static void SendMovementFlagChangeToMover(Unit* unit, MovementFlags mFlag, bool apply); + //static void SendMovementFlagChangeToMover(Unit* unit, MovementFlags2 mFlag, bool apply); + //static void SendMovementFlagChangeToObservers(Unit* unit, MovementFlags mFlag, bool apply); + //static void SendMovementFlagChangeToObservers(Unit* unit, MovementFlags2 mFlag); + + //static void SendMovementFlagChangeToAll(Unit* unit, MovementFlags mFlag, bool apply); + + // utility method + static MovementChangeType GetChangeTypeByMoveType(UnitMoveType moveType); + + private: + static Opcodes const moveTypeToOpcode[MAX_MOVE_TYPE][3]; +}; + +#endif diff --git a/src/server/game/Server/GameClient.cpp b/src/server/game/Server/GameClient.cpp index 9dde28b3bfb..d75ee938a4f 100644 --- a/src/server/game/Server/GameClient.cpp +++ b/src/server/game/Server/GameClient.cpp @@ -28,8 +28,7 @@ GameClient::GameClient(WorldSession* sessionToServer) void GameClient::AddAllowedMover(Unit* unit) { - if (GameClient* previousController = unit->GetGameClientMovingMe()) - previousController->RemoveAllowedMover(unit); + ASSERT(!unit->GetGameClientMovingMe() || unit->GetGameClientMovingMe() == this); _allowedMovers.insert(unit->GetGUID()); unit->SetGameClientMovingMe(this); @@ -37,6 +36,7 @@ void GameClient::AddAllowedMover(Unit* unit) void GameClient::RemoveAllowedMover(Unit* unit) { + unit->PurgeAndApplyPendingMovementChanges(); _allowedMovers.erase(unit->GetGUID()); if (unit->GetGameClientMovingMe() == this) { diff --git a/src/server/game/Server/GameClient.h b/src/server/game/Server/GameClient.h index 4baf5015ddb..d44f1625718 100644 --- a/src/server/game/Server/GameClient.h +++ b/src/server/game/Server/GameClient.h @@ -38,6 +38,7 @@ class TC_GAME_API GameClient void SetActivelyMovedUnit(Unit* activelyMovedUnit) { _activelyMovedUnit = activelyMovedUnit; } Player* GetBasePlayer() const { return _sessionToServer->GetPlayer(); } + WorldSession* GetWorldSession() const { return _sessionToServer; } void SendDirectMessage(WorldPacket const* data) const; private: diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 9cf46fba730..30be1c9e4ad 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -926,6 +926,8 @@ void WorldSession::ReadMovementInfo(WorldPacket &data, MovementInfo* mi) mi->RemoveMovementFlag((maskToRemove)); #endif + Unit* mover = GetGameClient()->GetActivelyMovedUnit(); + if (!GetPlayer()->GetVehicleBase() || !(GetPlayer()->GetVehicle()->GetVehicleInfo()->Flags & VEHICLE_FLAG_FIXED_POSITION)) REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_ROOT), MOVEMENTFLAG_ROOT); @@ -937,7 +939,7 @@ void WorldSession::ReadMovementInfo(WorldPacket &data, MovementInfo* mi) MOVEMENTFLAG_MASK_MOVING); //! Cannot hover without SPELL_AURA_HOVER - REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_HOVER) && !GetPlayer()->HasAuraType(SPELL_AURA_HOVER), + REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_HOVER) && !mover->HasAuraType(SPELL_AURA_HOVER), MOVEMENTFLAG_HOVER); //! Cannot ascend and descend at the same time @@ -962,12 +964,12 @@ void WorldSession::ReadMovementInfo(WorldPacket &data, MovementInfo* mi) //! Cannot walk on water without SPELL_AURA_WATER_WALK except for ghosts REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_WATERWALKING) && - !GetPlayer()->HasAuraType(SPELL_AURA_WATER_WALK) && - !GetPlayer()->HasAuraType(SPELL_AURA_GHOST), + !mover->HasAuraType(SPELL_AURA_WATER_WALK) && + !mover->HasAuraType(SPELL_AURA_GHOST), MOVEMENTFLAG_WATERWALKING); //! Cannot feather fall without SPELL_AURA_FEATHER_FALL - REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_FALLING_SLOW) && !GetPlayer()->HasAuraType(SPELL_AURA_FEATHER_FALL), + REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_FALLING_SLOW) && !mover->HasAuraType(SPELL_AURA_FEATHER_FALL), MOVEMENTFLAG_FALLING_SLOW); /*! Cannot fly if no fly auras present. Exception is being a GM. @@ -977,8 +979,8 @@ void WorldSession::ReadMovementInfo(WorldPacket &data, MovementInfo* mi) */ REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY) && GetSecurity() == SEC_PLAYER && - !GetPlayer()->GetCharmedOrSelf()->HasAuraType(SPELL_AURA_FLY) && - !GetPlayer()->GetCharmedOrSelf()->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED), + !mover->HasAuraType(SPELL_AURA_FLY) && + !mover->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED), MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY); //! Cannot fly and fall at the same time @@ -986,7 +988,7 @@ void WorldSession::ReadMovementInfo(WorldPacket &data, MovementInfo* mi) MOVEMENTFLAG_FALLING); REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_SPLINE_ENABLED) && - (!GetPlayer()->movespline->Initialized() || GetPlayer()->movespline->Finalized()), MOVEMENTFLAG_SPLINE_ENABLED); + (!mover->movespline->Initialized() || mover->movespline->Finalized()), MOVEMENTFLAG_SPLINE_ENABLED); #undef REMOVE_VIOLATING_FLAGS } diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 582b083f2ff..93b4561d66a 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -411,7 +411,7 @@ class TC_GAME_API WorldSession void SendAddonsInfo(); void ReadMovementInfo(WorldPacket& data, MovementInfo* mi); - void WriteMovementInfo(WorldPacket* data, MovementInfo* mi); + void static WriteMovementInfo(WorldPacket* data, MovementInfo* mi); void SendPacket(WorldPacket const* packet); void SendNotification(const char *format, ...) ATTR_PRINTF(2, 3); diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 5e07e6be032..f96eb28ef4e 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1539,6 +1539,9 @@ void World::LoadConfigSettings(bool reload) // Whether to use LoS from game objects m_bool_configs[CONFIG_CHECK_GOBJECT_LOS] = sConfigMgr->GetBoolDefault("CheckGameObjectLoS", true); + // Anti movement cheat measure. Time each client have to acknowledge a movement change until they are kicked + m_int_configs[CONFIG_PENDING_MOVE_CHANGES_TIMEOUT] = sConfigMgr->GetIntDefault("AntiCheat.PendingMoveChangesTimeoutTime", 0); + // call ScriptMgr if we're reloading the configuration if (reload) sScriptMgr->OnConfigLoad(reload); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index a8c9bfba39d..49171f94ecd 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -396,6 +396,7 @@ enum WorldIntConfigs CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT, CONFIG_RESPAWN_GUIDWARNING_FREQUENCY, CONFIG_SOCKET_TIMEOUTTIME_ACTIVE, + CONFIG_PENDING_MOVE_CHANGES_TIMEOUT, INT_CONFIG_VALUE_COUNT }; diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 0a796644764..d9f145a76ce 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -36,6 +36,7 @@ # AUCTION HOUSE BOT BUYER CONFIG # LOGGING SYSTEM SETTINGS # PACKET SPOOF PROTECTION SETTINGS +# MISC ANTI-CHEAT SETTINGS # METRIC SETTINGS # ################################################################################################### @@ -4038,6 +4039,24 @@ PacketSpoof.BanDuration = 86400 ################################################################################################### ################################################################################################### +# MISC ANTI-CHEAT SETTINGS +# +# Settings to tweak various anti cheat measures. +# +# AntiCheats.PendingMoveChangesTimeoutTime +# Description: Time each client has to acknowledge a movement change until they are kicked. +# Note: A lower time will give cheaters less advantage when using a very specific +# type of hack. However, the cost is that innocent but laggy players might +# sometimes get kicked. Disabled if 0. +# Default: 0 - (Disabled) +# 3000 - (3 seconds, Recommended) + +AntiCheat.PendingMoveChangesTimeoutTime = 0 + +# +################################################################################################### + +################################################################################################### # METRIC SETTINGS # # These settings control the statistics sent to the metric database (currently InfluxDB) |