diff --git a/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml b/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
index 796af9c5735..8b68487547f 100644
--- a/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
+++ b/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
@@ -16,9 +16,6 @@
-
-
-
diff --git a/Content.Client/_CorvaxNext/Administration/UI/Audio/AdminAudioPanelEui.cs b/Content.Client/_CorvaxNext/Administration/UI/Audio/AdminAudioPanelEui.cs
deleted file mode 100644
index 806d55cd407..00000000000
--- a/Content.Client/_CorvaxNext/Administration/UI/Audio/AdminAudioPanelEui.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-using Content.Client.Eui;
-using Content.Client._CorvaxNext.Administration.UI.Audio.Widgets;
-using Content.Shared.Eui;
-using Content.Shared._CorvaxNext.Administration.UI.Audio;
-using Robust.Shared.Audio.Components;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Utility;
-
-namespace Content.Client._CorvaxNext.Administration.UI.Audio;
-
-public sealed partial class AdminAudioPanelEui : BaseEui
-{
- [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
- [Dependency] private readonly IEntityManager _entity = default!;
-
- private SharedAudioSystem _audioSystem;
-
- private AdminAudioPanelEuiState? _state = null;
- private AdminAudioPanel? _adminAudioPanel = null;
-
- public AdminAudioPanelEui() : base()
- {
- IoCManager.InjectDependencies(this);
- _audioSystem = _entitySystem.GetEntitySystem();
- }
-
- public override void HandleState(EuiStateBase state)
- {
- if (state is AdminAudioPanelEuiState adminAudioPanelState)
- {
- _state = adminAudioPanelState;
- UpdateUI();
- }
- }
-
- public override void Opened()
- {
- _adminAudioPanel = new AdminAudioPanel();
- _adminAudioPanel.OpenCentered();
-
- _adminAudioPanel.OnPlayButtonEnabled += () => Play();
- _adminAudioPanel.OnPauseButtonEnabled += () => Pause();
- _adminAudioPanel.OnStopButtonEnabled += () => Stop();
- _adminAudioPanel.OnAddTrackPressed += (track) => AddTrack(track);
- _adminAudioPanel.OnPlaybackReleased += (ratio) => SetPlayback(ratio);
- _adminAudioPanel.OnGlobalCheckboxToggled += (toggled) => ChangeGlobalToggled(toggled);
- _adminAudioPanel.OnVolumeLineTextChanged += (volume) => SetVolume(volume);
- _adminAudioPanel.OnSelectPlayer += (guid) => SelectPlayer(guid);
- _adminAudioPanel.OnUnselectPlayer += (guid) => UnselectPlayer(guid);
- }
-
- public override void Closed()
- {
- if (_adminAudioPanel != null)
- _adminAudioPanel.Close();
- }
-
- public void Play()
- {
- var message = new AdminAudioPanelEuiMessage.Play();
- SendMessage(message);
- }
-
- public void Stop()
- {
- var message = new AdminAudioPanelEuiMessage.Stop();
- SendMessage(message);
- }
-
- public void Pause()
- {
- var message = new AdminAudioPanelEuiMessage.Pause();
- SendMessage(message);
- }
-
- public void SetPlayback(float ratio)
- {
- var message = new AdminAudioPanelEuiMessage.SetPlaybackPosition(ratio);
- SendMessage(message);
- }
-
- public void AddTrack(string track)
- {
- var message = new AdminAudioPanelEuiMessage.AddTrack(track);
- SendMessage(message);
- }
-
- public void ChangeGlobalToggled(bool toggled)
- {
- var message = new AdminAudioPanelEuiMessage.GlobalToggled(toggled);
- SendMessage(message);
- }
-
- public void SetVolume(float volume)
- {
- var message = new AdminAudioPanelEuiMessage.SetVolume(volume);
- SendMessage(message);
- }
-
- private void SelectPlayer(Guid player)
- {
- var message = new AdminAudioPanelEuiMessage.SelectPlayer(player);
- SendMessage(message);
- }
-
- private void UnselectPlayer(Guid player)
- {
- var message = new AdminAudioPanelEuiMessage.UnselectPlayer(player);
- SendMessage(message);
- }
-
- private void UpdateUI()
- {
- if (_adminAudioPanel is not { })
- return;
-
- if (_state is not { })
- return;
-
- var audioEntity = _entity.GetEntity(_state.Audio);
-
- _adminAudioPanel.SetAudioStream(audioEntity);
- _adminAudioPanel.UpdateGlobalToggled(_state.Global);
- _adminAudioPanel.UpdatePlayersContainer(_state.Players, _state.SelectedPlayers);
- _adminAudioPanel.UpdatePlayingState(_state.Playing);
- _adminAudioPanel.UpdateQueue(_state.Queue);
- _adminAudioPanel.UpdateVolume(_state.Volume);
-
- if (_entity.TryGetComponent(audioEntity, out var audio))
- {
- _adminAudioPanel.UpdateCurrentTrackLabel(audio.FileName);
- }
- else
- {
- _adminAudioPanel.UpdateCurrentTrackLabel(Loc.GetString("admin-audio-panel-track-name-nothing-playing"));
- }
- }
-}
diff --git a/Content.Client/_CorvaxNext/Administration/UI/Audio/Widgets/AdminAudioPanel.xaml b/Content.Client/_CorvaxNext/Administration/UI/Audio/Widgets/AdminAudioPanel.xaml
deleted file mode 100644
index 79e45b2bc8d..00000000000
--- a/Content.Client/_CorvaxNext/Administration/UI/Audio/Widgets/AdminAudioPanel.xaml
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Content.Client/_CorvaxNext/Administration/UI/Audio/Widgets/AdminAudioPanel.xaml.cs b/Content.Client/_CorvaxNext/Administration/UI/Audio/Widgets/AdminAudioPanel.xaml.cs
deleted file mode 100644
index 21af1696611..00000000000
--- a/Content.Client/_CorvaxNext/Administration/UI/Audio/Widgets/AdminAudioPanel.xaml.cs
+++ /dev/null
@@ -1,185 +0,0 @@
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface.CustomControls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Utility;
-using System.Linq;
-using Robust.Shared.Timing;
-using Robust.Shared.Audio.Components;
-using Robust.Shared.Audio.Systems;
-
-namespace Content.Client._CorvaxNext.Administration.UI.Audio.Widgets;
-
-[GenerateTypedNameReferences]
-public sealed partial class AdminAudioPanel : DefaultWindow
-{
- [Dependency] private readonly IEntityManager _entity = default!;
- [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
-
- private readonly SharedAudioSystem _audioSystem;
-
- public event Action? OnPlayButtonEnabled;
- public event Action? OnStopButtonEnabled;
- public event Action? OnPauseButtonEnabled;
- public event Action? OnPlaybackReleased;
- public event Action? OnVolumeLineTextChanged;
- public event Action? OnAddTrackPressed;
- public event Action? OnGlobalCheckboxToggled;
- public event Action? OnSelectPlayer;
- public event Action? OnUnselectPlayer;
-
- private string _volumeLineText = "";
- private EntityUid _audio;
-
- public AdminAudioPanel()
- {
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
-
- _audioSystem = _entitySystem.GetEntitySystem();
-
- PlayButton.OnToggled += (args) =>
- {
- if (args.Button.Pressed)
- {
- OnPlayButtonEnabled?.Invoke();
- }
- else
- {
- PlayButton.Pressed = true;
- }
- };
- StopButton.OnPressed += (args) =>
- {
- if (!PlayButton.Pressed)
- return;
-
- OnStopButtonEnabled?.Invoke();
- PlayButton.Pressed = false;
- };
- PauseButton.OnPressed += (args) =>
- {
- if (!PlayButton.Pressed)
- return;
-
- OnPauseButtonEnabled?.Invoke();
- PlayButton.Pressed = false;
- };
- PlaybackSlider.OnReleased += (slider) => OnPlaybackReleased?.Invoke(slider.Value);
- VolumeLine.OnTextEntered += (args) =>
- {
- // performs validation of text the user is typing to field
- // doesn't let type something that isn't a parsible value
- if (float.TryParse(args.Text, out var result))
- {
- _volumeLineText = args.Text;
- OnVolumeLineTextChanged?.Invoke(result);
- }
- else
- {
- args.Control.SetText(_volumeLineText);
- }
- };
- AddTrackButton.OnPressed += (args) =>
- {
- OnAddTrackPressed?.Invoke(TrackPathLine.Text);
- TrackPathLine.SetText("");
- };
- GlobalCheckbox.OnToggled += (args) => OnGlobalCheckboxToggled?.Invoke(args.Pressed);
- }
-
- public void SetAudioStream(EntityUid audio)
- {
- _audio = audio;
- }
-
- protected override void FrameUpdate(FrameEventArgs args)
- {
- if (_entity.TryGetComponent(_audio, out var audio))
- {
- var currentTrackLength = _audioSystem.GetAudioLength(audio.FileName);
- var playbackPosition = TimeSpan.FromSeconds(audio.PlaybackPosition);
-
- UpdateDurationLabel(playbackPosition, currentTrackLength);
- if (!PlaybackSlider.Grabbed)
- UpdatePlaybackPosition(currentTrackLength, playbackPosition);
- }
- }
-
- public void UpdatePlayersContainer(Dictionary players, HashSet selectedPlayers)
- {
- PlayersContainer.RemoveAllChildren();
-
- foreach (var player in players)
- {
- var newButton = new Button
- {
- ClipText = true,
- ToggleMode = true,
- Text = player.Value,
- HorizontalExpand = true,
- Pressed = selectedPlayers.FirstOrNull(selectedPlayer => selectedPlayer == player.Key) != null,
- Disabled = GlobalCheckbox.Pressed,
- };
- newButton.OnToggled += (args) =>
- {
- if (args.Pressed)
- {
- OnSelectPlayer?.Invoke(player.Key);
- }
- else
- {
- OnUnselectPlayer?.Invoke(player.Key);
- }
- };
-
- PlayersContainer.AddChild(newButton);
- }
- }
-
- public void UpdatePlayingState(bool playing)
- {
- PlayButton.Pressed = playing;
- }
-
- public void UpdateGlobalToggled(bool toggled)
- {
- GlobalCheckbox.Pressed = toggled;
- }
-
- public void UpdatePlaybackPosition(TimeSpan currentTrackLength, TimeSpan playbackPosition)
- {
- PlaybackSlider.MaxValue = (float)currentTrackLength.TotalSeconds;
- PlaybackSlider.SetValueWithoutEvent((float)playbackPosition.TotalSeconds);
- }
-
- public void UpdateDurationLabel(TimeSpan playbackPosition, TimeSpan currentTrackLength)
- {
- DurationLabel.Text = $@"{playbackPosition:mm\:ss} / {currentTrackLength:mm\:ss}";
- }
-
- public void UpdateCurrentTrackLabel(string currentTrack)
- {
- TrackName.Text = currentTrack.Split("/").Last();
- }
-
- public void UpdateQueue(Queue queue)
- {
- TrackList.RemoveAllChildren();
-
- foreach (var track in queue.ToList())
- {
- var label = new Label()
- {
- Text = track,
- };
- TrackList.AddChild(label);
- }
- }
-
- public void UpdateVolume(float volume)
- {
- VolumeLine.SetText(volume.ToString());
- _volumeLineText = volume.ToString();
- }
-}
diff --git a/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchEntryControl.xaml b/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchEntryControl.xaml
new file mode 100644
index 00000000000..12440db5d9b
--- /dev/null
+++ b/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchEntryControl.xaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchEntryControl.xaml.cs b/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchEntryControl.xaml.cs
new file mode 100644
index 00000000000..0b3bd362d2c
--- /dev/null
+++ b/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchEntryControl.xaml.cs
@@ -0,0 +1,21 @@
+using Content.Shared.CartridgeLoader.Cartridges;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._CorvaxNext.CartridgeLoader.Cartridges;
+
+[GenerateTypedNameReferences]
+public sealed partial class SecWatchEntryControl : BoxContainer
+{
+ public SecWatchEntryControl(SecWatchEntry entry)
+ {
+ RobustXamlLoader.Load(this);
+
+ Status.Text = Loc.GetString($"criminal-records-status-{entry.Status.ToString().ToLower()}");
+ Title.Text = Loc.GetString("sec-watch-entry", ("name", entry.Name), ("job", entry.Job));
+
+ Reason.Text = entry.Reason ?? Loc.GetString("sec-watch-no-reason");
+ }
+}
\ No newline at end of file
diff --git a/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchUi.cs b/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchUi.cs
new file mode 100644
index 00000000000..838f4395c73
--- /dev/null
+++ b/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchUi.cs
@@ -0,0 +1,27 @@
+using Content.Client.UserInterface.Fragments;
+using Content.Shared.CartridgeLoader;
+using Content.Shared.CartridgeLoader.Cartridges;
+using Robust.Client.UserInterface;
+
+namespace Content.Client._CorvaxNext.CartridgeLoader.Cartridges;
+
+public sealed partial class SecWatchUi : UIFragment
+{
+ private SecWatchUiFragment? _fragment;
+
+ public override Control GetUIFragmentRoot()
+ {
+ return _fragment!;
+ }
+
+ public override void Setup(BoundUserInterface ui, EntityUid? owner)
+ {
+ _fragment = new SecWatchUiFragment();
+ }
+
+ public override void UpdateState(BoundUserInterfaceState state)
+ {
+ if (state is SecWatchUiState cast)
+ _fragment?.UpdateState(cast);
+ }
+}
\ No newline at end of file
diff --git a/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchUiFragment.xaml b/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchUiFragment.xaml
new file mode 100644
index 00000000000..4ebea90f59c
--- /dev/null
+++ b/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchUiFragment.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchUiFragment.xaml.cs b/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchUiFragment.xaml.cs
new file mode 100644
index 00000000000..068ff2c8a38
--- /dev/null
+++ b/Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchUiFragment.xaml.cs
@@ -0,0 +1,25 @@
+using Content.Shared.CartridgeLoader.Cartridges;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._CorvaxNext.CartridgeLoader.Cartridges;
+
+[GenerateTypedNameReferences]
+public sealed partial class SecWatchUiFragment : BoxContainer
+{
+ public SecWatchUiFragment()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ public void UpdateState(SecWatchUiState state)
+ {
+ NoEntries.Visible = state.Entries.Count == 0;
+ Entries.RemoveAllChildren();
+ foreach (var entry in state.Entries)
+ {
+ Entries.AddChild(new SecWatchEntryControl(entry));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Content.Client/_CorvaxNext/Overlays/BaseSwitchableOverlay.cs b/Content.Client/_CorvaxNext/Overlays/BaseSwitchableOverlay.cs
new file mode 100644
index 00000000000..5910ae4a836
--- /dev/null
+++ b/Content.Client/_CorvaxNext/Overlays/BaseSwitchableOverlay.cs
@@ -0,0 +1,47 @@
+using Content.Shared._CorvaxNext.Overlays;
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Shared.Enums;
+using Robust.Shared.Prototypes;
+using System.Numerics;
+
+namespace Content.Client._CorvaxNext.Overlays;
+
+public class BaseSwitchableOverlay : Overlay
+ where TComp : SwitchableOverlayComponent
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly IEntityManager _entity = default!;
+
+ public override bool RequestScreenTexture => true;
+ public override OverlaySpace Space => OverlaySpace.WorldSpace;
+ private readonly ShaderInstance _shader;
+
+ public BaseSwitchableOverlay()
+ {
+ IoCManager.InjectDependencies(this);
+ _shader = _prototype.Index("NightVision").Instance().Duplicate();
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ if (ScreenTexture is null
+ || _player.LocalEntity is null
+ || !_entity.TryGetComponent(_player.LocalEntity.Value, out var component)
+ || !component.IsActive)
+ return;
+
+ _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
+ _shader.SetParameter("tint", component.Tint);
+ _shader.SetParameter("luminance_threshold", component.Strength);
+ _shader.SetParameter("noise_amount", component.Noise);
+
+ var worldHandle = args.WorldHandle;
+
+ worldHandle.SetTransform(Matrix3x2.Identity);
+ worldHandle.UseShader(_shader);
+ worldHandle.DrawRect(args.WorldBounds, component.Color);
+ worldHandle.UseShader(null);
+ }
+}
diff --git a/Content.Client/_CorvaxNext/Overlays/NightVisionSystem.cs b/Content.Client/_CorvaxNext/Overlays/NightVisionSystem.cs
new file mode 100644
index 00000000000..1b8909a1540
--- /dev/null
+++ b/Content.Client/_CorvaxNext/Overlays/NightVisionSystem.cs
@@ -0,0 +1,85 @@
+using Content.Shared._CorvaxNext.Overlays;
+using Content.Shared.GameTicking;
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Shared.Player;
+
+namespace Content.Client._CorvaxNext.Overlays;
+
+public sealed class NightVisionSystem : SwitchableOverlaySystem
+{
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly IOverlayManager _overlayMan = default!;
+ [Dependency] private readonly ILightManager _lightManager = default!;
+
+ private BaseSwitchableOverlay _overlay = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnPlayerAttached);
+ SubscribeLocalEvent(OnPlayerDetached);
+ SubscribeLocalEvent(OnRestart);
+
+ _overlay = new BaseSwitchableOverlay();
+ }
+
+ private void OnPlayerAttached(EntityUid uid, NightVisionComponent component, PlayerAttachedEvent args)
+ {
+ if (!component.IsActive)
+ return;
+
+ UpdateVision(args.Player, component.IsActive);
+ }
+
+ private void OnPlayerDetached(EntityUid uid, NightVisionComponent component, PlayerDetachedEvent args)
+ {
+ UpdateVision(args.Player, false);
+ }
+
+ private void OnRestart(RoundRestartCleanupEvent ev)
+ {
+ _overlayMan.RemoveOverlay(_overlay);
+ _lightManager.DrawLighting = true;
+ }
+
+ protected override void UpdateVision(EntityUid uid, bool active)
+ {
+ if (_player.LocalSession?.AttachedEntity != uid)
+ return;
+
+ UpdateOverlay(active);
+ UpdateNightVision(active);
+ }
+
+ private void UpdateVision(ICommonSession player, bool active)
+ {
+ if (_player.LocalSession != player)
+ return;
+
+ UpdateOverlay(active);
+ UpdateNightVision(active);
+ }
+
+ private void UpdateNightVision(bool active)
+ {
+ _lightManager.DrawLighting = !active;
+ }
+
+ private void UpdateOverlay(bool active)
+ {
+ if (_player.LocalEntity == null)
+ {
+ _overlayMan.RemoveOverlay(_overlay);
+ return;
+ }
+
+ active |= TryComp(_player.LocalEntity.Value, out var component) && component.IsActive;
+
+ if (active)
+ _overlayMan.AddOverlay(_overlay);
+ else
+ _overlayMan.RemoveOverlay(_overlay);
+ }
+}
diff --git a/Content.Client/_CorvaxNext/Overlays/ThermalVisionOverlay.cs b/Content.Client/_CorvaxNext/Overlays/ThermalVisionOverlay.cs
new file mode 100644
index 00000000000..6d7018a73b5
--- /dev/null
+++ b/Content.Client/_CorvaxNext/Overlays/ThermalVisionOverlay.cs
@@ -0,0 +1,130 @@
+using System.Linq;
+using System.Numerics;
+using Content.Client.Stealth;
+using Content.Shared._CorvaxNext.Overlays;
+using Content.Shared.Body.Components;
+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;
+
+namespace Content.Client._CorvaxNext.Overlays;
+
+public sealed class ThermalVisionOverlay : Overlay
+{
+ [Dependency] private readonly IEntityManager _entity = default!;
+ [Dependency] private readonly IPlayerManager _players = default!;
+
+ private readonly TransformSystem _transform;
+ private readonly OccluderSystem _occluder;
+ private readonly StealthSystem _stealth;
+ private readonly ContainerSystem _container;
+
+ public override bool RequestScreenTexture => true;
+ public override OverlaySpace Space => OverlaySpace.WorldSpace;
+
+ private readonly List _entries = [];
+
+ public ThermalVisionOverlay()
+ {
+ IoCManager.InjectDependencies(this);
+
+ _container = _entity.System();
+ _transform = _entity.System();
+ _occluder = _entity.System();
+ _stealth = _entity.System();
+
+ ZIndex = -1;
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ if (ScreenTexture is null
+ || _players.LocalEntity is null
+ || !_entity.TryGetComponent(_players.LocalEntity.Value, out var component)
+ || !component.IsActive)
+ return;
+
+ var worldHandle = args.WorldHandle;
+ var eye = args.Viewport.Eye;
+
+ if (eye is null)
+ return;
+
+ var mapId = eye.Position.MapId;
+ var eyeRot = eye.Rotation;
+
+ _entries.Clear();
+ var entities = _entity.EntityQueryEnumerator();
+ while (entities.MoveNext(out var uid, out var body, out var sprite, out var xform))
+ {
+ if (!CanSee(uid, sprite, body))
+ continue;
+
+ var entity = uid;
+
+ if (_container.TryGetOuterContainer(uid, xform, out var container))
+ {
+ var owner = container.Owner;
+ if (_entity.TryGetComponent(owner, out var ownerSprite)
+ && _entity.TryGetComponent(owner, out var ownerXform))
+ {
+ entity = owner;
+ sprite = ownerSprite;
+ xform = ownerXform;
+ }
+ }
+
+ if (_entries.Any(e => e.Ent.Item1 == entity))
+ continue;
+
+ _entries.Add(new ThermalVisionRenderEntry((entity, sprite, xform, body), mapId, eyeRot));
+ }
+
+ foreach (var entry in _entries)
+ {
+ Render(entry.Ent, entry.Map, worldHandle, entry.EyeRot);
+ }
+
+ worldHandle.SetTransform(Matrix3x2.Identity);
+ }
+
+ private void Render(Entity ent,
+ MapId? map,
+ DrawingHandleWorld handle,
+ Angle eyeRot)
+ {
+ var (uid, sprite, xform, body) = ent;
+ if (xform.MapID != map || HasOccluders(uid) || !CanSee(uid, sprite, body))
+ return;
+
+ var position = _transform.GetWorldPosition(xform);
+ var rotation = _transform.GetWorldRotation(xform);
+
+ sprite.Render(handle, eyeRot, rotation, position: position);
+ }
+
+ private bool CanSee(EntityUid uid, SpriteComponent sprite, BodyComponent body)
+ {
+ return sprite.Visible
+ && !_entity.HasComponent(uid)
+ && (!_entity.TryGetComponent(uid, out StealthComponent? stealth)
+ || _stealth.GetVisibility(uid, stealth) > 0.5f);
+ }
+
+ private bool HasOccluders(EntityUid uid)
+ {
+ var mapCoordinates = _transform.GetMapCoordinates(uid);
+ var occluders = _occluder.QueryAabb(mapCoordinates.MapId,
+ Box2.CenteredAround(mapCoordinates.Position, new Vector2(0.3f, 0.3f)));
+
+ return occluders.Any(o => o.Component.Enabled);
+ }
+}
+
+public record struct ThermalVisionRenderEntry(
+ (EntityUid, SpriteComponent, TransformComponent, BodyComponent) Ent,
+ MapId? Map,
+ Angle EyeRot);
diff --git a/Content.Client/_CorvaxNext/Overlays/ThermalVisionSystem.cs b/Content.Client/_CorvaxNext/Overlays/ThermalVisionSystem.cs
new file mode 100644
index 00000000000..ce3713c4be3
--- /dev/null
+++ b/Content.Client/_CorvaxNext/Overlays/ThermalVisionSystem.cs
@@ -0,0 +1,81 @@
+using Content.Shared._CorvaxNext.Overlays;
+using Content.Shared.GameTicking;
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Shared.Player;
+
+namespace Content.Client._CorvaxNext.Overlays;
+
+public sealed class ThermalVisionSystem : SwitchableOverlaySystem
+{
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly IOverlayManager _overlayMan = default!;
+
+ private ThermalVisionOverlay _thermalOverlay = default!;
+ private BaseSwitchableOverlay _overlay = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnPlayerAttached);
+ SubscribeLocalEvent(OnPlayerDetached);
+ SubscribeLocalEvent(OnRestart);
+
+ _thermalOverlay = new ThermalVisionOverlay();
+ _overlay = new BaseSwitchableOverlay();
+ }
+
+ private void OnPlayerAttached(EntityUid uid, ThermalVisionComponent component, PlayerAttachedEvent args)
+ {
+ if (!component.IsActive)
+ return;
+
+ UpdateVision(args.Player, component.IsActive);
+ }
+
+ private void OnPlayerDetached(EntityUid uid, ThermalVisionComponent component, PlayerDetachedEvent args)
+ {
+ UpdateVision(args.Player, false);
+ }
+
+ private void OnRestart(RoundRestartCleanupEvent ev)
+ {
+ _overlayMan.RemoveOverlay(_thermalOverlay);
+ _overlayMan.RemoveOverlay(_overlay);
+ }
+
+ protected override void UpdateVision(EntityUid uid, bool active)
+ {
+ if (_player.LocalSession?.AttachedEntity != uid)
+ return;
+
+ UpdateOverlay(active, _thermalOverlay);
+ UpdateOverlay(active, _overlay);
+ }
+
+ private void UpdateVision(ICommonSession player, bool active)
+ {
+ if (_player.LocalSession != player)
+ return;
+
+ UpdateOverlay(active, _thermalOverlay);
+ UpdateOverlay(active, _overlay);
+ }
+
+ private void UpdateOverlay(bool active, Overlay overlay)
+ {
+ if (_player.LocalEntity == null)
+ {
+ _overlayMan.RemoveOverlay(overlay);
+ return;
+ }
+
+ active |= TryComp(_player.LocalEntity.Value, out var component) && component.IsActive;
+
+ if (active)
+ _overlayMan.AddOverlay(overlay);
+ else
+ _overlayMan.RemoveOverlay(overlay);
+ }
+}
diff --git a/Content.Server/_CorvaxNext/Administration/UI/Audio/AdminAudioPanelEui.cs b/Content.Server/_CorvaxNext/Administration/UI/Audio/AdminAudioPanelEui.cs
deleted file mode 100644
index b36493970a6..00000000000
--- a/Content.Server/_CorvaxNext/Administration/UI/Audio/AdminAudioPanelEui.cs
+++ /dev/null
@@ -1,121 +0,0 @@
-using Content.Server.Administration.Managers;
-using Content.Server.EUI;
-using Content.Shared.Administration;
-using Content.Shared.Eui;
-using Content.Shared._CorvaxNext.Administration.UI.Audio;
-using Robust.Server.Player;
-using Robust.Shared.ContentPack;
-using Robust.Shared.Enums;
-using Robust.Shared.Player;
-using Robust.Shared.Utility;
-
-namespace Content.Server._CorvaxNext.Administration.UI.Audio;
-
-public sealed partial class AdminAudioPanelEui : BaseEui
-{
- [Dependency] private readonly IAdminManager _adminManager = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IEntityManager _entityManager = default!;
- [Dependency] private readonly IResourceManager _resourceManager = default!;
-
- private Dictionary _availablePlayers = new();
-
- private readonly AdminAudioPanelSystem _audioPanel;
-
- public AdminAudioPanelEui() : base()
- {
- IoCManager.InjectDependencies(this);
-
- _audioPanel = IoCManager.Resolve().GetEntitySystem();
-
- foreach (var player in Filter.Broadcast().Recipients)
- {
- _availablePlayers.Add(player.UserId.UserId, player.Name);
- }
-
- _audioPanel.AudioUpdated += () => StateDirty();
-
- _playerManager.PlayerStatusChanged += (object? sender, SessionStatusEventArgs args) =>
- {
- switch (args.NewStatus)
- {
- case SessionStatus.InGame:
- _availablePlayers.Add(args.Session.UserId.UserId, args.Session.Name);
- StateDirty();
- break;
- case SessionStatus.Disconnected:
- _availablePlayers.Remove(args.Session.UserId.UserId);
- StateDirty();
- break;
- }
- };
- }
-
- public override void Opened()
- {
- StateDirty();
- }
-
- public override AdminAudioPanelEuiState GetNewState()
- {
- return new(
- _audioPanel.Playing,
- _entityManager.GetNetEntity(_audioPanel.AudioEntity),
- _audioPanel.AudioParams.Volume,
- _audioPanel.Queue,
- _audioPanel.Global,
- _availablePlayers,
- _audioPanel.SelectedPlayers
- );
- }
-
- public override void HandleMessage(EuiMessageBase msg)
- {
- base.HandleMessage(msg);
-
- if (msg is not AdminAudioPanelEuiMessage.AdminAudioPanelEuiMessageBase)
- return;
-
- if (!_adminManager.HasAdminFlag(Player, AdminFlags.Fun))
- {
- Close();
- return;
- }
-
- switch (msg)
- {
- case AdminAudioPanelEuiMessage.Play:
- _audioPanel.Play();
- break;
- case AdminAudioPanelEuiMessage.Pause:
- _audioPanel.Pause();
- break;
- case AdminAudioPanelEuiMessage.Stop:
- _audioPanel.Stop();
- break;
- case AdminAudioPanelEuiMessage.AddTrack addTrack:
- var filename = addTrack.Filename.Trim();
- if (_resourceManager.ContentFileExists(new ResPath(filename).ToRootedPath()))
- _audioPanel.AddToQueue(filename);
- break;
- case AdminAudioPanelEuiMessage.SetVolume setVolume:
- _audioPanel.SetVolume(setVolume.Volume);
- break;
- case AdminAudioPanelEuiMessage.SetPlaybackPosition setPlayback:
- _audioPanel.SetPlaybackPosition(setPlayback.Position);
- break;
- case AdminAudioPanelEuiMessage.SelectPlayer selectPlayer:
- _audioPanel.SelectPlayer(selectPlayer.Player);
- break;
- case AdminAudioPanelEuiMessage.UnselectPlayer unselectPlayer:
- _audioPanel.UnselectPlayer(unselectPlayer.Player);
- break;
- case AdminAudioPanelEuiMessage.GlobalToggled globalToggled:
- _audioPanel.SetGlobal(globalToggled.Toggled);
- break;
- default:
- return;
- }
- StateDirty();
- }
-}
diff --git a/Content.Server/_CorvaxNext/Administration/UI/Audio/AdminAudioPanelSystem.cs b/Content.Server/_CorvaxNext/Administration/UI/Audio/AdminAudioPanelSystem.cs
deleted file mode 100644
index a0d517d635d..00000000000
--- a/Content.Server/_CorvaxNext/Administration/UI/Audio/AdminAudioPanelSystem.cs
+++ /dev/null
@@ -1,204 +0,0 @@
-using System.Linq;
-using Robust.Server.Player;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Components;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Player;
-using Robust.Shared.Timing;
-
-namespace Content.Server._CorvaxNext.Administration.UI.Audio;
-
-public sealed partial class AdminAudioPanelSystem : EntitySystem
-{
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
-
- private (EntityUid Entity, AudioComponent Audio)? _audioStream;
- private List _selectedPlayers = new();
-
- public Action? AudioUpdated;
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- if (!Playing)
- return;
-
- if (_audioStream is { } audioStream)
- {
- if (Exists(audioStream.Entity) &&
- (audioStream.Audio.State == AudioState.Playing || audioStream.Audio.State == AudioState.Paused))
- return;
- else
- {
- _audioStream = null;
- Queue.TryDequeue(out _);
- AudioUpdated?.Invoke();
- }
- }
-
- if (CurrentTrack != null)
- {
- _audioStream = _audio.PlayGlobal(CurrentTrack, Global ? Filter.Broadcast() : Filter.Empty().AddPlayers(_selectedPlayers), Global, AudioParams);
- AudioUpdated?.Invoke();
- }
- }
-
- private void SetStreamState(AudioState state)
- {
- if (_audioStream != null)
- _audio.SetState(_audioStream.Value.Entity, state, true, _audioStream.Value.Audio);
- AudioUpdated?.Invoke();
- }
-
- ///
- /// Stops sound and starts its over with current params with playback position of stopped sound.
- /// Used, for example, when need to update selected players.
- ///
- private void RecreateSound()
- {
- if (!Playing)
- return;
-
- if (_audioStream is not { } audioStream)
- return;
-
- Playing = false;
-
- var playback = (float)((audioStream.Audio.PauseTime ?? _timing.CurTime) - audioStream.Audio.AudioStart).TotalSeconds;
-
- _audio.Stop(audioStream.Entity, audioStream.Audio);
-
- _audioStream = _audio.PlayGlobal(CurrentTrack, Global ? Filter.Broadcast() : Filter.Empty().AddPlayers(_selectedPlayers), Global, AudioParams);
- _audio.SetPlaybackPosition(_audioStream, playback);
-
- Playing = true;
- AudioUpdated?.Invoke();
- }
-
- #region Public API
- public readonly Queue Queue = new();
- public string? CurrentTrack
- {
- get
- {
- if (Queue.TryPeek(out var track))
- return track;
- return null;
- }
- }
- public AudioParams AudioParams { get; private set; } = AudioParams.Default;
- public bool Playing { get; private set; } = false;
- public bool Global { get; private set; } = true;
- public HashSet SelectedPlayers => _selectedPlayers.Select(player => player.UserId.UserId).ToHashSet();
- public EntityUid AudioEntity => _audioStream?.Entity ?? EntityUid.Invalid;
-
- public void AddToQueue(string filename)
- {
- Queue.Enqueue(filename);
- AudioUpdated?.Invoke();
- }
-
- public bool Play()
- {
- if (_audioStream != null && _audioStream.Value.Audio.State == AudioState.Paused)
- {
- return Resume();
- }
-
- Playing = true;
- AudioUpdated?.Invoke();
- return Playing;
- }
-
- public void Pause()
- {
- if (_audioStream != null && _audioStream.Value.Audio.State == AudioState.Playing)
- SetStreamState(AudioState.Paused);
-
- Playing = false;
- AudioUpdated?.Invoke();
- }
-
- public bool Resume()
- {
- if (_audioStream != null && _audioStream.Value.Audio.State == AudioState.Paused)
- SetStreamState(AudioState.Playing);
-
- Playing = true;
- AudioUpdated?.Invoke();
- return Playing;
- }
-
- public bool Stop()
- {
- if (_audioStream != null && _audioStream.Value.Audio.State != AudioState.Stopped)
- {
- _audio.Stop(_audioStream.Value.Entity, _audioStream.Value.Audio);
- _audioStream = null;
- Queue.TryDequeue(out _);
- }
-
- Playing = false;
- AudioUpdated?.Invoke();
- return !Playing;
- }
-
- public void SetVolume(float volume)
- {
- AudioParams = AudioParams.WithVolume(volume);
- if (_audioStream != null)
- _audio.SetVolume(_audioStream.Value.Entity, volume, _audioStream.Value.Audio);
- AudioUpdated?.Invoke();
- }
-
- public void SelectPlayer(Guid player)
- {
- if (SelectedPlayers.Contains(player))
- return;
-
- var session = _playerManager.NetworkedSessions.FirstOrDefault(session => session.UserId.UserId == player);
-
- if (session == null)
- return;
-
- _selectedPlayers.Add(session);
- RecreateSound();
- AudioUpdated?.Invoke();
- }
-
- public void UnselectPlayer(Guid player)
- {
- if (!SelectedPlayers.Contains(player))
- return;
-
- var session = _playerManager.NetworkedSessions.FirstOrDefault(session => session.UserId.UserId == player);
-
- if (session == null)
- return;
-
- _selectedPlayers.Remove(session);
- RecreateSound();
- AudioUpdated?.Invoke();
- }
-
- public void SetPlaybackPosition(float position)
- {
- if (CurrentTrack != null && _audioStream is { } audioStream)
- {
- _audio.SetPlaybackPosition(audioStream, position);
- }
- AudioUpdated?.Invoke();
- }
-
- public void SetGlobal(bool global)
- {
- Global = global;
- RecreateSound();
- AudioUpdated?.Invoke();
- }
-
- #endregion
-}
diff --git a/Content.Server/_CorvaxNext/Administration/UI/Audio/Commands/OpenAudioPanelCommand.cs b/Content.Server/_CorvaxNext/Administration/UI/Audio/Commands/OpenAudioPanelCommand.cs
deleted file mode 100644
index 7b6b18e32d5..00000000000
--- a/Content.Server/_CorvaxNext/Administration/UI/Audio/Commands/OpenAudioPanelCommand.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using Content.Server.Administration;
-using Content.Server.EUI;
-using Content.Shared.Administration;
-using Robust.Shared.Console;
-
-namespace Content.Server._CorvaxNext.Administration.UI.Audio.Commands;
-
-[AdminCommand(AdminFlags.Fun)]
-public sealed class OpenAudioPanelCommand : IConsoleCommand
-{
- public string Command => "audiopanel";
- public string Description => "Opens the admin audio panel panel.";
- public string Help => $"Usage: {Command}";
-
- public void Execute(IConsoleShell shell, string argStr, string[] args)
- {
- if (shell.Player is not { } player)
- {
- shell.WriteError(Loc.GetString("shell-cannot-run-command-from-server"));
- return;
- }
-
- var eui = IoCManager.Resolve();
- var ui = new AdminAudioPanelEui();
- eui.OpenEui(ui, player);
- }
-}
diff --git a/Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchCartridgeComponent.cs b/Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchCartridgeComponent.cs
new file mode 100644
index 00000000000..c0ca5ad9dee
--- /dev/null
+++ b/Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchCartridgeComponent.cs
@@ -0,0 +1,23 @@
+using Content.Shared.Security;
+
+namespace Content.Server.CartridgeLoader.Cartridges;
+
+[RegisterComponent, Access(typeof(SecWatchCartridgeSystem))]
+public sealed partial class SecWatchCartridgeComponent : Component
+{
+ ///
+ /// Only show people with these statuses.
+ ///
+ [DataField]
+ public List Statuses = new()
+ {
+ SecurityStatus.Wanted,
+ SecurityStatus.Detained
+ };
+
+ ///
+ /// Station entity thats getting its records checked.
+ ///
+ [DataField]
+ public EntityUid? Station;
+}
diff --git a/Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchCartridgeSystem.cs b/Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchCartridgeSystem.cs
new file mode 100644
index 00000000000..2b46637da12
--- /dev/null
+++ b/Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchCartridgeSystem.cs
@@ -0,0 +1,73 @@
+using Content.Server.Station.Systems;
+using Content.Server.StationRecords;
+using Content.Server.StationRecords.Systems;
+using Content.Shared.CartridgeLoader;
+using Content.Shared.CartridgeLoader.Cartridges;
+using Content.Shared.CriminalRecords;
+using Content.Shared.StationRecords;
+
+namespace Content.Server.CartridgeLoader.Cartridges;
+
+public sealed class SecWatchCartridgeSystem : EntitySystem
+{
+ [Dependency] private readonly CartridgeLoaderSystem _cartridgeLoader = default!;
+ [Dependency] private readonly StationRecordsSystem _records = default!;
+ [Dependency] private readonly StationSystem _station = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnRecordModified);
+
+ SubscribeLocalEvent(OnUiReady);
+ }
+
+ private void OnRecordModified(RecordModifiedEvent args)
+ {
+ // when a record is modified update the ui of every loaded cartridge tuned to the same station
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var comp, out var cartridge))
+ {
+ if (cartridge.LoaderUid is not {} loader || comp.Station != args.Station)
+ continue;
+
+ UpdateUI((uid, comp), loader);
+ }
+ }
+
+ private void OnUiReady(Entity ent, ref CartridgeUiReadyEvent args)
+ {
+ UpdateUI(ent, args.Loader);
+ }
+
+ private void UpdateUI(Entity ent, EntityUid loader)
+ {
+ // if the loader is on a grid, update the station
+ // if it is off grid use the cached station
+ if (_station.GetOwningStation(loader) is {} station)
+ ent.Comp.Station = station;
+
+ if (!TryComp(ent.Comp.Station, out var records))
+ return;
+
+ station = ent.Comp.Station.Value;
+
+ var entries = new List();
+ foreach (var (id, criminal) in _records.GetRecordsOfType(station, records))
+ {
+ if (!ent.Comp.Statuses.Contains(criminal.Status))
+ continue;
+
+ var key = new StationRecordKey(id, station);
+ if (!_records.TryGetRecord(key, out var general, records))
+ continue;
+
+ var status = criminal.Status;
+ entries.Add(new SecWatchEntry(general.Name, general.JobTitle, criminal.Status, criminal.Reason));
+ }
+
+ var state = new SecWatchUiState(entries);
+ _cartridgeLoader.UpdateCartridgeUiState(loader, state);
+ }
+}
\ No newline at end of file
diff --git a/Content.Server/_CorvaxNext/Overlays/NightVisionSystem.cs b/Content.Server/_CorvaxNext/Overlays/NightVisionSystem.cs
new file mode 100644
index 00000000000..b38deb2d0b7
--- /dev/null
+++ b/Content.Server/_CorvaxNext/Overlays/NightVisionSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared._CorvaxNext.Overlays;
+
+namespace Content.Server._CorvaxNext.Overlays;
+
+public sealed class NightVisionSystem : SwitchableOverlaySystem;
diff --git a/Content.Server/_CorvaxNext/Overlays/ThermalVisionSystem.cs b/Content.Server/_CorvaxNext/Overlays/ThermalVisionSystem.cs
new file mode 100644
index 00000000000..6cffd8314d9
--- /dev/null
+++ b/Content.Server/_CorvaxNext/Overlays/ThermalVisionSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared._CorvaxNext.Overlays;
+
+namespace Content.Server._CorvaxNext.Overlays;
+
+public sealed class ThermalVisionSystem : SwitchableOverlaySystem;
diff --git a/Content.Shared/_CorvaxNext/Administration/UI/Audio/AdminAudioPanelEuiState.cs b/Content.Shared/_CorvaxNext/Administration/UI/Audio/AdminAudioPanelEuiState.cs
deleted file mode 100644
index 26e57e1874a..00000000000
--- a/Content.Shared/_CorvaxNext/Administration/UI/Audio/AdminAudioPanelEuiState.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using Content.Shared.Eui;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared._CorvaxNext.Administration.UI.Audio;
-
-[Serializable, NetSerializable]
-public sealed partial class AdminAudioPanelEuiState(bool playing, NetEntity audio, float volume, Queue queue, bool global, Dictionary players, HashSet selectedPlayers) : EuiStateBase
-{
- public bool Playing = playing;
- public NetEntity Audio = audio;
- public float Volume = volume;
- public Queue Queue = queue;
- public bool Global = global;
- public Dictionary Players = players;
- public HashSet SelectedPlayers = selectedPlayers;
-};
-
-public static class AdminAudioPanelEuiMessage
-{
- [Serializable]
- public abstract class AdminAudioPanelEuiMessageBase : EuiMessageBase
- {
- }
-
- [Serializable, NetSerializable]
- public sealed partial class Play : AdminAudioPanelEuiMessageBase
- {
- }
-
- [Serializable, NetSerializable]
- public sealed partial class Pause : AdminAudioPanelEuiMessageBase
- {
- }
-
- [Serializable, NetSerializable]
- public sealed partial class Stop : AdminAudioPanelEuiMessageBase
- {
- }
-
- [Serializable, NetSerializable]
- public sealed partial class AddTrack(string filename) : AdminAudioPanelEuiMessageBase
- {
- public string Filename = filename;
- }
-
- [Serializable, NetSerializable]
- public sealed partial class SetVolume(float volume) : AdminAudioPanelEuiMessageBase
- {
- public float Volume = volume;
- }
-
- [Serializable, NetSerializable]
- public sealed partial class SetPlaybackPosition(float position) : AdminAudioPanelEuiMessageBase
- {
- public float Position = position;
- }
-
- [Serializable, NetSerializable]
- public sealed partial class SelectPlayer(Guid player) : AdminAudioPanelEuiMessageBase
- {
- public Guid Player = player;
- }
-
- [Serializable, NetSerializable]
- public sealed partial class UnselectPlayer(Guid player) : AdminAudioPanelEuiMessageBase
- {
- public Guid Player = player;
- }
-
- [Serializable, NetSerializable]
- public sealed partial class GlobalToggled(bool toggled) : AdminAudioPanelEuiMessageBase
- {
- public bool Toggled = toggled;
- }
-}
diff --git a/Content.Shared/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchUiState.cs b/Content.Shared/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchUiState.cs
new file mode 100644
index 00000000000..29720630c43
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/CartridgeLoader/Cartridges/SecWatchUiState.cs
@@ -0,0 +1,24 @@
+using Content.Shared.Security;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.CartridgeLoader.Cartridges;
+
+///
+/// Show a list of wanted and suspected people from criminal records.
+///
+[Serializable, NetSerializable]
+public sealed class SecWatchUiState : BoundUserInterfaceState
+{
+ public readonly List Entries;
+
+ public SecWatchUiState(List entries)
+ {
+ Entries = entries;
+ }
+}
+
+///
+/// Entry for a person who is wanted or suspected.
+///
+[Serializable, NetSerializable]
+public record struct SecWatchEntry(string Name, string Job, SecurityStatus Status, string? Reason);
\ No newline at end of file
diff --git a/Content.Shared/_CorvaxNext/Clothing/Components/ClothingGrantComponentComponent.cs b/Content.Shared/_CorvaxNext/Clothing/Components/ClothingGrantComponentComponent.cs
new file mode 100644
index 00000000000..ff2e2a2cbca
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/Clothing/Components/ClothingGrantComponentComponent.cs
@@ -0,0 +1,14 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared._CorvaxNext.Clothing
+{
+ [RegisterComponent]
+ public sealed partial class ClothingGrantComponentComponent : Component
+ {
+ [DataField("component")]
+ [AlwaysPushInheritance]
+ public ComponentRegistry Components { get; private set; } = new();
+
+ public bool IsActive = false;
+ }
+}
diff --git a/Content.Shared/_CorvaxNext/Clothing/Components/ClothingGrantTagComponent.cs b/Content.Shared/_CorvaxNext/Clothing/Components/ClothingGrantTagComponent.cs
new file mode 100644
index 00000000000..27190765295
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/Clothing/Components/ClothingGrantTagComponent.cs
@@ -0,0 +1,10 @@
+namespace Content.Shared._CorvaxNext.Clothing;
+
+[RegisterComponent]
+public sealed partial class ClothingGrantTagComponent : Component
+{
+ [DataField("tag")]
+ public string Tag = "";
+
+ public bool IsActive = false;
+}
diff --git a/Content.Shared/_CorvaxNext/Clothing/Systems/ClothingGrantingSystem.cs b/Content.Shared/_CorvaxNext/Clothing/Systems/ClothingGrantingSystem.cs
new file mode 100644
index 00000000000..97044a34030
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/Clothing/Systems/ClothingGrantingSystem.cs
@@ -0,0 +1,92 @@
+using Content.Shared.Clothing.Components;
+using Content.Shared.Inventory.Events;
+using Robust.Shared.Serialization.Manager;
+using Content.Shared.Tag;
+
+namespace Content.Shared._CorvaxNext.Clothing;
+
+public sealed class ClothingGrantingSystem : EntitySystem
+{
+ [Dependency] private readonly IComponentFactory _componentFactory = default!;
+ [Dependency] private readonly ISerializationManager _serializationManager = default!;
+ [Dependency] private readonly TagSystem _tagSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnCompEquip);
+ SubscribeLocalEvent(OnCompUnequip);
+
+ SubscribeLocalEvent(OnTagEquip);
+ SubscribeLocalEvent(OnTagUnequip);
+ }
+
+ private void OnCompEquip(EntityUid uid, ClothingGrantComponentComponent component, GotEquippedEvent args)
+ {
+ if (!TryComp(uid, out var clothing)) return;
+
+ if (!clothing.Slots.HasFlag(args.SlotFlags)) return;
+
+ if (component.Components.Count > 1)
+ {
+ Logger.Error("Although a component registry supports multiple components, we cannot bookkeep more than 1 component for ClothingGrantComponent at this time.");
+ return;
+ }
+
+ foreach (var (name, data) in component.Components)
+ {
+ var newComp = (Component) _componentFactory.GetComponent(name);
+
+ if (HasComp(args.Equipee, newComp.GetType()))
+ continue;
+
+ newComp.Owner = args.Equipee;
+
+ var temp = (object) newComp;
+ _serializationManager.CopyTo(data.Component, ref temp);
+ EntityManager.AddComponent(args.Equipee, (Component)temp!);
+
+ component.IsActive = true;
+ }
+ }
+
+ private void OnCompUnequip(EntityUid uid, ClothingGrantComponentComponent component, GotUnequippedEvent args)
+ {
+ if (!component.IsActive) return;
+
+ foreach (var (name, data) in component.Components)
+ {
+ var newComp = (Component) _componentFactory.GetComponent(name);
+
+ RemComp(args.Equipee, newComp.GetType());
+ }
+
+ component.IsActive = false;
+ }
+
+
+ private void OnTagEquip(EntityUid uid, ClothingGrantTagComponent component, GotEquippedEvent args)
+ {
+ if (!TryComp(uid, out var clothing))
+ return;
+
+ if (!clothing.Slots.HasFlag(args.SlotFlags))
+ return;
+
+ EnsureComp(args.Equipee);
+ _tagSystem.AddTag(args.Equipee, component.Tag);
+
+ component.IsActive = true;
+ }
+
+ private void OnTagUnequip(EntityUid uid, ClothingGrantTagComponent component, GotUnequippedEvent args)
+ {
+ if (!component.IsActive)
+ return;
+
+ _tagSystem.RemoveTag(args.Equipee, component.Tag);
+
+ component.IsActive = false;
+ }
+}
diff --git a/Content.Shared/_CorvaxNext/Overlays/BaseOverlayComponent.cs b/Content.Shared/_CorvaxNext/Overlays/BaseOverlayComponent.cs
new file mode 100644
index 00000000000..4d5ad8b4727
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/Overlays/BaseOverlayComponent.cs
@@ -0,0 +1,16 @@
+namespace Content.Shared._CorvaxNext.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;
+}
diff --git a/Content.Shared/_CorvaxNext/Overlays/NightVisionComponent.cs b/Content.Shared/_CorvaxNext/Overlays/NightVisionComponent.cs
new file mode 100644
index 00000000000..084f7448568
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/Overlays/NightVisionComponent.cs
@@ -0,0 +1,15 @@
+using Content.Shared.Actions;
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared._CorvaxNext.Overlays;
+
+[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;
diff --git a/Content.Shared/_CorvaxNext/Overlays/SwitchableOverlayComponent.cs b/Content.Shared/_CorvaxNext/Overlays/SwitchableOverlayComponent.cs
new file mode 100644
index 00000000000..89a99a283f0
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/Overlays/SwitchableOverlayComponent.cs
@@ -0,0 +1,24 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared._CorvaxNext.Overlays;
+
+public abstract partial class SwitchableOverlayComponent : BaseOverlayComponent
+{
+ [DataField, AutoNetworkedField]
+ public bool IsActive = true;
+
+ [DataField]
+ public virtual SoundSpecifier? ActivateSound { get; set; } =
+ new SoundPathSpecifier("/Audio/_CorvaxNext/Items/Goggles/activate.ogg");
+
+ [DataField]
+ public virtual SoundSpecifier? DeactivateSound { get; set; } =
+ new SoundPathSpecifier("/Audio/_CorvaxNext/Items/Goggles/deactivate.ogg");
+
+ [DataField]
+ public virtual string? ToggleAction { get; set; }
+
+ [ViewVariables]
+ public EntityUid? ToggleActionEntity;
+}
diff --git a/Content.Shared/_CorvaxNext/Overlays/SwitchableOverlaySystem.cs b/Content.Shared/_CorvaxNext/Overlays/SwitchableOverlaySystem.cs
new file mode 100644
index 00000000000..062fa177264
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/Overlays/SwitchableOverlaySystem.cs
@@ -0,0 +1,52 @@
+using Content.Shared.Actions;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Network;
+using Robust.Shared.Player;
+using Robust.Shared.Timing;
+
+namespace Content.Shared._CorvaxNext.Overlays;
+
+public abstract class SwitchableOverlaySystem : 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(OnToggle);
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(OnRemove);
+ }
+
+ private void OnRemove(EntityUid uid, TComp component, ComponentRemove args)
+ {
+ _actions.RemoveAction(uid, component.ToggleActionEntity);
+ UpdateVision(uid, false);
+ }
+
+ private void OnInit(EntityUid uid, TComp component, ComponentInit args)
+ {
+ _actions.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
+ UpdateVision(uid, component.IsActive);
+ }
+
+ protected virtual void UpdateVision(EntityUid uid, bool active) { }
+
+ private void OnToggle(EntityUid uid, TComp component, TEvent args)
+ {
+ if (!_timing.IsFirstTimePredicted)
+ return;
+
+ component.IsActive = !component.IsActive;
+
+ if (_net.IsClient)
+ _audio.PlayEntity(component.IsActive ? component.ActivateSound : component.DeactivateSound, Filter.Local(), uid, false);
+
+ args.Handled = true;
+ UpdateVision(uid, component.IsActive);
+ }
+}
diff --git a/Content.Shared/_CorvaxNext/Overlays/ThermalInvisibilityComponent.cs b/Content.Shared/_CorvaxNext/Overlays/ThermalInvisibilityComponent.cs
new file mode 100644
index 00000000000..924fa8ef63f
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/Overlays/ThermalInvisibilityComponent.cs
@@ -0,0 +1,7 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared._CorvaxNext.Overlays;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ThermalInvisibilityComponent : SwitchableOverlayComponent;
+
diff --git a/Content.Shared/_CorvaxNext/Overlays/ThermalVisionComponent.cs b/Content.Shared/_CorvaxNext/Overlays/ThermalVisionComponent.cs
new file mode 100644
index 00000000000..65eb94d9221
--- /dev/null
+++ b/Content.Shared/_CorvaxNext/Overlays/ThermalVisionComponent.cs
@@ -0,0 +1,14 @@
+using Content.Shared.Actions;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared._CorvaxNext.Overlays;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ThermalVisionComponent : SwitchableOverlayComponent
+{
+ public override string? ToggleAction { get; set; } = "ToggleThermalVision";
+
+ public override Color Color { get; set; } = Color.FromHex("#C26E4A");
+}
+
+public sealed partial class ToggleThermalVisionEvent : InstantActionEvent;
diff --git a/Resources/Audio/_CorvaxNext/Items/Goggles/activate.ogg b/Resources/Audio/_CorvaxNext/Items/Goggles/activate.ogg
new file mode 100644
index 00000000000..96cdb288fe0
Binary files /dev/null and b/Resources/Audio/_CorvaxNext/Items/Goggles/activate.ogg differ
diff --git a/Resources/Audio/_CorvaxNext/Items/Goggles/attributions.yml b/Resources/Audio/_CorvaxNext/Items/Goggles/attributions.yml
new file mode 100644
index 00000000000..7b1121f5423
--- /dev/null
+++ b/Resources/Audio/_CorvaxNext/Items/Goggles/attributions.yml
@@ -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"
\ No newline at end of file
diff --git a/Resources/Audio/_CorvaxNext/Items/Goggles/deactivate.ogg b/Resources/Audio/_CorvaxNext/Items/Goggles/deactivate.ogg
new file mode 100644
index 00000000000..e1e8f4fd82f
Binary files /dev/null and b/Resources/Audio/_CorvaxNext/Items/Goggles/deactivate.ogg differ
diff --git a/Resources/Locale/en-US/_corvaxnext/cartridge-loader/secwatch.ftl b/Resources/Locale/en-US/_corvaxnext/cartridge-loader/secwatch.ftl
new file mode 100644
index 00000000000..ca18db233aa
--- /dev/null
+++ b/Resources/Locale/en-US/_corvaxnext/cartridge-loader/secwatch.ftl
@@ -0,0 +1,5 @@
+sec-watch-program-name = SecWatch
+sec-watch-title = SecWatch 1.0
+sec-watch-no-entries = Everything's calm. Why not enjoy a Monkin Donut?
+sec-watch-entry = {$name}
+sec-watch-no-reason = None given???
\ No newline at end of file
diff --git a/Resources/Locale/en-US/_corvaxnext/store/uplink-catalog.ftl b/Resources/Locale/en-US/_corvaxnext/store/uplink-catalog.ftl
new file mode 100644
index 00000000000..3b1db5115ee
--- /dev/null
+++ b/Resources/Locale/en-US/_corvaxnext/store/uplink-catalog.ftl
@@ -0,0 +1,5 @@
+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!
\ No newline at end of file
diff --git a/Resources/Locale/ru-RU/_CorvaxNext/audio/widgets/admin-audio-panel.ftl b/Resources/Locale/ru-RU/_CorvaxNext/audio/widgets/admin-audio-panel.ftl
deleted file mode 100644
index 5733003e8f3..00000000000
--- a/Resources/Locale/ru-RU/_CorvaxNext/audio/widgets/admin-audio-panel.ftl
+++ /dev/null
@@ -1,10 +0,0 @@
-admin-audio-panel-title = Панель управления аудио
-admin-audio-panel-current-track-label = Текущий трек
-admin-audio-panel-button-play-text = Играть
-admin-audio-panel-button-pause-text = Пауза
-admin-audio-panel-button-stop-text = Стоп
-admin-audio-panel-volume-line-placeholder = Громкость
-admin-audio-panel-toggle-global-checkbox-label = Слышат все
-admin-audio-panel-trackpath-line-placeholder = Путь к файлу OGG
-admin-audio-panel-add-track-button-text = Добавить трек
-admin-audio-panel-track-name-nothing-playing = Ничего не играет
diff --git a/Resources/Locale/ru-RU/_CorvaxNext/entities/clothing/eyes/goggles.ftl b/Resources/Locale/ru-RU/_CorvaxNext/entities/clothing/eyes/goggles.ftl
new file mode 100644
index 00000000000..6287842baf6
--- /dev/null
+++ b/Resources/Locale/ru-RU/_CorvaxNext/entities/clothing/eyes/goggles.ftl
@@ -0,0 +1,15 @@
+# Night Vision Goggles
+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-ClothingEyesThermalVisionGogglesSyndie = { ent-ClothingEyesThermalVisionGoggles }
+ .desc = { ent-ClothingEyesThermalVisionGoggles.desc }
+ent-ClothingEyesThermalVisionGogglesNukie = { ent-ClothingEyesThermalVisionGoggles }
+ .desc = { ent-ClothingEyesThermalVisionGoggles.desc }
\ No newline at end of file
diff --git a/Resources/Locale/ru-RU/_CorvaxNext/prototypes/actions/types.ftl b/Resources/Locale/ru-RU/_CorvaxNext/prototypes/actions/types.ftl
new file mode 100644
index 00000000000..ca88b9a6afa
--- /dev/null
+++ b/Resources/Locale/ru-RU/_CorvaxNext/prototypes/actions/types.ftl
@@ -0,0 +1,5 @@
+ent-ToggleNightVision = Переключить ночное зрение
+ .desc = Переключает ночное зрение.
+
+ent-ToggleThermalVision = Переключить термальное зрение
+ .desc = Переключает термальное зрение.
\ No newline at end of file
diff --git a/Resources/Locale/ru-RU/_CorvaxNext/store/uplink-catalog.ftl b/Resources/Locale/ru-RU/_CorvaxNext/store/uplink-catalog.ftl
new file mode 100644
index 00000000000..c91985a04b6
--- /dev/null
+++ b/Resources/Locale/ru-RU/_CorvaxNext/store/uplink-catalog.ftl
@@ -0,0 +1,5 @@
+uplink-night-vision-name = Прибор ночного видения
+uplink-night-vision-desc = Позволяет вам видеть в темноте, при этом выглядя как обычные солнцезащитные очки!
+
+uplink-thermal-vision-name = Прибор термального видения
+uplink-thermal-vision-desc = Позволяет вам видеть живых существ независимо от преград, при этом выглядя как обычные солнцезащитные очки!
\ No newline at end of file
diff --git a/Resources/Locale/ru-RU/_corvaxnext/cartridge-loader/secwatch.ftl b/Resources/Locale/ru-RU/_corvaxnext/cartridge-loader/secwatch.ftl
new file mode 100644
index 00000000000..bd9a5070cb0
--- /dev/null
+++ b/Resources/Locale/ru-RU/_corvaxnext/cartridge-loader/secwatch.ftl
@@ -0,0 +1,5 @@
+sec-watch-program-name = ОКО СБ
+sec-watch-title = ОКО СБ 1.0
+sec-watch-no-entries = Полный порядок. Безопасность превыше всего.
+sec-watch-entry = {$name}
+sec-watch-no-reason = Причина не уточнена.
\ No newline at end of file
diff --git a/Resources/Locale/ru-RU/administration/ui/tabs/admin-tab/player-actions-window.ftl b/Resources/Locale/ru-RU/administration/ui/tabs/admin-tab/player-actions-window.ftl
index f3a59f5d58d..5eb1438b100 100644
--- a/Resources/Locale/ru-RU/administration/ui/tabs/admin-tab/player-actions-window.ftl
+++ b/Resources/Locale/ru-RU/administration/ui/tabs/admin-tab/player-actions-window.ftl
@@ -8,4 +8,3 @@ admin-player-actions-window-shuttle = Вызвать/отозвать шаттл
admin-player-actions-window-admin-logs = Админ логи
admin-player-actions-window-admin-notes = Админ заметки
admin-player-actions-window-admin-fax = Админ факс
-admin-player-actions-window-audio-panel = Аудио панель
diff --git a/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/objects/devices/cartriges.ftl b/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/objects/devices/cartriges.ftl
new file mode 100644
index 00000000000..427dadde5e1
--- /dev/null
+++ b/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/objects/devices/cartriges.ftl
@@ -0,0 +1,2 @@
+ent-SecWatchCartridge = картридж "око сб"
+ .desc = Картридж, отслеживающий статус разыскиваемых службой безопасности лиц.
\ No newline at end of file
diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml
index e5f2c638318..390bec95b68 100644
--- a/Resources/Prototypes/Alerts/alerts.yml
+++ b/Resources/Prototypes/Alerts/alerts.yml
@@ -97,7 +97,7 @@
id: Offer
clickEvent: !type:AcceptOfferAlertEvent
icons:
- - sprite: /Textures/_CorvaxNext/Alerts/offer_item.rsi
+ - sprite: /Textures/_CorvaxNext/Interface/Alerts/offer_item.rsi
state: offer_item
name: alerts-offer-name
description: alerts-offer-desc
diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
index be32887c3f0..e8b942241d3 100644
--- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
+++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
@@ -6,6 +6,7 @@
save: false
abstract: true
components:
+ - type: ThermalInvisibility #Corvax-Next-ThermalVision
- type: Reactive
groups:
Acidic: [Touch]
diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
index c102b4714e3..de0959ebcb1 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
@@ -4,6 +4,7 @@
name: admin observer
categories: [ HideSpawnMenu ]
components:
+ - type: ThermalInvisibility #Corvax-Next-ThermalVision
- type: ContentEye
maxZoom: 8.916104, 8.916104
- type: Tag
diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
index 4febc87b90f..8b13d2598bb 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
@@ -76,6 +76,7 @@
- CrewManifestCartridge
- NotekeeperCartridge
- NewsReaderCartridge
+ - SecWatchCartridge # Corvax-Next-SecWatch
cartridgeSlot:
priority: -1
name: device-pda-slot-component-slot-name-cartridge
@@ -138,6 +139,7 @@
- NotekeeperCartridge
- NewsReaderCartridge
- MedTekCartridge
+ - SecWatchCartridge # Corvax-Next-SecWatch
- type: entity
parent: BasePDA
@@ -408,6 +410,7 @@
- NotekeeperCartridge
- NewsReaderCartridge
- AstroNavCartridge
+ - SecWatchCartridge # Corvax-Next-SecWatch
- type: entity
parent: BasePDA
@@ -773,6 +776,7 @@
- WantedListCartridge
- MedTekCartridge
- AstroNavCartridge
+ - SecWatchCartridge # Corvax-Next-SecWatch
- type: entity
parent: CentcomPDA
diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
index 55d881d1a95..97e8618ba2a 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
@@ -354,9 +354,11 @@
- FauxTileAstroSnow
- OreBagOfHolding
- DeviceQuantumSpinInverter
- - EnergyScalpel # _CorvaxNext: surgery Change
- - EnergyCautery # _CorvaxNext: surgery Change
- - AdvancedRetractor # _CorvaxNext: surgery Change
+ # Corvax-Next Start
+ - EnergyScalpel
+ - EnergyCautery
+ - AdvancedRetractor
+ # Corvax-Next End
- type: EmagLatheRecipes
emagDynamicRecipes:
- BoxBeanbag
diff --git a/Resources/Prototypes/_CorvaxNext/Actions/types.yml b/Resources/Prototypes/_CorvaxNext/Actions/types.yml
new file mode 100644
index 00000000000..f8c817185c8
--- /dev/null
+++ b/Resources/Prototypes/_CorvaxNext/Actions/types.yml
@@ -0,0 +1,25 @@
+- type: entity
+ id: ToggleNightVision
+ name: Switch night vision
+ description: Switches night vision
+ components:
+ - type: InstantAction
+ itemIconStyle: BigAction
+ priority: -20
+ icon:
+ sprite: _CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi
+ state: icon
+ event: !type:ToggleNightVisionEvent
+
+- type: entity
+ id: ToggleThermalVision
+ name: Switch Thermal vision
+ description: Switches Thermal vision
+ components:
+ - type: InstantAction
+ itemIconStyle: BigAction
+ priority: -20
+ icon:
+ sprite: _CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi
+ state: icon
+ event: !type:ToggleThermalVisionEvent
\ No newline at end of file
diff --git a/Resources/Prototypes/_CorvaxNext/Catalog/uplink_catalog.yml b/Resources/Prototypes/_CorvaxNext/Catalog/uplink_catalog.yml
new file mode 100644
index 00000000000..49ca58473e5
--- /dev/null
+++ b/Resources/Prototypes/_CorvaxNext/Catalog/uplink_catalog.yml
@@ -0,0 +1,63 @@
+# Night Vision
+
+- type: listing
+ id: UplinkNightGoggles
+ name: uplink-night-vision-name
+ description: uplink-night-vision-desc
+ productEntity: ClothingEyesNightVisionGogglesSyndie
+ cost:
+ Telecrystal: 6
+ categories:
+ - UplinkWearables
+ conditions:
+ - !type:StoreWhitelistCondition
+ blacklist:
+ tags:
+ - NukeOpsUplink
+
+- type: listing
+ id: UplinkNightGogglesNukie
+ name: uplink-night-vision-name
+ description: uplink-night-vision-desc
+ productEntity: ClothingEyesNightVisionGogglesNukie
+ cost:
+ Telecrystal: 10
+ categories:
+ - UplinkWearables
+ conditions:
+ - !type:StoreWhitelistCondition
+ whitelist:
+ tags:
+ - NukeOpsUplink
+
+# Thermal Vision
+
+- type: listing
+ id: UplinkThermalGoggles
+ name: uplink-thermal-vision-name
+ description: uplink-thermal-vision-desc
+ productEntity: ClothingEyesThermalVisionGogglesSyndie
+ cost:
+ Telecrystal: 8
+ categories:
+ - UplinkWearables
+ conditions:
+ - !type:StoreWhitelistCondition
+ blacklist:
+ tags:
+ - NukeOpsUplink
+
+- type: listing
+ id: UplinkThermalGogglesNukie
+ name: uplink-thermal-vision-name
+ description: uplink-thermal-vision-desc
+ productEntity: ClothingEyesThermalVisionGogglesNukie
+ cost:
+ Telecrystal: 12
+ categories:
+ - UplinkWearables
+ conditions:
+ - !type:StoreWhitelistCondition
+ whitelist:
+ tags:
+ - NukeOpsUplink
diff --git a/Resources/Prototypes/_CorvaxNext/Entities/Clothing/Eyes/goggles.yml b/Resources/Prototypes/_CorvaxNext/Entities/Clothing/Eyes/goggles.yml
new file mode 100644
index 00000000000..318139adff5
--- /dev/null
+++ b/Resources/Prototypes/_CorvaxNext/Entities/Clothing/Eyes/goggles.yml
@@ -0,0 +1,71 @@
+# Night Vision Goggles
+
+- type: entity
+ parent: ClothingEyesBase
+ id: ClothingEyesNightVisionGoggles
+ name: night vision goggles
+ description: Now you can see in the dark!
+ components:
+ - type: Sprite
+ sprite: _CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi
+ - type: Clothing
+ sprite: _CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi
+ - type: ClothingGrantComponent
+ component:
+ - type: NightVision
+
+- type: entity
+ parent: ClothingEyesNightVisionGoggles
+ id: ClothingEyesNightVisionGogglesSyndie
+ suffix: "Chameleon"
+ components:
+ - type: ChameleonClothing
+ slot: [eyes]
+ default: ClothingEyesGlassesSunglasses
+ - 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: Now you can see everyone!
+ components:
+ - type: Sprite
+ sprite: _CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi
+ - type: Clothing
+ sprite: _CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi
+ - type: ClothingGrantComponent
+ component:
+ - type: ThermalVision
+
+- type: entity
+ parent: ClothingEyesThermalVisionGoggles
+ id: ClothingEyesThermalVisionGogglesSyndie
+ suffix: "Chameleon"
+ components:
+ - type: ChameleonClothing
+ slot: [eyes]
+ default: ClothingEyesGlassesSunglasses
+ - type: UserInterface
+ interfaces:
+ enum.ChameleonUiKey.Key:
+ type: ChameleonBoundUserInterface
+
+- type: entity
+ parent: [ClothingEyesThermalVisionGogglesSyndie, ShowSecurityIcons]
+ id: ClothingEyesThermalVisionGogglesNukie
+ suffix: "Chameleon, NukeOps"
+ components:
+ - type: ShowSyndicateIcons
\ No newline at end of file
diff --git a/Resources/Prototypes/_CorvaxNext/Entities/Objects/Devices/cartridges.yml b/Resources/Prototypes/_CorvaxNext/Entities/Objects/Devices/cartridges.yml
new file mode 100644
index 00000000000..b271eb6378f
--- /dev/null
+++ b/Resources/Prototypes/_CorvaxNext/Entities/Objects/Devices/cartridges.yml
@@ -0,0 +1,20 @@
+- type: entity
+ parent: BaseItem
+ id: SecWatchCartridge
+ name: sec watch cartridge
+ description: A cartridge that tracks the status of currently wanted individuals.
+ components:
+ - type: Sprite
+ sprite: _CorvaxNext/Objects/Devices/cartridge.rsi
+ state: cart-cri
+ - type: Icon
+ sprite: _CorvaxNext/Objects/Devices/cartridge.rsi
+ state: cart-cri
+ - type: UIFragment
+ ui: !type:SecWatchUi
+ - type: Cartridge
+ programName: sec-watch-program-name
+ icon:
+ sprite: Objects/Weapons/Melee/stunbaton.rsi
+ state: stunbaton_on
+ - type: SecWatchCartridge
\ No newline at end of file
diff --git a/Resources/Prototypes/_CorvaxNext/Research/experimental.yml b/Resources/Prototypes/_CorvaxNext/Research/experimental.yml
index 0d69e6428ee..e949c566f56 100644
--- a/Resources/Prototypes/_CorvaxNext/Research/experimental.yml
+++ b/Resources/Prototypes/_CorvaxNext/Research/experimental.yml
@@ -1,3 +1,5 @@
+# Tier 2
+
- type: technology
id: BluespaceMining
name: research-technology-bluespace-mining
diff --git a/Resources/Prototypes/_CorvaxNext/Shaders/shaders.yml b/Resources/Prototypes/_CorvaxNext/Shaders/shaders.yml
new file mode 100644
index 00000000000..09733c85e20
--- /dev/null
+++ b/Resources/Prototypes/_CorvaxNext/Shaders/shaders.yml
@@ -0,0 +1,4 @@
+- type: shader
+ id: NightVision
+ kind: source
+ path: "/Textures/_CorvaxNext/Shaders/nightvision.swsl"
\ No newline at end of file
diff --git a/Resources/Textures/_CorvaxNext/Alerts/offer_item.rsi/meta.json b/Resources/Textures/_CorvaxNext/Alerts/offer_item.rsi/meta.json
deleted file mode 100644
index 0513aeb3e06..00000000000
--- a/Resources/Textures/_CorvaxNext/Alerts/offer_item.rsi/meta.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "version": 1,
- "license": "CC-BY-SA-3.0",
- "copyright": "Original icon taken from https://github.com/ss220-space/Paradise/blob/master220/icons/mob/screen_alert.dmi, modified by Mocho, resprited by AwareFoxy",
- "size": {
- "x": 32,
- "y": 32
- },
- "states": [
- {
- "name": "offer_item"
- }
- ]
-}
diff --git a/Resources/Textures/_CorvaxNext/Alerts/offer_item.rsi/offer_item.png b/Resources/Textures/_CorvaxNext/Alerts/offer_item.rsi/offer_item.png
deleted file mode 100644
index 6215d2e53ff..00000000000
Binary files a/Resources/Textures/_CorvaxNext/Alerts/offer_item.rsi/offer_item.png and /dev/null differ
diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES-off.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES-off.png
new file mode 100644
index 00000000000..b63f30fc713
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES-off.png differ
diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES.png
new file mode 100644
index 00000000000..199c44122bb
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES.png differ
diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/icon.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/icon.png
new file mode 100644
index 00000000000..bf770f70f89
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/icon.png differ
diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/inhand-left.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/inhand-left.png
new file mode 100644
index 00000000000..995b37471b3
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/inhand-left.png differ
diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/inhand-right.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/inhand-right.png
new file mode 100644
index 00000000000..c3efa67f837
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/inhand-right.png differ
diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/meta.json b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/meta.json
new file mode 100644
index 00000000000..987b20b9af7
--- /dev/null
+++ b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/meta.json
@@ -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
+ }
+ ]
+}
diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/equipped-EYES.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/equipped-EYES.png
new file mode 100644
index 00000000000..9bef0a8c05f
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/equipped-EYES.png differ
diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/icon.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/icon.png
new file mode 100644
index 00000000000..3d5f8ef9b65
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/icon.png differ
diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/inhand-left.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/inhand-left.png
new file mode 100644
index 00000000000..bf67e35d402
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/inhand-left.png differ
diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/inhand-right.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/inhand-right.png
new file mode 100644
index 00000000000..4ede078291d
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/inhand-right.png differ
diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/meta.json b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/meta.json
new file mode 100644
index 00000000000..205508acfa7
--- /dev/null
+++ b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/meta.json
@@ -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
+ }
+ ]
+}
diff --git a/Resources/Textures/_CorvaxNext/Interface/Alerts/offer_item.rsi/meta.json b/Resources/Textures/_CorvaxNext/Interface/Alerts/offer_item.rsi/meta.json
new file mode 100644
index 00000000000..afa08950c30
--- /dev/null
+++ b/Resources/Textures/_CorvaxNext/Interface/Alerts/offer_item.rsi/meta.json
@@ -0,0 +1,20 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Made by Ko4erga",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "offer_item",
+ "delays": [
+ [
+ 0.5,
+ 0.5
+ ]
+ ]
+ }
+ ]
+}
diff --git a/Resources/Textures/_CorvaxNext/Interface/Alerts/offer_item.rsi/offer_item.png b/Resources/Textures/_CorvaxNext/Interface/Alerts/offer_item.rsi/offer_item.png
new file mode 100644
index 00000000000..11b8f069206
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Interface/Alerts/offer_item.rsi/offer_item.png differ
diff --git a/Resources/Textures/_CorvaxNext/Objects/Devices/cartridge.rsi/cart-cri.png b/Resources/Textures/_CorvaxNext/Objects/Devices/cartridge.rsi/cart-cri.png
new file mode 100644
index 00000000000..4da4343e584
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Objects/Devices/cartridge.rsi/cart-cri.png differ
diff --git a/Resources/Textures/_CorvaxNext/Objects/Devices/cartridge.rsi/meta.json b/Resources/Textures/_CorvaxNext/Objects/Devices/cartridge.rsi/meta.json
new file mode 100644
index 00000000000..0eb27fde855
--- /dev/null
+++ b/Resources/Textures/_CorvaxNext/Objects/Devices/cartridge.rsi/meta.json
@@ -0,0 +1,14 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Timfa, plus edits by portfiend",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "cart-cri"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/Textures/_CorvaxNext/Shaders/nightvision.swsl b/Resources/Textures/_CorvaxNext/Shaders/nightvision.swsl
new file mode 100644
index 00000000000..655d725a0d5
--- /dev/null
+++ b/Resources/Textures/_CorvaxNext/Shaders/nightvision.swsl
@@ -0,0 +1,40 @@
+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.rgb = clamp(color.rgb, 0.0, 1.0);
+
+ COLOR = color;
+}
\ No newline at end of file