aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Entities/Unit
diff options
context:
space:
mode:
authorChaouki Dhib <chaodhib@gmail.com>2021-06-15 23:09:48 +0200
committerGitHub <noreply@github.com>2021-06-15 23:09:48 +0200
commitd337fb99ed81dc3522bdea17e762febd989a6956 (patch)
tree308c4402d114163a783d340c90bbe7ec42085169 /src/server/game/Entities/Unit
parent4b9465e1f91adbc139afe5f86ac84eb5f14b62b4 (diff)
Core/Movement: Implement proper player speed change (#26561)
Diffstat (limited to 'src/server/game/Entities/Unit')
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp209
-rw-r--r--src/server/game/Entities/Unit/Unit.h81
-rw-r--r--src/server/game/Entities/Unit/UnitDefines.h51
3 files changed, 240 insertions, 101 deletions
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,