From 80a845b2a37572eef9eaebecb1e122bea57211d5 Mon Sep 17 00:00:00 2001 From: Baptiste Marie Date: Tue, 30 Jul 2024 03:56:49 +0200 Subject: [PATCH] =?UTF-8?q?`PlayerPositionUpdateHandler`,=20`PlayerHeading?= =?UTF-8?q?UpdateHandler`:=20Rework=20to=20no=20longer=20broadcast=20immed?= =?UTF-8?q?iately=20*=20Prevents=20a=20potential=20abuse=20where=20a=20cli?= =?UTF-8?q?ent=20could=20send=20hundreds=20of=20forged=20update=20packets?= =?UTF-8?q?=20every=20second,=20flooding=20other=20clients.=20*=20Created?= =?UTF-8?q?=20`PlayerMovementComponent`=20for=20this=20purpose.=20The=20la?= =?UTF-8?q?st=20received=20packet=20will=20be=20broadcasted=20from=20there?= =?UTF-8?q?.=20Called=20by=20`ClientService`.=20*=20This=20may=20also=20ev?= =?UTF-8?q?entually=20give=20the=20option=20to=20reduce=20the=20broadcast?= =?UTF-8?q?=20rate=20in=20some=20areas=20if=20the=20game=20loop=20can't=20?= =?UTF-8?q?keep=20up.=20*=20Made=20`PlayerMovementComponent`=20check=20for?= =?UTF-8?q?=20"soft"=20LD=20(when=20a=20client=20stops=20sending=20positio?= =?UTF-8?q?n=20update=20packets)=20instead=20of=20the=20client=20service?= =?UTF-8?q?=20directly.=20*=20Fixes=20soft=20LD=20sometimes=20triggering?= =?UTF-8?q?=20again=20too=20soon=20after=20being=20canceled.=20*=20Fixes?= =?UTF-8?q?=20soft=20LD=20triggering=20during=20zoning,=20and=20kicking=20?= =?UTF-8?q?the=20player=20out=20if=20it=20takes=20more=20than=20~65=20seco?= =?UTF-8?q?nds.=20*=20Removed=20LD=20chat=20message=20for=20soft=20LD=20so?= =?UTF-8?q?=20as=20to=20not=20confuse=20other=20players.=20*=20Various=20c?= =?UTF-8?q?hanges=20to=20the=20way=20=E2=80=9Caction=E2=80=9D=20and=20?= =?UTF-8?q?=E2=80=9Cstate=E2=80=9D=20flags=20are=20handled.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ECS-Components/Actions/AttackAction.cs | 12 +- .../ECS-Components/Actions/NpcAttackAction.cs | 4 +- .../Actions/PlayerAttackAction.cs | 4 +- .../ECS-Components/MovementComponent.cs | 10 +- .../ECS-Components/NpcMovementComponent.cs | 6 +- .../ECS-Components/PlayerMovementComponent.cs | 85 + GameServer/ECS-Services/ClientService.cs | 19 +- GameServer/GameClient.cs | 4 +- GameServer/gameobjects/GameDoor.cs | 2 +- GameServer/gameobjects/GameGravestone.cs | 24 +- GameServer/gameobjects/GameNPC.cs | 8 +- GameServer/gameobjects/GameObject.cs | 15 +- GameServer/gameobjects/GamePlayer.cs | 78 +- .../keeps/Gameobjects/FrontiersPortalStone.cs | 25 +- GameServer/keeps/Gameobjects/GameKeepDoor.cs | 2 +- GameServer/keeps/Gameobjects/GameRelicDoor.cs | 22 +- .../Client/168/PlayerHeadingUpdateHandler.cs | 241 ++- .../Client/168/PlayerPositionUpdateHandler.cs | 1880 +++++++++-------- .../packets/Client/168/UseSpellHandler.cs | 4 +- GameServer/packets/Server/PacketLib168.cs | 2 +- 20 files changed, 1266 insertions(+), 1181 deletions(-) create mode 100644 GameServer/ECS-Components/PlayerMovementComponent.cs diff --git a/GameServer/ECS-Components/Actions/AttackAction.cs b/GameServer/ECS-Components/Actions/AttackAction.cs index 5ff4eea8c6..f7645232b0 100644 --- a/GameServer/ECS-Components/Actions/AttackAction.cs +++ b/GameServer/ECS-Components/Actions/AttackAction.cs @@ -38,14 +38,14 @@ protected AttackAction(GameLiving owner) _nextRangedTick = GameLoop.GameLoopTime; } - public static AttackAction Create(GameLiving gameLiving) + public static AttackAction Create(GameLiving living) { - if (gameLiving is GameNPC gameNpc) - return new NpcAttackAction(gameNpc); - else if (gameLiving is GamePlayer gamePlayer) - return new PlayerAttackAction(gamePlayer); + if (living is GameNPC npc) + return new NpcAttackAction(npc); + else if (living is GamePlayer player) + return new PlayerAttackAction(player); else - return new AttackAction(gameLiving); + return new AttackAction(living); } public bool Tick() diff --git a/GameServer/ECS-Components/Actions/NpcAttackAction.cs b/GameServer/ECS-Components/Actions/NpcAttackAction.cs index 08a9b31f8f..02248038db 100644 --- a/GameServer/ECS-Components/Actions/NpcAttackAction.cs +++ b/GameServer/ECS-Components/Actions/NpcAttackAction.cs @@ -21,9 +21,9 @@ public class NpcAttackAction : AttackAction private static int LosCheckInterval => Properties.CHECK_LOS_DURING_RANGED_ATTACK_MINIMUM_INTERVAL; private bool HasLosOnCurrentTarget => _losCheckTarget == _target && _hasLos; - public NpcAttackAction(GameNPC npcOwner) : base(npcOwner) + public NpcAttackAction(GameNPC owner) : base(owner) { - _npcOwner = npcOwner; + _npcOwner = owner; _isGuardArcher = _npcOwner is GuardArcher; } diff --git a/GameServer/ECS-Components/Actions/PlayerAttackAction.cs b/GameServer/ECS-Components/Actions/PlayerAttackAction.cs index d886bdf88d..d34c6a92e0 100644 --- a/GameServer/ECS-Components/Actions/PlayerAttackAction.cs +++ b/GameServer/ECS-Components/Actions/PlayerAttackAction.cs @@ -7,9 +7,9 @@ public class PlayerAttackAction : AttackAction { private GamePlayer _playerOwner; - public PlayerAttackAction(GamePlayer playerOwner) : base(playerOwner) + public PlayerAttackAction(GamePlayer owner) : base(owner) { - _playerOwner = playerOwner; + _playerOwner = owner; } public override void OnAimInterrupt(GameObject attacker) diff --git a/GameServer/ECS-Components/MovementComponent.cs b/GameServer/ECS-Components/MovementComponent.cs index 75eafcfdd5..cb6036ba8c 100644 --- a/GameServer/ECS-Components/MovementComponent.cs +++ b/GameServer/ECS-Components/MovementComponent.cs @@ -22,12 +22,14 @@ protected MovementComponent(GameLiving owner) Owner = owner; } - public static MovementComponent Create(GameLiving gameLiving) + public static MovementComponent Create(GameLiving living) { - if (gameLiving is GameNPC gameNpc) - return new NpcMovementComponent(gameNpc); + if (living is GameNPC npc) + return new NpcMovementComponent(npc); + else if (living is GamePlayer player) + return new PlayerMovementComponent(player); else - return new MovementComponent(gameLiving); + return new MovementComponent(living); } public virtual void Tick() diff --git a/GameServer/ECS-Components/NpcMovementComponent.cs b/GameServer/ECS-Components/NpcMovementComponent.cs index f316d782f7..e2df73d692 100644 --- a/GameServer/ECS-Components/NpcMovementComponent.cs +++ b/GameServer/ECS-Components/NpcMovementComponent.cs @@ -54,10 +54,10 @@ public class NpcMovementComponent : MovementComponent public bool HasActiveResetHeadingAction => _resetHeadingAction != null && _resetHeadingAction.IsAlive; public Point3D DestinationForClient { get; private set; } - public NpcMovementComponent(GameNPC npcOwner) : base(npcOwner) + public NpcMovementComponent(GameNPC owner) : base(owner) { - Owner = npcOwner; - _pathCalculator = new(npcOwner); + Owner = owner; + _pathCalculator = new(owner); _positionForUpdatePackets = Owner; } diff --git a/GameServer/ECS-Components/PlayerMovementComponent.cs b/GameServer/ECS-Components/PlayerMovementComponent.cs new file mode 100644 index 0000000000..7813a4747a --- /dev/null +++ b/GameServer/ECS-Components/PlayerMovementComponent.cs @@ -0,0 +1,85 @@ +using System.Reflection; +using DOL.GS.PacketHandler.Client.v168; +using log4net; + +namespace DOL.GS +{ + public class PlayerMovementComponent : MovementComponent + { + private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private const int BROADCAST_MINIMUM_INTERVAL = 200; // Clients send a position or heading update packet every 200ms at most (when moving or rotating). + private const int SOFT_LINK_DEATH_TRESHOLD = 5000; // How long does it take without receiving a packet for a client to enter the soft link death state. + + private long _lastPositionUpdatePacketReceivedTime; + private long _nextPositionBroadcast; + private bool _needBroadcastPosition; + + private long _lastHeadingUpdatePacketReceivedTime; + private long _nextHeadingBroadcast; + private bool _needBroadcastHeading; + + public new GamePlayer Owner { get; } + public ref long LastPositionUpdatePacketReceivedTime => ref _lastPositionUpdatePacketReceivedTime; + public ref long LastHeadingUpdatePacketReceivedTime => ref _lastHeadingUpdatePacketReceivedTime; + + public PlayerMovementComponent(GameLiving owner) : base(owner) + { + Owner = owner as GamePlayer; + } + + public override void Tick() + { + if (!Owner.IsLinkDeathTimerRunning) + { + if (ServiceUtils.ShouldTickNoEarly(_lastPositionUpdatePacketReceivedTime + SOFT_LINK_DEATH_TRESHOLD)) + { + if (log.IsInfoEnabled) + log.Info($"Position update timeout on client. Calling link death. ({Owner.Client})"); + + // The link death timer will handle the position broadcast. + Owner.Client.OnLinkDeath(true); + return; + } + + // Position and heading broadcasts are mutually exclusive. + if (_needBroadcastPosition && ServiceUtils.ShouldTickAdjust(ref _nextPositionBroadcast)) + { + BroadcastPosition(); + _nextPositionBroadcast += BROADCAST_MINIMUM_INTERVAL; + _needBroadcastPosition = false; + } + else if (_needBroadcastHeading && ServiceUtils.ShouldTickAdjust(ref _nextHeadingBroadcast)) + { + BroadcastHeading(); + _nextHeadingBroadcast += BROADCAST_MINIMUM_INTERVAL; + _needBroadcastHeading = false; + } + } + + base.Tick(); + } + + public void BroadcastPosition() + { + PlayerPositionUpdateHandler.BroadcastPosition(Owner.Client); + } + + public void BroadcastHeading() + { + PlayerHeadingUpdateHandler.BroadcastHeading(Owner.Client); + } + + public void OnPositionPacketReceivedEnd() + { + _needBroadcastPosition = true; + _lastPositionUpdatePacketReceivedTime = GameLoop.GameLoopTime; + } + + public void OnHeadingPacketReceived() + { + _needBroadcastHeading = true; + _lastHeadingUpdatePacketReceivedTime = GameLoop.GameLoopTime; + } + } +} diff --git a/GameServer/ECS-Services/ClientService.cs b/GameServer/ECS-Services/ClientService.cs index 097ae56afe..d53fba935a 100644 --- a/GameServer/ECS-Services/ClientService.cs +++ b/GameServer/ECS-Services/ClientService.cs @@ -18,7 +18,6 @@ public static class ClientService private const string SERVICE_NAME = nameof(ClientService); private const int PING_TIMEOUT = 60000; private const int HARD_TIMEOUT = 600000; - private const int POSITION_UPDATE_TIMEOUT = 5000; private const int STATIC_OBJECT_UPDATE_MIN_DISTANCE = 4000; private static List _clients = new(); @@ -71,7 +70,7 @@ private static void TickInternal(int index) CheckInGameTimeout(client); GamePlayer player = client.Player; - if (player?.ObjectState == GameObject.eObjectState.Active) + if (player?.ObjectState is GameObject.eObjectState.Active) { if (ServiceUtils.ShouldTick(player.LastWorldUpdate + Properties.WORLD_PLAYER_UPDATE_INTERVAL)) { @@ -611,19 +610,9 @@ private static void CheckHardTimeout(GameClient client) private static void CheckInGameTimeout(GameClient client) { - if (client.Player.IsLinkDeathTimerRunning) - return; - - if (ServiceUtils.ShouldTickNoEarly(client.Player.LastPositionUpdateTime + POSITION_UPDATE_TIMEOUT)) - { - if (log.IsInfoEnabled) - log.Info($"Position update timeout on client. Calling link death. ({client})"); - - client.OnLinkDeath(true); - } - else if (Properties.KICK_IDLE_PLAYER_STATUS && - ServiceUtils.ShouldTickNoEarly(client.Player.LastPlayerActivityTime + Properties.KICK_IDLE_PLAYER_TIME * 60000) && - client.Account.PrivLevel == 1) + if (Properties.KICK_IDLE_PLAYER_STATUS && + ServiceUtils.ShouldTickNoEarly(client.Player.LastPlayerActivityTime + Properties.KICK_IDLE_PLAYER_TIME * 60000) && + client.Account.PrivLevel == 1) { if (log.IsInfoEnabled) log.Info($"Kicking inactive client to char screen. ({client})"); diff --git a/GameServer/GameClient.cs b/GameServer/GameClient.cs index 19e86e0797..80f170e846 100644 --- a/GameServer/GameClient.cs +++ b/GameServer/GameClient.cs @@ -291,7 +291,7 @@ public eClientState ClientState PingTime = GameLoop.GameLoopTime; if (m_player != null) - m_player.LastPositionUpdateTime = GameLoop.GameLoopTime; + m_player.LastPositionUpdatePacketReceivedTime = GameLoop.GameLoopTime; } m_clientState = value; @@ -304,8 +304,6 @@ public eClientState ClientState /// public long LinkDeathTime { get; set; } - public GSPacketIn LastPositionUpdatePacketReceived { get; set; } - /// /// Variable is false if account/player is Ban, for a wrong password, if server is closed etc ... /// diff --git a/GameServer/gameobjects/GameDoor.cs b/GameServer/gameobjects/GameDoor.cs index 941947738e..d6c3e24987 100644 --- a/GameServer/gameobjects/GameDoor.cs +++ b/GameServer/gameobjects/GameDoor.cs @@ -61,7 +61,7 @@ public override void LoadFromDatabase(DataObject obj) CurrentRegion = curZone.ZoneRegion; m_name = dbDoor.Name; - _heading = (ushort) dbDoor.Heading; + Heading = (ushort) dbDoor.Heading; m_x = dbDoor.X; m_y = dbDoor.Y; m_z = dbDoor.Z; diff --git a/GameServer/gameobjects/GameGravestone.cs b/GameServer/gameobjects/GameGravestone.cs index d75f3585df..2ef5379840 100644 --- a/GameServer/gameobjects/GameGravestone.cs +++ b/GameServer/gameobjects/GameGravestone.cs @@ -1,26 +1,4 @@ -/* - * DAWN OF LIGHT - The first free open source DAoC server emulator - * - * 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - */ -using System; - -using DOL.Database; using DOL.Language; -using DOL.GS.PacketHandler; namespace DOL.GS { @@ -44,7 +22,7 @@ public GameGravestone(GamePlayer player, long xpValue):base() //the same startingspots when we restart! m_saveInDB = false; m_name = LanguageMgr.GetTranslation(player.Client.Account.Language, "GameGravestone.GameGravestone.Grave", player.Name); - _heading = player.Heading; + Heading = player.Heading; m_x = player.X; m_y = player.Y; m_z = player.Z; diff --git a/GameServer/gameobjects/GameNPC.cs b/GameServer/gameobjects/GameNPC.cs index 7e166baabe..c55b955d67 100644 --- a/GameServer/gameobjects/GameNPC.cs +++ b/GameServer/gameobjects/GameNPC.cs @@ -1027,7 +1027,7 @@ public override void LoadFromDatabase(DataObject obj) m_x = dbMob.X; m_y = dbMob.Y; m_z = dbMob.Z; - _heading = (ushort) (dbMob.Heading & 0xFFF); + Heading = dbMob.Heading; MaxSpeedBase = (short) dbMob.Speed; CurrentRegionID = dbMob.Region; Realm = (eRealm)dbMob.Realm; @@ -2094,7 +2094,7 @@ public virtual bool MoveInRegion(ushort regionID, int x, int y, int z, ushort he m_x = x; m_y = y; m_z = z; - _heading = heading; + Heading = heading; // Previous position. foreach (GamePlayer player in playersInRadius) @@ -3206,7 +3206,7 @@ protected virtual int RespawnTimerCallback(ECSGameTimer respawnTimer) m_x = m_spawnPoint.X; m_y = m_spawnPoint.Y; m_z = m_spawnPoint.Z; - _heading = m_spawnHeading; + Heading = m_spawnHeading; SpawnTick = GameLoop.GameLoopTime; AddToWorld(); return 0; @@ -4480,7 +4480,7 @@ public GameNPC Copy(GameNPC copyTarget) public GameNPC(ABrain defaultBrain) : base() { if (movementComponent == null) - movementComponent = (NpcMovementComponent) base.movementComponent; + movementComponent = base.movementComponent as NpcMovementComponent; Level = 1; m_health = MaxHealth; diff --git a/GameServer/gameobjects/GameObject.cs b/GameServer/gameobjects/GameObject.cs index bcb789f333..fc54381c54 100644 --- a/GameServer/gameobjects/GameObject.cs +++ b/GameServer/gameobjects/GameObject.cs @@ -76,7 +76,7 @@ public virtual eObjectState ObjectState #region Position - protected ushort _heading; + private ushort _rawHeading; public virtual string OwnerID { get; set; } public virtual eRealm Realm { get; set; } @@ -90,9 +90,10 @@ public virtual ushort CurrentRegionID public SubZoneObject SubZoneObject { get; set; } public virtual ushort Heading { - get => _heading; - set => _heading = (ushort) (value & 0xFFF); + get => (ushort) (_rawHeading & 0xFFF); + set => _rawHeading = value; } + public ushort RawHeading => _rawHeading; // Includes extra bits that clients send. /// /// Returns the angle towards a target spot in degrees, clockwise @@ -102,12 +103,12 @@ public virtual ushort Heading /// the angle towards the spot public float GetAngle( IPoint2D point ) { - float headingDifference = ( GetHeading( point ) & 0xFFF ) - ( this.Heading & 0xFFF ); + float headingDifference = GetHeading(point) - Heading; if (headingDifference < 0) headingDifference += 4096.0f; - return (headingDifference * 360.0f / 4096.0f); + return headingDifference * 360.0f / 4096.0f; } /// @@ -669,7 +670,7 @@ public virtual bool Create(ushort regionID, int x, int y, int z, ushort heading) m_x = x; m_y = y; m_z = z; - _heading = heading; + Heading = heading; return AddToWorld(); } @@ -776,7 +777,7 @@ public virtual bool MoveTo(ushort regionID, int x, int y, int z, ushort heading) m_x = x; m_y = y; m_z = z; - _heading = heading; + Heading = heading; CurrentRegionID = regionID; return AddToWorld(); } diff --git a/GameServer/gameobjects/GamePlayer.cs b/GameServer/gameobjects/GamePlayer.cs index 81a45a8a82..aa18455b1e 100644 --- a/GameServer/gameobjects/GamePlayer.cs +++ b/GameServer/gameobjects/GamePlayer.cs @@ -36,15 +36,17 @@ namespace DOL.GS /// public class GamePlayer : GameLiving { + private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private const int SECONDS_TO_QUIT_ON_LINKDEATH = 60; - private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private readonly object m_LockObject = new(); + public new PlayerMovementComponent movementComponent; public override eGameObjectType GameObjectType => eGameObjectType.PLAYER; - private readonly object m_LockObject = new(); + public ChainedActions ChainedActions { get; } public double SpecLock { get; set; } public long LastWorldUpdate { get; set; } - public ChainedActions ChainedActions { get; } public ECSGameTimer PredatorTimeoutTimer { @@ -862,17 +864,40 @@ public virtual int QuitTime public bool IsLinkDeathTimerRunning => _linkDeathTimer?.IsAlive == true; - public bool OnUpdatePosition() + public long LastPositionUpdatePacketReceivedTime + { + get => movementComponent.LastPositionUpdatePacketReceivedTime; + set => movementComponent.LastPositionUpdatePacketReceivedTime = value; + } + + public long LastHeadingUpdatePacketReceivedTime + { + get => movementComponent.LastHeadingUpdatePacketReceivedTime; + set => movementComponent.LastHeadingUpdatePacketReceivedTime = value; + } + + public bool OnPositionPacketReceivedStart() { if (_linkDeathTimer == null) return true; + LastPositionUpdatePacketReceivedTime = GameLoop.GameLoopTime; _linkDeathTimer.Stop(); MoveTo(_linkDeathTimer.LocationAtLinkDeath); _linkDeathTimer = null; return false; } + public void OnPositionPacketReceivedEnd() + { + movementComponent.OnPositionPacketReceivedEnd(); + } + + public void OnHeadingPacketReceived() + { + movementComponent.OnHeadingPacketReceived(); + } + public void OnLinkDeath() { CurrentSpeed = 0; // Stop player if he's running. @@ -893,11 +918,14 @@ public void OnLinkDeath() if (TradeWindow != null) TradeWindow.CloseTrade(); - // Notify players in close proximity. - foreach (GamePlayer playerInRadius in GetPlayersInRadius(WorldMgr.INFO_DISTANCE)) + // Notify players in close proximity (hard LD only). + if (Client.ClientState is GameClient.eClientState.Linkdead) { - if (playerInRadius != this && GameServer.ServerRules.IsAllowedToUnderstand(this, playerInRadius)) - playerInRadius.Out.SendMessage(LanguageMgr.GetTranslation(playerInRadius.Client.Account.Language, "GamePlayer.OnLinkdeath.Linkdead", Name), eChatType.CT_Important, eChatLoc.CL_SystemWindow); + foreach (GamePlayer playerInRadius in GetPlayersInRadius(WorldMgr.INFO_DISTANCE)) + { + if (playerInRadius != this && GameServer.ServerRules.IsAllowedToUnderstand(this, playerInRadius)) + playerInRadius.Out.SendMessage(LanguageMgr.GetTranslation(playerInRadius.Client.Account.Language, "GamePlayer.OnLinkdeath.Linkdead", Name), eChatType.CT_Important, eChatLoc.CL_SystemWindow); + } } // Notify other group members. @@ -1145,7 +1173,7 @@ public LinkDeathTimer(GameObject owner) : base(owner) protected override int OnTick(ECSGameTimer timer) { if (_playerOwner.ObjectState is eObjectState.Active) - PlayerPositionUpdateHandler.BroadcastLastReceivedPacket(_playerOwner.Client); + _playerOwner.movementComponent.BroadcastPosition(); if (!ServiceUtils.ShouldTick(_playerOwner.Client.LinkDeathTime + SECONDS_TO_QUIT_ON_LINKDEATH * 1000)) return Interval; @@ -8887,7 +8915,7 @@ public override bool AddToWorld() else RandomNumberDeck = new PlayerDeck(); - LastPositionUpdateTime = GameLoop.GameLoopTime; + LastPositionUpdatePacketReceivedTime = GameLoop.GameLoopTime; LastPlayerActivityTime = GameLoop.GameLoopTime; return true; } @@ -9363,6 +9391,10 @@ public override short CurrentSpeed } } + public short FallSpeed { get; set; } + public PlayerPositionUpdateHandler.StateFlags StateFlags { get; set; } + public PlayerPositionUpdateHandler.ActionFlags ActionFlags { get; set; } + /// /// Gets or sets the region of this player /// @@ -9376,7 +9408,6 @@ public override Region CurrentRegion } public Zone LastPositionUpdateZone { get; set; } - public long LastPositionUpdateTime { get; set; } public long LastPlayerActivityTime { get; set; } public Point3DFloat LastPositionUpdatePoint { get; set; } = new(0, 0, 0); @@ -9536,7 +9567,8 @@ public bool IsDiving set { // Force the diving state instead of trusting the client. - value = IsUnderwater; + if (!value) + value = IsUnderwater; if (value && !CurrentZone.IsDivingEnabled && Client.Account.PrivLevel == 1) { @@ -9733,18 +9765,7 @@ public virtual bool Sprint(bool state) } } - protected bool m_strafing; - public override bool IsStrafing - { - get => m_strafing; - set - { - m_strafing = value; - - if (value) - OnPlayerMove(); - } - } + public override bool IsStrafing => (StateFlags & PlayerPositionUpdateHandler.StateFlags.STRAFING_ANY) != 0; public virtual void OnPlayerMove() { @@ -11529,7 +11550,7 @@ public override void LoadFromDatabase(DataObject obj) m_x = DBCharacter.Xpos; m_y = DBCharacter.Ypos; m_z = DBCharacter.Zpos; - _heading = (ushort)DBCharacter.Direction; + Heading = (ushort)DBCharacter.Direction; //important, use CurrentRegion property //instead because it sets the Region too CurrentRegionID = (ushort)DBCharacter.Region; @@ -11539,7 +11560,7 @@ public override void LoadFromDatabase(DataObject obj) m_x = DBCharacter.BindXpos; m_y = DBCharacter.BindYpos; m_z = DBCharacter.BindZpos; - _heading = (ushort)DBCharacter.BindHeading; + Heading = (ushort)DBCharacter.BindHeading; CurrentRegionID = (ushort)DBCharacter.BindRegion; } @@ -12523,6 +12544,8 @@ public bool RemoveEncounterCredit(Type questType) /// true if added, false if player is already doing the quest! public bool AddQuest(AbstractQuest quest) { + if (QuestList.Count > 25) + if (IsDoingQuest(quest) != null) return false; @@ -14788,6 +14811,9 @@ private GamePlayer(ICharacterClass charClass) : base() /// The character for this player public GamePlayer(GameClient client, DbCoreCharacter dbChar) : base() { + if (movementComponent == null) + movementComponent = base.movementComponent as PlayerMovementComponent; + IsJumping = false; m_steed = new WeakRef(null); m_client = client; diff --git a/GameServer/keeps/Gameobjects/FrontiersPortalStone.cs b/GameServer/keeps/Gameobjects/FrontiersPortalStone.cs index 4c080346bd..a9a6eb85c2 100644 --- a/GameServer/keeps/Gameobjects/FrontiersPortalStone.cs +++ b/GameServer/keeps/Gameobjects/FrontiersPortalStone.cs @@ -1,22 +1,3 @@ -/* - * DAWN OF LIGHT - The first free open source DAoC server emulator - * - * 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - */ - using DOL.Database; using DOL.GS.PacketHandler; @@ -126,13 +107,13 @@ public override bool Interact(GamePlayer player) public void GetTeleportLocation(out int x, out int y) { - ushort originalHeading = _heading; - _heading = (ushort)Util.Random((_heading - 500), (_heading + 500)); + ushort originalHeading = Heading; + Heading = (ushort)Util.Random((Heading - 500), (Heading + 500)); int distance = Util.Random(50, 150); Point2D portloc = this.GetPointFromHeading( this.Heading, distance ); x = portloc.X; y = portloc.Y; - _heading = originalHeading; + Heading = originalHeading; } public class TeleporterEffect : GameNPC diff --git a/GameServer/keeps/Gameobjects/GameKeepDoor.cs b/GameServer/keeps/Gameobjects/GameKeepDoor.cs index 24a346f5d8..015902f74a 100644 --- a/GameServer/keeps/Gameobjects/GameKeepDoor.cs +++ b/GameServer/keeps/Gameobjects/GameKeepDoor.cs @@ -648,7 +648,7 @@ public override void LoadFromDatabase(DataObject obj) CurrentRegion = curZone.ZoneRegion; m_name = dbDoor.Name; m_health = dbDoor.Health; - _heading = (ushort)dbDoor.Heading; + Heading = (ushort)dbDoor.Heading; m_x = dbDoor.X; m_y = dbDoor.Y; m_z = dbDoor.Z; diff --git a/GameServer/keeps/Gameobjects/GameRelicDoor.cs b/GameServer/keeps/Gameobjects/GameRelicDoor.cs index fa6a520c39..79893ae2de 100644 --- a/GameServer/keeps/Gameobjects/GameRelicDoor.cs +++ b/GameServer/keeps/Gameobjects/GameRelicDoor.cs @@ -1,26 +1,6 @@ -/* - * DAWN OF LIGHT - The first free open source DAoC server emulator - * - * 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - */ - using System.Collections; using DOL.Database; using DOL.GS.PacketHandler; -using static DOL.GS.GameSiegeWeapon; namespace DOL.GS.Keeps { @@ -234,7 +214,7 @@ public override void LoadFromDatabase(DataObject obj) if (curZone == null) return; this.CurrentRegion = curZone.ZoneRegion; m_name = door.Name; - _heading = (ushort)door.Heading; + Heading = (ushort)door.Heading; m_x = door.X; m_y = door.Y; m_z = door.Z; diff --git a/GameServer/packets/Client/168/PlayerHeadingUpdateHandler.cs b/GameServer/packets/Client/168/PlayerHeadingUpdateHandler.cs index 102809525a..a1fa469f29 100644 --- a/GameServer/packets/Client/168/PlayerHeadingUpdateHandler.cs +++ b/GameServer/packets/Client/168/PlayerHeadingUpdateHandler.cs @@ -3,125 +3,124 @@ namespace DOL.GS.PacketHandler.Client.v168 { - [PacketHandlerAttribute(PacketHandlerType.TCP, eClientPackets.PlayerHeadingUpdate, "Handles Player Heading Update (Short State)", eClientStatus.PlayerInGame)] - public class PlayerHeadingUpdateHandler : IPacketHandler - { - /// - /// Defines a logger for this class. - /// - private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - public void HandlePacket(GameClient client, GSPacketIn packet) - { - if (client?.Player == null) - return; - if (client.Player.ObjectState != GameObject.eObjectState.Active) - return; - - ushort sessionId = packet.ReadShort(); // session ID - if (client.SessionID != sessionId) - { -// GameServer.BanAccount(client, 120, "Hack sessionId", string.Format("Wrong sessionId:0x{0} in 0xBA packet (SessionID:{1})", sessionId, client.SessionID)); - return; // client hack - } - - if (client.Version >= GameClient.eClientVersion.Version1127) - packet.ReadShort(); // target - - ushort head = packet.ReadShort(); - var unk1 = (byte)packet.ReadByte(); // unknown - var flags = (byte)packet.ReadByte(); - var steedSlot = (byte) packet.ReadByte(); - var ridingFlag = (byte)packet.ReadByte(); - - client.Player.Heading = (ushort)(head & 0xFFF); - // client.Player.PetInView = ((flags & 0x04) != 0); // TODO - client.Player.GroundTargetInView = ((flags & 0x08) != 0); - if(!client.Player.IsCasting)client.Player.TargetInView = ((flags & 0x10) != 0); - - byte state = 0; - if (!client.Player.IsAlive) - state = 5; // set dead state - else if (client.Player.Steed != null && client.Player.Steed.ObjectState == GameObject.eObjectState.Active) - { - client.Player.Heading = client.Player.Steed.Heading; - state = 6; // Set ride state - steedSlot = (byte)client.Player.Steed.RiderSlot(client.Player); // there rider slot this player - head = (ushort)client.Player.Steed.ObjectID; // heading = steed ID - } - else - { - if (client.Player.IsSwimming) - state = 1; - if (client.Player.IsClimbing) - state = 7; - if (client.Player.IsSitting) - state = 4; - if (client.Player.IsStrafing) - state |= 8; - } - - byte flagcontent = 0; - if (client.Player.IsWireframe) - flagcontent |= 0x01; - if (client.Player.IsStealthed) - flagcontent |= 0x02; - if (client.Player.IsDiving) - flagcontent |= 0x04; - if (client.Player.IsTorchLighted) - flagcontent |= 0x80; - - GSUDPPacketOut outpak190 = new GSUDPPacketOut(client.Out.GetPacketCode(eServerPackets.PlayerHeading)); - outpak190.WriteShort((ushort) client.SessionID); - outpak190.WriteShort(head); - outpak190.WriteByte(unk1); // unknown - outpak190.WriteByte(flagcontent); - outpak190.WriteByte(steedSlot); - outpak190.WriteByte(ridingFlag); - outpak190.WriteByte((byte)(client.Player.HealthPercent + (client.Player.attackComponent.AttackState ? 0x80 : 0))); - outpak190.WriteByte(state); - outpak190.WriteByte(client.Player.ManaPercent); - outpak190.WriteByte(client.Player.EndurancePercent); - - GSUDPPacketOut outpak1124 = new GSUDPPacketOut(client.Out.GetPacketCode(eServerPackets.PlayerHeading)); - outpak1124.WriteShort((ushort)client.SessionID); - outpak1124.WriteShort(head); - outpak1124.WriteByte(steedSlot); - outpak1124.WriteByte(flagcontent); - outpak1124.WriteByte(0); - outpak1124.WriteByte(ridingFlag); - outpak1124.WriteByte((byte)(client.Player.HealthPercent + (client.Player.attackComponent.AttackState ? 0x80 : 0))); - outpak1124.WriteByte(client.Player.ManaPercent); - outpak1124.WriteByte(client.Player.EndurancePercent); - outpak1124.WriteByte(0); // unknown - - GSUDPPacketOut outpak1127 = new GSUDPPacketOut(client.Out.GetPacketCode(eServerPackets.PlayerHeading)); - outpak1127.WriteShort((ushort)client.SessionID); - outpak1127.WriteShort(0); // current target - outpak1127.WriteShort(head); - outpak1127.WriteByte(steedSlot); - outpak1127.WriteByte(flagcontent); - outpak1127.WriteByte(0); - outpak1127.WriteByte(ridingFlag); - outpak1127.WriteByte((byte)(client.Player.HealthPercent + (client.Player.attackComponent.AttackState ? 0x80 : 0))); - outpak1127.WriteByte(client.Player.ManaPercent); - outpak1127.WriteByte(client.Player.EndurancePercent); - outpak1127.WriteByte(0); // unknown - - foreach (GamePlayer player in client.Player.GetPlayersInRadius(WorldMgr.VISIBILITY_DISTANCE)) - { - if(player != null && player != client.Player) - { - if (client.Player.IsStealthed && !player.CanDetect(client.Player)) - return; - if (player.Client.Version >= GameClient.eClientVersion.Version1127) - player.Out.SendUDP(outpak1127); - else if (player.Client.Version >= GameClient.eClientVersion.Version1124) - player.Out.SendUDP(outpak1124); - else - player.Out.SendUDP(outpak190); - } - } - } - } + [PacketHandlerAttribute(PacketHandlerType.TCP, eClientPackets.PlayerHeadingUpdate, "Handles Player Heading Update (Short State)", eClientStatus.PlayerInGame)] + public class PlayerHeadingUpdateHandler : IPacketHandler + { + private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public void HandlePacket(GameClient client, GSPacketIn packet) + { + if (client.Player == null || client.Player.ObjectState is not GameObject.eObjectState.Active) + return; + + packet.Skip(2); // Session ID. + + if (client.Version >= GameClient.eClientVersion.Version1127) + packet.Skip(2); // Target. + + client.Player.Heading = packet.ReadShort(); + packet.Skip(1); // Unknown. + PlayerPositionUpdateHandler.ProcessActionFlags(client.Player, (PlayerPositionUpdateHandler.ActionFlags) packet.ReadByte()); + packet.Skip(1); // Steed slot (supposedly). + PlayerPositionUpdateHandler.ProcessStateFlags(client.Player, (PlayerPositionUpdateHandler.StateFlags) (packet.ReadByte() << 2)); // 1.127. Same as position update's state flags, but shifted to the right and without strafing bits. + + if (client.Player.Steed != null && client.Player.Steed.ObjectState is GameObject.eObjectState.Active) + client.Player.Heading = client.Player.Steed.Heading; + + client.Player.OnHeadingPacketReceived(); + } + + public static void BroadcastHeading(GameClient client) + { + GamePlayer player = client.Player; + byte actionFlags = (byte) PlayerPositionUpdateHandler.GetActionFlagsOut(player); + byte stateFlags = (byte) ((byte) player.StateFlags >> 2); + byte healthByte = (byte) (player.HealthPercent + (player.attackComponent.AttackState ? 0x80 : 0)); + byte steedSeatPosition = (byte) (player.Steed?.RiderSlot(player) ?? 0); + ushort heading; + + if (player.Steed != null && player.Steed.ObjectState is GameObject.eObjectState.Active) + heading = (ushort) client.Player.Steed.ObjectID; + else + heading = player.RawHeading; + + GSUDPPacketOut outPak1127 = null; + GSUDPPacketOut outPak1124 = null; + GSUDPPacketOut outPak190 = null; + + foreach (GamePlayer otherPlayer in client.Player.GetPlayersInRadius(WorldMgr.VISIBILITY_DISTANCE)) + { + if (otherPlayer == player) + continue; + + if (player.IsStealthed && !otherPlayer.CanDetect(player)) + continue; + + if (otherPlayer.Client.Version >= GameClient.eClientVersion.Version1127) + { + outPak1127 ??= CreateOutPak1127(); + otherPlayer.Out.SendUDP(outPak1127); + } + else if (otherPlayer.Client.Version >= GameClient.eClientVersion.Version1124) + { + outPak1124 ??= CreateOutPak1124(); + otherPlayer.Out.SendUDP(outPak1124); + } + else + { + outPak190 ??= CreateOutPak190(); + otherPlayer.Out.SendUDP(outPak190); + } + } + + GSUDPPacketOut CreateOutPak1127() + { + GSUDPPacketOut outPak = new(client.Out.GetPacketCode(eServerPackets.PlayerHeading)); + outPak.WriteShort((ushort) client.SessionID); + outPak.WriteShort(0); // Current target. + outPak.WriteShort(heading); + outPak.WriteByte(steedSeatPosition); + outPak.WriteByte(actionFlags); + outPak.WriteByte(0); + outPak.WriteByte(stateFlags); + outPak.WriteByte(healthByte); + outPak.WriteByte(player.ManaPercent); + outPak.WriteByte(player.EndurancePercent); + outPak.WriteByte(0); // Unknown. + return outPak; + } + + GSUDPPacketOut CreateOutPak1124() + { + GSUDPPacketOut outPak = new(client.Out.GetPacketCode(eServerPackets.PlayerHeading)); + outPak.WriteShort((ushort) client.SessionID); + outPak.WriteShort(heading); + outPak.WriteByte(steedSeatPosition); + outPak.WriteByte(actionFlags); + outPak.WriteByte(0); + outPak.WriteByte(stateFlags); + outPak.WriteByte(healthByte); + outPak.WriteByte(player.ManaPercent); + outPak.WriteByte(player.EndurancePercent); + outPak.WriteByte(0); // Unknown. + return outPak; + } + + GSUDPPacketOut CreateOutPak190() + { + GSUDPPacketOut outPak = new(client.Out.GetPacketCode(eServerPackets.PlayerHeading)); + outPak.WriteShort((ushort) client.SessionID); + outPak.WriteShort(heading); + outPak.WriteByte(0); // Unknown. + outPak.WriteByte(actionFlags); + outPak.WriteByte(steedSeatPosition); + outPak.WriteByte(stateFlags); + outPak.WriteByte(healthByte); + outPak.WriteByte(0); // State? + outPak.WriteByte(player.ManaPercent); + outPak.WriteByte(player.EndurancePercent); + return outPak; + } + } + } } diff --git a/GameServer/packets/Client/168/PlayerPositionUpdateHandler.cs b/GameServer/packets/Client/168/PlayerPositionUpdateHandler.cs index 264273bfa2..b5e698883b 100644 --- a/GameServer/packets/Client/168/PlayerPositionUpdateHandler.cs +++ b/GameServer/packets/Client/168/PlayerPositionUpdateHandler.cs @@ -29,16 +29,26 @@ public class PlayerPositionUpdateHandler : IPacketHandler public void HandlePacket(GameClient client, GSPacketIn packet) { - if (!client.Player.OnUpdatePosition()) + //Tiv: in very rare cases client send 0xA9 packet before sending S<=C 0xE8 player world initialize + if (client.Player.ObjectState is not GameObject.eObjectState.Active || client.ClientState is not GameClient.eClientState.Playing) return; - //Tiv: in very rare cases client send 0xA9 packet before sending S<=C 0xE8 player world initialize - if ((client.Player.ObjectState != GameObject.eObjectState.Active) || (client.ClientState != GameClient.eClientState.Playing)) + if (!client.Player.OnPositionPacketReceivedStart()) + return; + + HandlePacketInternal(client, packet); + client.Player.OnPositionPacketReceivedEnd(); + } + + private static void HandlePacketInternal(GameClient client, GSPacketIn packet) + { + // In very rare cases client send 0xA9 packet before sending S<=C 0xE8 player world initialize + if (client.Player.ObjectState is not GameObject.eObjectState.Active || client.ClientState is not GameClient.eClientState.Playing and not GameClient.eClientState.Linkdead) return; // Don't allow movement if the player isn't close to the NPC they're supposed to be riding. // Instead, teleport them to it and send an update packet (the client may then ask for a create packet). - if (client.Player.Steed != null && client.Player.Steed.ObjectState == GameObject.eObjectState.Active) + if (client.Player.Steed != null && client.Player.Steed.ObjectState is GameObject.eObjectState.Active) { GamePlayer rider = client.Player; GameNPC steed = rider.Steed; @@ -56,355 +66,282 @@ public void HandlePacket(GameClient client, GSPacketIn packet) } } - HandlePacketInternal(client, packet); - client.LastPositionUpdatePacketReceived = packet; - } - - public static void BroadcastLastReceivedPacket(GameClient client) - { - GSPacketIn packet = client.LastPositionUpdatePacketReceived; - - if (packet == null) - return; - - packet.Position = 0; - HandlePacketInternal(client, packet); - } - - private static void HandlePacketInternal(GameClient client, GSPacketIn packet) - { if (client.Version >= GameClient.eClientVersion.Version1124) HandlePacketSince1124(client, packet); else - HandlePacketPre1124(client, packet); - } + HandlePacketPre1124(client, packet); // This is very outdated and is no longer working. - private static void HandlePacketSince1124(GameClient client, GSPacketIn packet) - { - //Tiv: in very rare cases client send 0xA9 packet before sending S<=C 0xE8 player world initialize - if (client.Player.ObjectState != GameObject.eObjectState.Active || client.ClientState is not GameClient.eClientState.Playing and not GameClient.eClientState.Linkdead) - return; - - long environmentTick = GameLoop.GameLoopTime; - - float x = packet.ReadFloatLowEndian(); - float y = packet.ReadFloatLowEndian(); - float z = packet.ReadFloatLowEndian(); - float speed = packet.ReadFloatLowEndian(); - float zSpeed = packet.ReadFloatLowEndian(); - ushort sessionId = packet.ReadShort(); - - if (client.Version >= GameClient.eClientVersion.Version1127) - packet.Skip(2); // object ID - - ushort zoneId = packet.ReadShort(); - State state = (State) packet.ReadByte(); - packet.Skip(1); // Unknown. - ushort fallingDamage = packet.ReadShort(); - ushort heading = packet.ReadShort(); - Action action = (Action) packet.ReadByte(); - packet.Skip(2); // unknown bytes x2 - packet.Skip(1); // Health. - // two trailing bytes, no data + 2 more for 1.127+ - - if ((client.Player.IsMezzed || client.Player.IsStunned) && !client.Player.effectListComponent.ContainsEffectForEffectType(eEffect.SpeedOfSound)) - client.Player.CurrentSpeed = 0; - else + static void HandlePacketSince1124(GameClient client, GSPacketIn packet) { - if (client.Player.CurrentSpeed == 0 && - (client.Player.LastPositionUpdatePoint.X != x || client.Player.LastPositionUpdatePoint.Y != y)) - { - if (client.Player.IsSitting) - client.Player.Sit(false); - } + float x = packet.ReadFloatLowEndian(); + float y = packet.ReadFloatLowEndian(); + float z = packet.ReadFloatLowEndian(); + float speed = packet.ReadFloatLowEndian(); + float zSpeed = packet.ReadFloatLowEndian(); + packet.Skip(2); // Session ID. - client.Player.CurrentSpeed = (short) speed; - } + if (client.Version >= GameClient.eClientVersion.Version1127) + packet.Skip(2); // Object ID. - client.Player.IsStrafing = (state & State.STRAFING_ANY) != 0; - client.Player.IsClimbing = (state & State.CLIMBING) is State.CLIMBING; + ushort zoneId = packet.ReadShort(); - // CLIMBING combines SITTING, JUMPING, SWIMMING and is always allowed. - if (!client.Player.IsClimbing) - { - // This turns the player invisible if it isn't riding. - if ((state & State.RIDING) is State.RIDING && !client.Player.IsRiding) + if (!ProcessStateFlags(client.Player, (StateFlags) packet.ReadByte())) + return; + + packet.Skip(1); // Unknown. + ushort fallingDamage = packet.ReadShort(); + ushort heading = packet.ReadShort(); + ProcessActionFlags(client.Player, (ActionFlags) packet.ReadByte()); + packet.Skip(2); // Unknown bytes. + packet.Skip(1); // Health. + // two trailing bytes, no data, +2 more for 1.127+. + + if ((client.Player.IsMezzed || client.Player.IsStunned) && !client.Player.effectListComponent.ContainsEffectForEffectType(eEffect.SpeedOfSound)) + client.Player.CurrentSpeed = 0; + else { - if (ServerProperties.Properties.BAN_HACKERS) + if (client.Player.CurrentSpeed == 0 && + (client.Player.LastPositionUpdatePoint.X != x || client.Player.LastPositionUpdatePoint.Y != y)) { - client.BanAccount($"Autoban forged position update packet ({nameof(state)}: {State.SITTING | State.JUMPING})"); - client.Out.SendPlayerQuit(true); - client.Disconnect(); - return; + if (client.Player.IsSitting) + client.Player.Sit(false); } - state &= ~State.RIDING; + client.Player.CurrentSpeed = (short) speed; } - // Sitting and swimming (death animation). Don't allow players to play dead. - // Clients that just got resurrected but aren't aware yet also send this. - if ((state & State.DEAD) is State.DEAD && client.Player.HealthPercent > 0) - state &= ~State.DEAD; + client.Player.FallSpeed = (short) zSpeed; - // If the client has flying enabled but the debug option wasn't enabled. - if ((state & State.FLYING) is State.FLYING && !client.Player.TempProperties.GetProperty(GamePlayer.DEBUG_MODE_PROPERTY, false) && !client.Player.IsAllowedToFly) + Zone newZone = WorldMgr.GetZone(zoneId); + + if (newZone == null) { - if (ServerProperties.Properties.BAN_HACKERS) + if (!client.Player.TempProperties.GetProperty("isbeingbanned", false)) { - client.BanAccount($"Autoban forged position update packet ({nameof(state)}: {State.FLYING})"); - client.Out.SendPlayerQuit(true); - client.Disconnect(); - return; + log.Error($"{client.Player.Name}'s position in unknown zone! => {zoneId}"); + GamePlayer player = client.Player; + player.TempProperties.SetProperty("isbeingbanned", true); + player.MoveToBind(); } - state &= ~State.FLYING; + return; // TODO: what should we do? player lost in space } - client.Player.IsSwimming = (state & State.SWIMMING) != 0; - } - - Zone newZone = WorldMgr.GetZone(zoneId); - - if (newZone == null) - { - if (!client.Player.TempProperties.GetProperty("isbeingbanned", false)) + // move to bind if player fell through the floor + if (z == 0) { - log.Error($"{client.Player.Name}'s position in unknown zone! => {zoneId}"); - GamePlayer player = client.Player; - player.TempProperties.SetProperty("isbeingbanned", true); - player.MoveToBind(); + client.Player.MoveToBind(); + return; } - return; // TODO: what should we do? player lost in space - } + bool zoneChange = newZone != client.Player.LastPositionUpdateZone; - // move to bind if player fell through the floor - if (z == 0) - { - client.Player.MoveToBind(); - return; - } - - bool zoneChange = newZone != client.Player.LastPositionUpdateZone; - - if (zoneChange) - { - //If the region changes -> make sure we don't take any falling damage - if (client.Player.LastPositionUpdateZone != null && newZone.ZoneRegion.ID != client.Player.LastPositionUpdateZone.ZoneRegion.ID) - client.Player.MaxLastZ = int.MinValue; - - /* - * "You have entered Burial Tomb." - * "Burial Tomb" - * "Current area is adjusted for one level 1 player." - * "Current area has a 50% instance bonus." - */ - - string description = newZone.Description; - string screenDescription = description; - - if (LanguageMgr.GetTranslation(client, newZone) is DbLanguageZone translation) + if (zoneChange) { - if (!string.IsNullOrEmpty(translation.Description)) - description = translation.Description; + //If the region changes -> make sure we don't take any falling damage + if (client.Player.LastPositionUpdateZone != null && newZone.ZoneRegion.ID != client.Player.LastPositionUpdateZone.ZoneRegion.ID) + client.Player.MaxLastZ = int.MinValue; - if (!string.IsNullOrEmpty(translation.ScreenDescription)) - screenDescription = translation.ScreenDescription; - } - - client.Out.SendMessage(LanguageMgr.GetTranslation(client.Account.Language, "PlayerPositionUpdateHandler.Entered", description), eChatType.CT_System, eChatLoc.CL_SystemWindow); - client.Out.SendMessage(screenDescription, eChatType.CT_ScreenCenterSmaller, eChatLoc.CL_SystemWindow); - client.Player.LastPositionUpdateZone = newZone; + /* + * "You have entered Burial Tomb." + * "Burial Tomb" + * "Current area is adjusted for one level 1 player." + * "Current area has a 50% instance bonus." + */ - if (client.Player.GMStealthed) - client.Player.Stealth(true); - } + string description = newZone.Description; + string screenDescription = description; - int coordsPerSec = 0; - int jumpDetect = 0; - long timediff = GameLoop.GameLoopTime - client.Player.LastPositionUpdateTime; - int distance = 0; + if (LanguageMgr.GetTranslation(client, newZone) is DbLanguageZone translation) + { + if (!string.IsNullOrEmpty(translation.Description)) + description = translation.Description; - if (timediff > 0) - { - Point3D newPoint = new(x, y, z); - distance = newPoint.GetDistanceTo(new Point3D((int) client.Player.LastPositionUpdatePoint.X, (int) client.Player.LastPositionUpdatePoint.Y, (int)client.Player.LastPositionUpdatePoint.Z)); - coordsPerSec = distance * 1000 /(int)timediff; + if (!string.IsNullOrEmpty(translation.ScreenDescription)) + screenDescription = translation.ScreenDescription; + } - if (distance < 100 && client.Player.LastPositionUpdatePoint.Z > 0) - jumpDetect = (int) (z - client.Player.LastPositionUpdatePoint.Z); - } + client.Out.SendMessage(LanguageMgr.GetTranslation(client.Account.Language, "PlayerPositionUpdateHandler.Entered", description), eChatType.CT_System, eChatLoc.CL_SystemWindow); + client.Out.SendMessage(screenDescription, eChatType.CT_ScreenCenterSmaller, eChatLoc.CL_SystemWindow); + client.Player.LastPositionUpdateZone = newZone; - if (distance > 0) - client.Player.LastPlayerActivityTime = GameLoop.GameLoopTime; + if (client.Player.GMStealthed) + client.Player.Stealth(true); + } - client.Player.LastPositionUpdateTime = GameLoop.GameLoopTime; - client.Player.LastPositionUpdatePoint.X = x; - client.Player.LastPositionUpdatePoint.Y = y; - client.Player.LastPositionUpdatePoint.Z = z; - int tolerance = ServerProperties.Properties.CPS_TOLERANCE; + int coordsPerSec = 0; + int jumpDetect = 0; + long timediff = GameLoop.GameLoopTime - client.Player.LastPositionUpdatePacketReceivedTime; + int distance = 0; - if (client.Player.Steed != null && client.Player.Steed.MaxSpeed > 0) - tolerance += client.Player.Steed.MaxSpeed; - else if (client.Player.MaxSpeed > 0) - tolerance += client.Player.MaxSpeed; + if (timediff > 0) + { + Point3D newPoint = new(x, y, z); + distance = newPoint.GetDistanceTo(new Point3D((int) client.Player.LastPositionUpdatePoint.X, (int) client.Player.LastPositionUpdatePoint.Y, (int)client.Player.LastPositionUpdatePoint.Z)); + coordsPerSec = distance * 1000 /(int)timediff; - // Don't trust the client to set it to true. We rely on that to detect move hacks. - if ((action & Action.TELEPORT) == 0) - client.Player.IsJumping = false; + if (distance < 100 && client.Player.LastPositionUpdatePoint.Z > 0) + jumpDetect = (int) (z - client.Player.LastPositionUpdatePoint.Z); + } - client.Player.TargetInView = (action & Action.TARGET_IN_VIEW) is Action.TARGET_IN_VIEW; - client.Player.GroundTargetInView = (action & Action.GROUNT_TARGET_IN_VIEW) != 0; - client.Player.IsTorchLighted = (action & Action.TORCH) != 0; - // patch 0069 player diving is 0x02, but will broadcast to other players as 0x04 - // if player has a pet summoned, player action is sent by client as 0x04, but sending to other players this is skipped - client.Player.IsDiving = (action & Action.DIVING) != 0; + if (distance > 0) + client.Player.LastPlayerActivityTime = GameLoop.GameLoopTime; - if (client.Player.IsJumping) - { - coordsPerSec = 0; - jumpDetect = 0; - client.Player.IsJumping = false; - } + client.Player.LastPositionUpdatePacketReceivedTime = GameLoop.GameLoopTime; + client.Player.LastPositionUpdatePoint.X = x; + client.Player.LastPositionUpdatePoint.Y = y; + client.Player.LastPositionUpdatePoint.Z = z; + int tolerance = ServerProperties.Properties.CPS_TOLERANCE; - if (!client.Player.IsAllowedToFly && (coordsPerSec > tolerance || jumpDetect > ServerProperties.Properties.JUMP_TOLERANCE)) - { - bool isHackDetected = true; + if (client.Player.Steed != null && client.Player.Steed.MaxSpeed > 0) + tolerance += client.Player.Steed.MaxSpeed; + else if (client.Player.MaxSpeed > 0) + tolerance += client.Player.MaxSpeed; - if (coordsPerSec > tolerance) + if (client.Player.IsJumping) { - // check to see if CPS time tolerance is exceeded - int lastCPSTick = client.Player.TempProperties.GetProperty(LASTCPSTICK, 0); - - if (environmentTick - lastCPSTick > ServerProperties.Properties.CPS_TIME_TOLERANCE) - isHackDetected = false; + coordsPerSec = 0; + jumpDetect = 0; + client.Player.IsJumping = false; } - if (isHackDetected) + if (!client.Player.IsAllowedToFly && (coordsPerSec > tolerance || jumpDetect > ServerProperties.Properties.JUMP_TOLERANCE)) { - StringBuilder builder = new(); - builder.Append("MOVEHACK_DETECT"); - builder.Append(": CharName="); - builder.Append(client.Player.Name); - builder.Append(" Account="); - builder.Append(client.Account.Name); - builder.Append(" IP="); - builder.Append(client.TcpEndpointAddress); - builder.Append(" CPS:="); - builder.Append(coordsPerSec); - builder.Append(" JT="); - builder.Append(jumpDetect); - ChatUtil.SendDebugMessage(client, builder.ToString()); + bool isHackDetected = true; - if (client.Account.PrivLevel == 1) + if (coordsPerSec > tolerance) { - GameServer.Instance.LogCheatAction(builder.ToString()); + // check to see if CPS time tolerance is exceeded + int lastCPSTick = client.Player.TempProperties.GetProperty(LASTCPSTICK, 0); + + if (GameLoop.GameLoopTime - lastCPSTick > ServerProperties.Properties.CPS_TIME_TOLERANCE) + isHackDetected = false; + } - if (ServerProperties.Properties.ENABLE_MOVEDETECT) + if (isHackDetected) + { + StringBuilder builder = new(); + builder.Append("MOVEHACK_DETECT"); + builder.Append(": CharName="); + builder.Append(client.Player.Name); + builder.Append(" Account="); + builder.Append(client.Account.Name); + builder.Append(" IP="); + builder.Append(client.TcpEndpointAddress); + builder.Append(" CPS:="); + builder.Append(coordsPerSec); + builder.Append(" JT="); + builder.Append(jumpDetect); + ChatUtil.SendDebugMessage(client, builder.ToString()); + + if (client.Account.PrivLevel == 1) { - if (ServerProperties.Properties.BAN_HACKERS && false) // banning disabled until this technique is proven accurate + GameServer.Instance.LogCheatAction(builder.ToString()); + + if (ServerProperties.Properties.ENABLE_MOVEDETECT) { - DbBans b = new() - { - Author = "SERVER", - Ip = client.TcpEndpointAddress, - Account = client.Account.Name, - DateBan = DateTime.Now, - Type = "B", - Reason = string.Format("Autoban MOVEHACK:(CPS:{0}, JT:{1}) on player:{2}", coordsPerSec, jumpDetect, client.Player.Name) - }; - - GameServer.Database.AddObject(b); - GameServer.Database.SaveObject(b); - string message = "You have been auto kicked and banned due to movement hack detection!"; - - for (int i = 0; i < 8; i++) + if (ServerProperties.Properties.BAN_HACKERS && false) // banning disabled until this technique is proven accurate { - client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_SystemWindow); - client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_ChatWindow); - } + DbBans b = new() + { + Author = "SERVER", + Ip = client.TcpEndpointAddress, + Account = client.Account.Name, + DateBan = DateTime.Now, + Type = "B", + Reason = string.Format("Autoban MOVEHACK:(CPS:{0}, JT:{1}) on player:{2}", coordsPerSec, jumpDetect, client.Player.Name) + }; - client.Out.SendPlayerQuit(true); - client.Player.SaveIntoDatabase(); - client.Player.Quit(true); - } - else - { - string message = "You have been auto kicked due to movement hack detection!"; + GameServer.Database.AddObject(b); + GameServer.Database.SaveObject(b); + string message = "You have been auto kicked and banned due to movement hack detection!"; + + for (int i = 0; i < 8; i++) + { + client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_SystemWindow); + client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_ChatWindow); + } - for (int i = 0; i < 8; i++) + client.Out.SendPlayerQuit(true); + client.Player.SaveIntoDatabase(); + client.Player.Quit(true); + } + else { - client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_SystemWindow); - client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_ChatWindow); + string message = "You have been auto kicked due to movement hack detection!"; + + for (int i = 0; i < 8; i++) + { + client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_SystemWindow); + client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_ChatWindow); + } + + client.Out.SendPlayerQuit(true); + client.Player.SaveIntoDatabase(); + client.Player.Quit(true); } - client.Out.SendPlayerQuit(true); - client.Player.SaveIntoDatabase(); - client.Player.Quit(true); + client.Disconnect(); + return; } - - client.Disconnect(); - return; } } - } - client.Player.TempProperties.SetProperty(LASTCPSTICK, environmentTick); - } + client.Player.TempProperties.SetProperty(LASTCPSTICK, GameLoop.GameLoopTime); + } - client.Player.X = (int) x; - client.Player.Y = (int) y; - client.Player.Z = (int) z; - client.Player.Heading = (ushort) (heading & 0xFFF); + client.Player.X = (int) x; + client.Player.Y = (int) y; + client.Player.Z = (int) z; + client.Player.Heading = heading; - // Begin ---------- New Area System ----------- - if (client.Player.CurrentRegion.Time > client.Player.AreaUpdateTick) // check if update is needed - { - IList oldAreas = client.Player.CurrentAreas; + // Area. + if (client.Player.CurrentRegion.Time > client.Player.AreaUpdateTick) + { + IList oldAreas = client.Player.CurrentAreas; - // Because we may be in an instance we need to do the area check from the current region - // rather than relying on the zone which is in the skinned region. - Tolakram + // Because we may be in an instance we need to do the area check from the current region + // rather than relying on the zone which is in the skinned region. - Tolakram - IList newAreas = client.Player.CurrentRegion.GetAreasOfZone(newZone, client.Player); + IList newAreas = client.Player.CurrentRegion.GetAreasOfZone(newZone, client.Player); - // Check for left areas - if (oldAreas != null) - { - foreach (IArea area in oldAreas) + // Check for left areas + if (oldAreas != null) { - if (!newAreas.Contains(area)) + foreach (IArea area in oldAreas) { - area.OnPlayerLeave(client.Player); - - //Check if leaving Border Keep areas so we can check RealmTimer - if (area is AbstractArea checkrvrarea && (checkrvrarea.Description.Equals("Castle Sauvage") || - checkrvrarea.Description.Equals("Snowdonia Fortress") || - checkrvrarea.Description.Equals("Svasud Faste") || - checkrvrarea.Description.Equals("Vindsaul Faste") || - checkrvrarea.Description.Equals("Druim Ligen") || - checkrvrarea.Description.Equals("Druim Cain"))) + if (!newAreas.Contains(area)) { - RealmTimer.CheckRealmTimer(client.Player); + area.OnPlayerLeave(client.Player); + + // Check if leaving Border Keep areas so we can check RealmTimer. + // This is very ugly. + if (area is AbstractArea borderKeep && + (borderKeep.Description.Equals("Castle Sauvage") || + borderKeep.Description.Equals("Snowdonia Fortress") || + borderKeep.Description.Equals("Svasud Faste") || + borderKeep.Description.Equals("Vindsaul Faste") || + borderKeep.Description.Equals("Druim Ligen") || + borderKeep.Description.Equals("Druim Cain"))) + { + RealmTimer.CheckRealmTimer(client.Player); + } } } } - } - // Check for entered areas - foreach (IArea area in newAreas) - { - if (oldAreas == null || !oldAreas.Contains(area)) - area.OnPlayerEnter(client.Player); - } + // Check for entered areas + foreach (IArea area in newAreas) + { + if (oldAreas == null || !oldAreas.Contains(area)) + area.OnPlayerEnter(client.Player); + } - // set current areas to new one... - client.Player.CurrentAreas = newAreas; - client.Player.AreaUpdateTick = client.Player.CurrentRegion.Time + 2000; // update every 2 seconds - } - // End ---------- New Area System ----------- + // set current areas to new one... + client.Player.CurrentAreas = newAreas; + client.Player.AreaUpdateTick = client.Player.CurrentRegion.Time + 2000; // update every 2 seconds + } - lock (client.Player.LastUniqueLocations) - { GameLocation[] locations = client.Player.LastUniqueLocations; GameLocation loc = locations[0]; @@ -419,521 +356,246 @@ private static void HandlePacketSince1124(GameClient client, GSPacketIn packet) loc.Heading = client.Player.Heading; loc.RegionID = client.Player.CurrentRegionID; } - } - //FALLING DAMAGE - if (GameServer.ServerRules.CanTakeFallDamage(client.Player) && !client.Player.IsSwimming) - { - try + // Fall damage. + if (GameServer.ServerRules.CanTakeFallDamage(client.Player) && !client.Player.IsSwimming) { - int maxLastZ = client.Player.MaxLastZ; - - // Are we on the ground? - if ((fallingDamage >> 15) != 0) + try { - int safeFallLevel = client.Player.GetAbilityLevel(Abilities.SafeFall); - float fallSpeed = zSpeed * -1 - 100 * safeFallLevel; - int fallDivide = 15; - int fallPercent = (int) Math.Min(99, (fallSpeed - 501) / fallDivide); + int maxLastZ = client.Player.MaxLastZ; - if (fallSpeed > 500) + // Are we on the ground? + if ((fallingDamage >> 15) != 0) { - if (client.Player.CharacterClass.ID != (int) eCharacterClass.Necromancer || !client.Player.IsShade) - client.Player.CalcFallDamage(fallPercent); - } - - client.Player.MaxLastZ = client.Player.Z; - } - else if (maxLastZ < client.Player.Z || client.Player.IsRiding || zSpeed > -150) // is riding, for dragonflys - client.Player.MaxLastZ = client.Player.Z; - } - catch - { - log.Warn("error when attempting to calculate fall damage"); - } - } - - ushort steedSeatPosition = 0; - - if (client.Player.Steed != null && client.Player.Steed.ObjectState == GameObject.eObjectState.Active) - { - client.Player.Heading = client.Player.Steed.Heading; - heading = (ushort) client.Player.Steed.ObjectID; - steedSeatPosition = (ushort)client.Player.Steed.RiderSlot(client.Player); - } - - BroadcastPositionSince1124(); - - //handle closing of windows - //trade window - if (client.Player.TradeWindow?.Partner != null && !client.Player.IsWithinRadius(client.Player.TradeWindow.Partner, WorldMgr.GIVE_ITEM_DISTANCE)) - client.Player.TradeWindow.CloseTrade(); - - void BroadcastPositionSince1124() - { - GSUDPPacketOut outpak1124 = new(client.Out.GetPacketCode(eServerPackets.PlayerPosition)); - //patch 0069 test to fix player swim out byte flag - byte playerOutAction = 0x00; - if (client.Player.IsDiving) - playerOutAction |= 0x04; - if (client.Player.TargetInView) - playerOutAction |= 0x30; - if (client.Player.GroundTargetInView) - playerOutAction |= 0x08; - if (client.Player.IsTorchLighted) - playerOutAction |= 0x80; - if (client.Player.IsStealthed) - playerOutAction |= 0x02; - - outpak1124.WriteFloatLowEndian(x); - outpak1124.WriteFloatLowEndian(y); - outpak1124.WriteFloatLowEndian(z); - outpak1124.WriteFloatLowEndian(speed); - outpak1124.WriteFloatLowEndian(zSpeed); - outpak1124.WriteShort(sessionId); - outpak1124.WriteShort(zoneId); - outpak1124.WriteByte((byte) state); - outpak1124.WriteByte(0); - outpak1124.WriteShort(steedSeatPosition); // fall damage flag coming in, steed seat position going out - outpak1124.WriteShort(heading); - outpak1124.WriteByte(playerOutAction); - outpak1124.WriteByte((byte) (client.Player.RPFlag ? 1 : 0)); - outpak1124.WriteByte(0); - outpak1124.WriteByte((byte) (client.Player.HealthPercent + (client.Player.attackComponent.AttackState ? 0x80 : 0))); - outpak1124.WriteByte(client.Player.ManaPercent); - outpak1124.WriteByte(client.Player.EndurancePercent); - - GSUDPPacketOut outpak1127 = new(client.Out.GetPacketCode(eServerPackets.PlayerPosition)); - outpak1127.Write(outpak1124.GetBuffer(), 5, 22); // from position X to sessionID - outpak1127.WriteShort((ushort) client.Player.ObjectID); - outpak1127.WriteShort(zoneId); - outpak1127.WriteByte((byte) state); - outpak1127.WriteByte(0); - outpak1127.WriteShort(steedSeatPosition); // fall damage flag coming in, steed seat position going out - outpak1127.WriteShort(heading); - outpak1127.WriteByte(playerOutAction); - outpak1127.WriteByte((byte) (client.Player.RPFlag ? 1 : 0)); - outpak1127.WriteByte(0); - outpak1127.WriteByte((byte) (client.Player.HealthPercent + (client.Player.attackComponent.AttackState ? 0x80 : 0))); - outpak1127.WriteByte(client.Player.ManaPercent); - outpak1127.WriteByte(client.Player.EndurancePercent); - outpak1127.WriteShort(0); - - GSUDPPacketOut outpak190 = new(client.Out.GetPacketCode(eServerPackets.PlayerPosition)); - outpak190.WriteShort((ushort) client.SessionID); - outpak190.WriteShort((ushort) (client.Player.CurrentSpeed & 0x1FF)); - outpak190.WriteShort((ushort) z); - ushort xoff = (ushort) (x - (client.Player.CurrentZone?.XOffset ?? 0)); - outpak190.WriteShort(xoff); - ushort yoff = (ushort) (y - (client.Player.CurrentZone?.YOffset ?? 0)); - outpak190.WriteShort(yoff); - outpak190.WriteShort(zoneId); - outpak190.WriteShort(heading); - outpak190.WriteShort(steedSeatPosition); - outpak190.WriteByte((byte) action); - outpak190.WriteByte((byte) (client.Player.HealthPercent + (client.Player.attackComponent.AttackState ? 0x80 : 0))); - outpak190.WriteByte(client.Player.ManaPercent); - outpak190.WriteByte(client.Player.EndurancePercent); - - GSUDPPacketOut outpak1112 = new(client.Out.GetPacketCode(eServerPackets.PlayerPosition)); - outpak1112.Write(outpak190.GetBuffer(), 5, (int) outpak190.Length - 5); - outpak1112.WriteByte((byte) (client.Player.RPFlag ? 1 : 0)); - outpak1112.WriteByte(0); - - outpak190.FillString(client.Player.CharacterClass.Name, 32); - outpak190.WriteByte((byte) (client.Player.RPFlag ? 1 : 0)); - outpak190.WriteByte(0); + int safeFallLevel = client.Player.GetAbilityLevel(Abilities.SafeFall); + float fallSpeed = zSpeed * -1 - 100 * safeFallLevel; + int fallDivide = 15; + int fallPercent = (int) Math.Min(99, (fallSpeed - 501) / fallDivide); - foreach (GamePlayer player in client.Player.GetPlayersInRadius(WorldMgr.VISIBILITY_DISTANCE)) - { - if (player == null || player == client.Player) - continue; - - if ((client.Player.InHouse || player.InHouse) && player.CurrentHouse != client.Player.CurrentHouse) - continue; + if (fallSpeed > 500) + { + if ((eCharacterClass) client.Player.CharacterClass.ID is not eCharacterClass.Necromancer || !client.Player.IsShade) + client.Player.CalcFallDamage(fallPercent); + } - if (!client.Player.IsStealthed || player.CanDetect(client.Player)) + client.Player.MaxLastZ = client.Player.Z; + } + else if (maxLastZ < client.Player.Z || client.Player.IsRiding || zSpeed > -150) // is riding, for dragonflys + client.Player.MaxLastZ = client.Player.Z; + } + catch { - if (player.Client.Version >= GameClient.eClientVersion.Version1127) - player.Out.SendUDP(outpak1127); - else if (player.Client.Version >= GameClient.eClientVersion.Version1124) - player.Out.SendUDP(outpak1124); - else if (player.Client.Version >= GameClient.eClientVersion.Version1112) - player.Out.SendUDP(outpak1112); - else - player.Out.SendUDP(outpak190); + log.Warn("error when attempting to calculate fall damage"); } - else - player.Out.SendObjectDelete(client.Player); //remove the stealthed player from view } - } - } - private static void HandlePacketPre1124(GameClient client, GSPacketIn packet) - { - long environmentTick = GameLoop.GameLoopTime; - int oldSpeed = client.Player.CurrentSpeed; - packet.Skip(2); //PID - ushort speedData = packet.ReadShort(); - int speed = speedData & 0x1FF; - - if ((speedData & 0x200) != 0) - speed = -speed; - - if ((client.Player.IsMezzed || client.Player.IsStunned) && !client.Player.effectListComponent.ContainsEffectForEffectType(eEffect.SpeedOfSound)) - // Nidel: updating client.Player.CurrentSpeed instead of speed - client.Player.CurrentSpeed = 0; - else - client.Player.CurrentSpeed = (short) speed; - client.Player.IsStrafing = (speedData & 0xe000) != 0; - int realZ = packet.ReadShort(); - ushort xOffsetInZone = packet.ReadShort(); - ushort yOffsetInZone = packet.ReadShort(); - ushort zoneId = packet.ReadShort(); + if (client.Player.Steed != null && client.Player.Steed.ObjectState is GameObject.eObjectState.Active) + client.Player.Heading = client.Player.Steed.Heading; - try - { - Zone grabZone = WorldMgr.GetZone(zoneId); - } - catch (Exception) - { - //if we get a zone that doesn't exist, move player to their bindstone - client.Player.MoveTo((ushort) client.Player.BindRegion, - client.Player.BindXpos, - client.Player.BindYpos, - (ushort) client.Player.BindZpos, - (ushort) client.Player.BindHeading); - return; - + // Close trade window. + if (client.Player.TradeWindow?.Partner != null && !client.Player.IsWithinRadius(client.Player.TradeWindow.Partner, WorldMgr.GIVE_ITEM_DISTANCE)) + client.Player.TradeWindow.CloseTrade(); } - //Dinberg - Instance considerations. - //Now this gets complicated, so listen up! We have told the client a lie when it comes to the zoneID. - //As a result, every movement update, they are sending a lie back to us. Two liars could get confusing! - - //BUT, the lie we sent has a truth to it - the geometry and layout of the zone. As a result, the zones - //x and y offsets will still actually be relevant to our current zone. And for the clones to have been - //created, there must have been a real zone to begin with, of id == instanceZone.SkinID. + static void HandlePacketPre1124(GameClient client, GSPacketIn packet) + { + long environmentTick = GameLoop.GameLoopTime; + packet.Skip(2); //PID + ushort speedData = packet.ReadShort(); + int speed = speedData & 0x1FF; - //So, although our client is lying to us, and thinks its in another zone, that zone happens to coincide - //exactly with the zone we are instancing - and so all the positions still ring true. + if ((speedData & 0x200) != 0) + speed = -speed; - //Philosophically speaking, its like looking in a mirror and saying 'Am I a reflected, or reflector?' - //What it boils down to has no bearing whatsoever on the result of anything, so long as someone sitting - //outside of the unvierse knows not to listen to whether you say which you are, and knows the truth to the - //answer. Then, he need only know what you are doing ;) + if ((client.Player.IsMezzed || client.Player.IsStunned) && !client.Player.effectListComponent.ContainsEffectForEffectType(eEffect.SpeedOfSound)) + // Nidel: updating client.Player.CurrentSpeed instead of speed + client.Player.CurrentSpeed = 0; + else + client.Player.CurrentSpeed = (short) speed; - Zone newZone = WorldMgr.GetZone(zoneId); + client.Player.IsStrafing = (speedData & 0xe000) != 0; + int realZ = packet.ReadShort(); + ushort xOffsetInZone = packet.ReadShort(); + ushort yOffsetInZone = packet.ReadShort(); + ushort zoneId = packet.ReadShort(); - if (newZone == null) - { - if (client.Player==null) - return; - - if (!client.Player.TempProperties.GetProperty("isbeingbanned",false)) + try { - if (log.IsErrorEnabled) - log.Error($"{client.Player.Name}'s position in unknown zone! => {zoneId}"); - - GamePlayer player=client.Player; - player.TempProperties.SetProperty("isbeingbanned", true); - player.MoveToBind(); + Zone grabZone = WorldMgr.GetZone(zoneId); } - - return; // TODO: what should we do? player lost in space - } - - // move to bind if player fell through the floor - if (realZ == 0) - { - client.Player.MoveTo((ushort)client.Player.BindRegion, - client.Player.BindXpos, - client.Player.BindYpos, - (ushort)client.Player.BindZpos, - (ushort)client.Player.BindHeading); - return; - } - - int realX = newZone.XOffset + xOffsetInZone; - int realY = newZone.YOffset + yOffsetInZone; - bool zoneChange = newZone != client.Player.LastPositionUpdateZone; - - if (zoneChange) - { - //If the region changes -> make sure we don't take any falling damage - if (client.Player.LastPositionUpdateZone != null && newZone.ZoneRegion.ID != client.Player.LastPositionUpdateZone.ZoneRegion.ID) - client.Player.MaxLastZ = int.MinValue; - - // Update water level and diving flag for the new zone - // commenting this out for now, creates a race condition when teleporting within same region, jumping player back and forth as player xyz isnt updated yet. - //client.Out.SendPlayerPositionAndObjectID(); - zoneChange = true; - - /* - * "You have entered Burial Tomb." - * "Burial Tomb" - * "Current area is adjusted for one level 1 player." - * "Current area has a 50% instance bonus." - */ - - string description = newZone.Description; - string screenDescription = description; - - if (LanguageMgr.GetTranslation(client, newZone) is DbLanguageZone translation) + catch (Exception) { - if (!string.IsNullOrEmpty(translation.Description)) - description = translation.Description; - - if (!string.IsNullOrEmpty(translation.ScreenDescription)) - screenDescription = translation.ScreenDescription; + //if we get a zone that doesn't exist, move player to their bindstone + client.Player.MoveTo((ushort) client.Player.BindRegion, + client.Player.BindXpos, + client.Player.BindYpos, + (ushort) client.Player.BindZpos, + (ushort) client.Player.BindHeading); + return; + } - client.Out.SendMessage(LanguageMgr.GetTranslation(client.Account.Language, "PlayerPositionUpdateHandler.Entered", description), eChatType.CT_System, eChatLoc.CL_SystemWindow); - client.Out.SendMessage(screenDescription, eChatType.CT_ScreenCenterSmaller, eChatLoc.CL_SystemWindow); - client.Player.LastPositionUpdateZone = newZone; + //Dinberg - Instance considerations. + //Now this gets complicated, so listen up! We have told the client a lie when it comes to the zoneID. + //As a result, every movement update, they are sending a lie back to us. Two liars could get confusing! - if (client.Player.GMStealthed) - client.Player.Stealth(true); - } + //BUT, the lie we sent has a truth to it - the geometry and layout of the zone. As a result, the zones + //x and y offsets will still actually be relevant to our current zone. And for the clones to have been + //created, there must have been a real zone to begin with, of id == instanceZone.SkinID. - int coordsPerSec = 0; - int jumpDetect = 0; - int timediff = (int) (GameLoop.GameLoopTime - client.Player.LastPositionUpdateTime); - int distance = 0; + //So, although our client is lying to us, and thinks its in another zone, that zone happens to coincide + //exactly with the zone we are instancing - and so all the positions still ring true. - if (timediff > 0) - { - Point3D newPoint = new(realX, realY, realZ); - distance = newPoint.GetDistanceTo(new Point3D((int) client.Player.LastPositionUpdatePoint.X, (int) client.Player.LastPositionUpdatePoint.Y, (int) client.Player.LastPositionUpdatePoint.Z)); - coordsPerSec = distance * 1000 / timediff; - - if (distance < 100 && client.Player.LastPositionUpdatePoint.Z > 0) - jumpDetect = realZ - (int) client.Player.LastPositionUpdatePoint.Z; - } + //Philosophically speaking, its like looking in a mirror and saying 'Am I a reflected, or reflector?' + //What it boils down to has no bearing whatsoever on the result of anything, so long as someone sitting + //outside of the unvierse knows not to listen to whether you say which you are, and knows the truth to the + //answer. Then, he need only know what you are doing ;) - if (distance > 0) - client.Player.LastPlayerActivityTime = GameLoop.GameLoopTime; + Zone newZone = WorldMgr.GetZone(zoneId); - client.Player.LastPositionUpdateTime = GameLoop.GameLoopTime; - client.Player.LastPositionUpdatePoint.X = realX; - client.Player.LastPositionUpdatePoint.Y = realY; - client.Player.LastPositionUpdatePoint.Z = realZ; - int tolerance = ServerProperties.Properties.CPS_TOLERANCE; + if (newZone == null) + { + if (client.Player==null) + return; - if (client.Player.Steed != null && client.Player.Steed.MaxSpeed > 0) - tolerance += client.Player.Steed.MaxSpeed; - else if (client.Player.MaxSpeed > 0) - tolerance += client.Player.MaxSpeed; + if (!client.Player.TempProperties.GetProperty("isbeingbanned",false)) + { + if (log.IsErrorEnabled) + log.Error($"{client.Player.Name}'s position in unknown zone! => {zoneId}"); - if (client.Player.IsJumping) - { - coordsPerSec = 0; - jumpDetect = 0; - client.Player.IsJumping = false; - } + GamePlayer player=client.Player; + player.TempProperties.SetProperty("isbeingbanned", true); + player.MoveToBind(); + } - if (client.Player.IsAllowedToFly == false && (coordsPerSec > tolerance || jumpDetect > ServerProperties.Properties.JUMP_TOLERANCE)) - { - bool isHackDetected = true; + return; // TODO: what should we do? player lost in space + } - if (coordsPerSec > tolerance) + // move to bind if player fell through the floor + if (realZ == 0) { - // check to see if CPS time tolerance is exceeded - int lastCPSTick = client.Player.TempProperties.GetProperty(LASTCPSTICK, 0); - - if (environmentTick - lastCPSTick > ServerProperties.Properties.CPS_TIME_TOLERANCE) - isHackDetected = false; + client.Player.MoveTo((ushort)client.Player.BindRegion, + client.Player.BindXpos, + client.Player.BindYpos, + (ushort)client.Player.BindZpos, + (ushort)client.Player.BindHeading); + return; } - if (isHackDetected) - { - StringBuilder builder = new(); - builder.Append("MOVEHACK_DETECT"); - builder.Append(": CharName="); - builder.Append(client.Player.Name); - builder.Append(" Account="); - builder.Append(client.Account.Name); - builder.Append(" IP="); - builder.Append(client.TcpEndpointAddress); - builder.Append(" CPS:="); - builder.Append(coordsPerSec); - builder.Append(" JT="); - builder.Append(jumpDetect); - ChatUtil.SendDebugMessage(client, builder.ToString()); + int realX = newZone.XOffset + xOffsetInZone; + int realY = newZone.YOffset + yOffsetInZone; + bool zoneChange = newZone != client.Player.LastPositionUpdateZone; - if (client.Account.PrivLevel == 1) + if (zoneChange) + { + //If the region changes -> make sure we don't take any falling damage + if (client.Player.LastPositionUpdateZone != null && newZone.ZoneRegion.ID != client.Player.LastPositionUpdateZone.ZoneRegion.ID) + client.Player.MaxLastZ = int.MinValue; + + // Update water level and diving flag for the new zone + // commenting this out for now, creates a race condition when teleporting within same region, jumping player back and forth as player xyz isnt updated yet. + //client.Out.SendPlayerPositionAndObjectID(); + zoneChange = true; + + /* + * "You have entered Burial Tomb." + * "Burial Tomb" + * "Current area is adjusted for one level 1 player." + * "Current area has a 50% instance bonus." + */ + + string description = newZone.Description; + string screenDescription = description; + + if (LanguageMgr.GetTranslation(client, newZone) is DbLanguageZone translation) { - GameServer.Instance.LogCheatAction(builder.ToString()); + if (!string.IsNullOrEmpty(translation.Description)) + description = translation.Description; - if (ServerProperties.Properties.ENABLE_MOVEDETECT) - { - if (ServerProperties.Properties.BAN_HACKERS && false) // banning disabled until this technique is proven accurate - { - DbBans b = new() - { - Author = "SERVER", - Ip = client.TcpEndpointAddress, - Account = client.Account.Name, - DateBan = DateTime.Now, - Type = "B", - Reason = string.Format("Autoban MOVEHACK:(CPS:{0}, JT:{1}) on player:{2}", coordsPerSec, jumpDetect, client.Player.Name) - }; - - GameServer.Database.AddObject(b); - GameServer.Database.SaveObject(b); - string message = "You have been auto kicked and banned due to movement hack detection!"; - - for (int i = 0; i < 8; i++) - { - client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_SystemWindow); - client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_ChatWindow); - } - - client.Out.SendPlayerQuit(true); - client.Player.SaveIntoDatabase(); - client.Player.Quit(true); - } - else - { - string message = "You have been auto kicked due to movement hack detection!"; - - for (int i = 0; i < 8; i++) - { - client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_SystemWindow); - client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_ChatWindow); - } + if (!string.IsNullOrEmpty(translation.ScreenDescription)) + screenDescription = translation.ScreenDescription; + } - client.Out.SendPlayerQuit(true); - client.Player.SaveIntoDatabase(); - client.Player.Quit(true); - } + client.Out.SendMessage(LanguageMgr.GetTranslation(client.Account.Language, "PlayerPositionUpdateHandler.Entered", description), eChatType.CT_System, eChatLoc.CL_SystemWindow); + client.Out.SendMessage(screenDescription, eChatType.CT_ScreenCenterSmaller, eChatLoc.CL_SystemWindow); + client.Player.LastPositionUpdateZone = newZone; - client.Disconnect(); - return; - } - } + if (client.Player.GMStealthed) + client.Player.Stealth(true); } - client.Player.TempProperties.SetProperty(LASTCPSTICK, environmentTick); - } - - ushort headingflag = packet.ReadShort(); - ushort flyingflag = packet.ReadShort(); - byte flags = (byte) packet.ReadByte(); + int coordsPerSec = 0; + int jumpDetect = 0; + int timediff = (int) (GameLoop.GameLoopTime - client.Player.LastPositionUpdatePacketReceivedTime); + int distance = 0; - client.Player.Heading = (ushort) (headingflag & 0xFFF); - client.Player.X = realX; - client.Player.Y = realY; - client.Player.Z = realZ; + if (timediff > 0) + { + Point3D newPoint = new(realX, realY, realZ); + distance = newPoint.GetDistanceTo(new Point3D((int) client.Player.LastPositionUpdatePoint.X, (int) client.Player.LastPositionUpdatePoint.Y, (int) client.Player.LastPositionUpdatePoint.Z)); + coordsPerSec = distance * 1000 / timediff; - // update client zone information for waterlevel and diving - if (zoneChange) - client.Out.SendPlayerPositionAndObjectID(); + if (distance < 100 && client.Player.LastPositionUpdatePoint.Z > 0) + jumpDetect = realZ - (int) client.Player.LastPositionUpdatePoint.Z; + } - // Begin ---------- New Area System ----------- - if (client.Player.CurrentRegion.Time > client.Player.AreaUpdateTick) // check if update is needed - { - IList oldAreas = client.Player.CurrentAreas; + if (distance > 0) + client.Player.LastPlayerActivityTime = GameLoop.GameLoopTime; - // Because we may be in an instance we need to do the area check from the current region - // rather than relying on the zone which is in the skinned region. - Tolakram + client.Player.LastPositionUpdatePacketReceivedTime = GameLoop.GameLoopTime; + client.Player.LastPositionUpdatePoint.X = realX; + client.Player.LastPositionUpdatePoint.Y = realY; + client.Player.LastPositionUpdatePoint.Z = realZ; + int tolerance = ServerProperties.Properties.CPS_TOLERANCE; - IList newAreas = client.Player.CurrentRegion.GetAreasOfZone(newZone, client.Player); + if (client.Player.Steed != null && client.Player.Steed.MaxSpeed > 0) + tolerance += client.Player.Steed.MaxSpeed; + else if (client.Player.MaxSpeed > 0) + tolerance += client.Player.MaxSpeed; - // Check for left areas - if (oldAreas != null) + if (client.Player.IsJumping) { - foreach (IArea area in oldAreas) - { - if (!newAreas.Contains(area)) - { - area.OnPlayerLeave(client.Player); - - //Check if leaving Border Keep areas so we can check RealmTimer - if (area is AbstractArea checkrvrarea && (checkrvrarea.Description.Equals("Castle Sauvage") || - checkrvrarea.Description.Equals("Snowdonia Fortress") || - checkrvrarea.Description.Equals("Svasud Faste") || - checkrvrarea.Description.Equals("Vindsaul Faste") || - checkrvrarea.Description.Equals("Druim Ligen") || - checkrvrarea.Description.Equals("Druim Cain"))) - { - RealmTimer.CheckRealmTimer(client.Player); - } - } - } + coordsPerSec = 0; + jumpDetect = 0; + client.Player.IsJumping = false; } - // Check for entered areas - foreach (IArea area in newAreas) + if (client.Player.IsAllowedToFly == false && (coordsPerSec > tolerance || jumpDetect > ServerProperties.Properties.JUMP_TOLERANCE)) { - if (oldAreas == null || !oldAreas.Contains(area)) - area.OnPlayerEnter(client.Player); - } + bool isHackDetected = true; - // set current areas to new one... - client.Player.CurrentAreas = newAreas; - client.Player.AreaUpdateTick = client.Player.CurrentRegion.Time + 750; // update every .75 seconds - } - // End ---------- New Area System ----------- - - client.Player.TargetInView = (flags & 0x10) != 0; - client.Player.GroundTargetInView = (flags & 0x08) != 0; - client.Player.IsTorchLighted = (flags & 0x80) != 0; - client.Player.IsDiving = (flags & 0x02) != 0x00; - //7 6 5 4 3 2 1 0 - //15 14 13 12 11 10 9 8 - // 1 1 - - long SHlastTick = client.Player.TempProperties.GetProperty(SHLASTUPDATETICK); - int SHlastFly = client.Player.TempProperties.GetProperty(SHLASTFLY); - int SHlastStatus = client.Player.TempProperties.GetProperty(SHLASTSTATUS); - int SHcount = client.Player.TempProperties.GetProperty(SHSPEEDCOUNTER); - int status = (speedData & 0x1FF ^ speedData) >> 8; - int fly = (flyingflag & 0x1FF ^ flyingflag) >> 8; - - if (client.Player.IsJumping) - SHcount = 0; - - if (SHlastTick != 0 && SHlastTick != environmentTick) - { - if ((SHlastStatus == status || (status & 0x8) == 0) && ((fly & 0x80) != 0x80) && (SHlastFly == fly || (SHlastFly & 0x10) == (fly & 0x10) || !(((SHlastFly & 0x10) == 0x10) && ((fly & 0x10) == 0x0) && (flyingflag & 0x7FF) > 0))) - { - if ((environmentTick - SHlastTick) < 400) + if (coordsPerSec > tolerance) { - SHcount++; + // check to see if CPS time tolerance is exceeded + int lastCPSTick = client.Player.TempProperties.GetProperty(LASTCPSTICK, 0); - if (SHcount > 1 && client.Account.PrivLevel > 1) - { - //Apo: ?? no idea how to name the first parameter for language translation: 1: ??, 2: {detected} ?, 3: {count} ? - client.Out.SendMessage(string.Format("SH: ({0}) detected: {1}, count {2}", 500 / (environmentTick - SHlastTick), environmentTick - SHlastTick, SHcount), eChatType.CT_Staff, eChatLoc.CL_SystemWindow); - } + if (environmentTick - lastCPSTick > ServerProperties.Properties.CPS_TIME_TOLERANCE) + isHackDetected = false; + } - if (SHcount % 5 == 0) + if (isHackDetected) + { + StringBuilder builder = new(); + builder.Append("MOVEHACK_DETECT"); + builder.Append(": CharName="); + builder.Append(client.Player.Name); + builder.Append(" Account="); + builder.Append(client.Account.Name); + builder.Append(" IP="); + builder.Append(client.TcpEndpointAddress); + builder.Append(" CPS:="); + builder.Append(coordsPerSec); + builder.Append(" JT="); + builder.Append(jumpDetect); + ChatUtil.SendDebugMessage(client, builder.ToString()); + + if (client.Account.PrivLevel == 1) { - StringBuilder builder = new(); - builder.Append("TEST_SH_DETECT["); - builder.Append(SHcount); - builder.Append("] ("); - builder.Append(environmentTick - SHlastTick); - builder.Append("): CharName="); - builder.Append(client.Player.Name); - builder.Append(" Account="); - builder.Append(client.Account.Name); - builder.Append(" IP="); - builder.Append(client.TcpEndpointAddress); GameServer.Instance.LogCheatAction(builder.ToString()); - if (client.Account.PrivLevel > 1) + if (ServerProperties.Properties.ENABLE_MOVEDETECT) { - client.Out.SendMessage("SH: Logging SH cheat.", eChatType.CT_Damaged, eChatLoc.CL_SystemWindow); - - if (SHcount >= ServerProperties.Properties.SPEEDHACK_TOLERANCE) - client.Out.SendMessage("SH: Player would have been banned!", eChatType.CT_Damaged, eChatLoc.CL_SystemWindow); - } - - if ((client.Account.PrivLevel == 1) && SHcount >= ServerProperties.Properties.SPEEDHACK_TOLERANCE) - { - if (ServerProperties.Properties.BAN_HACKERS) + if (ServerProperties.Properties.BAN_HACKERS && false) // banning disabled until this technique is proven accurate { DbBans b = new() { @@ -942,12 +604,12 @@ private static void HandlePacketPre1124(GameClient client, GSPacketIn packet) Account = client.Account.Name, DateBan = DateTime.Now, Type = "B", - Reason = string.Format("Autoban SH:({0},{1}) on player:{2}", SHcount, environmentTick - SHlastTick, client.Player.Name) + Reason = string.Format("Autoban MOVEHACK:(CPS:{0}, JT:{1}) on player:{2}", coordsPerSec, jumpDetect, client.Player.Name) }; GameServer.Database.AddObject(b); GameServer.Database.SaveObject(b); - string message = "You have been auto kicked and banned for speed hacking!"; + string message = "You have been auto kicked and banned due to movement hack detection!"; for (int i = 0; i < 8; i++) { @@ -961,7 +623,7 @@ private static void HandlePacketPre1124(GameClient client, GSPacketIn packet) } else { - string message = "You have been auto kicked for speed hacking!"; + string message = "You have been auto kicked due to movement hack detection!"; for (int i = 0; i < 8; i++) { @@ -979,72 +641,228 @@ private static void HandlePacketPre1124(GameClient client, GSPacketIn packet) } } } - else - SHcount = 0; - SHlastTick = environmentTick; + client.Player.TempProperties.SetProperty(LASTCPSTICK, environmentTick); } - } - else - SHlastTick = environmentTick; - int state = (speedData >> 10) & 7; - client.Player.IsClimbing = state == 7; - client.Player.IsSwimming = state == 1; + ushort headingflag = packet.ReadShort(); + ushort flyingflag = packet.ReadShort(); + ProcessActionFlags(client.Player, (ActionFlags) packet.ReadByte()); - // debugFly on, but player not do /debug on (hack) - if (state == 3 && !client.Player.TempProperties.GetProperty(GamePlayer.DEBUG_MODE_PROPERTY, false) && !client.Player.IsAllowedToFly) - { - StringBuilder builder = new(); - builder.Append("HACK_FLY"); - builder.Append(": CharName="); - builder.Append(client.Player.Name); - builder.Append(" Account="); - builder.Append(client.Account.Name); - builder.Append(" IP="); - builder.Append(client.TcpEndpointAddress); - - GameServer.Instance.LogCheatAction(builder.ToString()); + client.Player.Heading = headingflag; + client.Player.X = realX; + client.Player.Y = realY; + client.Player.Z = realZ; + + // update client zone information for waterlevel and diving + if (zoneChange) + client.Out.SendPlayerPositionAndObjectID(); + + // Begin ---------- New Area System ----------- + if (client.Player.CurrentRegion.Time > client.Player.AreaUpdateTick) // check if update is needed { - if (ServerProperties.Properties.BAN_HACKERS) + IList oldAreas = client.Player.CurrentAreas; + + // Because we may be in an instance we need to do the area check from the current region + // rather than relying on the zone which is in the skinned region. - Tolakram + + IList newAreas = client.Player.CurrentRegion.GetAreasOfZone(newZone, client.Player); + + // Check for left areas + if (oldAreas != null) { - DbBans b = new() + foreach (IArea area in oldAreas) { - Author = "SERVER", - Ip = client.TcpEndpointAddress, - Account = client.Account.Name, - DateBan = DateTime.Now, - Type = "B", - Reason = string.Format("Autoban flying hack: on player:{0}", client.Player.Name) - }; - - GameServer.Database.AddObject(b); - GameServer.Database.SaveObject(b); + if (!newAreas.Contains(area)) + { + area.OnPlayerLeave(client.Player); + + //Check if leaving Border Keep areas so we can check RealmTimer + if (area is AbstractArea checkrvrarea && (checkrvrarea.Description.Equals("Castle Sauvage") || + checkrvrarea.Description.Equals("Snowdonia Fortress") || + checkrvrarea.Description.Equals("Svasud Faste") || + checkrvrarea.Description.Equals("Vindsaul Faste") || + checkrvrarea.Description.Equals("Druim Ligen") || + checkrvrarea.Description.Equals("Druim Cain"))) + { + RealmTimer.CheckRealmTimer(client.Player); + } + } + } } - string message = "Client Hack Detected!"; + // Check for entered areas + foreach (IArea area in newAreas) + { + if (oldAreas == null || !oldAreas.Contains(area)) + area.OnPlayerEnter(client.Player); + } + + // set current areas to new one... + client.Player.CurrentAreas = newAreas; + client.Player.AreaUpdateTick = client.Player.CurrentRegion.Time + 750; // update every .75 seconds + } + // End ---------- New Area System ----------- - for (int i = 0; i < 6; i++) + long SHlastTick = client.Player.TempProperties.GetProperty(SHLASTUPDATETICK); + int SHlastFly = client.Player.TempProperties.GetProperty(SHLASTFLY); + int SHlastStatus = client.Player.TempProperties.GetProperty(SHLASTSTATUS); + int SHcount = client.Player.TempProperties.GetProperty(SHSPEEDCOUNTER); + int status = (speedData & 0x1FF ^ speedData) >> 8; + int fly = (flyingflag & 0x1FF ^ flyingflag) >> 8; + + if (client.Player.IsJumping) + SHcount = 0; + + if (SHlastTick != 0 && SHlastTick != environmentTick) + { + if ((SHlastStatus == status || (status & 0x8) == 0) && ((fly & 0x80) != 0x80) && (SHlastFly == fly || (SHlastFly & 0x10) == (fly & 0x10) || !(((SHlastFly & 0x10) == 0x10) && ((fly & 0x10) == 0x0) && (flyingflag & 0x7FF) > 0))) { - client.Out.SendMessage(message, eChatType.CT_System, eChatLoc.CL_SystemWindow); - client.Out.SendMessage(message, eChatType.CT_System, eChatLoc.CL_ChatWindow); + if ((environmentTick - SHlastTick) < 400) + { + SHcount++; + + if (SHcount > 1 && client.Account.PrivLevel > 1) + { + //Apo: ?? no idea how to name the first parameter for language translation: 1: ??, 2: {detected} ?, 3: {count} ? + client.Out.SendMessage(string.Format("SH: ({0}) detected: {1}, count {2}", 500 / (environmentTick - SHlastTick), environmentTick - SHlastTick, SHcount), eChatType.CT_Staff, eChatLoc.CL_SystemWindow); + } + + if (SHcount % 5 == 0) + { + StringBuilder builder = new(); + builder.Append("TEST_SH_DETECT["); + builder.Append(SHcount); + builder.Append("] ("); + builder.Append(environmentTick - SHlastTick); + builder.Append("): CharName="); + builder.Append(client.Player.Name); + builder.Append(" Account="); + builder.Append(client.Account.Name); + builder.Append(" IP="); + builder.Append(client.TcpEndpointAddress); + GameServer.Instance.LogCheatAction(builder.ToString()); + + if (client.Account.PrivLevel > 1) + { + client.Out.SendMessage("SH: Logging SH cheat.", eChatType.CT_Damaged, eChatLoc.CL_SystemWindow); + + if (SHcount >= ServerProperties.Properties.SPEEDHACK_TOLERANCE) + client.Out.SendMessage("SH: Player would have been banned!", eChatType.CT_Damaged, eChatLoc.CL_SystemWindow); + } + + if ((client.Account.PrivLevel == 1) && SHcount >= ServerProperties.Properties.SPEEDHACK_TOLERANCE) + { + if (ServerProperties.Properties.BAN_HACKERS) + { + DbBans b = new() + { + Author = "SERVER", + Ip = client.TcpEndpointAddress, + Account = client.Account.Name, + DateBan = DateTime.Now, + Type = "B", + Reason = string.Format("Autoban SH:({0},{1}) on player:{2}", SHcount, environmentTick - SHlastTick, client.Player.Name) + }; + + GameServer.Database.AddObject(b); + GameServer.Database.SaveObject(b); + string message = "You have been auto kicked and banned for speed hacking!"; + + for (int i = 0; i < 8; i++) + { + client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_SystemWindow); + client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_ChatWindow); + } + + client.Out.SendPlayerQuit(true); + client.Player.SaveIntoDatabase(); + client.Player.Quit(true); + } + else + { + string message = "You have been auto kicked for speed hacking!"; + + for (int i = 0; i < 8; i++) + { + client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_SystemWindow); + client.Out.SendMessage(message, eChatType.CT_Help, eChatLoc.CL_ChatWindow); + } + + client.Out.SendPlayerQuit(true); + client.Player.SaveIntoDatabase(); + client.Player.Quit(true); + } + + client.Disconnect(); + return; + } + } + } + else + SHcount = 0; + + SHlastTick = environmentTick; } + } + else + SHlastTick = environmentTick; - client.Out.SendPlayerQuit(true); - client.Disconnect(); - return; + int state = (speedData >> 10) & 7; + client.Player.IsClimbing = state == 7; + client.Player.IsSwimming = state == 1; + + // debugFly on, but player not do /debug on (hack) + if (state == 3 && !client.Player.TempProperties.GetProperty(GamePlayer.DEBUG_MODE_PROPERTY, false) && !client.Player.IsAllowedToFly) + { + StringBuilder builder = new(); + builder.Append("HACK_FLY"); + builder.Append(": CharName="); + builder.Append(client.Player.Name); + builder.Append(" Account="); + builder.Append(client.Account.Name); + builder.Append(" IP="); + builder.Append(client.TcpEndpointAddress); + + GameServer.Instance.LogCheatAction(builder.ToString()); + { + if (ServerProperties.Properties.BAN_HACKERS) + { + DbBans b = new() + { + Author = "SERVER", + Ip = client.TcpEndpointAddress, + Account = client.Account.Name, + DateBan = DateTime.Now, + Type = "B", + Reason = string.Format("Autoban flying hack: on player:{0}", client.Player.Name) + }; + + GameServer.Database.AddObject(b); + GameServer.Database.SaveObject(b); + } + + string message = "Client Hack Detected!"; + + for (int i = 0; i < 6; i++) + { + client.Out.SendMessage(message, eChatType.CT_System, eChatLoc.CL_SystemWindow); + client.Out.SendMessage(message, eChatType.CT_System, eChatLoc.CL_ChatWindow); + } + + client.Out.SendPlayerQuit(true); + client.Disconnect(); + return; + } } - } - SHlastFly = fly; - SHlastStatus = status; - client.Player.TempProperties.SetProperty(SHLASTUPDATETICK, SHlastTick); - client.Player.TempProperties.SetProperty(SHLASTFLY, SHlastFly); - client.Player.TempProperties.SetProperty(SHLASTSTATUS, SHlastStatus); - client.Player.TempProperties.SetProperty(SHSPEEDCOUNTER, SHcount); + SHlastFly = fly; + SHlastStatus = status; + client.Player.TempProperties.SetProperty(SHLASTUPDATETICK, SHlastTick); + client.Player.TempProperties.SetProperty(SHLASTFLY, SHlastFly); + client.Player.TempProperties.SetProperty(SHLASTSTATUS, SHlastStatus); + client.Player.TempProperties.SetProperty(SHSPEEDCOUNTER, SHcount); - lock (client.Player.LastUniqueLocations) - { GameLocation[] locations = client.Player.LastUniqueLocations; GameLocation loc = locations[0]; @@ -1059,89 +877,242 @@ private static void HandlePacketPre1124(GameClient client, GSPacketIn packet) loc.Heading = client.Player.Heading; loc.RegionID = client.Player.CurrentRegionID; } - } - //**************// - //FALLING DAMAGE// - //**************// - double fallDamage = 0; - int fallSpeed = 0; + //**************// + //FALLING DAMAGE// + //**************// + int fallSpeed; - if (GameServer.ServerRules.CanTakeFallDamage(client.Player) && !client.Player.IsSwimming) - { - int maxLastZ = client.Player.MaxLastZ; - - /* Are we on the ground? */ - if ((flyingflag >> 15) != 0) + if (GameServer.ServerRules.CanTakeFallDamage(client.Player) && !client.Player.IsSwimming) { - int safeFallLevel = client.Player.GetAbilityLevel(Abilities.SafeFall); - fallSpeed = (flyingflag & 0xFFF) - 100 * safeFallLevel; // 0x7FF fall speed and 0x800 bit = fall speed overcaped - int fallMinSpeed = 400; - int fallDivide = 6; + int maxLastZ = client.Player.MaxLastZ; - if (client.Version >= GameClient.eClientVersion.Version188) + /* Are we on the ground? */ + if ((flyingflag >> 15) != 0) { - fallMinSpeed = 500; - fallDivide = 15; - } + int safeFallLevel = client.Player.GetAbilityLevel(Abilities.SafeFall); + fallSpeed = (flyingflag & 0xFFF) - 100 * safeFallLevel; // 0x7FF fall speed and 0x800 bit = fall speed overcaped + client.Player.FallSpeed = (short) fallSpeed; + int fallMinSpeed = 400; + int fallDivide = 6; + + if (client.Version >= GameClient.eClientVersion.Version188) + { + fallMinSpeed = 500; + fallDivide = 15; + } - int fallPercent = Math.Min(99, (fallSpeed - (fallMinSpeed + 1)) / fallDivide); + int fallPercent = Math.Min(99, (fallSpeed - (fallMinSpeed + 1)) / fallDivide); + + if (fallSpeed > fallMinSpeed) + client.Player.CalcFallDamage(fallPercent); + + client.Player.MaxLastZ = client.Player.Z; + } - if (fallSpeed > fallMinSpeed) + else { - // client.Out.SendMessage(LanguageMgr.GetTranslation(client.Account.Language, "PlayerPositionUpdateHandler.FallingDamage"), - // eChatType.CT_Damaged, eChatLoc.CL_SystemWindow); - fallDamage = client.Player.CalcFallDamage(fallPercent); + // always set Z if on the ground + if (flyingflag == 0) + client.Player.MaxLastZ = client.Player.Z; + // set Z if in air and higher than old Z + else if (maxLastZ < client.Player.Z) + client.Player.MaxLastZ = client.Player.Z; } + } + //**************// + + //Riding is set here! + if (client.Player.Steed != null && client.Player.Steed.ObjectState is GameObject.eObjectState.Active) + client.Player.Heading = client.Player.Steed.Heading; - client.Player.MaxLastZ = client.Player.Z; + if ((eCharacterClass) client.Player.CharacterClass.ID is eCharacterClass.Warlock) + { + //Send Chamber effect + client.Player.Out.SendWarlockChamberEffect(client.Player); } - else + //handle closing of windows + //trade window + if (client.Player.TradeWindow != null) { - // always set Z if on the ground - if (flyingflag == 0) - client.Player.MaxLastZ = client.Player.Z; - // set Z if in air and higher than old Z - else if (maxLastZ < client.Player.Z) - client.Player.MaxLastZ = client.Player.Z; + if (client.Player.TradeWindow.Partner != null) + { + if (!client.Player.IsWithinRadius(client.Player.TradeWindow.Partner, WorldMgr.GIVE_ITEM_DISTANCE)) + client.Player.TradeWindow.CloseTrade(); + } } } - //**************// - - //Riding is set here! - if (client.Player.Steed != null && client.Player.Steed.ObjectState == GameObject.eObjectState.Active) - client.Player.Heading = client.Player.Steed.Heading; + } - BroadcastPositionPre1124(); + public static void BroadcastPosition(GameClient client) + { + if (client.Version >= GameClient.eClientVersion.Version1124) + BroadcastPositionSince1124(client); + else + BroadcastPositionPre1124(client); // Likely outdated and bugged. - if (client.Player.CharacterClass.ID == (int)eCharacterClass.Warlock) + static void BroadcastPositionSince1124(GameClient client) { - //Send Chamber effect - client.Player.Out.SendWarlockChamberEffect(client.Player); - } + GamePlayer player = client.Player; + ActionFlags actionFlags = GetActionFlagsOut(player); + StateFlags stateFlags = player.StateFlags; + byte healthByte = GetHealthByte(player); + ushort steedSeatPosition = GetSeatPosition(player); + ushort heading; + + if (player.Steed != null && player.Steed.ObjectState is GameObject.eObjectState.Active) + heading = (ushort) client.Player.Steed.ObjectID; + else + heading = player.RawHeading; - //handle closing of windows - //trade window - if (client.Player.TradeWindow != null) - { - if (client.Player.TradeWindow.Partner != null) + GSUDPPacketOut outPak1127 = null; + GSUDPPacketOut outPak1124 = null; + GSUDPPacketOut outPak1112 = null; + GSUDPPacketOut outPak190 = null; + + foreach (GamePlayer otherPlayer in player.GetPlayersInRadius(WorldMgr.VISIBILITY_DISTANCE)) + { + if (otherPlayer == player) + continue; + + if ((player.InHouse || otherPlayer.InHouse) && otherPlayer.CurrentHouse != player.CurrentHouse) + continue; + + if (!player.IsStealthed || otherPlayer.CanDetect(player)) + { + if (otherPlayer.Client.Version >= GameClient.eClientVersion.Version1127) + { + outPak1127 ??= CreateOutPak1127(); + otherPlayer.Out.SendUDP(outPak1127); + } + else if (otherPlayer.Client.Version >= GameClient.eClientVersion.Version1124) + { + outPak1124 ??= CreateOutPak1124(); + otherPlayer.Out.SendUDP(outPak1124); + } + else if (otherPlayer.Client.Version >= GameClient.eClientVersion.Version1112) + { + outPak1112 ??= CreateOutPak1112(); + otherPlayer.Out.SendUDP(outPak1112); + } + else + { + outPak190 ??= CreateOutPak190(); + otherPlayer.Out.SendUDP(outPak190); + } + } + else + otherPlayer.Out.SendObjectDelete(player); // Remove the stealthed player from view. + } + + GSUDPPacketOut CreateOutPak1127() + { + GSUDPPacketOut outPak = new(client.Out.GetPacketCode(eServerPackets.PlayerPosition)); + outPak.WriteFloatLowEndian(player.X); + outPak.WriteFloatLowEndian(player.Y); + outPak.WriteFloatLowEndian(player.Z); + outPak.WriteFloatLowEndian(player.CurrentSpeed); + outPak.WriteFloatLowEndian(player.FallSpeed); + outPak.WriteShort((ushort) client.SessionID); + outPak.WriteShort((ushort) player.ObjectID); + outPak.WriteShort(player.CurrentZone.ID); + outPak.WriteByte((byte) stateFlags); + outPak.WriteByte(0); + outPak.WriteShort(steedSeatPosition); // Fall damage flag coming in, steed seat position going out. + outPak.WriteShort(heading); + outPak.WriteByte((byte) actionFlags); + outPak.WriteByte((byte) (player.RPFlag ? 1 : 0)); + outPak.WriteByte(0); + outPak.WriteByte(healthByte); + outPak.WriteByte(player.ManaPercent); + outPak.WriteByte(player.EndurancePercent); + outPak.WriteShort(0); + return outPak; + } + + GSUDPPacketOut CreateOutPak1124() { - if (!client.Player.IsWithinRadius(client.Player.TradeWindow.Partner, WorldMgr.GIVE_ITEM_DISTANCE)) - client.Player.TradeWindow.CloseTrade(); + GSUDPPacketOut outPak = new(client.Out.GetPacketCode(eServerPackets.PlayerPosition)); + outPak.WriteFloatLowEndian(player.X); + outPak.WriteFloatLowEndian(player.Y); + outPak.WriteFloatLowEndian(player.Z); + outPak.WriteFloatLowEndian(player.CurrentSpeed); + outPak.WriteFloatLowEndian(player.FallSpeed); + outPak.WriteShort((ushort) client.SessionID); + outPak.WriteShort(player.CurrentZone.ID); + outPak.WriteByte((byte) stateFlags); + outPak.WriteByte(0); + outPak.WriteShort(steedSeatPosition); // Fall damage flag coming in, steed seat position going out. + outPak.WriteShort(heading); + outPak.WriteByte((byte) actionFlags); + outPak.WriteByte((byte) (player.RPFlag ? 1 : 0)); + outPak.WriteByte(0); + outPak.WriteByte(healthByte); + outPak.WriteByte(player.ManaPercent); + outPak.WriteByte(player.EndurancePercent); + return outPak; + } + + GSUDPPacketOut CreateOutPak1112() + { + GSUDPPacketOut outPak = new(client.Out.GetPacketCode(eServerPackets.PlayerPosition)); + outPak.WriteShort((ushort) client.SessionID); + outPak.WriteShort((ushort) (player.CurrentSpeed & 0x1FF)); + outPak.WriteShort((ushort) player.Z); + ushort xOffset = (ushort) (player.X - (player.CurrentZone?.XOffset ?? 0)); + outPak.WriteShort(xOffset); + ushort yOffset = (ushort) (player.Y - (player.CurrentZone?.YOffset ?? 0)); + outPak.WriteShort(yOffset); + outPak.WriteShort(player.CurrentZone.ID); + outPak.WriteShort(heading); + outPak.WriteShort(steedSeatPosition); + outPak.WriteByte((byte) actionFlags); + outPak.WriteByte(healthByte); + outPak.WriteByte(player.ManaPercent); + outPak.WriteByte(player.EndurancePercent); + outPak.WriteByte((byte) (player.RPFlag ? 1 : 0)); + outPak.WriteByte(0); + return outPak; + } + + GSUDPPacketOut CreateOutPak190() + { + GSUDPPacketOut outPak = new(client.Out.GetPacketCode(eServerPackets.PlayerPosition)); + outPak.WriteShort((ushort) client.SessionID); + outPak.WriteShort((ushort) (player.CurrentSpeed & 0x1FF)); + outPak.WriteShort((ushort) player.Z); + ushort xOffset = (ushort) (player.X - (player.CurrentZone?.XOffset ?? 0)); + outPak.WriteShort(xOffset); + ushort yOffset = (ushort) (player.Y - (player.CurrentZone?.YOffset ?? 0)); + outPak.WriteShort(yOffset); + outPak.WriteShort(player.CurrentZone.ID); + outPak.WriteShort(heading); + outPak.WriteShort(steedSeatPosition); + outPak.WriteByte((byte) actionFlags); + outPak.WriteByte(healthByte); + outPak.WriteByte(player.ManaPercent); + outPak.WriteByte(player.EndurancePercent); + outPak.FillString(player.CharacterClass.Name, 32); + outPak.WriteByte((byte) (player.RPFlag ? 1 : 0)); + outPak.WriteByte(0); + return outPak; } } - void BroadcastPositionPre1124() + static void BroadcastPositionPre1124(GameClient client) { + GamePlayer player = client.Player; GSUDPPacketOut outpak = new(client.Out.GetPacketCode(eServerPackets.PlayerPosition)); - outpak.WriteShort((ushort)client.SessionID); + byte healthByte = GetHealthByte(player); + ushort seatPosition = GetSeatPosition(player); + outpak.WriteShort((ushort) client.SessionID); - if (client.Player.Steed != null && client.Player.Steed.ObjectState == GameObject.eObjectState.Active) + if (player.Steed != null && player.Steed.ObjectState is GameObject.eObjectState.Active) outpak.WriteShort(0x1800); else { - int rSpeed = client.Player.IsIncapacitated ? 0 : client.Player.CurrentSpeed; + int rSpeed = player.IsIncapacitated ? 0 : player.CurrentSpeed; ushort content; if (rSpeed < 0) @@ -1149,19 +1120,19 @@ void BroadcastPositionPre1124() else content = (ushort) (rSpeed > 511 ? 511 : rSpeed); - if (!client.Player.IsAlive) + if (!player.IsAlive) content |= 5 << 10; else { ushort pState = 0; - if (client.Player.IsSwimming) + if (player.IsSwimming) pState = 1; - if (client.Player.IsClimbing) + if (player.IsClimbing) pState = 7; - if (client.Player.IsSitting) + if (player.IsSitting) pState = 4; - if (client.Player.IsStrafing) + if (player.IsStrafing) pState |= 8; content |= (ushort) (pState << 10); @@ -1170,21 +1141,21 @@ void BroadcastPositionPre1124() outpak.WriteShort(content); } - outpak.WriteShort((ushort) client.Player.Z); - outpak.WriteShort((ushort) (client.Player.X - client.Player.CurrentZone.XOffset)); - outpak.WriteShort((ushort) (client.Player.Y - client.Player.CurrentZone.YOffset)); - outpak.WriteShort(client.Player.CurrentZone.ZoneSkinID); + outpak.WriteShort((ushort) player.Z); + outpak.WriteShort((ushort) (player.X - player.CurrentZone.XOffset)); + outpak.WriteShort((ushort) (player.Y - player.CurrentZone.YOffset)); + outpak.WriteShort(player.CurrentZone.ZoneSkinID); // Copy Heading && Falling or Write Steed - if (client.Player.Steed != null && client.Player.Steed.ObjectState == GameObject.eObjectState.Active) + if (player.Steed != null && player.Steed.ObjectState is GameObject.eObjectState.Active) { - outpak.WriteShort((ushort) client.Player.Steed.ObjectID); - outpak.WriteShort((ushort) client.Player.Steed.RiderSlot(client.Player)); + outpak.WriteShort((ushort) player.Steed.ObjectID); + outpak.WriteShort(seatPosition); } else { // Set Player always on ground, this is an "anti lag" packet - ushort contentHead = (ushort) (client.Player.Heading + (true ? 0x1000 : 0)); + ushort contentHead = (ushort) (player.Heading + (true ? 0x1000 : 0)); outpak.WriteShort(contentHead); outpak.WriteShort(0); // No Fall Speed. } @@ -1192,100 +1163,173 @@ void BroadcastPositionPre1124() // Write Flags byte flagcontent = 0; - if (client.Player.IsWireframe) + if (player.IsWireframe) flagcontent |= 0x01; - if (client.Player.IsStealthed) + if (player.IsStealthed) flagcontent |= 0x02; - if (client.Player.IsDiving) + if (player.IsDiving) flagcontent |= 0x04; - if (client.Player.IsTorchLighted) + if (player.IsTorchLighted) flagcontent |= 0x80; outpak.WriteByte(flagcontent); - outpak.WriteByte((byte)(client.Player.HealthPercent + (client.Player.attackComponent.AttackState ? 0x80 : 0))); - outpak.WriteByte(client.Player.ManaPercent); - outpak.WriteByte(client.Player.EndurancePercent); + outpak.WriteByte(healthByte); + outpak.WriteByte(player.ManaPercent); + outpak.WriteByte(player.EndurancePercent); byte[] outpakArr = outpak.ToArray(); GSUDPPacketOut outpak190 = new(client.Out.GetPacketCode(eServerPackets.PlayerPosition)); outpak190.Write(outpakArr, 5, outpakArr.Length - 5); - outpak190.FillString(client.Player.CharacterClass.Name, 32); - outpak190.WriteByte((byte)(client.Player.RPFlag ? 1 : 0)); // roleplay flag, if == 1, show name (RP) with gray color + outpak190.FillString(player.CharacterClass.Name, 32); + outpak190.WriteByte((byte)(player.RPFlag ? 1 : 0)); // roleplay flag, if == 1, show name (RP) with gray color outpak190.WriteByte(0); // send last byte for 190+ packets GSUDPPacketOut outpak1112 = new(client.Out.GetPacketCode(eServerPackets.PlayerPosition)); outpak1112.Write(outpakArr, 5, outpakArr.Length - 5); - outpak1112.WriteByte((byte) (client.Player.RPFlag ? 1 : 0)); + outpak1112.WriteByte((byte) (player.RPFlag ? 1 : 0)); outpak1112.WriteByte(0); //outpak1112.WriteByte((con168.Length == 22) ? con168[21] : (byte)0); GSUDPPacketOut outpak1124 = new(client.Out.GetPacketCode(eServerPackets.PlayerPosition)); - byte playerAction = 0x00; - - if (client.Player.IsDiving) - playerAction |= 0x04; - if (client.Player.TargetInView) - playerAction |= 0x30; - if (client.Player.GroundTargetInView) - playerAction |= 0x08; - if (client.Player.IsTorchLighted) - playerAction |= 0x80; - if (client.Player.IsStealthed) - playerAction |= 0x02; - - ushort playerState = 0; - outpak1124.WriteFloatLowEndian(client.Player.X); - outpak1124.WriteFloatLowEndian(client.Player.Y); - outpak1124.WriteFloatLowEndian(client.Player.Z); - outpak1124.WriteFloatLowEndian(client.Player.CurrentSpeed); - outpak1124.WriteFloatLowEndian(fallSpeed); + outpak1124.WriteFloatLowEndian(player.X); + outpak1124.WriteFloatLowEndian(player.Y); + outpak1124.WriteFloatLowEndian(player.Z); + outpak1124.WriteFloatLowEndian(player.CurrentSpeed); + outpak1124.WriteFloatLowEndian(player.FallSpeed); outpak1124.WriteShort((ushort) client.SessionID); - outpak1124.WriteShort(zoneId); - outpak1124.WriteShort(playerState); - outpak1124.WriteShort((ushort) (client.Player.Steed?.RiderSlot(client.Player) ?? 0)); // fall damage flag coming in, steed seat position going out - outpak1124.WriteShort(client.Player.Heading); - outpak1124.WriteByte(playerAction); - outpak1124.WriteByte((byte) (client.Player.RPFlag ? 1 : 0)); + outpak1124.WriteShort(player.CurrentZone.ID); + outpak1124.WriteShort(0); // Missing. + outpak1124.WriteShort(seatPosition); // fall damage flag coming in, steed seat position going out + outpak1124.WriteShort(player.RawHeading); + outpak1124.WriteByte((byte) GetActionFlagsOut(player)); + outpak1124.WriteByte((byte) (player.RPFlag ? 1 : 0)); outpak1124.WriteByte(0); - outpak1124.WriteByte((byte) (client.Player.HealthPercent + (client.Player.attackComponent.AttackState ? 0x80 : 0))); - outpak1124.WriteByte(client.Player.ManaPercent); - outpak1124.WriteByte(client.Player.EndurancePercent); + outpak1124.WriteByte(healthByte); + outpak1124.WriteByte(player.ManaPercent); + outpak1124.WriteByte(player.EndurancePercent); - foreach (GamePlayer player in client.Player.GetPlayersInRadius(WorldMgr.VISIBILITY_DISTANCE)) + foreach (GamePlayer otherPlayer in player.GetPlayersInRadius(WorldMgr.VISIBILITY_DISTANCE)) { - if (player == null || player == client.Player) + if (otherPlayer == player) continue; - if ((client.Player.InHouse || player.InHouse) && player.CurrentHouse != client.Player.CurrentHouse) + if ((player.InHouse || otherPlayer.InHouse) && otherPlayer.CurrentHouse != player.CurrentHouse) continue; - if (client.Player.MinotaurRelic != null) + if (player.MinotaurRelic != null) { - MinotaurRelic relic = client.Player.MinotaurRelic; - if (!relic.Playerlist.Contains(player) && player != client.Player) + MinotaurRelic relic = player.MinotaurRelic; + if (!relic.Playerlist.Contains(otherPlayer) && otherPlayer != player) { - relic.Playerlist.Add(player); - player.Out.SendMinotaurRelicWindow(client.Player, client.Player.MinotaurRelic.Effect, true); + relic.Playerlist.Add(otherPlayer); + otherPlayer.Out.SendMinotaurRelicWindow(player, player.MinotaurRelic.Effect, true); } } - if (!client.Player.IsStealthed || player.CanDetect(client.Player)) + if (!player.IsStealthed || otherPlayer.CanDetect(player)) { //forward the position packet like normal! - if (player.Client.Version >= GameClient.eClientVersion.Version1124) - player.Out.SendUDP(outpak1124); - else if (player.Client.Version >= GameClient.eClientVersion.Version1112) - player.Out.SendUDP(outpak1112); - else if (player.Client.Version >= GameClient.eClientVersion.Version190) - player.Out.SendUDP(outpak190); + if (otherPlayer.Client.Version >= GameClient.eClientVersion.Version1124) + otherPlayer.Out.SendUDP(outpak1124); + else if (otherPlayer.Client.Version >= GameClient.eClientVersion.Version1112) + otherPlayer.Out.SendUDP(outpak1112); + else if (otherPlayer.Client.Version >= GameClient.eClientVersion.Version190) + otherPlayer.Out.SendUDP(outpak190); } else - player.Out.SendObjectDelete(client.Player); //remove the stealthed player from view + otherPlayer.Out.SendObjectDelete(player); //remove the stealthed player from view } } } + public static byte GetHealthByte(GamePlayer player) + { + return (byte) (player.HealthPercent + (player.attackComponent.AttackState ? 0x80 : 0)); + } + + public static ushort GetSeatPosition(GamePlayer player) + { + return (ushort) (player.Steed is null ? 0 : player.Steed.RiderSlot(player)); + } + + public static bool ProcessStateFlags(GamePlayer player, StateFlags stateFlags) + { + player.IsStrafing = (stateFlags & StateFlags.STRAFING_ANY) != 0; + player.IsClimbing = (stateFlags & StateFlags.CLIMBING) is StateFlags.CLIMBING; + + // CLIMBING combines SITTING, JUMPING, SWIMMING and is always allowed. + // Other combinations can cause issues. + if (!player.IsClimbing) + { + // This turns the player invisible if it isn't riding. + if ((stateFlags & StateFlags.RIDING) is StateFlags.RIDING && !player.IsRiding) + { + if (ServerProperties.Properties.BAN_HACKERS) + { + player.Client.BanAccount($"Autoban forged position update packet ({nameof(stateFlags)}: {StateFlags.RIDING})"); + player.Out.SendPlayerQuit(true); + player.Client.Disconnect(); + return false; + } + + stateFlags &= ~StateFlags.RIDING; + } + + // Sitting and swimming (death animation). Don't allow players to play dead. + // Clients that just got resurrected but aren't aware yet also send this. + if ((stateFlags & StateFlags.DEAD) is StateFlags.DEAD && player.HealthPercent > 0) + stateFlags &= ~StateFlags.DEAD; + + // If the client has flying enabled but the debug option wasn't enabled. + if ((stateFlags & StateFlags.FLYING) is StateFlags.FLYING && !player.TempProperties.GetProperty(GamePlayer.DEBUG_MODE_PROPERTY, false) && !player.IsAllowedToFly) + { + if (ServerProperties.Properties.BAN_HACKERS) + { + player.Client.BanAccount($"Autoban forged position update packet ({nameof(stateFlags)}: {StateFlags.FLYING})"); + player.Out.SendPlayerQuit(true); + player.Client.Disconnect(); + return false; + } + + stateFlags &= ~StateFlags.FLYING; + } + } + + player.StateFlags = stateFlags; + return true; + } + + public static void ProcessActionFlags(GamePlayer player, ActionFlags actionFlags) + { + // Don't trust the client to set it to true. We rely on that to detect move hacks. + if ((actionFlags & ActionFlags.TELEPORT) == 0) + player.IsJumping = false; + + player.TargetInView = (actionFlags & ActionFlags.TARGET_IN_VIEW) is ActionFlags.TARGET_IN_VIEW; + player.GroundTargetInView = (actionFlags & ActionFlags.GROUNT_TARGET_IN_VIEW) != 0; + player.IsTorchLighted = (actionFlags & ActionFlags.TORCH) != 0; + player.IsDiving = (actionFlags & ActionFlags.DIVING_IN__STEALTHED_OUT) != 0; + player.ActionFlags = actionFlags; + } + + public static ActionFlags GetActionFlagsOut(GamePlayer player) + { + ActionFlags actionFlags = player.ActionFlags; + + if (player.IsStealthed) + actionFlags |= ActionFlags.DIVING_IN__STEALTHED_OUT; + else + actionFlags &= ~ActionFlags.DIVING_IN__STEALTHED_OUT; + + if (player.IsDiving) + actionFlags |= ActionFlags.PET_IN_VIEW_IN__DIVING_OUT; + else + actionFlags &= ~ActionFlags.PET_IN_VIEW_IN__DIVING_OUT; + + return actionFlags; + } + [Flags] - private enum State : byte + public enum StateFlags : byte { STRAFING_RIGHT = 1 << 7, STRAFING_LEFT = 1 << 6, @@ -1301,13 +1345,15 @@ private enum State : byte } [Flags] - private enum Action : byte + public enum ActionFlags : byte { - TORCH = 1 << 7, - TELEPORT = 1 << 6, - TARGET_IN_VIEW = (1 << 5) | (1 << 4), - GROUNT_TARGET_IN_VIEW = 1 << 3, - DIVING = 1 << 1 + TORCH = 1 << 7, + TELEPORT = 1 << 6, + TARGET_IN_VIEW = (1 << 5) | (1 << 4), + GROUNT_TARGET_IN_VIEW = 1 << 3, + PET_IN_VIEW_IN__DIVING_OUT = 1 << 2, + DIVING_IN__STEALTHED_OUT = 1 << 1, + WIREFRAME = 1 } } } diff --git a/GameServer/packets/Client/168/UseSpellHandler.cs b/GameServer/packets/Client/168/UseSpellHandler.cs index 62ecd86bb4..c1d7ad9c13 100644 --- a/GameServer/packets/Client/168/UseSpellHandler.cs +++ b/GameServer/packets/Client/168/UseSpellHandler.cs @@ -37,7 +37,7 @@ public void HandlePacket(GameClient client, GSPacketIn packet) else { flagSpeedData = packet.ReadShort(); - int heading = packet.ReadShort(); + ushort heading = packet.ReadShort(); if (client.Version > GameClient.eClientVersion.Version171) { @@ -62,7 +62,7 @@ public void HandlePacket(GameClient client, GSPacketIn packet) spellLevel = packet.ReadByte(); spellLineIndex = packet.ReadByte(); - client.Player.Heading = (ushort)(heading & 0xfff); + client.Player.Heading = heading; } GamePlayer player = client.Player; diff --git a/GameServer/packets/Server/PacketLib168.cs b/GameServer/packets/Server/PacketLib168.cs index 97c26151bf..32042f25e6 100644 --- a/GameServer/packets/Server/PacketLib168.cs +++ b/GameServer/packets/Server/PacketLib168.cs @@ -781,7 +781,7 @@ public virtual void SendObjectUpdate(GameObject obj) } speed |= (ushort) ((zSpeed & 0x70) << 8); - heading = (ushort) (((zSpeed & 0xF) << 12) | (npc.Heading & 0xFFF)); + heading = (ushort) (((zSpeed & 0xF) << 12) | npc.Heading); if (npc.IsDestinationValid) {