Optimize & clean up RadiationSystem (#34459)

* Optimize & clean up RadiationSystem

* comments

* Update Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs

Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com>

---------

Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com>
This commit is contained in:
Leon Friedrich
2025-01-18 12:07:20 +11:00
committed by GitHub
parent efd5d644e8
commit 9d4e60068b
7 changed files with 189 additions and 127 deletions

View File

@@ -9,7 +9,7 @@ public sealed class RadiationSystem : EntitySystem
{ {
[Dependency] private readonly IOverlayManager _overlayMan = default!; [Dependency] private readonly IOverlayManager _overlayMan = default!;
public List<RadiationRay>? Rays; public List<DebugRadiationRay>? Rays;
public Dictionary<NetEntity, Dictionary<Vector2i, float>>? ResistanceGrids; public Dictionary<NetEntity, Dictionary<Vector2i, float>>? ResistanceGrids;
public override void Initialize() public override void Initialize()

View File

@@ -5,6 +5,7 @@ using Content.Shared.Administration;
using Content.Shared.Radiation.Events; using Content.Shared.Radiation.Events;
using Content.Shared.Radiation.Systems; using Content.Shared.Radiation.Systems;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.Debugging;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Player; using Robust.Shared.Player;
@@ -42,12 +43,12 @@ public partial class RadiationSystem
/// </summary> /// </summary>
private void UpdateDebugOverlay(EntityEventArgs ev) private void UpdateDebugOverlay(EntityEventArgs ev)
{ {
var sessions = _debugSessions.ToArray(); foreach (var session in _debugSessions)
foreach (var session in sessions)
{ {
if (session.Status != SessionStatus.InGame) if (session.Status != SessionStatus.InGame)
_debugSessions.Remove(session); _debugSessions.Remove(session);
RaiseNetworkEvent(ev, session.Channel); else
RaiseNetworkEvent(ev, session);
} }
} }
@@ -70,13 +71,16 @@ public partial class RadiationSystem
UpdateDebugOverlay(ev); UpdateDebugOverlay(ev);
} }
private void UpdateGridcastDebugOverlay(double elapsedTime, int totalSources, private void UpdateGridcastDebugOverlay(
int totalReceivers, List<RadiationRay> rays) double elapsedTime,
int totalSources,
int totalReceivers,
List<DebugRadiationRay>? rays)
{ {
if (_debugSessions.Count == 0) if (_debugSessions.Count == 0)
return; return;
var ev = new OnRadiationOverlayUpdateEvent(elapsedTime, totalSources, totalReceivers, rays); var ev = new OnRadiationOverlayUpdateEvent(elapsedTime, totalSources, totalReceivers, rays ?? new());
UpdateDebugOverlay(ev); UpdateDebugOverlay(ev);
} }
} }

View File

