Radiation rework (#10970)
This commit is contained in:
@@ -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;
|
||||
|
||||
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.Timing;
|
||||
|
||||
namespace Content.Client.Radiation
|
||||
namespace Content.Client.Radiation.Overlays
|
||||
{
|
||||
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 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<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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,6 +623,36 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<int> ExplosionSingleTickAreaLimit =
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
namespace Content.Shared.Radiation.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Irradiate all objects in range.
|
||||
/// </summary>
|
||||
@@ -5,16 +7,20 @@
|
||||
public sealed class RadiationSourceComponent : Component
|
||||
{
|
||||
/// <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>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("radsPerSecond")]
|
||||
public float RadsPerSecond = 1;
|
||||
[DataField("intensity")]
|
||||
public float Intensity = 1;
|
||||
|
||||
/// <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>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("range")]
|
||||
public float Range = 5f;
|
||||
[DataField("slope")]
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public sealed class RadiationPulseSystem : EntitySystem
|
||||
// try to get radiation range or keep default visual range
|
||||
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.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))
|
||||
|
||||
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.
|
||||
components:
|
||||
- type: RadiationSource
|
||||
radsPerSecond: 5
|
||||
intensity: 5
|
||||
- type: TimedDespawn
|
||||
lifetime: 2
|
||||
- type: EmitSoundOnSpawn
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
-0.25
|
||||
- type: Damageable
|
||||
damageContainer: Biological
|
||||
- type: RadiationReceiver
|
||||
- type: AtmosExposed
|
||||
- type: Flammable
|
||||
fireSpread: true
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
preset: HumanPreset
|
||||
- type: Damageable
|
||||
damageContainer: Biological
|
||||
- type: RadiationReceiver
|
||||
- type: ThermalRegulator
|
||||
metabolismHeat: 800
|
||||
radiatedHeat: 100
|
||||
|
||||
@@ -119,6 +119,8 @@
|
||||
node: glassAirlock
|
||||
- type: PaintableAirlock
|
||||
group: Windoor
|
||||
- type: RadiationBlocker
|
||||
resistance: 2
|
||||
|
||||
- type: entity
|
||||
parent: AirlockGlass
|
||||
|
||||
@@ -79,6 +79,8 @@
|
||||
- type: Airtight
|
||||
fixVacuum: true
|
||||
noAirWhenFullyAirBlocked: false
|
||||
- type: RadiationBlocker
|
||||
resistance: 3
|
||||
- type: Occluder
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
|
||||
@@ -93,6 +93,8 @@
|
||||
fixVacuum: true
|
||||
airBlocked: false
|
||||
noAirWhenFullyAirBlocked: true
|
||||
- type: RadiationBlocker
|
||||
enabled: false
|
||||
- type: Occluder
|
||||
enabled: false
|
||||
- type: Construction
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -246,4 +246,4 @@
|
||||
- state: rtg_damaged
|
||||
- state: rtg_glow
|
||||
- type: RadiationSource # ideally only when opened.
|
||||
range: 2
|
||||
intensity: 2
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
sprite: Structures/Windows/cracks.rsi
|
||||
- type: StaticPrice
|
||||
price: 20.5
|
||||
- type: RadiationBlocker
|
||||
resistance: 2
|
||||
|
||||
- type: entity
|
||||
id: PlasmaWindowDirectional
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: RGlass
|
||||
- type: RadiationBlocker
|
||||
resistance: 4
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- 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/=Gibs/@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/=hardcode/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=hbox/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
Reference in New Issue
Block a user