Skip to content

Commit

Permalink
PlayerPositionUpdateHandler, PlayerHeadingUpdateHandler: Rework t…
Browse files Browse the repository at this point in the history
…o no longer broadcast immediately

* Prevents a potential abuse where a client could send hundreds of forged update packets every second, flooding other clients.
* Created `PlayerMovementComponent` for this purpose. The last received packet will be broadcasted from there. Called by `ClientService`.
* This may also eventually give the option to reduce the broadcast rate in some areas if the game loop can't keep up.
* Made `PlayerMovementComponent` check for "soft" LD (when a client stops sending position update packets) instead of the client service directly.
* Fixes soft LD sometimes triggering again too soon after being canceled.
* Fixes soft LD triggering during zoning, and kicking the player out if it takes more than ~65 seconds.
* Removed LD chat message for soft LD so as to not confuse other players.
* Various changes to the way “action” and “state” flags are handled.
  • Loading branch information
bm01 committed Aug 1, 2024
1 parent b35b04a commit 80a845b
Show file tree
Hide file tree
Showing 20 changed files with 1,266 additions and 1,181 deletions.
12 changes: 6 additions & 6 deletions GameServer/ECS-Components/Actions/AttackAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions GameServer/ECS-Components/Actions/NpcAttackAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
4 changes: 2 additions & 2 deletions GameServer/ECS-Components/Actions/PlayerAttackAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 6 additions & 4 deletions GameServer/ECS-Components/MovementComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 3 additions & 3 deletions GameServer/ECS-Components/NpcMovementComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
85 changes: 85 additions & 0 deletions GameServer/ECS-Components/PlayerMovementComponent.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
19 changes: 4 additions & 15 deletions GameServer/ECS-Services/ClientService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<GameClient> _clients = new();
Expand Down Expand Up @@ -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))
{
Expand Down Expand Up @@ -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})");
Expand Down
4 changes: 1 addition & 3 deletions GameServer/GameClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -304,8 +304,6 @@ public eClientState ClientState
/// </summary>
public long LinkDeathTime { get; set; }

public GSPacketIn LastPositionUpdatePacketReceived { get; set; }

/// <summary>
/// Variable is false if account/player is Ban, for a wrong password, if server is closed etc ...
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion GameServer/gameobjects/GameDoor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
24 changes: 1 addition & 23 deletions GameServer/gameobjects/GameGravestone.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions GameServer/gameobjects/GameNPC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
15 changes: 8 additions & 7 deletions GameServer/gameobjects/GameObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -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.

/// <summary>
/// Returns the angle towards a target spot in degrees, clockwise
Expand All @@ -102,12 +103,12 @@ public virtual ushort Heading
/// <returns>the angle towards the spot</returns>
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;
}

/// <summary>
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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();
}
Expand Down
Loading

0 comments on commit 80a845b

Please sign in to comment.