From 7d882f22c9869bcd73a950527970d8c8b530a250 Mon Sep 17 00:00:00 2001 From: Alex Evgrashin Date: Tue, 11 Oct 2022 05:09:10 +0200 Subject: [PATCH] Radiation rework (#10970) --- Content.Client/Entry/EntryPoint.cs | 2 +- .../Overlays/RadiationDebugOverlay.cs | 131 ++++++++++++ .../{ => Overlays}/RadiationPulseOverlay.cs | 2 +- .../Radiation/Systems/RadiationSystem.cs | 54 +++++ .../Components/RadiationBlockerComponent.cs | 29 +++ .../RadiationGridResistanceComponent.cs | 16 ++ .../Components/RadiationReceiverComponent.cs | 20 ++ .../Systems/RadiationSystem.Blockers.cs | 166 ++++++++++++++++ .../Radiation/Systems/RadiationSystem.Cvar.cs | 48 +++++ .../Systems/RadiationSystem.Debug.cs | 105 ++++++++++ .../Systems/RadiationSystem.GridCast.cs | 186 ++++++++++++++++++ .../Radiation/Systems/RadiationSystem.cs | 56 +++--- Content.Shared/CCVar/CCVars.cs | 30 +++ .../Components/RadiationSourceComponent.cs | 18 +- .../Events/OnRadiationOverlayUpdateEvent.cs | 81 ++++++++ Content.Shared/Radiation/RadiationRay.cs | 64 ++++++ .../Radiation/Systems/RadiationPulseSystem.cs | 4 +- .../Singularity/SharedSingularitySystem.cs | 3 +- .../en-US/radiation/radiation-command.ftl | 2 + .../Prototypes/Entities/Effects/radiation.yml | 2 +- .../Entities/Mobs/NPCs/simplemob.yml | 1 + .../Prototypes/Entities/Mobs/Species/base.yml | 1 + .../Structures/Doors/Airlocks/airlocks.yml | 2 + .../Doors/Airlocks/base_structureairlocks.yml | 2 + .../Structures/Doors/Firelocks/firelock.yml | 2 + .../Structures/Doors/Shutter/blast_door.yml | 4 + .../Structures/Doors/Shutter/shutters.yml | 8 + .../Generation/Singularity/collector.yml | 1 + .../Generation/Singularity/singularity.yml | 4 +- .../Power/Generation/generators.yml | 2 +- .../Entities/Structures/Walls/walls.yml | 4 + .../Entities/Structures/Windows/plasma.yml | 2 + .../Entities/Structures/Windows/rplasma.yml | 2 + SpaceStation14.sln.DotSettings | 2 + 34 files changed, 1010 insertions(+), 46 deletions(-) create mode 100644 Content.Client/Radiation/Overlays/RadiationDebugOverlay.cs rename Content.Client/Radiation/{ => Overlays}/RadiationPulseOverlay.cs (99%) create mode 100644 Content.Client/Radiation/Systems/RadiationSystem.cs create mode 100644 Content.Server/Radiation/Components/RadiationBlockerComponent.cs create mode 100644 Content.Server/Radiation/Components/RadiationGridResistanceComponent.cs create mode 100644 Content.Server/Radiation/Components/RadiationReceiverComponent.cs create mode 100644 Content.Server/Radiation/Systems/RadiationSystem.Blockers.cs create mode 100644 Content.Server/Radiation/Systems/RadiationSystem.Cvar.cs create mode 100644 Content.Server/Radiation/Systems/RadiationSystem.Debug.cs create mode 100644 Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs create mode 100644 Content.Shared/Radiation/Events/OnRadiationOverlayUpdateEvent.cs create mode 100644 Content.Shared/Radiation/RadiationRay.cs create mode 100644 Resources/Locale/en-US/radiation/radiation-command.ftl diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 586f7d271d..78563a883c 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -15,7 +15,7 @@ using Content.Client.MainMenu; using Content.Client.Parallax.Managers; using Content.Client.Players.PlayTimeTracking; using Content.Client.Preferences; -using Content.Client.Radiation; +using Content.Client.Radiation.Overlays; using Content.Client.Screenshot; using Content.Client.Singularity; using Content.Client.Stylesheets; diff --git a/Content.Client/Radiation/Overlays/RadiationDebugOverlay.cs b/Content.Client/Radiation/Overlays/RadiationDebugOverlay.cs new file mode 100644 index 0000000000..1a7419517b --- /dev/null +++ b/Content.Client/Radiation/Overlays/RadiationDebugOverlay.cs @@ -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(); + + var cache = IoCManager.Resolve(); + _font = new VectorFont(cache.GetResource("/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(); + 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); + } + } + } +} diff --git a/Content.Client/Radiation/RadiationPulseOverlay.cs b/Content.Client/Radiation/Overlays/RadiationPulseOverlay.cs similarity index 99% rename from Content.Client/Radiation/RadiationPulseOverlay.cs rename to Content.Client/Radiation/Overlays/RadiationPulseOverlay.cs index f4f7ddc27b..19f22f5e02 100644 --- a/Content.Client/Radiation/RadiationPulseOverlay.cs +++ b/Content.Client/Radiation/Overlays/RadiationPulseOverlay.cs @@ -6,7 +6,7 @@ using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Timing; -namespace Content.Client.Radiation +namespace Content.Client.Radiation.Overlays { public sealed class RadiationPulseOverlay : Overlay { diff --git a/Content.Client/Radiation/Systems/RadiationSystem.cs b/Content.Client/Radiation/Systems/RadiationSystem.cs new file mode 100644 index 0000000000..9288c60652 --- /dev/null +++ b/Content.Client/Radiation/Systems/RadiationSystem.cs @@ -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? Rays; + public Dictionary>? ResistanceGrids; + + public override void Initialize() + { + SubscribeNetworkEvent(OnOverlayToggled); + SubscribeNetworkEvent(OnOverlayUpdate); + SubscribeNetworkEvent(OnResistanceUpdate); + } + + public override void Shutdown() + { + base.Shutdown(); + _overlayMan.RemoveOverlay(); + } + + private void OnOverlayToggled(OnRadiationOverlayToggledEvent ev) + { + if (ev.IsEnabled) + _overlayMan.AddOverlay(new RadiationDebugOverlay()); + else + _overlayMan.RemoveOverlay(); + } + + 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; + } +} diff --git a/Content.Server/Radiation/Components/RadiationBlockerComponent.cs b/Content.Server/Radiation/Components/RadiationBlockerComponent.cs new file mode 100644 index 0000000000..d030048a94 --- /dev/null +++ b/Content.Server/Radiation/Components/RadiationBlockerComponent.cs @@ -0,0 +1,29 @@ +using Content.Server.Radiation.Systems; + +namespace Content.Server.Radiation.Components; + +/// +/// Blocks radiation when placed on tile. +/// +[RegisterComponent] +[Access(typeof(RadiationSystem))] +public sealed class RadiationBlockerComponent : Component +{ + /// + /// Does it block radiation at all? + /// + [DataField("enabled")] + public bool Enabled = true; + + /// + /// How many rads per second does the blocker absorb? + /// + [DataField("resistance")] + public float RadResistance = 1f; + + /// + /// Current position of the rad blocker in grid coordinates. + /// Null if doesn't anchored or doesn't block rads. + /// + public (EntityUid Grid, Vector2i Tile)? CurrentPosition; +} diff --git a/Content.Server/Radiation/Components/RadiationGridResistanceComponent.cs b/Content.Server/Radiation/Components/RadiationGridResistanceComponent.cs new file mode 100644 index 0000000000..72f49f1297 --- /dev/null +++ b/Content.Server/Radiation/Components/RadiationGridResistanceComponent.cs @@ -0,0 +1,16 @@ +using Content.Server.Radiation.Systems; + +namespace Content.Server.Radiation.Components; + +/// +/// Grid component that stores radiation resistance of per tile. +/// +[RegisterComponent] +[Access(typeof(RadiationSystem), Other = AccessPermissions.ReadExecute)] +public sealed class RadiationGridResistanceComponent : Component +{ + /// + /// Radiation resistance per tile. + /// + public readonly Dictionary ResistancePerTile = new(); +} diff --git a/Content.Server/Radiation/Components/RadiationReceiverComponent.cs b/Content.Server/Radiation/Components/RadiationReceiverComponent.cs new file mode 100644 index 0000000000..3021f2907f --- /dev/null +++ b/Content.Server/Radiation/Components/RadiationReceiverComponent.cs @@ -0,0 +1,20 @@ +using Content.Server.Radiation.Systems; +using Content.Shared.Radiation.Components; + +namespace Content.Server.Radiation.Components; + +/// +/// Marks component that receive radiation from . +/// +[RegisterComponent] +[Access(typeof(RadiationSystem))] +public sealed class RadiationReceiverComponent : Component +{ + /// + /// Current radiation value in rads per second. + /// Periodically updated by radiation system. + /// + [ViewVariables(VVAccess.ReadOnly)] + public float CurrentRadiation; +} + diff --git a/Content.Server/Radiation/Systems/RadiationSystem.Blockers.cs b/Content.Server/Radiation/Systems/RadiationSystem.Blockers.cs new file mode 100644 index 0000000000..3c6e01c894 --- /dev/null +++ b/Content.Server/Radiation/Systems/RadiationSystem.Blockers.cs @@ -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(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnAnchorChanged); + SubscribeLocalEvent(OnReAnchor); + + SubscribeLocalEvent(OnDoorChanged); + + SubscribeLocalEvent(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(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); + } + } +} diff --git a/Content.Server/Radiation/Systems/RadiationSystem.Cvar.cs b/Content.Server/Radiation/Systems/RadiationSystem.Cvar.cs new file mode 100644 index 0000000000..ffe533f9a6 --- /dev/null +++ b/Content.Server/Radiation/Systems/RadiationSystem.Cvar.cs @@ -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; + } +} diff --git a/Content.Server/Radiation/Systems/RadiationSystem.Debug.cs b/Content.Server/Radiation/Systems/RadiationSystem.Debug.cs new file mode 100644 index 0000000000..3e19750f2f --- /dev/null +++ b/Content.Server/Radiation/Systems/RadiationSystem.Debug.cs @@ -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 _debugSessions = new(); + + /// + /// Toggle radiation debug overlay for selected player. + /// + 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); + } + + /// + /// Send new information for radiation overlay. + /// + 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(); + var dict = new Dictionary>(); + + 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 rays) + { + if (_debugSessions.Count == 0) + return; + + var ev = new OnRadiationOverlayUpdateEvent(elapsedTime, totalSources, totalReceivers, rays); + UpdateDebugOverlay(ev); + } +} + +/// +/// Toggle visibility of radiation rays coming from rad sources. +/// +[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(); + entityManager.System().ToggleDebugView(session); + } +} diff --git a/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs b/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs new file mode 100644 index 0000000000..20882cce57 --- /dev/null +++ b/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs @@ -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(); + var destinations = EntityQuery(); + var resistanceQuery = GetEntityQuery(); + var transformQuery = GetEntityQuery(); + + // 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(); + 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 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 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; + } +} diff --git a/Content.Server/Radiation/Systems/RadiationSystem.cs b/Content.Server/Radiation/Systems/RadiationSystem.cs index 2c03ca3e4b..d6442013f7 100644 --- a/Content.Server/Radiation/Systems/RadiationSystem.cs +++ b/Content.Server/Radiation/Systems/RadiationSystem.cs @@ -1,52 +1,46 @@ using Content.Shared.Radiation.Events; +using Robust.Shared.Configuration; using Robust.Shared.Map; 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; + public override void Initialize() + { + base.Initialize(); + SubscribeCvars(); + InitRadBlocking(); + } + + public override void Shutdown() + { + base.Shutdown(); + UnsubscribeCvars(); + } + public override void Update(float frameTime) { base.Update(frameTime); + _accumulator += frameTime; + if (_accumulator < GridcastUpdateRate) + return; - while (_accumulator > RadiationCooldown) - { - _accumulator -= RadiationCooldown; - - // All code here runs effectively every RadiationCooldown seconds, so use that as the "frame time". - foreach (var comp in EntityManager.EntityQuery()) - { - 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); - } + UpdateGridcast(); + UpdateResistanceDebugOverlay(); + _accumulator = 0f; } public void IrradiateEntity(EntityUid uid, float radsPerSecond, float time) { var msg = new OnIrradiatedEvent(time, radsPerSecond); - RaiseLocalEvent(uid, msg, true); + RaiseLocalEvent(uid, msg); } } diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 8c863cae01..f98b96e8cc 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -623,6 +623,36 @@ namespace Content.Shared.CCVar public static readonly CVarDef ExplosionSingleTickAreaLimit = CVarDef.Create("explosion.single_tick_area_limit", 400, CVar.SERVERONLY); + /* + * Radiation + */ + + /// + /// What is the smallest radiation dose in rads that can be received by object. + /// Extremely small values may impact performance. + /// + public static readonly CVarDef RadiationMinIntensity = + CVarDef.Create("radiation.min_intensity", 0.1f, CVar.SERVERONLY); + + /// + /// Rate of radiation system update in seconds. + /// + public static readonly CVarDef RadiationGridcastUpdateRate = + CVarDef.Create("radiation.gridcast.update_rate", 1.0f, CVar.SERVERONLY); + + /// + /// 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. + /// + public static readonly CVarDef RadiationGridcastSimplifiedSameGrid = + CVarDef.Create("radiation.gridcast.simplified_same_grid", true, CVar.SERVERONLY); + + /// + /// Max distance that radiation ray can travel in meters. + /// + public static readonly CVarDef RadiationGridcastMaxDistance = + CVarDef.Create("radiation.gridcast.max_distance", 50f, CVar.SERVERONLY); + /* * Admin logs */ diff --git a/Content.Shared/Radiation/Components/RadiationSourceComponent.cs b/Content.Shared/Radiation/Components/RadiationSourceComponent.cs index a793ee4a95..968d7c0341 100644 --- a/Content.Shared/Radiation/Components/RadiationSourceComponent.cs +++ b/Content.Shared/Radiation/Components/RadiationSourceComponent.cs @@ -1,3 +1,5 @@ +namespace Content.Shared.Radiation.Components; + /// /// Irradiate all objects in range. /// @@ -5,16 +7,20 @@ public sealed class RadiationSourceComponent : Component { /// - /// 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. /// [ViewVariables(VVAccess.ReadWrite)] - [DataField("radsPerSecond")] - public float RadsPerSecond = 1; + [DataField("intensity")] + public float Intensity = 1; /// - /// 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. /// [ViewVariables(VVAccess.ReadWrite)] - [DataField("range")] - public float Range = 5f; + [DataField("slope")] + public float Slope = 0.5f; } diff --git a/Content.Shared/Radiation/Events/OnRadiationOverlayUpdateEvent.cs b/Content.Shared/Radiation/Events/OnRadiationOverlayUpdateEvent.cs new file mode 100644 index 0000000000..54a528a9ea --- /dev/null +++ b/Content.Shared/Radiation/Events/OnRadiationOverlayUpdateEvent.cs @@ -0,0 +1,81 @@ +using Content.Shared.Radiation.Components; +using Content.Shared.Radiation.Systems; +using Robust.Shared.Serialization; + +namespace Content.Shared.Radiation.Events; + +/// +/// 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. +/// +/// +/// Will be sent only to clients that activated radiation view using console command. +/// +[Serializable, NetSerializable] +public sealed class OnRadiationOverlayUpdateEvent : EntityEventArgs +{ + /// + /// Total time in milliseconds that server took to do radiation processing. + /// Exclude time of entities reacting to . + /// + public readonly double ElapsedTimeMs; + + /// + /// Total count of entities with on all maps. + /// + public readonly int SourcesCount; + + /// + /// Total count of entities with radiation receiver on all maps. + /// + public readonly int ReceiversCount; + + /// + /// All radiation rays that was processed by radiation system. + /// + public readonly List Rays; + + public OnRadiationOverlayUpdateEvent(double elapsedTimeMs, int sourcesCount, int receiversCount, List rays) + { + ElapsedTimeMs = elapsedTimeMs; + SourcesCount = sourcesCount; + ReceiversCount = receiversCount; + Rays = rays; + } +} + +/// +/// Raised when server enabled/disabled radiation debug view for client. +/// After that client will start/stop receiving . +/// +[Serializable, NetSerializable] +public sealed class OnRadiationOverlayToggledEvent : EntityEventArgs +{ + /// + /// Does debug radiation view enabled. + /// + public readonly bool IsEnabled; + + public OnRadiationOverlayToggledEvent(bool isEnabled) + { + IsEnabled = isEnabled; + } +} + +/// +/// Raised when grid resistance was update for radiation overlay visualization. +/// +[Serializable, NetSerializable] +public sealed class OnRadiationOverlayResistanceUpdateEvent : EntityEventArgs +{ + /// + /// Key is grids uid. Values are tiles with their rad resistance. + /// + public readonly Dictionary> Grids; + + public OnRadiationOverlayResistanceUpdateEvent(Dictionary> grids) + { + Grids = grids; + } +} diff --git a/Content.Shared/Radiation/RadiationRay.cs b/Content.Shared/Radiation/RadiationRay.cs new file mode 100644 index 0000000000..662d707dfd --- /dev/null +++ b/Content.Shared/Radiation/RadiationRay.cs @@ -0,0 +1,64 @@ +using Content.Shared.Radiation.Components; +using Robust.Shared.Map; +using Robust.Shared.Serialization; + +namespace Content.Shared.Radiation.Systems; + +/// +/// Ray emitted by radiation source towards radiation receiver. +/// Contains all information about encountered radiation blockers. +/// +[Serializable, NetSerializable] +public sealed class RadiationRay +{ + /// + /// Map on which source and receiver are placed. + /// + public MapId MapId; + /// + /// Uid of entity with . + /// + public EntityUid SourceUid; + /// + /// World coordinates of radiation source. + /// + public Vector2 Source; + /// + /// Uid of entity with radiation receiver component. + /// + public EntityUid DestinationUid; + /// + /// World coordinates of radiation receiver. + /// + public Vector2 Destination; + /// + /// How many rads intensity reached radiation receiver. + /// + public float Rads; + + /// + /// Has rad ray reached destination or lost all intensity after blockers? + /// + public bool ReachedDestination => Rads > 0; + + /// + /// All blockers visited by gridcast. Key is uid of grid. Values are pairs + /// of tile indices and floats with updated radiation value. + /// + /// + /// Last tile may have negative value if ray has lost all intensity. + /// Grid traversal order isn't guaranteed. + /// + public Dictionary> 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; + } +} diff --git a/Content.Shared/Radiation/Systems/RadiationPulseSystem.cs b/Content.Shared/Radiation/Systems/RadiationPulseSystem.cs index a2da34a63f..7991f331ee 100644 --- a/Content.Shared/Radiation/Systems/RadiationPulseSystem.cs +++ b/Content.Shared/Radiation/Systems/RadiationPulseSystem.cs @@ -25,8 +25,8 @@ public sealed class RadiationPulseSystem : EntitySystem } // try to get radiation range or keep default visual range if (TryComp(uid, out var radSource)) - { - component.VisualRange = radSource.Range; + { + component.VisualRange = radSource.Intensity / radSource.Slope; } } } diff --git a/Content.Shared/Singularity/SharedSingularitySystem.cs b/Content.Shared/Singularity/SharedSingularitySystem.cs index 2ec23dee29..e3088f4458 100644 --- a/Content.Shared/Singularity/SharedSingularitySystem.cs +++ b/Content.Shared/Singularity/SharedSingularitySystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Ghost; using Content.Shared.Radiation; +using Content.Shared.Radiation.Components; using Content.Shared.Singularity.Components; using Robust.Shared.Physics; using Robust.Shared.Physics.Collision.Shapes; @@ -109,7 +110,7 @@ namespace Content.Shared.Singularity 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)) diff --git a/Resources/Locale/en-US/radiation/radiation-command.ftl b/Resources/Locale/en-US/radiation/radiation-command.ftl new file mode 100644 index 0000000000..d41b2a5207 --- /dev/null +++ b/Resources/Locale/en-US/radiation/radiation-command.ftl @@ -0,0 +1,2 @@ +radiation-command-description = Toggle visibility of radiation rays coming from rad sources +radiation-command-help = Usage: showradiation diff --git a/Resources/Prototypes/Entities/Effects/radiation.yml b/Resources/Prototypes/Entities/Effects/radiation.yml index 5caa763716..143ffa8559 100644 --- a/Resources/Prototypes/Entities/Effects/radiation.yml +++ b/Resources/Prototypes/Entities/Effects/radiation.yml @@ -5,7 +5,7 @@ description: Looking at this anomaly makes you feel strange, like something is pushing at your eyes. components: - type: RadiationSource - radsPerSecond: 5 + intensity: 5 - type: TimedDespawn lifetime: 2 - type: EmitSoundOnSpawn diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index 8dd02877e5..c0e657c741 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -69,6 +69,7 @@ -0.25 - type: Damageable damageContainer: Biological + - type: RadiationReceiver - type: AtmosExposed - type: Flammable fireSpread: true diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 0f4eaa64c4..94b6b564d3 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -178,6 +178,7 @@ preset: HumanPreset - type: Damageable damageContainer: Biological + - type: RadiationReceiver - type: ThermalRegulator metabolismHeat: 800 radiatedHeat: 100 diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml index 8cc8c66938..ac83941153 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml @@ -119,6 +119,8 @@ node: glassAirlock - type: PaintableAirlock group: Windoor + - type: RadiationBlocker + resistance: 2 - type: entity parent: AirlockGlass diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml index b196d0c722..e5088b1ddc 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml @@ -79,6 +79,8 @@ - type: Airtight fixVacuum: true noAirWhenFullyAirBlocked: false + - type: RadiationBlocker + resistance: 3 - type: Occluder - type: Damageable damageContainer: Inorganic diff --git a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml index 9cb27f1afa..cea5d73774 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml @@ -93,6 +93,8 @@ fixVacuum: true airBlocked: false noAirWhenFullyAirBlocked: true + - type: RadiationBlocker + enabled: false - type: Occluder enabled: false - type: Construction diff --git a/Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml b/Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml index 861b48fa0f..28edd48109 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml @@ -25,6 +25,8 @@ - type: AirlockVisualizer simpleVisuals: true animationTime: 1.0 + - type: RadiationBlocker + resistance: 8 - type: entity id: BlastDoorOpen @@ -39,3 +41,5 @@ canCollide: false - type: Airtight airBlocked: false + - type: RadiationBlocker + enabled: false diff --git a/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml b/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml index df98972a64..55fa9b08ed 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml @@ -51,6 +51,8 @@ type: WiresBoundUserInterface - type: Airtight fixVacuum: true + - type: RadiationBlocker + resistance: 2 - type: Damageable damageContainer: Inorganic damageModifierSet: Metallic @@ -101,6 +103,8 @@ enabled: false - type: Airtight airBlocked: false + - type: RadiationBlocker + enabled: false - type: Construction graph: Shutters node: Shutters @@ -138,6 +142,8 @@ canCollide: false - type: Airtight airBlocked: false + - type: RadiationBlocker + enabled: false - type: entity id: ShuttersWindow @@ -169,6 +175,8 @@ canCollide: false - type: Airtight airBlocked: false + - type: RadiationBlocker + enabled: false # Frame for construction - type: entity diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/collector.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/collector.yml index 1d073b6711..f055cb8593 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/collector.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/collector.yml @@ -47,6 +47,7 @@ - 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. activeSupplyRate: 100000 + - type: RadiationReceiver - type: Anchorable - type: Rotatable - type: Pullable diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/singularity.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/singularity.yml index 97382898cd..392f414659 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/singularity.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/singularity.yml @@ -24,10 +24,10 @@ layer: - AllMask - type: Singularity - radsPerLevel: 1 + radsPerLevel: 2 - type: SingularityDistortion - type: RadiationSource - range: 10 + slope: 0.2 # its emit really far away - type: Sprite sprite: Structures/Power/Generation/Singularity/singularity_1.rsi state: singularity_1 diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/generators.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/generators.yml index b622cc8d03..cc1d6e2fce 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/generators.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/generators.yml @@ -246,4 +246,4 @@ - state: rtg_damaged - state: rtg_glow - type: RadiationSource # ideally only when opened. - range: 2 + intensity: 2 diff --git a/Resources/Prototypes/Entities/Structures/Walls/walls.yml b/Resources/Prototypes/Entities/Structures/Walls/walls.yml index 1814e77774..0711a60b72 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/walls.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/walls.yml @@ -47,6 +47,8 @@ - type: Airtight - type: StaticPrice price: 75 + - type: RadiationBlocker + resistance: 2 - type: entity parent: BaseWall @@ -451,6 +453,8 @@ - type: ReinforcedWallVisualizer - type: StaticPrice price: 150 + - type: RadiationBlocker + resistance: 5 # Riveting - type: entity diff --git a/Resources/Prototypes/Entities/Structures/Windows/plasma.yml b/Resources/Prototypes/Entities/Structures/Windows/plasma.yml index 45097ecd1e..756fc968f0 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/plasma.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/plasma.yml @@ -41,6 +41,8 @@ sprite: Structures/Windows/cracks.rsi - type: StaticPrice price: 20.5 + - type: RadiationBlocker + resistance: 2 - type: entity id: PlasmaWindowDirectional diff --git a/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml b/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml index da75a8272b..2642493a03 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml @@ -11,6 +11,8 @@ - type: Damageable damageContainer: Inorganic damageModifierSet: RGlass + - type: RadiationBlocker + resistance: 4 - type: Destructible thresholds: - trigger: diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index d946e311d4..2f9ec54ff3 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -525,6 +525,8 @@ public sealed class $CLASS$ : Shared$CLASS$ { True True True + True + True True True