-using System.Linq;
using System.Numerics;
using Content.Server.Radiation.Components;
using Content.Server.Radiation.Events;
using Content.Shared.Radiation.Components;
using Content.Shared.Radiation.Systems;
-using Content.Shared.Stacks;
using Robust.Shared.Collections;
-using Robust.Shared.Containers;
using Robust.Shared.Map.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
// main algorithm that fire radiation rays to target
public partial class RadiationSystem
{
- [Dependency] private readonly SharedStackSystem _stack = default!;
- [Dependency] private readonly SharedContainerSystem _container = default!;
+ private List<Entity<MapGridComponent>> _grids = new();
- private EntityQuery<RadiationBlockingContainerComponent> _radiationBlockingContainers;
+ private readonly record struct SourceData(
+ float Intensity,
+ Entity<RadiationSourceComponent, TransformComponent> Entity,
+ Vector2 WorldPosition)
+ {
+ public EntityUid? GridUid => Entity.Comp2.GridUid;
+ public float Slope => Entity.Comp1.Slope;
+ public TransformComponent Transform => Entity.Comp2;
+ }
private void UpdateGridcast()
{
// should we save debug information into rays?
// if there is no debug sessions connected - just ignore it
- var saveVisitedTiles = _debugSessions.Count > 0;
+ var debug = _debugSessions.Count > 0;
var stopwatch = new Stopwatch();
stopwatch.Start();
+ _sources.Clear();
+ _sources.EnsureCapacity(EntityManager.Count<RadiationSourceComponent>());
+
var sources = EntityQueryEnumerator<RadiationSourceComponent, TransformComponent>();
var destinations = EntityQueryEnumerator<RadiationReceiverComponent, TransformComponent>();
- var resistanceQuery = GetEntityQuery<RadiationGridResistanceComponent>();
- var transformQuery = GetEntityQuery<TransformComponent>();
- var gridQuery = GetEntityQuery<MapGridComponent>();
- var stackQuery = GetEntityQuery<StackComponent>();
-
- _radiationBlockingContainers = GetEntityQuery<RadiationBlockingContainerComponent>();
- // precalculate world positions for each source
- // so we won't need to calc this in cycle over and over again
- var sourcesData = new ValueList<(EntityUid, RadiationSourceComponent, TransformComponent, Vector2)>();
- while (sources.MoveNext(out var uid, out var source, out var sourceTrs))
+ while (sources.MoveNext(out var uid, out var source, out var xform))
{
if (!source.Enabled)
continue;
- var worldPos = _transform.GetWorldPosition(sourceTrs, transformQuery);
- var data = (uid, source, sourceTrs, worldPos);
- sourcesData.Add(data);
+ var worldPos = _transform.GetWorldPosition(xform);
+
+ // Intensity is scaled by stack size.
+ var intensity = source.Intensity * _stack.GetCount(uid);
+
+ // Apply rad modifier if the source is enclosed within a radiation blocking container
+ // Note that this also applies to receivers, and it doesn't bother to check if the container sits between them.
+ // I.e., a source & receiver in the same blocking container will get double-blocked, when no blocking should be applied.
+ intensity = GetAdjustedRadiationIntensity(uid, intensity);
+
+ _sources.Add(new(intensity, (uid, source, xform), worldPos));
}
- // trace all rays from rad source to rad receivers
- var rays = new List<RadiationRay>();
+ var debugRays = debug ? new List<DebugRadiationRay>() : null;
var receiversTotalRads = new ValueList<(Entity<RadiationReceiverComponent>, float)>();
+
+ // TODO RADIATION Parallelize
+ // Would need to give receiversTotalRads a fixed size.
+ // Also the _grids list needs to be local to a job. (or better yet cached in SourceData)
+ // And I guess disable parallelization when debugging to make populating the debug List<RadiationRay> easier.
+ // Or just make it threadsafe?
while (destinations.MoveNext(out var destUid, out var dest, out var destTrs))
{
- var destWorld = _transform.GetWorldPosition(destTrs, transformQuery);
+ var destWorld = _transform.GetWorldPosition(destTrs);
var rads = 0f;
- foreach (var (uid, source, sourceTrs, sourceWorld) in sourcesData)
+ foreach (var source in _sources)
{
- stackQuery.TryGetComponent(uid, out var stack);
- var intensity = source.Intensity * _stack.GetCount(uid, stack);
-
// send ray towards destination entity
- var ray = Irradiate(uid, sourceTrs, sourceWorld,
- destUid, destTrs, destWorld,
- intensity, source.Slope, saveVisitedTiles, resistanceQuery, transformQuery, gridQuery);
- if (ray == null)
+ if (Irradiate(source, destUid, destTrs, destWorld, debug) is not {} ray)
continue;
- // save ray for debug
- rays.Add(ray);
-
// add rads to total rad exposure
if (ray.ReachedDestination)
rads += ray.Rads;
+
+ if (!debug)
+ continue;
+
+ debugRays!.Add(new DebugRadiationRay(
+ ray.MapId,
+ GetNetEntity(ray.SourceUid),
+ ray.Source,
+ GetNetEntity(ray.DestinationUid),
+ ray.Destination,
+ ray.Rads,
+ ray.Blockers ?? new())
+ );
}
// Apply modifier if the destination entity is hidden within a radiation blocking container
// update information for debug overlay
var elapsedTime = stopwatch.Elapsed.TotalMilliseconds;
- var totalSources = sourcesData.Count;
+ var totalSources = _sources.Count;
var totalReceivers = receiversTotalRads.Count;
- UpdateGridcastDebugOverlay(elapsedTime, totalSources, totalReceivers, rays);
+ UpdateGridcastDebugOverlay(elapsedTime, totalSources, totalReceivers, debugRays);
// send rads to each entity
foreach (var (receiver, rads) in receiversTotalRads)
RaiseLocalEvent(new RadiationSystemUpdatedEvent());
}
- private RadiationRay? Irradiate(EntityUid sourceUid, TransformComponent sourceTrs, Vector2 sourceWorld,
- EntityUid destUid, TransformComponent destTrs, Vector2 destWorld,
- float incomingRads, float slope, bool saveVisitedTiles,
- EntityQuery<RadiationGridResistanceComponent> resistanceQuery,
- EntityQuery<TransformComponent> transformQuery, EntityQuery<MapGridComponent> gridQuery)
+ private RadiationRay? Irradiate(SourceData source,
+ EntityUid destUid,
+ TransformComponent destTrs,
+ Vector2 destWorld,
+ bool saveVisitedTiles)
{
// lets first check that source and destination on the same map
- if (sourceTrs.MapID != destTrs.MapID)
+ if (source.Transform.MapID != destTrs.MapID)
return null;
- var mapId = sourceTrs.MapID;
+
+ var mapId = destTrs.MapID;
// get direction from rad source to destination and its distance
- var dir = destWorld - sourceWorld;
+ var dir = destWorld - source.WorldPosition;
var dist = dir.Length();
// check if receiver is too far away
return null;
// will it even reach destination considering distance penalty
- var rads = incomingRads - slope * dist;
-
- // Apply rad modifier if the source is enclosed within a radiation blocking container
- rads = GetAdjustedRadiationIntensity(sourceUid, rads);
-
- if (rads <= MinIntensity)
+ var rads = source.Intensity - source.Slope * dist;
+ if (rads < MinIntensity)
return null;
// create a new radiation ray from source to destination
// at first we assume that it doesn't hit any radiation blockers
// and has only distance penalty
- var ray = new RadiationRay(mapId, GetNetEntity(sourceUid), sourceWorld, GetNetEntity(destUid), destWorld, rads);
+ var ray = new RadiationRay(mapId, source.Entity, source.WorldPosition, destUid, destWorld, rads);
// if source and destination on the same grid it's possible that
// between them can be another grid (ie. shuttle in center of donut station)
// however we can do simplification and ignore that case
- if (GridcastSimplifiedSameGrid && sourceTrs.GridUid != null && sourceTrs.GridUid == destTrs.GridUid)
+ if (GridcastSimplifiedSameGrid && destTrs.GridUid is {} gridUid && source.GridUid == gridUid)
{
- if (!gridQuery.TryGetComponent(sourceTrs.GridUid.Value, out var gridComponent))
+ if (!_gridQuery.TryGetComponent(gridUid, out var gridComponent))
return ray;
- return Gridcast((sourceTrs.GridUid.Value, gridComponent), ray, saveVisitedTiles, resistanceQuery, sourceTrs, destTrs, transformQuery.GetComponent(sourceTrs.GridUid.Value));
+ return Gridcast((gridUid, gridComponent, Transform(gridUid)), ref ray, saveVisitedTiles, source.Transform, destTrs);
}
// lets check how many grids are between source and destination
// do a box intersection test between target and destination
// it's not very precise, but really cheap
- var box = Box2.FromTwoPoints(sourceWorld, destWorld);
- var grids = new List<Entity<MapGridComponent>>();
- _mapManager.FindGridsIntersecting(mapId, box, ref grids, true);
+
+ // TODO RADIATION
+ // Consider caching this in SourceData?
+ // I.e., make the lookup for grids as large as the sources's max distance and store the result in SourceData.
+ // Avoids having to do a lookup per source*receiver.
+ var box = Box2.FromTwoPoints(source.WorldPosition, destWorld);
+ _grids.Clear();
+ _mapManager.FindGridsIntersecting(mapId, box, ref _grids, true);
// gridcast through each grid and try to hit some radiation blockers
// the ray will be updated with each grid that has some blockers
- foreach (var grid in grids)
+ foreach (var grid in _grids)
{
- ray = Gridcast(grid, ray, saveVisitedTiles, resistanceQuery, sourceTrs, destTrs, transformQuery.GetComponent(grid));
+ ray = Gridcast((grid.Owner, grid.Comp, Transform(grid)), ref ray, saveVisitedTiles, source.Transform, destTrs);
// looks like last grid blocked all radiation
// we can return right now
return ray;
}
+ _grids.Clear();
+
return ray;
}
- private RadiationRay Gridcast(Entity<MapGridComponent> grid, RadiationRay ray, bool saveVisitedTiles,
- EntityQuery<RadiationGridResistanceComponent> resistanceQuery,
+ private RadiationRay Gridcast(
+ Entity<MapGridComponent, TransformComponent> grid,
+ ref RadiationRay ray,
+ bool saveVisitedTiles,
TransformComponent sourceTrs,
- TransformComponent destTrs,
- TransformComponent gridTrs)
+ TransformComponent destTrs)
{
- var blockers = new List<(Vector2i, float)>();
+ var blockers = saveVisitedTiles ? new List<(Vector2i, float)>() : null;
// if grid doesn't have resistance map just apply distance penalty
var gridUid = grid.Owner;
- if (!resistanceQuery.TryGetComponent(gridUid, out var resistance))
+ if (!_resistanceQuery.TryGetComponent(gridUid, out var resistance))
return ray;
var resistanceMap = resistance.ResistancePerTile;
Vector2 srcLocal = sourceTrs.ParentUid == grid.Owner
? sourceTrs.LocalPosition
- : Vector2.Transform(ray.Source, gridTrs.InvLocalMatrix);
+ : Vector2.Transform(ray.Source, grid.Comp2.InvLocalMatrix);
Vector2 dstLocal = destTrs.ParentUid == grid.Owner
? destTrs.LocalPosition
- : Vector2.Transform(ray.Destination, gridTrs.InvLocalMatrix);
+ : Vector2.Transform(ray.Destination, grid.Comp2.InvLocalMatrix);
Vector2i sourceGrid = new(
- (int) Math.Floor(srcLocal.X / grid.Comp.TileSize),
- (int) Math.Floor(srcLocal.Y / grid.Comp.TileSize));
+ (int) Math.Floor(srcLocal.X / grid.Comp1.TileSize),
+ (int) Math.Floor(srcLocal.Y / grid.Comp1.TileSize));
Vector2i destGrid = new(
- (int) Math.Floor(dstLocal.X / grid.Comp.TileSize),
- (int) Math.Floor(dstLocal.Y / grid.Comp.TileSize));
+ (int) Math.Floor(dstLocal.X / grid.Comp1.TileSize),
+ (int) Math.Floor(dstLocal.Y / grid.Comp1.TileSize));
// iterate tiles in grid line from source to destination
var line = new GridLineEnumerator(sourceGrid, destGrid);
// save data for debug
if (saveVisitedTiles)
- blockers.Add((point, ray.Rads));
+ blockers!.Add((point, ray.Rads));
// no intensity left after blocker
if (ray.Rads <= MinIntensity)
}
}
+ if (!saveVisitedTiles || blockers!.Count <= 0)
+ return ray;
+
// save data for debug if needed
- if (saveVisitedTiles && blockers.Count > 0)
- ray.Blockers.Add(GetNetEntity(gridUid), blockers);
+ ray.Blockers ??= new();
+ ray.Blockers.Add(GetNetEntity(gridUid), blockers);
return ray;
}
private float GetAdjustedRadiationIntensity(EntityUid uid, float rads)
{
- var radblockingComps = new List<RadiationBlockingContainerComponent>();
- if (_container.TryFindComponentsOnEntityContainerOrParent(uid, _radiationBlockingContainers, radblockingComps))
+ var child = uid;
+ var xform = Transform(uid);
+ var parent = xform.ParentUid;
+
+ while (parent.IsValid())
{
- rads -= radblockingComps.Sum(x => x.RadResistance);
+ var parentXform = Transform(parent);
+ var childMeta = MetaData(child);
+
+ if ((childMeta.Flags & MetaDataFlags.InContainer) != MetaDataFlags.InContainer)
+ {
+ child = parent;
+ parent = parentXform.ParentUid;
+ continue;
+ }
+
+ if (_blockerQuery.TryComp(xform.ParentUid, out var blocker))
+ {
+ rads -= blocker.RadResistance;
+ if (rads < 0)
+ return 0;
+ }
+
+ child = parent;
+ parent = parentXform.ParentUid;
}
- return float.Max(rads, 0);
+ return rads;
}
}