@@ -1,12 +1,9 @@
using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Server.Radiation.Components; using Content.Server.Radiation.Components;
using Content.Server.Radiation.Events; using Content.Server.Radiation.Events;
using Content.Shared.Radiation.Components; using Content.Shared.Radiation.Components;
using Content.Shared.Radiation.Systems; using Content.Shared.Radiation.Systems;
using Content.Shared.Stacks;
using Robust.Shared.Collections; using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -16,68 +13,86 @@ namespace Content.Server.Radiation.Systems;
// main algorithm that fire radiation rays to target // main algorithm that fire radiation rays to target
public partial class RadiationSystem public partial class RadiationSystem
{ {
[Dependency] private readonly SharedStackSystem _stack = default!; private List<Entity<MapGridComponent>> _grids = new();
[Dependency] private readonly SharedContainerSystem _container = default!;
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() private void UpdateGridcast()
{ {
// should we save debug information into rays? // should we save debug information into rays?
// if there is no debug sessions connected - just ignore it // if there is no debug sessions connected - just ignore it
var saveVisitedTiles = _debugSessions.Count > 0; var debug = _debugSessions.Count > 0;
var stopwatch = new Stopwatch(); var stopwatch = new Stopwatch();
stopwatch.Start(); stopwatch.Start();
_sources.Clear();
_sources.EnsureCapacity(EntityManager.Count<RadiationSourceComponent>());
var sources = EntityQueryEnumerator<RadiationSourceComponent, TransformComponent>(); var sources = EntityQueryEnumerator<RadiationSourceComponent, TransformComponent>();
var destinations = EntityQueryEnumerator<RadiationReceiverComponent, 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>(); while (sources.MoveNext(out var uid, out var source, out var xform))
// 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))
{ {
if (!source.Enabled) if (!source.Enabled)
continue; continue;
var worldPos = _transform.GetWorldPosition(sourceTrs, transformQuery); var worldPos = _transform.GetWorldPosition(xform);
var data = (uid, source, sourceTrs, worldPos);
sourcesData.Add(data); // 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 debugRays = debug ? new List<DebugRadiationRay>() : null;
var rays = new List<RadiationRay>();
var receiversTotalRads = new ValueList<(Entity<RadiationReceiverComponent>, float)>(); 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)) 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; 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 // send ray towards destination entity
var ray = Irradiate(uid, sourceTrs, sourceWorld, if (Irradiate(source, destUid, destTrs, destWorld, debug) is not {} ray)
destUid, destTrs, destWorld,
intensity, source.Slope, saveVisitedTiles, resistanceQuery, transformQuery, gridQuery);
if (ray == null)
continue; continue;
// save ray for debug
rays.Add(ray);
// add rads to total rad exposure // add rads to total rad exposure
if (ray.ReachedDestination) if (ray.ReachedDestination)
rads += ray.Rads; 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 // Apply modifier if the destination entity is hidden within a radiation blocking container
@@ -88,9 +103,9 @@ public partial class RadiationSystem
// update information for debug overlay // update information for debug overlay
var elapsedTime = stopwatch.Elapsed.TotalMilliseconds; var elapsedTime = stopwatch.Elapsed.TotalMilliseconds;
var totalSources = sourcesData.Count; var totalSources = _sources.Count;
var totalReceivers = receiversTotalRads.Count; var totalReceivers = receiversTotalRads.Count;
UpdateGridcastDebugOverlay(elapsedTime, totalSources, totalReceivers, rays); UpdateGridcastDebugOverlay(elapsedTime, totalSources, totalReceivers, debugRays);
// send rads to each entity // send rads to each entity
foreach (var (receiver, rads) in receiversTotalRads) foreach (var (receiver, rads) in receiversTotalRads)
@@ -108,19 +123,20 @@ public partial class RadiationSystem
RaiseLocalEvent(new RadiationSystemUpdatedEvent()); RaiseLocalEvent(new RadiationSystemUpdatedEvent());
} }
private RadiationRay? Irradiate(EntityUid sourceUid, TransformComponent sourceTrs, Vector2 sourceWorld, private RadiationRay? Irradiate(SourceData source,
EntityUid destUid, TransformComponent destTrs, Vector2 destWorld, EntityUid destUid,
float incomingRads, float slope, bool saveVisitedTiles, TransformComponent destTrs,
EntityQuery<RadiationGridResistanceComponent> resistanceQuery, Vector2 destWorld,
EntityQuery<TransformComponent> transformQuery, EntityQuery<MapGridComponent> gridQuery) bool saveVisitedTiles)
{ {
// lets first check that source and destination on the same map // lets first check that source and destination on the same map
if (sourceTrs.MapID != destTrs.MapID) if (source.Transform.MapID != destTrs.MapID)
return null; return null;
var mapId = sourceTrs.MapID;
var mapId = destTrs.MapID;
// get direction from rad source to destination and its distance // get direction from rad source to destination and its distance
var dir = destWorld - sourceWorld; var dir = destWorld - source.WorldPosition;
var dist = dir.Length(); var dist = dir.Length();
// check if receiver is too far away // check if receiver is too far away
@@ -128,41 +144,42 @@ public partial class RadiationSystem
return null; return null;
// will it even reach destination considering distance penalty // will it even reach destination considering distance penalty
var rads = incomingRads - slope * dist; var rads = source.Intensity - source.Slope * dist;
if (rads < MinIntensity)
// Apply rad modifier if the source is enclosed within a radiation blocking container
rads = GetAdjustedRadiationIntensity(sourceUid, rads);
if (rads <= MinIntensity)
return null; return null;
// create a new radiation ray from source to destination // create a new radiation ray from source to destination
// at first we assume that it doesn't hit any radiation blockers // at first we assume that it doesn't hit any radiation blockers
// and has only distance penalty // 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 // 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) // between them can be another grid (ie. shuttle in center of donut station)
// however we can do simplification and ignore that case // 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 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 // lets check how many grids are between source and destination
// do a box intersection test between target and destination // do a box intersection test between target and destination
// it's not very precise, but really cheap // it's not very precise, but really cheap
var box = Box2.FromTwoPoints(sourceWorld, destWorld);
var grids = new List<Entity<MapGridComponent>>(); // TODO RADIATION
_mapManager.FindGridsIntersecting(mapId, box, ref grids, true); // 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 // gridcast through each grid and try to hit some radiation blockers
// the ray will be updated with each grid that has some 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 // looks like last grid blocked all radiation
// we can return right now // we can return right now
@@ -170,20 +187,23 @@ public partial class RadiationSystem
return ray; return ray;
} }
_grids.Clear();
return ray; return ray;
} }
private RadiationRay Gridcast(Entity<MapGridComponent> grid, RadiationRay ray, bool saveVisitedTiles, private RadiationRay Gridcast(
EntityQuery<RadiationGridResistanceComponent> resistanceQuery, Entity<MapGridComponent, TransformComponent> grid,
ref RadiationRay ray,
bool saveVisitedTiles,
TransformComponent sourceTrs, TransformComponent sourceTrs,
TransformComponent destTrs, TransformComponent destTrs)
TransformComponent gridTrs)
{ {
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 // if grid doesn't have resistance map just apply distance penalty
var gridUid = grid.Owner; var gridUid = grid.Owner;
if (!resistanceQuery.TryGetComponent(gridUid, out var resistance)) if (!_resistanceQuery.TryGetComponent(gridUid, out var resistance))
return ray; return ray;
var resistanceMap = resistance.ResistancePerTile; var resistanceMap = resistance.ResistancePerTile;
@@ -195,19 +215,19 @@ public partial class RadiationSystem
Vector2 srcLocal = sourceTrs.ParentUid == grid.Owner Vector2 srcLocal = sourceTrs.ParentUid == grid.Owner
? sourceTrs.LocalPosition ? sourceTrs.LocalPosition
: Vector2.Transform(ray.Source, gridTrs.InvLocalMatrix); : Vector2.Transform(ray.Source, grid.Comp2.InvLocalMatrix);
Vector2 dstLocal = destTrs.ParentUid == grid.Owner Vector2 dstLocal = destTrs.ParentUid == grid.Owner
? destTrs.LocalPosition ? destTrs.LocalPosition
: Vector2.Transform(ray.Destination, gridTrs.InvLocalMatrix); : Vector2.Transform(ray.Destination, grid.Comp2.InvLocalMatrix);
Vector2i sourceGrid = new( Vector2i sourceGrid = new(
(int) Math.Floor(srcLocal.X / grid.Comp.TileSize), (int) Math.Floor(srcLocal.X / grid.Comp1.TileSize),
(int) Math.Floor(srcLocal.Y / grid.Comp.TileSize)); (int) Math.Floor(srcLocal.Y / grid.Comp1.TileSize));
Vector2i destGrid = new( Vector2i destGrid = new(
(int) Math.Floor(dstLocal.X / grid.Comp.TileSize), (int) Math.Floor(dstLocal.X / grid.Comp1.TileSize),
(int) Math.Floor(dstLocal.Y / grid.Comp.TileSize)); (int) Math.Floor(dstLocal.Y / grid.Comp1.TileSize));
// iterate tiles in grid line from source to destination // iterate tiles in grid line from source to destination
var line = new GridLineEnumerator(sourceGrid, destGrid); var line = new GridLineEnumerator(sourceGrid, destGrid);
@@ -220,7 +240,7 @@ public partial class RadiationSystem
// save data for debug // save data for debug
if (saveVisitedTiles) if (saveVisitedTiles)
blockers.Add((point, ray.Rads)); blockers!.Add((point, ray.Rads));
// no intensity left after blocker // no intensity left after blocker
if (ray.Rads <= MinIntensity) if (ray.Rads <= MinIntensity)
@@ -230,21 +250,45 @@ public partial class RadiationSystem
} }
} }
if (!saveVisitedTiles || blockers!.Count <= 0)
return ray;
// save data for debug if needed // save data for debug if needed
if (saveVisitedTiles && blockers.Count > 0) ray.Blockers ??= new();
ray.Blockers.Add(GetNetEntity(gridUid), blockers); ray.Blockers.Add(GetNetEntity(gridUid), blockers);
return ray; return ray;
} }
private float GetAdjustedRadiationIntensity(EntityUid uid, float rads) private float GetAdjustedRadiationIntensity(EntityUid uid, float rads)
{ {
var radblockingComps = new List<RadiationBlockingContainerComponent>(); var child = uid;
if (_container.TryFindComponentsOnEntityContainerOrParent(uid, _radiationBlockingContainers, radblockingComps)) 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;
} }
} }

