Radiation rework (#10970)
This commit is contained in:
@@ -15,7 +15,7 @@ using Content.Client.MainMenu;
|
|||||||
using Content.Client.Parallax.Managers;
|
using Content.Client.Parallax.Managers;
|
||||||
using Content.Client.Players.PlayTimeTracking;
|
using Content.Client.Players.PlayTimeTracking;
|
||||||
using Content.Client.Preferences;
|
using Content.Client.Preferences;
|
||||||
using Content.Client.Radiation;
|
using Content.Client.Radiation.Overlays;
|
||||||
using Content.Client.Screenshot;
|
using Content.Client.Screenshot;
|
||||||
using Content.Client.Singularity;
|
using Content.Client.Singularity;
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
|
|||||||
131
Content.Client/Radiation/Overlays/RadiationDebugOverlay.cs
Normal file
131
Content.Client/Radiation/Overlays/RadiationDebugOverlay.cs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Client.Radiation.Systems;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.ResourceManagement;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.Client.Radiation.Overlays;
|
||||||
|
|
||||||
|
public sealed class RadiationDebugOverlay : Overlay
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
private readonly RadiationSystem _radiation;
|
||||||
|
|
||||||
|
private readonly Font _font;
|
||||||
|
|
||||||
|
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
|
||||||
|
|
||||||
|
public RadiationDebugOverlay()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
_radiation = _entityManager.System<RadiationSystem>();
|
||||||
|
|
||||||
|
var cache = IoCManager.Resolve<IResourceCache>();
|
||||||
|
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
switch (args.Space)
|
||||||
|
{
|
||||||
|
case OverlaySpace.ScreenSpace:
|
||||||
|
DrawScreenRays(args);
|
||||||
|
DrawScreenResistance(args);
|
||||||
|
break;
|
||||||
|
case OverlaySpace.WorldSpace:
|
||||||
|
DrawWorld(args);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawScreenRays(OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
var rays = _radiation.Rays;
|
||||||
|
if (rays == null || args.ViewportControl == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var handle = args.ScreenHandle;
|
||||||
|
foreach (var ray in rays)
|
||||||
|
{
|
||||||
|
if (ray.MapId != args.MapId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ray.ReachedDestination)
|
||||||
|
{
|
||||||
|
var screenCenter = args.ViewportControl.WorldToScreen(ray.Destination);
|
||||||
|
handle.DrawString(_font, screenCenter, ray.Rads.ToString("F2"), 2f, Color.White);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (gridUid, blockers) in ray.Blockers)
|
||||||
|
{
|
||||||
|
if (!_mapManager.TryGetGrid(gridUid, out var grid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var (tile, rads) in blockers)
|
||||||
|
{
|
||||||
|
var worldPos = grid.GridTileToWorldPos(tile);
|
||||||
|
var screenCenter = args.ViewportControl.WorldToScreen(worldPos);
|
||||||
|
handle.DrawString(_font, screenCenter, rads.ToString("F2"), 1.5f, Color.White);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawScreenResistance(OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
var resistance = _radiation.ResistanceGrids;
|
||||||
|
if (resistance == null || args.ViewportControl == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var handle = args.ScreenHandle;
|
||||||
|
var query = _entityManager.GetEntityQuery<TransformComponent>();
|
||||||
|
foreach (var (gridUid, resMap) in resistance)
|
||||||
|
{
|
||||||
|
if (!_mapManager.TryGetGrid(gridUid, out var grid))
|
||||||
|
continue;
|
||||||
|
if (query.TryGetComponent(gridUid, out var trs) && trs.MapID != args.MapId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var offset = new Vector2(grid.TileSize, -grid.TileSize) * 0.25f;
|
||||||
|
foreach (var (tile, value) in resMap)
|
||||||
|
{
|
||||||
|
var localPos = grid.GridTileToLocal(tile).Position + offset;
|
||||||
|
var worldPos = grid.LocalToWorld(localPos);
|
||||||
|
var screenCenter = args.ViewportControl.WorldToScreen(worldPos);
|
||||||
|
handle.DrawString(_font, screenCenter, value.ToString("F2"), color: Color.White);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawWorld(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
var rays = _radiation.Rays;
|
||||||
|
if (rays == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var handle = args.WorldHandle;
|
||||||
|
// draw lines for raycasts
|
||||||
|
foreach (var ray in rays)
|
||||||
|
{
|
||||||
|
if (ray.MapId != args.MapId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ray.ReachedDestination)
|
||||||
|
{
|
||||||
|
handle.DrawLine(ray.Source, ray.Destination, Color.Red);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (gridUid, blockers) in ray.Blockers)
|
||||||
|
{
|
||||||
|
if (!_mapManager.TryGetGrid(gridUid, out var grid))
|
||||||
|
continue;
|
||||||
|
var (destTile, _) = blockers.Last();
|
||||||
|
var destWorld = grid.GridTileToWorldPos(destTile);
|
||||||
|
handle.DrawLine(ray.Source, destWorld, Color.Red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ using Robust.Shared.Map;
|
|||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Client.Radiation
|
namespace Content.Client.Radiation.Overlays
|
||||||
{
|
{
|
||||||
public sealed class RadiationPulseOverlay : Overlay
|
public sealed class RadiationPulseOverlay : Overlay
|
||||||
{
|
{
|
||||||
54
Content.Client/Radiation/Systems/RadiationSystem.cs
Normal file
54
Content.Client/Radiation/Systems/RadiationSystem.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using Content.Client.Radiation.Overlays;
|
||||||
|
using Content.Shared.Radiation.Events;
|
||||||
|
using Content.Shared.Radiation.Systems;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
|
||||||
|
namespace Content.Client.Radiation.Systems;
|
||||||
|
|
||||||
|
public sealed class RadiationSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||||
|
|
||||||
|
public List<RadiationRay>? Rays;
|
||||||
|
public Dictionary<EntityUid, Dictionary<Vector2i, float>>? ResistanceGrids;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeNetworkEvent<OnRadiationOverlayToggledEvent>(OnOverlayToggled);
|
||||||
|
SubscribeNetworkEvent<OnRadiationOverlayUpdateEvent>(OnOverlayUpdate);
|
||||||
|
SubscribeNetworkEvent<OnRadiationOverlayResistanceUpdateEvent>(OnResistanceUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
_overlayMan.RemoveOverlay<RadiationDebugOverlay>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnOverlayToggled(OnRadiationOverlayToggledEvent ev)
|
||||||
|
{
|
||||||
|
if (ev.IsEnabled)
|
||||||
|
_overlayMan.AddOverlay(new RadiationDebugOverlay());
|
||||||
|
else
|
||||||
|
_overlayMan.RemoveOverlay<RadiationDebugOverlay>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnOverlayUpdate(OnRadiationOverlayUpdateEvent ev)
|
||||||
|
{
|
||||||
|
if (!_overlayMan.TryGetOverlay(out RadiationDebugOverlay? overlay))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var str = $"Radiation update: {ev.ElapsedTimeMs}ms with. Receivers: {ev.ReceiversCount}, " +
|
||||||
|
$"Sources: {ev.SourcesCount}, Rays: {ev.Rays.Count}";
|
||||||
|
Logger.Info(str);
|
||||||
|
|
||||||
|
Rays = ev.Rays;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnResistanceUpdate(OnRadiationOverlayResistanceUpdateEvent ev)
|
||||||
|
{
|
||||||
|
if (!_overlayMan.TryGetOverlay(out RadiationDebugOverlay? overlay))
|
||||||
|
return;
|
||||||
|
ResistanceGrids = ev.Grids;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Content.Server.Radiation.Systems;
|
||||||
|
|
||||||
|
namespace Content.Server.Radiation.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Blocks radiation when placed on tile.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
[Access(typeof(RadiationSystem))]
|
||||||
|
public sealed class RadiationBlockerComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Does it block radiation at all?
|
||||||
|
/// </summary>
|
||||||
|
[DataField("enabled")]
|
||||||
|
public bool Enabled = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How many rads per second does the blocker absorb?
|
||||||
|
/// </summary>
|
||||||
|
[DataField("resistance")]
|
||||||
|
public float RadResistance = 1f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current position of the rad blocker in grid coordinates.
|
||||||
|
/// Null if doesn't anchored or doesn't block rads.
|
||||||
|
/// </summary>
|
||||||
|
public (EntityUid Grid, Vector2i Tile)? CurrentPosition;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Content.Server.Radiation.Systems;
|
||||||
|
|
||||||
|
namespace Content.Server.Radiation.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Grid component that stores radiation resistance of <see cref="RadiationBlockerComponent"/> per tile.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
[Access(typeof(RadiationSystem), Other = AccessPermissions.ReadExecute)]
|
||||||
|
public sealed class RadiationGridResistanceComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Radiation resistance per tile.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Dictionary<Vector2i, float> ResistancePerTile = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Content.Server.Radiation.Systems;
|
||||||
|
using Content.Shared.Radiation.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.Radiation.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks component that receive radiation from <see cref="RadiationSourceComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
[Access(typeof(RadiationSystem))]
|
||||||
|
public sealed class RadiationReceiverComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Current radiation value in rads per second.
|
||||||
|
/// Periodically updated by radiation system.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public float CurrentRadiation;
|
||||||
|
}
|
||||||
|
|
||||||
166
Content.Server/Radiation/Systems/RadiationSystem.Blockers.cs
Normal file
166
Content.Server/Radiation/Systems/RadiationSystem.Blockers.cs
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
using Content.Server.Radiation.Components;
|
||||||
|
using Content.Shared.Doors;
|
||||||
|
using Content.Shared.Doors.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.Radiation.Systems;
|
||||||
|
|
||||||
|
// create and update map of radiation blockers
|
||||||
|
public partial class RadiationSystem
|
||||||
|
{
|
||||||
|
private void InitRadBlocking()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<RadiationBlockerComponent, ComponentInit>(OnInit);
|
||||||
|
SubscribeLocalEvent<RadiationBlockerComponent, ComponentShutdown>(OnShutdown);
|
||||||
|
SubscribeLocalEvent<RadiationBlockerComponent, AnchorStateChangedEvent>(OnAnchorChanged);
|
||||||
|
SubscribeLocalEvent<RadiationBlockerComponent, ReAnchorEvent>(OnReAnchor);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<RadiationBlockerComponent, DoorStateChangedEvent>(OnDoorChanged);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<RadiationGridResistanceComponent, EntityTerminatingEvent>(OnGridRemoved);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInit(EntityUid uid, RadiationBlockerComponent component, ComponentInit args)
|
||||||
|
{
|
||||||
|
if (!component.Enabled)
|
||||||
|
return;
|
||||||
|
AddTile(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnShutdown(EntityUid uid, RadiationBlockerComponent component, ComponentShutdown args)
|
||||||
|
{
|
||||||
|
if (component.Enabled)
|
||||||
|
return;
|
||||||
|
RemoveTile(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAnchorChanged(EntityUid uid, RadiationBlockerComponent component, ref AnchorStateChangedEvent args)
|
||||||
|
{
|
||||||
|
if (args.Anchored)
|
||||||
|
{
|
||||||
|
AddTile(uid, component);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RemoveTile(uid, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnReAnchor(EntityUid uid, RadiationBlockerComponent component, ref ReAnchorEvent args)
|
||||||
|
{
|
||||||
|
// probably grid was split
|
||||||
|
// we need to remove entity from old resistance map
|
||||||
|
RemoveTile(uid, component);
|
||||||
|
// and move it to the new one
|
||||||
|
AddTile(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoorChanged(EntityUid uid, RadiationBlockerComponent component, DoorStateChangedEvent args)
|
||||||
|
{
|
||||||
|
switch (args.State)
|
||||||
|
{
|
||||||
|
case DoorState.Open:
|
||||||
|
SetEnabled(uid, false, component);
|
||||||
|
break;
|
||||||
|
case DoorState.Closed:
|
||||||
|
SetEnabled(uid, true, component);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGridRemoved(EntityUid uid, RadiationGridResistanceComponent component, ref EntityTerminatingEvent args)
|
||||||
|
{
|
||||||
|
// grid is about to be removed - lets delete grid component first
|
||||||
|
// this should save a bit performance when blockers will be deleted
|
||||||
|
RemComp(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetEnabled(EntityUid uid, bool isEnabled, RadiationBlockerComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
if (isEnabled == component.Enabled)
|
||||||
|
return;
|
||||||
|
component.Enabled = isEnabled;
|
||||||
|
|
||||||
|
if (!component.Enabled)
|
||||||
|
RemoveTile(uid, component);
|
||||||
|
else
|
||||||
|
AddTile(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddTile(EntityUid uid, RadiationBlockerComponent component)
|
||||||
|
{
|
||||||
|
// check that last position was removed
|
||||||
|
if (component.CurrentPosition != null)
|
||||||
|
{
|
||||||
|
RemoveTile(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if entity even provide some rad protection
|
||||||
|
if (!component.Enabled || component.RadResistance <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// check if it's on a grid
|
||||||
|
var trs = Transform(uid);
|
||||||
|
if (!trs.Anchored || !TryComp(trs.GridUid, out IMapGridComponent? grid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// save resistance into rad protection grid
|
||||||
|
var gridId = trs.GridUid.Value;
|
||||||
|
var tilePos = grid.Grid.TileIndicesFor(trs.Coordinates);
|
||||||
|
AddToTile(gridId, tilePos, component.RadResistance);
|
||||||
|
|
||||||
|
// and remember it as last valid position
|
||||||
|
component.CurrentPosition = (gridId, tilePos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveTile(EntityUid uid, RadiationBlockerComponent component)
|
||||||
|
{
|
||||||
|
// check if blocker was placed on grid before component was removed
|
||||||
|
if (component.CurrentPosition == null)
|
||||||
|
return;
|
||||||
|
var (gridId, tilePos) = component.CurrentPosition.Value;
|
||||||
|
|
||||||
|
// try to remove
|
||||||
|
RemoveFromTile(gridId, tilePos, component.RadResistance);
|
||||||
|
component.CurrentPosition = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddToTile(EntityUid gridUid, Vector2i tilePos, float radResistance)
|
||||||
|
{
|
||||||
|
// get existing rad resistance grid or create it if it doesn't exist
|
||||||
|
var resistance = EnsureComp<RadiationGridResistanceComponent>(gridUid);
|
||||||
|
var grid = resistance.ResistancePerTile;
|
||||||
|
|
||||||
|
// add to existing cell more rad resistance
|
||||||
|
var newResistance = radResistance;
|
||||||
|
if (grid.TryGetValue(tilePos, out var existingResistance))
|
||||||
|
{
|
||||||
|
newResistance += existingResistance;
|
||||||
|
}
|
||||||
|
grid[tilePos] = newResistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveFromTile(EntityUid gridUid, Vector2i tilePos, float radResistance)
|
||||||
|
{
|
||||||
|
// get grid
|
||||||
|
if (!TryComp(gridUid, out RadiationGridResistanceComponent? resistance))
|
||||||
|
return;
|
||||||
|
var grid = resistance.ResistancePerTile;
|
||||||
|
|
||||||
|
// subtract resistance from tile
|
||||||
|
if (!grid.TryGetValue(tilePos, out var existingResistance))
|
||||||
|
return;
|
||||||
|
existingResistance -= radResistance;
|
||||||
|
|
||||||
|
// remove tile from grid if no resistance left
|
||||||
|
if (existingResistance > 0)
|
||||||
|
grid[tilePos] = existingResistance;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
grid.Remove(tilePos);
|
||||||
|
if (grid.Count == 0)
|
||||||
|
RemComp(gridUid, resistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
Content.Server/Radiation/Systems/RadiationSystem.Cvar.cs
Normal file
48
Content.Server/Radiation/Systems/RadiationSystem.Cvar.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using Content.Shared.CCVar;
|
||||||
|
|
||||||
|
namespace Content.Server.Radiation.Systems;
|
||||||
|
|
||||||
|
// cvar updates
|
||||||
|
public partial class RadiationSystem
|
||||||
|
{
|
||||||
|
public float MinIntensity { get; private set; }
|
||||||
|
public float GridcastUpdateRate { get; private set; }
|
||||||
|
public bool GridcastSimplifiedSameGrid { get; private set; }
|
||||||
|
public float GridcastMaxDistance { get; private set; }
|
||||||
|
|
||||||
|
private void SubscribeCvars()
|
||||||
|
{
|
||||||
|
_cfg.OnValueChanged(CCVars.RadiationMinIntensity, SetMinRadiationIntensity, true);
|
||||||
|
_cfg.OnValueChanged(CCVars.RadiationGridcastUpdateRate, SetGridcastUpdateRate, true);
|
||||||
|
_cfg.OnValueChanged(CCVars.RadiationGridcastSimplifiedSameGrid, SetGridcastSimplifiedSameGrid, true);
|
||||||
|
_cfg.OnValueChanged(CCVars.RadiationGridcastMaxDistance, SetGridcastMaxDistance, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnsubscribeCvars()
|
||||||
|
{
|
||||||
|
_cfg.UnsubValueChanged(CCVars.RadiationMinIntensity, SetMinRadiationIntensity);
|
||||||
|
_cfg.UnsubValueChanged(CCVars.RadiationGridcastUpdateRate, SetGridcastUpdateRate);
|
||||||
|
_cfg.UnsubValueChanged(CCVars.RadiationGridcastSimplifiedSameGrid, SetGridcastSimplifiedSameGrid);
|
||||||
|
_cfg.UnsubValueChanged(CCVars.RadiationGridcastMaxDistance, SetGridcastMaxDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetMinRadiationIntensity(float radiationMinIntensity)
|
||||||
|
{
|
||||||
|
MinIntensity = radiationMinIntensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetGridcastUpdateRate(float updateRate)
|
||||||
|
{
|
||||||
|
GridcastUpdateRate = updateRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetGridcastSimplifiedSameGrid(bool simplifiedSameGrid)
|
||||||
|
{
|
||||||
|
GridcastSimplifiedSameGrid = simplifiedSameGrid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetGridcastMaxDistance(float maxDistance)
|
||||||
|
{
|
||||||
|
GridcastMaxDistance = maxDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
105
Content.Server/Radiation/Systems/RadiationSystem.Debug.cs
Normal file
105
Content.Server/Radiation/Systems/RadiationSystem.Debug.cs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Server.Administration;
|
||||||
|
using Content.Server.Radiation.Components;
|
||||||
|
using Content.Shared.Administration;
|
||||||
|
using Content.Shared.Radiation.Events;
|
||||||
|
using Content.Shared.Radiation.Systems;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Players;
|
||||||
|
|
||||||
|
namespace Content.Server.Radiation.Systems;
|
||||||
|
|
||||||
|
// radiation overlay debug logic
|
||||||
|
// rad rays send only to clients that enabled debug overlay
|
||||||
|
public partial class RadiationSystem
|
||||||
|
{
|
||||||
|
private readonly HashSet<ICommonSession> _debugSessions = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Toggle radiation debug overlay for selected player.
|
||||||
|
/// </summary>
|
||||||
|
public void ToggleDebugView(ICommonSession session)
|
||||||
|
{
|
||||||
|
bool isEnabled;
|
||||||
|
if (_debugSessions.Add(session))
|
||||||
|
{
|
||||||
|
isEnabled = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_debugSessions.Remove(session);
|
||||||
|
isEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ev = new OnRadiationOverlayToggledEvent(isEnabled);
|
||||||
|
RaiseNetworkEvent(ev, session.ConnectedClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send new information for radiation overlay.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateDebugOverlay(EntityEventArgs ev)
|
||||||
|
{
|
||||||
|
var sessions = _debugSessions.ToArray();
|
||||||
|
foreach (var session in sessions)
|
||||||
|
{
|
||||||
|
if (session.Status != SessionStatus.InGame)
|
||||||
|
_debugSessions.Remove(session);
|
||||||
|
RaiseNetworkEvent(ev, session.ConnectedClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateResistanceDebugOverlay()
|
||||||
|
{
|
||||||
|
if (_debugSessions.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var query = GetEntityQuery<RadiationGridResistanceComponent>();
|
||||||
|
var dict = new Dictionary<EntityUid, Dictionary<Vector2i, float>>();
|
||||||
|
|
||||||
|
foreach (var grid in _mapManager.GetAllGrids())
|
||||||
|
{
|
||||||
|
var gridUid = grid.GridEntityId;
|
||||||
|
if (!query.TryGetComponent(gridUid, out var resistance))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var resMap = resistance.ResistancePerTile;
|
||||||
|
dict.Add(gridUid, resMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
var ev = new OnRadiationOverlayResistanceUpdateEvent(dict);
|
||||||
|
UpdateDebugOverlay(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateGridcastDebugOverlay(double elapsedTime, int totalSources,
|
||||||
|
int totalReceivers, List<RadiationRay> rays)
|
||||||
|
{
|
||||||
|
if (_debugSessions.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ev = new OnRadiationOverlayUpdateEvent(elapsedTime, totalSources, totalReceivers, rays);
|
||||||
|
UpdateDebugOverlay(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Toggle visibility of radiation rays coming from rad sources.
|
||||||
|
/// </summary>
|
||||||
|
[AdminCommand(AdminFlags.Admin)]
|
||||||
|
public sealed class RadiationViewCommand : IConsoleCommand
|
||||||
|
{
|
||||||
|
public string Command => "showradiation";
|
||||||
|
public string Description => Loc.GetString("radiation-command-description");
|
||||||
|
public string Help => Loc.GetString("radiation-command-help");
|
||||||
|
|
||||||
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
var session = shell.Player;
|
||||||
|
if (session == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
entityManager.System<RadiationSystem>().ToggleDebugView(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
186
Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs
Normal file
186
Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
using Content.Server.Radiation.Components;
|
||||||
|
using Content.Shared.Radiation.Components;
|
||||||
|
using Content.Shared.Radiation.Systems;
|
||||||
|
using Robust.Shared.Collections;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.Radiation.Systems;
|
||||||
|
|
||||||
|
// main algorithm that fire radiation rays to target
|
||||||
|
public partial class RadiationSystem
|
||||||
|
{
|
||||||
|
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 stopwatch = new Stopwatch();
|
||||||
|
stopwatch.Start();
|
||||||
|
|
||||||
|
var sources = EntityQuery<RadiationSourceComponent, TransformComponent>();
|
||||||
|
var destinations = EntityQuery<RadiationReceiverComponent, TransformComponent>();
|
||||||
|
var resistanceQuery = GetEntityQuery<RadiationGridResistanceComponent>();
|
||||||
|
var transformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
|
||||||
|
// precalculate world positions for each source
|
||||||
|
// so we won't need to calc this in cycle over and over again
|
||||||
|
var sourcesData = new ValueList<(RadiationSourceComponent, TransformComponent, Vector2)>();
|
||||||
|
foreach (var (source, sourceTrs) in sources)
|
||||||
|
{
|
||||||
|
var worldPos = _transform.GetWorldPosition(sourceTrs, transformQuery);
|
||||||
|
var data = (source, sourceTrs, worldPos);
|
||||||
|
sourcesData.Add(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// trace all rays from rad source to rad receivers
|
||||||
|
var rays = new List<RadiationRay>();
|
||||||
|
var receiversTotalRads = new ValueList<(RadiationReceiverComponent, float)>();
|
||||||
|
foreach (var (dest, destTrs) in destinations)
|
||||||
|
{
|
||||||
|
var destWorld = _transform.GetWorldPosition(destTrs, transformQuery);
|
||||||
|
|
||||||
|
var rads = 0f;
|
||||||
|
foreach (var (source, sourceTrs, sourceWorld) in sourcesData)
|
||||||
|
{
|
||||||
|
// send ray towards destination entity
|
||||||
|
var ray = Irradiate(sourceTrs.Owner, sourceTrs, sourceWorld,
|
||||||
|
destTrs.Owner, destTrs, destWorld,
|
||||||
|
source.Intensity, source.Slope, saveVisitedTiles, resistanceQuery);
|
||||||
|
if (ray == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// save ray for debug
|
||||||
|
rays.Add(ray);
|
||||||
|
|
||||||
|
// add rads to total rad exposure
|
||||||
|
if (ray.ReachedDestination)
|
||||||
|
rads += ray.Rads;
|
||||||
|
}
|
||||||
|
|
||||||
|
receiversTotalRads.Add((dest, rads));
|
||||||
|
}
|
||||||
|
|
||||||
|
// update information for debug overlay
|
||||||
|
var elapsedTime = stopwatch.Elapsed.TotalMilliseconds;
|
||||||
|
var totalSources = sourcesData.Count;
|
||||||
|
var totalReceivers = receiversTotalRads.Count;
|
||||||
|
UpdateGridcastDebugOverlay(elapsedTime, totalSources, totalReceivers, rays);
|
||||||
|
|
||||||
|
// send rads to each entity
|
||||||
|
foreach (var (receiver, rads) in receiversTotalRads)
|
||||||
|
{
|
||||||
|
// update radiation value of receiver
|
||||||
|
// if no radiation rays reached target, that will set it to 0
|
||||||
|
receiver.CurrentRadiation = rads;
|
||||||
|
|
||||||
|
// also send an event with combination of total rad
|
||||||
|
if (rads > 0)
|
||||||
|
IrradiateEntity(receiver.Owner, rads,GridcastUpdateRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private RadiationRay? Irradiate(EntityUid sourceUid, TransformComponent sourceTrs, Vector2 sourceWorld,
|
||||||
|
EntityUid destUid, TransformComponent destTrs, Vector2 destWorld,
|
||||||
|
float incomingRads, float slope, bool saveVisitedTiles,
|
||||||
|
EntityQuery<RadiationGridResistanceComponent> resistanceQuery)
|
||||||
|
{
|
||||||
|
// lets first check that source and destination on the same map
|
||||||
|
if (sourceTrs.MapID != destTrs.MapID)
|
||||||
|
return null;
|
||||||
|
var mapId = sourceTrs.MapID;
|
||||||
|
|
||||||
|
// get direction from rad source to destination and its distance
|
||||||
|
var dir = destWorld - sourceWorld;
|
||||||
|
var dist = dir.Length;
|
||||||
|
|
||||||
|
// check if receiver is too far away
|
||||||
|
if (dist > GridcastMaxDistance)
|
||||||
|
return null;
|
||||||
|
// will it even reach destination considering distance penalty
|
||||||
|
var rads = incomingRads - 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, sourceUid, sourceWorld, 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)
|
||||||
|
{
|
||||||
|
// todo: entity queries doesn't support interface - use it when IMapGridComponent will be removed
|
||||||
|
if (!TryComp(sourceTrs.GridUid.Value, out IMapGridComponent? gridComponent))
|
||||||
|
return ray;
|
||||||
|
return Gridcast(gridComponent.Grid, ray, saveVisitedTiles, resistanceQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = _mapManager.FindGridsIntersecting(mapId, box, 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)
|
||||||
|
{
|
||||||
|
ray = Gridcast(grid, ray, saveVisitedTiles, resistanceQuery);
|
||||||
|
|
||||||
|
// looks like last grid blocked all radiation
|
||||||
|
// we can return right now
|
||||||
|
if (ray.Rads <= 0)
|
||||||
|
return ray;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ray;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RadiationRay Gridcast(IMapGrid grid, RadiationRay ray, bool saveVisitedTiles,
|
||||||
|
EntityQuery<RadiationGridResistanceComponent> resistanceQuery)
|
||||||
|
{
|
||||||
|
var blockers = new List<(Vector2i, float)>();
|
||||||
|
|
||||||
|
// if grid doesn't have resistance map just apply distance penalty
|
||||||
|
var gridUid = grid.GridEntityId;
|
||||||
|
if (!resistanceQuery.TryGetComponent(gridUid, out var resistance))
|
||||||
|
return ray;
|
||||||
|
var resistanceMap = resistance.ResistancePerTile;
|
||||||
|
|
||||||
|
// get coordinate of source and destination in grid coordinates
|
||||||
|
var sourceGrid = grid.TileIndicesFor(ray.Source);
|
||||||
|
var destGrid = grid.TileIndicesFor(ray.Destination);
|
||||||
|
|
||||||
|
// iterate tiles in grid line from source to destination
|
||||||
|
var line = new GridLineEnumerator(sourceGrid, destGrid);
|
||||||
|
while (line.MoveNext())
|
||||||
|
{
|
||||||
|
var point = line.Current;
|
||||||
|
if (!resistanceMap.TryGetValue(point, out var resData))
|
||||||
|
continue;
|
||||||
|
ray.Rads -= resData;
|
||||||
|
|
||||||
|
// save data for debug
|
||||||
|
if (saveVisitedTiles)
|
||||||
|
blockers.Add((point, ray.Rads));
|
||||||
|
|
||||||
|
// no intensity left after blocker
|
||||||
|
if (ray.Rads <= MinIntensity)
|
||||||
|
{
|
||||||
|
ray.Rads = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// save data for debug if needed
|
||||||
|
if (saveVisitedTiles && blockers.Count > 0)
|
||||||
|
ray.Blockers.Add(gridUid, blockers);
|
||||||
|
|
||||||
|
return ray;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,52 +1,46 @@
|
|||||||
using Content.Shared.Radiation.Events;
|
using Content.Shared.Radiation.Events;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
namespace Content.Server.Radiation.Systems;
|
namespace Content.Server.Radiation.Systems;
|
||||||
|
|
||||||
public sealed class RadiationSystem : EntitySystem
|
public sealed partial class RadiationSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
|
|
||||||
private const float RadiationCooldown = 1.0f;
|
|
||||||
private float _accumulator;
|
private float _accumulator;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeCvars();
|
||||||
|
InitRadBlocking();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
UnsubscribeCvars();
|
||||||
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
|
|
||||||
_accumulator += frameTime;
|
_accumulator += frameTime;
|
||||||
|
if (_accumulator < GridcastUpdateRate)
|
||||||
|
return;
|
||||||
|
|
||||||
while (_accumulator > RadiationCooldown)
|
UpdateGridcast();
|
||||||
{
|
UpdateResistanceDebugOverlay();
|
||||||
_accumulator -= RadiationCooldown;
|
_accumulator = 0f;
|
||||||
|
|
||||||
// All code here runs effectively every RadiationCooldown seconds, so use that as the "frame time".
|
|
||||||
foreach (var comp in EntityManager.EntityQuery<RadiationSourceComponent>())
|
|
||||||
{
|
|
||||||
var ent = comp.Owner;
|
|
||||||
if (Deleted(ent))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var cords = Transform(ent).MapPosition;
|
|
||||||
IrradiateRange(cords, comp.Range, comp.RadsPerSecond, RadiationCooldown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void IrradiateRange(MapCoordinates coordinates, float range, float radsPerSecond, float time)
|
|
||||||
{
|
|
||||||
var lookUp = _lookup.GetEntitiesInRange(coordinates, range);
|
|
||||||
foreach (var uid in lookUp)
|
|
||||||
{
|
|
||||||
if (Deleted(uid))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
IrradiateEntity(uid, radsPerSecond, time);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void IrradiateEntity(EntityUid uid, float radsPerSecond, float time)
|
public void IrradiateEntity(EntityUid uid, float radsPerSecond, float time)
|
||||||
{
|
{
|
||||||
var msg = new OnIrradiatedEvent(time, radsPerSecond);
|
var msg = new OnIrradiatedEvent(time, radsPerSecond);
|
||||||
RaiseLocalEvent(uid, msg, true);
|
RaiseLocalEvent(uid, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -623,6 +623,36 @@ namespace Content.Shared.CCVar
|
|||||||
public static readonly CVarDef<int> ExplosionSingleTickAreaLimit =
|
public static readonly CVarDef<int> ExplosionSingleTickAreaLimit =
|
||||||
CVarDef.Create("explosion.single_tick_area_limit", 400, CVar.SERVERONLY);
|
CVarDef.Create("explosion.single_tick_area_limit", 400, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Radiation
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What is the smallest radiation dose in rads that can be received by object.
|
||||||
|
/// Extremely small values may impact performance.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly CVarDef<float> RadiationMinIntensity =
|
||||||
|
CVarDef.Create("radiation.min_intensity", 0.1f, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rate of radiation system update in seconds.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly CVarDef<float> RadiationGridcastUpdateRate =
|
||||||
|
CVarDef.Create("radiation.gridcast.update_rate", 1.0f, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If both radiation source and receiver are placed on same grid, ignore grids between them.
|
||||||
|
/// May get inaccurate result in some cases, but greatly boost performance in general.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly CVarDef<bool> RadiationGridcastSimplifiedSameGrid =
|
||||||
|
CVarDef.Create("radiation.gridcast.simplified_same_grid", true, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Max distance that radiation ray can travel in meters.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly CVarDef<float> RadiationGridcastMaxDistance =
|
||||||
|
CVarDef.Create("radiation.gridcast.max_distance", 50f, CVar.SERVERONLY);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Admin logs
|
* Admin logs
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
namespace Content.Shared.Radiation.Components;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Irradiate all objects in range.
|
/// Irradiate all objects in range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -5,16 +7,20 @@
|
|||||||
public sealed class RadiationSourceComponent : Component
|
public sealed class RadiationSourceComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How many rads per second receive irradiated object.
|
/// Radiation intensity in center of the source in rads per second.
|
||||||
|
/// From there radiation rays will travel over distance and loose intensity
|
||||||
|
/// when hit radiation blocker.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("radsPerSecond")]
|
[DataField("intensity")]
|
||||||
public float RadsPerSecond = 1;
|
public float Intensity = 1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Radius of radiation source.
|
/// Defines how fast radiation rays will loose intensity
|
||||||
|
/// over distance. The bigger the value, the shorter range
|
||||||
|
/// of radiation source will be.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("range")]
|
[DataField("slope")]
|
||||||
public float Range = 5f;
|
public float Slope = 0.5f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
using Content.Shared.Radiation.Components;
|
||||||
|
using Content.Shared.Radiation.Systems;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Radiation.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised on server as networked event when radiation system update its state
|
||||||
|
/// and emitted all rays from rad sources towards rad receivers.
|
||||||
|
/// Contains debug information about rad rays and all blockers on their way.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Will be sent only to clients that activated radiation view using console command.
|
||||||
|
/// </remarks>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class OnRadiationOverlayUpdateEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Total time in milliseconds that server took to do radiation processing.
|
||||||
|
/// Exclude time of entities reacting to <see cref="OnIrradiatedEvent"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly double ElapsedTimeMs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total count of entities with <see cref="RadiationSourceComponent"/> on all maps.
|
||||||
|
/// </summary>
|
||||||
|
public readonly int SourcesCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total count of entities with radiation receiver on all maps.
|
||||||
|
/// </summary>
|
||||||
|
public readonly int ReceiversCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All radiation rays that was processed by radiation system.
|
||||||
|
/// </summary>
|
||||||
|
public readonly List<RadiationRay> Rays;
|
||||||
|
|
||||||
|
public OnRadiationOverlayUpdateEvent(double elapsedTimeMs, int sourcesCount, int receiversCount, List<RadiationRay> rays)
|
||||||
|
{
|
||||||
|
ElapsedTimeMs = elapsedTimeMs;
|
||||||
|
SourcesCount = sourcesCount;
|
||||||
|
ReceiversCount = receiversCount;
|
||||||
|
Rays = rays;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when server enabled/disabled radiation debug view for client.
|
||||||
|
/// After that client will start/stop receiving <see cref="OnRadiationOverlayUpdateEvent"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class OnRadiationOverlayToggledEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Does debug radiation view enabled.
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool IsEnabled;
|
||||||
|
|
||||||
|
public OnRadiationOverlayToggledEvent(bool isEnabled)
|
||||||
|
{
|
||||||
|
IsEnabled = isEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when grid resistance was update for radiation overlay visualization.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class OnRadiationOverlayResistanceUpdateEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Key is grids uid. Values are tiles with their rad resistance.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Dictionary<EntityUid, Dictionary<Vector2i, float>> Grids;
|
||||||
|
|
||||||
|
public OnRadiationOverlayResistanceUpdateEvent(Dictionary<EntityUid, Dictionary<Vector2i, float>> grids)
|
||||||
|
{
|
||||||
|
Grids = grids;
|
||||||
|
}
|
||||||
|
}
|
||||||
64
Content.Shared/Radiation/RadiationRay.cs
Normal file
64
Content.Shared/Radiation/RadiationRay.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using Content.Shared.Radiation.Components;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Radiation.Systems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ray emitted by radiation source towards radiation receiver.
|
||||||
|
/// Contains all information about encountered radiation blockers.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class RadiationRay
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Map on which source and receiver are placed.
|
||||||
|
/// </summary>
|
||||||
|
public MapId MapId;
|
||||||
|
/// <summary>
|
||||||
|
/// Uid of entity with <see cref="RadiationSourceComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
public EntityUid SourceUid;
|
||||||
|
/// <summary>
|
||||||
|
/// World coordinates of radiation source.
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 Source;
|
||||||
|
/// <summary>
|
||||||
|
/// Uid of entity with radiation receiver component.
|
||||||
|
/// </summary>
|
||||||
|
public EntityUid DestinationUid;
|
||||||
|
/// <summary>
|
||||||
|
/// World coordinates of radiation receiver.
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 Destination;
|
||||||
|
/// <summary>
|
||||||
|
/// How many rads intensity reached radiation receiver.
|
||||||
|
/// </summary>
|
||||||
|
public float Rads;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Has rad ray reached destination or lost all intensity after blockers?
|
||||||
|
/// </summary>
|
||||||
|
public bool ReachedDestination => Rads > 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All blockers visited by gridcast. Key is uid of grid. Values are pairs
|
||||||
|
/// of tile indices and floats with updated radiation value.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Last tile may have negative value if ray has lost all intensity.
|
||||||
|
/// Grid traversal order isn't guaranteed.
|
||||||
|
/// </remarks>
|
||||||
|
public Dictionary<EntityUid, List<(Vector2i, float)>> Blockers = new();
|
||||||
|
|
||||||
|
public RadiationRay(MapId mapId, EntityUid sourceUid, Vector2 source,
|
||||||
|
EntityUid destinationUid, Vector2 destination, float rads)
|
||||||
|
{
|
||||||
|
MapId = mapId;
|
||||||
|
SourceUid = sourceUid;
|
||||||
|
Source = source;
|
||||||
|
DestinationUid = destinationUid;
|
||||||
|
Destination = destination;
|
||||||
|
Rads = rads;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,8 +25,8 @@ public sealed class RadiationPulseSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
// try to get radiation range or keep default visual range
|
// try to get radiation range or keep default visual range
|
||||||
if (TryComp<RadiationSourceComponent>(uid, out var radSource))
|
if (TryComp<RadiationSourceComponent>(uid, out var radSource))
|
||||||
{
|
{
|
||||||
component.VisualRange = radSource.Range;
|
component.VisualRange = radSource.Intensity / radSource.Slope;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Content.Shared.Ghost;
|
using Content.Shared.Ghost;
|
||||||
using Content.Shared.Radiation;
|
using Content.Shared.Radiation;
|
||||||
|
using Content.Shared.Radiation.Components;
|
||||||
using Content.Shared.Singularity.Components;
|
using Content.Shared.Singularity.Components;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Physics.Collision.Shapes;
|
using Robust.Shared.Physics.Collision.Shapes;
|
||||||
@@ -109,7 +110,7 @@ namespace Content.Shared.Singularity
|
|||||||
|
|
||||||
if (EntityManager.TryGetComponent(singularity.Owner, out RadiationSourceComponent? source))
|
if (EntityManager.TryGetComponent(singularity.Owner, out RadiationSourceComponent? source))
|
||||||
{
|
{
|
||||||
source.RadsPerSecond = singularity.RadsPerLevel * value;
|
source.Intensity = singularity.RadsPerLevel * value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EntityManager.TryGetComponent(singularity.Owner, out AppearanceComponent? appearance))
|
if (EntityManager.TryGetComponent(singularity.Owner, out AppearanceComponent? appearance))
|
||||||
|
|||||||
2
Resources/Locale/en-US/radiation/radiation-command.ftl
Normal file
2
Resources/Locale/en-US/radiation/radiation-command.ftl
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
radiation-command-description = Toggle visibility of radiation rays coming from rad sources
|
||||||
|
radiation-command-help = Usage: showradiation
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
description: Looking at this anomaly makes you feel strange, like something is pushing at your eyes.
|
description: Looking at this anomaly makes you feel strange, like something is pushing at your eyes.
|
||||||
components:
|
components:
|
||||||
- type: RadiationSource
|
- type: RadiationSource
|
||||||
radsPerSecond: 5
|
intensity: 5
|
||||||
- type: TimedDespawn
|
- type: TimedDespawn
|
||||||
lifetime: 2
|
lifetime: 2
|
||||||
- type: EmitSoundOnSpawn
|
- type: EmitSoundOnSpawn
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
-0.25
|
-0.25
|
||||||
- type: Damageable
|
- type: Damageable
|
||||||
damageContainer: Biological
|
damageContainer: Biological
|
||||||
|
- type: RadiationReceiver
|
||||||
- type: AtmosExposed
|
- type: AtmosExposed
|
||||||
- type: Flammable
|
- type: Flammable
|
||||||
fireSpread: true
|
fireSpread: true
|
||||||
|
|||||||
@@ -178,6 +178,7 @@
|
|||||||
preset: HumanPreset
|
preset: HumanPreset
|
||||||
- type: Damageable
|
- type: Damageable
|
||||||
damageContainer: Biological
|
damageContainer: Biological
|
||||||
|
- type: RadiationReceiver
|
||||||
- type: ThermalRegulator
|
- type: ThermalRegulator
|
||||||
metabolismHeat: 800
|
metabolismHeat: 800
|
||||||
radiatedHeat: 100
|
radiatedHeat: 100
|
||||||
|
|||||||
@@ -119,6 +119,8 @@
|
|||||||
node: glassAirlock
|
node: glassAirlock
|
||||||
- type: PaintableAirlock
|
- type: PaintableAirlock
|
||||||
group: Windoor
|
group: Windoor
|
||||||
|
- type: RadiationBlocker
|
||||||
|
resistance: 2
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: AirlockGlass
|
parent: AirlockGlass
|
||||||
|
|||||||
@@ -79,6 +79,8 @@
|
|||||||
- type: Airtight
|
- type: Airtight
|
||||||
fixVacuum: true
|
fixVacuum: true
|
||||||
noAirWhenFullyAirBlocked: false
|
noAirWhenFullyAirBlocked: false
|
||||||
|
- type: RadiationBlocker
|
||||||
|
resistance: 3
|
||||||
- type: Occluder
|
- type: Occluder
|
||||||
- type: Damageable
|
- type: Damageable
|
||||||
damageContainer: Inorganic
|
damageContainer: Inorganic
|
||||||
|
|||||||
@@ -93,6 +93,8 @@
|
|||||||
fixVacuum: true
|
fixVacuum: true
|
||||||
airBlocked: false
|
airBlocked: false
|
||||||
noAirWhenFullyAirBlocked: true
|
noAirWhenFullyAirBlocked: true
|
||||||
|
- type: RadiationBlocker
|
||||||
|
enabled: false
|
||||||
- type: Occluder
|
- type: Occluder
|
||||||
enabled: false
|
enabled: false
|
||||||
- type: Construction
|
- type: Construction
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
- type: AirlockVisualizer
|
- type: AirlockVisualizer
|
||||||
simpleVisuals: true
|
simpleVisuals: true
|
||||||
animationTime: 1.0
|
animationTime: 1.0
|
||||||
|
- type: RadiationBlocker
|
||||||
|
resistance: 8
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: BlastDoorOpen
|
id: BlastDoorOpen
|
||||||
@@ -39,3 +41,5 @@
|
|||||||
canCollide: false
|
canCollide: false
|
||||||
- type: Airtight
|
- type: Airtight
|
||||||
airBlocked: false
|
airBlocked: false
|
||||||
|
- type: RadiationBlocker
|
||||||
|
enabled: false
|
||||||
|
|||||||
@@ -51,6 +51,8 @@
|
|||||||
type: WiresBoundUserInterface
|
type: WiresBoundUserInterface
|
||||||
- type: Airtight
|
- type: Airtight
|
||||||
fixVacuum: true
|
fixVacuum: true
|
||||||
|
- type: RadiationBlocker
|
||||||
|
resistance: 2
|
||||||
- type: Damageable
|
- type: Damageable
|
||||||
damageContainer: Inorganic
|
damageContainer: Inorganic
|
||||||
damageModifierSet: Metallic
|
damageModifierSet: Metallic
|
||||||
@@ -101,6 +103,8 @@
|
|||||||
enabled: false
|
enabled: false
|
||||||
- type: Airtight
|
- type: Airtight
|
||||||
airBlocked: false
|
airBlocked: false
|
||||||
|
- type: RadiationBlocker
|
||||||
|
enabled: false
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: Shutters
|
graph: Shutters
|
||||||
node: Shutters
|
node: Shutters
|
||||||
@@ -138,6 +142,8 @@
|
|||||||
canCollide: false
|
canCollide: false
|
||||||
- type: Airtight
|
- type: Airtight
|
||||||
airBlocked: false
|
airBlocked: false
|
||||||
|
- type: RadiationBlocker
|
||||||
|
enabled: false
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: ShuttersWindow
|
id: ShuttersWindow
|
||||||
@@ -169,6 +175,8 @@
|
|||||||
canCollide: false
|
canCollide: false
|
||||||
- type: Airtight
|
- type: Airtight
|
||||||
airBlocked: false
|
airBlocked: false
|
||||||
|
- type: RadiationBlocker
|
||||||
|
enabled: false
|
||||||
|
|
||||||
# Frame for construction
|
# Frame for construction
|
||||||
- type: entity
|
- type: entity
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
- type: BatteryDischarger
|
- type: BatteryDischarger
|
||||||
# This is JUST a default. It has to be dynamically adjusted to ensure that the battery doesn't discharge "too fast" & run out immediately, while still scaling by input power.
|
# This is JUST a default. It has to be dynamically adjusted to ensure that the battery doesn't discharge "too fast" & run out immediately, while still scaling by input power.
|
||||||
activeSupplyRate: 100000
|
activeSupplyRate: 100000
|
||||||
|
- type: RadiationReceiver
|
||||||
- type: Anchorable
|
- type: Anchorable
|
||||||
- type: Rotatable
|
- type: Rotatable
|
||||||
- type: Pullable
|
- type: Pullable
|
||||||
|
|||||||
@@ -24,10 +24,10 @@
|
|||||||
layer:
|
layer:
|
||||||
- AllMask
|
- AllMask
|
||||||
- type: Singularity
|
- type: Singularity
|
||||||
radsPerLevel: 1
|
radsPerLevel: 2
|
||||||
- type: SingularityDistortion
|
- type: SingularityDistortion
|
||||||
- type: RadiationSource
|
- type: RadiationSource
|
||||||
range: 10
|
slope: 0.2 # its emit really far away
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Structures/Power/Generation/Singularity/singularity_1.rsi
|
sprite: Structures/Power/Generation/Singularity/singularity_1.rsi
|
||||||
state: singularity_1
|
state: singularity_1
|
||||||
|
|||||||
@@ -246,4 +246,4 @@
|
|||||||
- state: rtg_damaged
|
- state: rtg_damaged
|
||||||
- state: rtg_glow
|
- state: rtg_glow
|
||||||
- type: RadiationSource # ideally only when opened.
|
- type: RadiationSource # ideally only when opened.
|
||||||
range: 2
|
intensity: 2
|
||||||
|
|||||||
@@ -47,6 +47,8 @@
|
|||||||
- type: Airtight
|
- type: Airtight
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 75
|
price: 75
|
||||||
|
- type: RadiationBlocker
|
||||||
|
resistance: 2
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseWall
|
parent: BaseWall
|
||||||
@@ -451,6 +453,8 @@
|
|||||||
- type: ReinforcedWallVisualizer
|
- type: ReinforcedWallVisualizer
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 150
|
price: 150
|
||||||
|
- type: RadiationBlocker
|
||||||
|
resistance: 5
|
||||||
|
|
||||||
# Riveting
|
# Riveting
|
||||||
- type: entity
|
- type: entity
|
||||||
|
|||||||
@@ -41,6 +41,8 @@
|
|||||||
sprite: Structures/Windows/cracks.rsi
|
sprite: Structures/Windows/cracks.rsi
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 20.5
|
price: 20.5
|
||||||
|
- type: RadiationBlocker
|
||||||
|
resistance: 2
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: PlasmaWindowDirectional
|
id: PlasmaWindowDirectional
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
- type: Damageable
|
- type: Damageable
|
||||||
damageContainer: Inorganic
|
damageContainer: Inorganic
|
||||||
damageModifierSet: RGlass
|
damageModifierSet: RGlass
|
||||||
|
- type: RadiationBlocker
|
||||||
|
resistance: 4
|
||||||
- type: Destructible
|
- type: Destructible
|
||||||
thresholds:
|
thresholds:
|
||||||
- trigger:
|
- trigger:
|
||||||
|
|||||||
@@ -525,6 +525,8 @@ public sealed class $CLASS$ : Shared$CLASS$ {
|
|||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=gamemode/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=gamemode/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Gibs/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Gibs/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=godmode/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=godmode/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=gridcast/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Grindable/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Grindable/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=hardcode/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=hardcode/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=hbox/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=hbox/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|||||||
Reference in New Issue
Block a user