1
0

GunPredictionSystem.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. using Content.Server.Movement.Components;
  2. using Content.Server.Weapons.Ranged.Systems;
  3. using Content.Shared._RMC14.CCVar;
  4. using Content.Shared._RMC14.Weapons.Ranged.Prediction;
  5. using Content.Shared.GameTicking;
  6. using Content.Shared.Projectiles;
  7. using Content.Shared.Weapons.Ranged.Events;
  8. using Robust.Server.GameObjects;
  9. using Robust.Shared.Configuration;
  10. using Robust.Shared.Map;
  11. using Robust.Shared.Physics;
  12. using Robust.Shared.Physics.Components;
  13. using Robust.Shared.Physics.Events;
  14. using Robust.Shared.Physics.Systems;
  15. using Robust.Shared.Player;
  16. using Robust.Shared.Timing;
  17. namespace Content.Server._RMC14.Weapons.Ranged.Prediction;
  18. public sealed class GunPredictionSystem : SharedGunPredictionSystem
  19. {
  20. [Dependency] private readonly IConfigurationManager _config = default!;
  21. [Dependency] private readonly GunSystem _gun = default!;
  22. [Dependency] private readonly SharedPhysicsSystem _physics = default!;
  23. [Dependency] private readonly SharedProjectileSystem _projectile = default!;
  24. [Dependency] private readonly IGameTiming _timing = default!;
  25. [Dependency] private readonly TransformSystem _transform = default!;
  26. private readonly Dictionary<(Guid, int), EntityUid> _predicted = new();
  27. private readonly List<(PredictedProjectileHitEvent Event, ICommonSession Player)> _predictedHits = new();
  28. private bool _preventCollision;
  29. private bool _logHits;
  30. private float _coordinateDeviation;
  31. private float _lowestCoordinateDeviation;
  32. private float _aabbEnlargement;
  33. private EntityQuery<FixturesComponent> _fixturesQuery;
  34. private EntityQuery<LagCompensationComponent> _lagCompensationQuery;
  35. private EntityQuery<PhysicsComponent> _physicsQuery;
  36. private EntityQuery<ProjectileComponent> _projectileQuery;
  37. private EntityQuery<PredictedProjectileServerComponent> _predictedProjectileServerQuery;
  38. private EntityQuery<TransformComponent> _transformQuery;
  39. public override void Initialize()
  40. {
  41. base.Initialize();
  42. _fixturesQuery = GetEntityQuery<FixturesComponent>();
  43. _lagCompensationQuery = GetEntityQuery<LagCompensationComponent>();
  44. _physicsQuery = GetEntityQuery<PhysicsComponent>();
  45. _projectileQuery = GetEntityQuery<ProjectileComponent>();
  46. _predictedProjectileServerQuery = GetEntityQuery<PredictedProjectileServerComponent>();
  47. _transformQuery = GetEntityQuery<TransformComponent>();
  48. SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestartCleanup);
  49. SubscribeNetworkEvent<RequestShootEvent>(OnShootRequest);
  50. SubscribeNetworkEvent<PredictedProjectileHitEvent>(OnPredictedProjectileHit);
  51. SubscribeLocalEvent<PredictedProjectileServerComponent, MapInitEvent>(OnPredictedMapInit);
  52. SubscribeLocalEvent<PredictedProjectileServerComponent, ComponentRemove>(OnPredictedRemove);
  53. SubscribeLocalEvent<PredictedProjectileServerComponent, EntityTerminatingEvent>(OnPredictedRemove);
  54. SubscribeLocalEvent<PredictedProjectileServerComponent, PreventCollideEvent>(OnPredictedPreventCollide);
  55. Subs.CVar(_config, RMCCVars.RMCGunPredictionPreventCollision, v => _preventCollision = v, true);
  56. Subs.CVar(_config, RMCCVars.RMCGunPredictionLogHits, v => _logHits = v, true);
  57. Subs.CVar(_config, RMCCVars.RMCGunPredictionCoordinateDeviation, v => _coordinateDeviation = v, true);
  58. Subs.CVar(_config, RMCCVars.RMCGunPredictionLowestCoordinateDeviation, v => _lowestCoordinateDeviation = v, true);
  59. Subs.CVar(_config, RMCCVars.RMCGunPredictionAabbEnlargement, v => _aabbEnlargement = v, true);
  60. }
  61. private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev)
  62. {
  63. _predicted.Clear();
  64. }
  65. private void OnShootRequest(RequestShootEvent ev, EntitySessionEventArgs args)
  66. {
  67. _gun.ShootRequested(ev.Gun, ev.Coordinates, ev.Target, ev.Shot, args.SenderSession);
  68. }
  69. private void OnPredictedMapInit(Entity<PredictedProjectileServerComponent> ent, ref MapInitEvent args)
  70. {
  71. if (ent.Comp.Shooter == null)
  72. {
  73. Log.Warning($"{nameof(PredictedProjectileServerComponent)} map initialized with a null shooter session!");
  74. return;
  75. }
  76. _predicted[(ent.Comp.Shooter.UserId, ent.Comp.ClientId)] = ent;
  77. }
  78. private void OnPredictedRemove<T>(Entity<PredictedProjectileServerComponent> ent, ref T args)
  79. {
  80. if (ent.Comp.Shooter == null)
  81. return;
  82. _predicted.Remove((ent.Comp.Shooter.UserId, ent.Comp.ClientId));
  83. }
  84. private void OnPredictedProjectileHit(PredictedProjectileHitEvent ev, EntitySessionEventArgs args)
  85. {
  86. _predictedHits.Add((ev, args.SenderSession));
  87. }
  88. private void OnPredictedPreventCollide(Entity<PredictedProjectileServerComponent> ent, ref PreventCollideEvent args)
  89. {
  90. if (!_preventCollision)
  91. return;
  92. if (args.Cancelled)
  93. return;
  94. var other = args.OtherEntity;
  95. if (!_lagCompensationQuery.TryComp(other, out var otherLagComp) ||
  96. !_fixturesQuery.TryComp(other, out var otherFixtures) ||
  97. !_transformQuery.TryComp(other, out var otherTransform))
  98. {
  99. return;
  100. }
  101. if (!_physicsQuery.TryComp(ent, out var entPhysics))
  102. return;
  103. if (!Collides(
  104. (ent, ent, entPhysics),
  105. (other, otherLagComp, otherFixtures, args.OtherBody, otherTransform),
  106. null))
  107. {
  108. args.Cancelled = true;
  109. }
  110. }
  111. private bool Collides(
  112. Entity<PredictedProjectileServerComponent, PhysicsComponent> projectile,
  113. Entity<LagCompensationComponent, FixturesComponent, PhysicsComponent, TransformComponent> other,
  114. MapCoordinates? clientCoordinates)
  115. {
  116. var projectileCoordinates = _transform.GetMapCoordinates(projectile);
  117. var projectilePosition = projectileCoordinates.Position;
  118. MapCoordinates lowestCoordinate = default;
  119. var otherCoordinates = EntityCoordinates.Invalid;
  120. var ping = projectile.Comp1.Shooter?.Channel.Ping ?? 0;
  121. // Use 1.5 due to the trip buffer.
  122. var sentTime = _timing.CurTime - TimeSpan.FromMilliseconds(ping * 1.5);
  123. var pingTime = TimeSpan.FromMilliseconds(ping);
  124. foreach (var pos in other.Comp1.Positions)
  125. {
  126. otherCoordinates = pos.Item2;
  127. if (pos.Item1 >= sentTime)
  128. break;
  129. else if (lowestCoordinate == default && pos.Item1 >= sentTime - pingTime)
  130. lowestCoordinate = _transform.ToMapCoordinates(pos.Item2);
  131. }
  132. var otherMapCoordinates = otherCoordinates == default
  133. ? _transform.GetMapCoordinates(other)
  134. : _transform.ToMapCoordinates(otherCoordinates);
  135. if (clientCoordinates != null &&
  136. (clientCoordinates.Value.InRange(otherMapCoordinates, _coordinateDeviation) ||
  137. clientCoordinates.Value.InRange(lowestCoordinate, _lowestCoordinateDeviation)))
  138. {
  139. otherMapCoordinates = clientCoordinates.Value;
  140. }
  141. var transform = new Transform(otherMapCoordinates.Position, 0);
  142. var bounds = new Box2(transform.Position, transform.Position);
  143. foreach (var fixture in other.Comp2.Fixtures.Values)
  144. {
  145. if ((fixture.CollisionLayer & projectile.Comp2.CollisionMask) == 0)
  146. continue;
  147. for (var i = 0; i < fixture.Shape.ChildCount; i++)
  148. {
  149. var boundy = fixture.Shape.ComputeAABB(transform, i);
  150. bounds = bounds.Union(boundy);
  151. }
  152. }
  153. bounds = bounds.Enlarged(_aabbEnlargement);
  154. if (bounds.Contains(projectilePosition))
  155. return true;
  156. var projectileVelocity = _physics.GetLinearVelocity(projectile, projectile.Comp2.LocalCenter);
  157. projectilePosition = projectileCoordinates.Position + projectileVelocity / _timing.TickRate / 1.5f;
  158. if (bounds.Contains(projectilePosition))
  159. return true;
  160. return false;
  161. }
  162. private void ProcessPredictedHit(PredictedProjectileHitEvent ev, ICommonSession player)
  163. {
  164. if (!_predicted.TryGetValue((player.UserId, ev.Projectile), out var projectile))
  165. return;
  166. if (!_predictedProjectileServerQuery.TryComp(projectile, out var predictedProjectile) ||
  167. predictedProjectile.Hit)
  168. {
  169. return;
  170. }
  171. if (predictedProjectile.Shooter?.UserId != player.UserId.UserId)
  172. return;
  173. if (!_projectileQuery.TryComp(projectile, out var projectileComp) ||
  174. !_physicsQuery.TryComp(projectile, out var projectilePhysics))
  175. {
  176. return;
  177. }
  178. predictedProjectile.Hit = true;
  179. foreach (var (netEnt, clientPos) in ev.Hit)
  180. {
  181. if (GetEntity(netEnt) is not { Valid: true } hit)
  182. continue;
  183. if (!_lagCompensationQuery.TryComp(hit, out var otherLagComp) ||
  184. !_fixturesQuery.TryComp(hit, out var otherFixtures) ||
  185. !_physicsQuery.TryComp(hit, out var otherPhysics) ||
  186. !_transformQuery.TryComp(hit, out var otherTransform))
  187. {
  188. continue;
  189. }
  190. if (!Collides(
  191. (projectile, predictedProjectile, projectilePhysics),
  192. (hit, otherLagComp, otherFixtures, otherPhysics, otherTransform),
  193. clientPos))
  194. {
  195. if (_logHits)
  196. Log.Info("missed");
  197. continue;
  198. }
  199. if (_logHits)
  200. Log.Info("hit");
  201. _projectile.ProjectileCollide((projectile, projectileComp, projectilePhysics), hit, true);
  202. }
  203. }
  204. public override void Update(float frameTime)
  205. {
  206. try
  207. {
  208. foreach (var ev in _predictedHits)
  209. {
  210. ProcessPredictedHit(ev.Event, ev.Player);
  211. }
  212. }
  213. finally
  214. {
  215. _predictedHits.Clear();
  216. }
  217. var predicted = EntityQueryEnumerator<PredictedProjectileHitComponent, TransformComponent>();
  218. while (predicted.MoveNext(out var uid, out var hit, out var xform))
  219. {
  220. var origin = hit.Origin;
  221. var coordinates = xform.Coordinates;
  222. if (!origin.TryDistance(EntityManager, _transform, coordinates, out var distance) ||
  223. distance >= hit.Distance)
  224. {
  225. QueueDel(uid);
  226. }
  227. }
  228. }
  229. }