View File

@@ -1,8 +1,10 @@
using Content.Server.Radiation.Components; using Content.Server.Radiation.Components;
using Content.Shared.Radiation.Components; using Content.Shared.Radiation.Components;
using Content.Shared.Radiation.Events; using Content.Shared.Radiation.Events;
using Content.Shared.Stacks;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Map.Components;
namespace Content.Server.Radiation.Systems; namespace Content.Server.Radiation.Systems;
@@ -11,14 +13,26 @@ public sealed partial class RadiationSystem : EntitySystem
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedStackSystem _stack = default!;
private EntityQuery<RadiationBlockingContainerComponent> _blockerQuery;
private EntityQuery<RadiationGridResistanceComponent> _resistanceQuery;
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<StackComponent> _stackQuery;
private float _accumulator; private float _accumulator;
private List<SourceData> _sources = new();
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeCvars(); SubscribeCvars();
InitRadBlocking(); InitRadBlocking();
_blockerQuery = GetEntityQuery<RadiationBlockingContainerComponent>();
_resistanceQuery = GetEntityQuery<RadiationGridResistanceComponent>();
_gridQuery = GetEntityQuery<MapGridComponent>();
_stackQuery = GetEntityQuery<StackComponent>();
} }
public override void Update(float frameTime) public override void Update(float frameTime)

