1
0

SpecialRespawnSystem.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. using Content.Server.Administration.Logs;
  2. using Content.Server.Atmos.EntitySystems;
  3. using Content.Server.Chat.Managers;
  4. using Content.Server.GameTicking;
  5. using Content.Shared.Database;
  6. using Content.Shared.Maps;
  7. using Content.Shared.Physics;
  8. using Content.Shared.Respawn;
  9. using Content.Shared.Station.Components;
  10. using Robust.Shared.Map;
  11. using Robust.Shared.Map.Components;
  12. using Robust.Shared.Prototypes;
  13. using Robust.Shared.Random;
  14. namespace Content.Server.Respawn;
  15. public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem
  16. {
  17. [Dependency] private readonly IAdminLogManager _adminLog = default!;
  18. [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
  19. [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
  20. [Dependency] private readonly IRobustRandom _random = default!;
  21. [Dependency] private readonly SharedTransformSystem _transform = default!;
  22. [Dependency] private readonly SharedMapSystem _map = default!;
  23. [Dependency] private readonly TurfSystem _turf = default!;
  24. [Dependency] private readonly IChatManager _chat = default!;
  25. [Dependency] private readonly IPrototypeManager _proto = default!;
  26. public override void Initialize()
  27. {
  28. base.Initialize();
  29. SubscribeLocalEvent<GameRunLevelChangedEvent>(OnRunLevelChanged);
  30. SubscribeLocalEvent<SpecialRespawnSetupEvent>(OnSpecialRespawnSetup);
  31. SubscribeLocalEvent<SpecialRespawnComponent, ComponentStartup>(OnStartup);
  32. SubscribeLocalEvent<SpecialRespawnComponent, EntityTerminatingEvent>(OnTermination);
  33. }
  34. private void OnRunLevelChanged(GameRunLevelChangedEvent ev)
  35. {
  36. //Try to compensate for restartroundnow command
  37. if (ev.Old == GameRunLevel.InRound && ev.New == GameRunLevel.PreRoundLobby)
  38. OnRoundEnd();
  39. switch (ev.New)
  40. {
  41. case GameRunLevel.PostRound:
  42. OnRoundEnd();
  43. break;
  44. }
  45. }
  46. private void OnSpecialRespawnSetup(SpecialRespawnSetupEvent ev)
  47. {
  48. if (!TryComp<SpecialRespawnComponent>(ev.Entity, out var comp))
  49. return;
  50. var xform = Transform(ev.Entity);
  51. if (xform.GridUid != null)
  52. comp.StationMap = (xform.MapUid, xform.GridUid);
  53. }
  54. private void OnRoundEnd()
  55. {
  56. var specialRespawnQuery = EntityQuery<SpecialRespawnComponent>();
  57. //Turn respawning off so the entity doesn't respawn during reset
  58. foreach (var entity in specialRespawnQuery)
  59. {
  60. entity.Respawn = false;
  61. }
  62. }
  63. private void OnStartup(EntityUid uid, SpecialRespawnComponent component, ComponentStartup args)
  64. {
  65. var ev = new SpecialRespawnSetupEvent(uid);
  66. QueueLocalEvent(ev);
  67. }
  68. private void OnTermination(EntityUid uid, SpecialRespawnComponent component, ref EntityTerminatingEvent args)
  69. {
  70. var entityMapUid = component.StationMap.Item1;
  71. var entityGridUid = component.StationMap.Item2;
  72. if (!component.Respawn || !HasComp<StationMemberComponent>(entityGridUid) || entityMapUid == null)
  73. return;
  74. if (!TryComp<MapGridComponent>(entityGridUid, out var grid) || MetaData(entityGridUid.Value).EntityLifeStage >= EntityLifeStage.Terminating)
  75. return;
  76. //Invalid prototype
  77. if (!_proto.HasIndex(component.Prototype))
  78. return;
  79. if (TryFindRandomTile(entityGridUid.Value, entityMapUid.Value, 10, out var coords))
  80. Respawn(uid, component.Prototype, coords);
  81. //If the above fails, spawn at the center of the grid on the station
  82. else
  83. {
  84. var xform = Transform(entityGridUid.Value);
  85. var pos = xform.Coordinates;
  86. var mapPos = _transform.GetMapCoordinates(entityGridUid.Value, xform: xform);
  87. var circle = new Circle(mapPos.Position, 2);
  88. var found = false;
  89. foreach (var tile in _map.GetTilesIntersecting(entityGridUid.Value, grid, circle))
  90. {
  91. if (tile.IsSpace(_tileDefinitionManager)
  92. || _turf.IsTileBlocked(tile, CollisionGroup.MobMask)
  93. || !_atmosphere.IsTileMixtureProbablySafe(entityGridUid, entityMapUid.Value,
  94. grid.TileIndicesFor(mapPos)))
  95. {
  96. continue;
  97. }
  98. pos = _turf.GetTileCenter(tile);
  99. found = true;
  100. if (found)
  101. break;
  102. }
  103. Respawn(uid, component.Prototype, pos);
  104. }
  105. }
  106. /// <summary>
  107. /// Respawn the entity and log it.
  108. /// </summary>
  109. /// <param name="oldEntity">The entity being deleted</param>
  110. /// <param name="prototype">The prototype being spawned</param>
  111. /// <param name="coords">The place where it will be spawned</param>
  112. private void Respawn(EntityUid oldEntity, string prototype, EntityCoordinates coords)
  113. {
  114. var entity = Spawn(prototype, coords);
  115. _adminLog.Add(LogType.Respawn, LogImpact.High, $"{ToPrettyString(oldEntity)} was deleted and was respawned at {coords.ToMap(EntityManager, _transform)} as {ToPrettyString(entity)}");
  116. _chat.SendAdminAlert($"{MetaData(oldEntity).EntityName} was deleted and was respawned as {ToPrettyString(entity)}");
  117. }
  118. /// <summary>
  119. /// Try to find a random safe tile on the supplied grid
  120. /// </summary>
  121. /// <param name="targetGrid">The grid that you're looking for a safe tile on</param>
  122. /// <param name="targetMap">The map that you're looking for a safe tile on</param>
  123. /// <param name="maxAttempts">The maximum amount of attempts it should try before it gives up</param>
  124. /// <param name="targetCoords">If successful, the coordinates of the safe tile</param>
  125. /// <returns></returns>
  126. public bool TryFindRandomTile(EntityUid targetGrid, EntityUid targetMap, int maxAttempts, out EntityCoordinates targetCoords)
  127. {
  128. targetCoords = EntityCoordinates.Invalid;
  129. if (!TryComp<MapGridComponent>(targetGrid, out var grid))
  130. return false;
  131. var xform = Transform(targetGrid);
  132. if (!grid.TryGetTileRef(xform.Coordinates, out var tileRef))
  133. return false;
  134. var tile = tileRef.GridIndices;
  135. var found = false;
  136. var (gridPos, _, gridMatrix) = _transform.GetWorldPositionRotationMatrix(xform);
  137. var gridBounds = gridMatrix.TransformBox(grid.LocalAABB);
  138. //Obviously don't put anything ridiculous in here
  139. for (var i = 0; i < maxAttempts; i++)
  140. {
  141. var randomX = _random.Next((int) gridBounds.Left, (int) gridBounds.Right);
  142. var randomY = _random.Next((int) gridBounds.Bottom, (int) gridBounds.Top);
  143. tile = new Vector2i(randomX - (int) gridPos.X, randomY - (int) gridPos.Y);
  144. var mapPos = grid.GridTileToWorldPos(tile);
  145. var mapTarget = grid.WorldToTile(mapPos);
  146. var circle = new Circle(mapPos, 2);
  147. foreach (var newTileRef in _map.GetTilesIntersecting(targetGrid, grid, circle))
  148. {
  149. if (newTileRef.IsSpace(_tileDefinitionManager) || _turf.IsTileBlocked(newTileRef, CollisionGroup.MobMask) || !_atmosphere.IsTileMixtureProbablySafe(targetGrid, targetMap, mapTarget))
  150. continue;
  151. found = true;
  152. targetCoords = grid.GridTileToLocal(tile);
  153. break;
  154. }
  155. //Found a safe tile, no need to continue
  156. if (found)
  157. break;
  158. }
  159. if (!found)
  160. return false;
  161. return true;
  162. }
  163. }