RadiationSystem.GridCast.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. using System.Numerics;
  2. using Content.Server.Radiation.Components;
  3. using Content.Server.Radiation.Events;
  4. using Content.Shared.Radiation.Components;
  5. using Content.Shared.Radiation.Systems;
  6. using Robust.Shared.Collections;
  7. using Robust.Shared.Map.Components;
  8. using Robust.Shared.Timing;
  9. using Robust.Shared.Utility;
  10. namespace Content.Server.Radiation.Systems;
  11. // main algorithm that fire radiation rays to target
  12. public partial class RadiationSystem
  13. {
  14. private List<Entity<MapGridComponent>> _grids = new();
  15. private readonly record struct SourceData(
  16. float Intensity,
  17. Entity<RadiationSourceComponent, TransformComponent> Entity,
  18. Vector2 WorldPosition)
  19. {
  20. public EntityUid? GridUid => Entity.Comp2.GridUid;
  21. public float Slope => Entity.Comp1.Slope;
  22. public TransformComponent Transform => Entity.Comp2;
  23. }
  24. private void UpdateGridcast()
  25. {
  26. // should we save debug information into rays?
  27. // if there is no debug sessions connected - just ignore it
  28. var debug = _debugSessions.Count > 0;
  29. var stopwatch = new Stopwatch();
  30. stopwatch.Start();
  31. _sources.Clear();
  32. _sources.EnsureCapacity(EntityManager.Count<RadiationSourceComponent>());
  33. var sources = EntityQueryEnumerator<RadiationSourceComponent, TransformComponent>();
  34. var destinations = EntityQueryEnumerator<RadiationReceiverComponent, TransformComponent>();
  35. while (sources.MoveNext(out var uid, out var source, out var xform))
  36. {
  37. if (!source.Enabled)
  38. continue;
  39. var worldPos = _transform.GetWorldPosition(xform);
  40. // Intensity is scaled by stack size.
  41. var intensity = source.Intensity * _stack.GetCount(uid);
  42. // Apply rad modifier if the source is enclosed within a radiation blocking container
  43. // Note that this also applies to receivers, and it doesn't bother to check if the container sits between them.
  44. // I.e., a source & receiver in the same blocking container will get double-blocked, when no blocking should be applied.
  45. intensity = GetAdjustedRadiationIntensity(uid, intensity);
  46. _sources.Add(new(intensity, (uid, source, xform), worldPos));
  47. }
  48. var debugRays = debug ? new List<DebugRadiationRay>() : null;
  49. var receiversTotalRads = new ValueList<(Entity<RadiationReceiverComponent>, float)>();
  50. // TODO RADIATION Parallelize
  51. // Would need to give receiversTotalRads a fixed size.
  52. // Also the _grids list needs to be local to a job. (or better yet cached in SourceData)
  53. // And I guess disable parallelization when debugging to make populating the debug List<RadiationRay> easier.
  54. // Or just make it threadsafe?
  55. while (destinations.MoveNext(out var destUid, out var dest, out var destTrs))
  56. {
  57. var destWorld = _transform.GetWorldPosition(destTrs);
  58. var rads = 0f;
  59. foreach (var source in _sources)
  60. {
  61. // send ray towards destination entity
  62. if (Irradiate(source, destUid, destTrs, destWorld, debug) is not {} ray)
  63. continue;
  64. // add rads to total rad exposure
  65. if (ray.ReachedDestination)
  66. rads += ray.Rads;
  67. if (!debug)
  68. continue;
  69. debugRays!.Add(new DebugRadiationRay(
  70. ray.MapId,
  71. GetNetEntity(ray.SourceUid),
  72. ray.Source,
  73. GetNetEntity(ray.DestinationUid),
  74. ray.Destination,
  75. ray.Rads,
  76. ray.Blockers ?? new())
  77. );
  78. }
  79. // Apply modifier if the destination entity is hidden within a radiation blocking container
  80. rads = GetAdjustedRadiationIntensity(destUid, rads);
  81. receiversTotalRads.Add(((destUid, dest), rads));
  82. }
  83. // update information for debug overlay
  84. var elapsedTime = stopwatch.Elapsed.TotalMilliseconds;
  85. var totalSources = _sources.Count;
  86. var totalReceivers = receiversTotalRads.Count;
  87. UpdateGridcastDebugOverlay(elapsedTime, totalSources, totalReceivers, debugRays);
  88. // send rads to each entity
  89. foreach (var (receiver, rads) in receiversTotalRads)
  90. {
  91. // update radiation value of receiver
  92. // if no radiation rays reached target, that will set it to 0
  93. receiver.Comp.CurrentRadiation = rads;
  94. // also send an event with combination of total rad
  95. if (rads > 0)
  96. IrradiateEntity(receiver, rads, GridcastUpdateRate);
  97. }
  98. // raise broadcast event that radiation system has updated
  99. RaiseLocalEvent(new RadiationSystemUpdatedEvent());
  100. }
  101. private RadiationRay? Irradiate(SourceData source,
  102. EntityUid destUid,
  103. TransformComponent destTrs,
  104. Vector2 destWorld,
  105. bool saveVisitedTiles)
  106. {
  107. // lets first check that source and destination on the same map
  108. if (source.Transform.MapID != destTrs.MapID)
  109. return null;
  110. var mapId = destTrs.MapID;
  111. // get direction from rad source to destination and its distance
  112. var dir = destWorld - source.WorldPosition;
  113. var dist = dir.Length();
  114. // check if receiver is too far away
  115. if (dist > GridcastMaxDistance)
  116. return null;
  117. // will it even reach destination considering distance penalty
  118. var rads = source.Intensity - source.Slope * dist;
  119. if (rads < MinIntensity)
  120. return null;
  121. // create a new radiation ray from source to destination
  122. // at first we assume that it doesn't hit any radiation blockers
  123. // and has only distance penalty
  124. var ray = new RadiationRay(mapId, source.Entity, source.WorldPosition, destUid, destWorld, rads);
  125. // if source and destination on the same grid it's possible that
  126. // between them can be another grid (ie. shuttle in center of donut station)
  127. // however we can do simplification and ignore that case
  128. if (GridcastSimplifiedSameGrid && destTrs.GridUid is {} gridUid && source.GridUid == gridUid)
  129. {
  130. if (!_gridQuery.TryGetComponent(gridUid, out var gridComponent))
  131. return ray;
  132. return Gridcast((gridUid, gridComponent, Transform(gridUid)), ref ray, saveVisitedTiles, source.Transform, destTrs);
  133. }
  134. // lets check how many grids are between source and destination
  135. // do a box intersection test between target and destination
  136. // it's not very precise, but really cheap
  137. // TODO RADIATION
  138. // Consider caching this in SourceData?
  139. // I.e., make the lookup for grids as large as the sources's max distance and store the result in SourceData.
  140. // Avoids having to do a lookup per source*receiver.
  141. var box = Box2.FromTwoPoints(source.WorldPosition, destWorld);
  142. _grids.Clear();
  143. _mapManager.FindGridsIntersecting(mapId, box, ref _grids, true);
  144. // gridcast through each grid and try to hit some radiation blockers
  145. // the ray will be updated with each grid that has some blockers
  146. foreach (var grid in _grids)
  147. {
  148. ray = Gridcast((grid.Owner, grid.Comp, Transform(grid)), ref ray, saveVisitedTiles, source.Transform, destTrs);
  149. // looks like last grid blocked all radiation
  150. // we can return right now
  151. if (ray.Rads <= 0)
  152. return ray;
  153. }
  154. _grids.Clear();
  155. return ray;
  156. }
  157. private RadiationRay Gridcast(
  158. Entity<MapGridComponent, TransformComponent> grid,
  159. ref RadiationRay ray,
  160. bool saveVisitedTiles,
  161. TransformComponent sourceTrs,
  162. TransformComponent destTrs)
  163. {
  164. var blockers = saveVisitedTiles ? new List<(Vector2i, float)>() : null;
  165. // if grid doesn't have resistance map just apply distance penalty
  166. var gridUid = grid.Owner;
  167. if (!_resistanceQuery.TryGetComponent(gridUid, out var resistance))
  168. return ray;
  169. var resistanceMap = resistance.ResistancePerTile;
  170. // get coordinate of source and destination in grid coordinates
  171. // TODO Grid overlap. This currently assumes the grid is always parented directly to the map (local matrix == world matrix).
  172. // If ever grids are allowed to overlap, this might no longer be true. In that case, this should precompute and cache
  173. // inverse world matrices.
  174. Vector2 srcLocal = sourceTrs.ParentUid == grid.Owner
  175. ? sourceTrs.LocalPosition
  176. : Vector2.Transform(ray.Source, grid.Comp2.InvLocalMatrix);
  177. Vector2 dstLocal = destTrs.ParentUid == grid.Owner
  178. ? destTrs.LocalPosition
  179. : Vector2.Transform(ray.Destination, grid.Comp2.InvLocalMatrix);
  180. Vector2i sourceGrid = new(
  181. (int) Math.Floor(srcLocal.X / grid.Comp1.TileSize),
  182. (int) Math.Floor(srcLocal.Y / grid.Comp1.TileSize));
  183. Vector2i destGrid = new(
  184. (int) Math.Floor(dstLocal.X / grid.Comp1.TileSize),
  185. (int) Math.Floor(dstLocal.Y / grid.Comp1.TileSize));
  186. // iterate tiles in grid line from source to destination
  187. var line = new GridLineEnumerator(sourceGrid, destGrid);
  188. while (line.MoveNext())
  189. {
  190. var point = line.Current;
  191. if (!resistanceMap.TryGetValue(point, out var resData))
  192. continue;
  193. ray.Rads -= resData;
  194. // save data for debug
  195. if (saveVisitedTiles)
  196. blockers!.Add((point, ray.Rads));
  197. // no intensity left after blocker
  198. if (ray.Rads <= MinIntensity)
  199. {
  200. ray.Rads = 0;
  201. break;
  202. }
  203. }
  204. if (!saveVisitedTiles || blockers!.Count <= 0)
  205. return ray;
  206. // save data for debug if needed
  207. ray.Blockers ??= new();
  208. ray.Blockers.Add(GetNetEntity(gridUid), blockers);
  209. return ray;
  210. }
  211. private float GetAdjustedRadiationIntensity(EntityUid uid, float rads)
  212. {
  213. var child = uid;
  214. var xform = Transform(uid);
  215. var parent = xform.ParentUid;
  216. while (parent.IsValid())
  217. {
  218. var parentXform = Transform(parent);
  219. var childMeta = MetaData(child);
  220. if ((childMeta.Flags & MetaDataFlags.InContainer) != MetaDataFlags.InContainer)
  221. {
  222. child = parent;
  223. parent = parentXform.ParentUid;
  224. continue;
  225. }
  226. if (_blockerQuery.TryComp(xform.ParentUid, out var blocker))
  227. {
  228. rads -= blocker.RadResistance;
  229. if (rads < 0)
  230. return 0;
  231. }
  232. child = parent;
  233. parent = parentXform.ParentUid;
  234. }
  235. return rads;
  236. }
  237. }