View File

@@ -4,17 +4,11 @@
/// Raised on entity when it was irradiated /// Raised on entity when it was irradiated
/// by some radiation source. /// by some radiation source.
/// </summary> /// </summary>
public sealed class OnIrradiatedEvent : EntityEventArgs public readonly record struct OnIrradiatedEvent(float FrameTime, float RadsPerSecond)
{ {
public readonly float FrameTime; public readonly float FrameTime = FrameTime;
public readonly float RadsPerSecond; public readonly float RadsPerSecond = RadsPerSecond;
public float TotalRads => RadsPerSecond * FrameTime; public float TotalRads => RadsPerSecond * FrameTime;
public OnIrradiatedEvent(float frameTime, float radsPerSecond)
{
FrameTime = frameTime;
RadsPerSecond = radsPerSecond;
}
} }

View File

@@ -13,36 +13,33 @@ namespace Content.Shared.Radiation.Events;
/// Will be sent only to clients that activated radiation view using console command. /// Will be sent only to clients that activated radiation view using console command.
/// </remarks> /// </remarks>
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed class OnRadiationOverlayUpdateEvent : EntityEventArgs public sealed class OnRadiationOverlayUpdateEvent(
double elapsedTimeMs,
int sourcesCount,
int receiversCount,
List<DebugRadiationRay> rays)
: EntityEventArgs
{ {
/// <summary> /// <summary>
/// Total time in milliseconds that server took to do radiation processing. /// Total time in milliseconds that server took to do radiation processing.
/// Exclude time of entities reacting to <see cref="OnIrradiatedEvent"/>. /// Exclude time of entities reacting to <see cref="OnIrradiatedEvent"/>.
/// </summary> /// </summary>
public readonly double ElapsedTimeMs; public readonly double ElapsedTimeMs = elapsedTimeMs;
/// <summary> /// <summary>
/// Total count of entities with <see cref="RadiationSourceComponent"/> on all maps. /// Total count of entities with <see cref="RadiationSourceComponent"/> on all maps.
/// </summary> /// </summary>
public readonly int SourcesCount; public readonly int SourcesCount = sourcesCount;
/// <summary> /// <summary>
/// Total count of entities with radiation receiver on all maps. /// Total count of entities with radiation receiver on all maps.
/// </summary> /// </summary>
public readonly int ReceiversCount; public readonly int ReceiversCount = receiversCount;
/// <summary> /// <summary>
/// All radiation rays that was processed by radiation system. /// All radiation rays that was processed by radiation system.
/// </summary> /// </summary>
public readonly List<RadiationRay> Rays; public readonly List<DebugRadiationRay> Rays = rays;
public OnRadiationOverlayUpdateEvent(double elapsedTimeMs, int sourcesCount, int receiversCount, List<RadiationRay> rays)
{
ElapsedTimeMs = elapsedTimeMs;
SourcesCount = sourcesCount;
ReceiversCount = receiversCount;
Rays = rays;
}
} }
/// <summary> /// <summary>

