diff --git a/Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml
index e09a422ddf..7dcd993f80 100644
--- a/Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml
+++ b/Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml
@@ -3,6 +3,7 @@
+
diff --git a/Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs
index 3325c0d379..175f009ce5 100644
--- a/Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs
+++ b/Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs
@@ -26,12 +26,14 @@ public GlimmerMonitorUiFragment()
VerticalExpand = true;
var intervalGroup = new ButtonGroup();
+ IntervalButton6s.Group = intervalGroup;
IntervalButton1.Group = intervalGroup;
IntervalButton5.Group = intervalGroup;
IntervalButton10.Group = intervalGroup;
- IntervalButton1.Pressed = true;
+ IntervalButton6s.Pressed = true;
+ IntervalButton6s.OnPressed += _ => UpdateState(_cachedValues);
IntervalButton1.OnPressed += _ => UpdateState(_cachedValues);
IntervalButton5.OnPressed += _ => UpdateState(_cachedValues);
IntervalButton10.OnPressed += _ => UpdateState(_cachedValues);
@@ -62,14 +64,12 @@ private List FormatGlimmerValues(List glimmerValues)
{
var returnList = glimmerValues;
- if (IntervalButton5.Pressed)
- {
- returnList = GetAveragedList(glimmerValues, 5);
- }
- else if (IntervalButton10.Pressed)
- {
+ if (IntervalButton1.Pressed)
returnList = GetAveragedList(glimmerValues, 10);
- }
+ else if (IntervalButton5.Pressed)
+ returnList = GetAveragedList(glimmerValues, 50);
+ else if (IntervalButton10.Pressed)
+ returnList = GetAveragedList(glimmerValues, 100);
return ClipToFifteen(returnList);
}
diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs
index 68c937a788..b0902f7e22 100644
--- a/Content.Client/Chat/UI/SpeechBubble.cs
+++ b/Content.Client/Chat/UI/SpeechBubble.cs
@@ -2,6 +2,8 @@
using Content.Client.Chat.Managers;
using Content.Shared.CCVar;
using Content.Shared.Chat;
+using Content.Shared.Speech;
+using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -17,6 +19,8 @@ public abstract class SpeechBubble : Control
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] protected readonly IConfigurationManager ConfigManager = default!;
+ private readonly SharedTransformSystem _transformSystem;
+
public enum SpeechType : byte
{
Emote,
@@ -83,6 +87,7 @@ public SpeechBubble(ChatMessage message, EntityUid senderEntity, string speechSt
{
IoCManager.InjectDependencies(this);
_senderEntity = senderEntity;
+ _transformSystem = _entityManager.System();
// Use text clipping so new messages don't overlap old ones being pushed up.
RectClipContent = true;
@@ -139,8 +144,13 @@ protected override void FrameUpdate(FrameEventArgs args)
Modulate = Color.White;
}
- var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -EntityVerticalOffset;
- var worldPos = xform.WorldPosition + offset;
+ var baseOffset = 0f;
+
+ if (_entityManager.TryGetComponent(_senderEntity, out var speech))
+ baseOffset = speech.SpeechBubbleOffset;
+
+ var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -(EntityVerticalOffset + baseOffset);
+ var worldPos = _transformSystem.GetWorldPosition(xform) + offset;
var lowerCenter = _eyeManager.WorldToScreen(worldPos) / UIScale;
var screenPos = lowerCenter - new Vector2(ContentSize.X / 2, ContentSize.Y + _verticalOffsetAchieved);
diff --git a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml
index b1f4f5917f..61bbba3c35 100644
--- a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml
+++ b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml
@@ -2,7 +2,7 @@
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
- MinSize="620 670"
+ MinSize="620 770"
Title="{Loc 'chem-master-bound-user-interface-title'}">
@@ -13,12 +13,12 @@
-
+
-
+
@@ -38,12 +38,12 @@
-
+
-
+
@@ -60,12 +60,12 @@
-
+
-
+
@@ -129,4 +129,4 @@
-
+
\ No newline at end of file
diff --git a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs
index d312a69e7a..ec6d41bd32 100644
--- a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs
+++ b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs
@@ -103,7 +103,7 @@ private ReagentButton MakeReagentButton(string text, ChemMasterReagentAmount amo
private List CreateReagentTransferButtons(ReagentId reagent, bool isBuffer, bool addReagentButtons)
{
if (!addReagentButtons)
- return new List(); // Return an empty list if reagentTransferButton creation is disabled.
+ return new(); // Return an empty list if reagentTransferButton creation is disabled.
var buttons = new List();
var names = Enum.GetNames();
@@ -111,7 +111,8 @@ private List CreateReagentTransferButtons(ReagentId reagent, bool
for (int i = 0; i < names.Length; i++)
{
- var name = names[i];
+ var isNumber = int.TryParse(names[i].Substring(1), out int number);
+ var name = isNumber ? number.ToString() : names[i];
var reagentAmount = values[i];
var reagentTransferButton = MakeReagentButton(
diff --git a/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs b/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs
index e5be0b1811..44501767dd 100644
--- a/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs
+++ b/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs
@@ -31,7 +31,7 @@ protected override void Open()
private void PopulateCrayons()
{
var crayonDecals = _protoManager.EnumeratePrototypes().Where(x => x.Tags.Contains("crayon"));
- _menu?.Populate(crayonDecals);
+ _menu?.Populate(crayonDecals.ToList());
}
public override void OnProtoReload(PrototypesReloadedEventArgs args)
@@ -44,6 +44,16 @@ public override void OnProtoReload(PrototypesReloadedEventArgs args)
PopulateCrayons();
}
+ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
+ {
+ base.ReceiveMessage(message);
+
+ if (_menu is null || message is not CrayonUsedMessage crayonMessage)
+ return;
+
+ _menu.AdvanceState(crayonMessage.DrawnDecal);
+ }
+
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
diff --git a/Content.Client/Crayon/UI/CrayonWindow.xaml b/Content.Client/Crayon/UI/CrayonWindow.xaml
index 7729318ae7..7acb22551b 100644
--- a/Content.Client/Crayon/UI/CrayonWindow.xaml
+++ b/Content.Client/Crayon/UI/CrayonWindow.xaml
@@ -1,14 +1,13 @@
+ MinSize="450 500"
+ SetSize="450 500">
-
+
-
-
-
+
+
diff --git a/Content.Client/Crayon/UI/CrayonWindow.xaml.cs b/Content.Client/Crayon/UI/CrayonWindow.xaml.cs
index b97786cd41..88475562c6 100644
--- a/Content.Client/Crayon/UI/CrayonWindow.xaml.cs
+++ b/Content.Client/Crayon/UI/CrayonWindow.xaml.cs
@@ -1,8 +1,10 @@
using System.Collections.Generic;
+using System.Linq;
using Content.Client.Stylesheets;
using Content.Shared.Crayon;
using Content.Shared.Decals;
using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
@@ -18,7 +20,12 @@ namespace Content.Client.Crayon.UI
[GenerateTypedNameReferences]
public sealed partial class CrayonWindow : DefaultWindow
{
- private Dictionary? _decals;
+ [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
+ private readonly SpriteSystem _spriteSystem = default!;
+
+ private Dictionary>? _decals;
+ private List? _allDecals;
+ private string? _autoSelected;
private string? _selected;
private Color _color;
@@ -28,8 +35,10 @@ public sealed partial class CrayonWindow : DefaultWindow
public CrayonWindow()
{
RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ _spriteSystem = _entitySystem.GetEntitySystem();
- Search.OnTextChanged += _ => RefreshList();
+ Search.OnTextChanged += SearchChanged;
ColorSelector.OnColorChanged += SelectColor;
}
@@ -44,51 +53,95 @@ private void SelectColor(Color color)
private void RefreshList()
{
// Clear
- Grid.DisposeAllChildren();
- if (_decals == null)
+ Grids.DisposeAllChildren();
+
+ if (_decals == null || _allDecals == null)
return;
var filter = Search.Text;
- foreach (var (decal, tex) in _decals)
+ var comma = filter.IndexOf(',');
+ var first = (comma == -1 ? filter : filter[..comma]).Trim();
+
+ var names = _decals.Keys.ToList();
+ names.Sort((a, b) => a == "random" ? 1 : b == "random" ? -1 : a.CompareTo(b));
+
+ if (_autoSelected != null && first != _autoSelected && _allDecals.Contains(first))
+ {
+ _selected = first;
+ _autoSelected = _selected;
+ OnSelected?.Invoke(_selected);
+ }
+
+ foreach (var categoryName in names)
{
- if (!decal.Contains(filter))
+ var locName = Loc.GetString("crayon-category-" + categoryName);
+ var category = _decals[categoryName].Where(d => locName.Contains(first) || d.Name.Contains(first)).ToList();
+
+ if (category.Count == 0)
continue;
- var button = new TextureButton()
+ var label = new Label
{
- TextureNormal = tex,
- Name = decal,
- ToolTip = decal,
- Modulate = _color,
+ Text = locName
};
- button.OnPressed += ButtonOnPressed;
- if (_selected == decal)
+
+ var grid = new GridContainer
{
- var panelContainer = new PanelContainer()
+ Columns = 6,
+ Margin = new Thickness(0, 0, 0, 16)
+ };
+
+ Grids.AddChild(label);
+ Grids.AddChild(grid);
+
+ foreach (var (name, texture) in category)
+ {
+ var button = new TextureButton()
{
- PanelOverride = new StyleBoxFlat()
- {
- BackgroundColor = StyleNano.ButtonColorDefault,
- },
- Children =
- {
- button,
- },
+ TextureNormal = texture,
+ Name = name,
+ ToolTip = name,
+ Modulate = _color,
+ Scale = new System.Numerics.Vector2(2, 2)
};
- Grid.AddChild(panelContainer);
- }
- else
- {
- Grid.AddChild(button);
+ button.OnPressed += ButtonOnPressed;
+
+ if (_selected == name)
+ {
+ var panelContainer = new PanelContainer()
+ {
+ PanelOverride = new StyleBoxFlat()
+ {
+ BackgroundColor = StyleNano.ButtonColorDefault,
+ },
+ Children =
+ {
+ button,
+ },
+ };
+ grid.AddChild(panelContainer);
+ }
+ else
+ {
+ grid.AddChild(button);
+ }
}
}
}
+ private void SearchChanged(LineEdit.LineEditEventArgs obj)
+ {
+ _autoSelected = ""; // Placeholder to kick off the auto-select in refreshlist()
+ RefreshList();
+ }
+
private void ButtonOnPressed(ButtonEventArgs obj)
{
if (obj.Button.Name == null) return;
_selected = obj.Button.Name;
+ _autoSelected = null;
+ OnSelected?.Invoke(_selected);
RefreshList();
}
@@ -106,12 +159,38 @@ public void UpdateState(CrayonBoundUserInterfaceState state)
RefreshList();
}
- public void Populate(IEnumerable prototypes)
+ public void AdvanceState(string drawnDecal)
{
- _decals = new Dictionary();
+ var filter = Search.Text;
+ if (!filter.Contains(',') || !filter.Contains(drawnDecal))
+ return;
+
+ var first = filter[..filter.IndexOf(',')].Trim();
+
+ if (first.Equals(drawnDecal, StringComparison.InvariantCultureIgnoreCase))
+ {
+ Search.Text = filter[(filter.IndexOf(',') + 1)..].Trim();
+ _autoSelected = first;
+ }
+
+ RefreshList();
+ }
+
+ public void Populate(List prototypes)
+ {
+ _decals = [];
+ _allDecals = [];
+
+ prototypes.Sort((a, b) => a.ID.CompareTo(b.ID));
+
foreach (var decalPrototype in prototypes)
{
- _decals.Add(decalPrototype.ID, decalPrototype.Sprite.Frame0());
+ var category = "random";
+ if (decalPrototype.Tags.Count > 1 && decalPrototype.Tags[1].StartsWith("crayon-"))
+ category = decalPrototype.Tags[1].Replace("crayon-", "");
+ var list = _decals.GetOrNew(category);
+ list.Add((decalPrototype.ID, _spriteSystem.Frame0(decalPrototype.Sprite)));
+ _allDecals.Add(decalPrototype.ID);
}
RefreshList();
diff --git a/Content.Client/DeltaV/Hologram/HologramSystem.cs b/Content.Client/DeltaV/Hologram/HologramSystem.cs
index 212a797fd8..0d4cf098e0 100644
--- a/Content.Client/DeltaV/Hologram/HologramSystem.cs
+++ b/Content.Client/DeltaV/Hologram/HologramSystem.cs
@@ -17,7 +17,7 @@ public override void Initialize()
{
base.Initialize();
- _shader = _protoMan.Index("Hologram").InstanceUnique();
+ _shader = _protoMan.Index("HologramDeltaV").InstanceUnique();
SubscribeLocalEvent(OnShutdown);
SubscribeLocalEvent(OnStartup);
}
diff --git a/Content.Client/GameTicking/Managers/ClientGameTicker.cs b/Content.Client/GameTicking/Managers/ClientGameTicker.cs
index 309db2eb4e..a9e109df8a 100644
--- a/Content.Client/GameTicking/Managers/ClientGameTicker.cs
+++ b/Content.Client/GameTicking/Managers/ClientGameTicker.cs
@@ -3,6 +3,7 @@
using Content.Client.Lobby;
using Content.Client.RoundEnd;
using Content.Shared.GameTicking;
+using Content.Shared.GameTicking.Prototypes;
using Content.Shared.GameWindow;
using JetBrains.Annotations;
using Robust.Client.Graphics;
@@ -26,7 +27,7 @@ public sealed class ClientGameTicker : SharedGameTicker
[ViewVariables] public bool AreWeReady { get; private set; }
[ViewVariables] public bool IsGameStarted { get; private set; }
[ViewVariables] public string? RestartSound { get; private set; }
- [ViewVariables] public string? LobbyBackground { get; private set; }
+ [ViewVariables] public LobbyBackgroundPrototype? LobbyBackground { get; private set; }
[ViewVariables] public bool DisallowedLateJoin { get; private set; }
[ViewVariables] public string? ServerInfoBlob { get; private set; }
[ViewVariables] public TimeSpan StartTime { get; private set; }
diff --git a/Content.Client/Holopad/HolopadBoundUserInterface.cs b/Content.Client/Holopad/HolopadBoundUserInterface.cs
new file mode 100644
index 0000000000..20b55ea8c7
--- /dev/null
+++ b/Content.Client/Holopad/HolopadBoundUserInterface.cs
@@ -0,0 +1,101 @@
+using Content.Shared.Holopad;
+using Content.Shared.Silicons.StationAi;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
+using Robust.Shared.Player;
+using System.Numerics;
+
+namespace Content.Client.Holopad;
+
+public sealed class HolopadBoundUserInterface : BoundUserInterface
+{
+ [Dependency] private readonly ISharedPlayerManager _playerManager = default!;
+ [Dependency] private readonly IClyde _displayManager = default!;
+
+ [ViewVariables]
+ private HolopadWindow? _window;
+
+ public HolopadBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ IoCManager.InjectDependencies(this);
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _window = this.CreateWindow();
+ _window.Title = Loc.GetString("holopad-window-title", ("title", EntMan.GetComponent(Owner).EntityName));
+
+ if (this.UiKey is not HolopadUiKey)
+ {
+ Close();
+ return;
+ }
+
+ var uiKey = (HolopadUiKey)this.UiKey;
+
+ // AIs will see a different holopad interface to crew when interacting with them in the world
+ if (uiKey == HolopadUiKey.InteractionWindow && EntMan.HasComponent(_playerManager.LocalEntity))
+ uiKey = HolopadUiKey.InteractionWindowForAi;
+
+ _window.SetState(Owner, uiKey);
+ _window.UpdateState(new Dictionary());
+
+ // Set message actions
+ _window.SendHolopadStartNewCallMessageAction += SendHolopadStartNewCallMessage;
+ _window.SendHolopadAnswerCallMessageAction += SendHolopadAnswerCallMessage;
+ _window.SendHolopadEndCallMessageAction += SendHolopadEndCallMessage;
+ _window.SendHolopadStartBroadcastMessageAction += SendHolopadStartBroadcastMessage;
+ _window.SendHolopadActivateProjectorMessageAction += SendHolopadActivateProjectorMessage;
+ _window.SendHolopadRequestStationAiMessageAction += SendHolopadRequestStationAiMessage;
+
+ // If this call is addressed to an AI, open the window in the bottom right hand corner of the screen
+ if (uiKey == HolopadUiKey.AiRequestWindow)
+ _window.OpenCenteredAt(new Vector2(1f, 1f));
+
+ // Otherwise offset to the left so the holopad can still be seen
+ else
+ _window.OpenCenteredAt(new Vector2(0.3333f, 0.50f));
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ var castState = (HolopadBoundInterfaceState)state;
+ EntMan.TryGetComponent(Owner, out var xform);
+
+ _window?.UpdateState(castState.Holopads);
+ }
+
+ public void SendHolopadStartNewCallMessage(NetEntity receiver)
+ {
+ SendMessage(new HolopadStartNewCallMessage(receiver));
+ }
+
+ public void SendHolopadAnswerCallMessage()
+ {
+ SendMessage(new HolopadAnswerCallMessage());
+ }
+
+ public void SendHolopadEndCallMessage()
+ {
+ SendMessage(new HolopadEndCallMessage());
+ }
+
+ public void SendHolopadStartBroadcastMessage()
+ {
+ SendMessage(new HolopadStartBroadcastMessage());
+ }
+
+ public void SendHolopadActivateProjectorMessage()
+ {
+ SendMessage(new HolopadActivateProjectorMessage());
+ }
+
+ public void SendHolopadRequestStationAiMessage()
+ {
+ SendMessage(new HolopadStationAiRequestMessage());
+ }
+}
diff --git a/Content.Client/Holopad/HolopadSystem.cs b/Content.Client/Holopad/HolopadSystem.cs
new file mode 100644
index 0000000000..3bd556f1fc
--- /dev/null
+++ b/Content.Client/Holopad/HolopadSystem.cs
@@ -0,0 +1,172 @@
+using Content.Shared.Chat.TypingIndicator;
+using Content.Shared.Holopad;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+using System.Linq;
+
+namespace Content.Client.Holopad;
+
+public sealed class HolopadSystem : SharedHolopadSystem
+{
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnComponentInit);
+ SubscribeLocalEvent(OnShaderRender);
+ SubscribeAllEvent(OnTypingChanged);
+
+ SubscribeNetworkEvent(OnPlayerSpriteStateRequest);
+ SubscribeNetworkEvent(OnPlayerSpriteStateMessage);
+ }
+
+ private void OnComponentInit(EntityUid uid, HolopadHologramComponent component, ComponentInit ev)
+ {
+ if (!TryComp(uid, out var sprite))
+ return;
+
+ UpdateHologramSprite(uid);
+ }
+
+ private void OnShaderRender(EntityUid uid, HolopadHologramComponent component, BeforePostShaderRenderEvent ev)
+ {
+ if (ev.Sprite.PostShader == null)
+ return;
+
+ ev.Sprite.PostShader.SetParameter("t", (float)_timing.CurTime.TotalSeconds * component.ScrollRate);
+ }
+
+ private void OnTypingChanged(TypingChangedEvent ev, EntitySessionEventArgs args)
+ {
+ var uid = args.SenderSession.AttachedEntity;
+
+ if (!Exists(uid))
+ return;
+
+ if (!HasComp(uid))
+ return;
+
+ var netEv = new HolopadUserTypingChangedEvent(GetNetEntity(uid.Value), ev.IsTyping);
+ RaiseNetworkEvent(netEv);
+ }
+
+ private void OnPlayerSpriteStateRequest(PlayerSpriteStateRequest ev)
+ {
+ var targetPlayer = GetEntity(ev.TargetPlayer);
+ var player = _playerManager.LocalSession?.AttachedEntity;
+
+ // Ignore the request if received by a player who isn't the target
+ if (targetPlayer != player)
+ return;
+
+ if (!TryComp(player, out var playerSprite))
+ return;
+
+ var spriteLayerData = new List();
+
+ if (playerSprite.Visible)
+ {
+ // Record the RSI paths, state names and shader paramaters of all visible layers
+ for (int i = 0; i < playerSprite.AllLayers.Count(); i++)
+ {
+ if (!playerSprite.TryGetLayer(i, out var layer))
+ continue;
+
+ if (!layer.Visible ||
+ string.IsNullOrEmpty(layer.ActualRsi?.Path.ToString()) ||
+ string.IsNullOrEmpty(layer.State.Name))
+ continue;
+
+ var layerDatum = new PrototypeLayerData();
+ layerDatum.RsiPath = layer.ActualRsi.Path.ToString();
+ layerDatum.State = layer.State.Name;
+
+ if (layer.CopyToShaderParameters != null)
+ {
+ var key = (string)layer.CopyToShaderParameters.LayerKey;
+
+ if (playerSprite.LayerMapTryGet(key, out var otherLayerIdx) &&
+ playerSprite.TryGetLayer(otherLayerIdx, out var otherLayer) &&
+ otherLayer.Visible)
+ {
+ layerDatum.MapKeys = new() { key };
+
+ layerDatum.CopyToShaderParameters = new PrototypeCopyToShaderParameters()
+ {
+ LayerKey = key,
+ ParameterTexture = layer.CopyToShaderParameters.ParameterTexture,
+ ParameterUV = layer.CopyToShaderParameters.ParameterUV
+ };
+ }
+ }
+
+ spriteLayerData.Add(layerDatum);
+ }
+ }
+
+ // Return the recorded data to the server
+ var evResponse = new PlayerSpriteStateMessage(ev.TargetPlayer, spriteLayerData.ToArray());
+ RaiseNetworkEvent(evResponse);
+ }
+
+ private void OnPlayerSpriteStateMessage(PlayerSpriteStateMessage ev)
+ {
+ UpdateHologramSprite(GetEntity(ev.SpriteEntity), ev.SpriteLayerData);
+ }
+
+ private void UpdateHologramSprite(EntityUid uid, PrototypeLayerData[]? layerData = null)
+ {
+ if (!TryComp(uid, out var hologramSprite))
+ return;
+
+ if (!TryComp(uid, out var holopadhologram))
+ return;
+
+ for (int i = hologramSprite.AllLayers.Count() - 1; i >= 0; i--)
+ hologramSprite.RemoveLayer(i);
+
+ if (layerData == null || layerData.Length == 0)
+ {
+ layerData = new PrototypeLayerData[1];
+ layerData[0] = new PrototypeLayerData()
+ {
+ RsiPath = holopadhologram.RsiPath,
+ State = holopadhologram.RsiState
+ };
+ }
+
+ for (int i = 0; i < layerData.Length; i++)
+ {
+ var layer = layerData[i];
+ layer.Shader = "unshaded";
+
+ hologramSprite.AddLayer(layerData[i], i);
+ }
+
+ UpdateHologramShader(uid, hologramSprite, holopadhologram);
+ }
+
+ private void UpdateHologramShader(EntityUid uid, SpriteComponent sprite, HolopadHologramComponent holopadHologram)
+ {
+ // Find the texture height of the largest layer
+ float texHeight = sprite.AllLayers.Max(x => x.PixelSize.Y);
+
+ var instance = _prototypeManager.Index(holopadHologram.ShaderName).InstanceUnique();
+ instance.SetParameter("color1", new Vector3(holopadHologram.Color1.R, holopadHologram.Color1.G, holopadHologram.Color1.B));
+ instance.SetParameter("color2", new Vector3(holopadHologram.Color2.R, holopadHologram.Color2.G, holopadHologram.Color2.B));
+ instance.SetParameter("alpha", holopadHologram.Alpha);
+ instance.SetParameter("intensity", holopadHologram.Intensity);
+ instance.SetParameter("texHeight", texHeight);
+ instance.SetParameter("t", (float)_timing.CurTime.TotalSeconds * holopadHologram.ScrollRate);
+
+ sprite.PostShader = instance;
+ sprite.RaiseShaderEvent = true;
+ }
+}
diff --git a/Content.Client/Holopad/HolopadWindow.xaml b/Content.Client/Holopad/HolopadWindow.xaml
new file mode 100644
index 0000000000..882f918158
--- /dev/null
+++ b/Content.Client/Holopad/HolopadWindow.xaml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Holopad/HolopadWindow.xaml.cs b/Content.Client/Holopad/HolopadWindow.xaml.cs
new file mode 100644
index 0000000000..25982b901c
--- /dev/null
+++ b/Content.Client/Holopad/HolopadWindow.xaml.cs
@@ -0,0 +1,344 @@
+using Content.Client.Popups;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Access.Systems;
+using Content.Shared.Holopad;
+using Content.Shared.Telephone;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+using System.Linq;
+
+namespace Content.Client.Holopad;
+
+[GenerateTypedNameReferences]
+public sealed partial class HolopadWindow : FancyWindow
+{
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ private readonly SharedHolopadSystem _holopadSystem = default!;
+ private readonly SharedTelephoneSystem _telephoneSystem = default!;
+ private readonly AccessReaderSystem _accessReaderSystem = default!;
+ private readonly PopupSystem _popupSystem = default!;
+
+ private EntityUid? _owner = null;
+ private HolopadUiKey _currentUiKey;
+ private TelephoneState _currentState;
+ private TelephoneState _previousState;
+ private TimeSpan _buttonUnlockTime;
+ private float _updateTimer = 0.25f;
+
+ private const float UpdateTime = 0.25f;
+ private TimeSpan _buttonUnlockDelay = TimeSpan.FromSeconds(0.5f);
+
+ public event Action? SendHolopadStartNewCallMessageAction;
+ public event Action? SendHolopadAnswerCallMessageAction;
+ public event Action? SendHolopadEndCallMessageAction;
+ public event Action? SendHolopadStartBroadcastMessageAction;
+ public event Action? SendHolopadActivateProjectorMessageAction;
+ public event Action? SendHolopadRequestStationAiMessageAction;
+
+ public HolopadWindow()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ _holopadSystem = _entManager.System();
+ _telephoneSystem = _entManager.System();
+ _accessReaderSystem = _entManager.System();
+ _popupSystem = _entManager.System();
+
+ _buttonUnlockTime = _timing.CurTime + _buttonUnlockDelay;
+
+ // Assign button actions
+ AnswerCallButton.OnPressed += args => { OnHolopadAnswerCallMessage(); };
+ EndCallButton.OnPressed += args => { OnHolopadEndCallMessage(); };
+ StartBroadcastButton.OnPressed += args => { OnHolopadStartBroadcastMessage(); };
+ ActivateProjectorButton.OnPressed += args => { OnHolopadActivateProjectorMessage(); };
+ RequestStationAiButton.OnPressed += args => { OnHolopadRequestStationAiMessage(); };
+
+ // XML formatting
+ AnswerCallButton.AddStyleClass("ButtonAccept");
+ EndCallButton.AddStyleClass("Caution");
+ StartBroadcastButton.AddStyleClass("Caution");
+
+ HolopadContactListPanel.PanelOverride = new StyleBoxFlat
+ {
+ BackgroundColor = new Color(47, 47, 59) * Color.DarkGray,
+ BorderColor = new Color(82, 82, 82), //new Color(70, 73, 102),
+ BorderThickness = new Thickness(2),
+ };
+
+ HolopadContactListHeaderPanel.PanelOverride = new StyleBoxFlat
+ {
+ BackgroundColor = new Color(82, 82, 82),
+ };
+
+ EmergencyBroadcastText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-emergency-broadcast-in-progress")));
+ SubtitleText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-subtitle")));
+ OptionsText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-options")));
+ }
+
+ #region: Button actions
+
+ private void OnSendHolopadStartNewCallMessage(NetEntity receiver)
+ {
+ SendHolopadStartNewCallMessageAction?.Invoke(receiver);
+ }
+
+ private void OnHolopadAnswerCallMessage()
+ {
+ SendHolopadAnswerCallMessageAction?.Invoke();
+ }
+
+ private void OnHolopadEndCallMessage()
+ {
+ SendHolopadEndCallMessageAction?.Invoke();
+
+ if (_currentUiKey == HolopadUiKey.AiRequestWindow)
+ Close();
+ }
+
+ private void OnHolopadStartBroadcastMessage()
+ {
+ if (_playerManager.LocalSession?.AttachedEntity == null || _owner == null)
+ return;
+
+ var player = _playerManager.LocalSession.AttachedEntity;
+
+ if (!_accessReaderSystem.IsAllowed(player.Value, _owner.Value))
+ {
+ _popupSystem.PopupClient(Loc.GetString("holopad-window-access-denied"), _owner.Value, player.Value);
+ return;
+ }
+
+ SendHolopadStartBroadcastMessageAction?.Invoke();
+ }
+
+ private void OnHolopadActivateProjectorMessage()
+ {
+ SendHolopadActivateProjectorMessageAction?.Invoke();
+ }
+
+ private void OnHolopadRequestStationAiMessage()
+ {
+ SendHolopadRequestStationAiMessageAction?.Invoke();
+ }
+
+ #endregion
+
+ public void SetState(EntityUid owner, HolopadUiKey uiKey)
+ {
+ _owner = owner;
+ _currentUiKey = uiKey;
+
+ // Determines what UI containers are available to the user.
+ // Components of these will be toggled on and off when
+ // UpdateAppearance() is called
+
+ switch (uiKey)
+ {
+ case HolopadUiKey.InteractionWindow:
+ RequestStationAiContainer.Visible = true;
+ HolopadContactListContainer.Visible = true;
+ StartBroadcastContainer.Visible = true;
+ break;
+
+ case HolopadUiKey.InteractionWindowForAi:
+ ActivateProjectorContainer.Visible = true;
+ StartBroadcastContainer.Visible = true;
+ break;
+
+ case HolopadUiKey.AiActionWindow:
+ HolopadContactListContainer.Visible = true;
+ StartBroadcastContainer.Visible = true;
+ break;
+
+ case HolopadUiKey.AiRequestWindow:
+ break;
+ }
+ }
+
+ public void UpdateState(Dictionary holopads)
+ {
+ if (_owner == null || !_entManager.TryGetComponent(_owner.Value, out var telephone))
+ return;
+
+ // Caller ID text
+ var callerId = _telephoneSystem.GetFormattedCallerIdForEntity(telephone.LastCallerId.Item1, telephone.LastCallerId.Item2, Color.LightGray, "Default", 11);
+ var holoapdId = _telephoneSystem.GetFormattedDeviceIdForEntity(telephone.LastCallerId.Item3, Color.LightGray, "Default", 11);
+
+ CallerIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(callerId));
+ HolopadIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(holoapdId));
+ LockOutIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(callerId));
+
+ // Sort holopads alphabetically
+ var holopadArray = holopads.ToArray();
+ Array.Sort(holopadArray, AlphabeticalSort);
+
+ // Clear excess children from the contact list
+ while (ContactsList.ChildCount > holopadArray.Length)
+ ContactsList.RemoveChild(ContactsList.GetChild(ContactsList.ChildCount - 1));
+
+ // Make / update required children
+ for (int i = 0; i < holopadArray.Length; i++)
+ {
+ var (netEntity, label) = holopadArray[i];
+
+ if (i >= ContactsList.ChildCount)
+ {
+ var newContactButton = new HolopadContactButton();
+ newContactButton.OnPressed += args => { OnSendHolopadStartNewCallMessage(newContactButton.NetEntity); };
+
+ ContactsList.AddChild(newContactButton);
+ }
+
+ var child = ContactsList.GetChild(i);
+
+ if (child is not HolopadContactButton)
+ continue;
+
+ var contactButton = (HolopadContactButton)child;
+ contactButton.UpdateValues(netEntity, label);
+ }
+
+ // Update buttons
+ UpdateAppearance();
+ }
+
+ private void UpdateAppearance()
+ {
+ if (_owner == null || !_entManager.TryGetComponent(_owner.Value, out var telephone))
+ return;
+
+ if (_owner == null || !_entManager.TryGetComponent(_owner.Value, out var holopad))
+ return;
+
+ var hasBroadcastAccess = !_holopadSystem.IsHolopadBroadcastOnCoolDown((_owner.Value, holopad));
+ var localPlayer = _playerManager.LocalSession?.AttachedEntity;
+
+ ControlsLockOutContainer.Visible = _holopadSystem.IsHolopadControlLocked((_owner.Value, holopad), localPlayer);
+ ControlsContainer.Visible = !ControlsLockOutContainer.Visible;
+
+ // Temporarily disable the interface buttons when the call state changes to prevent any misclicks
+ if (_currentState != telephone.CurrentState)
+ {
+ _previousState = _currentState;
+ _currentState = telephone.CurrentState;
+ _buttonUnlockTime = _timing.CurTime + _buttonUnlockDelay;
+ }
+
+ var lockButtons = _timing.CurTime < _buttonUnlockTime;
+
+ // Make / update required children
+ foreach (var child in ContactsList.Children)
+ {
+ if (child is not HolopadContactButton contactButton)
+ continue;
+
+ var passesFilter = string.IsNullOrEmpty(SearchLineEdit.Text) ||
+ contactButton.Text?.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase) == true;
+
+ contactButton.Visible = passesFilter;
+ contactButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons);
+ }
+
+ // Update control text
+ var cooldown = _holopadSystem.GetHolopadBroadcastCoolDown((_owner.Value, holopad));
+ var cooldownString = $"{cooldown.Minutes:00}:{cooldown.Seconds:00}";
+
+ StartBroadcastButton.Text = _holopadSystem.IsHolopadBroadcastOnCoolDown((_owner.Value, holopad)) ?
+ Loc.GetString("holopad-window-emergency-broadcast-with-countdown", ("countdown", cooldownString)) :
+ Loc.GetString("holopad-window-emergency-broadcast");
+
+ var lockout = _holopadSystem.GetHolopadControlLockedPeriod((_owner.Value, holopad));
+ var lockoutString = $"{lockout.Minutes:00}:{lockout.Seconds:00}";
+
+ LockOutCountDownText.Text = Loc.GetString("holopad-window-controls-unlock-countdown", ("countdown", lockoutString));
+
+ switch (_currentState)
+ {
+ case TelephoneState.Idle:
+ CallStatusText.Text = Loc.GetString("holopad-window-no-calls-in-progress"); break;
+
+ case TelephoneState.Calling:
+ CallStatusText.Text = Loc.GetString("holopad-window-outgoing-call"); break;
+
+ case TelephoneState.Ringing:
+ CallStatusText.Text = (_currentUiKey == HolopadUiKey.AiRequestWindow) ?
+ Loc.GetString("holopad-window-ai-request") : Loc.GetString("holopad-window-incoming-call"); break;
+
+ case TelephoneState.InCall:
+ CallStatusText.Text = Loc.GetString("holopad-window-call-in-progress"); break;
+
+ case TelephoneState.EndingCall:
+ if (_previousState == TelephoneState.Calling || _previousState == TelephoneState.Idle)
+ CallStatusText.Text = Loc.GetString("holopad-window-call-rejected");
+ else
+ CallStatusText.Text = Loc.GetString("holopad-window-call-ending");
+ break;
+ }
+
+ // Update control disability
+ AnswerCallButton.Disabled = (_currentState != TelephoneState.Ringing || lockButtons);
+ EndCallButton.Disabled = (_currentState == TelephoneState.Idle || _currentState == TelephoneState.EndingCall || lockButtons);
+ StartBroadcastButton.Disabled = (_currentState != TelephoneState.Idle || !hasBroadcastAccess || lockButtons);
+ RequestStationAiButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons);
+ ActivateProjectorButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons);
+
+ // Update control visibility
+ FetchingAvailableHolopadsContainer.Visible = (ContactsList.ChildCount == 0);
+ ActiveCallControlsContainer.Visible = (_currentState != TelephoneState.Idle || _currentUiKey == HolopadUiKey.AiRequestWindow);
+ CallPlacementControlsContainer.Visible = !ActiveCallControlsContainer.Visible;
+ CallerIdContainer.Visible = (_currentState == TelephoneState.Ringing);
+ AnswerCallButton.Visible = (_currentState == TelephoneState.Ringing);
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ _updateTimer += args.DeltaSeconds;
+
+ if (_updateTimer >= UpdateTime)
+ {
+ _updateTimer -= UpdateTime;
+ UpdateAppearance();
+ }
+ }
+
+ private sealed class HolopadContactButton : Button
+ {
+ public NetEntity NetEntity;
+
+ public HolopadContactButton()
+ {
+ HorizontalExpand = true;
+ SetHeight = 32;
+ Margin = new Thickness(0f, 1f, 0f, 1f);
+ ReservesSpace = false;
+ }
+
+ public void UpdateValues(NetEntity netEntity, string label)
+ {
+ NetEntity = netEntity;
+ Text = Loc.GetString("holopad-window-contact-label", ("label", label));
+ }
+ }
+
+ private int AlphabeticalSort(KeyValuePair x, KeyValuePair y)
+ {
+ if (string.IsNullOrEmpty(x.Value))
+ return -1;
+
+ if (string.IsNullOrEmpty(y.Value))
+ return 1;
+
+ return x.Value.CompareTo(y.Value);
+ }
+}
diff --git a/Content.Client/Jittering/JitteringSystem.cs b/Content.Client/Jittering/JitteringSystem.cs
index aafaf318bb..cd3e5065ff 100644
--- a/Content.Client/Jittering/JitteringSystem.cs
+++ b/Content.Client/Jittering/JitteringSystem.cs
@@ -48,6 +48,9 @@ private void OnAnimationCompleted(EntityUid uid, JitteringComponent jittering, A
if (args.Key != _jitterAnimationKey || jittering.LifeStage >= ComponentLifeStage.Stopping)
return;
+ if (!args.Finished)
+ return;
+
if (TryComp(uid, out AnimationPlayerComponent? animationPlayer)
&& TryComp(uid, out SpriteComponent? sprite))
_animationPlayer.Play(uid, animationPlayer, GetAnimation(jittering, sprite), _jitterAnimationKey);
diff --git a/Content.Client/Light/Components/LightBehaviourComponent.cs b/Content.Client/Light/Components/LightBehaviourComponent.cs
index 7e8bf82a29..bcdb890c01 100644
--- a/Content.Client/Light/Components/LightBehaviourComponent.cs
+++ b/Content.Client/Light/Components/LightBehaviourComponent.cs
@@ -68,7 +68,7 @@ public void UpdatePlaybackValues(Animation owner)
if (MinDuration > 0)
{
- MaxTime = (float) _random.NextDouble() * (MaxDuration - MinDuration) + MinDuration;
+ MaxTime = (float)_random.NextDouble() * (MaxDuration - MinDuration) + MinDuration;
}
else
{
@@ -192,11 +192,11 @@ public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
{
if (interpolateValue < 0.5f)
{
- ApplyInterpolation(StartValue, EndValue, interpolateValue*2);
+ ApplyInterpolation(StartValue, EndValue, interpolateValue * 2);
}
else
{
- ApplyInterpolation(EndValue, StartValue, (interpolateValue-0.5f)*2);
+ ApplyInterpolation(EndValue, StartValue, (interpolateValue - 0.5f) * 2);
}
}
else
@@ -238,9 +238,9 @@ public sealed partial class RandomizeBehaviour : LightBehaviourAnimationTrack
public override void OnInitialize()
{
- _randomValue1 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
- _randomValue2 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
- _randomValue3 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
+ _randomValue1 = (float)InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
+ _randomValue2 = (float)InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
+ _randomValue3 = (float)InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
}
public override void OnStart()
@@ -258,7 +258,7 @@ public override void OnStart()
}
_randomValue3 = _randomValue4;
- _randomValue4 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
+ _randomValue4 = (float)InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
}
public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
@@ -362,7 +362,7 @@ public sealed partial class LightBehaviourComponent : SharedLightBehaviourCompon
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IRobustRandom _random = default!;
- private const string KeyPrefix = nameof(LightBehaviourComponent);
+ public const string KeyPrefix = nameof(LightBehaviourComponent);
public sealed class AnimationContainer
{
@@ -387,7 +387,7 @@ public AnimationContainer(int key, Animation animation, LightBehaviourAnimationT
public readonly List Animations = new();
[ViewVariables(VVAccess.ReadOnly)]
- private Dictionary _originalPropertyValues = new();
+ public Dictionary OriginalPropertyValues = new();
void ISerializationHooks.AfterDeserialization()
{
@@ -397,155 +397,12 @@ void ISerializationHooks.AfterDeserialization()
{
var animation = new Animation()
{
- AnimationTracks = {behaviour}
+ AnimationTracks = { behaviour }
};
Animations.Add(new AnimationContainer(key, animation, behaviour));
key++;
}
}
-
- ///
- /// If we disable all the light behaviours we want to be able to revert the light to its original state.
- ///
- private void CopyLightSettings(EntityUid uid, string property)
- {
- if (_entMan.TryGetComponent(uid, out PointLightComponent? light))
- {
- var propertyValue = AnimationHelper.GetAnimatableProperty(light, property);
- if (propertyValue != null)
- {
- _originalPropertyValues.Add(property, propertyValue);
- }
- }
- else
- {
- Logger.Warning($"{_entMan.GetComponent(uid).EntityName} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!");
- }
- }
-
- ///
- /// Start animating a light behaviour with the specified ID. If the specified ID is empty, it will start animating all light behaviour entries.
- /// If specified light behaviours are already animating, calling this does nothing.
- /// Multiple light behaviours can have the same ID.
- ///
- public void StartLightBehaviour(string id = "")
- {
- var uid = Owner;
- if (!_entMan.TryGetComponent(uid, out AnimationPlayerComponent? animation))
- {
- return;
- }
-
- var animations = _entMan.System();
-
- foreach (var container in Animations)
- {
- if (container.LightBehaviour.ID == id || id == string.Empty)
- {
- if (!animations.HasRunningAnimation(uid, animation, KeyPrefix + container.Key))
- {
- CopyLightSettings(uid, container.LightBehaviour.Property);
- container.LightBehaviour.UpdatePlaybackValues(container.Animation);
- animations.Play(uid, animation, container.Animation, KeyPrefix + container.Key);
- }
- }
- }
- }
-
- ///
- /// If any light behaviour with the specified ID is animating, then stop it.
- /// If no ID is specified then all light behaviours will be stopped.
- /// Multiple light behaviours can have the same ID.
- ///
- ///
- /// Should the behaviour(s) also be removed permanently?
- /// Should the light have its original settings applied?
- public void StopLightBehaviour(string id = "", bool removeBehaviour = false, bool resetToOriginalSettings = false)
- {
- var uid = Owner;
- if (!_entMan.TryGetComponent(uid, out AnimationPlayerComponent? animation))
- {
- return;
- }
-
- var toRemove = new List();
- var animations = _entMan.System();
-
- foreach (var container in Animations)
- {
- if (container.LightBehaviour.ID == id || id == string.Empty)
- {
- if (animations.HasRunningAnimation(uid, animation, KeyPrefix + container.Key))
- {
- animations.Stop(uid, animation, KeyPrefix + container.Key);
- }
-
- if (removeBehaviour)
- {
- toRemove.Add(container);
- }
- }
- }
-
- foreach (var container in toRemove)
- {
- Animations.Remove(container);
- }
-
- if (resetToOriginalSettings && _entMan.TryGetComponent(uid, out PointLightComponent? light))
- {
- foreach (var (property, value) in _originalPropertyValues)
- {
- AnimationHelper.SetAnimatableProperty(light, property, value);
- }
- }
-
- _originalPropertyValues.Clear();
- }
-
- ///
- /// Checks if at least one behaviour is running.
- ///
- /// Whether at least one behaviour is running, false if none is.
- public bool HasRunningBehaviours()
- {
- var uid = Owner;
- if (!_entMan.TryGetComponent(uid, out AnimationPlayerComponent? animation))
- {
- return false;
- }
-
- var animations = _entMan.System();
- return Animations.Any(container => animations.HasRunningAnimation(uid, animation, KeyPrefix + container.Key));
- }
-
- ///
- /// Add a new light behaviour to the component and start it immediately unless otherwise specified.
- ///
- public void AddNewLightBehaviour(LightBehaviourAnimationTrack behaviour, bool playImmediately = true)
- {
- var key = 0;
-
- while (Animations.Any(x => x.Key == key))
- {
- key++;
- }
-
- var animation = new Animation()
- {
- AnimationTracks = {behaviour}
- };
-
- behaviour.Initialize(Owner, _random, _entMan);
-
- var container = new AnimationContainer(key, animation, behaviour);
- Animations.Add(container);
-
- if (playImmediately)
- {
- StartLightBehaviour(behaviour.ID);
- }
- }
}
}
diff --git a/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs b/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs
index 8077406730..4d52b0933d 100644
--- a/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs
+++ b/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs
@@ -11,6 +11,7 @@ public sealed class ExpendableLightSystem : VisualizerSystem(uid, ExpendableLightVisuals.Behavior, out var lightBehaviourID, args.Component)
&& TryComp(uid, out var lightBehaviour))
{
- lightBehaviour.StopLightBehaviour();
+ _lightBehavior.StopLightBehaviour((uid, lightBehaviour));
if (!string.IsNullOrEmpty(lightBehaviourID))
{
- lightBehaviour.StartLightBehaviour(lightBehaviourID);
+ _lightBehavior.StartLightBehaviour((uid, lightBehaviour), lightBehaviourID);
}
else if (TryComp(uid, out var light))
{
diff --git a/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs b/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs
index 11f69165cf..789e6e1d20 100644
--- a/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs
+++ b/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs
@@ -1,7 +1,9 @@
using System.Linq;
using Content.Client.Light.Components;
using Robust.Client.GameObjects;
+using Robust.Client.Animations;
using Robust.Shared.Random;
+using Robust.Shared.Animations;
namespace Content.Client.Light.EntitySystems;
@@ -19,6 +21,9 @@ public override void Initialize()
private void OnBehaviorAnimationCompleted(EntityUid uid, LightBehaviourComponent component, AnimationCompletedEvent args)
{
+ if (!args.Finished)
+ return;
+
var container = component.Animations.FirstOrDefault(x => x.FullKey == args.Key);
if (container == null)
@@ -33,23 +38,139 @@ private void OnBehaviorAnimationCompleted(EntityUid uid, LightBehaviourComponent
}
}
- private void OnLightStartup(EntityUid uid, LightBehaviourComponent component, ComponentStartup args)
+ private void OnLightStartup(Entity entity, ref ComponentStartup args)
{
// TODO: Do NOT ensure component here. And use eventbus events instead...
- EnsureComp(uid);
+ EnsureComp(entity);
- foreach (var container in component.Animations)
+ foreach (var container in entity.Comp.Animations)
{
- container.LightBehaviour.Initialize(uid, _random, EntityManager);
+ container.LightBehaviour.Initialize(entity, _random, EntityManager);
}
// we need to initialize all behaviours before starting any
- foreach (var container in component.Animations)
+ foreach (var container in entity.Comp.Animations)
{
if (container.LightBehaviour.Enabled)
{
- component.StartLightBehaviour(container.LightBehaviour.ID);
+ StartLightBehaviour(entity, container.LightBehaviour.ID);
}
}
}
+
+ ///
+ /// If we disable all the light behaviours we want to be able to revert the light to its original state.
+ ///
+ private void CopyLightSettings(Entity entity, string property)
+ {
+ if (EntityManager.TryGetComponent(entity, out PointLightComponent? light))
+ {
+ var propertyValue = AnimationHelper.GetAnimatableProperty(light, property);
+ if (propertyValue != null)
+ {
+ entity.Comp.OriginalPropertyValues.Add(property, propertyValue);
+ }
+ }
+ else
+ {
+ Log.Warning($"{EntityManager.GetComponent(entity).EntityName} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!");
+ }
+ }
+ ///
+ /// Start animating a light behaviour with the specified ID. If the specified ID is empty, it will start animating all light behaviour entries.
+ /// If specified light behaviours are already animating, calling this does nothing.
+ /// Multiple light behaviours can have the same ID.
+ ///
+ public void StartLightBehaviour(Entity entity, string id = "")
+ {
+ if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
+ return;
+
+ foreach (var container in entity.Comp.Animations)
+ {
+ if (container.LightBehaviour.ID != id || id != string.Empty
+ || _player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key))
+ continue;
+
+ CopyLightSettings(entity, container.LightBehaviour.Property);
+ container.LightBehaviour.UpdatePlaybackValues(container.Animation);
+ _player.Play(entity, container.Animation, LightBehaviourComponent.KeyPrefix + container.Key);
+ }
+ }
+ ///
+ /// If any light behaviour with the specified ID is animating, then stop it.
+ /// If no ID is specified then all light behaviours will be stopped.
+ /// Multiple light behaviours can have the same ID.
+ ///
+ ///
+ /// Should the behaviour(s) also be removed permanently?
+ /// Should the light have its original settings applied?
+ public void StopLightBehaviour(Entity entity, string id = "", bool removeBehaviour = false, bool resetToOriginalSettings = false)
+ {
+ if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
+ {
+ return;
+ }
+ var comp = entity.Comp;
+ var toRemove = new List();
+ foreach (var container in comp.Animations)
+ {
+ if (container.LightBehaviour.ID != id || id != string.Empty)
+ continue;
+
+ if (_player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key))
+ _player.Stop(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key);
+
+ if (removeBehaviour)
+ toRemove.Add(container);
+ }
+ foreach (var container in toRemove)
+ {
+ comp.Animations.Remove(container);
+ }
+ if (resetToOriginalSettings && EntityManager.TryGetComponent(entity, out PointLightComponent? light))
+ {
+ foreach (var (property, value) in comp.OriginalPropertyValues)
+ {
+ AnimationHelper.SetAnimatableProperty(light, property, value);
+ }
+ }
+ comp.OriginalPropertyValues.Clear();
+ }
+ ///
+ /// Checks if at least one behaviour is running.
+ ///
+ /// Whether at least one behaviour is running, false if none is.
+ public bool HasRunningBehaviours(Entity entity)
+ {
+ //var uid = Owner;
+ if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
+ {
+ return false;
+ }
+ return entity.Comp.Animations.Any(container => _player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key));
+ }
+ ///
+ /// Add a new light behaviour to the component and start it immediately unless otherwise specified.
+ ///
+ public void AddNewLightBehaviour(Entity entity, LightBehaviourAnimationTrack behaviour, bool playImmediately = true)
+ {
+ var key = 0;
+ var comp = entity.Comp;
+ while (comp.Animations.Any(x => x.Key == key))
+ {
+ key++;
+ }
+ var animation = new Animation()
+ {
+ AnimationTracks = { behaviour }
+ };
+ behaviour.Initialize(entity.Owner, _random, EntityManager);
+ var container = new LightBehaviourComponent.AnimationContainer(key, animation, behaviour);
+ comp.Animations.Add(container);
+ if (playImmediately)
+ {
+ StartLightBehaviour(entity, behaviour.ID);
+ }
+ }
}
diff --git a/Content.Client/Light/EntitySystems/RotatingLightSystem.cs b/Content.Client/Light/EntitySystems/RotatingLightSystem.cs
index 842c13dedf..5c2c4e4c87 100644
--- a/Content.Client/Light/EntitySystems/RotatingLightSystem.cs
+++ b/Content.Client/Light/EntitySystems/RotatingLightSystem.cs
@@ -69,6 +69,9 @@ private void OnAfterAutoHandleState(EntityUid uid, RotatingLightComponent comp,
private void OnAnimationComplete(EntityUid uid, RotatingLightComponent comp, AnimationCompletedEvent args)
{
+ if (!args.Finished)
+ return;
+
PlayAnimation(uid, comp);
}
diff --git a/Content.Client/Light/HandheldLightSystem.cs b/Content.Client/Light/HandheldLightSystem.cs
index 7f18223811..ddd99c7c48 100644
--- a/Content.Client/Light/HandheldLightSystem.cs
+++ b/Content.Client/Light/HandheldLightSystem.cs
@@ -3,15 +3,15 @@
using Content.Shared.Light;
using Content.Shared.Light.Components;
using Content.Shared.Toggleable;
-using Robust.Client.Animations;
using Robust.Client.GameObjects;
-using Robust.Shared.Animations;
+using Content.Client.Light.EntitySystems;
namespace Content.Client.Light;
public sealed class HandheldLightSystem : SharedHandheldLightSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly LightBehaviorSystem _lightBehavior = default!;
public override void Initialize()
{
@@ -41,9 +41,9 @@ private void OnAppearanceChange(EntityUid uid, HandheldLightComponent? component
if (TryComp(uid, out var lightBehaviour))
{
// Reset any running behaviour to reset the animated properties back to the original value, to avoid conflicts between resets
- if (lightBehaviour.HasRunningBehaviours())
+ if (_lightBehavior.HasRunningBehaviours((uid, lightBehaviour)))
{
- lightBehaviour.StopLightBehaviour(resetToOriginalSettings: true);
+ _lightBehavior.StopLightBehaviour((uid, lightBehaviour), resetToOriginalSettings: true);
}
if (!enabled)
@@ -56,10 +56,10 @@ private void OnAppearanceChange(EntityUid uid, HandheldLightComponent? component
case HandheldLightPowerStates.FullPower:
break; // We just needed to reset all behaviours
case HandheldLightPowerStates.LowPower:
- lightBehaviour.StartLightBehaviour(component.RadiatingBehaviourId);
+ _lightBehavior.StartLightBehaviour((uid, lightBehaviour), component.RadiatingBehaviourId);
break;
case HandheldLightPowerStates.Dying:
- lightBehaviour.StartLightBehaviour(component.BlinkingBehaviourId);
+ _lightBehavior.StartLightBehaviour((uid, lightBehaviour), component.BlinkingBehaviourId);
break;
}
}
diff --git a/Content.Client/Lobby/LobbyState.cs b/Content.Client/Lobby/LobbyState.cs
index 2e728f552a..f003189007 100644
--- a/Content.Client/Lobby/LobbyState.cs
+++ b/Content.Client/Lobby/LobbyState.cs
@@ -12,7 +12,6 @@
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
-
namespace Content.Client.Lobby
{
public sealed class LobbyState : Robust.Client.State.State
@@ -25,6 +24,7 @@ public sealed class LobbyState : Robust.Client.State.State
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IVoteManager _voteManager = default!;
+ private ISawmill _sawmill = default!;
private ClientGameTicker _gameTicker = default!;
private ContentAudioSystem _contentAudioSystem = default!;
@@ -42,6 +42,7 @@ protected override void Startup()
_gameTicker = _entityManager.System();
_contentAudioSystem = _entityManager.System();
_contentAudioSystem.LobbySoundtrackChanged += UpdateLobbySoundtrackInfo;
+ _sawmill = Logger.GetSawmill("lobby");
chatController.SetMainChat(true);
@@ -113,7 +114,7 @@ public override void FrameUpdate(FrameEventArgs e)
return;
}
- Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started");
+ Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started");
string text;
if (_gameTicker.Paused)
@@ -161,7 +162,7 @@ private void UpdateLobbyUi()
else
{
Lobby!.StartTime.Text = string.Empty;
- Lobby!.ReadyButton.Text = Loc.GetString(Lobby!.ReadyButton.Pressed ? "lobby-state-player-status-ready": "lobby-state-player-status-not-ready");
+ Lobby!.ReadyButton.Text = Loc.GetString(Lobby!.ReadyButton.Pressed ? "lobby-state-player-status-ready" : "lobby-state-player-status-not-ready");
Lobby!.ReadyButton.ToggleMode = true;
Lobby!.ReadyButton.Disabled = false;
Lobby!.ReadyButton.Pressed = _gameTicker.AreWeReady;
@@ -200,10 +201,31 @@ private void UpdateLobbySoundtrackInfo(LobbySoundtrackChangedEvent ev)
private void UpdateLobbyBackground()
{
if (_gameTicker.LobbyBackground != null)
- Lobby!.Background.Texture = _resourceCache.GetResource(_gameTicker.LobbyBackground);
- else
- Lobby!.Background.Texture = null;
+ {
+ Lobby!.Background.Texture = _resourceCache.GetResource(_gameTicker.LobbyBackground.Background);
+
+ var lobbyBackground = _gameTicker.LobbyBackground;
+
+ var name = string.IsNullOrEmpty(lobbyBackground.Name)
+ ? Loc.GetString("lobby-state-background-unknown-title")
+ : lobbyBackground.Name;
+
+ var artist = string.IsNullOrEmpty(lobbyBackground.Artist)
+ ? Loc.GetString("lobby-state-background-unknown-artist")
+ : lobbyBackground.Artist;
+
+ var markup = Loc.GetString("lobby-state-background-text",
+ ("backgroundName", name),
+ ("backgroundArtist", artist));
+
+ Lobby!.LobbyBackground.SetMarkup(markup);
+
+ return;
+ }
+ _sawmill.Warning("_gameTicker.LobbyBackground was null! No lobby background selected.");
+ Lobby!.Background.Texture = null;
+ Lobby!.LobbyBackground.SetMarkup(Loc.GetString("lobby-state-background-no-background-text"));
}
private void SetReady(bool newReady)
diff --git a/Content.Client/Lobby/UI/LoadoutPreferenceSelector.xaml.cs b/Content.Client/Lobby/UI/LoadoutPreferenceSelector.xaml.cs
index eb472e7d95..cd73bf65b8 100644
--- a/Content.Client/Lobby/UI/LoadoutPreferenceSelector.xaml.cs
+++ b/Content.Client/Lobby/UI/LoadoutPreferenceSelector.xaml.cs
@@ -9,6 +9,7 @@
using Content.Shared.Clothing.Loadouts.Prototypes;
using Content.Shared.Clothing.Loadouts.Systems;
using Content.Shared.Customization.Systems;
+using Content.Shared.Labels.Components;
using Content.Shared.Paint;
using Content.Shared.Preferences;
using Content.Shared.Roles;
@@ -140,6 +141,15 @@ public LoadoutPreferenceSelector(LoadoutPrototype loadout, JobPrototype highJob,
Loc.GetString($"loadout-name-{loadout.ID}") == $"loadout-name-{loadout.ID}"
? entityManager.GetComponent(dummyLoadoutItem).EntityName
: Loc.GetString($"loadout-name-{loadout.ID}");
+
+ // Display the item's label if it's present
+ if (entityManager.TryGetComponent(dummyLoadoutItem, out LabelComponent? labelComponent))
+ {
+ var itemLabel = labelComponent.CurrentLabel;
+ if (!string.IsNullOrEmpty(itemLabel))
+ loadoutName += $" ({Loc.GetString(itemLabel)})";
+ }
+
var loadoutDesc =
!Loc.TryGetString($"loadout-description-{loadout.ID}", out var description)
? entityManager.GetComponent(dummyLoadoutItem).EntityDescription
diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml b/Content.Client/Lobby/UI/LobbyGui.xaml
index 4a158fd811..6f3072a29f 100644
--- a/Content.Client/Lobby/UI/LobbyGui.xaml
+++ b/Content.Client/Lobby/UI/LobbyGui.xaml
@@ -50,11 +50,14 @@
-
+
+
+
+
diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml.cs b/Content.Client/Lobby/UI/LobbyGui.xaml.cs
index d19643f20a..8e18db8993 100644
--- a/Content.Client/Lobby/UI/LobbyGui.xaml.cs
+++ b/Content.Client/Lobby/UI/LobbyGui.xaml.cs
@@ -22,6 +22,7 @@ public LobbyGui()
SetAnchorPreset(Background, LayoutPreset.Wide);
LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text"));
+ LobbyBackground.SetMarkup(Loc.GetString("lobby-state-background-no-background-text"));
LeaveButton.OnPressed += _ => _consoleHost.ExecuteCommand("disconnect");
OptionsButton.OnPressed += _ => UserInterfaceManager.GetUIController().ToggleWindow();
diff --git a/Content.Client/Mech/Ui/MechMenu.xaml.cs b/Content.Client/Mech/Ui/MechMenu.xaml.cs
index 6f39bc386e..7ce863b7cd 100644
--- a/Content.Client/Mech/Ui/MechMenu.xaml.cs
+++ b/Content.Client/Mech/Ui/MechMenu.xaml.cs
@@ -12,7 +12,7 @@ public sealed partial class MechMenu : FancyWindow
{
[Dependency] private readonly IEntityManager _ent = default!;
- private readonly EntityUid _mech;
+ private EntityUid _mech;
public event Action? OnRemoveButtonPressed;
@@ -25,6 +25,7 @@ public MechMenu()
public void SetEntity(EntityUid uid)
{
MechView.SetEntity(uid);
+ _mech = uid;
}
public void UpdateMechStats()
diff --git a/Content.Client/Movement/Systems/ClientSpriteMovementSystem.cs b/Content.Client/Movement/Systems/ClientSpriteMovementSystem.cs
new file mode 100644
index 0000000000..1700796ede
--- /dev/null
+++ b/Content.Client/Movement/Systems/ClientSpriteMovementSystem.cs
@@ -0,0 +1,46 @@
+using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Systems;
+using Robust.Client.GameObjects;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Movement.Systems;
+
+///
+/// Controls the switching of motion and standing still animation
+///
+public sealed class ClientSpriteMovementSystem : SharedSpriteMovementSystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ private EntityQuery _spriteQuery;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _spriteQuery = GetEntityQuery();
+
+ SubscribeLocalEvent(OnAfterAutoHandleState);
+ }
+
+ private void OnAfterAutoHandleState(Entity ent, ref AfterAutoHandleStateEvent args)
+ {
+ if (!_spriteQuery.TryGetComponent(ent, out var sprite))
+ return;
+
+ if (ent.Comp.IsMoving)
+ {
+ foreach (var (layer, state) in ent.Comp.MovementLayers)
+ {
+ sprite.LayerSetData(layer, state);
+ }
+ }
+ else
+ {
+ foreach (var (layer, state) in ent.Comp.NoMovementLayers)
+ {
+ sprite.LayerSetData(layer, state);
+ }
+ }
+ }
+}
diff --git a/Content.Client/Movement/Systems/SpriteMovementSystem.cs b/Content.Client/Movement/Systems/SpriteMovementSystem.cs
deleted file mode 100644
index 313683855d..0000000000
--- a/Content.Client/Movement/Systems/SpriteMovementSystem.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using Content.Shared.Movement.Components;
-using Content.Shared.Movement.Events;
-using Content.Shared.Movement.Systems;
-using Robust.Client.GameObjects;
-using Robust.Shared.Timing;
-
-namespace Content.Client.Movement.Systems;
-
-///
-/// Handles setting sprite states based on whether an entity has movement input.
-///
-public sealed class SpriteMovementSystem : EntitySystem
-{
- [Dependency] private readonly IGameTiming _timing = default!;
-
- private EntityQuery _spriteQuery;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent(OnSpriteMoveInput);
- _spriteQuery = GetEntityQuery();
- }
-
- private void OnSpriteMoveInput(EntityUid uid, SpriteMovementComponent component, ref MoveInputEvent args)
- {
- if (!_timing.IsFirstTimePredicted)
- return;
-
- var oldMoving = (SharedMoverController.GetNormalizedMovement(args.OldMovement) & MoveButtons.AnyDirection) != MoveButtons.None;
- var moving = (SharedMoverController.GetNormalizedMovement(args.Component.HeldMoveButtons) & MoveButtons.AnyDirection) != MoveButtons.None;
-
- if (oldMoving == moving || !_spriteQuery.TryGetComponent(uid, out var sprite))
- return;
-
- if (moving)
- {
- foreach (var (layer, state) in component.MovementLayers)
- {
- sprite.LayerSetData(layer, state);
- }
- }
- else
- {
- foreach (var (layer, state) in component.NoMovementLayers)
- {
- sprite.LayerSetData(layer, state);
- }
- }
- }
-}
diff --git a/Content.Client/Overlays/EquipmentHudSystem.cs b/Content.Client/Overlays/EquipmentHudSystem.cs
index 3672892ae3..1e36f0a4d6 100644
--- a/Content.Client/Overlays/EquipmentHudSystem.cs
+++ b/Content.Client/Overlays/EquipmentHudSystem.cs
@@ -93,7 +93,8 @@ private void OnRoundRestart(RoundRestartCleanupEvent args)
protected virtual void OnRefreshEquipmentHud(EntityUid uid, T component, InventoryRelayedEvent> args)
{
- OnRefreshComponentHud(uid, component, args.Args);
+ args.Args.Active = true;
+ args.Args.Components.Add(component);
}
protected virtual void OnRefreshComponentHud(EntityUid uid, T component, RefreshEquipmentHudEvent args)
diff --git a/Content.Client/Overlays/Switchable/NightVisionSystem.cs b/Content.Client/Overlays/Switchable/NightVisionSystem.cs
index c85b3758d8..7764e39a0a 100644
--- a/Content.Client/Overlays/Switchable/NightVisionSystem.cs
+++ b/Content.Client/Overlays/Switchable/NightVisionSystem.cs
@@ -1,3 +1,4 @@
+using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Overlays.Switchable;
using Robust.Client.Graphics;
@@ -20,6 +21,26 @@ public override void Initialize()
_overlay = new BaseSwitchableOverlay();
}
+ protected override void OnRefreshComponentHud(EntityUid uid,
+ NightVisionComponent component,
+ RefreshEquipmentHudEvent args)
+ {
+ if (component.IsEquipment)
+ return;
+
+ base.OnRefreshComponentHud(uid, component, args);
+ }
+
+ protected override void OnRefreshEquipmentHud(EntityUid uid,
+ NightVisionComponent component,
+ InventoryRelayedEvent> args)
+ {
+ if (!component.IsEquipment)
+ return;
+
+ base.OnRefreshEquipmentHud(uid, component, args);
+ }
+
private void OnToggle(Entity ent, ref SwitchableOverlayToggledEvent args)
{
RefreshOverlay(args.User);
diff --git a/Content.Client/Overlays/Switchable/ThermalVisionOverlay.cs b/Content.Client/Overlays/Switchable/ThermalVisionOverlay.cs
index eb12b33e3a..010d141170 100644
--- a/Content.Client/Overlays/Switchable/ThermalVisionOverlay.cs
+++ b/Content.Client/Overlays/Switchable/ThermalVisionOverlay.cs
@@ -143,9 +143,9 @@ private bool CanSee(EntityUid uid, SpriteComponent sprite)
_stealth.GetVisibility(uid, stealth) > 0.5f);
}
- public void ResetLight()
+ public void ResetLight(bool checkFirstTimePredicted = true)
{
- if (_lightEntity == null || !_timing.IsFirstTimePredicted)
+ if (_lightEntity == null || checkFirstTimePredicted && !_timing.IsFirstTimePredicted)
return;
_entity.DeleteEntity(_lightEntity);
diff --git a/Content.Client/Overlays/Switchable/ThermalVisionSystem.cs b/Content.Client/Overlays/Switchable/ThermalVisionSystem.cs
index 9b6e5eed0f..7c3f33d068 100644
--- a/Content.Client/Overlays/Switchable/ThermalVisionSystem.cs
+++ b/Content.Client/Overlays/Switchable/ThermalVisionSystem.cs
@@ -1,3 +1,4 @@
+using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Overlays.Switchable;
using Robust.Client.Graphics;
@@ -21,6 +22,26 @@ public override void Initialize()
_overlay = new BaseSwitchableOverlay();
}
+ protected override void OnRefreshComponentHud(EntityUid uid,
+ ThermalVisionComponent component,
+ RefreshEquipmentHudEvent args)
+ {
+ if (component.IsEquipment)
+ return;
+
+ base.OnRefreshComponentHud(uid, component, args);
+ }
+
+ protected override void OnRefreshEquipmentHud(EntityUid uid,
+ ThermalVisionComponent component,
+ InventoryRelayedEvent> args)
+ {
+ if (!component.IsEquipment)
+ return;
+
+ base.OnRefreshEquipmentHud(uid, component, args);
+ }
+
private void OnToggle(Entity ent, ref SwitchableOverlayToggledEvent args)
{
RefreshOverlay(args.User);
@@ -54,6 +75,7 @@ protected override void DeactivateInternal()
{
base.DeactivateInternal();
+ _thermalOverlay.ResetLight(false);
UpdateOverlay(null);
UpdateThermalOverlay(null, 0f);
}
diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs
index 9d453e5518..edd4ef1426 100644
--- a/Content.Client/Physics/Controllers/MoverController.cs
+++ b/Content.Client/Physics/Controllers/MoverController.cs
@@ -34,57 +34,57 @@ public override void Initialize()
Subs.CVar(_config, CCVars.DefaultWalk, _ => RaiseNetworkEvent(new UpdateInputCVarsMessage()));
}
- private void OnUpdatePredicted(EntityUid uid, InputMoverComponent component, ref UpdateIsPredictedEvent args)
+ private void OnUpdatePredicted(Entity entity, ref UpdateIsPredictedEvent args)
{
// Enable prediction if an entity is controlled by the player
- if (uid == _playerManager.LocalEntity)
+ if (entity.Owner == _playerManager.LocalEntity)
args.IsPredicted = true;
}
- private void OnUpdateRelayTargetPredicted(EntityUid uid, MovementRelayTargetComponent component, ref UpdateIsPredictedEvent args)
+ private void OnUpdateRelayTargetPredicted(Entity entity, ref UpdateIsPredictedEvent args)
{
- if (component.Source == _playerManager.LocalEntity)
+ if (entity.Comp.Source == _playerManager.LocalEntity)
args.IsPredicted = true;
}
- private void OnUpdatePullablePredicted(EntityUid uid, PullableComponent component, ref UpdateIsPredictedEvent args)
+ private void OnUpdatePullablePredicted(Entity entity, ref UpdateIsPredictedEvent args)
{
// Enable prediction if an entity is being pulled by the player.
// Disable prediction if an entity is being pulled by some non-player entity.
- if (component.Puller == _playerManager.LocalEntity)
+ if (entity.Comp.Puller == _playerManager.LocalEntity)
args.IsPredicted = true;
- else if (component.Puller != null)
+ else if (entity.Comp.Puller != null)
args.BlockPrediction = true;
// TODO recursive pulling checks?
// What if the entity is being pulled by a vehicle controlled by the player?
}
- private void OnRelayPlayerAttached(EntityUid uid, RelayInputMoverComponent component, LocalPlayerAttachedEvent args)
+ private void OnRelayPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args)
{
- Physics.UpdateIsPredicted(uid);
- Physics.UpdateIsPredicted(component.RelayEntity);
- if (MoverQuery.TryGetComponent(component.RelayEntity, out var inputMover))
- SetMoveInput(inputMover, MoveButtons.None);
+ Physics.UpdateIsPredicted(entity.Owner);
+ Physics.UpdateIsPredicted(entity.Comp.RelayEntity);
+ if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover))
+ SetMoveInput((entity.Owner, inputMover), MoveButtons.None);
}
- private void OnRelayPlayerDetached(EntityUid uid, RelayInputMoverComponent component, LocalPlayerDetachedEvent args)
+ private void OnRelayPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args)
{
- Physics.UpdateIsPredicted(uid);
- Physics.UpdateIsPredicted(component.RelayEntity);
- if (MoverQuery.TryGetComponent(component.RelayEntity, out var inputMover))
- SetMoveInput(inputMover, MoveButtons.None);
+ Physics.UpdateIsPredicted(entity.Owner);
+ Physics.UpdateIsPredicted(entity.Comp.RelayEntity);
+ if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover))
+ SetMoveInput((entity.Owner, inputMover), MoveButtons.None);
}
- private void OnPlayerAttached(EntityUid uid, InputMoverComponent component, LocalPlayerAttachedEvent args)
+ private void OnPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args)
{
- SetMoveInput(component, MoveButtons.None);
+ SetMoveInput(entity, MoveButtons.None);
}
- private void OnPlayerDetached(EntityUid uid, InputMoverComponent component, LocalPlayerDetachedEvent args)
+ private void OnPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args)
{
- SetMoveInput(component, MoveButtons.None);
+ SetMoveInput(entity, MoveButtons.None);
}
public override void UpdateBeforeSolve(bool prediction, float frameTime)
diff --git a/Content.Client/Stylesheets/StyleNano.cs b/Content.Client/Stylesheets/StyleNano.cs
index 2bbf7af550..2b4e3657d6 100644
--- a/Content.Client/Stylesheets/StyleNano.cs
+++ b/Content.Client/Stylesheets/StyleNano.cs
@@ -113,6 +113,7 @@ public sealed class StyleNano : StyleBase
public static readonly Color ButtonColorGoodDefault = Color.FromHex("#3E6C45");
public static readonly Color ButtonColorGoodHovered = Color.FromHex("#31843E");
+ public static readonly Color ButtonColorGoodDisabled = Color.FromHex("#164420");
//NavMap
public static readonly Color PointRed = Color.FromHex("#B02E26");
@@ -1500,6 +1501,20 @@ public StyleNano(IResourceCache resCache) : base(resCache)
Element