Night And Thermal Vision (#1462)
<!-- This is a semi-strict format, you can add/remove sections as needed but the order/format should be kept the same Remove these comments before submitting --> # Description <!-- Explain this PR in as much detail as applicable Some example prompts to consider: How might this affect the game? The codebase? What might be some alternatives to this? How/Who does this benefit/hurt [the game/codebase]? --> Port from WWDP. Refactor from [Goob](https://github.com/Goob-Station/Goob-Station/pull/1251) --- <!-- This is default collapsed, readers click to expand it and see all your media The PR media section can get very large at times, so this is a good way to keep it clean The title is written using HTML tags The title must be within the <summary> tags or you won't see it --> <details><summary><h1>Media</h1></summary> <p>  Night vision goggles:  Zealot's blindfold:  Animal vision:  Thermal vision goggles:  Deathsquad helmet:  Xeno vision:  </p> </details> --- # Changelog <!-- You can add an author after the `🆑` to change the name that appears in the changelog (ex: `🆑 Death`) Leaving it blank will default to your GitHub display name This includes all available types for the changelog --> 🆑 @Aviu00, Spatison, @PuroSlavKing - add: Added night vision goggle - add: Added thermal vision goggle - add: Deathsquad helmet now grants night and thermal vision. - add: Ninja visor now grants night vision. - tweak: Some animals have gained night vision. - tweak: Xenos have gained night vision. --------- Signed-off-by: Spatison <137375981+Spatison@users.noreply.github.com> Co-authored-by: PuroSlavKing <103608145+puroslavking@users.noreply.github.com> (cherry picked from commit 0f481422a54a197923f4bf03db1b5733e481965f)
@@ -102,7 +102,7 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
|
||||
args.Components.Add(component);
|
||||
}
|
||||
|
||||
private void RefreshOverlay(EntityUid uid)
|
||||
protected void RefreshOverlay(EntityUid uid)
|
||||
{
|
||||
if (uid != _player.LocalSession?.AttachedEntity)
|
||||
return;
|
||||
|
||||
48
Content.Client/Overlays/Switchable/BaseSwitchableOverlay.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Overlays.Switchable;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Overlays.Switchable;
|
||||
|
||||
public sealed class BaseSwitchableOverlay<TComp> : Overlay where TComp : SwitchableOverlayComponent
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public override bool RequestScreenTexture => true;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
public TComp? Comp = null;
|
||||
|
||||
public bool IsActive = true;
|
||||
|
||||
public BaseSwitchableOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = _prototype.Index<ShaderPrototype>("NightVision").InstanceUnique();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (ScreenTexture is null || Comp is null || !IsActive)
|
||||
return;
|
||||
|
||||
_shader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
_shader.SetParameter("tint", Comp.Tint);
|
||||
_shader.SetParameter("luminance_threshold", Comp.Strength);
|
||||
_shader.SetParameter("noise_amount", Comp.Noise);
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
var accumulator = Math.Clamp(Comp.PulseAccumulator, 0f, Comp.PulseTime);
|
||||
var alpha = Comp.PulseTime <= 0f ? 1f : float.Lerp(1f, 0f, accumulator / Comp.PulseTime);
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
worldHandle.UseShader(_shader);
|
||||
worldHandle.DrawRect(args.WorldBounds, Comp.Color.WithAlpha(alpha));
|
||||
worldHandle.UseShader(null);
|
||||
}
|
||||
}
|
||||
87
Content.Client/Overlays/Switchable/NightVisionSystem.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Overlays.Switchable;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Overlays.Switchable;
|
||||
|
||||
public sealed class NightVisionSystem : EquipmentHudSystem<NightVisionComponent>
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
[Dependency] private readonly ILightManager _lightManager = default!;
|
||||
|
||||
private BaseSwitchableOverlay<NightVisionComponent> _overlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<NightVisionComponent, SwitchableOverlayToggledEvent>(OnToggle);
|
||||
|
||||
_overlay = new BaseSwitchableOverlay<NightVisionComponent>();
|
||||
}
|
||||
|
||||
private void OnToggle(Entity<NightVisionComponent> ent, ref SwitchableOverlayToggledEvent args)
|
||||
{
|
||||
RefreshOverlay(args.User);
|
||||
}
|
||||
|
||||
protected override void UpdateInternal(RefreshEquipmentHudEvent<NightVisionComponent> args)
|
||||
{
|
||||
base.UpdateInternal(args);
|
||||
|
||||
var active = false;
|
||||
NightVisionComponent? nvComp = null;
|
||||
foreach (var comp in args.Components)
|
||||
{
|
||||
if (comp.IsActive || comp.PulseTime > 0f && comp.PulseAccumulator < comp.PulseTime)
|
||||
active = true;
|
||||
else
|
||||
continue;
|
||||
|
||||
if (comp.DrawOverlay)
|
||||
{
|
||||
if (nvComp == null)
|
||||
nvComp = comp;
|
||||
else if (nvComp.PulseTime > 0f && comp.PulseTime <= 0f)
|
||||
nvComp = comp;
|
||||
}
|
||||
|
||||
if (active && nvComp is { PulseTime: <= 0 })
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateNightVision(active);
|
||||
UpdateOverlay(nvComp);
|
||||
}
|
||||
|
||||
protected override void DeactivateInternal()
|
||||
{
|
||||
base.DeactivateInternal();
|
||||
|
||||
UpdateNightVision(false);
|
||||
UpdateOverlay(null);
|
||||
}
|
||||
|
||||
private void UpdateNightVision(bool active)
|
||||
{
|
||||
_lightManager.DrawLighting = !active;
|
||||
}
|
||||
|
||||
private void UpdateOverlay(NightVisionComponent? nvComp)
|
||||
{
|
||||
_overlay.Comp = nvComp;
|
||||
|
||||
switch (nvComp)
|
||||
{
|
||||
case not null when !_overlayMan.HasOverlay<BaseSwitchableOverlay<NightVisionComponent>>():
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
break;
|
||||
case null:
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
break;
|
||||
}
|
||||
|
||||
if (_overlayMan.TryGetOverlay<BaseSwitchableOverlay<ThermalVisionComponent>>(out var overlay))
|
||||
overlay.IsActive = nvComp == null;
|
||||
}
|
||||
}
|
||||
159
Content.Client/Overlays/Switchable/ThermalVisionOverlay.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Stealth;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Overlays.Switchable;
|
||||
using Content.Shared.Stealth.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Overlays.Switchable;
|
||||
|
||||
public sealed class ThermalVisionOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private readonly TransformSystem _transform;
|
||||
private readonly StealthSystem _stealth;
|
||||
private readonly ContainerSystem _container;
|
||||
private readonly SharedPointLightSystem _light;
|
||||
|
||||
public override bool RequestScreenTexture => true;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly List<ThermalVisionRenderEntry> _entries = [];
|
||||
|
||||
private EntityUid? _lightEntity;
|
||||
|
||||
public float LightRadius;
|
||||
|
||||
public ThermalVisionComponent? Comp;
|
||||
|
||||
public ThermalVisionOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_container = _entity.System<ContainerSystem>();
|
||||
_transform = _entity.System<TransformSystem>();
|
||||
_stealth = _entity.System<StealthSystem>();
|
||||
_light = _entity.System<SharedPointLightSystem>();
|
||||
|
||||
ZIndex = -1;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (ScreenTexture is null || Comp is null)
|
||||
return;
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
var eye = args.Viewport.Eye;
|
||||
|
||||
if (eye == null)
|
||||
return;
|
||||
|
||||
var player = _player.LocalEntity;
|
||||
|
||||
if (!_entity.TryGetComponent(player, out TransformComponent? playerXform))
|
||||
return;
|
||||
|
||||
var accumulator = Math.Clamp(Comp.PulseAccumulator, 0f, Comp.PulseTime);
|
||||
var alpha = Comp.PulseTime <= 0f ? 1f : float.Lerp(1f, 0f, accumulator / Comp.PulseTime);
|
||||
|
||||
// Thermal vision grants some night vision (clientside light)
|
||||
if (LightRadius > 0)
|
||||
{
|
||||
_lightEntity ??= _entity.SpawnAttachedTo(null, playerXform.Coordinates);
|
||||
_transform.SetParent(_lightEntity.Value, player.Value);
|
||||
var light = _entity.EnsureComponent<PointLightComponent>(_lightEntity.Value);
|
||||
_light.SetRadius(_lightEntity.Value, LightRadius, light);
|
||||
_light.SetEnergy(_lightEntity.Value, alpha, light);
|
||||
_light.SetColor(_lightEntity.Value, Comp.Color, light);
|
||||
}
|
||||
else
|
||||
ResetLight();
|
||||
|
||||
var mapId = eye.Position.MapId;
|
||||
var eyeRot = eye.Rotation;
|
||||
|
||||
_entries.Clear();
|
||||
var entities = _entity.EntityQueryEnumerator<BodyComponent, SpriteComponent, TransformComponent>();
|
||||
while (entities.MoveNext(out var uid, out var body, out var sprite, out var xform))
|
||||
{
|
||||
if (!CanSee(uid, sprite) || !body.ThermalVisibility)
|
||||
continue;
|
||||
|
||||
var entity = uid;
|
||||
|
||||
if (_container.TryGetOuterContainer(uid, xform, out var container))
|
||||
{
|
||||
var owner = container.Owner;
|
||||
if (_entity.TryGetComponent<SpriteComponent>(owner, out var ownerSprite)
|
||||
&& _entity.TryGetComponent<TransformComponent>(owner, out var ownerXform))
|
||||
{
|
||||
entity = owner;
|
||||
sprite = ownerSprite;
|
||||
xform = ownerXform;
|
||||
}
|
||||
}
|
||||
|
||||
if (_entries.Any(e => e.Ent.Owner == entity))
|
||||
continue;
|
||||
|
||||
_entries.Add(new ThermalVisionRenderEntry((entity, sprite, xform), mapId, eyeRot));
|
||||
}
|
||||
|
||||
foreach (var entry in _entries)
|
||||
{
|
||||
Render(entry.Ent, entry.Map, worldHandle, entry.EyeRot, Comp.Color, alpha);
|
||||
}
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
private void Render(Entity<SpriteComponent, TransformComponent> ent,
|
||||
MapId? map,
|
||||
DrawingHandleWorld handle,
|
||||
Angle eyeRot,
|
||||
Color color,
|
||||
float alpha)
|
||||
{
|
||||
var (uid, sprite, xform) = ent;
|
||||
if (xform.MapID != map || !CanSee(uid, sprite))
|
||||
return;
|
||||
|
||||
var position = _transform.GetWorldPosition(xform);
|
||||
var rotation = _transform.GetWorldRotation(xform);
|
||||
|
||||
var originalColor = sprite.Color;
|
||||
sprite.Color = color.WithAlpha(alpha);
|
||||
sprite.Render(handle, eyeRot, rotation, position: position);
|
||||
sprite.Color = originalColor;
|
||||
}
|
||||
|
||||
private bool CanSee(EntityUid uid, SpriteComponent sprite)
|
||||
{
|
||||
return sprite.Visible && (!_entity.TryGetComponent(uid, out StealthComponent? stealth) ||
|
||||
_stealth.GetVisibility(uid, stealth) > 0.5f);
|
||||
}
|
||||
|
||||
public void ResetLight()
|
||||
{
|
||||
if (_lightEntity == null || !_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
_entity.DeleteEntity(_lightEntity);
|
||||
_lightEntity = null;
|
||||
}
|
||||
}
|
||||
|
||||
public record struct ThermalVisionRenderEntry(
|
||||
Entity<SpriteComponent, TransformComponent> Ent,
|
||||
MapId? Map,
|
||||
Angle EyeRot);
|
||||
95
Content.Client/Overlays/Switchable/ThermalVisionSystem.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Overlays.Switchable;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Overlays.Switchable;
|
||||
|
||||
public sealed class ThermalVisionSystem : EquipmentHudSystem<ThermalVisionComponent>
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
private ThermalVisionOverlay _thermalOverlay = default!;
|
||||
private BaseSwitchableOverlay<ThermalVisionComponent> _overlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ThermalVisionComponent, SwitchableOverlayToggledEvent>(OnToggle);
|
||||
|
||||
_thermalOverlay = new ThermalVisionOverlay();
|
||||
_overlay = new BaseSwitchableOverlay<ThermalVisionComponent>();
|
||||
}
|
||||
|
||||
private void OnToggle(Entity<ThermalVisionComponent> ent, ref SwitchableOverlayToggledEvent args)
|
||||
{
|
||||
RefreshOverlay(args.User);
|
||||
}
|
||||
|
||||
protected override void UpdateInternal(RefreshEquipmentHudEvent<ThermalVisionComponent> args)
|
||||
{
|
||||
base.UpdateInternal(args);
|
||||
ThermalVisionComponent? tvComp = null;
|
||||
var lightRadius = 0f;
|
||||
foreach (var comp in args.Components)
|
||||
{
|
||||
if (!comp.IsActive && (comp.PulseTime <= 0f || comp.PulseAccumulator >= comp.PulseTime))
|
||||
continue;
|
||||
|
||||
if (tvComp == null)
|
||||
tvComp = comp;
|
||||
else if (!tvComp.DrawOverlay && comp.DrawOverlay)
|
||||
tvComp = comp;
|
||||
else if (tvComp.DrawOverlay == comp.DrawOverlay && tvComp.PulseTime > 0f && comp.PulseTime <= 0f)
|
||||
tvComp = comp;
|
||||
|
||||
lightRadius = MathF.Max(lightRadius, comp.LightRadius);
|
||||
}
|
||||
|
||||
UpdateThermalOverlay(tvComp, lightRadius);
|
||||
UpdateOverlay(tvComp);
|
||||
}
|
||||
|
||||
protected override void DeactivateInternal()
|
||||
{
|
||||
base.DeactivateInternal();
|
||||
|
||||
UpdateOverlay(null);
|
||||
UpdateThermalOverlay(null, 0f);
|
||||
}
|
||||
|
||||
private void UpdateThermalOverlay(ThermalVisionComponent? comp, float lightRadius)
|
||||
{
|
||||
_thermalOverlay.LightRadius = lightRadius;
|
||||
_thermalOverlay.Comp = comp;
|
||||
|
||||
switch (comp)
|
||||
{
|
||||
case not null when !_overlayMan.HasOverlay<ThermalVisionOverlay>():
|
||||
_overlayMan.AddOverlay(_thermalOverlay);
|
||||
break;
|
||||
case null:
|
||||
_overlayMan.RemoveOverlay(_thermalOverlay);
|
||||
_thermalOverlay.ResetLight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateOverlay(ThermalVisionComponent? tvComp)
|
||||
{
|
||||
_overlay.Comp = tvComp;
|
||||
|
||||
switch (tvComp)
|
||||
{
|
||||
case { DrawOverlay: true } when !_overlayMan.HasOverlay<BaseSwitchableOverlay<ThermalVisionComponent>>():
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
break;
|
||||
case null or { DrawOverlay: false }:
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
break;
|
||||
}
|
||||
|
||||
// Night vision overlay is prioritized
|
||||
_overlay.IsActive = !_overlayMan.HasOverlay<BaseSwitchableOverlay<NightVisionComponent>>();
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -23,6 +24,8 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IGameTiming GameTiming = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
|
||||
@@ -774,7 +777,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (!ResolveActionData(actionId, ref action))
|
||||
return false;
|
||||
|
||||
DebugTools.Assert(action.Container == null ||
|
||||
DebugTools.Assert(_net.IsClient || action.Container == null ||
|
||||
(TryComp(action.Container, out ActionsContainerComponent? containerComp)
|
||||
&& containerComp.Container.Contains(actionId)));
|
||||
|
||||
|
||||
@@ -41,4 +41,7 @@ public sealed partial class BodyComponent : Component
|
||||
[ViewVariables]
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<EntityUid> LegEntities = new();
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool ThermalVisibility = true;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ using Content.Shared.Temperature;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Overlays.Switchable;
|
||||
|
||||
|
||||
namespace Content.Shared.Inventory;
|
||||
|
||||
@@ -65,6 +67,8 @@ public partial class InventorySystem
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowMindShieldIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSyndicateIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowCriminalRecordIconsComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<NightVisionComponent>>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ThermalVisionComponent>>(RelayInventoryEvent);
|
||||
|
||||
SubscribeLocalEvent<InventoryComponent, GetVerbsEvent<EquipmentVerb>>(OnGetEquipmentVerbs);
|
||||
}
|
||||
|
||||
16
Content.Shared/Overlays/BaseOverlayComponent.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Content.Shared.Overlays;
|
||||
|
||||
public abstract partial class BaseOverlayComponent : Component
|
||||
{
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public virtual Vector3 Tint { get; set; } = new(0.3f, 0.3f, 0.3f);
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public virtual float Strength { get; set; } = 2f;
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public virtual float Noise { get; set; } = 0.5f;
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public virtual Color Color { get; set; } = Color.White;
|
||||
}
|
||||
14
Content.Shared/Overlays/Switchable/NightVisionComponent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Overlays.Switchable;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class NightVisionComponent : SwitchableOverlayComponent
|
||||
{
|
||||
public override string? ToggleAction { get; set; } = "ToggleNightVision";
|
||||
|
||||
public override Color Color { get; set; } = Color.FromHex("#98FB98");
|
||||
}
|
||||
|
||||
public sealed partial class ToggleNightVisionEvent : InstantActionEvent;
|
||||
3
Content.Shared/Overlays/Switchable/NightVisionSystem.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Content.Shared.Overlays.Switchable;
|
||||
|
||||
public sealed class NightVisionSystem : SwitchableOverlaySystem<NightVisionComponent, ToggleNightVisionEvent>;
|
||||
@@ -0,0 +1,48 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Overlays.Switchable;
|
||||
|
||||
public abstract partial class SwitchableOverlayComponent : BaseOverlayComponent
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool IsActive;
|
||||
|
||||
[DataField]
|
||||
public bool DrawOverlay = true;
|
||||
|
||||
/// <summary>
|
||||
/// If it is greater than 0, overlay isn't toggled but pulsed instead
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float PulseTime;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public float PulseAccumulator;
|
||||
|
||||
[DataField]
|
||||
public virtual SoundSpecifier? ActivateSound { get; set; } =
|
||||
new SoundPathSpecifier("/Audio/Items/Goggles/activate.ogg");
|
||||
|
||||
[DataField]
|
||||
public virtual SoundSpecifier? DeactivateSound { get; set; } =
|
||||
new SoundPathSpecifier("/Audio/Items/Goggles/deactivate.ogg");
|
||||
|
||||
[DataField]
|
||||
public virtual string? ToggleAction { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid? ToggleActionEntity;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SwitchableVisionOverlayComponentState : IComponentState
|
||||
{
|
||||
public Color Color;
|
||||
public bool IsActive;
|
||||
public SoundSpecifier? ActivateSound;
|
||||
public SoundSpecifier? DeactivateSound;
|
||||
public EntProtoId? ToggleAction;
|
||||
public float LightRadius;
|
||||
}
|
||||
166
Content.Shared/Overlays/Switchable/SwitchableOverlaySystem.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Overlays.Switchable;
|
||||
|
||||
public abstract class SwitchableOverlaySystem<TComp, TEvent> : EntitySystem
|
||||
where TComp : SwitchableOverlayComponent
|
||||
where TEvent : InstantActionEvent
|
||||
{
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<TComp, TEvent>(OnToggle);
|
||||
SubscribeLocalEvent<TComp, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<TComp, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<TComp, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<TComp, GetItemActionsEvent>(OnGetItemActions);
|
||||
SubscribeLocalEvent<TComp, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<TComp, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
if (_net.IsClient)
|
||||
ActiveTick(frameTime);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (_net.IsServer)
|
||||
ActiveTick(frameTime);
|
||||
}
|
||||
|
||||
private void ActiveTick(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<TComp>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.PulseTime <= 0f || comp.PulseAccumulator >= comp.PulseTime)
|
||||
continue;
|
||||
|
||||
comp.PulseAccumulator += frameTime;
|
||||
|
||||
if (comp.PulseAccumulator < comp.PulseTime)
|
||||
continue;
|
||||
|
||||
Toggle(uid, comp, false, false);
|
||||
RaiseSwitchableOverlayToggledEvent(uid, uid, comp.IsActive);
|
||||
RaiseSwitchableOverlayToggledEvent(uid, Transform(uid).ParentUid, comp.IsActive);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, TComp component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new SwitchableVisionOverlayComponentState
|
||||
{
|
||||
Color = component.Color,
|
||||
IsActive = component.IsActive,
|
||||
ActivateSound = component.ActivateSound,
|
||||
DeactivateSound = component.DeactivateSound,
|
||||
ToggleAction = component.ToggleAction,
|
||||
LightRadius = component is ThermalVisionComponent thermal ? thermal.LightRadius : 0f,
|
||||
};
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, TComp component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not SwitchableVisionOverlayComponentState state)
|
||||
return;
|
||||
|
||||
component.Color = state.Color;
|
||||
component.ActivateSound = state.ActivateSound;
|
||||
component.DeactivateSound = state.DeactivateSound;
|
||||
|
||||
if (component.ToggleAction != state.ToggleAction)
|
||||
{
|
||||
_actions.RemoveAction(uid, component.ToggleActionEntity);
|
||||
component.ToggleAction = state.ToggleAction;
|
||||
if (component.ToggleAction != null)
|
||||
_actions.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
if (component is ThermalVisionComponent thermal)
|
||||
thermal.LightRadius = state.LightRadius;
|
||||
|
||||
if (component.IsActive == state.IsActive)
|
||||
return;
|
||||
|
||||
component.IsActive = state.IsActive;
|
||||
|
||||
RaiseSwitchableOverlayToggledEvent(uid, uid, component.IsActive);
|
||||
RaiseSwitchableOverlayToggledEvent(uid, Transform(uid).ParentUid, component.IsActive);
|
||||
}
|
||||
|
||||
private void OnGetItemActions(Entity<TComp> ent, ref GetItemActionsEvent args)
|
||||
{
|
||||
if (ent.Comp.ToggleAction != null && args.SlotFlags is not SlotFlags.POCKET and not null)
|
||||
args.AddAction(ref ent.Comp.ToggleActionEntity, ent.Comp.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, TComp component, ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveAction(uid, component.ToggleActionEntity);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, TComp component, ComponentInit args)
|
||||
{
|
||||
component.PulseAccumulator = component.PulseTime;
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, TComp component, MapInitEvent args)
|
||||
{
|
||||
if (component.ToggleActionEntity == null && component.ToggleAction != null)
|
||||
_actions.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnToggle(EntityUid uid, TComp component, TEvent args)
|
||||
{
|
||||
Toggle(uid, component, !component.IsActive);
|
||||
RaiseSwitchableOverlayToggledEvent(uid, args.Performer, component.IsActive);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void Toggle(EntityUid uid, TComp component, bool activate, bool playSound = true)
|
||||
{
|
||||
if (playSound && _net.IsClient && _timing.IsFirstTimePredicted)
|
||||
{
|
||||
_audio.PlayEntity(activate ? component.ActivateSound : component.DeactivateSound,
|
||||
Filter.Local(),
|
||||
uid,
|
||||
false);
|
||||
}
|
||||
|
||||
if (component.PulseTime > 0f)
|
||||
{
|
||||
component.PulseAccumulator = activate ? 0f : component.PulseTime;
|
||||
return;
|
||||
}
|
||||
|
||||
component.IsActive = activate;
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void RaiseSwitchableOverlayToggledEvent(EntityUid uid, EntityUid user, bool activated)
|
||||
{
|
||||
var ev = new SwitchableOverlayToggledEvent(user, activated);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct SwitchableOverlayToggledEvent(EntityUid User, bool Activated);
|
||||
17
Content.Shared/Overlays/Switchable/ThermalVisionComponent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Overlays.Switchable;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ThermalVisionComponent : SwitchableOverlayComponent
|
||||
{
|
||||
public override string? ToggleAction { get; set; } = "ToggleThermalVision";
|
||||
|
||||
public override Color Color { get; set; } = Color.FromHex("#F84742");
|
||||
|
||||
[DataField]
|
||||
public float LightRadius = 5f;
|
||||
}
|
||||
|
||||
public sealed partial class ToggleThermalVisionEvent : InstantActionEvent;
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Content.Shared.Overlays.Switchable;
|
||||
|
||||
public sealed class SharedThermalVisionSystem : SwitchableOverlaySystem<ThermalVisionComponent, ToggleThermalVisionEvent>;
|
||||
BIN
Resources/Audio/Items/Goggles/activate.ogg
Normal file
9
Resources/Audio/Items/Goggles/attributions.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
- files: ["activate.ogg"]
|
||||
license: "CC-BY-NC-SA-3.0"
|
||||
copyright: "Taken from TGstation"
|
||||
source: "https://github.com/tgstation/tgstation"
|
||||
|
||||
- files: ["deactivate.ogg"]
|
||||
license: "CC-BY-NC-SA-3.0"
|
||||
copyright: "Taken from TGstation"
|
||||
source: "https://github.com/tgstation/tgstation"
|
||||
BIN
Resources/Audio/Items/Goggles/deactivate.ogg
Normal file
@@ -53,6 +53,8 @@ research-technology-advanced-anomaly-research = Advanced Anomaly Research
|
||||
research-technology-rped = Rapid Part Exchange
|
||||
research-technology-super-parts = Super Parts
|
||||
research-technology-deterrence = Deterrence Technologies
|
||||
research-technology-night-vision = Night vision
|
||||
research-technology-thermal-vision = Thermal vision
|
||||
|
||||
research-technology-janitorial-equipment = Janitorial Equipment
|
||||
research-technology-laundry-tech = Laundry Tech
|
||||
|
||||
@@ -329,6 +329,12 @@ uplink-hardsuit-syndieelite-desc = An elite version of the blood-red hardsuit, w
|
||||
uplink-clothing-outer-hardsuit-juggernaut-name = Cybersun Juggernaut Suit
|
||||
uplink-clothing-outer-hardsuit-juggernaut-desc = Hyper resilient armor made of materials tested in the Tau chromosphere facility. The only thing that's going to be slowing you down is this suit... and tasers.
|
||||
|
||||
uplink-night-vision-name = Night vision goggles
|
||||
uplink-night-vision-desc = They allow you to see in the dark, all while looking like normal sunglasses!
|
||||
|
||||
uplink-thermal-vision-name = Thermal vision goggles
|
||||
uplink-thermal-vision-desc = They allow you to see living creatures regardless of obstacles, all while looking like normal sunglasses!
|
||||
|
||||
# Misc
|
||||
uplink-cyberpen-name = Cybersun Pen
|
||||
uplink-cyberpen-desc = Cybersun's legal department pen, invaluable for forging documents and escaping prisons. Smells vaguely of hard-light and war profiteering.
|
||||
|
||||
@@ -48,3 +48,12 @@ ent-ActionToggleEyes = Открыть/закрыть глаза
|
||||
.desc = Закройте глаза, чтобы защитить их, или откройте, чтобы насладиться яркими цветами.
|
||||
ent-ActionToggleWagging = action-name-toggle-wagging
|
||||
.desc = action-description-toggle-wagging
|
||||
|
||||
ent-ToggleNightVision = Переключить ночное зрение
|
||||
.desc = Переключает ночное зрение.
|
||||
|
||||
ent-ToggleThermalVision = Переключить тепловизионное зрение
|
||||
.desc = Переключает тепловизионное зрение.
|
||||
|
||||
ent-PulseThermalVision = Подать термальный импульт
|
||||
.desc = Временно активируйте тепловизионное зрение.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Night Vision Goggles
|
||||
ent-ClothingEyesNightVisionGoggles = ПНВ
|
||||
.desc = Усовершенствованный дисплей, который обеспечивает видимость в полной темноте.
|
||||
ent-ClothingEyesNightVisionSecurityGoggles = ПНВ службы безопасности
|
||||
.desc = { ent-ClothingEyesNightVisionGoggles.desc }
|
||||
ent-ClothingEyesNightVisionMedicalGoggles = ПНВ медицинский
|
||||
.desc = { ent-ClothingEyesNightVisionGoggles.desc }
|
||||
ent-ClothingEyesNightVisionDiagnosticGoggles = ПНВ диагностический
|
||||
.desc = { ent-ClothingEyesNightVisionGoggles.desc }
|
||||
ent-ClothingEyesNightVisionGogglesSyndie = { ent-ClothingEyesNightVisionGoggles }
|
||||
.desc = { ent-ClothingEyesNightVisionGoggles.desc }
|
||||
ent-ClothingEyesNightVisionGogglesNukie = { ent-ClothingEyesNightVisionGoggles }
|
||||
.desc = { ent-ClothingEyesNightVisionGoggles.desc }
|
||||
|
||||
# Thermal Vision Goggles
|
||||
ent-ClothingEyesThermalVisionGoggles = ПТВ
|
||||
.desc = Термальность - это как отношения, важно не только наличие тепла, но и его распределение.
|
||||
ent-ClothingEyesThermalVisionMonocle = термонокль
|
||||
.desc = Видение сквозь стены ещё никогда не было таким нежным и личностным.
|
||||
ent-ClothingEyesThermalVisionGogglesSyndie = { ent-ClothingEyesThermalVisionGoggles }
|
||||
.desc = { ent-ClothingEyesThermalVisionGoggles.desc }
|
||||
ent-ClothingEyesThermalVisionGogglesNukie = { ent-ClothingEyesThermalVisionGoggles }
|
||||
.desc = { ent-ClothingEyesThermalVisionGoggles.desc }
|
||||
2
Resources/Locale/ru-RU/research/techologies.ftl
Normal file
@@ -0,0 +1,2 @@
|
||||
research-technology-night-vision = Ночное видение
|
||||
research-technology-thermal-vision = Термальное видение
|
||||
@@ -341,6 +341,13 @@ uplink-hardsuit-syndieelite-desc = Элитная версия кроваво-к
|
||||
|
||||
uplink-clothing-outer-hardsuit-juggernaut-name = Костюм джаггернаута Cybersun
|
||||
uplink-clothing-outer-hardsuit-juggernaut-desc = Сверхпрочная броня из материалов, испытанных в хромосферном комплексе Тау. Единственное, что будет замедлять вас - это сам костюм... и тазеры.
|
||||
|
||||
uplink-night-vision-name = Прибор ночного видения
|
||||
uplink-night-vision-desc = Позволяет вам видеть в темноте, при этом выглядя как обычные солнцезащитные очки!
|
||||
|
||||
uplink-thermal-vision-name = Прибор термального видения
|
||||
uplink-thermal-vision-desc = Позволяет вам видеть живых существ независимо от преград, при этом выглядя как обычные солнцезащитные очки!
|
||||
|
||||
# Misc
|
||||
uplink-cyberpen-name = Ручка Cybersun
|
||||
uplink-cyberpen-desc = Ручка юридического отдела "Cybersun". Бесценная для подделки документов и побегов из тюрем. Смутно пахнет хардлайтом и наживой на ВПК.
|
||||
@@ -443,4 +450,4 @@ uplink-hypodart-name = Гиподротик
|
||||
uplink-hypodart-desc = Неприметный на первый взгляд дротик с увеличенным резервуаром для химических веществ. Он вмещает в себя до 7 ед. реагентов и мгновенно впрыскивает их при попадании в цель. Изначально пуст.
|
||||
|
||||
uplink-helmet-name = Шлем спецназа
|
||||
uplink-helmet-desc = Чрезвычайно прочный шлем, обычно используемый военизированными формированиями. Он украшен гнусным рисунком в красную и черную полоску.
|
||||
uplink-helmet-desc = Чрезвычайно прочный шлем, обычно используемый военизированными формированиями. Он украшен гнусным рисунком в красную и черную полоску.
|
||||
|
||||
@@ -405,3 +405,42 @@
|
||||
event:
|
||||
!type:FabricateActionEvent
|
||||
fabrication: FoodGumball
|
||||
|
||||
- type: entity
|
||||
id: ToggleNightVision
|
||||
name: Switch night vision
|
||||
description: Switches night vision.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: BigAction
|
||||
priority: -20
|
||||
icon:
|
||||
sprite: Clothing/Eyes/Goggles/nightvision.rsi
|
||||
state: icon
|
||||
event: !type:ToggleNightVisionEvent
|
||||
|
||||
- type: entity
|
||||
id: ToggleThermalVision
|
||||
name: Switch Thermal vision
|
||||
description: Switches Thermal vision.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: BigAction
|
||||
priority: -20
|
||||
icon:
|
||||
sprite: Clothing/Eyes/Goggles/thermal.rsi
|
||||
state: icon
|
||||
event: !type:ToggleThermalVisionEvent
|
||||
|
||||
- type: entity
|
||||
id: PulseThermalVision
|
||||
parent: ToggleThermalVision
|
||||
name: Pulse Thermal Vision
|
||||
description: Activate thermal vision temporarily.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: InstantAction
|
||||
useDelay: 4
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
prob: 0.2
|
||||
- !type:EntSelector
|
||||
id: WeaponFlareGun
|
||||
prob: 0.1
|
||||
prob: 0.05
|
||||
- !type:EntSelector
|
||||
id: BoxMRE
|
||||
prob: 0.1
|
||||
@@ -165,6 +165,10 @@
|
||||
- !type:EntSelector
|
||||
id: WeaponSniperMosin
|
||||
weight: 2
|
||||
- !type:EntSelector
|
||||
id: ClothingEyesNightVisionGogglesSyndie
|
||||
- !type:EntSelector
|
||||
id: ClothingEyesThermalVisionGogglesSyndie
|
||||
|
||||
- type: entityTable
|
||||
id: MaintenanceLockerLoot
|
||||
|
||||
@@ -1532,6 +1532,78 @@
|
||||
components:
|
||||
- SurplusBundle
|
||||
|
||||
- type: listing
|
||||
id: UplinkNightGoggles
|
||||
name: uplink-night-vision-name
|
||||
description: uplink-night-vision-desc
|
||||
productEntity: ClothingEyesNightVisionGogglesSyndie
|
||||
discountCategory: rareDiscounts
|
||||
discountDownTo:
|
||||
Telecrystal: 1
|
||||
cost:
|
||||
Telecrystal: 3
|
||||
categories:
|
||||
- UplinkWearables
|
||||
conditions:
|
||||
- !type:StoreWhitelistCondition
|
||||
blacklist:
|
||||
tags:
|
||||
- NukeOpsUplink
|
||||
|
||||
- type: listing
|
||||
id: UplinkNightGogglesNukie
|
||||
name: uplink-night-vision-name
|
||||
description: uplink-night-vision-desc
|
||||
productEntity: ClothingEyesNightVisionGogglesNukie
|
||||
discountCategory: rareDiscounts
|
||||
discountDownTo:
|
||||
Telecrystal: 1
|
||||
cost:
|
||||
Telecrystal: 3
|
||||
categories:
|
||||
- UplinkWearables
|
||||
conditions:
|
||||
- !type:StoreWhitelistCondition
|
||||
whitelist:
|
||||
tags:
|
||||
- NukeOpsUplink
|
||||
|
||||
- type: listing
|
||||
id: UplinkThermalGoggles
|
||||
name: uplink-thermal-vision-name
|
||||
description: uplink-thermal-vision-desc
|
||||
productEntity: ClothingEyesThermalVisionGogglesSyndie
|
||||
discountCategory: rareDiscounts
|
||||
discountDownTo:
|
||||
Telecrystal: 1
|
||||
cost:
|
||||
Telecrystal: 3
|
||||
categories:
|
||||
- UplinkWearables
|
||||
conditions:
|
||||
- !type:StoreWhitelistCondition
|
||||
blacklist:
|
||||
tags:
|
||||
- NukeOpsUplink
|
||||
|
||||
- type: listing
|
||||
id: UplinkThermalGogglesNukie
|
||||
name: uplink-thermal-vision-name
|
||||
description: uplink-thermal-vision-desc
|
||||
productEntity: ClothingEyesThermalVisionGogglesNukie
|
||||
discountCategory: rareDiscounts
|
||||
discountDownTo:
|
||||
Telecrystal: 1
|
||||
cost:
|
||||
Telecrystal: 3
|
||||
categories:
|
||||
- UplinkWearables
|
||||
conditions:
|
||||
- !type:StoreWhitelistCondition
|
||||
whitelist:
|
||||
tags:
|
||||
- NukeOpsUplink
|
||||
|
||||
# Tools
|
||||
|
||||
- type: listing
|
||||
|
||||
@@ -233,6 +233,7 @@
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Glasses/ninjavisor.rsi
|
||||
- type: FlashImmunity
|
||||
- type: NightVision
|
||||
|
||||
- type: entity #Fake goggles, the latest in anti-valid hunting technology
|
||||
parent: ClothingEyesBase
|
||||
|
||||
114
Resources/Prototypes/Entities/Clothing/Eyes/goggles.yml
Normal file
@@ -0,0 +1,114 @@
|
||||
# Night Vision Goggles
|
||||
|
||||
- type: entity
|
||||
parent: ClothingEyesBase
|
||||
id: ClothingEyesNightVisionGoggles
|
||||
name: night vision goggles
|
||||
description: An advanced heads-up display which provides id data and vision in complete darkness.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Clothing/Eyes/Goggles/nightvision.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Goggles/nightvision.rsi
|
||||
- type: NightVision
|
||||
- type: IdentityBlocker
|
||||
coverage: EYES
|
||||
|
||||
- type: entity
|
||||
parent: [ClothingEyesNightVisionGoggles, ShowSecurityIcons]
|
||||
id: ClothingEyesNightVisionSecurityGoggles
|
||||
name: night vision security goggles
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Clothing/Eyes/Goggles/security_nightvision.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Goggles/security_nightvision.rsi
|
||||
|
||||
- type: entity
|
||||
parent: [ClothingEyesNightVisionGoggles, ClothingEyesHudMedical]
|
||||
id: ClothingEyesNightVisionMedicalGoggles
|
||||
name: night vision medical goggles
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Clothing/Eyes/Goggles/medical_nightvision.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Goggles/medical_nightvision.rsi
|
||||
|
||||
- type: entity
|
||||
parent: [ClothingEyesNightVisionGoggles, ClothingEyesHudDiagnostic]
|
||||
id: ClothingEyesNightVisionDiagnosticGoggles
|
||||
name: night vision diagnostic goggles
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Clothing/Eyes/Goggles/diagnostic_nightvision.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Goggles/diagnostic_nightvision.rsi
|
||||
|
||||
- type: entity
|
||||
parent: ClothingEyesNightVisionGoggles
|
||||
id: ClothingEyesNightVisionGogglesSyndie
|
||||
suffix: "Chameleon"
|
||||
components:
|
||||
- type: ChameleonClothing
|
||||
slot: [eyes]
|
||||
default: ClothingEyesNightVisionGoggles
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
enum.ChameleonUiKey.Key:
|
||||
type: ChameleonBoundUserInterface
|
||||
|
||||
- type: entity
|
||||
parent: [ClothingEyesNightVisionGogglesSyndie, ShowSecurityIcons]
|
||||
id: ClothingEyesNightVisionGogglesNukie
|
||||
suffix: "Chameleon, NukeOps"
|
||||
components:
|
||||
- type: ShowSyndicateIcons
|
||||
|
||||
# Thermal Vision Goggles
|
||||
|
||||
- type: entity
|
||||
parent: ClothingEyesBase
|
||||
id: ClothingEyesThermalVisionGoggles
|
||||
name: thermal vision goggles
|
||||
description: Thermals in the shape of glasses.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Clothing/Eyes/Goggles/thermal.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Goggles/thermal.rsi
|
||||
- type: ThermalVision
|
||||
pulseTime: 2
|
||||
toggleAction: PulseThermalVision
|
||||
- type: IdentityBlocker
|
||||
coverage: EYES
|
||||
|
||||
- type: entity
|
||||
parent: ClothingEyesThermalVisionGoggles
|
||||
id: ClothingEyesThermalVisionMonocle
|
||||
name: thermonocle
|
||||
description: Never before has seeing through walls felt so gentlepersonly.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Clothing/Eyes/Goggles/monocle_thermal.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Goggles/monocle_thermal.rsi
|
||||
|
||||
- type: entity
|
||||
parent: ClothingEyesThermalVisionGoggles
|
||||
id: ClothingEyesThermalVisionGogglesSyndie
|
||||
suffix: "Chameleon"
|
||||
components:
|
||||
- type: ChameleonClothing
|
||||
slot: [eyes]
|
||||
default: ClothingEyesThermalVisionGoggles
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
enum.ChameleonUiKey.Key:
|
||||
type: ChameleonBoundUserInterface
|
||||
|
||||
- type: entity
|
||||
parent: [ClothingEyesThermalVisionGogglesSyndie, ShowSecurityIcons]
|
||||
id: ClothingEyesThermalVisionGogglesNukie
|
||||
suffix: "Chameleon, NukeOps"
|
||||
components:
|
||||
- type: ShowSyndicateIcons
|
||||
@@ -799,6 +799,9 @@
|
||||
- type: FlashSoundSuppression
|
||||
maxRange: 0
|
||||
# WD EDIT END
|
||||
- type: ThermalVision
|
||||
color: "#98EEFB"
|
||||
lightRadius: 15
|
||||
|
||||
#MISC. HARDSUITS
|
||||
#Clown Hardsuit
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
cell_slot:
|
||||
name: power-cell-slot-component-slot-name-default
|
||||
- type: Body
|
||||
thermalVisibility: false
|
||||
- type: StatusEffects
|
||||
allowed:
|
||||
- Stun
|
||||
|
||||
@@ -70,6 +70,12 @@
|
||||
barkType: mouse
|
||||
minTime: 10 # Mice like to squeak, I think. You can always put your pet mouse to sleep if it gets annoying
|
||||
maxTime: 160
|
||||
- type: NightVision
|
||||
isActive: true
|
||||
toggleAction: null
|
||||
color: "#808080"
|
||||
activateSound: null
|
||||
deactivateSound: null
|
||||
|
||||
- type: entity
|
||||
name: bee
|
||||
@@ -1793,6 +1799,12 @@
|
||||
Taco: RatTaco
|
||||
Burger: RatBurger
|
||||
Skewer: RatSkewer
|
||||
- type: NightVision
|
||||
isActive: true
|
||||
toggleAction: null
|
||||
color: "#808080"
|
||||
activateSound: null
|
||||
deactivateSound: null
|
||||
# WD EDIT START
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
|
||||
@@ -88,6 +88,12 @@
|
||||
interfaces:
|
||||
enum.SurgeryUIKey.Key:
|
||||
type: SurgeryBui
|
||||
- type: NightVision
|
||||
isActive: true
|
||||
toggleAction: null
|
||||
color: "#808080"
|
||||
activateSound: null
|
||||
deactivateSound: null
|
||||
|
||||
- type: entity
|
||||
parent: BaseMobCarp
|
||||
|
||||
@@ -132,6 +132,12 @@
|
||||
interfaces:
|
||||
enum.SurgeryUIKey.Key:
|
||||
type: SurgeryBui
|
||||
- type: NightVision
|
||||
isActive: true
|
||||
toggleAction: null
|
||||
color: "#808080"
|
||||
activateSound: null
|
||||
deactivateSound: null
|
||||
|
||||
- type: entity
|
||||
id: MobRatKingBuff
|
||||
@@ -317,6 +323,12 @@
|
||||
- type: FireVisuals
|
||||
sprite: Mobs/Effects/onfire.rsi
|
||||
normalState: Mouse_burning
|
||||
- type: NightVision
|
||||
isActive: true
|
||||
toggleAction: null
|
||||
color: "#808080"
|
||||
activateSound: null
|
||||
deactivateSound: null
|
||||
|
||||
- type: weightedRandomEntity
|
||||
id: RatKingLoot
|
||||
|
||||
@@ -140,6 +140,12 @@
|
||||
- Xeno
|
||||
understands:
|
||||
- Xeno
|
||||
- type: ThermalVision
|
||||
isActive: true
|
||||
lightRadius: 15
|
||||
color: "#808080"
|
||||
activateSound: null
|
||||
deactivateSound: null
|
||||
|
||||
- type: entity
|
||||
name: Praetorian
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
bodyType: Kinematic
|
||||
- type: Body
|
||||
prototype: Aghost
|
||||
thermalVisibility: false
|
||||
- type: Access
|
||||
groups:
|
||||
- AllAccess
|
||||
|
||||
@@ -140,6 +140,11 @@
|
||||
tags:
|
||||
- CannotSuicide
|
||||
- DoorBumpOpener
|
||||
- type: NightVision
|
||||
isActive: true
|
||||
color: "#808080"
|
||||
activateSound: null
|
||||
deactivateSound: null
|
||||
|
||||
- type: entity
|
||||
parent: BaseMobDragon
|
||||
|
||||
@@ -233,6 +233,7 @@
|
||||
- type: Body
|
||||
prototype: IPC
|
||||
requiredLegs: 2
|
||||
thermalVisibility: false
|
||||
- type: Ensnareable
|
||||
sprite: Objects/Misc/ensnare.rsi
|
||||
- type: Speech
|
||||
|
||||
@@ -362,6 +362,9 @@
|
||||
- AnimalTranslator
|
||||
- MofficTranslatorImplanter
|
||||
- MofficTranslator
|
||||
- ClothingEyesNightVisionGoggles
|
||||
- ClothingEyesNightVisionDiagnosticGoggles
|
||||
- ClothingEyesThermalVisionGoggles
|
||||
- RCDAmmo #DeltaV
|
||||
- RCD #EE
|
||||
# Shitmed Change
|
||||
@@ -911,6 +914,7 @@
|
||||
- WeaponDisablerSMG
|
||||
- WeaponLaserCannon
|
||||
- WeaponLaserCarbine
|
||||
- ClothingEyesNightVisionSecurityGoggles
|
||||
- ClothingHeadHelmetInsulated # Nyanotrasen - Insulative headgear
|
||||
- ClothingHeadCage # Nyanotrasen - Insulative headgear
|
||||
- ShockCollar # Nyanotrasen - Shock Collar
|
||||
@@ -1059,6 +1063,7 @@
|
||||
- ClothingEyesHudMedical # Nyano
|
||||
- ChemicalPayload # Nyano
|
||||
- SyringeCryostasis
|
||||
- ClothingEyesNightVisionMedicalGoggles
|
||||
# Shitmed Change
|
||||
- EnergyScalpel
|
||||
- EnergyCautery
|
||||
|
||||
@@ -106,6 +106,10 @@
|
||||
- proto: WeaponTeslaGun
|
||||
prob: 0.1
|
||||
cost: 2
|
||||
- proto: ClothingEyesNightVisionGoggles
|
||||
cost: 8
|
||||
- proto: ClothingEyesGlassesThermal
|
||||
cost: 8
|
||||
|
||||
# Mob loot table
|
||||
|
||||
|
||||
@@ -234,3 +234,33 @@
|
||||
Steel: 500
|
||||
Glass: 400
|
||||
Gold: 100
|
||||
|
||||
- type: latheRecipe
|
||||
id: ClothingEyesNightVisionGoggles
|
||||
result: ClothingEyesNightVisionGoggles
|
||||
completetime: 2
|
||||
materials:
|
||||
Steel: 200
|
||||
Glass: 100
|
||||
Silver: 100
|
||||
Gold: 100
|
||||
|
||||
- type: latheRecipe
|
||||
id: ClothingEyesNightVisionDiagnosticGoggles
|
||||
result: ClothingEyesNightVisionDiagnosticGoggles
|
||||
completetime: 2
|
||||
materials:
|
||||
Steel: 200
|
||||
Glass: 100
|
||||
Silver: 100
|
||||
Gold: 100
|
||||
|
||||
- type: latheRecipe
|
||||
id: ClothingEyesThermalVisionGoggles
|
||||
result: ClothingEyesThermalVisionGoggles
|
||||
completetime: 2
|
||||
materials:
|
||||
Steel: 200
|
||||
Glass: 100
|
||||
Silver: 100
|
||||
Gold: 100
|
||||
|
||||
@@ -250,4 +250,15 @@
|
||||
completetime: 2
|
||||
materials:
|
||||
Steel: 100
|
||||
Plastic: 100
|
||||
Plastic: 100
|
||||
|
||||
- type: latheRecipe
|
||||
id: ClothingEyesNightVisionMedicalGoggles
|
||||
result: ClothingEyesNightVisionMedicalGoggles
|
||||
completetime: 7
|
||||
materials:
|
||||
Steel: 300
|
||||
Glass: 300
|
||||
Silver: 100
|
||||
Gold: 100
|
||||
Plasma: 200
|
||||
|
||||
@@ -995,4 +995,14 @@
|
||||
completetime: 2
|
||||
materials:
|
||||
Plastic: 15
|
||||
Uranium: 10
|
||||
Uranium: 10
|
||||
|
||||
- type: latheRecipe
|
||||
id: ClothingEyesNightVisionSecurityGoggles
|
||||
result: ClothingEyesNightVisionSecurityGoggles
|
||||
completetime: 5
|
||||
materials:
|
||||
Steel: 500
|
||||
Glass: 300
|
||||
Silver: 100
|
||||
Gold: 100
|
||||
|
||||
@@ -167,6 +167,33 @@
|
||||
- MedicalScannerMachineCircuitboard
|
||||
- MetempsychoticMachineCircuitboard
|
||||
|
||||
- type: technology
|
||||
id: NightVisionTech
|
||||
name: research-technology-night-vision
|
||||
icon:
|
||||
sprite: Clothing/Eyes/Goggles/nightvision.rsi
|
||||
state: icon
|
||||
discipline: Experimental
|
||||
tier: 2
|
||||
cost: 10000
|
||||
recipeUnlocks:
|
||||
- ClothingEyesNightVisionGoggles
|
||||
- ClothingEyesNightVisionSecurityGoggles
|
||||
- ClothingEyesNightVisionMedicalGoggles
|
||||
- ClothingEyesNightVisionDiagnosticGoggles
|
||||
|
||||
- type: technology
|
||||
id: ThermalVisionTech
|
||||
name: research-technology-thermal-vision
|
||||
icon:
|
||||
sprite: Clothing/Eyes/Goggles/thermal.rsi
|
||||
state: icon
|
||||
discipline: Experimental
|
||||
tier: 2
|
||||
cost: 10000
|
||||
recipeUnlocks:
|
||||
- ClothingEyesThermalVisionGoggles
|
||||
|
||||
# Tier 3
|
||||
|
||||
- type: technology
|
||||
|
||||
@@ -127,4 +127,9 @@
|
||||
- type: shader
|
||||
id: Ethereal
|
||||
kind: source
|
||||
path: "/Textures/Shaders/ethereal.swsl"
|
||||
path: "/Textures/Shaders/ethereal.swsl"
|
||||
|
||||
- type: shader
|
||||
id: NightVision
|
||||
kind: source
|
||||
path: "/Textures/Shaders/nightvision.swsl"
|
||||
|
||||
@@ -113,6 +113,12 @@
|
||||
sprite: Clothing/Eyes/Misc/blindfold.rsi
|
||||
- type: FlashImmunity
|
||||
- type: EyeProtection
|
||||
- type: NightVision
|
||||
isActive: true
|
||||
toggleAction: null
|
||||
activateSound: null
|
||||
deactivateSound: null
|
||||
color: White
|
||||
- type: ShowHealthBars
|
||||
damageContainers:
|
||||
- Biological
|
||||
@@ -120,5 +126,3 @@
|
||||
- type: ShowHealthIcons
|
||||
damageContainers:
|
||||
- Biological
|
||||
# TODO: ADD NIGHT VISION
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 452 B |
|
After Width: | Height: | Size: 739 B |
|
After Width: | Height: | Size: 306 B |
|
After Width: | Height: | Size: 233 B |
|
After Width: | Height: | Size: 230 B |
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from https://github.com/BlueMoon-Labs/MOLOT-BlueMoon-Station/blob/master/icons/obj/clothing/glasses.dmi",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "equipped-EYES",
|
||||
"directions": 4,
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "equipped-EYES-off",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 452 B |
|
After Width: | Height: | Size: 733 B |
|
After Width: | Height: | Size: 306 B |
|
After Width: | Height: | Size: 233 B |
|
After Width: | Height: | Size: 230 B |
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from https://github.com/BlueMoon-Labs/MOLOT-BlueMoon-Station/blob/master/icons/obj/clothing/glasses.dmi",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "equipped-EYES",
|
||||
"directions": 4,
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "equipped-EYES-off",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 477 B |
|
After Width: | Height: | Size: 410 B |
|
After Width: | Height: | Size: 298 B |
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from https://github.com/BlueMoon-Labs/MOLOT-BlueMoon-Station/blob/master/icons/obj/clothing/glasses.dmi",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "equipped-EYES",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "equipped-EYES-off",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "icon"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 452 B |
|
After Width: | Height: | Size: 719 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 385 B |
|
After Width: | Height: | Size: 410 B |
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "equipped-EYES",
|
||||
"directions": 4,
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "equipped-EYES-off",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 452 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 505 B |
|
After Width: | Height: | Size: 233 B |
|
After Width: | Height: | Size: 309 B |
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from https://github.com/BlueMoon-Labs/MOLOT-BlueMoon-Station/blob/master/icons/obj/clothing/glasses.dmi",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "equipped-EYES",
|
||||
"directions": 4,
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "equipped-EYES-off",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 524 B |
BIN
Resources/Textures/Clothing/Eyes/Goggles/thermal.rsi/icon.png
Normal file
|
After Width: | Height: | Size: 510 B |
|
After Width: | Height: | Size: 327 B |
|
After Width: | Height: | Size: 325 B |
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "equipped-EYES",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
38
Resources/Textures/Shaders/nightvision.swsl
Normal file
@@ -0,0 +1,38 @@
|
||||
light_mode unshaded;
|
||||
|
||||
uniform sampler2D SCREEN_TEXTURE;
|
||||
uniform highp vec3 tint; // Colour of the tint
|
||||
uniform highp float luminance_threshold; // number between 0 and 1
|
||||
uniform highp float noise_amount; // number between 0 and 1
|
||||
|
||||
lowp float rand (lowp vec2 n) {
|
||||
return 0.5 + 0.5 * fract (sin (dot (n.xy, vec2 (12.9898, 78.233)))* 43758.5453);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
|
||||
highp vec4 color = zTextureSpec(SCREEN_TEXTURE, FRAGCOORD.xy * SCREEN_PIXEL_SIZE);
|
||||
|
||||
// convert color to grayscale using luminance
|
||||
highp float grey = dot(color.rgb, vec3(0.298, 0.5882, 0.1137));
|
||||
|
||||
// calculate local threshold
|
||||
highp float threshold = grey * luminance_threshold;
|
||||
|
||||
// amplify low luminance parts
|
||||
if (grey < threshold) {
|
||||
grey += (threshold - grey) * 0.5;
|
||||
if (grey > 1.0) {
|
||||
grey = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
// apply night vision color tint
|
||||
color.rgb = mix(color.rgb, tint, grey);
|
||||
|
||||
// add some noise for realism
|
||||
lowp float noise = rand(FRAGCOORD.xy + TIME) * noise_amount / 10.0;
|
||||
color.rgb += noise;
|
||||
|
||||
COLOR = color;
|
||||
}
|
||||