Geiger counter (#12082)
95
Content.Client/Radiation/Systems/GeigerSystem.cs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
using Content.Client.Items;
|
||||||
|
using Content.Client.Radiation.UI;
|
||||||
|
using Content.Shared.Radiation.Components;
|
||||||
|
using Content.Shared.Radiation.Systems;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Player;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
|
||||||
|
namespace Content.Client.Radiation.Systems;
|
||||||
|
|
||||||
|
public sealed class GeigerSystem : SharedGeigerSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly AudioSystem _audio = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<PlayerAttachSysMessage>(OnAttachedEntityChanged);
|
||||||
|
SubscribeLocalEvent<GeigerComponent, ComponentHandleState>(OnHandleState);
|
||||||
|
SubscribeLocalEvent<GeigerComponent, ItemStatusCollectMessage>(OnGetStatusMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnHandleState(EntityUid uid, GeigerComponent component, ref ComponentHandleState args)
|
||||||
|
{
|
||||||
|
if (args.Current is not GeigerComponentState state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
UpdateGeigerSound(uid, state.IsEnabled, state.User, state.DangerLevel, false, component);
|
||||||
|
|
||||||
|
component.CurrentRadiation = state.CurrentRadiation;
|
||||||
|
component.DangerLevel = state.DangerLevel;
|
||||||
|
component.IsEnabled = state.IsEnabled;
|
||||||
|
component.User = state.User;
|
||||||
|
component.UiUpdateNeeded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGetStatusMessage(EntityUid uid, GeigerComponent component, ItemStatusCollectMessage args)
|
||||||
|
{
|
||||||
|
if (!component.ShowControl)
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Controls.Add(new GeigerItemControl(component));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAttachedEntityChanged(PlayerAttachSysMessage ev)
|
||||||
|
{
|
||||||
|
// need to go for each component known to client
|
||||||
|
// and update their geiger sound
|
||||||
|
foreach (var geiger in EntityQuery<GeigerComponent>())
|
||||||
|
{
|
||||||
|
ForceUpdateGeigerSound(geiger.Owner, geiger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ForceUpdateGeigerSound(EntityUid uid, GeigerComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
UpdateGeigerSound(uid, component.IsEnabled, component.User, component.DangerLevel, true, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateGeigerSound(EntityUid uid, bool isEnabled, EntityUid? user,
|
||||||
|
GeigerDangerLevel dangerLevel, bool force = false, GeigerComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// check if we even need to update sound
|
||||||
|
if (!force && isEnabled == component.IsEnabled &&
|
||||||
|
user == component.User && dangerLevel == component.DangerLevel)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.Stream?.Stop();
|
||||||
|
|
||||||
|
if (!isEnabled || user == null)
|
||||||
|
return;
|
||||||
|
if (!component.Sounds.TryGetValue(dangerLevel, out var sounds))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// check that that local player controls entity that is holding geiger counter
|
||||||
|
if (_playerManager.LocalPlayer == null)
|
||||||
|
return;
|
||||||
|
var attachedEnt = _playerManager.LocalPlayer.Session.AttachedEntity;
|
||||||
|
if (attachedEnt != user)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var sound = _audio.GetSound(sounds);
|
||||||
|
var param = sounds.Params.WithLoop(true).WithVolume(-4f);
|
||||||
|
component.Stream = _audio.Play(sound, Filter.Local(), uid, false, param);
|
||||||
|
}
|
||||||
|
}
|
||||||
53
Content.Client/Radiation/UI/GeigerItemControl.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using Content.Client.Message;
|
||||||
|
using Content.Client.Stylesheets;
|
||||||
|
using Content.Shared.Radiation.Components;
|
||||||
|
using Content.Shared.Radiation.Systems;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Client.Radiation.UI;
|
||||||
|
|
||||||
|
public sealed class GeigerItemControl : Control
|
||||||
|
{
|
||||||
|
private readonly GeigerComponent _component;
|
||||||
|
private readonly RichTextLabel _label;
|
||||||
|
|
||||||
|
public GeigerItemControl(GeigerComponent component)
|
||||||
|
{
|
||||||
|
_component = component;
|
||||||
|
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
|
||||||
|
AddChild(_label);
|
||||||
|
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
|
{
|
||||||
|
base.FrameUpdate(args);
|
||||||
|
|
||||||
|
if (!_component.UiUpdateNeeded)
|
||||||
|
return;
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
string msg;
|
||||||
|
if (_component.IsEnabled)
|
||||||
|
{
|
||||||
|
var color = SharedGeigerSystem.LevelToColor(_component.DangerLevel);
|
||||||
|
var currentRads = _component.CurrentRadiation;
|
||||||
|
var rads = currentRads.ToString("N1");
|
||||||
|
msg = Loc.GetString("geiger-item-control-status",
|
||||||
|
("rads", rads), ("color", color));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
msg = Loc.GetString("geiger-item-control-disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
_label.SetMarkup(msg);
|
||||||
|
_component.UiUpdateNeeded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using Content.Server.Radiation.Systems;
|
||||||
|
|
||||||
|
namespace Content.Server.Radiation.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when <see cref="RadiationSystem"/> updated all
|
||||||
|
/// radiation receivers and radiation sources.
|
||||||
|
/// </summary>
|
||||||
|
public record struct RadiationSystemUpdatedEvent;
|
||||||
165
Content.Server/Radiation/Systems/GeigerSystem.cs
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
using Content.Server.Radiation.Components;
|
||||||
|
using Content.Server.Radiation.Events;
|
||||||
|
using Content.Shared.Hands;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Inventory.Events;
|
||||||
|
using Content.Shared.Radiation.Components;
|
||||||
|
using Content.Shared.Radiation.Systems;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
|
namespace Content.Server.Radiation.Systems;
|
||||||
|
|
||||||
|
public sealed class GeigerSystem : SharedGeigerSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
|
[Dependency] private readonly RadiationSystem _radiation = default!;
|
||||||
|
|
||||||
|
private static readonly float ApproxEqual = 0.01f;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<GeigerComponent, ActivateInWorldEvent>(OnActivate);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<GeigerComponent, GotEquippedEvent>(OnEquipped);
|
||||||
|
SubscribeLocalEvent<GeigerComponent, GotEquippedHandEvent>(OnEquippedHand);
|
||||||
|
SubscribeLocalEvent<GeigerComponent, GotUnequippedEvent>(OnUnequipped);
|
||||||
|
SubscribeLocalEvent<GeigerComponent, GotUnequippedHandEvent>(OnUnequippedHand);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<RadiationSystemUpdatedEvent>(OnUpdate);
|
||||||
|
SubscribeLocalEvent<GeigerComponent, ComponentGetState>(OnGetState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnActivate(EntityUid uid, GeigerComponent component, ActivateInWorldEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled || component.AttachedToSuit)
|
||||||
|
return;
|
||||||
|
args.Handled = true;
|
||||||
|
|
||||||
|
SetEnabled(uid, component, !component.IsEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEquipped(EntityUid uid, GeigerComponent component, GotEquippedEvent args)
|
||||||
|
{
|
||||||
|
if (component.AttachedToSuit)
|
||||||
|
SetEnabled(uid, component, true);
|
||||||
|
SetUser(component, args.Equipee);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEquippedHand(EntityUid uid, GeigerComponent component, GotEquippedHandEvent args)
|
||||||
|
{
|
||||||
|
if (component.AttachedToSuit)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SetUser(component, args.User);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnequipped(EntityUid uid, GeigerComponent component, GotUnequippedEvent args)
|
||||||
|
{
|
||||||
|
if (component.AttachedToSuit)
|
||||||
|
SetEnabled(uid, component, false);
|
||||||
|
SetUser(component, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnequippedHand(EntityUid uid, GeigerComponent component, GotUnequippedHandEvent args)
|
||||||
|
{
|
||||||
|
if (component.AttachedToSuit)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SetUser(component, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUpdate(RadiationSystemUpdatedEvent ev)
|
||||||
|
{
|
||||||
|
// update only active geiger counters
|
||||||
|
// deactivated shouldn't have rad receiver component
|
||||||
|
var query = EntityQuery<GeigerComponent, RadiationReceiverComponent>();
|
||||||
|
foreach (var (geiger, receiver) in query)
|
||||||
|
{
|
||||||
|
var rads = receiver.CurrentRadiation;
|
||||||
|
SetCurrentRadiation(geiger.Owner, geiger, rads);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGetState(EntityUid uid, GeigerComponent component, ref ComponentGetState args)
|
||||||
|
{
|
||||||
|
args.State = new GeigerComponentState
|
||||||
|
{
|
||||||
|
CurrentRadiation = component.CurrentRadiation,
|
||||||
|
DangerLevel = component.DangerLevel,
|
||||||
|
IsEnabled = component.IsEnabled,
|
||||||
|
User = component.User
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetCurrentRadiation(EntityUid uid, GeigerComponent component, float rads)
|
||||||
|
{
|
||||||
|
// check that it's approx equal
|
||||||
|
if (MathHelper.CloseTo(component.CurrentRadiation, rads, ApproxEqual))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var curLevel = component.DangerLevel;
|
||||||
|
var newLevel = RadsToLevel(rads);
|
||||||
|
|
||||||
|
component.CurrentRadiation = rads;
|
||||||
|
component.DangerLevel = newLevel;
|
||||||
|
|
||||||
|
if (curLevel != newLevel)
|
||||||
|
{
|
||||||
|
UpdateAppearance(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dirty(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetUser(GeigerComponent component, EntityUid? user)
|
||||||
|
{
|
||||||
|
if (component.User == user)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.User = user;
|
||||||
|
Dirty(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetEnabled(EntityUid uid, GeigerComponent component, bool isEnabled)
|
||||||
|
{
|
||||||
|
if (component.IsEnabled == isEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.IsEnabled = isEnabled;
|
||||||
|
if (!isEnabled)
|
||||||
|
{
|
||||||
|
component.CurrentRadiation = 0f;
|
||||||
|
component.DangerLevel = GeigerDangerLevel.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
_radiation.SetCanReceive(uid, isEnabled);
|
||||||
|
|
||||||
|
UpdateAppearance(uid, component);
|
||||||
|
Dirty(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppearance(EntityUid uid, GeigerComponent? component = null,
|
||||||
|
AppearanceComponent? appearance = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component, ref appearance, false))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_appearance.SetData(uid, GeigerVisuals.IsEnabled, component.IsEnabled, appearance);
|
||||||
|
_appearance.SetData(uid, GeigerVisuals.DangerLevel, component.DangerLevel, appearance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GeigerDangerLevel RadsToLevel(float rads)
|
||||||
|
{
|
||||||
|
return rads switch
|
||||||
|
{
|
||||||
|
< 0.2f => GeigerDangerLevel.None,
|
||||||
|
< 1f => GeigerDangerLevel.Low,
|
||||||
|
< 3f => GeigerDangerLevel.Med,
|
||||||
|
< 6f => GeigerDangerLevel.High,
|
||||||
|
_ => GeigerDangerLevel.Extreme
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.Radiation.Components;
|
using Content.Server.Radiation.Components;
|
||||||
|
using Content.Server.Radiation.Events;
|
||||||
using Content.Shared.Radiation.Components;
|
using Content.Shared.Radiation.Components;
|
||||||
using Content.Shared.Radiation.Systems;
|
using Content.Shared.Radiation.Systems;
|
||||||
using Robust.Shared.Collections;
|
using Robust.Shared.Collections;
|
||||||
@@ -82,6 +83,9 @@ public partial class RadiationSystem
|
|||||||
if (rads > 0)
|
if (rads > 0)
|
||||||
IrradiateEntity(receiver.Owner, rads,GridcastUpdateRate);
|
IrradiateEntity(receiver.Owner, rads,GridcastUpdateRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// raise broadcast event that radiation system has updated
|
||||||
|
RaiseLocalEvent(new RadiationSystemUpdatedEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
private RadiationRay? Irradiate(EntityUid sourceUid, TransformComponent sourceTrs, Vector2 sourceWorld,
|
private RadiationRay? Irradiate(EntityUid sourceUid, TransformComponent sourceTrs, Vector2 sourceWorld,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Shared.Radiation.Events;
|
using Content.Server.Radiation.Components;
|
||||||
|
using Content.Shared.Radiation.Events;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
@@ -43,4 +44,19 @@ public sealed partial class RadiationSystem : EntitySystem
|
|||||||
var msg = new OnIrradiatedEvent(time, radsPerSecond);
|
var msg = new OnIrradiatedEvent(time, radsPerSecond);
|
||||||
RaiseLocalEvent(uid, msg);
|
RaiseLocalEvent(uid, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks entity to receive/ignore radiation rays.
|
||||||
|
/// </summary>
|
||||||
|
public void SetCanReceive(EntityUid uid, bool canReceive)
|
||||||
|
{
|
||||||
|
if (canReceive)
|
||||||
|
{
|
||||||
|
EnsureComp<RadiationReceiverComponent>(uid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RemComp<RadiationReceiverComponent>(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
118
Content.Shared/Radiation/Components/GeigerComponent.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
using Content.Shared.Radiation.Systems;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Radiation.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Geiger counter that shows current radiation level.
|
||||||
|
/// Can be added as a component to clothes.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent]
|
||||||
|
[Access(typeof(SharedGeigerSystem))]
|
||||||
|
public sealed class GeigerComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// If true it will be active only when player equipped it.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("attachedToSuit")]
|
||||||
|
public bool AttachedToSuit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is geiger counter currently active?
|
||||||
|
/// If false attached entity will ignore any radiation rays.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("isEnabled")]
|
||||||
|
public bool IsEnabled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should it shows examine message with current radiation level?
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("showExamine")]
|
||||||
|
public bool ShowExamine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should it shows item control when equipped by player?
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("showControl")]
|
||||||
|
public bool ShowControl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Map of sounds that should be play on loop for different radiation levels.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("sounds")]
|
||||||
|
public Dictionary<GeigerDangerLevel, SoundSpecifier> Sounds = new()
|
||||||
|
{
|
||||||
|
{GeigerDangerLevel.Low, new SoundPathSpecifier("/Audio/Items/Geiger/low.ogg")},
|
||||||
|
{GeigerDangerLevel.Med, new SoundPathSpecifier("/Audio/Items/Geiger/med.ogg")},
|
||||||
|
{GeigerDangerLevel.High, new SoundPathSpecifier("/Audio/Items/Geiger/high.ogg")},
|
||||||
|
{GeigerDangerLevel.Extreme, new SoundPathSpecifier("/Audio/Items/Geiger/ext.ogg")}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current radiation level in rad per second.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public float CurrentRadiation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Estimated radiation danger level.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public GeigerDangerLevel DangerLevel = GeigerDangerLevel.None;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current player that equipped geiger counter.
|
||||||
|
/// Because sound is annoying, geiger counter clicks will play
|
||||||
|
/// only for player that equipped it.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public EntityUid? User;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marked true if control needs to update UI with latest component state.
|
||||||
|
/// </summary>
|
||||||
|
[Access(typeof(SharedGeigerSystem), Other = AccessPermissions.ReadWrite)]
|
||||||
|
public bool UiUpdateNeeded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current stream of geiger counter audio.
|
||||||
|
/// Played only for current user.
|
||||||
|
/// </summary>
|
||||||
|
public IPlayingAudioStream? Stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class GeigerComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public float CurrentRadiation;
|
||||||
|
public GeigerDangerLevel DangerLevel;
|
||||||
|
public bool IsEnabled;
|
||||||
|
public EntityUid? User;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum GeigerDangerLevel : byte
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Low,
|
||||||
|
Med,
|
||||||
|
High,
|
||||||
|
Extreme
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum GeigerLayers : byte
|
||||||
|
{
|
||||||
|
Screen
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum GeigerVisuals : byte
|
||||||
|
{
|
||||||
|
DangerLevel,
|
||||||
|
IsEnabled
|
||||||
|
}
|
||||||
44
Content.Shared/Radiation/Systems/SharedGeigerSystem.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Content.Shared.Examine;
|
||||||
|
using Content.Shared.Radiation.Components;
|
||||||
|
|
||||||
|
namespace Content.Shared.Radiation.Systems;
|
||||||
|
|
||||||
|
public abstract class SharedGeigerSystem : EntitySystem
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<GeigerComponent, ExaminedEvent>(OnExamine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExamine(EntityUid uid, GeigerComponent component, ExaminedEvent args)
|
||||||
|
{
|
||||||
|
if (!component.ShowExamine || !component.IsEnabled || !args.IsInDetailsRange)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var currentRads = component.CurrentRadiation;
|
||||||
|
var rads = currentRads.ToString("N1");
|
||||||
|
var color = LevelToColor(component.DangerLevel);
|
||||||
|
var msg = Loc.GetString("geiger-component-examine",
|
||||||
|
("rads", rads), ("color", color));
|
||||||
|
args.PushMarkup(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color LevelToColor(GeigerDangerLevel level)
|
||||||
|
{
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case GeigerDangerLevel.None:
|
||||||
|
return Color.Green;
|
||||||
|
case GeigerDangerLevel.Low:
|
||||||
|
return Color.Yellow;
|
||||||
|
case GeigerDangerLevel.Med:
|
||||||
|
return Color.DarkOrange;
|
||||||
|
case GeigerDangerLevel.High:
|
||||||
|
case GeigerDangerLevel.Extreme:
|
||||||
|
return Color.Red;
|
||||||
|
default:
|
||||||
|
return Color.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Resources/Audio/Items/Geiger/attributions.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
- files:
|
||||||
|
- "low.ogg"
|
||||||
|
- "med.ogg"
|
||||||
|
- "high.ogg"
|
||||||
|
- "ext.ogg"
|
||||||
|
license: "CC-BY-SA-3.0"
|
||||||
|
copyright: "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/456cd10d94084c7c2574f628cf7ac9b67087ba26"
|
||||||
|
source: "https://github.com/tgstation/tgstation/tree/456cd10d94084c7c2574f628cf7ac9b67087ba26/sound/items/geiger"
|
||||||
BIN
Resources/Audio/Items/Geiger/ext.ogg
Normal file
BIN
Resources/Audio/Items/Geiger/high.ogg
Normal file
BIN
Resources/Audio/Items/Geiger/low.ogg
Normal file
BIN
Resources/Audio/Items/Geiger/med.ogg
Normal file
3
Resources/Locale/en-US/radiation/geiger-component.ftl
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
geiger-item-control-status = Radiation: [color={$color}]{$rads} rads[/color]
|
||||||
|
geiger-item-control-disabled = Disabled
|
||||||
|
geiger-component-examine = Current radiation: [color={$color}]{$rads} rads[/color]
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
sprite: Structures/Wallmounts/signs.rsi
|
sprite: Structures/Wallmounts/signs.rsi
|
||||||
state: radiation
|
state: radiation
|
||||||
product: CrateEmergencyRadiation
|
product: CrateEmergencyRadiation
|
||||||
cost: 900
|
cost: 1000
|
||||||
category: Emergency
|
category: Emergency
|
||||||
group: market
|
group: market
|
||||||
|
|
||||||
|
|||||||
@@ -63,8 +63,8 @@
|
|||||||
amount: 2
|
amount: 2
|
||||||
- id: ClothingHeadHatHoodRad
|
- id: ClothingHeadHatHoodRad
|
||||||
amount: 2
|
amount: 2
|
||||||
# - id: GeigerCounter
|
- id: GeigerCounter
|
||||||
# amount: 2
|
amount: 2
|
||||||
- id: DrinkVodkaBottleFull
|
- id: DrinkVodkaBottleFull
|
||||||
amount: 1
|
amount: 1
|
||||||
- id: DrinkShotGlass
|
- id: DrinkShotGlass
|
||||||
|
|||||||
@@ -115,3 +115,5 @@
|
|||||||
amount: 2
|
amount: 2
|
||||||
- id: ClothingOuterSuitRad
|
- id: ClothingOuterSuitRad
|
||||||
amount: 2
|
amount: 2
|
||||||
|
- id: GeigerCounter
|
||||||
|
amount: 2
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
- Screwdriver
|
- Screwdriver
|
||||||
- Flashlight
|
- Flashlight
|
||||||
- Wrench
|
- Wrench
|
||||||
# - GeigerCounter
|
- GeigerCounter
|
||||||
- Flare
|
- Flare
|
||||||
- CableCoil
|
- CableCoil
|
||||||
- CigPack
|
- CigPack
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
- Screwdriver
|
- Screwdriver
|
||||||
- Flashlight
|
- Flashlight
|
||||||
- Wrench
|
- Wrench
|
||||||
# - GeigerCounter
|
- GeigerCounter
|
||||||
- Flare
|
- Flare
|
||||||
- CableCoil
|
- CableCoil
|
||||||
- Powerdrill
|
- Powerdrill
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
abstract: true
|
abstract: true
|
||||||
parent: ClothingOuterBase
|
parent: [ClothingOuterBase, GeigerCounterClothing]
|
||||||
id: ClothingOuterHardsuitBase
|
id: ClothingOuterHardsuitBase
|
||||||
name: base hardsuit
|
name: base hardsuit
|
||||||
components:
|
components:
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
coefficient: 0.01
|
coefficient: 0.01
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: ClothingOuterBaseLarge
|
parent: [ClothingOuterBaseLarge, GeigerCounterClothing]
|
||||||
id: ClothingOuterSuitRad
|
id: ClothingOuterSuitRad
|
||||||
name: radiation suit
|
name: radiation suit
|
||||||
description: "A suit that protects against radiation. The label reads, 'Made with lead. Please do not consume insulation.'"
|
description: "A suit that protects against radiation. The label reads, 'Made with lead. Please do not consume insulation.'"
|
||||||
|
|||||||
@@ -10,3 +10,10 @@
|
|||||||
- WhitelistChameleon
|
- WhitelistChameleon
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 15
|
price: 15
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
abstract: true
|
||||||
|
id: GeigerCounterClothing
|
||||||
|
components:
|
||||||
|
- type: Geiger
|
||||||
|
attachedToSuit: true
|
||||||
|
|||||||
35
Resources/Prototypes/Entities/Objects/Devices/geiger.yml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
- type: entity
|
||||||
|
parent: BaseItem
|
||||||
|
id: GeigerCounter
|
||||||
|
name: Geiger counter
|
||||||
|
description: A handheld device used for detecting and measuring radiation pulses.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
sprite: Objects/Tools/geiger.rsi
|
||||||
|
layers:
|
||||||
|
- state: geiger_base
|
||||||
|
- state: geiger_on_idle
|
||||||
|
map: ["enum.GeigerLayers.Screen"]
|
||||||
|
shader: unshaded
|
||||||
|
visible: false
|
||||||
|
- type: Item
|
||||||
|
sprite: Objects/Tools/geiger.rsi
|
||||||
|
- type: Geiger
|
||||||
|
showControl: true
|
||||||
|
showExamine: true
|
||||||
|
- type: Appearance
|
||||||
|
- type: GenericVisualizer
|
||||||
|
visuals:
|
||||||
|
enum.GeigerVisuals.IsEnabled:
|
||||||
|
GeigerLayers.Screen:
|
||||||
|
True: { visible: True }
|
||||||
|
False: { visible: False }
|
||||||
|
enum.GeigerVisuals.DangerLevel:
|
||||||
|
GeigerLayers.Screen:
|
||||||
|
None: {state: geiger_on_idle}
|
||||||
|
Low: {state: geiger_on_low}
|
||||||
|
Med: {state: geiger_on_med}
|
||||||
|
High: {state: geiger_on_high}
|
||||||
|
Extreme: {state: geiger_on_ext}
|
||||||
|
|
||||||
@@ -249,6 +249,9 @@
|
|||||||
- type: Tag
|
- type: Tag
|
||||||
id: Gauze
|
id: Gauze
|
||||||
|
|
||||||
|
- type: Tag
|
||||||
|
id: GeigerCounter
|
||||||
|
|
||||||
- type: Tag
|
- type: Tag
|
||||||
id: GlassBeaker
|
id: GlassBeaker
|
||||||
|
|
||||||
|
|||||||
BIN
Resources/Textures/Objects/Tools/geiger.rsi/geiger_base.png
Normal file
|
After Width: | Height: | Size: 427 B |
BIN
Resources/Textures/Objects/Tools/geiger.rsi/geiger_on_ext.png
Normal file
|
After Width: | Height: | Size: 228 B |
BIN
Resources/Textures/Objects/Tools/geiger.rsi/geiger_on_high.png
Normal file
|
After Width: | Height: | Size: 176 B |
BIN
Resources/Textures/Objects/Tools/geiger.rsi/geiger_on_idle.png
Normal file
|
After Width: | Height: | Size: 173 B |
BIN
Resources/Textures/Objects/Tools/geiger.rsi/geiger_on_low.png
Normal file
|
After Width: | Height: | Size: 176 B |
BIN
Resources/Textures/Objects/Tools/geiger.rsi/geiger_on_med.png
Normal file
|
After Width: | Height: | Size: 176 B |
BIN
Resources/Textures/Objects/Tools/geiger.rsi/inhand-left.png
Normal file
|
After Width: | Height: | Size: 302 B |
BIN
Resources/Textures/Objects/Tools/geiger.rsi/inhand-right.png
Normal file
|
After Width: | Height: | Size: 301 B |
43
Resources/Textures/Objects/Tools/geiger.rsi/meta.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/bfc9c6ba8126ee8c41564d68c4bfb9ce37faa8f8",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "geiger_base"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "geiger_on_idle"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "geiger_on_low"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "geiger_on_med"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "geiger_on_high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "geiger_on_ext",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-left",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-right",
|
||||||
|
"directions": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||