View File

@@ -9,33 +9,38 @@ namespace Content.Shared.Radiation.Systems;
/// Ray emitted by radiation source towards radiation receiver. /// Ray emitted by radiation source towards radiation receiver.
/// Contains all information about encountered radiation blockers. /// Contains all information about encountered radiation blockers.
/// </summary> /// </summary>
[Serializable, NetSerializable] public struct RadiationRay(
public sealed class RadiationRay MapId mapId,
EntityUid sourceUid,
Vector2 source,
EntityUid destinationUid,
Vector2 destination,
float rads)
{ {
/// <summary> /// <summary>
/// Map on which source and receiver are placed. /// Map on which source and receiver are placed.
/// </summary> /// </summary>
public MapId MapId; public MapId MapId = mapId;
/// <summary> /// <summary>
/// Uid of entity with <see cref="RadiationSourceComponent"/>. /// Uid of entity with <see cref="RadiationSourceComponent"/>.
/// </summary> /// </summary>
public NetEntity SourceUid; public EntityUid SourceUid = sourceUid;
/// <summary> /// <summary>
/// World coordinates of radiation source. /// World coordinates of radiation source.
/// </summary> /// </summary>
public Vector2 Source; public Vector2 Source = source;
/// <summary> /// <summary>
/// Uid of entity with radiation receiver component. /// Uid of entity with radiation receiver component.
/// </summary> /// </summary>
public NetEntity DestinationUid; public EntityUid DestinationUid = destinationUid;
/// <summary> /// <summary>
/// World coordinates of radiation receiver. /// World coordinates of radiation receiver.
/// </summary> /// </summary>
public Vector2 Destination; public Vector2 Destination = destination;
/// <summary> /// <summary>
/// How many rads intensity reached radiation receiver. /// How many rads intensity reached radiation receiver.
/// </summary> /// </summary>
public float Rads; public float Rads = rads;
/// <summary> /// <summary>
/// Has rad ray reached destination or lost all intensity after blockers? /// Has rad ray reached destination or lost all intensity after blockers?
@@ -43,23 +48,27 @@ public sealed class RadiationRay
public bool ReachedDestination => Rads > 0; public bool ReachedDestination => Rads > 0;
/// <summary> /// <summary>
/// All blockers visited by gridcast. Key is uid of grid. Values are pairs /// All blockers visited by gridcast, used for debug overlays. Key is uid of grid. Values are pairs
/// of tile indices and floats with updated radiation value. /// of tile indices and floats with updated radiation value.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Last tile may have negative value if ray has lost all intensity. /// Last tile may have negative value if ray has lost all intensity.
/// Grid traversal order isn't guaranteed. /// Grid traversal order isn't guaranteed.
/// </remarks> /// </remarks>
public Dictionary<NetEntity, List<(Vector2i, float)>> Blockers = new(); public Dictionary<NetEntity, List<(Vector2i, float)>>? Blockers;
public RadiationRay(MapId mapId, NetEntity sourceUid, Vector2 source, }
NetEntity destinationUid, Vector2 destination, float rads)
{ // Variant of RadiationRay that uses NetEntities.
MapId = mapId; [Serializable, NetSerializable]
SourceUid = sourceUid; public readonly record struct DebugRadiationRay(
Source = source; MapId MapId,
DestinationUid = destinationUid; NetEntity SourceUid,
Destination = destination; Vector2 Source,
Rads = rads; NetEntity DestinationUid,
} Vector2 Destination,
float Rads,
Dictionary<NetEntity, List<(Vector2i, float)>> Blockers)
{
public bool ReachedDestination => Rads > 0;
} }