diff --git a/.editorconfig b/.editorconfig
index a5dfab07a5..1583c600aa 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -129,7 +129,7 @@ csharp_indent_braces = false
csharp_indent_switch_labels = true
# Space preferences
-csharp_space_after_cast = true
+csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index a397604185..7a8129df1a 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -17,10 +17,17 @@ Small fixes/refactors are exempt.
Any media may be used in SS14 progress reports, with clear credit given.
If you're unsure whether your PR will require media, ask a maintainer.
-
-Check the box below to confirm that you have in fact seen this (put an X in the brackets, like [X]):
-->
+## Requirements
+
+- [ ] I have read and I am following the [Pull Request Guidelines](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html). I understand that not doing so may get my pr closed at maintainer’s discretion
- [ ] I have added screenshots/videos to this PR showcasing its changes ingame, **or** this PR does not require an ingame showcase
## Breaking changes
@@ -34,7 +41,7 @@ Make players aware of new features and changes that could affect how they play t
-->
-
-
+
+
+
+
diff --git a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs
index 6d0b2a184f..320bb88a67 100644
--- a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs
+++ b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs
@@ -8,6 +8,7 @@
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using System.Numerics;
+using System.Linq;
namespace Content.Client.Access.UI
{
@@ -17,19 +18,19 @@ public sealed partial class AgentIDCardWindow : DefaultWindow
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
private readonly SpriteSystem _spriteSystem;
- private readonly AgentIDCardBoundUserInterface _bui;
private const int JobIconColumnCount = 10;
public event Action? OnNameChanged;
public event Action? OnJobChanged;
- public AgentIDCardWindow(AgentIDCardBoundUserInterface bui)
+ public event Action>? OnJobIconChanged;
+
+ public AgentIDCardWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_spriteSystem = _entitySystem.GetEntitySystem();
- _bui = bui;
NameLineEdit.OnTextEntered += e => OnNameChanged?.Invoke(e.Text);
NameLineEdit.OnFocusExit += e => OnNameChanged?.Invoke(e.Text);
@@ -38,17 +39,16 @@ public AgentIDCardWindow(AgentIDCardBoundUserInterface bui)
JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text);
}
- public void SetAllowedIcons(HashSet> icons, string currentJobIconId)
+ public void SetAllowedIcons(string currentJobIconId)
{
IconGrid.DisposeAllChildren();
- var jobIconGroup = new ButtonGroup();
+ var jobIconButtonGroup = new ButtonGroup();
var i = 0;
- foreach (var jobIconId in icons)
+ var icons = _prototypeManager.EnumeratePrototypes().Where(icon => icon.AllowSelection).ToList();
+ icons.Sort((x, y) => string.Compare(x.LocalizedJobName, y.LocalizedJobName, StringComparison.CurrentCulture));
+ foreach (var jobIcon in icons)
{
- if (!_prototypeManager.TryIndex(jobIconId, out var jobIcon))
- continue;
-
String styleBase = StyleBase.ButtonOpenBoth;
var modulo = i % JobIconColumnCount;
if (modulo == 0)
@@ -62,12 +62,13 @@ public void SetAllowedIcons(HashSet> icons, string
Access = AccessLevel.Public,
StyleClasses = { styleBase },
MaxSize = new Vector2(42, 28),
- Group = jobIconGroup,
- Pressed = i == 0,
+ Group = jobIconButtonGroup,
+ Pressed = currentJobIconId == jobIcon.ID,
+ ToolTip = jobIcon.LocalizedJobName
};
// Generate buttons textures
- TextureRect jobIconTexture = new TextureRect
+ var jobIconTexture = new TextureRect
{
Texture = _spriteSystem.Frame0(jobIcon.Icon),
TextureScale = new Vector2(2.5f, 2.5f),
@@ -75,12 +76,9 @@ public void SetAllowedIcons(HashSet> icons, string
};
jobIconButton.AddChild(jobIconTexture);
- jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIconId);
+ jobIconButton.OnPressed += _ => OnJobIconChanged?.Invoke(jobIcon.ID);
IconGrid.AddChild(jobIconButton);
- if (jobIconId.Equals(currentJobIconId))
- jobIconButton.Pressed = true;
-
i++;
}
}
diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs
index aff6c1ff7b..f05e445588 100644
--- a/Content.Client/Actions/ActionsSystem.cs
+++ b/Content.Client/Actions/ActionsSystem.cs
@@ -48,6 +48,7 @@ public override void Initialize()
SubscribeLocalEvent(OnInstantHandleState);
SubscribeLocalEvent(OnEntityTargetHandleState);
SubscribeLocalEvent(OnWorldTargetHandleState);
+ SubscribeLocalEvent(OnEntityWorldTargetHandleState);
}
private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
@@ -76,6 +77,18 @@ private void OnWorldTargetHandleState(EntityUid uid, WorldTargetActionComponent
BaseHandleState(uid, component, state);
}
+ private void OnEntityWorldTargetHandleState(EntityUid uid,
+ EntityWorldTargetActionComponent component,
+ ref ComponentHandleState args)
+ {
+ if (args.Current is not EntityWorldTargetActionComponentState state)
+ return;
+
+ component.Whitelist = state.Whitelist;
+ component.CanTargetSelf = state.CanTargetSelf;
+ BaseHandleState(uid, component, state);
+ }
+
private void BaseHandleState(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent
{
// TODO ACTIONS use auto comp states
@@ -107,7 +120,7 @@ private void BaseHandleState(EntityUid uid, BaseActionComponent component, Ba
UpdateAction(uid, component);
}
- protected override void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
+ public override void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
{
if (!ResolveActionData(actionId, ref action))
return;
@@ -293,7 +306,7 @@ public void LoadActionAssignments(string path, bool userData)
continue;
var action = _serialization.Read(actionNode, notNullableOverride: true);
- var actionId = Spawn(null);
+ var actionId = Spawn();
AddComp(actionId, action);
AddActionDirect(user, actionId);
diff --git a/Content.Client/Actions/UI/ActionAlertTooltip.cs b/Content.Client/Actions/UI/ActionAlertTooltip.cs
index ddc498b6e9..f805f6643d 100644
--- a/Content.Client/Actions/UI/ActionAlertTooltip.cs
+++ b/Content.Client/Actions/UI/ActionAlertTooltip.cs
@@ -1,4 +1,4 @@
-using Content.Client.Stylesheets;
+using Content.Client.Stylesheets;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -77,9 +77,12 @@ public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string?
MaxWidth = TooltipTextMaxWidth,
StyleClasses = {StyleNano.StyleClassTooltipActionRequirements}
};
- requiresLabel.SetMessage(FormattedMessage.FromMarkup("[color=#635c5c]" +
- requires +
- "[/color]"));
+
+ if (!FormattedMessage.TryFromMarkup("[color=#635c5c]" + requires + "[/color]", out var markup))
+ return;
+
+ requiresLabel.SetMessage(markup);
+
vbox.AddChild(requiresLabel);
}
}
@@ -97,8 +100,11 @@ protected override void FrameUpdate(FrameEventArgs args)
if (timeLeft > TimeSpan.Zero)
{
var duration = Cooldown.Value.End - Cooldown.Value.Start;
- _cooldownLabel.SetMessage(FormattedMessage.FromMarkup(
- $"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]"));
+
+ if (!FormattedMessage.TryFromMarkup($"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]", out var markup))
+ return;
+
+ _cooldownLabel.SetMessage(markup);
_cooldownLabel.Visible = true;
}
else
diff --git a/Content.Client/Administration/AdminNameOverlay.cs b/Content.Client/Administration/AdminNameOverlay.cs
index c21ba2e32c..27b2a5dedb 100644
--- a/Content.Client/Administration/AdminNameOverlay.cs
+++ b/Content.Client/Administration/AdminNameOverlay.cs
@@ -44,7 +44,7 @@ protected override void Draw(in OverlayDrawArgs args)
}
// if not on the same map, continue
- if (_entityManager.GetComponent(entity.Value).MapID != _eyeManager.CurrentMap)
+ if (_entityManager.GetComponent(entity.Value).MapID != args.MapId)
{
continue;
}
diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs
index ddd66623bd..dd8e3e2212 100644
--- a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs
+++ b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs
@@ -88,26 +88,51 @@ public BwoinkControl()
var ach = AHelpHelper.EnsurePanel(a.SessionId);
var bch = AHelpHelper.EnsurePanel(b.SessionId);
- // First, sort by unread. Any chat with unread messages appears first. We just sort based on unread
- // status, not number of unread messages, so that more recent unread messages take priority.
+ // Pinned players first
+ if (a.IsPinned != b.IsPinned)
+ return a.IsPinned ? -1 : 1;
+
+ // First, sort by unread. Any chat with unread messages appears first.
var aUnread = ach.Unread > 0;
var bUnread = bch.Unread > 0;
if (aUnread != bUnread)
return aUnread ? -1 : 1;
+ // Sort by recent messages during the current round.
+ var aRecent = a.ActiveThisRound && ach.LastMessage != DateTime.MinValue;
+ var bRecent = b.ActiveThisRound && bch.LastMessage != DateTime.MinValue;
+ if (aRecent != bRecent)
+ return aRecent ? -1 : 1;
+
// Next, sort by connection status. Any disconnected players are grouped towards the end.
if (a.Connected != b.Connected)
return a.Connected ? -1 : 1;
- // Next, group by whether or not the players have participated in this round.
- // The ahelp window shows all players that have connected since server restart, this groups them all towards the bottom.
- if (a.ActiveThisRound != b.ActiveThisRound)
- return a.ActiveThisRound ? -1 : 1;
+ // Sort connected players by New Player status, then by Antag status
+ if (a.Connected && b.Connected)
+ {
+ var aNewPlayer = a.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold));
+ var bNewPlayer = b.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold));
+
+ if (aNewPlayer != bNewPlayer)
+ return aNewPlayer ? -1 : 1;
+
+ if (a.Antag != b.Antag)
+ return a.Antag ? -1 : 1;
+ }
+
+ // Sort disconnected players by participation in the round
+ if (!a.Connected && !b.Connected)
+ {
+ if (a.ActiveThisRound != b.ActiveThisRound)
+ return a.ActiveThisRound ? -1 : 1;
+ }
// Finally, sort by the most recent message.
return bch.LastMessage.CompareTo(ach.LastMessage);
};
+
Bans.OnPressed += _ =>
{
if (_currentPlayer is not null)
@@ -253,7 +278,20 @@ private void SwitchToChannel(NetUserId? ch)
public void PopulateList()
{
+ // Maintain existing pin statuses
+ var pinnedPlayers = ChannelSelector.PlayerInfo.Where(p => p.IsPinned).ToDictionary(p => p.SessionId);
+
ChannelSelector.PopulateList();
+
+ // Restore pin statuses
+ foreach (var player in ChannelSelector.PlayerInfo)
+ {
+ if (pinnedPlayers.TryGetValue(player.SessionId, out var pinnedPlayer))
+ {
+ player.IsPinned = pinnedPlayer.IsPinned;
+ }
+ }
+
UpdateButtons();
}
}
diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs
index 30f9d24df1..e8653843c7 100644
--- a/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs
+++ b/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs
@@ -30,7 +30,11 @@ public BwoinkWindow()
}
};
- OnOpen += () => Bwoink.PopulateList();
+ OnOpen += () =>
+ {
+ Bwoink.ChannelSelector.StopFiltering();
+ Bwoink.PopulateList();
+ };
}
}
}
diff --git a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs
index 12522d552d..c7fbf6c2dc 100644
--- a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs
+++ b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs
@@ -4,154 +4,166 @@
using Content.Client.Verbs.UI;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
+using Robust.Shared.Utility;
-namespace Content.Client.Administration.UI.CustomControls
+namespace Content.Client.Administration.UI.CustomControls;
+
+[GenerateTypedNameReferences]
+public sealed partial class PlayerListControl : BoxContainer
{
- [GenerateTypedNameReferences]
- public sealed partial class PlayerListControl : BoxContainer
- {
- private readonly AdminSystem _adminSystem;
+ private readonly AdminSystem _adminSystem;
- private List _playerList = new();
- private readonly List _sortedPlayerList = new();
+ private readonly IEntityManager _entManager;
+ private readonly IUserInterfaceManager _uiManager;
- public event Action? OnSelectionChanged;
- public IReadOnlyList PlayerInfo => _playerList;
+ private PlayerInfo? _selectedPlayer;
- public Func? OverrideText;
- public Comparison? Comparison;
+ private List _playerList = new();
+ private List _sortedPlayerList = new();
- private IEntityManager _entManager;
- private IUserInterfaceManager _uiManager;
+ public Comparison? Comparison;
+ public Func? OverrideText;
- private PlayerInfo? _selectedPlayer;
+ public PlayerListControl()
+ {
+ _entManager = IoCManager.Resolve();
+ _uiManager = IoCManager.Resolve();
+ _adminSystem = _entManager.System();
+ RobustXamlLoader.Load(this);
+ // Fill the Option data
+ PlayerListContainer.ItemPressed += PlayerListItemPressed;
+ PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
+ PlayerListContainer.GenerateItem += GenerateButton;
+ PlayerListContainer.NoItemSelected += PlayerListNoItemSelected;
+ PopulateList(_adminSystem.PlayerList);
+ FilterLineEdit.OnTextChanged += _ => FilterList();
+ _adminSystem.PlayerListChanged += PopulateList;
+ BackgroundPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = new Color(32, 32, 40) };
+ }
- public PlayerListControl()
- {
- _entManager = IoCManager.Resolve();
- _uiManager = IoCManager.Resolve();
- _adminSystem = _entManager.System();
- RobustXamlLoader.Load(this);
- // Fill the Option data
- PlayerListContainer.ItemPressed += PlayerListItemPressed;
- PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
- PlayerListContainer.GenerateItem += GenerateButton;
- PlayerListContainer.NoItemSelected += PlayerListNoItemSelected;
- PopulateList(_adminSystem.PlayerList);
- FilterLineEdit.OnTextChanged += _ => FilterList();
- _adminSystem.PlayerListChanged += PopulateList;
- BackgroundPanel.PanelOverride = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 40)};
- }
+ public IReadOnlyList PlayerInfo => _playerList;
- private void PlayerListNoItemSelected()
- {
- _selectedPlayer = null;
- OnSelectionChanged?.Invoke(null);
- }
+ public event Action? OnSelectionChanged;
- private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data)
- {
- if (args == null || data is not PlayerListData {Info: var selectedPlayer})
- return;
+ private void PlayerListNoItemSelected()
+ {
+ _selectedPlayer = null;
+ OnSelectionChanged?.Invoke(null);
+ }
- if (selectedPlayer == _selectedPlayer)
- return;
+ private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data)
+ {
+ if (args == null || data is not PlayerListData { Info: var selectedPlayer })
+ return;
- if (args.Event.Function != EngineKeyFunctions.UIClick)
- return;
+ if (selectedPlayer == _selectedPlayer)
+ return;
- OnSelectionChanged?.Invoke(selectedPlayer);
- _selectedPlayer = selectedPlayer;
+ if (args.Event.Function != EngineKeyFunctions.UIClick)
+ return;
- // update label text. Only required if there is some override (e.g. unread bwoink count).
- if (OverrideText != null && args.Button.Children.FirstOrDefault()?.Children?.FirstOrDefault() is Label label)
- label.Text = GetText(selectedPlayer);
- }
+ OnSelectionChanged?.Invoke(selectedPlayer);
+ _selectedPlayer = selectedPlayer;
- private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data)
- {
- if (args == null || data is not PlayerListData { Info: var selectedPlayer })
- return;
+ // update label text. Only required if there is some override (e.g. unread bwoink count).
+ if (OverrideText != null && args.Button.Children.FirstOrDefault()?.Children?.FirstOrDefault() is Label label)
+ label.Text = GetText(selectedPlayer);
+ }
- if (args.Function != EngineKeyFunctions.UIRightClick || selectedPlayer.NetEntity == null)
- return;
+ private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data)
+ {
+ if (args == null || data is not PlayerListData { Info: var selectedPlayer })
+ return;
- _uiManager.GetUIController().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
- args.Handle();
- }
+ if (args.Function != EngineKeyFunctions.UIRightClick || selectedPlayer.NetEntity == null)
+ return;
+
+ _uiManager.GetUIController().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
+ args.Handle();
+ }
+
+ public void StopFiltering()
+ {
+ FilterLineEdit.Text = string.Empty;
+ }
- public void StopFiltering()
+ private void FilterList()
+ {
+ _sortedPlayerList.Clear();
+ foreach (var info in _playerList)
{
- FilterLineEdit.Text = string.Empty;
+ var displayName = $"{info.CharacterName} ({info.Username})";
+ if (info.IdentityName != info.CharacterName)
+ displayName += $" [{info.IdentityName}]";
+ if (!string.IsNullOrEmpty(FilterLineEdit.Text)
+ && !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant()))
+ continue;
+ _sortedPlayerList.Add(info);
}
- private void FilterList()
- {
- _sortedPlayerList.Clear();
- foreach (var info in _playerList)
- {
- var displayName = $"{info.CharacterName} ({info.Username})";
- if (info.IdentityName != info.CharacterName)
- displayName += $" [{info.IdentityName}]";
- if (!string.IsNullOrEmpty(FilterLineEdit.Text)
- && !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant()))
- continue;
- _sortedPlayerList.Add(info);
- }
+ if (Comparison != null)
+ _sortedPlayerList.Sort((a, b) => Comparison(a, b));
- if (Comparison != null)
- _sortedPlayerList.Sort((a, b) => Comparison(a, b));
+ PlayerListContainer.PopulateList(_sortedPlayerList.Select(info => new PlayerListData(info)).ToList());
+ if (_selectedPlayer != null)
+ PlayerListContainer.Select(new PlayerListData(_selectedPlayer));
+ }
- PlayerListContainer.PopulateList(_sortedPlayerList.Select(info => new PlayerListData(info)).ToList());
- if (_selectedPlayer != null)
- PlayerListContainer.Select(new PlayerListData(_selectedPlayer));
- }
- public void PopulateList(IReadOnlyList? players = null)
- {
- players ??= _adminSystem.PlayerList;
+ public void PopulateList(IReadOnlyList? players = null)
+ {
+ // Maintain existing pin statuses
+ var pinnedPlayers = _playerList.Where(p => p.IsPinned).ToDictionary(p => p.SessionId);
- _playerList = players.ToList();
- if (_selectedPlayer != null && !_playerList.Contains(_selectedPlayer))
- _selectedPlayer = null;
+ players ??= _adminSystem.PlayerList;
- FilterList();
- }
+ _playerList = players.ToList();
- private string GetText(PlayerInfo info)
+ // Restore pin statuses
+ foreach (var player in _playerList)
{
- var text = $"{info.CharacterName} ({info.Username})";
- if (OverrideText != null)
- text = OverrideText.Invoke(info, text);
- return text;
+ if (pinnedPlayers.TryGetValue(player.SessionId, out var pinnedPlayer))
+ {
+ player.IsPinned = pinnedPlayer.IsPinned;
+ }
}
- private void GenerateButton(ListData data, ListContainerButton button)
- {
- if (data is not PlayerListData { Info: var info })
- return;
+ if (_selectedPlayer != null && !_playerList.Contains(_selectedPlayer))
+ _selectedPlayer = null;
- button.AddChild(new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- Children =
- {
- new Label
- {
- ClipText = true,
- Text = GetText(info)
- }
- }
- });
-
- button.AddStyleClass(ListContainer.StyleClassListContainerButton);
- }
+ FilterList();
+ }
+
+
+ private string GetText(PlayerInfo info)
+ {
+ var text = $"{info.CharacterName} ({info.Username})";
+ if (OverrideText != null)
+ text = OverrideText.Invoke(info, text);
+ return text;
}
- public record PlayerListData(PlayerInfo Info) : ListData;
+ private void GenerateButton(ListData data, ListContainerButton button)
+ {
+ if (data is not PlayerListData { Info: var info })
+ return;
+
+ var entry = new PlayerListEntry();
+ entry.Setup(info, OverrideText);
+ entry.OnPinStatusChanged += _ =>
+ {
+ FilterList();
+ };
+
+ button.AddChild(entry);
+ button.AddStyleClass(ListContainer.StyleClassListContainerButton);
+ }
}
+
+public record PlayerListData(PlayerInfo Info) : ListData;
diff --git a/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml b/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml
new file mode 100644
index 0000000000..af13ccc0e0
--- /dev/null
+++ b/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml.cs b/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml.cs
new file mode 100644
index 0000000000..cd6a56ea71
--- /dev/null
+++ b/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml.cs
@@ -0,0 +1,58 @@
+using Content.Client.Stylesheets;
+using Content.Shared.Administration;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Administration.UI.CustomControls;
+
+[GenerateTypedNameReferences]
+public sealed partial class PlayerListEntry : BoxContainer
+{
+ public PlayerListEntry()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ public event Action? OnPinStatusChanged;
+
+ public void Setup(PlayerInfo info, Func? overrideText)
+ {
+ Update(info, overrideText);
+ PlayerEntryPinButton.OnPressed += HandlePinButtonPressed(info);
+ }
+
+ private Action HandlePinButtonPressed(PlayerInfo info)
+ {
+ return args =>
+ {
+ info.IsPinned = !info.IsPinned;
+ UpdatePinButtonTexture(info.IsPinned);
+ OnPinStatusChanged?.Invoke(info);
+ };
+ }
+
+ private void Update(PlayerInfo info, Func? overrideText)
+ {
+ PlayerEntryLabel.Text = overrideText?.Invoke(info, $"{info.CharacterName} ({info.Username})") ??
+ $"{info.CharacterName} ({info.Username})";
+
+ UpdatePinButtonTexture(info.IsPinned);
+ }
+
+ private void UpdatePinButtonTexture(bool isPinned)
+ {
+ if (isPinned)
+ {
+ PlayerEntryPinButton?.RemoveStyleClass(StyleNano.StyleClassPinButtonUnpinned);
+ PlayerEntryPinButton?.AddStyleClass(StyleNano.StyleClassPinButtonPinned);
+ }
+ else
+ {
+ PlayerEntryPinButton?.RemoveStyleClass(StyleNano.StyleClassPinButtonPinned);
+ PlayerEntryPinButton?.AddStyleClass(StyleNano.StyleClassPinButtonUnpinned);
+ }
+ }
+}
diff --git a/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml b/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml
index f978138ca5..4a3c0ef7ac 100644
--- a/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml
+++ b/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml
@@ -1,10 +1,10 @@
-
+
-
+
-
+
diff --git a/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml b/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml
new file mode 100644
index 0000000000..8feec273b4
--- /dev/null
+++ b/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml.cs b/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml.cs
new file mode 100644
index 0000000000..824d9eb6c7
--- /dev/null
+++ b/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml.cs
@@ -0,0 +1,132 @@
+using Content.Client.Administration.Managers;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Administration;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Network;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Administration.UI.PlayerPanel;
+
+[GenerateTypedNameReferences]
+public sealed partial class PlayerPanel : FancyWindow
+{
+ private readonly IClientAdminManager _adminManager;
+
+ public event Action? OnUsernameCopy;
+ public event Action? OnOpenNotes;
+ public event Action? OnOpenBans;
+ public event Action? OnAhelp;
+ public event Action? OnKick;
+ public event Action? OnOpenBanPanel;
+ public event Action? OnWhitelistToggle;
+ public event Action? OnFreezeAndMuteToggle;
+ public event Action? OnFreeze;
+ public event Action? OnLogs;
+ public event Action? OnDelete;
+ public event Action? OnRejuvenate;
+
+ public NetUserId? TargetPlayer;
+ public string? TargetUsername;
+ private bool _isWhitelisted;
+
+ public PlayerPanel(IClientAdminManager adminManager)
+ {
+ RobustXamlLoader.Load(this);
+ _adminManager = adminManager;
+
+ UsernameCopyButton.OnPressed += _ => OnUsernameCopy?.Invoke(PlayerName.Text ?? "");
+ BanButton.OnPressed += _ => OnOpenBanPanel?.Invoke(TargetPlayer);
+ KickButton.OnPressed += _ => OnKick?.Invoke(TargetUsername);
+ NotesButton.OnPressed += _ => OnOpenNotes?.Invoke(TargetPlayer);
+ ShowBansButton.OnPressed += _ => OnOpenBans?.Invoke(TargetPlayer);
+ AhelpButton.OnPressed += _ => OnAhelp?.Invoke(TargetPlayer);
+ WhitelistToggle.OnPressed += _ =>
+ {
+ OnWhitelistToggle?.Invoke(TargetPlayer, _isWhitelisted);
+ SetWhitelisted(!_isWhitelisted);
+ };
+ FreezeButton.OnPressed += _ => OnFreeze?.Invoke();
+ FreezeAndMuteToggleButton.OnPressed += _ => OnFreezeAndMuteToggle?.Invoke();
+ LogsButton.OnPressed += _ => OnLogs?.Invoke();
+ DeleteButton.OnPressed += _ => OnDelete?.Invoke();
+ RejuvenateButton.OnPressed += _ => OnRejuvenate?.Invoke();
+ }
+
+ public void SetUsername(string player)
+ {
+ Title = Loc.GetString("player-panel-title", ("player", player));
+ PlayerName.Text = Loc.GetString("player-panel-username", ("player", player));
+ }
+
+ public void SetWhitelisted(bool? whitelisted)
+ {
+ if (whitelisted == null)
+ {
+ Whitelisted.Text = null;
+ WhitelistToggle.Visible = false;
+ }
+ else
+ {
+ Whitelisted.Text = Loc.GetString("player-panel-whitelisted");
+ WhitelistToggle.Text = whitelisted.Value.ToString();
+ WhitelistToggle.Visible = true;
+ _isWhitelisted = whitelisted.Value;
+ }
+ }
+
+ public void SetBans(int? totalBans, int? totalRoleBans)
+ {
+ // If one value exists then so should the other.
+ DebugTools.Assert(totalBans.HasValue && totalRoleBans.HasValue || totalBans == null && totalRoleBans == null);
+
+ Bans.Text = totalBans != null ? Loc.GetString("player-panel-bans", ("totalBans", totalBans)) : null;
+
+ RoleBans.Text = totalRoleBans != null ? Loc.GetString("player-panel-rolebans", ("totalRoleBans", totalRoleBans)) : null;
+ }
+
+ public void SetNotes(int? totalNotes)
+ {
+ Notes.Text = totalNotes != null ? Loc.GetString("player-panel-notes", ("totalNotes", totalNotes)) : null;
+ }
+
+ public void SetSharedConnections(int sharedConnections)
+ {
+ SharedConnections.Text = Loc.GetString("player-panel-shared-connections", ("sharedConnections", sharedConnections));
+ }
+
+ public void SetPlaytime(TimeSpan playtime)
+ {
+ Playtime.Text = Loc.GetString("player-panel-playtime",
+ ("days", playtime.Days),
+ ("hours", playtime.Hours % 24),
+ ("minutes", playtime.Minutes % (24 * 60)));
+ }
+
+ public void SetFrozen(bool canFreeze, bool frozen)
+ {
+ FreezeAndMuteToggleButton.Disabled = !canFreeze;
+ FreezeButton.Disabled = !canFreeze || frozen;
+
+ FreezeAndMuteToggleButton.Text = Loc.GetString(!frozen ? "player-panel-freeze-and-mute" : "player-panel-unfreeze");
+ }
+
+ public void SetAhelp(bool canAhelp)
+ {
+ AhelpButton.Disabled = !canAhelp;
+ }
+
+ public void SetButtons()
+ {
+ BanButton.Disabled = !_adminManager.CanCommand("banpanel");
+ KickButton.Disabled = !_adminManager.CanCommand("kick");
+ NotesButton.Disabled = !_adminManager.CanCommand("adminnotes");
+ ShowBansButton.Disabled = !_adminManager.CanCommand("banlist");
+ WhitelistToggle.Disabled =
+ !(_adminManager.CanCommand("addwhitelist") && _adminManager.CanCommand("removewhitelist"));
+ LogsButton.Disabled = !_adminManager.CanCommand("adminlogs");
+ RejuvenateButton.Disabled = !_adminManager.HasFlag(AdminFlags.Debug);
+ DeleteButton.Disabled = !_adminManager.HasFlag(AdminFlags.Debug);
+ }
+}
diff --git a/Content.Client/Administration/UI/PlayerPanel/PlayerPanelEui.cs b/Content.Client/Administration/UI/PlayerPanel/PlayerPanelEui.cs
new file mode 100644
index 0000000000..87ce756046
--- /dev/null
+++ b/Content.Client/Administration/UI/PlayerPanel/PlayerPanelEui.cs
@@ -0,0 +1,72 @@
+using Content.Client.Administration.Managers;
+using Content.Client.Eui;
+using Content.Shared.Administration;
+using Content.Shared.Eui;
+using JetBrains.Annotations;
+using Robust.Client.Console;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Administration.UI.PlayerPanel;
+
+[UsedImplicitly]
+public sealed class PlayerPanelEui : BaseEui
+{
+ [Dependency] private readonly IClientConsoleHost _console = default!;
+ [Dependency] private readonly IClientAdminManager _admin = default!;
+ [Dependency] private readonly IClipboardManager _clipboard = default!;
+
+ private PlayerPanel PlayerPanel { get; }
+
+ public PlayerPanelEui()
+ {
+ PlayerPanel = new PlayerPanel(_admin);
+
+ PlayerPanel.OnUsernameCopy += username => _clipboard.SetText(username);
+ PlayerPanel.OnOpenNotes += id => _console.ExecuteCommand($"adminnotes \"{id}\"");
+ // Kick command does not support GUIDs
+ PlayerPanel.OnKick += username => _console.ExecuteCommand($"kick \"{username}\"");
+ PlayerPanel.OnOpenBanPanel += id => _console.ExecuteCommand($"banpanel \"{id}\"");
+ PlayerPanel.OnOpenBans += id => _console.ExecuteCommand($"banlist \"{id}\"");
+ PlayerPanel.OnAhelp += id => _console.ExecuteCommand($"openahelp \"{id}\"");
+ PlayerPanel.OnWhitelistToggle += (id, whitelisted) =>
+ {
+ _console.ExecuteCommand(whitelisted ? $"whitelistremove \"{id}\"" : $"whitelistadd \"{id}\"");
+ };
+
+ PlayerPanel.OnFreezeAndMuteToggle += () => SendMessage(new PlayerPanelFreezeMessage(true));
+ PlayerPanel.OnFreeze += () => SendMessage(new PlayerPanelFreezeMessage());
+ PlayerPanel.OnLogs += () => SendMessage(new PlayerPanelLogsMessage());
+ PlayerPanel.OnRejuvenate += () => SendMessage(new PlayerPanelRejuvenationMessage());
+ PlayerPanel.OnDelete+= () => SendMessage(new PlayerPanelDeleteMessage());
+
+ PlayerPanel.OnClose += () => SendMessage(new CloseEuiMessage());
+ }
+
+ public override void Opened()
+ {
+ PlayerPanel.OpenCentered();
+ }
+
+ public override void Closed()
+ {
+ PlayerPanel.Close();
+ }
+
+ public override void HandleState(EuiStateBase state)
+ {
+ if (state is not PlayerPanelEuiState s)
+ return;
+
+ PlayerPanel.TargetPlayer = s.Guid;
+ PlayerPanel.TargetUsername = s.Username;
+ PlayerPanel.SetUsername(s.Username);
+ PlayerPanel.SetPlaytime(s.Playtime);
+ PlayerPanel.SetBans(s.TotalBans, s.TotalRoleBans);
+ PlayerPanel.SetNotes(s.TotalNotes);
+ PlayerPanel.SetWhitelisted(s.Whitelisted);
+ PlayerPanel.SetSharedConnections(s.SharedConnections);
+ PlayerPanel.SetFrozen(s.CanFreeze, s.Frozen);
+ PlayerPanel.SetAhelp(s.CanAhelp);
+ PlayerPanel.SetButtons();
+ }
+}
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
index 78eefa3462..4b50771b0f 100644
--- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
@@ -1,20 +1,21 @@
+using Content.Client.Administration.Managers;
using Content.Client.Station;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
+using Robust.Client.Console;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map.Components;
-using Robust.Shared.Timing;
namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
[GenerateTypedNameReferences]
public sealed partial class ObjectsTab : Control
{
+ [Dependency] private readonly IClientAdminManager _admin = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IClientConsoleHost _console = default!;
private readonly Color _altColor = Color.FromHex("#292B38");
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
@@ -50,10 +51,20 @@ public ObjectsTab()
RefreshListButton.OnPressed += _ => RefreshObjectList();
var defaultSelection = ObjectsTabSelection.Grids;
- ObjectTypeOptions.SelectId((int) defaultSelection);
+ ObjectTypeOptions.SelectId((int)defaultSelection);
RefreshObjectList(defaultSelection);
}
+ private void TeleportTo(NetEntity nent)
+ {
+ _console.ExecuteCommand($"tpto {nent}");
+ }
+
+ private void Delete(NetEntity nent)
+ {
+ _console.ExecuteCommand($"delete {nent}");
+ }
+
public void RefreshObjectList()
{
RefreshObjectList(_selections[ObjectTypeOptions.SelectedId]);
@@ -117,9 +128,9 @@ private void GenerateButton(ListData data, ListContainerButton button)
if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor })
return;
- var entry = new ObjectsTabEntry(info.Name,
- info.Entity,
- new StyleBoxFlat { BackgroundColor = backgroundColor });
+ var entry = new ObjectsTabEntry(_admin, info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor });
+ entry.OnTeleport += TeleportTo;
+ entry.OnDelete += Delete;
button.ToolTip = $"{info.Name}, {info.Entity}";
button.AddChild(entry);
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml
index 83c4cc5697..c561125a30 100644
--- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml
@@ -5,13 +5,25 @@
HorizontalExpand="True"
SeparationOverride="4">
+
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs
index aab06c6ccd..29774bb587 100644
--- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs
@@ -1,4 +1,5 @@
-using Robust.Client.AutoGenerated;
+using Content.Client.Administration.Managers;
+using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -10,12 +11,22 @@ public sealed partial class ObjectsTabEntry : PanelContainer
{
public NetEntity AssocEntity;
- public ObjectsTabEntry(string name, NetEntity nent, StyleBox styleBox)
+ public Action? OnTeleport;
+ public Action? OnDelete;
+
+ public ObjectsTabEntry(IClientAdminManager manager, string name, NetEntity nent, StyleBox styleBox)
{
RobustXamlLoader.Load(this);
+
AssocEntity = nent;
EIDLabel.Text = nent.ToString();
NameLabel.Text = name;
BackgroundColorPanel.PanelOverride = styleBox;
+
+ TeleportButton.Disabled = !manager.CanCommand("tpto");
+ DeleteButton.Disabled = !manager.CanCommand("delete");
+
+ TeleportButton.OnPressed += _ => OnTeleport?.Invoke(nent);
+ DeleteButton.OnPressed += _ => OnDelete?.Invoke(nent);
}
}
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml
index 71a1f5c7bc..2cc7d76180 100644
--- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml
@@ -5,17 +5,23 @@
HorizontalExpand="True"
SeparationOverride="4">
+
+
diff --git a/Content.Client/Alerts/ClientAlertsSystem.cs b/Content.Client/Alerts/ClientAlertsSystem.cs
index 359c8957f9..525ef1f018 100644
--- a/Content.Client/Alerts/ClientAlertsSystem.cs
+++ b/Content.Client/Alerts/ClientAlertsSystem.cs
@@ -93,6 +93,6 @@ private void OnPlayerDetached(EntityUid uid, AlertsComponent component, LocalPla
public void AlertClicked(ProtoId alertType)
{
- RaiseNetworkEvent(new ClickAlertEvent(alertType));
+ RaisePredictiveEvent(new ClickAlertEvent(alertType));
}
}
diff --git a/Content.Client/Ame/UI/AmeControllerBoundUserInterface.cs b/Content.Client/Ame/UI/AmeControllerBoundUserInterface.cs
index e84cf5d34d..3d65f75189 100644
--- a/Content.Client/Ame/UI/AmeControllerBoundUserInterface.cs
+++ b/Content.Client/Ame/UI/AmeControllerBoundUserInterface.cs
@@ -1,5 +1,6 @@
using Content.Shared.Ame.Components;
using JetBrains.Annotations;
+using Robust.Client.UserInterface;
namespace Content.Client.Ame.UI
{
@@ -16,9 +17,8 @@ protected override void Open()
{
base.Open();
- _window = new AmeWindow(this);
- _window.OnClose += Close;
- _window.OpenCentered();
+ _window = this.CreateWindow();
+ _window.OnAmeButton += ButtonPressed;
}
///
@@ -40,15 +40,5 @@ public void ButtonPressed(UiButton button)
{
SendMessage(new UiButtonPressedMessage(button));
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- {
- _window?.Dispose();
- }
- }
}
}
diff --git a/Content.Client/Ame/UI/AmeWindow.xaml.cs b/Content.Client/Ame/UI/AmeWindow.xaml.cs
index 8b91ec5966..d6d580bcda 100644
--- a/Content.Client/Ame/UI/AmeWindow.xaml.cs
+++ b/Content.Client/Ame/UI/AmeWindow.xaml.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using Content.Client.UserInterface;
using Content.Shared.Ame.Components;
using Robust.Client.AutoGenerated;
@@ -9,15 +10,17 @@ namespace Content.Client.Ame.UI
[GenerateTypedNameReferences]
public sealed partial class AmeWindow : DefaultWindow
{
- public AmeWindow(AmeControllerBoundUserInterface ui)
+ public event Action? OnAmeButton;
+
+ public AmeWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
- EjectButton.OnPressed += _ => ui.ButtonPressed(UiButton.Eject);
- ToggleInjection.OnPressed += _ => ui.ButtonPressed(UiButton.ToggleInjection);
- IncreaseFuelButton.OnPressed += _ => ui.ButtonPressed(UiButton.IncreaseFuel);
- DecreaseFuelButton.OnPressed += _ => ui.ButtonPressed(UiButton.DecreaseFuel);
+ EjectButton.OnPressed += _ => OnAmeButton?.Invoke(UiButton.Eject);
+ ToggleInjection.OnPressed += _ => OnAmeButton?.Invoke(UiButton.ToggleInjection);
+ IncreaseFuelButton.OnPressed += _ => OnAmeButton?.Invoke(UiButton.IncreaseFuel);
+ DecreaseFuelButton.OnPressed += _ => OnAmeButton?.Invoke(UiButton.DecreaseFuel);
}
///
@@ -29,7 +32,7 @@ public void UpdateState(BoundUserInterfaceState state)
var castState = (AmeControllerBoundUserInterfaceState) state;
// Disable all buttons if not powered
- if (Contents.Children != null)
+ if (Contents.Children.Any())
{
ButtonHelpers.SetButtonDisabledRecursive(Contents, !castState.HasPower);
EjectButton.Disabled = false;
@@ -65,8 +68,8 @@ public void UpdateState(BoundUserInterfaceState state)
CoreCount.Text = $"{castState.CoreCount}";
InjectionAmount.Text = $"{castState.InjectionAmount}";
// format power statistics to pretty numbers
- CurrentPowerSupply.Text = $"{castState.CurrentPowerSupply.ToString("N1")}";
- TargetedPowerSupply.Text = $"{castState.TargetedPowerSupply.ToString("N1")}";
+ CurrentPowerSupply.Text = $"{castState.CurrentPowerSupply:N1}";
+ TargetedPowerSupply.Text = $"{castState.TargetedPowerSupply:N1}";
}
}
}
diff --git a/Content.Client/Anomaly/Ui/AnomalyGeneratorBoundUserInterface.cs b/Content.Client/Anomaly/Ui/AnomalyGeneratorBoundUserInterface.cs
index 5764d0a097..5d1985485c 100644
--- a/Content.Client/Anomaly/Ui/AnomalyGeneratorBoundUserInterface.cs
+++ b/Content.Client/Anomaly/Ui/AnomalyGeneratorBoundUserInterface.cs
@@ -2,6 +2,7 @@
using Content.Shared.Gravity;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Anomaly.Ui;
@@ -18,10 +19,8 @@ protected override void Open()
{
base.Open();
- _window = new(Owner);
-
- _window.OpenCentered();
- _window.OnClose += Close;
+ _window = this.CreateWindow();
+ _window.SetEntity(Owner);
_window.OnGenerateButtonPressed += () =>
{
@@ -37,18 +36,5 @@ protected override void UpdateState(BoundUserInterfaceState state)
return;
_window?.UpdateState(msg);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing) return;
-
- _window?.Dispose();
- }
-
- public void SetPowerSwitch(bool on)
- {
- SendMessage(new SharedGravityGeneratorComponent.SwitchGeneratorMessage(on));
- }
}
diff --git a/Content.Client/Anomaly/Ui/AnomalyGeneratorWindow.xaml.cs b/Content.Client/Anomaly/Ui/AnomalyGeneratorWindow.xaml.cs
index 08438e2a1b..82d41192dd 100644
--- a/Content.Client/Anomaly/Ui/AnomalyGeneratorWindow.xaml.cs
+++ b/Content.Client/Anomaly/Ui/AnomalyGeneratorWindow.xaml.cs
@@ -18,17 +18,21 @@ public sealed partial class AnomalyGeneratorWindow : FancyWindow
public Action? OnGenerateButtonPressed;
- public AnomalyGeneratorWindow(EntityUid gen)
+ public AnomalyGeneratorWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
- EntityView.SetEntity(gen);
EntityView.SpriteOffset = false;
GenerateButton.OnPressed += _ => OnGenerateButtonPressed?.Invoke();
}
+ public void SetEntity(EntityUid uid)
+ {
+ EntityView.SetEntity(uid);
+ }
+
public void UpdateState(AnomalyGeneratorUserInterfaceState state)
{
_cooldownEnd = state.CooldownEndTime;
diff --git a/Content.Client/Arcade/BlockGameMenu.cs b/Content.Client/Arcade/BlockGameMenu.cs
index eeda2a3102..ad360c5439 100644
--- a/Content.Client/Arcade/BlockGameMenu.cs
+++ b/Content.Client/Arcade/BlockGameMenu.cs
@@ -28,8 +28,6 @@ public sealed class BlockGameMenu : DefaultWindow
private static readonly Vector2 BlockSize = new(15, 15);
- private readonly BlockGameBoundUserInterface _owner;
-
private readonly PanelContainer _mainPanel;
private readonly BoxContainer _gameRootContainer;
@@ -58,10 +56,11 @@ public sealed class BlockGameMenu : DefaultWindow
private bool _isPlayer = false;
private bool _gameOver = false;
- public BlockGameMenu(BlockGameBoundUserInterface owner)
+ public event Action? OnAction;
+
+ public BlockGameMenu()
{
Title = Loc.GetString("blockgame-menu-title");
- _owner = owner;
MinSize = SetSize = new Vector2(410, 490);
@@ -176,7 +175,7 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
};
_newGameButton.OnPressed += (e) =>
{
- _owner.SendAction(BlockGamePlayerAction.NewGame);
+ OnAction?.Invoke(BlockGamePlayerAction.NewGame);
};
pauseMenuContainer.AddChild(_newGameButton);
pauseMenuContainer.AddChild(new Control { MinSize = new Vector2(1, 10) });
@@ -186,7 +185,10 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
Text = Loc.GetString("blockgame-menu-button-scoreboard"),
TextAlign = Label.AlignMode.Center
};
- _scoreBoardButton.OnPressed += (e) => _owner.SendAction(BlockGamePlayerAction.ShowHighscores);
+ _scoreBoardButton.OnPressed += (e) =>
+ {
+ OnAction?.Invoke(BlockGamePlayerAction.ShowHighscores);
+ };
pauseMenuContainer.AddChild(_scoreBoardButton);
_unpauseButtonMargin = new Control { MinSize = new Vector2(1, 10), Visible = false };
pauseMenuContainer.AddChild(_unpauseButtonMargin);
@@ -199,7 +201,7 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
};
_unpauseButton.OnPressed += (e) =>
{
- _owner.SendAction(BlockGamePlayerAction.Unpause);
+ OnAction?.Invoke(BlockGamePlayerAction.Unpause);
};
pauseMenuContainer.AddChild(_unpauseButton);
@@ -257,7 +259,7 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
};
_finalNewGameButton.OnPressed += (e) =>
{
- _owner.SendAction(BlockGamePlayerAction.NewGame);
+ OnAction?.Invoke(BlockGamePlayerAction.NewGame);
};
gameOverMenuContainer.AddChild(_finalNewGameButton);
@@ -327,7 +329,10 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
Text = Loc.GetString("blockgame-menu-button-back"),
TextAlign = Label.AlignMode.Center
};
- _highscoreBackButton.OnPressed += (e) => _owner.SendAction(BlockGamePlayerAction.Pause);
+ _highscoreBackButton.OnPressed += (e) =>
+ {
+ OnAction?.Invoke(BlockGamePlayerAction.Pause);
+ };
menuContainer.AddChild(_highscoreBackButton);
menuInnerPanel.AddChild(menuContainer);
@@ -375,7 +380,7 @@ private Control SetupGameGrid(Texture panelTex)
{
PanelOverride = back,
HorizontalExpand = true,
- SizeFlagsStretchRatio = 60
+ SizeFlagsStretchRatio = 34.25f
};
var backgroundPanel = new PanelContainer
{
@@ -473,7 +478,7 @@ protected override void KeyboardFocusExited()
private void TryPause()
{
- _owner.SendAction(BlockGamePlayerAction.Pause);
+ OnAction?.Invoke(BlockGamePlayerAction.Pause);
}
public void SetStarted()
@@ -576,19 +581,19 @@ protected override void KeyBindDown(GUIBoundKeyEventArgs args)
return;
else if (args.Function == ContentKeyFunctions.ArcadeLeft)
- _owner.SendAction(BlockGamePlayerAction.StartLeft);
+ OnAction?.Invoke(BlockGamePlayerAction.StartLeft);
else if (args.Function == ContentKeyFunctions.ArcadeRight)
- _owner.SendAction(BlockGamePlayerAction.StartRight);
+ OnAction?.Invoke(BlockGamePlayerAction.StartRight);
else if (args.Function == ContentKeyFunctions.ArcadeUp)
- _owner.SendAction(BlockGamePlayerAction.Rotate);
+ OnAction?.Invoke(BlockGamePlayerAction.Rotate);
else if (args.Function == ContentKeyFunctions.Arcade3)
- _owner.SendAction(BlockGamePlayerAction.CounterRotate);
+ OnAction?.Invoke(BlockGamePlayerAction.CounterRotate);
else if (args.Function == ContentKeyFunctions.ArcadeDown)
- _owner.SendAction(BlockGamePlayerAction.SoftdropStart);
+ OnAction?.Invoke(BlockGamePlayerAction.SoftdropStart);
else if (args.Function == ContentKeyFunctions.Arcade2)
- _owner.SendAction(BlockGamePlayerAction.Hold);
+ OnAction?.Invoke(BlockGamePlayerAction.Hold);
else if (args.Function == ContentKeyFunctions.Arcade1)
- _owner.SendAction(BlockGamePlayerAction.Harddrop);
+ OnAction?.Invoke(BlockGamePlayerAction.Harddrop);
}
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
@@ -599,11 +604,11 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args)
return;
else if (args.Function == ContentKeyFunctions.ArcadeLeft)
- _owner.SendAction(BlockGamePlayerAction.EndLeft);
+ OnAction?.Invoke(BlockGamePlayerAction.EndLeft);
else if (args.Function == ContentKeyFunctions.ArcadeRight)
- _owner.SendAction(BlockGamePlayerAction.EndRight);
+ OnAction?.Invoke(BlockGamePlayerAction.EndRight);
else if (args.Function == ContentKeyFunctions.ArcadeDown)
- _owner.SendAction(BlockGamePlayerAction.SoftdropEnd);
+ OnAction?.Invoke(BlockGamePlayerAction.SoftdropEnd);
}
public void UpdateNextBlock(BlockGameBlock[] blocks)
diff --git a/Content.Client/Arcade/SpaceVillainArcadeMenu.cs b/Content.Client/Arcade/SpaceVillainArcadeMenu.cs
index e5542a5848..1ee4c26818 100644
--- a/Content.Client/Arcade/SpaceVillainArcadeMenu.cs
+++ b/Content.Client/Arcade/SpaceVillainArcadeMenu.cs
@@ -8,8 +8,6 @@ namespace Content.Client.Arcade
{
public sealed class SpaceVillainArcadeMenu : DefaultWindow
{
- public SpaceVillainArcadeBoundUserInterface Owner { get; set; }
-
private readonly Label _enemyNameLabel;
private readonly Label _playerInfoLabel;
private readonly Label _enemyInfoLabel;
@@ -17,11 +15,13 @@ public sealed class SpaceVillainArcadeMenu : DefaultWindow
private readonly Label _enemyActionLabel;
private readonly Button[] _gameButtons = new Button[3]; //used to disable/enable all game buttons
- public SpaceVillainArcadeMenu(SpaceVillainArcadeBoundUserInterface owner)
+
+ public event Action? OnPlayerAction;
+
+ public SpaceVillainArcadeMenu()
{
MinSize = SetSize = new Vector2(300, 225);
Title = Loc.GetString("spacevillain-menu-title");
- Owner = owner;
var grid = new GridContainer { Columns = 1 };
@@ -47,32 +47,43 @@ public SpaceVillainArcadeMenu(SpaceVillainArcadeBoundUserInterface owner)
grid.AddChild(_enemyActionLabel);
var buttonGrid = new GridContainer { Columns = 3 };
- _gameButtons[0] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Attack)
+ _gameButtons[0] = new Button()
{
Text = Loc.GetString("spacevillain-menu-button-attack")
};
+
+ _gameButtons[0].OnPressed +=
+ _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Attack);
buttonGrid.AddChild(_gameButtons[0]);
- _gameButtons[1] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Heal)
+ _gameButtons[1] = new Button()
{
Text = Loc.GetString("spacevillain-menu-button-heal")
};
+
+ _gameButtons[1].OnPressed +=
+ _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Heal);
buttonGrid.AddChild(_gameButtons[1]);
- _gameButtons[2] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Recharge)
+ _gameButtons[2] = new Button()
{
Text = Loc.GetString("spacevillain-menu-button-recharge")
};
+
+ _gameButtons[2].OnPressed +=
+ _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Recharge);
buttonGrid.AddChild(_gameButtons[2]);
centerContainer = new CenterContainer();
centerContainer.AddChild(buttonGrid);
grid.AddChild(centerContainer);
- var newGame = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.NewGame)
+ var newGame = new Button()
{
Text = Loc.GetString("spacevillain-menu-button-new-game")
};
+
+ newGame.OnPressed += _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.NewGame);
grid.AddChild(newGame);
Contents.AddChild(grid);
@@ -99,23 +110,5 @@ public void UpdateInfo(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeDataU
_playerActionLabel.Text = message.PlayerActionMessage;
_enemyActionLabel.Text = message.EnemyActionMessage;
}
-
- private sealed class ActionButton : Button
- {
- private readonly SpaceVillainArcadeBoundUserInterface _owner;
- private readonly SharedSpaceVillainArcadeComponent.PlayerAction _playerAction;
-
- public ActionButton(SpaceVillainArcadeBoundUserInterface owner, SharedSpaceVillainArcadeComponent.PlayerAction playerAction)
- {
- _owner = owner;
- _playerAction = playerAction;
- OnPressed += Clicked;
- }
-
- private void Clicked(ButtonEventArgs e)
- {
- _owner.SendAction(_playerAction);
- }
- }
}
}
diff --git a/Content.Client/Arcade/UI/BlockGameBoundUserInterface.cs b/Content.Client/Arcade/UI/BlockGameBoundUserInterface.cs
index 1a3422dec0..4f08e6bd0a 100644
--- a/Content.Client/Arcade/UI/BlockGameBoundUserInterface.cs
+++ b/Content.Client/Arcade/UI/BlockGameBoundUserInterface.cs
@@ -1,5 +1,6 @@
using Content.Shared.Arcade;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Arcade.UI;
@@ -15,9 +16,8 @@ protected override void Open()
{
base.Open();
- _menu = new BlockGameMenu(this);
- _menu.OnClose += Close;
- _menu.OpenCentered();
+ _menu = this.CreateWindow();
+ _menu.OnAction += SendAction;
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
diff --git a/Content.Client/Arcade/UI/SpaceVillainArcadeBoundUserInterface.cs b/Content.Client/Arcade/UI/SpaceVillainArcadeBoundUserInterface.cs
index 40bbe8b2d8..8fff406e86 100644
--- a/Content.Client/Arcade/UI/SpaceVillainArcadeBoundUserInterface.cs
+++ b/Content.Client/Arcade/UI/SpaceVillainArcadeBoundUserInterface.cs
@@ -1,4 +1,5 @@
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent;
@@ -9,8 +10,6 @@ public sealed class SpaceVillainArcadeBoundUserInterface : BoundUserInterface
{
[ViewVariables] private SpaceVillainArcadeMenu? _menu;
- //public SharedSpaceVillainArcadeComponent SpaceVillainArcade;
-
public SpaceVillainArcadeBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
SendAction(PlayerAction.RequestData);
@@ -25,10 +24,8 @@ protected override void Open()
{
base.Open();
- _menu = new SpaceVillainArcadeMenu(this);
-
- _menu.OnClose += Close;
- _menu.OpenCentered();
+ _menu = this.CreateWindow();
+ _menu.OnPlayerAction += SendAction;
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
@@ -36,12 +33,4 @@ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
if (message is SpaceVillainArcadeDataUpdateMessage msg)
_menu?.UpdateInfo(msg);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- _menu?.Dispose();
- }
}
diff --git a/Content.Client/Atmos/EntitySystems/GasMinerSystem.cs b/Content.Client/Atmos/EntitySystems/GasMinerSystem.cs
new file mode 100644
index 0000000000..aec138eed8
--- /dev/null
+++ b/Content.Client/Atmos/EntitySystems/GasMinerSystem.cs
@@ -0,0 +1,10 @@
+using Content.Shared.Atmos.EntitySystems;
+using JetBrains.Annotations;
+
+namespace Content.Client.Atmos.EntitySystems;
+
+[UsedImplicitly]
+public sealed class GasMinerSystem : SharedGasMinerSystem
+{
+
+}
diff --git a/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs b/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs
index 8f3b507c80..2ae1518835 100644
--- a/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs
+++ b/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs
@@ -2,6 +2,7 @@
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -20,16 +21,9 @@ protected override void Open()
{
base.Open();
- _window = new AirAlarmWindow(this);
+ _window = this.CreateWindow();
+ _window.SetEntity(Owner);
- if (State != null)
- {
- UpdateState(State);
- }
-
- _window.OpenCentered();
-
- _window.OnClose += Close;
_window.AtmosDeviceDataChanged += OnDeviceDataChanged;
_window.AtmosDeviceDataCopied += OnDeviceDataCopied;
_window.AtmosAlarmThresholdChanged += OnThresholdChanged;
diff --git a/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs b/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs
index 43be67c9d6..eeec11c766 100644
--- a/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs
@@ -47,7 +47,7 @@ public sealed partial class AirAlarmWindow : FancyWindow
private CheckBox _autoMode => AutoModeCheckBox;
- public AirAlarmWindow(BoundUserInterface owner)
+ public AirAlarmWindow()
{
RobustXamlLoader.Load(this);
@@ -95,8 +95,11 @@ public AirAlarmWindow(BoundUserInterface owner)
_sensors.Clear();
ResyncAllRequested!.Invoke();
};
+ }
- EntityView.SetEntity(owner.Owner);
+ public void SetEntity(EntityUid uid)
+ {
+ EntityView.SetEntity(uid);
}
public void UpdateState(AirAlarmUIState state)
diff --git a/Content.Client/Atmos/UI/GasCanisterBoundUserInterface.cs b/Content.Client/Atmos/UI/GasCanisterBoundUserInterface.cs
index a5e316a8de..7bf9b396d5 100644
--- a/Content.Client/Atmos/UI/GasCanisterBoundUserInterface.cs
+++ b/Content.Client/Atmos/UI/GasCanisterBoundUserInterface.cs
@@ -1,6 +1,7 @@
using Content.Shared.Atmos.Piping.Binary.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Atmos.UI
{
@@ -21,14 +22,8 @@ protected override void Open()
{
base.Open();
- _window = new GasCanisterWindow();
+ _window = this.CreateWindow();
- if (State != null)
- UpdateState(State);
-
- _window.OpenCentered();
-
- _window.OnClose += Close;
_window.ReleaseValveCloseButtonPressed += OnReleaseValveClosePressed;
_window.ReleaseValveOpenButtonPressed += OnReleaseValveOpenPressed;
_window.ReleasePressureSet += OnReleasePressureSet;
diff --git a/Content.Client/Atmos/UI/GasFilterBoundUserInterface.cs b/Content.Client/Atmos/UI/GasFilterBoundUserInterface.cs
index 1904e2b340..2b8020924c 100644
--- a/Content.Client/Atmos/UI/GasFilterBoundUserInterface.cs
+++ b/Content.Client/Atmos/UI/GasFilterBoundUserInterface.cs
@@ -3,6 +3,7 @@
using Content.Shared.Atmos.Piping.Trinary.Components;
using Content.Shared.Localizations;
using JetBrains.Annotations;
+using Robust.Client.UserInterface;
namespace Content.Client.Atmos.UI
{
@@ -28,14 +29,8 @@ protected override void Open()
var atmosSystem = EntMan.System();
- _window = new GasFilterWindow(atmosSystem.Gases);
-
- if (State != null)
- UpdateState(State);
-
- _window.OpenCentered();
-
- _window.OnClose += Close;
+ _window = this.CreateWindow();
+ _window.PopulateGasList(atmosSystem.Gases);
_window.ToggleStatusButtonPressed += OnToggleStatusButtonPressed;
_window.FilterTransferRateChanged += OnFilterTransferRatePressed;
diff --git a/Content.Client/Atmos/UI/GasFilterWindow.xaml.cs b/Content.Client/Atmos/UI/GasFilterWindow.xaml.cs
index 28766c688a..62748b5259 100644
--- a/Content.Client/Atmos/UI/GasFilterWindow.xaml.cs
+++ b/Content.Client/Atmos/UI/GasFilterWindow.xaml.cs
@@ -26,10 +26,9 @@ public sealed partial class GasFilterWindow : DefaultWindow
public event Action? FilterTransferRateChanged;
public event Action? SelectGasPressed;
- public GasFilterWindow(IEnumerable gases)
+ public GasFilterWindow()
{
RobustXamlLoader.Load(this);
- PopulateGasList(gases);
ToggleStatusButton.OnPressed += _ => SetFilterStatus(!FilterStatus);
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
@@ -73,7 +72,7 @@ public void SetGasFiltered(string? id, string name)
SelectGasButton.Disabled = true;
}
- private void PopulateGasList(IEnumerable gases)
+ public void PopulateGasList(IEnumerable gases)
{
GasList.Add(new ItemList.Item(GasList)
{
@@ -81,7 +80,7 @@ private void PopulateGasList(IEnumerable gases)
Text = Loc.GetString("comp-gas-filter-ui-filter-gas-none")
});
- foreach (GasPrototype gas in gases)
+ foreach (var gas in gases)
{
var gasName = Loc.GetString(gas.Name);
GasList.Add(GetGasItem(gas.ID, gasName, GasList));
diff --git a/Content.Client/Atmos/UI/GasMixerBoundUserInteface.cs b/Content.Client/Atmos/UI/GasMixerBoundUserInteface.cs
index 709c06517c..392fbf1cd9 100644
--- a/Content.Client/Atmos/UI/GasMixerBoundUserInteface.cs
+++ b/Content.Client/Atmos/UI/GasMixerBoundUserInteface.cs
@@ -2,7 +2,7 @@
using Content.Shared.Atmos.Piping.Trinary.Components;
using Content.Shared.Localizations;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Atmos.UI
{
@@ -26,14 +26,7 @@ protected override void Open()
{
base.Open();
- _window = new GasMixerWindow();
-
- if (State != null)
- UpdateState(State);
-
- _window.OpenCentered();
-
- _window.OnClose += Close;
+ _window = this.CreateWindow();
_window.ToggleStatusButtonPressed += OnToggleStatusButtonPressed;
_window.MixerOutputPressureChanged += OnMixerOutputPressurePressed;
@@ -83,12 +76,5 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window.SetOutputPressure(cast.OutputPressure);
_window.SetNodePercentages(cast.NodeOne);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing) return;
- _window?.Dispose();
- }
}
}
diff --git a/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs b/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs
index 6eba2e0d21..220fdbe875 100644
--- a/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs
+++ b/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs
@@ -3,6 +3,7 @@
using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Atmos.UI
{
@@ -26,14 +27,7 @@ protected override void Open()
{
base.Open();
- _window = new GasPressurePumpWindow();
-
- if (State != null)
- UpdateState(State);
-
- _window.OpenCentered();
-
- _window.OnClose += Close;
+ _window = this.CreateWindow();
_window.ToggleStatusButtonPressed += OnToggleStatusButtonPressed;
_window.PumpOutputPressureChanged += OnPumpOutputPressurePressed;
@@ -67,12 +61,5 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window.SetPumpStatus(cast.Enabled);
_window.SetOutputPressure(cast.OutputPressure);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing) return;
- _window?.Dispose();
- }
}
}
diff --git a/Content.Client/Atmos/UI/GasThermomachineBoundUserInterface.cs b/Content.Client/Atmos/UI/GasThermomachineBoundUserInterface.cs
index 1664c8b9d7..d62be8f4bb 100644
--- a/Content.Client/Atmos/UI/GasThermomachineBoundUserInterface.cs
+++ b/Content.Client/Atmos/UI/GasThermomachineBoundUserInterface.cs
@@ -2,6 +2,7 @@
using Content.Shared.Atmos.Piping.Unary.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Atmos.UI
{
@@ -31,14 +32,7 @@ protected override void Open()
{
base.Open();
- _window = new GasThermomachineWindow();
-
- if (State != null)
- UpdateState(State);
-
- _window.OpenCentered();
-
- _window.OnClose += Close;
+ _window = this.CreateWindow();
_window.ToggleStatusButton.OnPressed += _ => OnToggleStatusButtonPressed();
_window.TemperatureSpinbox.OnValueChanged += _ => OnTemperatureChanged(_window.TemperatureSpinbox.Value);
@@ -91,12 +85,5 @@ protected override void UpdateState(BoundUserInterfaceState state)
true => Loc.GetString("comp-gas-thermomachine-ui-title-heater")
};
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing) return;
- _window?.Dispose();
- }
}
}
diff --git a/Content.Client/Atmos/UI/GasVolumePumpBoundUserInterface.cs b/Content.Client/Atmos/UI/GasVolumePumpBoundUserInterface.cs
index 1b39306181..642f34c2f9 100644
--- a/Content.Client/Atmos/UI/GasVolumePumpBoundUserInterface.cs
+++ b/Content.Client/Atmos/UI/GasVolumePumpBoundUserInterface.cs
@@ -3,6 +3,7 @@
using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Atmos.UI
{
@@ -26,14 +27,7 @@ protected override void Open()
{
base.Open();
- _window = new GasVolumePumpWindow();
-
- if (State != null)
- UpdateState(State);
-
- _window.OpenCentered();
-
- _window.OnClose += Close;
+ _window = this.CreateWindow();
_window.ToggleStatusButtonPressed += OnToggleStatusButtonPressed;
_window.PumpTransferRateChanged += OnPumpTransferRatePressed;
@@ -64,16 +58,9 @@ protected override void UpdateState(BoundUserInterfaceState state)
if (_window == null || state is not GasVolumePumpBoundUserInterfaceState cast)
return;
- _window.Title = (cast.PumpLabel);
+ _window.Title = cast.PumpLabel;
_window.SetPumpStatus(cast.Enabled);
_window.SetTransferRate(cast.TransferRate);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing) return;
- _window?.Dispose();
- }
}
}
diff --git a/Content.Client/Atmos/UI/SpaceHeaterBoundUserInterface.cs b/Content.Client/Atmos/UI/SpaceHeaterBoundUserInterface.cs
index 4d8d1191e9..e70426575d 100644
--- a/Content.Client/Atmos/UI/SpaceHeaterBoundUserInterface.cs
+++ b/Content.Client/Atmos/UI/SpaceHeaterBoundUserInterface.cs
@@ -1,5 +1,6 @@
using Content.Shared.Atmos.Piping.Portable.Components;
using JetBrains.Annotations;
+using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.Atmos.UI;
@@ -21,14 +22,7 @@ protected override void Open()
{
base.Open();
- _window = new SpaceHeaterWindow();
-
- if (State != null)
- UpdateState(State);
-
- _window.OpenCentered();
-
- _window.OnClose += Close;
+ _window = this.CreateWindow();
_window.ToggleStatusButton.OnPressed += _ => OnToggleStatusButtonPressed();
_window.IncreaseTempRange.OnPressed += _ => OnTemperatureRangeChanged(_window.TemperatureChangeDelta);
diff --git a/Content.Client/Audio/AudioUIController.cs b/Content.Client/Audio/AudioUIController.cs
index ef903672fd..16e1edd252 100644
--- a/Content.Client/Audio/AudioUIController.cs
+++ b/Content.Client/Audio/AudioUIController.cs
@@ -54,7 +54,7 @@ private void SetClickSound(string value)
{
if (!string.IsNullOrEmpty(value))
{
- var resource = _cache.GetResource(value);
+ var resource = GetSoundOrFallback(value, CCVars.UIClickSound.DefaultValue);
var source =
_audioManager.CreateAudioSource(resource);
@@ -77,7 +77,7 @@ private void SetHoverSound(string value)
{
if (!string.IsNullOrEmpty(value))
{
- var hoverResource = _cache.GetResource(value);
+ var hoverResource = GetSoundOrFallback(value, CCVars.UIHoverSound.DefaultValue);
var hoverSource =
_audioManager.CreateAudioSource(hoverResource);
@@ -95,4 +95,12 @@ private void SetHoverSound(string value)
UIManager.SetHoverSound(null);
}
}
+
+ private AudioResource GetSoundOrFallback(string path, string fallback)
+ {
+ if (!_cache.TryGetResource(path, out AudioResource? resource))
+ return _cache.GetResource(fallback);
+
+ return resource;
+ }
}
diff --git a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
index 84b787a4ec..d60c978ccf 100644
--- a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
+++ b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
@@ -4,6 +4,7 @@
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Random;
+using Content.Shared.Random.Rules;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
diff --git a/Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs b/Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs
index 60fe339069..865dfc478d 100644
--- a/Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs
+++ b/Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs
@@ -1,8 +1,7 @@
using Content.Shared.Audio.Jukebox;
using Robust.Client.Audio;
-using Robust.Client.Player;
+using Robust.Client.UserInterface;
using Robust.Shared.Audio.Components;
-using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Audio.Jukebox;
@@ -23,9 +22,7 @@ protected override void Open()
{
base.Open();
- _menu = new JukeboxMenu();
- _menu.OnClose += Close;
- _menu.OpenCentered();
+ _menu = this.CreateWindow();
_menu.OnPlayPressed += args =>
{
@@ -100,19 +97,5 @@ public void SetTime(float time)
SendMessage(new JukeboxSetTimeMessage(sentTime));
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
-
- if (_menu == null)
- return;
-
- _menu.OnClose -= Close;
- _menu.Dispose();
- _menu = null;
- }
}
diff --git a/Content.Client/Bed/Cryostorage/CryostorageBoundUserInterface.cs b/Content.Client/Bed/Cryostorage/CryostorageBoundUserInterface.cs
index ffab162548..09f3cec8fb 100644
--- a/Content.Client/Bed/Cryostorage/CryostorageBoundUserInterface.cs
+++ b/Content.Client/Bed/Cryostorage/CryostorageBoundUserInterface.cs
@@ -1,5 +1,6 @@
using Content.Shared.Bed.Cryostorage;
using JetBrains.Annotations;
+using Robust.Client.UserInterface;
namespace Content.Client.Bed.Cryostorage;
@@ -17,9 +18,7 @@ protected override void Open()
{
base.Open();
- _menu = new();
-
- _menu.OnClose += Close;
+ _menu = this.CreateWindow();
_menu.SlotRemoveButtonPressed += (ent, slot) =>
{
@@ -30,8 +29,6 @@ protected override void Open()
{
SendMessage(new CryostorageRemoveItemBuiMessage(ent, hand, CryostorageRemoveItemBuiMessage.RemovalType.Hand));
};
-
- _menu.OpenCentered();
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -45,12 +42,4 @@ protected override void UpdateState(BoundUserInterfaceState state)
break;
}
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
- _menu?.Dispose();
- }
}
diff --git a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
index d3365702bc..44c40143d8 100644
--- a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
+++ b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
@@ -1,6 +1,7 @@
using Content.Client.Cargo.UI;
using Content.Shared.Cargo.Components;
using JetBrains.Annotations;
+using Robust.Client.UserInterface;
namespace Content.Client.Cargo.BUI;
@@ -18,9 +19,7 @@ protected override void Open()
{
base.Open();
- _menu = new();
-
- _menu.OnClose += Close;
+ _menu = this.CreateWindow();
_menu.OnLabelButtonPressed += id =>
{
@@ -31,8 +30,6 @@ protected override void Open()
{
SendMessage(new BountySkipMessage(id));
};
-
- _menu.OpenCentered();
}
protected override void UpdateState(BoundUserInterfaceState message)
@@ -44,14 +41,4 @@ protected override void UpdateState(BoundUserInterfaceState message)
_menu?.UpdateEntries(state.Bounties, state.UntilNextSkip);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (!disposing)
- return;
-
- _menu?.Dispose();
- }
}
diff --git a/Content.Client/Cargo/BUI/CargoPalletConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoPalletConsoleBoundUserInterface.cs
index 20c23a48a0..2461dafb5f 100644
--- a/Content.Client/Cargo/BUI/CargoPalletConsoleBoundUserInterface.cs
+++ b/Content.Client/Cargo/BUI/CargoPalletConsoleBoundUserInterface.cs
@@ -2,6 +2,7 @@
using Content.Shared.Cargo.BUI;
using Content.Shared.Cargo.Events;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Cargo.BUI;
@@ -18,21 +19,9 @@ protected override void Open()
{
base.Open();
- _menu = new CargoPalletMenu();
+ _menu = this.CreateWindow();
_menu.AppraiseRequested += OnAppraisal;
_menu.SellRequested += OnSell;
- _menu.OnClose += Close;
-
- _menu.OpenCentered();
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (disposing)
- {
- _menu?.Dispose();
- }
}
private void OnAppraisal()
diff --git a/Content.Client/Cargo/BUI/CargoShuttleConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoShuttleConsoleBoundUserInterface.cs
index 422d03707a..02b721b902 100644
--- a/Content.Client/Cargo/BUI/CargoShuttleConsoleBoundUserInterface.cs
+++ b/Content.Client/Cargo/BUI/CargoShuttleConsoleBoundUserInterface.cs
@@ -2,6 +2,7 @@
using Content.Shared.Cargo.BUI;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client.Cargo.BUI;
@@ -9,6 +10,8 @@ namespace Content.Client.Cargo.BUI;
[UsedImplicitly]
public sealed class CargoShuttleConsoleBoundUserInterface : BoundUserInterface
{
+ [Dependency] private readonly IPrototypeManager _protoManager = default!;
+
[ViewVariables]
private CargoShuttleMenu? _menu;
@@ -19,24 +22,7 @@ public CargoShuttleConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base
protected override void Open()
{
base.Open();
- var collection = IoCManager.Instance;
-
- if (collection == null)
- return;
-
- _menu = new CargoShuttleMenu(collection.Resolve(), collection.Resolve().GetEntitySystem());
- _menu.OnClose += Close;
-
- _menu.OpenCentered();
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (disposing)
- {
- _menu?.Dispose();
- }
+ _menu = this.CreateWindow();
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -45,6 +31,6 @@ protected override void UpdateState(BoundUserInterfaceState state)
if (state is not CargoShuttleConsoleBoundUserInterfaceState cargoState) return;
_menu?.SetAccountName(cargoState.AccountName);
_menu?.SetShuttleName(cargoState.ShuttleName);
- _menu?.SetOrders(cargoState.Orders);
+ _menu?.SetOrders(EntMan.System(), _protoManager, cargoState.Orders);
}
}
diff --git a/Content.Client/Cargo/UI/CargoShuttleMenu.xaml.cs b/Content.Client/Cargo/UI/CargoShuttleMenu.xaml.cs
index c591f917da..43b00089e1 100644
--- a/Content.Client/Cargo/UI/CargoShuttleMenu.xaml.cs
+++ b/Content.Client/Cargo/UI/CargoShuttleMenu.xaml.cs
@@ -12,14 +12,9 @@ namespace Content.Client.Cargo.UI
[GenerateTypedNameReferences]
public sealed partial class CargoShuttleMenu : FancyWindow
{
- private readonly IPrototypeManager _protoManager;
- private readonly SpriteSystem _spriteSystem;
-
- public CargoShuttleMenu(IPrototypeManager protoManager, SpriteSystem spriteSystem)
+ public CargoShuttleMenu()
{
RobustXamlLoader.Load(this);
- _protoManager = protoManager;
- _spriteSystem = spriteSystem;
Title = Loc.GetString("cargo-shuttle-console-menu-title");
}
@@ -33,19 +28,19 @@ public void SetShuttleName(string name)
ShuttleNameLabel.Text = name;
}
- public void SetOrders(List orders)
+ public void SetOrders(SpriteSystem sprites, IPrototypeManager protoManager, List orders)
{
Orders.DisposeAllChildren();
foreach (var order in orders)
{
- var product = _protoManager.Index(order.ProductId);
+ var product = protoManager.Index(order.ProductId);
var productName = product.Name;
var row = new CargoOrderRow
{
Order = order,
- Icon = { Texture = _spriteSystem.Frame0(product) },
+ Icon = { Texture = sprites.Frame0(product) },
ProductName =
{
Text = Loc.GetString(
diff --git a/Content.Client/CartridgeLoader/Cartridges/NewsReaderUiFragment.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/NewsReaderUiFragment.xaml.cs
index f3b2d373d7..2d4d192ea8 100644
--- a/Content.Client/CartridgeLoader/Cartridges/NewsReaderUiFragment.xaml.cs
+++ b/Content.Client/CartridgeLoader/Cartridges/NewsReaderUiFragment.xaml.cs
@@ -31,7 +31,7 @@ public void UpdateState(NewsArticle article, int targetNum, int totalNum, bool n
Author.Visible = true;
PageName.Text = article.Title;
- PageText.SetMarkup(article.Content);
+ PageText.SetMarkupPermissive(article.Content);
PageNum.Text = $"{targetNum}/{totalNum}";
diff --git a/Content.Client/Chat/TypingIndicator/TypingIndicatorVisualizerSystem.cs b/Content.Client/Chat/TypingIndicator/TypingIndicatorVisualizerSystem.cs
index d04c9d661d..e89f7ab500 100644
--- a/Content.Client/Chat/TypingIndicator/TypingIndicatorVisualizerSystem.cs
+++ b/Content.Client/Chat/TypingIndicator/TypingIndicatorVisualizerSystem.cs
@@ -2,21 +2,36 @@
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Prototypes;
+using Content.Shared.Inventory;
namespace Content.Client.Chat.TypingIndicator;
public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly InventorySystem _inventory = default!;
+
protected override void OnAppearanceChange(EntityUid uid, TypingIndicatorComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
- if (!_prototypeManager.TryIndex(component.Prototype, out var proto))
+ var currentTypingIndicator = component.TypingIndicatorPrototype;
+
+ var evt = new BeforeShowTypingIndicatorEvent();
+
+ if (TryComp(uid, out var inventoryComp))
+ _inventory.RelayEvent((uid, inventoryComp), ref evt);
+
+ var overrideIndicator = evt.GetMostRecentIndicator();
+
+ if (overrideIndicator != null)
+ currentTypingIndicator = overrideIndicator.Value;
+
+ if (!_prototypeManager.TryIndex(currentTypingIndicator, out var proto))
{
- Log.Error($"Unknown typing indicator id: {component.Prototype}");
+ Log.Error($"Unknown typing indicator id: {component.TypingIndicatorPrototype}");
return;
}
diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs
index 68c937a788..adb61d10e6 100644
--- a/Content.Client/Chat/UI/SpeechBubble.cs
+++ b/Content.Client/Chat/UI/SpeechBubble.cs
@@ -16,6 +16,7 @@ public abstract class SpeechBubble : Control
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] protected readonly IConfigurationManager ConfigManager = default!;
+ private readonly SharedTransformSystem _transformSystem;
public enum SpeechType : byte
{
@@ -83,6 +84,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;
@@ -140,7 +142,7 @@ protected override void FrameUpdate(FrameEventArgs args)
}
var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -EntityVerticalOffset;
- var worldPos = xform.WorldPosition + offset;
+ 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/ChemMasterBoundUserInterface.cs b/Content.Client/Chemistry/UI/ChemMasterBoundUserInterface.cs
index 988fea7978..3ef7f0ae73 100644
--- a/Content.Client/Chemistry/UI/ChemMasterBoundUserInterface.cs
+++ b/Content.Client/Chemistry/UI/ChemMasterBoundUserInterface.cs
@@ -2,6 +2,7 @@
using Content.Shared.Containers.ItemSlots;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Chemistry.UI
{
@@ -27,13 +28,8 @@ protected override void Open()
base.Open();
// Setup window layout/elements
- _window = new ChemMasterWindow
- {
- Title = EntMan.GetComponent(Owner).EntityName,
- };
-
- _window.OpenCentered();
- _window.OnClose += Close;
+ _window = this.CreateWindow();
+ _window.Title = EntMan.GetComponent(Owner).EntityName;
// Setup static button actions.
_window.InputEjectButton.OnPressed += _ => SendMessage(
@@ -75,15 +71,5 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window?.UpdateState(castState); // Update window state
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- {
- _window?.Dispose();
- }
- }
}
}
diff --git a/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs b/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs
index 99e5a3d395..2ad1b71888 100644
--- a/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs
+++ b/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs
@@ -3,6 +3,7 @@
using Content.Shared.Containers.ItemSlots;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Chemistry.UI
{
@@ -15,9 +16,6 @@ public sealed class ReagentDispenserBoundUserInterface : BoundUserInterface
[ViewVariables]
private ReagentDispenserWindow? _window;
- [ViewVariables]
- private ReagentDispenserBoundUserInterfaceState? _lastState;
-
public ReagentDispenserBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
@@ -32,14 +30,9 @@ protected override void Open()
base.Open();
// Setup window layout/elements
- _window = new()
- {
- Title = EntMan.GetComponent(Owner).EntityName,
- HelpGuidebookIds = EntMan.GetComponent(Owner).Guides
- };
-
- _window.OpenCentered();
- _window.OnClose += Close;
+ _window = this.CreateWindow();
+ _window.Title = EntMan.GetComponent(Owner).EntityName;
+ _window.HelpGuidebookIds = EntMan.GetComponent(Owner).Guides;
// Setup static button actions.
_window.EjectButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(SharedReagentDispenser.OutputSlotName));
@@ -63,19 +56,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
base.UpdateState(state);
var castState = (ReagentDispenserBoundUserInterfaceState) state;
- _lastState = castState;
-
_window?.UpdateState(castState); //Update window state
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- {
- _window?.Dispose();
- }
- }
}
}
diff --git a/Content.Client/Chemistry/UI/TransferAmountBoundUserInterface.cs b/Content.Client/Chemistry/UI/TransferAmountBoundUserInterface.cs
index 35df131312..1bc1c0dba9 100644
--- a/Content.Client/Chemistry/UI/TransferAmountBoundUserInterface.cs
+++ b/Content.Client/Chemistry/UI/TransferAmountBoundUserInterface.cs
@@ -1,24 +1,33 @@
using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.Components;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Chemistry.UI
{
[UsedImplicitly]
public sealed class TransferAmountBoundUserInterface : BoundUserInterface
{
+ private IEntityManager _entManager;
+ private EntityUid _owner;
[ViewVariables]
private TransferAmountWindow? _window;
public TransferAmountBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
+ _owner = owner;
+ _entManager = IoCManager.Resolve();
}
protected override void Open()
{
base.Open();
- _window = new TransferAmountWindow();
+ _window = this.CreateWindow();
+
+ if (_entManager.TryGetComponent(_owner, out var comp))
+ _window.SetBounds(comp.MinimumTransferAmount.Int(), comp.MaximumTransferAmount.Int());
_window.ApplyButton.OnPressed += _ =>
{
@@ -28,15 +37,6 @@ protected override void Open()
_window.Close();
}
};
- _window.OnClose += Close;
- _window.OpenCentered();
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing) return;
- _window?.Dispose();
}
}
}
diff --git a/Content.Client/Chemistry/UI/TransferAmountWindow.xaml b/Content.Client/Chemistry/UI/TransferAmountWindow.xaml
index 3d787c69c1..c73d86b10f 100644
--- a/Content.Client/Chemistry/UI/TransferAmountWindow.xaml
+++ b/Content.Client/Chemistry/UI/TransferAmountWindow.xaml
@@ -6,6 +6,10 @@
+
+
+
+
diff --git a/Content.Client/Chemistry/UI/TransferAmountWindow.xaml.cs b/Content.Client/Chemistry/UI/TransferAmountWindow.xaml.cs
index 767e5cc823..6bae044441 100644
--- a/Content.Client/Chemistry/UI/TransferAmountWindow.xaml.cs
+++ b/Content.Client/Chemistry/UI/TransferAmountWindow.xaml.cs
@@ -8,9 +8,29 @@ namespace Content.Client.Chemistry.UI
[GenerateTypedNameReferences]
public sealed partial class TransferAmountWindow : DefaultWindow
{
+ private int _max = Int32.MaxValue;
+ private int _min = 1;
+
public TransferAmountWindow()
{
RobustXamlLoader.Load(this);
+ AmountLineEdit.OnTextChanged += OnValueChanged;
+ }
+
+ public void SetBounds(int min, int max)
+ {
+ _min = min;
+ _max = max;
+ MinimumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-min", ("amount", _min));
+ MaximumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-max", ("amount", _max));
+ }
+
+ private void OnValueChanged(LineEdit.LineEditEventArgs args)
+ {
+ if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount > _max || amount < _min)
+ ApplyButton.Disabled = true;
+ else
+ ApplyButton.Disabled = false;
}
}
}
diff --git a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs
index 17b88fb5a8..010bfb3184 100644
--- a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs
+++ b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs
@@ -2,6 +2,8 @@
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Clothing;
+using Content.Shared.Clothing.Components;
using Content.Shared.Hands;
using Content.Shared.Item;
using Content.Shared.Rounding;
@@ -20,6 +22,7 @@ public override void Initialize()
base.Initialize();
SubscribeLocalEvent(OnMapInit);
SubscribeLocalEvent(OnGetHeldVisuals);
+ SubscribeLocalEvent(OnGetClothingVisuals);
}
private void OnMapInit(EntityUid uid, SolutionContainerVisualsComponent component, MapInitEvent args)
@@ -174,4 +177,41 @@ private void OnGetHeldVisuals(EntityUid uid, SolutionContainerVisualsComponent c
args.Layers.Add((key, layer));
}
}
+
+ private void OnGetClothingVisuals(Entity ent, ref GetEquipmentVisualsEvent args)
+ {
+ if (ent.Comp.EquippedFillBaseName == null)
+ return;
+
+ if (!TryComp(ent, out var appearance))
+ return;
+
+ if (!TryComp(ent, out var clothing))
+ return;
+
+ if (!AppearanceSystem.TryGetData(ent, SolutionContainerVisuals.FillFraction, out var fraction, appearance))
+ return;
+
+ var closestFillSprite = ContentHelpers.RoundToLevels(fraction, 1, ent.Comp.EquippedMaxFillLevels + 1);
+
+ if (closestFillSprite > 0)
+ {
+ var layer = new PrototypeLayerData();
+
+ var equippedPrefix = clothing.EquippedPrefix == null ? $"equipped-{args.Slot}" : $" {clothing.EquippedPrefix}-equipped-{args.Slot}";
+ var key = equippedPrefix + ent.Comp.EquippedFillBaseName + closestFillSprite;
+
+ // Make sure the sprite state is valid so we don't show a big red error message
+ // This saves us from having to make fill level sprites for every possible slot the item could be in (including pockets).
+ if (!TryComp(ent, out var sprite) || sprite.BaseRSI == null || !sprite.BaseRSI.TryGetState(key, out _))
+ return;
+
+ layer.State = key;
+
+ if (ent.Comp.ChangeColor && AppearanceSystem.TryGetData(ent, SolutionContainerVisuals.Color, out var color, appearance))
+ layer.Color = color;
+
+ args.Layers.Add((key, layer));
+ }
+ }
}
diff --git a/Content.Client/Clickable/ClickableComponent.cs b/Content.Client/Clickable/ClickableComponent.cs
index 987473ca46..da81ed4c84 100644
--- a/Content.Client/Clickable/ClickableComponent.cs
+++ b/Content.Client/Clickable/ClickableComponent.cs
@@ -1,148 +1,17 @@
-using System.Numerics;
-using Robust.Client.GameObjects;
-using Robust.Client.Graphics;
-using Robust.Client.Utility;
-using Robust.Shared.Graphics;
-using static Robust.Client.GameObjects.SpriteComponent;
-using Direction = Robust.Shared.Maths.Direction;
+namespace Content.Client.Clickable;
-namespace Content.Client.Clickable
+[RegisterComponent]
+public sealed partial class ClickableComponent : Component
{
- [RegisterComponent]
- public sealed partial class ClickableComponent : Component
- {
- [Dependency] private readonly IClickMapManager _clickMapManager = default!;
-
- [DataField("bounds")] public DirBoundData? Bounds;
-
- ///
- /// Used to check whether a click worked. Will first check if the click falls inside of some explicit bounding
- /// boxes (see ). If that fails, attempts to use automatically generated click maps.
- ///
- /// The world position that was clicked.
- ///
- /// The draw depth for the sprite that captured the click.
- ///
- /// True if the click worked, false otherwise.
- public bool CheckClick(SpriteComponent sprite, TransformComponent transform, EntityQuery xformQuery, Vector2 worldPos, IEye eye, out int drawDepth, out uint renderOrder, out float bottom)
- {
- if (!sprite.Visible)
- {
- drawDepth = default;
- renderOrder = default;
- bottom = default;
- return false;
- }
-
- drawDepth = sprite.DrawDepth;
- renderOrder = sprite.RenderOrder;
- var (spritePos, spriteRot) = transform.GetWorldPositionRotation(xformQuery);
- var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye.Rotation);
- bottom = Matrix3Helpers.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom;
-
- Matrix3x2.Invert(sprite.GetLocalMatrix(), out var invSpriteMatrix);
-
- // This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites.
- var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive();
-
- Angle cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero;
-
- // First we get `localPos`, the clicked location in the sprite-coordinate frame.
- var entityXform = Matrix3Helpers.CreateInverseTransform(spritePos, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping);
- var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix);
-
- // Check explicitly defined click-able bounds
- if (CheckDirBound(sprite, relativeRotation, localPos))
- return true;
-
- // Next check each individual sprite layer using automatically computed click maps.
- foreach (var spriteLayer in sprite.AllLayers)
- {
- // TODO: Move this to a system and also use SpriteSystem.IsVisible instead.
- if (!spriteLayer.Visible || spriteLayer is not Layer layer || layer.CopyToShaderParameters != null)
- {
- continue;
- }
-
- // Check the layer's texture, if it has one
- if (layer.Texture != null)
- {
- // Convert to image coordinates
- var imagePos = (Vector2i) (localPos * EyeManager.PixelsPerMeter * new Vector2(1, -1) + layer.Texture.Size / 2f);
-
- if (_clickMapManager.IsOccluding(layer.Texture, imagePos))
- return true;
- }
-
- // Either we weren't clicking on the texture, or there wasn't one. In which case: check the RSI next
- if (layer.ActualRsi is not { } rsi || !rsi.TryGetState(layer.State, out var rsiState))
- continue;
-
- var dir = Layer.GetDirection(rsiState.RsiDirections, relativeRotation);
+ [DataField] public DirBoundData? Bounds;
- // convert to layer-local coordinates
- layer.GetLayerDrawMatrix(dir, out var matrix);
- Matrix3x2.Invert(matrix, out var inverseMatrix);
- var layerLocal = Vector2.Transform(localPos, inverseMatrix);
-
- // Convert to image coordinates
- var layerImagePos = (Vector2i) (layerLocal * EyeManager.PixelsPerMeter * new Vector2(1, -1) + rsiState.Size / 2f);
-
- // Next, to get the right click map we need the "direction" of this layer that is actually being used to draw the sprite on the screen.
- // This **can** differ from the dir defined before, but can also just be the same.
- if (sprite.EnableDirectionOverride)
- dir = sprite.DirectionOverride.Convert(rsiState.RsiDirections);
- dir = dir.OffsetRsiDir(layer.DirOffset);
-
- if (_clickMapManager.IsOccluding(layer.ActualRsi!, layer.State, dir, layer.AnimationFrame, layerImagePos))
- return true;
- }
-
- drawDepth = default;
- renderOrder = default;
- bottom = default;
- return false;
- }
-
- public bool CheckDirBound(SpriteComponent sprite, Angle relativeRotation, Vector2 localPos)
- {
- if (Bounds == null)
- return false;
-
- // These explicit bounds only work for either 1 or 4 directional sprites.
-
- // This would be the orientation of a 4-directional sprite.
- var direction = relativeRotation.GetCardinalDir();
-
- var modLocalPos = sprite.NoRotation
- ? localPos
- : direction.ToAngle().RotateVec(localPos);
-
- // First, check the bounding box that is valid for all orientations
- if (Bounds.All.Contains(modLocalPos))
- return true;
-
- // Next, get and check the appropriate bounding box for the current sprite orientation
- var boundsForDir = (sprite.EnableDirectionOverride ? sprite.DirectionOverride : direction) switch
- {
- Direction.East => Bounds.East,
- Direction.North => Bounds.North,
- Direction.South => Bounds.South,
- Direction.West => Bounds.West,
- _ => throw new InvalidOperationException()
- };
-
- return boundsForDir.Contains(modLocalPos);
- }
-
- [DataDefinition]
- public sealed partial class DirBoundData
- {
- [DataField("all")] public Box2 All;
- [DataField("north")] public Box2 North;
- [DataField("south")] public Box2 South;
- [DataField("east")] public Box2 East;
- [DataField("west")] public Box2 West;
- }
+ [DataDefinition]
+ public sealed partial class DirBoundData
+ {
+ [DataField] public Box2 All;
+ [DataField] public Box2 North;
+ [DataField] public Box2 South;
+ [DataField] public Box2 East;
+ [DataField] public Box2 West;
}
}
diff --git a/Content.Client/Clickable/ClickableSystem.cs b/Content.Client/Clickable/ClickableSystem.cs
new file mode 100644
index 0000000000..15d13df625
--- /dev/null
+++ b/Content.Client/Clickable/ClickableSystem.cs
@@ -0,0 +1,168 @@
+using System.Numerics;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.Utility;
+using Robust.Shared.Graphics;
+
+namespace Content.Client.Clickable;
+
+///
+/// Handles click detection for sprites.
+///
+public sealed class ClickableSystem : EntitySystem
+{
+ [Dependency] private readonly IClickMapManager _clickMapManager = default!;
+ [Dependency] private readonly SharedTransformSystem _transforms = default!;
+ [Dependency] private readonly SpriteSystem _sprites = default!;
+
+ private EntityQuery _clickableQuery;
+ private EntityQuery _xformQuery;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ _clickableQuery = GetEntityQuery();
+ _xformQuery = GetEntityQuery();
+ }
+
+ ///
+ /// Used to check whether a click worked. Will first check if the click falls inside of some explicit bounding
+ /// boxes (see ). If that fails, attempts to use automatically generated click maps.
+ ///
+ /// The world position that was clicked.
+ ///
+ /// The draw depth for the sprite that captured the click.
+ ///
+ /// True if the click worked, false otherwise.
+ public bool CheckClick(Entity entity, Vector2 worldPos, IEye eye, out int drawDepth, out uint renderOrder, out float bottom)
+ {
+ if (!_clickableQuery.Resolve(entity.Owner, ref entity.Comp1, false))
+ {
+ drawDepth = default;
+ renderOrder = default;
+ bottom = default;
+ return false;
+ }
+
+ if (!_xformQuery.Resolve(entity.Owner, ref entity.Comp3))
+ {
+ drawDepth = default;
+ renderOrder = default;
+ bottom = default;
+ return false;
+ }
+
+ var sprite = entity.Comp2;
+ var transform = entity.Comp3;
+
+ if (!sprite.Visible)
+ {
+ drawDepth = default;
+ renderOrder = default;
+ bottom = default;
+ return false;
+ }
+
+ drawDepth = sprite.DrawDepth;
+ renderOrder = sprite.RenderOrder;
+ var (spritePos, spriteRot) = _transforms.GetWorldPositionRotation(transform);
+ var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye.Rotation);
+ bottom = Matrix3Helpers.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom;
+
+ Matrix3x2.Invert(sprite.GetLocalMatrix(), out var invSpriteMatrix);
+
+ // This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites.
+ var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive();
+
+ var cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero;
+
+ // First we get `localPos`, the clicked location in the sprite-coordinate frame.
+ var entityXform = Matrix3Helpers.CreateInverseTransform(spritePos, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping);
+ var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix);
+
+ // Check explicitly defined click-able bounds
+ if (CheckDirBound((entity.Owner, entity.Comp1, entity.Comp2), relativeRotation, localPos))
+ return true;
+
+ // Next check each individual sprite layer using automatically computed click maps.
+ foreach (var spriteLayer in sprite.AllLayers)
+ {
+ if (spriteLayer is not SpriteComponent.Layer layer || !_sprites.IsVisible(layer))
+ {
+ continue;
+ }
+
+ // Check the layer's texture, if it has one
+ if (layer.Texture != null)
+ {
+ // Convert to image coordinates
+ var imagePos = (Vector2i) (localPos * EyeManager.PixelsPerMeter * new Vector2(1, -1) + layer.Texture.Size / 2f);
+
+ if (_clickMapManager.IsOccluding(layer.Texture, imagePos))
+ return true;
+ }
+
+ // Either we weren't clicking on the texture, or there wasn't one. In which case: check the RSI next
+ if (layer.ActualRsi is not { } rsi || !rsi.TryGetState(layer.State, out var rsiState))
+ continue;
+
+ var dir = SpriteComponent.Layer.GetDirection(rsiState.RsiDirections, relativeRotation);
+
+ // convert to layer-local coordinates
+ layer.GetLayerDrawMatrix(dir, out var matrix);
+ Matrix3x2.Invert(matrix, out var inverseMatrix);
+ var layerLocal = Vector2.Transform(localPos, inverseMatrix);
+
+ // Convert to image coordinates
+ var layerImagePos = (Vector2i) (layerLocal * EyeManager.PixelsPerMeter * new Vector2(1, -1) + rsiState.Size / 2f);
+
+ // Next, to get the right click map we need the "direction" of this layer that is actually being used to draw the sprite on the screen.
+ // This **can** differ from the dir defined before, but can also just be the same.
+ if (sprite.EnableDirectionOverride)
+ dir = sprite.DirectionOverride.Convert(rsiState.RsiDirections);
+ dir = dir.OffsetRsiDir(layer.DirOffset);
+
+ if (_clickMapManager.IsOccluding(layer.ActualRsi!, layer.State, dir, layer.AnimationFrame, layerImagePos))
+ return true;
+ }
+
+ drawDepth = default;
+ renderOrder = default;
+ bottom = default;
+ return false;
+ }
+
+ public bool CheckDirBound(Entity entity, Angle relativeRotation, Vector2 localPos)
+ {
+ var clickable = entity.Comp1;
+ var sprite = entity.Comp2;
+
+ if (clickable.Bounds == null)
+ return false;
+
+ // These explicit bounds only work for either 1 or 4 directional sprites.
+
+ // This would be the orientation of a 4-directional sprite.
+ var direction = relativeRotation.GetCardinalDir();
+
+ var modLocalPos = sprite.NoRotation
+ ? localPos
+ : direction.ToAngle().RotateVec(localPos);
+
+ // First, check the bounding box that is valid for all orientations
+ if (clickable.Bounds.All.Contains(modLocalPos))
+ return true;
+
+ // Next, get and check the appropriate bounding box for the current sprite orientation
+ var boundsForDir = (sprite.EnableDirectionOverride ? sprite.DirectionOverride : direction) switch
+ {
+ Direction.East => clickable.Bounds.East,
+ Direction.North => clickable.Bounds.North,
+ Direction.South => clickable.Bounds.South,
+ Direction.West => clickable.Bounds.West,
+ _ => throw new InvalidOperationException()
+ };
+
+ return boundsForDir.Contains(modLocalPos);
+ }
+}
diff --git a/Content.Client/Clock/ClockSystem.cs b/Content.Client/Clock/ClockSystem.cs
new file mode 100644
index 0000000000..7097ada9df
--- /dev/null
+++ b/Content.Client/Clock/ClockSystem.cs
@@ -0,0 +1,26 @@
+using Content.Shared.Clock;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Clock;
+
+public sealed class ClockSystem : SharedClockSystem
+{
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var comp, out var sprite))
+ {
+ if (!sprite.LayerMapTryGet(ClockVisualLayers.HourHand, out var hourLayer) ||
+ !sprite.LayerMapTryGet(ClockVisualLayers.MinuteHand, out var minuteLayer))
+ continue;
+
+ var time = GetClockTime((uid, comp));
+ var hourState = $"{comp.HoursBase}{time.Hours % 12}";
+ var minuteState = $"{comp.MinutesBase}{time.Minutes / 5}";
+ sprite.LayerSetState(hourLayer, hourState);
+ sprite.LayerSetState(minuteLayer, minuteState);
+ }
+ }
+}
diff --git a/Content.Client/CloningConsole/UI/CloningConsoleBoundUserInterface.cs b/Content.Client/CloningConsole/UI/CloningConsoleBoundUserInterface.cs
index 26f0994701..62a02f3718 100644
--- a/Content.Client/CloningConsole/UI/CloningConsoleBoundUserInterface.cs
+++ b/Content.Client/CloningConsole/UI/CloningConsoleBoundUserInterface.cs
@@ -1,6 +1,7 @@
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Content.Shared.Cloning.CloningConsole;
+using Robust.Client.UserInterface;
namespace Content.Client.CloningConsole.UI
{
@@ -17,13 +18,11 @@ public CloningConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owne
protected override void Open()
{
base.Open();
- _window = new CloningConsoleWindow
- {
- Title = Loc.GetString("cloning-console-window-title")
- };
- _window.OnClose += Close;
+
+ _window = this.CreateWindow();
+ _window.Title = Loc.GetString("cloning-console-window-title");
+
_window.CloneButton.OnPressed += _ => SendMessage(new UiButtonPressedMessage(UiButton.Clone));
- _window.OpenCentered();
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -32,19 +31,5 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window?.Populate((CloningConsoleBoundUserInterfaceState) state);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
-
- if (_window != null)
- {
- _window.OnClose -= Close;
- _window.CloneButton.OnPressed -= _ => SendMessage(new UiButtonPressedMessage(UiButton.Clone));
- }
- _window?.Dispose();
- }
}
}
diff --git a/Content.Client/Clothing/ClientClothingSystem.cs b/Content.Client/Clothing/ClientClothingSystem.cs
index 1c0d831226..96bbcc54f2 100644
--- a/Content.Client/Clothing/ClientClothingSystem.cs
+++ b/Content.Client/Clothing/ClientClothingSystem.cs
@@ -1,10 +1,12 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
+using Content.Client.DisplacementMap;
using Content.Client.Inventory;
using Content.Shared.Clothing;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems;
+using Content.Shared.DisplacementMap;
using Content.Shared.Humanoid;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
@@ -50,6 +52,7 @@ public sealed class ClientClothingSystem : ClothingSystem
[Dependency] private readonly IResourceCache _cache = default!;
[Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
+ [Dependency] private readonly DisplacementMapSystem _displacement = default!;
public override void Initialize()
{
@@ -64,15 +67,14 @@ public override void Initialize()
private void OnAppearanceUpdate(EntityUid uid, InventoryComponent component, ref AppearanceChangeEvent args)
{
- // May need to update jumpsuit stencils if the sex changed. Also required to properly set the stencil on init
+ // May need to update displacement maps if the sex changed. Also required to properly set the stencil on init
if (args.Sprite == null)
return;
- if (_inventorySystem.TryGetSlotEntity(uid, Jumpsuit, out var suit, component)
- && TryComp(suit, out ClothingComponent? clothing))
+ var enumerator = _inventorySystem.GetSlotEnumerator((uid, component));
+ while (enumerator.NextItem(out var item, out var slot))
{
- SetGenderedMask(uid, args.Sprite, clothing);
- return;
+ RenderEquipment(uid, item, slot.Name, component);
}
// No clothing equipped -> make sure the layer is hidden, though this should already be handled by on-unequip.
@@ -179,14 +181,6 @@ private void OnVisualsChanged(EntityUid uid, InventoryComponent component, Visua
private void OnDidUnequip(EntityUid uid, SpriteComponent component, DidUnequipEvent args)
{
- // Hide jumpsuit mask layer.
- if (args.Slot == Jumpsuit
- && TryComp(uid, out SpriteComponent? sprite)
- && sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var maskLayer))
- {
- sprite.LayerSetVisible(maskLayer, false);
- }
-
if (!TryComp(uid, out InventorySlotsComponent? inventorySlots))
return;
@@ -231,9 +225,6 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
return;
}
- if (slot == Jumpsuit)
- SetGenderedMask(equipee, sprite, clothingComponent);
-
if (!_inventorySystem.TryGetSlot(equipee, slot, out var slotDef, inventory))
return;
@@ -265,7 +256,25 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
// temporary, until layer draw depths get added. Basically: a layer with the key "slot" is being used as a
// bookmark to determine where in the list of layers we should insert the clothing layers.
bool slotLayerExists = sprite.LayerMapTryGet(slot, out var index);
- var displacementData = inventory.Displacements.GetValueOrDefault(slot);
+
+ // Select displacement maps
+ var displacementData = inventory.Displacements.GetValueOrDefault(slot); //Default unsexed map
+
+ var equipeeSex = CompOrNull(equipee)?.Sex;
+ if (equipeeSex != null)
+ {
+ switch (equipeeSex)
+ {
+ case Sex.Male:
+ if (inventory.MaleDisplacements.Count > 0)
+ displacementData = inventory.MaleDisplacements.GetValueOrDefault(slot);
+ break;
+ case Sex.Female:
+ if (inventory.FemaleDisplacements.Count > 0)
+ displacementData = inventory.FemaleDisplacements.GetValueOrDefault(slot);
+ break;
+ }
+ }
// add the new layers
foreach (var (key, layerData) in ev.Layers)
@@ -292,7 +301,7 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
index = sprite.LayerMapReserveBlank(key);
if (sprite[index] is not Layer layer)
- return;
+ continue;
// In case no RSI is given, use the item's base RSI as a default. This cuts down on a lot of unnecessary yaml entries.
if (layerData.RsiPath == null
@@ -303,78 +312,19 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
layer.SetRsi(clothingSprite.BaseRSI);
}
- // Another "temporary" fix for clothing stencil masks.
- // Sprite layer redactor when
- // Sprite "redactor" just a week away.
- if (slot == Jumpsuit)
- layerData.Shader ??= "StencilDraw";
-
sprite.LayerSetData(index, layerData);
layer.Offset += slotDef.Offset;
- if (displacementData != null)
+ if (displacementData is not null)
{
- if (displacementData.ShaderOverride != null)
- sprite.LayerSetShader(index, displacementData.ShaderOverride);
-
- var displacementKey = $"{key}-displacement";
- if (!revealedLayers.Add(displacementKey))
- {
- Log.Warning($"Duplicate key for clothing visuals DISPLACEMENT: {displacementKey}.");
+ //Checking that the state is not tied to the current race. In this case we don't need to use the displacement maps.
+ if (layerData.State is not null && inventory.SpeciesId is not null && layerData.State.EndsWith(inventory.SpeciesId))
continue;
- }
-
- var displacementLayer = _serialization.CreateCopy(displacementData.Layer, notNullableOverride: true);
- displacementLayer.CopyToShaderParameters!.LayerKey = key;
- // Add before main layer for this item.
- sprite.AddLayer(displacementLayer, index);
- sprite.LayerMapSet(displacementKey, index);
-
- revealedLayers.Add(displacementKey);
+ _displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers);
}
}
RaiseLocalEvent(equipment, new EquipmentVisualsUpdatedEvent(equipee, slot, revealedLayers), true);
}
-
-
- ///
- /// Sets a sprite's gendered mask based on gender (obviously).
- ///
- /// Sprite to modify
- /// Humanoid, to get gender from
- /// Clothing component, to get mask sprite type
- private void SetGenderedMask(EntityUid uid, SpriteComponent sprite, ClothingComponent clothing)
- {
- if (!sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer))
- return;
-
- ClothingMask mask;
- string prefix;
-
- switch (CompOrNull(uid)?.Sex)
- {
- case Sex.Male:
- mask = clothing.MaleMask;
- prefix = "male_";
- break;
- case Sex.Female:
- mask = clothing.FemaleMask;
- prefix = "female_";
- break;
- default:
- mask = clothing.UnisexMask;
- prefix = "unisex_";
- break;
- }
-
- sprite.LayerSetState(layer, mask switch
- {
- ClothingMask.NoMask => $"{prefix}none",
- ClothingMask.UniformTop => $"{prefix}top",
- _ => $"{prefix}full",
- });
- sprite.LayerSetVisible(layer, true);
- }
}
diff --git a/Content.Client/Clothing/UI/ChameleonBoundUserInterface.cs b/Content.Client/Clothing/UI/ChameleonBoundUserInterface.cs
index 5b0d5fcf21..83f6ba1566 100644
--- a/Content.Client/Clothing/UI/ChameleonBoundUserInterface.cs
+++ b/Content.Client/Clothing/UI/ChameleonBoundUserInterface.cs
@@ -2,6 +2,7 @@
using Content.Shared.Clothing.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Clothing.UI;
@@ -22,10 +23,8 @@ protected override void Open()
{
base.Open();
- _menu = new ChameleonMenu();
- _menu.OnClose += Close;
+ _menu = this.CreateWindow();
_menu.OnIdSelected += OnIdSelected;
- _menu.OpenCentered();
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -42,15 +41,4 @@ private void OnIdSelected(string selectedId)
{
SendMessage(new ChameleonPrototypeSelectedMessage(selectedId));
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- {
- _menu?.Close();
- _menu = null;
- }
- }
}
diff --git a/Content.Client/Commands/ActionsCommands.cs b/Content.Client/Commands/ActionsCommands.cs
index 3d8e906e09..593b8e8256 100644
--- a/Content.Client/Commands/ActionsCommands.cs
+++ b/Content.Client/Commands/ActionsCommands.cs
@@ -1,3 +1,4 @@
+using Content.Client.Actions;
using Content.Client.Actions;
using Content.Client.Mapping;
using Content.Shared.Administration;
@@ -61,27 +62,3 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args)
}
}
}
-
-[AnyCommand]
-public sealed class LoadMappingActionsCommand : LocalizedCommands
-{
- [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
-
- public const string CommandName = "loadmapacts";
-
- public override string Command => CommandName;
-
- public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
-
- public override void Execute(IConsoleShell shell, string argStr, string[] args)
- {
- try
- {
- _entitySystemManager.GetEntitySystem().LoadMappingActions();
- }
- catch
- {
- shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error"));
- }
- }
-}
diff --git a/Content.Client/Commands/HideMechanismsCommand.cs b/Content.Client/Commands/HideMechanismsCommand.cs
index 5f9afc78b9..97d792628a 100644
--- a/Content.Client/Commands/HideMechanismsCommand.cs
+++ b/Content.Client/Commands/HideMechanismsCommand.cs
@@ -30,7 +30,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args)
sprite.ContainerOccluded = false;
var tempParent = uid;
- while (containerSys.TryGetContainingContainer(tempParent, out var container))
+ while (containerSys.TryGetContainingContainer((tempParent, null, null), out var container))
{
if (!container.ShowContents)
{
diff --git a/Content.Client/Commands/MappingClientSideSetupCommand.cs b/Content.Client/Commands/MappingClientSideSetupCommand.cs
index 39268c6284..3255e85e18 100644
--- a/Content.Client/Commands/MappingClientSideSetupCommand.cs
+++ b/Content.Client/Commands/MappingClientSideSetupCommand.cs
@@ -1,6 +1,9 @@
+using Content.Client.Actions;
+using Content.Client.Mapping;
using Content.Client.Markers;
using JetBrains.Annotations;
using Robust.Client.Graphics;
+using Robust.Client.State;
using Robust.Shared.Console;
namespace Content.Client.Commands;
@@ -10,6 +13,7 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
{
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly ILightManager _lightManager = default!;
+ [Dependency] private readonly IStateManager _stateManager = default!;
public override string Command => "mappingclientsidesetup";
@@ -21,8 +25,8 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_entitySystemManager.GetEntitySystem().MarkersVisible = true;
_lightManager.Enabled = false;
- shell.ExecuteCommand(ShowSubFloorForever.CommandName);
- shell.ExecuteCommand(LoadMappingActionsCommand.CommandName);
+ shell.ExecuteCommand("showsubfloorforever");
+ _entitySystemManager.GetEntitySystem().LoadActionAssignments("/mapping_actions.yml", false);
}
}
}
diff --git a/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs b/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs
index 1c94d32bf8..0310e91eeb 100644
--- a/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs
+++ b/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs
@@ -1,6 +1,7 @@
using Content.Shared.CCVar;
using Content.Shared.Chat;
using Content.Shared.Communications;
+using Robust.Client.UserInterface;
using Robust.Shared.Configuration;
using Robust.Shared.Timing;
@@ -8,34 +9,11 @@ namespace Content.Client.Communications.UI
{
public sealed class CommunicationsConsoleBoundUserInterface : BoundUserInterface
{
- [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[ViewVariables]
private CommunicationsConsoleMenu? _menu;
- [ViewVariables]
- public bool CanAnnounce { get; private set; }
- [ViewVariables]
- public bool CanBroadcast { get; private set; }
-
- [ViewVariables]
- public bool CanCall { get; private set; }
-
- [ViewVariables]
- public bool CountdownStarted { get; private set; }
-
- [ViewVariables]
- public bool AlertLevelSelectable { get; private set; }
-
- [ViewVariables]
- public string CurrentLevel { get; private set; } = default!;
-
- [ViewVariables]
- private TimeSpan? _expectedCountdownTime;
-
- public int Countdown => _expectedCountdownTime == null ? 0 : Math.Max((int) _expectedCountdownTime.Value.Subtract(_gameTiming.CurTime).TotalSeconds, 0);
-
public CommunicationsConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
@@ -44,23 +22,25 @@ protected override void Open()
{
base.Open();
- _menu = new CommunicationsConsoleMenu(this);
- _menu.OnClose += Close;
- _menu.OpenCentered();
+ _menu = this.CreateWindow();
+ _menu.OnAnnounce += AnnounceButtonPressed;
+ _menu.OnBroadcast += BroadcastButtonPressed;
+ _menu.OnAlertLevel += AlertLevelSelected;
+ _menu.OnEmergencyLevel += EmergencyShuttleButtonPressed;
}
public void AlertLevelSelected(string level)
{
- if (AlertLevelSelectable)
+ if (_menu!.AlertLevelSelectable)
{
- CurrentLevel = level;
+ _menu.CurrentLevel = level;
SendMessage(new CommunicationsConsoleSelectAlertLevelMessage(level));
}
}
public void EmergencyShuttleButtonPressed()
{
- if (CountdownStarted)
+ if (_menu!.CountdownStarted)
RecallShuttle();
else
CallShuttle();
@@ -95,31 +75,23 @@ protected override void UpdateState(BoundUserInterfaceState state)
if (state is not CommunicationsConsoleInterfaceState commsState)
return;
- CanAnnounce = commsState.CanAnnounce;
- CanBroadcast = commsState.CanBroadcast;
- CanCall = commsState.CanCall;
- _expectedCountdownTime = commsState.ExpectedCountdownEnd;
- CountdownStarted = commsState.CountdownStarted;
- AlertLevelSelectable = commsState.AlertLevels != null && !float.IsNaN(commsState.CurrentAlertDelay) && commsState.CurrentAlertDelay <= 0;
- CurrentLevel = commsState.CurrentAlert;
-
if (_menu != null)
{
+ _menu.CanAnnounce = commsState.CanAnnounce;
+ _menu.CanBroadcast = commsState.CanBroadcast;
+ _menu.CanCall = commsState.CanCall;
+ _menu.CountdownStarted = commsState.CountdownStarted;
+ _menu.AlertLevelSelectable = commsState.AlertLevels != null && !float.IsNaN(commsState.CurrentAlertDelay) && commsState.CurrentAlertDelay <= 0;
+ _menu.CurrentLevel = commsState.CurrentAlert;
+ _menu.CountdownEnd = commsState.ExpectedCountdownEnd;
+
_menu.UpdateCountdown();
- _menu.UpdateAlertLevels(commsState.AlertLevels, CurrentLevel);
- _menu.AlertLevelButton.Disabled = !AlertLevelSelectable;
- _menu.EmergencyShuttleButton.Disabled = !CanCall;
- _menu.AnnounceButton.Disabled = !CanAnnounce;
- _menu.BroadcastButton.Disabled = !CanBroadcast;
+ _menu.UpdateAlertLevels(commsState.AlertLevels, _menu.CurrentLevel);
+ _menu.AlertLevelButton.Disabled = !_menu.AlertLevelSelectable;
+ _menu.EmergencyShuttleButton.Disabled = !_menu.CanCall;
+ _menu.AnnounceButton.Disabled = !_menu.CanAnnounce;
+ _menu.BroadcastButton.Disabled = !_menu.CanBroadcast;
}
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing) return;
-
- _menu?.Dispose();
- }
}
}
diff --git a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs
index 4d8dd86a4d..56604ba526 100644
--- a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs
+++ b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs
@@ -1,31 +1,40 @@
-using Content.Client.UserInterface.Controls;
-using System.Threading;
+using System.Globalization;
+using Content.Client.UserInterface.Controls;
using Content.Shared.CCVar;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
+using Robust.Shared.Timing;
using Robust.Shared.Utility;
-using Timer = Robust.Shared.Timing.Timer;
namespace Content.Client.Communications.UI
{
[GenerateTypedNameReferences]
public sealed partial class CommunicationsConsoleMenu : FancyWindow
{
- private CommunicationsConsoleBoundUserInterface Owner { get; set; }
- private readonly CancellationTokenSource _timerCancelTokenSource = new();
-
[Dependency] private readonly IConfigurationManager _cfg = default!;
-
- public CommunicationsConsoleMenu(CommunicationsConsoleBoundUserInterface owner)
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly ILocalizationManager _loc = default!;
+
+ public bool CanAnnounce;
+ public bool CanBroadcast;
+ public bool CanCall;
+ public bool AlertLevelSelectable;
+ public bool CountdownStarted;
+ public string CurrentLevel = string.Empty;
+ public TimeSpan? CountdownEnd;
+
+ public event Action? OnEmergencyLevel;
+ public event Action? OnAlertLevel;
+ public event Action? OnAnnounce;
+ public event Action? OnBroadcast;
+
+ public CommunicationsConsoleMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
- Owner = owner;
-
- var loc = IoCManager.Resolve();
- MessageInput.Placeholder = new Rope.Leaf(loc.GetString("comms-console-menu-announcement-placeholder"));
+ MessageInput.Placeholder = new Rope.Leaf(_loc.GetString("comms-console-menu-announcement-placeholder"));
var maxAnnounceLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength);
MessageInput.OnTextChanged += (args) =>
@@ -37,33 +46,38 @@ public CommunicationsConsoleMenu(CommunicationsConsoleBoundUserInterface owner)
}
else
{
- AnnounceButton.Disabled = !owner.CanAnnounce;
+ AnnounceButton.Disabled = !CanAnnounce;
AnnounceButton.ToolTip = null;
}
};
- AnnounceButton.OnPressed += (_) => Owner.AnnounceButtonPressed(Rope.Collapse(MessageInput.TextRope));
- AnnounceButton.Disabled = !owner.CanAnnounce;
+ AnnounceButton.OnPressed += _ => OnAnnounce?.Invoke(Rope.Collapse(MessageInput.TextRope));
+ AnnounceButton.Disabled = !CanAnnounce;
- BroadcastButton.OnPressed += (_) => Owner.BroadcastButtonPressed(Rope.Collapse(MessageInput.TextRope));
- BroadcastButton.Disabled = !owner.CanBroadcast;
+ BroadcastButton.OnPressed += _ => OnBroadcast?.Invoke(Rope.Collapse(MessageInput.TextRope));
+ BroadcastButton.Disabled = !CanBroadcast;
AlertLevelButton.OnItemSelected += args =>
{
var metadata = AlertLevelButton.GetItemMetadata(args.Id);
if (metadata != null && metadata is string cast)
{
- Owner.AlertLevelSelected(cast);
+ OnAlertLevel?.Invoke(cast);
}
};
- AlertLevelButton.Disabled = !owner.AlertLevelSelectable;
- EmergencyShuttleButton.OnPressed += (_) => Owner.EmergencyShuttleButtonPressed();
- EmergencyShuttleButton.Disabled = !owner.CanCall;
+ AlertLevelButton.Disabled = !AlertLevelSelectable;
+
+ EmergencyShuttleButton.OnPressed += _ => OnEmergencyLevel?.Invoke();
+ EmergencyShuttleButton.Disabled = !CanCall;
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
UpdateCountdown();
- Timer.SpawnRepeating(1000, UpdateCountdown, _timerCancelTokenSource.Token);
}
// The current alert could make levels unselectable, so we need to ensure that the UI reacts properly.
@@ -105,30 +119,19 @@ public void UpdateAlertLevels(List? alerts, string currentAlert)
public void UpdateCountdown()
{
- if (!Owner.CountdownStarted)
+ if (!CountdownStarted)
{
- CountdownLabel.SetMessage("");
+ CountdownLabel.SetMessage(string.Empty);
EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-call-shuttle");
return;
}
- EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-recall-shuttle");
- CountdownLabel.SetMessage($"Time remaining\n{Owner.Countdown.ToString()}s");
- }
-
- public override void Close()
- {
- base.Close();
-
- _timerCancelTokenSource.Cancel();
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
+ var diff = MathHelper.Max((CountdownEnd - _timing.CurTime) ?? TimeSpan.Zero, TimeSpan.Zero);
- if (disposing)
- _timerCancelTokenSource.Cancel();
+ EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-recall-shuttle");
+ var infoText = Loc.GetString($"comms-console-menu-time-remaining",
+ ("time", diff.ToString(@"hh\:mm\:ss", CultureInfo.CurrentCulture)));
+ CountdownLabel.SetMessage(infoText);
}
}
}
diff --git a/Content.Client/Computer/ComputerBoundUserInterface.cs b/Content.Client/Computer/ComputerBoundUserInterface.cs
index bdbfe03fa1..11c26b252e 100644
--- a/Content.Client/Computer/ComputerBoundUserInterface.cs
+++ b/Content.Client/Computer/ComputerBoundUserInterface.cs
@@ -1,4 +1,5 @@
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
namespace Content.Client.Computer
@@ -19,10 +20,8 @@ protected override void Open()
{
base.Open();
- _window = (TWindow) _dynamicTypeFactory.CreateInstance(typeof(TWindow));
+ _window = this.CreateWindow();
_window.SetupComputerWindow(this);
- _window.OnClose += Close;
- _window.OpenCentered();
}
// Alas, this constructor has to be copied to the subclass. :(
@@ -42,16 +41,6 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window.UpdateState((TState) state);
}
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- {
- _window?.Dispose();
- }
- }
-
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
_window?.ReceiveMessage(message);
diff --git a/Content.Client/Configurable/UI/ConfigurationBoundUserInterface.cs b/Content.Client/Configurable/UI/ConfigurationBoundUserInterface.cs
index 4fea44f225..e4966f1ec4 100644
--- a/Content.Client/Configurable/UI/ConfigurationBoundUserInterface.cs
+++ b/Content.Client/Configurable/UI/ConfigurationBoundUserInterface.cs
@@ -1,5 +1,6 @@
using System.Text.RegularExpressions;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
using static Content.Shared.Configurable.ConfigurationComponent;
namespace Content.Client.Configurable.UI
@@ -9,9 +10,6 @@ public sealed class ConfigurationBoundUserInterface : BoundUserInterface
[ViewVariables]
private ConfigurationMenu? _menu;
- [ViewVariables]
- public Regex? Validation { get; internal set; }
-
public ConfigurationBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
@@ -19,10 +17,8 @@ public ConfigurationBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner
protected override void Open()
{
base.Open();
- _menu = new ConfigurationMenu(this);
-
- _menu.OnClose += Close;
- _menu.OpenCentered();
+ _menu = this.CreateWindow();
+ _menu.OnConfiguration += SendConfiguration;
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -30,9 +26,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
base.UpdateState(state);
if (state is not ConfigurationBoundUserInterfaceState configurationState)
- {
return;
- }
_menu?.Populate(configurationState);
}
@@ -41,9 +35,12 @@ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
base.ReceiveMessage(message);
+ if (_menu == null)
+ return;
+
if (message is ValidationUpdateMessage msg)
{
- Validation = new Regex(msg.ValidationString, RegexOptions.Compiled);
+ _menu.Validation = new Regex(msg.ValidationString, RegexOptions.Compiled);
}
}
@@ -51,16 +48,5 @@ public void SendConfiguration(Dictionary config)
{
SendMessage(new ConfigurationUpdatedMessage(config));
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing && _menu != null)
- {
- _menu.OnClose -= Close;
- _menu.Close();
- }
- }
}
}
diff --git a/Content.Client/Configurable/UI/ConfigurationMenu.cs b/Content.Client/Configurable/UI/ConfigurationMenu.cs
index cc24af2869..29217eef7b 100644
--- a/Content.Client/Configurable/UI/ConfigurationMenu.cs
+++ b/Content.Client/Configurable/UI/ConfigurationMenu.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Numerics;
+using System.Text.RegularExpressions;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
@@ -13,23 +14,25 @@ namespace Content.Client.Configurable.UI
{
public sealed class ConfigurationMenu : DefaultWindow
{
- public ConfigurationBoundUserInterface Owner { get; }
-
private readonly BoxContainer _column;
private readonly BoxContainer _row;
private readonly List<(string name, LineEdit input)> _inputs;
- public ConfigurationMenu(ConfigurationBoundUserInterface owner)
+ [ViewVariables]
+ public Regex? Validation { get; internal set; }
+
+ public event Action>? OnConfiguration;
+
+ public ConfigurationMenu()
{
MinSize = SetSize = new Vector2(300, 250);
- Owner = owner;
_inputs = new List<(string name, LineEdit input)>();
Title = Loc.GetString("configuration-menu-device-title");
- BoxContainer baseContainer = new BoxContainer
+ var baseContainer = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
VerticalExpand = true,
@@ -116,14 +119,13 @@ public void Populate(ConfigurationBoundUserInterfaceState state)
private void OnConfirm(ButtonEventArgs args)
{
var config = GenerateDictionary(_inputs, "Text");
-
- Owner.SendConfiguration(config);
+ OnConfiguration?.Invoke(config);
Close();
}
private bool Validate(string value)
{
- return Owner.Validation == null || Owner.Validation.IsMatch(value);
+ return Validation?.IsMatch(value) != false;
}
private Dictionary GenerateDictionary(IEnumerable<(string name, LineEdit input)> inputs, string propertyName)
diff --git a/Content.Client/Construction/ConstructionSystem.cs b/Content.Client/Construction/ConstructionSystem.cs
index 453658bebf..f909b23423 100644
--- a/Content.Client/Construction/ConstructionSystem.cs
+++ b/Content.Client/Construction/ConstructionSystem.cs
@@ -26,7 +26,6 @@ public sealed class ConstructionSystem : SharedConstructionSystem
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
@@ -48,11 +47,11 @@ public override void Initialize()
CommandBinds.Builder
.Bind(ContentKeyFunctions.OpenCraftingMenu,
- new PointerInputCmdHandler(HandleOpenCraftingMenu, outsidePrediction:true))
+ new PointerInputCmdHandler(HandleOpenCraftingMenu, outsidePrediction: true))
.Bind(EngineKeyFunctions.Use,
new PointerInputCmdHandler(HandleUse, outsidePrediction: true))
.Bind(ContentKeyFunctions.EditorFlipObject,
- new PointerInputCmdHandler(HandleFlip, outsidePrediction:true))
+ new PointerInputCmdHandler(HandleFlip, outsidePrediction: true))
.Register();
SubscribeLocalEvent(HandleConstructionGhostExamined);
@@ -196,7 +195,7 @@ public bool TrySpawnGhost(
if (GhostPresent(loc))
return false;
- var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager, _transformSystem));
+ var predicate = GetPredicate(prototype.CanBuildInImpassable, _transformSystem.ToMapCoordinates(loc));
if (!_examineSystem.InRangeUnOccluded(user, loc, 20f, predicate: predicate))
return false;
diff --git a/Content.Client/Construction/UI/FlatpackCreatorBoundUserInterface.cs b/Content.Client/Construction/UI/FlatpackCreatorBoundUserInterface.cs
index 86f1b8b83c..887492955e 100644
--- a/Content.Client/Construction/UI/FlatpackCreatorBoundUserInterface.cs
+++ b/Content.Client/Construction/UI/FlatpackCreatorBoundUserInterface.cs
@@ -1,5 +1,6 @@
using Content.Shared.Construction.Components;
using JetBrains.Annotations;
+using Robust.Client.UserInterface;
namespace Content.Client.Construction.UI
{
@@ -17,8 +18,8 @@ protected override void Open()
{
base.Open();
- _menu = new FlatpackCreatorMenu(Owner);
- _menu.OnClose += Close;
+ _menu = this.CreateWindow();
+ _menu.SetEntity(Owner);
_menu.PackButtonPressed += () =>
{
@@ -27,14 +28,5 @@ protected override void Open()
_menu.OpenCentered();
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
-
- _menu?.Dispose();
- }
}
}
diff --git a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
index 9f3d5695bb..269694ebf9 100644
--- a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
+++ b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
@@ -24,7 +24,7 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
private readonly FlatpackSystem _flatpack;
private readonly MaterialStorageSystem _materialStorage;
- private readonly EntityUid _owner;
+ private EntityUid _owner;
[ValidatePrototypeId]
public const string NoBoardEffectId = "FlatpackerNoBoardEffect";
@@ -33,7 +33,7 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
public event Action? PackButtonPressed;
- public FlatpackCreatorMenu(EntityUid uid)
+ public FlatpackCreatorMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
@@ -42,14 +42,17 @@ public FlatpackCreatorMenu(EntityUid uid)
_flatpack = _entityManager.System();
_materialStorage = _entityManager.System();
- _owner = uid;
-
PackButton.OnPressed += _ => PackButtonPressed?.Invoke();
- MaterialStorageControl.SetOwner(uid);
InsertLabel.SetMarkup(Loc.GetString("flatpacker-ui-insert-board"));
}
+ public void SetEntity(EntityUid uid)
+ {
+ _owner = uid;
+ MaterialStorageControl.SetOwner(uid);
+ }
+
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
diff --git a/Content.Client/ContextMenu/UI/ContextMenuUIController.cs b/Content.Client/ContextMenu/UI/ContextMenuUIController.cs
index 5b156644a7..2d94034bb9 100644
--- a/Content.Client/ContextMenu/UI/ContextMenuUIController.cs
+++ b/Content.Client/ContextMenu/UI/ContextMenuUIController.cs
@@ -2,6 +2,7 @@
using System.Threading;
using Content.Client.CombatMode;
using Content.Client.Gameplay;
+using Content.Client.Mapping;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Timer = Robust.Shared.Timing.Timer;
@@ -16,7 +17,7 @@ namespace Content.Client.ContextMenu.UI
///
/// This largely involves setting up timers to open and close sub-menus when hovering over other menu elements.
///
- public sealed class ContextMenuUIController : UIController, IOnStateEntered, IOnStateExited, IOnSystemChanged
+ public sealed class ContextMenuUIController : UIController, IOnStateEntered, IOnStateExited, IOnSystemChanged, IOnStateEntered, IOnStateExited
{
public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
@@ -42,18 +43,51 @@ public sealed class ContextMenuUIController : UIController, IOnStateEntered? OnSubMenuOpened;
public Action? OnContextKeyEvent;
+ private bool _setup;
+
public void OnStateEntered(GameplayState state)
{
+ Setup();
+ }
+
+ public void OnStateExited(GameplayState state)
+ {
+ Shutdown();
+ }
+
+ public void OnStateEntered(MappingState state)
+ {
+ Setup();
+ }
+
+ public void OnStateExited(MappingState state)
+ {
+ Shutdown();
+ }
+
+ public void Setup()
+ {
+ if (_setup)
+ return;
+
+ _setup = true;
+
RootMenu = new(this, null);
RootMenu.OnPopupHide += Close;
Menus.Push(RootMenu);
}
- public void OnStateExited(GameplayState state)
+ public void Shutdown()
{
+ if (!_setup)
+ return;
+
+ _setup = false;
+
Close();
RootMenu.OnPopupHide -= Close;
RootMenu.Dispose();
+ RootMenu = default!;
}
///
diff --git a/Content.Client/ContextMenu/UI/EntityMenuUIController.cs b/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
index a60619baa3..0462c095ba 100644
--- a/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
+++ b/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
@@ -170,7 +170,7 @@ private bool HandleOpenEntityMenu(in PointerInputCmdHandler.PointerInputCmdArgs
if (_combatMode.IsInCombatMode(args.Session?.AttachedEntity))
return false;
- var coords = args.Coordinates.ToMap(_entityManager, _xform);
+ var coords = _xform.ToMapCoordinates(args.Coordinates);
if (_verbSystem.TryGetEntityMenuEntities(coords, out var entities))
OpenRootMenu(entities);
diff --git a/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs b/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs
index e2c4d51ecd..e5be0b1811 100644
--- a/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs
+++ b/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs
@@ -2,12 +2,15 @@
using Content.Shared.Crayon;
using Content.Shared.Decals;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client.Crayon.UI
{
public sealed class CrayonBoundUserInterface : BoundUserInterface
{
+ [Dependency] private readonly IPrototypeManager _protoManager = default!;
+
[ViewVariables]
private CrayonWindow? _menu;
@@ -18,15 +21,29 @@ public CrayonBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey
protected override void Open()
{
base.Open();
- _menu = new CrayonWindow(this);
-
- _menu.OnClose += Close;
- var prototypeManager = IoCManager.Resolve();
- var crayonDecals = prototypeManager.EnumeratePrototypes().Where(x => x.Tags.Contains("crayon"));
- _menu.Populate(crayonDecals);
+ _menu = this.CreateWindow();
+ _menu.OnColorSelected += SelectColor;
+ _menu.OnSelected += Select;
+ PopulateCrayons();
_menu.OpenCenteredLeft();
}
+ private void PopulateCrayons()
+ {
+ var crayonDecals = _protoManager.EnumeratePrototypes().Where(x => x.Tags.Contains("crayon"));
+ _menu?.Populate(crayonDecals);
+ }
+
+ public override void OnProtoReload(PrototypesReloadedEventArgs args)
+ {
+ base.OnProtoReload(args);
+
+ if (!args.WasModified())
+ return;
+
+ PopulateCrayons();
+ }
+
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
@@ -43,16 +60,5 @@ public void SelectColor(Color color)
{
SendMessage(new CrayonColorMessage(color));
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- {
- _menu?.Close();
- _menu = null;
- }
- }
}
}
diff --git a/Content.Client/Crayon/UI/CrayonWindow.xaml.cs b/Content.Client/Crayon/UI/CrayonWindow.xaml.cs
index 2a5801ccf2..6ef282d219 100644
--- a/Content.Client/Crayon/UI/CrayonWindow.xaml.cs
+++ b/Content.Client/Crayon/UI/CrayonWindow.xaml.cs
@@ -18,18 +18,17 @@ namespace Content.Client.Crayon.UI
[GenerateTypedNameReferences]
public sealed partial class CrayonWindow : DefaultWindow
{
- public CrayonBoundUserInterface Owner { get; }
-
private Dictionary? _decals;
private string? _selected;
private Color _color;
- public CrayonWindow(CrayonBoundUserInterface owner)
+ public event Action? OnColorSelected;
+ public event Action? OnSelected;
+
+ public CrayonWindow()
{
RobustXamlLoader.Load(this);
- Owner = owner;
-
Search.OnTextChanged += _ => RefreshList();
ColorSelector.OnColorChanged += SelectColor;
}
@@ -38,16 +37,16 @@ private void SelectColor(Color color)
{
_color = color;
- Owner.SelectColor(color);
-
+ OnColorSelected?.Invoke(color);
RefreshList();
}
private void RefreshList()
{
// Clear
- Grid.RemoveAllChildren();
- if (_decals == null) return;
+ Grid.DisposeAllChildren();
+ if (_decals == null)
+ return;
var filter = Search.Text;
foreach (var (decal, tex) in _decals)
@@ -89,8 +88,8 @@ private void ButtonOnPressed(ButtonEventArgs obj)
{
if (obj.Button.Name == null) return;
- Owner.Select(obj.Button.Name);
_selected = obj.Button.Name;
+ OnSelected?.Invoke(_selected);
RefreshList();
}
diff --git a/Content.Client/CrewManifest/UI/CrewManifestSection.cs b/Content.Client/CrewManifest/UI/CrewManifestSection.cs
index 4b1b02cb75..29cf850e2b 100644
--- a/Content.Client/CrewManifest/UI/CrewManifestSection.cs
+++ b/Content.Client/CrewManifest/UI/CrewManifestSection.cs
@@ -51,7 +51,7 @@ public CrewManifestSection(
title.SetMessage(entry.JobTitle);
- if (prototypeManager.TryIndex(entry.JobIcon, out var jobIcon))
+ if (prototypeManager.TryIndex(entry.JobIcon, out var jobIcon))
{
var icon = new TextureRect()
{
diff --git a/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs b/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs
index b259e08e72..21aa54c962 100644
--- a/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs
+++ b/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs
@@ -7,10 +7,12 @@
using Content.Shared.StationRecords;
using Robust.Client.AutoGenerated;
using Robust.Client.Player;
+using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
+using System.Linq;
namespace Content.Client.CriminalRecords;
@@ -36,7 +38,6 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
public Action? OnDialogConfirmed;
private uint _maxLength;
- private bool _isPopulating;
private bool _access;
private uint? _selectedKey;
private CriminalRecord? _selectedRecord;
@@ -74,7 +75,7 @@ public CriminalRecordsConsoleWindow(EntityUid console, uint maxLength, IPlayerMa
RecordListing.OnItemSelected += args =>
{
- if (_isPopulating || RecordListing[args.ItemIndex].Metadata is not uint cast)
+ if (RecordListing[args.ItemIndex].Metadata is not uint cast)
return;
OnKeySelected?.Invoke(cast);
@@ -82,8 +83,7 @@ public CriminalRecordsConsoleWindow(EntityUid console, uint maxLength, IPlayerMa
RecordListing.OnItemDeselected += _ =>
{
- if (!_isPopulating)
- OnKeySelected?.Invoke(null);
+ OnKeySelected?.Invoke(null);
};
FilterType.OnItemSelected += eventArgs =>
@@ -133,13 +133,8 @@ public void UpdateState(CriminalRecordsConsoleState state)
FilterType.SelectId((int)_currentFilterType);
- // set up the records listing panel
- RecordListing.Clear();
-
- var hasRecords = state.RecordListing != null && state.RecordListing.Count > 0;
- NoRecords.Visible = !hasRecords;
- if (hasRecords)
- PopulateRecordListing(state.RecordListing!);
+ NoRecords.Visible = state.RecordListing == null || state.RecordListing.Count == 0;
+ PopulateRecordListing(state.RecordListing);
// set up the selected person's record
var selected = _selectedKey != null;
@@ -167,19 +162,59 @@ public void UpdateState(CriminalRecordsConsoleState state)
}
}
- private void PopulateRecordListing(Dictionary listing)
+ private void PopulateRecordListing(Dictionary? listing)
{
- _isPopulating = true;
+ if (listing == null)
+ {
+ RecordListing.Clear();
+ return;
+ }
+
+ var entries = listing.ToList();
+ entries.Sort((a, b) => string.Compare(a.Value, b.Value, StringComparison.Ordinal));
+ // `entries` now contains the definitive list of items which should be in
+ // our list of records and is in the order we want to present those items.
+
+ // Walk through the existing items in RecordListing and in the updated listing
+ // in parallel to synchronize the items in RecordListing with `entries`.
+ int i = RecordListing.Count - 1;
+ int j = entries.Count - 1;
+ while(i >= 0 && j >= 0)
+ {
+ var strcmp = string.Compare(RecordListing[i].Text, entries[j].Value, StringComparison.Ordinal);
+ if (strcmp == 0)
+ {
+ // This item exists in both RecordListing and `entries`. Nothing to do.
+ i--;
+ j--;
+ }
+ else if (strcmp > 0)
+ {
+ // Item exists in RecordListing, but not in `entries`. Remove it.
+ RecordListing.RemoveAt(i);
+ i--;
+ }
+ else if (strcmp < 0)
+ {
+ // A new entry which doesn't exist in RecordListing. Create it.
+ RecordListing.Insert(i + 1, new ItemList.Item(RecordListing){Text = entries[j].Value, Metadata = entries[j].Key});
+ j--;
+ }
+ }
- foreach (var (key, name) in listing)
+ // Any remaining items in RecordListing don't exist in `entries`, so remove them
+ while (i >= 0)
{
- var item = RecordListing.AddItem(name);
- item.Metadata = key;
- item.Selected = key == _selectedKey;
+ RecordListing.RemoveAt(i);
+ i--;
}
- _isPopulating = false;
- RecordListing.SortItemsByText();
+ // And finally, any remaining items in `entries`, don't exist in RecordListing. Create them.
+ while (j >= 0)
+ {
+ RecordListing.Insert(0, new ItemList.Item(RecordListing){Text = entries[j].Value, Metadata = entries[j].Key});
+ j--;
+ }
}
private void PopulateRecordContainer(GeneralStationRecord stationRecord, CriminalRecord criminalRecord)
@@ -211,10 +246,7 @@ private void AddStatusSelect(SecurityStatus status)
private void FilterListingOfRecords(string text = "")
{
- if (!_isPopulating)
- {
- OnFiltersChanged?.Invoke(_currentFilterType, text);
- }
+ OnFiltersChanged?.Invoke(_currentFilterType, text);
}
private void SetStatus(SecurityStatus status)
diff --git a/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs b/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs
index 845bd7c03d..07b6f57bdb 100644
--- a/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs
+++ b/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs
@@ -4,6 +4,7 @@
using Robust.Client.Input;
using Robust.Shared.Enums;
using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
namespace Content.Client.Decals.Overlays;
@@ -16,7 +17,7 @@ public sealed class DecalPlacementOverlay : Overlay
private readonly SharedTransformSystem _transform;
private readonly SpriteSystem _sprite;
- public override OverlaySpace Space => OverlaySpace.WorldSpace;
+ public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities;
public DecalPlacementOverlay(DecalPlacementSystem placement, SharedTransformSystem transform, SpriteSystem sprite)
{
@@ -24,6 +25,7 @@ public DecalPlacementOverlay(DecalPlacementSystem placement, SharedTransformSyst
_placement = placement;
_transform = transform;
_sprite = sprite;
+ ZIndex = 1000;
}
protected override void Draw(in OverlayDrawArgs args)
@@ -55,7 +57,7 @@ protected override void Draw(in OverlayDrawArgs args)
if (snap)
{
- localPos = (Vector2) localPos.Floored() + grid.TileSizeHalfVector;
+ localPos = localPos.Floored() + grid.TileSizeHalfVector;
}
// Nothing uses snap cardinals so probably don't need preview?
diff --git a/Content.Client/DisplacementMap/DisplacementMapSystem.cs b/Content.Client/DisplacementMap/DisplacementMapSystem.cs
new file mode 100644
index 0000000000..6db164a09f
--- /dev/null
+++ b/Content.Client/DisplacementMap/DisplacementMapSystem.cs
@@ -0,0 +1,65 @@
+using Content.Shared.DisplacementMap;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Shared.Serialization.Manager;
+
+namespace Content.Client.DisplacementMap;
+
+public sealed class DisplacementMapSystem : EntitySystem
+{
+ [Dependency] private readonly ISerializationManager _serialization = default!;
+
+ public bool TryAddDisplacement(DisplacementData data, SpriteComponent sprite, int index, string key, HashSet revealedLayers)
+ {
+ if (data.ShaderOverride != null)
+ sprite.LayerSetShader(index, data.ShaderOverride);
+
+ var displacementKey = $"{key}-displacement";
+ if (!revealedLayers.Add(displacementKey))
+ {
+ Log.Warning($"Duplicate key for DISPLACEMENT: {displacementKey}.");
+ return false;
+ }
+
+ //allows you not to write it every time in the YML
+ foreach (var pair in data.SizeMaps)
+ {
+ pair.Value.CopyToShaderParameters??= new()
+ {
+ LayerKey = "dummy",
+ ParameterTexture = "displacementMap",
+ ParameterUV = "displacementUV",
+ };
+ }
+
+ if (!data.SizeMaps.ContainsKey(32))
+ {
+ Log.Error($"DISPLACEMENT: {displacementKey} don't have 32x32 default displacement map");
+ return false;
+ }
+
+ // We choose a displacement map from the possible ones, matching the size with the original layer size.
+ // If there is no such a map, we use a standard 32 by 32 one
+ var displacementDataLayer = data.SizeMaps[EyeManager.PixelsPerMeter];
+ var actualRSI = sprite.LayerGetActualRSI(index);
+ if (actualRSI is not null)
+ {
+ if (actualRSI.Size.X != actualRSI.Size.Y)
+ Log.Warning($"DISPLACEMENT: {displacementKey} has a resolution that is not 1:1, things can look crooked");
+
+ var layerSize = actualRSI.Size.X;
+ if (data.SizeMaps.ContainsKey(layerSize))
+ displacementDataLayer = data.SizeMaps[layerSize];
+ }
+
+ var displacementLayer = _serialization.CreateCopy(displacementDataLayer, notNullableOverride: true);
+ displacementLayer.CopyToShaderParameters!.LayerKey = key;
+
+ sprite.AddLayer(displacementLayer, index);
+ sprite.LayerMapSet(displacementKey, index);
+
+ revealedLayers.Add(displacementKey);
+
+ return true;
+ }
+}
diff --git a/Content.Client/Disposal/UI/DisposalRouterBoundUserInterface.cs b/Content.Client/Disposal/UI/DisposalRouterBoundUserInterface.cs
index e8e77217ea..296e71d3a9 100644
--- a/Content.Client/Disposal/UI/DisposalRouterBoundUserInterface.cs
+++ b/Content.Client/Disposal/UI/DisposalRouterBoundUserInterface.cs
@@ -1,5 +1,6 @@
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
using static Content.Shared.Disposal.Components.SharedDisposalRouterComponent;
namespace Content.Client.Disposal.UI
@@ -21,20 +22,16 @@ protected override void Open()
{
base.Open();
- _window = new DisposalRouterWindow();
-
- _window.OpenCentered();
- _window.OnClose += Close;
+ _window = this.CreateWindow();
_window.Confirm.OnPressed += _ => ButtonPressed(UiAction.Ok, _window.TagInput.Text);
_window.TagInput.OnTextEntered += args => ButtonPressed(UiAction.Ok, args.Text);
-
}
private void ButtonPressed(UiAction action, string tag)
{
SendMessage(new UiActionMessage(action, tag));
- _window?.Close();
+ Close();
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -48,18 +45,5 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window?.UpdateState(cast);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- {
- _window?.Dispose();
- }
- }
-
-
}
-
}
diff --git a/Content.Client/Disposal/UI/DisposalTaggerBoundUserInterface.cs b/Content.Client/Disposal/UI/DisposalTaggerBoundUserInterface.cs
index 3aeed8dc80..7fc0eb8540 100644
--- a/Content.Client/Disposal/UI/DisposalTaggerBoundUserInterface.cs
+++ b/Content.Client/Disposal/UI/DisposalTaggerBoundUserInterface.cs
@@ -1,5 +1,6 @@
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
using static Content.Shared.Disposal.Components.SharedDisposalTaggerComponent;
namespace Content.Client.Disposal.UI
@@ -21,20 +22,17 @@ protected override void Open()
{
base.Open();
- _window = new DisposalTaggerWindow();
-
- _window.OpenCentered();
- _window.OnClose += Close;
+ _window = this.CreateWindow();
_window.Confirm.OnPressed += _ => ButtonPressed(UiAction.Ok, _window.TagInput.Text);
_window.TagInput.OnTextEntered += args => ButtonPressed(UiAction.Ok, args.Text);
-
}
private void ButtonPressed(UiAction action, string tag)
{
+ // TODO: This looks copy-pasted with the other mailing stuff...
SendMessage(new UiActionMessage(action, tag));
- _window?.Close();
+ Close();
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -48,18 +46,5 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window?.UpdateState(cast);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- {
- _window?.Dispose();
- }
- }
-
-
}
-
}
diff --git a/Content.Client/Disposal/UI/MailingUnitWindow.xaml.cs b/Content.Client/Disposal/UI/MailingUnitWindow.xaml.cs
index 797e20ae7d..489d749a0c 100644
--- a/Content.Client/Disposal/UI/MailingUnitWindow.xaml.cs
+++ b/Content.Client/Disposal/UI/MailingUnitWindow.xaml.cs
@@ -2,6 +2,7 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Timing;
namespace Content.Client.Disposal.UI
{
@@ -11,6 +12,8 @@ namespace Content.Client.Disposal.UI
[GenerateTypedNameReferences]
public sealed partial class MailingUnitWindow : DefaultWindow
{
+ public TimeSpan FullPressure;
+
public MailingUnitWindow()
{
RobustXamlLoader.Load(this);
@@ -26,6 +29,7 @@ public bool UpdateState(MailingUnitBoundUserInterfaceState state)
Title = Loc.GetString("ui-mailing-unit-window-title", ("tag", state.Tag ?? " "));
UnitState.Text = disposalState.UnitState;
+ FullPressure = disposalState.FullPressureTime;
var pressureReached = PressureBar.UpdatePressure(disposalState.FullPressureTime);
Power.Pressed = disposalState.Powered;
Engage.Pressed = disposalState.Engaged;
@@ -42,9 +46,10 @@ public bool UpdateState(MailingUnitBoundUserInterfaceState state)
return !disposalState.Powered || pressureReached;
}
- public bool UpdatePressure(TimeSpan stateFullPressureTime)
+ protected override void FrameUpdate(FrameEventArgs args)
{
- return PressureBar.UpdatePressure(stateFullPressureTime);
+ base.FrameUpdate(args);
+ PressureBar.UpdatePressure(FullPressure);
}
}
}
diff --git a/Content.Client/Doors/Electronics/DoorElectronicsBoundUserInterface.cs b/Content.Client/Doors/Electronics/DoorElectronicsBoundUserInterface.cs
index cd7ea717ce..9b7e23c03a 100644
--- a/Content.Client/Doors/Electronics/DoorElectronicsBoundUserInterface.cs
+++ b/Content.Client/Doors/Electronics/DoorElectronicsBoundUserInterface.cs
@@ -1,6 +1,7 @@
using Content.Shared.Access;
using Content.Shared.Doors.Electronics;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client.Doors.Electronics;
@@ -18,6 +19,23 @@ public DoorElectronicsBoundUserInterface(EntityUid owner, Enum uiKey) : base(own
protected override void Open()
{
base.Open();
+ _window = this.CreateWindow();
+ _window.OnAccessChanged += UpdateConfiguration;
+ Reset();
+ }
+
+ public override void OnProtoReload(PrototypesReloadedEventArgs args)
+ {
+ base.OnProtoReload(args);
+
+ if (!args.WasModified())
+ return;
+
+ Reset();
+ }
+
+ private void Reset()
+ {
List> accessLevels = new();
foreach (var accessLevel in _prototypeManager.EnumeratePrototypes())
@@ -29,10 +47,7 @@ protected override void Open()
}
accessLevels.Sort();
-
- _window = new DoorElectronicsConfigurationMenu(this, accessLevels, _prototypeManager);
- _window.OnClose += Close;
- _window.OpenCentered();
+ _window?.Reset(_prototypeManager, accessLevels);
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -44,14 +59,6 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window?.UpdateState(castState);
}
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing) return;
-
- _window?.Dispose();
- }
-
public void UpdateConfiguration(List> newAccessList)
{
SendMessage(new DoorElectronicsUpdateConfigurationMessage(newAccessList));
diff --git a/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml.cs b/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml.cs
index c01f13a462..2112a56297 100644
--- a/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml.cs
+++ b/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml.cs
@@ -15,22 +15,23 @@ namespace Content.Client.Doors.Electronics;
[GenerateTypedNameReferences]
public sealed partial class DoorElectronicsConfigurationMenu : FancyWindow
{
- private readonly DoorElectronicsBoundUserInterface _owner;
- private AccessLevelControl _buttonsList = new();
+ private readonly AccessLevelControl _buttonsList = new();
- public DoorElectronicsConfigurationMenu(DoorElectronicsBoundUserInterface ui, List> accessLevels, IPrototypeManager prototypeManager)
+ public event Action>>? OnAccessChanged;
+
+ public DoorElectronicsConfigurationMenu()
{
RobustXamlLoader.Load(this);
-
- _owner = ui;
-
- _buttonsList.Populate(accessLevels, prototypeManager);
AccessLevelControlContainer.AddChild(_buttonsList);
+ }
+
+ public void Reset(IPrototypeManager protoManager, List> accessLevels)
+ {
+ _buttonsList.Populate(accessLevels, protoManager);
- foreach (var (id, button) in _buttonsList.ButtonsList)
+ foreach (var button in _buttonsList.ButtonsList.Values)
{
- button.OnPressed += _ => _owner.UpdateConfiguration(
- _buttonsList.ButtonsList.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
+ button.OnPressed += _ => OnAccessChanged?.Invoke(_buttonsList.ButtonsList.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
}
}
diff --git a/Content.Client/Drowsiness/DrowsinessOverlay.cs b/Content.Client/Drowsiness/DrowsinessOverlay.cs
new file mode 100644
index 0000000000..a316f31ae6
--- /dev/null
+++ b/Content.Client/Drowsiness/DrowsinessOverlay.cs
@@ -0,0 +1,80 @@
+using Content.Shared.Drowsiness;
+using Content.Shared.StatusEffect;
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Shared.Enums;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Drowsiness;
+
+public sealed class DrowsinessOverlay : Overlay
+{
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IEntitySystemManager _sysMan = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ public override OverlaySpace Space => OverlaySpace.WorldSpace;
+ public override bool RequestScreenTexture => true;
+ private readonly ShaderInstance _drowsinessShader;
+
+ public float CurrentPower = 0.0f;
+
+ private const float PowerDivisor = 250.0f;
+ private const float Intensity = 0.2f; // for adjusting the visual scale
+ private float _visualScale = 0; // between 0 and 1
+
+ public DrowsinessOverlay()
+ {
+ IoCManager.InjectDependencies(this);
+ _drowsinessShader = _prototypeManager.Index("Drowsiness").InstanceUnique();
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ var playerEntity = _playerManager.LocalEntity;
+
+ if (playerEntity == null)
+ return;
+
+ if (!_entityManager.HasComponent(playerEntity)
+ || !_entityManager.TryGetComponent(playerEntity, out var status))
+ return;
+
+ var statusSys = _sysMan.GetEntitySystem();
+ if (!statusSys.TryGetTime(playerEntity.Value, SharedDrowsinessSystem.DrowsinessKey, out var time, status))
+ return;
+
+ var curTime = _timing.CurTime;
+ var timeLeft = (float)(time.Value.Item2 - curTime).TotalSeconds;
+
+ CurrentPower += 8f * (0.5f * timeLeft - CurrentPower) * args.DeltaSeconds / (timeLeft + 1);
+ }
+
+ protected override bool BeforeDraw(in OverlayDrawArgs args)
+ {
+ if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp))
+ return false;
+
+ if (args.Viewport.Eye != eyeComp.Eye)
+ return false;
+
+ _visualScale = Math.Clamp(CurrentPower / PowerDivisor, 0.0f, 1.0f);
+ return _visualScale > 0;
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ if (ScreenTexture == null)
+ return;
+
+ var handle = args.WorldHandle;
+ _drowsinessShader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
+ _drowsinessShader.SetParameter("Strength", _visualScale * Intensity);
+ handle.UseShader(_drowsinessShader);
+ handle.DrawRect(args.WorldBounds, Color.White);
+ handle.UseShader(null);
+ }
+}
diff --git a/Content.Client/Drowsiness/DrowsinessSystem.cs b/Content.Client/Drowsiness/DrowsinessSystem.cs
new file mode 100644
index 0000000000..bc8862b19d
--- /dev/null
+++ b/Content.Client/Drowsiness/DrowsinessSystem.cs
@@ -0,0 +1,53 @@
+using Content.Shared.Drowsiness;
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Shared.Player;
+
+namespace Content.Client.Drowsiness;
+
+public sealed class DrowsinessSystem : SharedDrowsinessSystem
+{
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly IOverlayManager _overlayMan = default!;
+
+ private DrowsinessOverlay _overlay = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnDrowsinessInit);
+ SubscribeLocalEvent(OnDrowsinessShutdown);
+
+ SubscribeLocalEvent(OnPlayerAttached);
+ SubscribeLocalEvent(OnPlayerDetached);
+
+ _overlay = new();
+ }
+
+ private void OnPlayerAttached(EntityUid uid, DrowsinessComponent component, LocalPlayerAttachedEvent args)
+ {
+ _overlayMan.AddOverlay(_overlay);
+ }
+
+ private void OnPlayerDetached(EntityUid uid, DrowsinessComponent component, LocalPlayerDetachedEvent args)
+ {
+ _overlay.CurrentPower = 0;
+ _overlayMan.RemoveOverlay(_overlay);
+ }
+
+ private void OnDrowsinessInit(EntityUid uid, DrowsinessComponent component, ComponentInit args)
+ {
+ if (_player.LocalEntity == uid)
+ _overlayMan.AddOverlay(_overlay);
+ }
+
+ private void OnDrowsinessShutdown(EntityUid uid, DrowsinessComponent component, ComponentShutdown args)
+ {
+ if (_player.LocalEntity == uid)
+ {
+ _overlay.CurrentPower = 0;
+ _overlayMan.RemoveOverlay(_overlay);
+ }
+ }
+}
diff --git a/Content.Client/Explosion/ExplosionOverlay.cs b/Content.Client/Explosion/ExplosionOverlay.cs
index 8cf7447a5d..a005d0317b 100644
--- a/Content.Client/Explosion/ExplosionOverlay.cs
+++ b/Content.Client/Explosion/ExplosionOverlay.cs
@@ -16,15 +16,19 @@ public sealed class ExplosionOverlay : Overlay
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
+ private readonly SharedTransformSystem _transformSystem;
+ private SharedAppearanceSystem _appearance;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
private ShaderInstance _shader;
- public ExplosionOverlay()
+ public ExplosionOverlay(SharedAppearanceSystem appearanceSystem)
{
IoCManager.InjectDependencies(this);
_shader = _proto.Index("unshaded").Instance();
+ _transformSystem = _entMan.System();
+ _appearance = appearanceSystem;
}
protected override void Draw(in OverlayDrawArgs args)
@@ -33,15 +37,14 @@ protected override void Draw(in OverlayDrawArgs args)
drawHandle.UseShader(_shader);
var xforms = _entMan.GetEntityQuery();
- var query = _entMan
- .EntityQuery(true);
+ var query = _entMan.EntityQueryEnumerator();
- foreach (var (visuals, textures, appearance) in query)
+ while (query.MoveNext(out var uid, out var visuals, out var textures))
{
if (visuals.Epicenter.MapId != args.MapId)
continue;
- if (!appearance.TryGetData(ExplosionAppearanceData.Progress, out int index))
+ if (!_appearance.TryGetData(uid, ExplosionAppearanceData.Progress, out int index))
continue;
index = Math.Min(index, visuals.Intensity.Count - 1);
@@ -67,7 +70,7 @@ private void DrawExplosion(
continue;
var xform = xforms.GetComponent(gridId);
- var (_, _, worldMatrix, invWorldMatrix) = xform.GetWorldPositionRotationMatrixWithInv(xforms);
+ var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(xform, xforms);
gridBounds = invWorldMatrix.TransformBox(worldBounds).Enlarged(grid.TileSize * 2);
drawHandle.SetTransform(worldMatrix);
diff --git a/Content.Client/Explosion/ExplosionOverlaySystem.cs b/Content.Client/Explosion/ExplosionOverlaySystem.cs
index fe552af0e7..7e43bbdc38 100644
--- a/Content.Client/Explosion/ExplosionOverlaySystem.cs
+++ b/Content.Client/Explosion/ExplosionOverlaySystem.cs
@@ -20,6 +20,7 @@ public sealed class ExplosionOverlaySystem : EntitySystem
[Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly SharedPointLightSystem _lights = default!;
[Dependency] private readonly IMapManager _mapMan = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize()
{
@@ -28,7 +29,7 @@ public override void Initialize()
SubscribeLocalEvent(OnExplosionInit);
SubscribeLocalEvent(OnCompRemove);
SubscribeLocalEvent(OnExplosionHandleState);
- _overlayMan.AddOverlay(new ExplosionOverlay());
+ _overlayMan.AddOverlay(new ExplosionOverlay(_appearance));
}
private void OnExplosionHandleState(EntityUid uid, ExplosionVisualsComponent component, ref ComponentHandleState args)
diff --git a/Content.Client/Explosion/ExplosionSystem.cs b/Content.Client/Explosion/ExplosionSystem.cs
new file mode 100644
index 0000000000..a2ed2d50e0
--- /dev/null
+++ b/Content.Client/Explosion/ExplosionSystem.cs
@@ -0,0 +1,8 @@
+using Content.Shared.Explosion.EntitySystems;
+
+namespace Content.Client.Explosion.EntitySystems;
+
+public sealed class ExplosionSystem : SharedExplosionSystem
+{
+
+}
diff --git a/Content.Client/Extinguisher/FireExtinguisherComponent.cs b/Content.Client/Extinguisher/FireExtinguisherComponent.cs
deleted file mode 100644
index 324b05a93d..0000000000
--- a/Content.Client/Extinguisher/FireExtinguisherComponent.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using Content.Shared.Extinguisher;
-using Robust.Shared.GameStates;
-
-namespace Content.Client.Extinguisher;
-
-[RegisterComponent]
-public sealed partial class FireExtinguisherComponent : SharedFireExtinguisherComponent;
diff --git a/Content.Client/Fax/AdminUI/AdminFaxEui.cs b/Content.Client/Fax/AdminUI/AdminFaxEui.cs
index ace3f3eb7b..452c54eb79 100644
--- a/Content.Client/Fax/AdminUI/AdminFaxEui.cs
+++ b/Content.Client/Fax/AdminUI/AdminFaxEui.cs
@@ -16,7 +16,7 @@ public AdminFaxEui()
_window.OnClose += () => SendMessage(new AdminFaxEuiMsg.Close());
_window.OnFollowFax += entity => SendMessage(new AdminFaxEuiMsg.Follow(entity));
_window.OnMessageSend += args => SendMessage(new AdminFaxEuiMsg.Send(args.entity, args.title,
- args.stampedBy, args.message, args.stampSprite, args.stampColor));
+ args.stampedBy, args.message, args.stampSprite, args.stampColor, args.locked));
}
public override void Opened()
diff --git a/Content.Client/Fax/AdminUI/AdminFaxWindow.xaml b/Content.Client/Fax/AdminUI/AdminFaxWindow.xaml
index d469a0e9d3..dc4092a3b5 100644
--- a/Content.Client/Fax/AdminUI/AdminFaxWindow.xaml
+++ b/Content.Client/Fax/AdminUI/AdminFaxWindow.xaml
@@ -23,7 +23,7 @@
-
-
+
+
diff --git a/Content.Client/Fax/AdminUI/AdminFaxWindow.xaml.cs b/Content.Client/Fax/AdminUI/AdminFaxWindow.xaml.cs
index c1fba48309..698b3114b7 100644
--- a/Content.Client/Fax/AdminUI/AdminFaxWindow.xaml.cs
+++ b/Content.Client/Fax/AdminUI/AdminFaxWindow.xaml.cs
@@ -14,7 +14,7 @@ public sealed partial class AdminFaxWindow : DefaultWindow
{
private const string StampsRsiPath = "/Textures/Objects/Misc/bureaucracy.rsi";
- public Action<(NetEntity entity, string title, string stampedBy, string message, string stampSprite, Color stampColor)>? OnMessageSend;
+ public Action<(NetEntity entity, string title, string stampedBy, string message, string stampSprite, Color stampColor, bool locked)>? OnMessageSend;
public Action? OnFollowFax;
[Dependency] private readonly IResourceCache _resCache = default!;
@@ -98,6 +98,7 @@ private void SendMessage(BaseButton.ButtonEventArgs obj)
var from = FromEdit.Text;
var stampColor = StampColorSelector.Color;
- OnMessageSend?.Invoke((faxEntity.Value, title, from, message, stamp, stampColor));
+ var locked = LockPageCheckbox.Pressed;
+ OnMessageSend?.Invoke((faxEntity.Value, title, from, message, stamp, stampColor, locked));
}
}
diff --git a/Content.Client/Fax/System/FaxVisualsSystem.cs b/Content.Client/Fax/System/FaxVisualsSystem.cs
index 892aec1d95..e752fbf48e 100644
--- a/Content.Client/Fax/System/FaxVisualsSystem.cs
+++ b/Content.Client/Fax/System/FaxVisualsSystem.cs
@@ -25,24 +25,30 @@ private void OnAppearanceChanged(EntityUid uid, FaxMachineComponent component, r
if (args.Sprite == null)
return;
- if (_appearance.TryGetData(uid, FaxMachineVisuals.VisualState, out FaxMachineVisualState visuals) && visuals == FaxMachineVisualState.Inserting)
+ if (_player.HasRunningAnimation(uid, "faxecute"))
+ return;
+
+ if (_appearance.TryGetData(uid, FaxMachineVisuals.VisualState, out FaxMachineVisualState visuals) &&
+ visuals == FaxMachineVisualState.Inserting)
{
- _player.Play(uid, new Animation()
- {
- Length = TimeSpan.FromSeconds(2.4),
- AnimationTracks =
+ _player.Play(uid,
+ new Animation()
{
- new AnimationTrackSpriteFlick()
+ Length = TimeSpan.FromSeconds(2.4),
+ AnimationTracks =
{
- LayerKey = FaxMachineVisuals.VisualState,
- KeyFrames =
+ new AnimationTrackSpriteFlick()
{
- new AnimationTrackSpriteFlick.KeyFrame(component.InsertingState, 0f),
- new AnimationTrackSpriteFlick.KeyFrame("icon", 2.4f),
- }
- }
- }
- }, "faxecute");
+ LayerKey = FaxMachineVisuals.VisualState,
+ KeyFrames =
+ {
+ new AnimationTrackSpriteFlick.KeyFrame(component.InsertingState, 0f),
+ new AnimationTrackSpriteFlick.KeyFrame("icon", 2.4f),
+ },
+ },
+ },
+ },
+ "faxecute");
}
}
}
diff --git a/Content.Client/Fax/UI/FaxBoundUi.cs b/Content.Client/Fax/UI/FaxBoundUi.cs
index a95066a3b5..ca2e834b4f 100644
--- a/Content.Client/Fax/UI/FaxBoundUi.cs
+++ b/Content.Client/Fax/UI/FaxBoundUi.cs
@@ -25,10 +25,7 @@ protected override void Open()
{
base.Open();
- _window = new FaxWindow();
- _window.OpenCentered();
-
- _window.OnClose += Close;
+ _window = this.CreateWindow();
_window.FileButtonPressed += OnFileButtonPressed;
_window.CopyButtonPressed += OnCopyButtonPressed;
_window.SendButtonPressed += OnSendButtonPressed;
@@ -104,11 +101,4 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window.UpdateState(cast);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (disposing)
- _window?.Dispose();
- }
}
diff --git a/Content.Client/Flash/FlashOverlay.cs b/Content.Client/Flash/FlashOverlay.cs
index 9ea00275e8..046be2aa62 100644
--- a/Content.Client/Flash/FlashOverlay.cs
+++ b/Content.Client/Flash/FlashOverlay.cs
@@ -1,27 +1,22 @@
using Content.Shared.Flash;
using Content.Shared.Flash.Components;
using Content.Shared.StatusEffect;
-using Content.Client.Viewport;
using Robust.Client.Graphics;
-using Robust.Client.State;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
-using SixLabors.ImageSharp.PixelFormats;
namespace Content.Client.Flash
{
public sealed class FlashOverlay : Overlay
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly IClyde _displayManager = default!;
- [Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
- private readonly StatusEffectsSystem _statusSys;
+ private readonly StatusEffectsSystem _statusSys;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _shader;
@@ -56,20 +51,6 @@ protected override void FrameUpdate(FrameEventArgs args)
PercentComplete = timeDone / lastsFor;
}
- public void ReceiveFlash()
- {
- if (_stateManager.CurrentState is IMainViewportState state)
- {
- // take a screenshot
- // note that the callback takes a while and ScreenshotTexture will be null the first few Draws
- state.Viewport.Viewport.Screenshot(image =>
- {
- var rgba32Image = image.CloneAs(SixLabors.ImageSharp.Configuration.Default);
- ScreenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image);
- });
- }
- }
-
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp))
@@ -82,6 +63,11 @@ protected override bool BeforeDraw(in OverlayDrawArgs args)
protected override void Draw(in OverlayDrawArgs args)
{
+ if (RequestScreenTexture && ScreenTexture != null)
+ {
+ ScreenshotTexture = ScreenTexture;
+ RequestScreenTexture = false; // we only need the first frame, so we can stop the request now for performance reasons
+ }
if (ScreenshotTexture == null)
return;
@@ -96,7 +82,6 @@ protected override void DisposeBehavior()
{
base.DisposeBehavior();
ScreenshotTexture = null;
- PercentComplete = 1.0f;
}
}
}
diff --git a/Content.Client/Flash/FlashSystem.cs b/Content.Client/Flash/FlashSystem.cs
index 9a0579f6aa..146d84b990 100644
--- a/Content.Client/Flash/FlashSystem.cs
+++ b/Content.Client/Flash/FlashSystem.cs
@@ -22,7 +22,6 @@ public override void Initialize()
SubscribeLocalEvent(OnShutdown);
SubscribeLocalEvent(OnPlayerAttached);
SubscribeLocalEvent(OnPlayerDetached);
- SubscribeLocalEvent(OnStatusAdded);
_overlay = new();
}
@@ -34,8 +33,8 @@ private void OnPlayerAttached(EntityUid uid, FlashedComponent component, LocalPl
private void OnPlayerDetached(EntityUid uid, FlashedComponent component, LocalPlayerDetachedEvent args)
{
- _overlay.PercentComplete = 1.0f;
_overlay.ScreenshotTexture = null;
+ _overlay.RequestScreenTexture = false;
_overlayMan.RemoveOverlay(_overlay);
}
@@ -43,6 +42,7 @@ private void OnInit(EntityUid uid, FlashedComponent component, ComponentInit arg
{
if (_player.LocalEntity == uid)
{
+ _overlay.RequestScreenTexture = true;
_overlayMan.AddOverlay(_overlay);
}
}
@@ -51,17 +51,9 @@ private void OnShutdown(EntityUid uid, FlashedComponent component, ComponentShut
{
if (_player.LocalEntity == uid)
{
- _overlay.PercentComplete = 1.0f;
_overlay.ScreenshotTexture = null;
+ _overlay.RequestScreenTexture = false;
_overlayMan.RemoveOverlay(_overlay);
}
}
-
- private void OnStatusAdded(EntityUid uid, FlashedComponent component, StatusEffectAddedEvent args)
- {
- if (_player.LocalEntity == uid && args.Key == FlashedKey)
- {
- _overlay.ReceiveFlash();
- }
- }
}
diff --git a/Content.Client/Fluids/PuddleOverlay.cs b/Content.Client/Fluids/PuddleOverlay.cs
index a8c1d35510..caa5a92580 100644
--- a/Content.Client/Fluids/PuddleOverlay.cs
+++ b/Content.Client/Fluids/PuddleOverlay.cs
@@ -14,6 +14,7 @@ public sealed class PuddleOverlay : Overlay
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
private readonly PuddleDebugOverlaySystem _debugOverlaySystem;
+ private readonly SharedTransformSystem _transformSystem;
private readonly Color _heavyPuddle = new(0, 255, 255, 50);
private readonly Color _mediumPuddle = new(0, 150, 255, 50);
@@ -29,6 +30,7 @@ public PuddleOverlay()
_debugOverlaySystem = _entitySystemManager.GetEntitySystem();
var cache = IoCManager.Resolve();
_font = new VectorFont(cache.GetResource("/Fonts/NotoSans/NotoSans-Regular.ttf"), 8);
+ _transformSystem = _entityManager.System();
}
protected override void Draw(in OverlayDrawArgs args)
@@ -56,7 +58,7 @@ private void DrawWorld(in OverlayDrawArgs args)
continue;
var gridXform = xformQuery.GetComponent(gridId);
- var (_, _, worldMatrix, invWorldMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv(xformQuery);
+ var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform, xformQuery);
gridBounds = invWorldMatrix.TransformBox(args.WorldBounds).Enlarged(mapGrid.TileSize * 2);
drawHandle.SetTransform(worldMatrix);
@@ -89,7 +91,7 @@ private void DrawScreen(in OverlayDrawArgs args)
continue;
var gridXform = xformQuery.GetComponent(gridId);
- var (_, _, matrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv(xformQuery);
+ var (_, _, matrix, invMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform, xformQuery);
var gridBounds = invMatrix.TransformBox(args.WorldBounds).Enlarged(mapGrid.TileSize * 2);
foreach (var debugOverlayData in _debugOverlaySystem.GetData(gridId))
diff --git a/Content.Client/Forensics/ForensicScannerBoundUserInterface.cs b/Content.Client/Forensics/ForensicScannerBoundUserInterface.cs
index ba49f11ea0..08596b04e6 100644
--- a/Content.Client/Forensics/ForensicScannerBoundUserInterface.cs
+++ b/Content.Client/Forensics/ForensicScannerBoundUserInterface.cs
@@ -1,6 +1,7 @@
using Robust.Client.GameObjects;
using Robust.Shared.Timing;
using Content.Shared.Forensics;
+using Robust.Client.UserInterface;
namespace Content.Client.Forensics
{
@@ -21,11 +22,9 @@ public ForensicScannerBoundUserInterface(EntityUid owner, Enum uiKey) : base(own
protected override void Open()
{
base.Open();
- _window = new ForensicScannerMenu();
- _window.OnClose += Close;
+ _window = this.CreateWindow();
_window.Print.OnPressed += _ => Print();
_window.Clear.OnPressed += _ => Clear();
- _window.OpenCentered();
}
private void Print()
@@ -62,6 +61,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
_printCooldown = cast.PrintCooldown;
+ // TODO: Fix this
if (cast.PrintReadyAt > _gameTiming.CurTime)
Timer.Spawn(cast.PrintReadyAt - _gameTiming.CurTime, () =>
{
@@ -71,14 +71,5 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window.UpdateState(cast);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
-
- _window?.Dispose();
- }
}
}
diff --git a/Content.Client/Forensics/ForensicScannerMenu.xaml.cs b/Content.Client/Forensics/ForensicScannerMenu.xaml.cs
index 5f7f8e0056..dd013ed235 100644
--- a/Content.Client/Forensics/ForensicScannerMenu.xaml.cs
+++ b/Content.Client/Forensics/ForensicScannerMenu.xaml.cs
@@ -54,10 +54,16 @@ public void UpdateState(ForensicScannerBoundUserInterfaceState msg)
}
text.AppendLine();
text.AppendLine(Loc.GetString("forensic-scanner-interface-dnas"));
- foreach (var dna in msg.DNAs)
+ foreach (var dna in msg.TouchDNAs)
{
text.AppendLine(dna);
}
+ foreach (var dna in msg.SolutionDNAs)
+ {
+ if (msg.TouchDNAs.Contains(dna))
+ continue;
+ text.AppendLine(dna);
+ }
text.AppendLine();
text.AppendLine(Loc.GetString("forensic-scanner-interface-residues"));
foreach (var residue in msg.Residues)
diff --git a/Content.Client/Gameplay/GameplayStateBase.cs b/Content.Client/Gameplay/GameplayStateBase.cs
index 6236cd8e95..1e6fd485b3 100644
--- a/Content.Client/Gameplay/GameplayStateBase.cs
+++ b/Content.Client/Gameplay/GameplayStateBase.cs
@@ -2,6 +2,7 @@
using System.Numerics;
using Content.Client.Clickable;
using Content.Client.UserInterface;
+using Content.Client.Viewport;
using Content.Shared.Input;
using Robust.Client.ComponentTrees;
using Robust.Client.GameObjects;
@@ -13,11 +14,13 @@
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Console;
+using Robust.Shared.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Timing;
+using YamlDotNet.Serialization.TypeInspectors;
namespace Content.Client.Gameplay
{
@@ -98,17 +101,40 @@ private bool HandleInspect(ICommonSession? session, EntityCoordinates coords, En
public EntityUid? GetClickedEntity(MapCoordinates coordinates)
{
- var first = GetClickableEntities(coordinates).FirstOrDefault();
+ return GetClickedEntity(coordinates, _eyeManager.CurrentEye);
+ }
+
+ public EntityUid? GetClickedEntity(MapCoordinates coordinates, IEye? eye)
+ {
+ if (eye == null)
+ return null;
+
+ var first = GetClickableEntities(coordinates, eye).FirstOrDefault();
return first.IsValid() ? first : null;
}
public IEnumerable GetClickableEntities(EntityCoordinates coordinates)
{
- return GetClickableEntities(coordinates.ToMap(_entityManager, _entitySystemManager.GetEntitySystem()));
+ var transformSystem = _entitySystemManager.GetEntitySystem();
+ return GetClickableEntities(transformSystem.ToMapCoordinates(coordinates));
}
public IEnumerable GetClickableEntities(MapCoordinates coordinates)
{
+ return GetClickableEntities(coordinates, _eyeManager.CurrentEye);
+ }
+
+ public IEnumerable GetClickableEntities(MapCoordinates coordinates, IEye? eye)
+ {
+ /*
+ * TODO:
+ * 1. Stuff like MeleeWeaponSystem need an easy way to hook into viewport specific entities / entities under mouse
+ * 2. Cleanup the mess around InteractionOutlineSystem + below the keybind click detection.
+ */
+
+ if (eye == null)
+ return Array.Empty();
+
// Find all the entities intersecting our click
var spriteTree = _entityManager.EntitySysManager.GetEntitySystem();
var entities = spriteTree.QueryAabb(coordinates.MapId, Box2.CenteredAround(coordinates.Position, new Vector2(1, 1)));
@@ -116,15 +142,12 @@ public IEnumerable GetClickableEntities(MapCoordinates coordinates)
// Check the entities against whether or not we can click them
var foundEntities = new List<(EntityUid, int, uint, float)>(entities.Count);
var clickQuery = _entityManager.GetEntityQuery();
- var xformQuery = _entityManager.GetEntityQuery();
-
- // TODO: Smelly
- var eye = _eyeManager.CurrentEye;
+ var clickables = _entityManager.System();
foreach (var entity in entities)
{
if (clickQuery.TryGetComponent(entity.Uid, out var component) &&
- component.CheckClick(entity.Component, entity.Transform, xformQuery, coordinates.Position, eye, out var drawDepthClicked, out var renderOrder, out var bottom))
+ clickables.CheckClick((entity.Uid, component, entity.Component, entity.Transform), coordinates.Position, eye, out var drawDepthClicked, out var renderOrder, out var bottom))
{
foundEntities.Add((entity.Uid, drawDepthClicked, renderOrder, bottom));
}
@@ -184,15 +207,27 @@ protected virtual void OnKeyBindStateChanged(ViewportBoundKeyEventArgs args)
EntityCoordinates coordinates = default;
EntityUid? entityToClick = null;
- if (args.Viewport is IViewportControl vp)
+ if (args.Viewport is IViewportControl vp && kArgs.PointerLocation.IsValid)
{
var mousePosWorld = vp.PixelToMap(kArgs.PointerLocation.Position);
- entityToClick = GetClickedEntity(mousePosWorld);
+
+ if (vp is ScalingViewport svp)
+ {
+ entityToClick = GetClickedEntity(mousePosWorld, svp.Eye);
+ }
+ else
+ {
+ entityToClick = GetClickedEntity(mousePosWorld);
+ }
coordinates = _mapManager.TryFindGridAt(mousePosWorld, out _, out var grid) ?
grid.MapToGrid(mousePosWorld) :
EntityCoordinates.FromMap(_mapManager, mousePosWorld);
}
+ else
+ {
+ coordinates = EntityCoordinates.Invalid;
+ }
var message = new ClientFullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId)
{
diff --git a/Content.Client/Gateway/UI/GatewayBoundUserInterface.cs b/Content.Client/Gateway/UI/GatewayBoundUserInterface.cs
index fdb3cdbc01..457b70ca7c 100644
--- a/Content.Client/Gateway/UI/GatewayBoundUserInterface.cs
+++ b/Content.Client/Gateway/UI/GatewayBoundUserInterface.cs
@@ -1,6 +1,7 @@
using Content.Shared.Gateway;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Gateway.UI;
@@ -17,24 +18,13 @@ protected override void Open()
{
base.Open();
- _window = new GatewayWindow(EntMan.GetNetEntity(Owner));
+ _window = this.CreateWindow();
+ _window.SetEntity(EntMan.GetNetEntity(Owner));
_window.OpenPortal += destination =>
{
SendMessage(new GatewayOpenPortalMessage(destination));
};
- _window.OnClose += Close;
- _window?.OpenCentered();
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (disposing)
- {
- _window?.Dispose();
- _window = null;
- }
}
protected override void UpdateState(BoundUserInterfaceState state)
diff --git a/Content.Client/Gateway/UI/GatewayWindow.xaml.cs b/Content.Client/Gateway/UI/GatewayWindow.xaml.cs
index 889dd6e175..1c779b2b35 100644
--- a/Content.Client/Gateway/UI/GatewayWindow.xaml.cs
+++ b/Content.Client/Gateway/UI/GatewayWindow.xaml.cs
@@ -22,7 +22,7 @@ public sealed partial class GatewayWindow : FancyWindow,
public event Action? OpenPortal;
private List _destinations = new();
- public readonly NetEntity Owner;
+ public NetEntity Owner;
private NetEntity? _current;
private TimeSpan _nextReady;
@@ -46,16 +46,20 @@ public sealed partial class GatewayWindow : FancyWindow,
///
private bool _isCooldownPending = true;
- public GatewayWindow(NetEntity netEntity)
+ public GatewayWindow()
{
RobustXamlLoader.Load(this);
var dependencies = IoCManager.Instance!;
_timing = dependencies.Resolve();
- Owner = netEntity;
NextUnlockBar.ForegroundStyleBoxOverride = new StyleBoxFlat(Color.FromHex("#C74EBD"));
}
+ public void SetEntity(NetEntity entity)
+ {
+
+ }
+
public void UpdateState(GatewayBoundUserInterfaceState state)
{
_destinations = state.Destinations;
diff --git a/Content.Client/Ghost/GhostRoleRadioBoundUserInterface.cs b/Content.Client/Ghost/GhostRoleRadioBoundUserInterface.cs
new file mode 100644
index 0000000000..52ea835f4a
--- /dev/null
+++ b/Content.Client/Ghost/GhostRoleRadioBoundUserInterface.cs
@@ -0,0 +1,29 @@
+using Content.Shared.Ghost.Roles;
+using Robust.Client.UserInterface;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Ghost;
+
+public sealed class GhostRoleRadioBoundUserInterface : BoundUserInterface
+{
+ private GhostRoleRadioMenu? _ghostRoleRadioMenu;
+
+ public GhostRoleRadioBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ IoCManager.InjectDependencies(this);
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _ghostRoleRadioMenu = this.CreateWindow();
+ _ghostRoleRadioMenu.SetEntity(Owner);
+ _ghostRoleRadioMenu.SendGhostRoleRadioMessageAction += SendGhostRoleRadioMessage;
+ }
+
+ private void SendGhostRoleRadioMessage(ProtoId protoId)
+ {
+ SendMessage(new GhostRoleRadioMessage(protoId));
+ }
+}
diff --git a/Content.Client/Ghost/GhostRoleRadioMenu.xaml b/Content.Client/Ghost/GhostRoleRadioMenu.xaml
new file mode 100644
index 0000000000..c35ee128c5
--- /dev/null
+++ b/Content.Client/Ghost/GhostRoleRadioMenu.xaml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/Content.Client/Ghost/GhostRoleRadioMenu.xaml.cs b/Content.Client/Ghost/GhostRoleRadioMenu.xaml.cs
new file mode 100644
index 0000000000..3897b1b949
--- /dev/null
+++ b/Content.Client/Ghost/GhostRoleRadioMenu.xaml.cs
@@ -0,0 +1,106 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Ghost.Roles;
+using Content.Shared.Ghost.Roles.Components;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using System.Numerics;
+
+namespace Content.Client.Ghost;
+
+public sealed partial class GhostRoleRadioMenu : RadialMenu
+{
+ [Dependency] private readonly EntityManager _entityManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ public event Action>? SendGhostRoleRadioMessageAction;
+
+ public EntityUid Entity { get; set; }
+
+ public GhostRoleRadioMenu()
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+ }
+
+ public void SetEntity(EntityUid uid)
+ {
+ Entity = uid;
+ RefreshUI();
+ }
+
+ private void RefreshUI()
+ {
+ // The main control that will contain all the clickable options
+ var main = FindControl("Main");
+
+ // The purpose of this radial UI is for ghost role radios that allow you to select
+ // more than one potential option, such as with kobolds/lizards.
+ // This means that it won't show anything if SelectablePrototypes is empty.
+ if (!_entityManager.TryGetComponent(Entity, out var comp))
+ return;
+
+ foreach (var ghostRoleProtoString in comp.SelectablePrototypes)
+ {
+ // For each prototype we find we want to create a button that uses the name of the ghost role
+ // as the hover tooltip, and the icon is taken from either the ghost role entityprototype
+ // or the indicated icon entityprototype.
+ if (!_prototypeManager.TryIndex(ghostRoleProtoString, out var ghostRoleProto))
+ continue;
+
+ var button = new GhostRoleRadioMenuButton()
+ {
+ StyleClasses = { "RadialMenuButton" },
+ SetSize = new Vector2(64, 64),
+ ToolTip = Loc.GetString(ghostRoleProto.Name),
+ ProtoId = ghostRoleProto.ID,
+ };
+
+ var entProtoView = new EntityPrototypeView()
+ {
+ SetSize = new Vector2(48, 48),
+ VerticalAlignment = VAlignment.Center,
+ HorizontalAlignment = HAlignment.Center,
+ Stretch = SpriteView.StretchMode.Fill
+ };
+
+ // pick the icon if it exists, otherwise fallback to the ghost role's entity
+ if (_prototypeManager.TryIndex(ghostRoleProto.IconPrototype, out var iconProto))
+ entProtoView.SetPrototype(iconProto);
+ else
+ entProtoView.SetPrototype(ghostRoleProto.EntityPrototype);
+
+ button.AddChild(entProtoView);
+ main.AddChild(button);
+ AddGhostRoleRadioMenuButtonOnClickActions(main);
+ }
+ }
+
+ private void AddGhostRoleRadioMenuButtonOnClickActions(Control control)
+ {
+ var mainControl = control as RadialContainer;
+
+ if (mainControl == null)
+ return;
+
+ foreach (var child in mainControl.Children)
+ {
+ var castChild = child as GhostRoleRadioMenuButton;
+
+ if (castChild == null)
+ continue;
+
+ castChild.OnButtonUp += _ =>
+ {
+ SendGhostRoleRadioMessageAction?.Invoke(castChild.ProtoId);
+ Close();
+ };
+ }
+ }
+}
+
+public sealed class GhostRoleRadioMenuButton : RadialMenuTextureButton
+{
+ public ProtoId ProtoId { get; set; }
+}
diff --git a/Content.Client/Gravity/UI/GravityGeneratorBoundUserInterface.cs b/Content.Client/Gravity/UI/GravityGeneratorBoundUserInterface.cs
index d72da3e812..32b40747d5 100644
--- a/Content.Client/Gravity/UI/GravityGeneratorBoundUserInterface.cs
+++ b/Content.Client/Gravity/UI/GravityGeneratorBoundUserInterface.cs
@@ -1,6 +1,6 @@
using Content.Shared.Gravity;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Gravity.UI
{
@@ -18,17 +18,8 @@ protected override void Open()
{
base.Open();
- _window = new GravityGeneratorWindow(this);
-
- /*
- _window.Switch.OnPressed += _ =>
- {
- SendMessage(new SharedGravityGeneratorComponent.SwitchGeneratorMessage(!IsOn));
- };
- */
-
- _window.OpenCentered();
- _window.OnClose += Close;
+ _window = this.CreateWindow();
+ _window.SetEntity(Owner);
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -39,14 +30,6 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window?.UpdateState(castState);
}
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing) return;
-
- _window?.Dispose();
- }
-
public void SetPowerSwitch(bool on)
{
SendMessage(new SharedGravityGeneratorComponent.SwitchGeneratorMessage(on));
diff --git a/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml.cs b/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml.cs
index 75f8eb479b..6f04133b59 100644
--- a/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml.cs
+++ b/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml.cs
@@ -12,22 +12,23 @@ public sealed partial class GravityGeneratorWindow : FancyWindow
{
private readonly ButtonGroup _buttonGroup = new();
- private readonly GravityGeneratorBoundUserInterface _owner;
+ public event Action? OnPowerSwitch;
- public GravityGeneratorWindow(GravityGeneratorBoundUserInterface owner)
+ public GravityGeneratorWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
- _owner = owner;
-
OnButton.Group = _buttonGroup;
OffButton.Group = _buttonGroup;
- OnButton.OnPressed += _ => _owner.SetPowerSwitch(true);
- OffButton.OnPressed += _ => _owner.SetPowerSwitch(false);
+ OnButton.OnPressed += _ => OnPowerSwitch?.Invoke(true);
+ OffButton.OnPressed += _ => OnPowerSwitch?.Invoke(false);
+ }
- EntityView.SetEntity(owner.Owner);
+ public void SetEntity(EntityUid uid)
+ {
+ EntityView.SetEntity(uid);
}
public void UpdateState(SharedGravityGeneratorComponent.GeneratorState state)
diff --git a/Content.Client/Guidebook/Controls/GuidebookError.xaml b/Content.Client/Guidebook/Controls/GuidebookError.xaml
new file mode 100644
index 0000000000..b84d527ea0
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuidebookError.xaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Guidebook/Controls/GuidebookError.xaml.cs b/Content.Client/Guidebook/Controls/GuidebookError.xaml.cs
new file mode 100644
index 0000000000..461f196c83
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuidebookError.xaml.cs
@@ -0,0 +1,23 @@
+using JetBrains.Annotations;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Guidebook.Controls;
+
+[UsedImplicitly] [GenerateTypedNameReferences]
+public sealed partial class GuidebookError : BoxContainer
+{
+ public GuidebookError()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ public GuidebookError(string original, string? error) : this()
+ {
+ Original.AddText(original);
+
+ if (error is not null)
+ Error.AddText(error);
+ }
+}
diff --git a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
index c904a9c789..469b0ed222 100644
--- a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
+++ b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
@@ -4,12 +4,10 @@
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Controls.FancyTree;
using Content.Client.UserInterface.Systems.Info;
-using Content.Shared.CCVar;
using Content.Shared.Guidebook;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
@@ -18,15 +16,18 @@ namespace Content.Client.Guidebook.Controls;
[GenerateTypedNameReferences]
public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
{
- [Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
+ [Dependency] private readonly IResourceManager _resourceManager = default!;
private Dictionary, GuideEntry> _entries = new();
+ private readonly ISawmill _sawmill;
+
public GuidebookWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
+ _sawmill = Logger.GetSawmill("Guidebook");
Tree.OnSelectedItemChanged += OnSelectionChanged;
@@ -36,6 +37,20 @@ public GuidebookWindow()
};
}
+ public void HandleClick(string link)
+ {
+ if (!_entries.TryGetValue(link, out var entry))
+ return;
+
+ if (Tree.TryGetIndexFromMetadata(entry, out var index))
+ {
+ Tree.ExpandParentEntries(index.Value);
+ Tree.SetSelectedIndex(index);
+ }
+ else
+ ShowGuide(entry);
+ }
+
private void OnSelectionChanged(TreeItem? item)
{
if (item != null && item.Metadata is GuideEntry entry)
@@ -71,8 +86,9 @@ private void ShowGuide(GuideEntry entry)
if (!_parsingMan.TryAddMarkup(EntryContainer, file.ReadToEnd()))
{
- EntryContainer.AddChild(new Label() { Text = "ERROR: Failed to parse document." });
- Logger.Error($"Failed to parse contents of guide document {entry.Id}.");
+ // The guidebook will automatically display the in-guidebook error if it fails
+
+ _sawmill.Error($"Failed to parse contents of guide document {entry.Id}.");
}
}
@@ -124,8 +140,10 @@ private IEnumerable GetSortedEntries(List GetSortedEntries(List Loc.GetString(rootEntry.Name));
}
- private void RepopulateTree(List>? roots = null, ProtoId? forcedRoot = null)
+ private void RepopulateTree(List>? roots = null,
+ ProtoId? forcedRoot = null)
{
Tree.Clear();
HashSet> addedEntries = new();
- TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries);
+ var parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries);
foreach (var entry in GetSortedEntries(roots))
{
AddEntry(entry.Id, parent, addedEntries);
}
+
Tree.SetAllExpanded(true);
}
- private TreeItem? AddEntry(ProtoId id, TreeItem? parent, HashSet> addedEntries)
+ private TreeItem? AddEntry(ProtoId id,
+ TreeItem? parent,
+ HashSet> addedEntries)
{
if (!_entries.TryGetValue(id, out var entry))
return null;
@@ -179,22 +201,6 @@ private void RepopulateTree(List>? roots = null, Pr
return item;
}
- public void HandleClick(string link)
- {
- if (!_entries.TryGetValue(link, out var entry))
- return;
-
- if (Tree.TryGetIndexFromMetadata(entry, out var index))
- {
- Tree.ExpandParentEntries(index.Value);
- Tree.SetSelectedIndex(index);
- }
- else
- {
- ShowGuide(entry);
- }
- }
-
private void HandleFilter()
{
var emptySearch = SearchBar.Text.Trim().Length == 0;
@@ -208,6 +214,5 @@ private void HandleFilter()
element.SetHiddenState(true, SearchBar.Text.Trim());
}
}
-
}
}
diff --git a/Content.Client/Guidebook/DocumentParsingManager.cs b/Content.Client/Guidebook/DocumentParsingManager.cs
index e8a0743b9e..857ae55202 100644
--- a/Content.Client/Guidebook/DocumentParsingManager.cs
+++ b/Content.Client/Guidebook/DocumentParsingManager.cs
@@ -1,4 +1,5 @@
using System.Linq;
+using Content.Client.Guidebook.Controls;
using Content.Client.Guidebook.Richtext;
using Content.Shared.Guidebook;
using Pidgin;
@@ -7,6 +8,7 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Sandboxing;
+using Robust.Shared.Utility;
using static Pidgin.Parser;
namespace Content.Client.Guidebook;
@@ -22,8 +24,10 @@ public sealed partial class DocumentParsingManager
[Dependency] private readonly ISandboxHelper _sandboxHelper = default!;
private readonly Dictionary> _tagControlParsers = new();
- private Parser _tagParser = default!;
private Parser _controlParser = default!;
+
+ private ISawmill _sawmill = default!;
+ private Parser _tagParser = default!;
public Parser> ControlParser = default!;
public void Initialize()
@@ -32,7 +36,8 @@ public void Initialize()
.Assert(_tagControlParsers.ContainsKey, tag => $"unknown tag: {tag}")
.Bind(tag => _tagControlParsers[tag]);
- _controlParser = OneOf(_tagParser, TryHeaderControl, ListControlParser, TextControlParser).Before(SkipWhitespaces);
+ _controlParser = OneOf(_tagParser, TryHeaderControl, ListControlParser, TextControlParser)
+ .Before(SkipWhitespaces);
foreach (var typ in _reflectionManager.GetAllChildren())
{
@@ -40,6 +45,8 @@ public void Initialize()
}
ControlParser = SkipWhitespaces.Then(_controlParser.Many());
+
+ _sawmill = Logger.GetSawmill("Guidebook");
}
public bool TryAddMarkup(Control control, ProtoId entryId, bool log = true)
@@ -68,37 +75,57 @@ public bool TryAddMarkup(Control control, string text, bool log = true)
}
catch (Exception e)
{
- if (log)
- Logger.Error($"Encountered error while generating markup controls: {e}");
+ _sawmill.Error($"Encountered error while generating markup controls: {e}");
+
+ control.AddChild(new GuidebookError(text, e.ToStringBetter()));
+
return false;
}
return true;
}
- private Parser CreateTagControlParser(string tagId, Type tagType, ISandboxHelper sandbox) => Map(
- (args, controls) =>
- {
- var tag = (IDocumentTag) sandbox.CreateInstance(tagType);
- if (!tag.TryParseTag(args, out var control))
- {
- Logger.Error($"Failed to parse {tagId} args");
- return new Control();
- }
+ private Parser CreateTagControlParser(string tagId, Type tagType, ISandboxHelper sandbox)
+ {
+ return Map(
+ (args, controls) =>
+ {
+ try
+ {
+ var tag = (IDocumentTag) sandbox.CreateInstance(tagType);
+ if (!tag.TryParseTag(args, out var control))
+ {
+ _sawmill.Error($"Failed to parse {tagId} args");
+ return new GuidebookError(args.ToString() ?? tagId, $"Failed to parse {tagId} args");
+ }
- foreach (var child in controls)
- {
- control.AddChild(child);
- }
- return control;
- },
- ParseTagArgs(tagId),
- TagContentParser(tagId)).Labelled($"{tagId} control");
+ foreach (var child in controls)
+ {
+ control.AddChild(child);
+ }
+
+ return control;
+ }
+ catch (Exception e)
+ {
+ var output = args.Aggregate(string.Empty,
+ (current, pair) => current + $"{pair.Key}=\"{pair.Value}\" ");
+
+ _sawmill.Error($"Tag: {tagId} \n Arguments: {output}/>");
+ return new GuidebookError($"Tag: {tagId}\nArguments: {output}", e.ToString());
+ }
+ },
+ ParseTagArgs(tagId),
+ TagContentParser(tagId))
+ .Labelled($"{tagId} control");
+ }
// Parse a bunch of controls until we encounter a matching closing tag.
- private Parser> TagContentParser(string tag) =>
- OneOf(
- Try(ImmediateTagEnd).ThenReturn(Enumerable.Empty()),
- TagEnd.Then(_controlParser.Until(TryTagTerminator(tag)).Labelled($"{tag} children"))
- );
+ private Parser> TagContentParser(string tag)
+ {
+ return OneOf(
+ Try(ImmediateTagEnd).ThenReturn(Enumerable.Empty()),
+ TagEnd.Then(_controlParser.Until(TryTagTerminator(tag)).Labelled($"{tag} children"))
+ );
+ }
}
diff --git a/Content.Client/Guidebook/DocumentParsingManager.static.cs b/Content.Client/Guidebook/DocumentParsingManager.static.cs
index ab38fcb154..5d25d8f645 100644
--- a/Content.Client/Guidebook/DocumentParsingManager.static.cs
+++ b/Content.Client/Guidebook/DocumentParsingManager.static.cs
@@ -1,4 +1,5 @@
using System.Linq;
+using Content.Client.Guidebook.Controls;
using Pidgin;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -14,92 +15,142 @@ public sealed partial class DocumentParsingManager
{
private const string ListBullet = " › ";
- #region Text Parsing
- #region Basic Text Parsing
- // Try look for an escaped character. If found, skip the escaping slash and return the character.
- private static readonly Parser TryEscapedChar = Try(Char('\\').Then(OneOf(
- Try(Char('<')),
- Try(Char('>')),
- Try(Char('\\')),
- Try(Char('-')),
- Try(Char('=')),
- Try(Char('"')),
- Try(Char(' ')),
- Try(Char('n')).ThenReturn('\n'),
- Try(Char('t')).ThenReturn('\t')
- )));
+ // Parser that consumes a - and then just parses normal rich text with some prefix text (a bullet point).
+ private static readonly Parser TryEscapedChar = Try(Char('\\')
+ .Then(OneOf(
+ Try(Char('<')),
+ Try(Char('>')),
+ Try(Char('\\')),
+ Try(Char('-')),
+ Try(Char('=')),
+ Try(Char('"')),
+ Try(Char(' ')),
+ Try(Char('n')).ThenReturn('\n'),
+ Try(Char('t')).ThenReturn('\t')
+ )));
private static readonly Parser SkipNewline = Whitespace.SkipUntil(Char('\n'));
- private static readonly Parser TrySingleNewlineToSpace = Try(SkipNewline).Then(SkipWhitespaces).ThenReturn(' ');
+ private static readonly Parser TrySingleNewlineToSpace =
+ Try(SkipNewline).Then(SkipWhitespaces).ThenReturn(' ');
private static readonly Parser TextChar = OneOf(
TryEscapedChar, // consume any backslashed being used to escape text
TrySingleNewlineToSpace, // turn single newlines into spaces
Any // just return the character.
- );
+ );
- // like TextChar, but not skipping whitespace around newlines
private static readonly Parser QuotedTextChar = OneOf(TryEscapedChar, Any);
+ private static readonly Parser QuotedText =
+ Char('"').Then(QuotedTextChar.Until(Try(Char('"'))).Select(string.Concat)).Labelled("quoted text");
+
+ private static readonly Parser TryStartList =
+ Try(SkipNewline.Then(SkipWhitespaces).Then(Char('-'))).Then(SkipWhitespaces);
+
+ private static readonly Parser TryStartTag = Try(Char('<')).Then(SkipWhitespaces);
+
+ private static readonly Parser TryStartParagraph =
+ Try(SkipNewline.Then(SkipNewline)).Then(SkipWhitespaces);
+
+ private static readonly Parser TryLookTextEnd =
+ Lookahead(OneOf(TryStartTag, TryStartList, TryStartParagraph, Try(Whitespace.SkipUntil(End))));
+
+ private static readonly Parser TextParser =
+ TextChar.AtLeastOnceUntil(TryLookTextEnd).Select(string.Concat);
+
+ private static readonly Parser TextControlParser = Try(Map(text =>
+ {
+ var rt = new RichTextLabel
+ {
+ HorizontalExpand = true,
+ Margin = new Thickness(0, 0, 0, 15.0f)
+ };
+
+ var msg = new FormattedMessage();
+ // THANK YOU RICHTEXT VERY COOL
+ // (text doesn't default to white).
+ msg.PushColor(Color.White);
+
+ // If the parsing fails, don't throw an error and instead make an inline error message
+ string? error;
+ if (!msg.TryAddMarkup(text, out error))
+ {
+ Logger.GetSawmill("Guidebook").Error("Failed to parse RichText in Guidebook");
+
+ return new GuidebookError(text, error);
+ }
+
+ msg.Pop();
+ rt.SetMessage(msg);
+ return rt;
+ },
+ TextParser)
+ .Cast())
+ .Labelled("richtext");
+
+ private static readonly Parser HeaderControlParser = Try(Char('#'))
+ .Then(SkipWhitespaces.Then(Map(text => new Label
+ {
+ Text = text,
+ StyleClasses = { "LabelHeadingBigger" }
+ },
+ AnyCharExcept('\n').AtLeastOnceString())
+ .Cast()))
+ .Labelled("header");
+
+ private static readonly Parser SubHeaderControlParser = Try(String("##"))
+ .Then(SkipWhitespaces.Then(Map(text => new Label
+ {
+ Text = text,
+ StyleClasses = { "LabelHeading" }
+ },
+ AnyCharExcept('\n').AtLeastOnceString())
+ .Cast()))
+ .Labelled("subheader");
+
+ private static readonly Parser TryHeaderControl = OneOf(SubHeaderControlParser, HeaderControlParser);
+
+ private static readonly Parser ListControlParser = Try(Char('-'))
+ .Then(SkipWhitespaces)
+ .Then(Map(
+ control => new BoxContainer
+ {
+ Children = { new Label { Text = ListBullet, VerticalAlignment = VAlignment.Top }, control },
+ Orientation = LayoutOrientation.Horizontal
+ },
+ TextControlParser)
+ .Cast())
+ .Labelled("list");
+
+ #region Text Parsing
+
+ #region Basic Text Parsing
+
+ // Try look for an escaped character. If found, skip the escaping slash and return the character.
+
+
+ // like TextChar, but not skipping whitespace around newlines
+
+
// Quoted text
- private static readonly Parser QuotedText = Char('"').Then(QuotedTextChar.Until(Try(Char('"'))).Select(string.Concat)).Labelled("quoted text");
+
#endregion
#region rich text-end markers
- private static readonly Parser TryStartList = Try(SkipNewline.Then(SkipWhitespaces).Then(Char('-'))).Then(SkipWhitespaces);
- private static readonly Parser TryStartTag = Try(Char('<')).Then(SkipWhitespaces);
- private static readonly Parser TryStartParagraph = Try(SkipNewline.Then(SkipNewline)).Then(SkipWhitespaces);
- private static readonly Parser TryLookTextEnd = Lookahead(OneOf(TryStartTag, TryStartList, TryStartParagraph, Try(Whitespace.SkipUntil(End))));
+
#endregion
// parses text characters until it hits a text-end
- private static readonly Parser TextParser = TextChar.AtLeastOnceUntil(TryLookTextEnd).Select(string.Concat);
- private static readonly Parser TextControlParser = Try(Map(text =>
- {
- var rt = new RichTextLabel()
- {
- HorizontalExpand = true,
- Margin = new Thickness(0, 0, 0, 15.0f),
- };
-
- var msg = new FormattedMessage();
- // THANK YOU RICHTEXT VERY COOL
- // (text doesn't default to white).
- msg.PushColor(Color.White);
- msg.AddMarkup(text);
- msg.Pop();
- rt.SetMessage(msg);
- return rt;
- }, TextParser).Cast()).Labelled("richtext");
#endregion
#region Headers
- private static readonly Parser HeaderControlParser = Try(Char('#')).Then(SkipWhitespaces.Then(Map(text => new Label()
- {
- Text = text,
- StyleClasses = { "LabelHeadingBigger" }
- }, AnyCharExcept('\n').AtLeastOnceString()).Cast())).Labelled("header");
- private static readonly Parser SubHeaderControlParser = Try(String("##")).Then(SkipWhitespaces.Then(Map(text => new Label()
- {
- Text = text,
- StyleClasses = { "LabelHeading" }
- }, AnyCharExcept('\n').AtLeastOnceString()).Cast())).Labelled("subheader");
-
- private static readonly Parser TryHeaderControl = OneOf(SubHeaderControlParser, HeaderControlParser);
#endregion
- // Parser that consumes a - and then just parses normal rich text with some prefix text (a bullet point).
- private static readonly Parser ListControlParser = Try(Char('-')).Then(SkipWhitespaces).Then(Map(
- control => new BoxContainer()
- {
- Children = { new Label() { Text = ListBullet, VerticalAlignment = VAlignment.Top, }, control },
- Orientation = LayoutOrientation.Horizontal,
- }, TextControlParser).Cast()).Labelled("list");
-
#region Tag Parsing
+
// closing brackets for tags
private static readonly Parser TagEnd = Char('>').Then(SkipWhitespaces);
private static readonly Parser ImmediateTagEnd = String("/>").Then(SkipWhitespaces);
@@ -107,20 +158,24 @@ public sealed partial class DocumentParsingManager
private static readonly Parser TryLookTagEnd = Lookahead(OneOf(Try(TagEnd), Try(ImmediateTagEnd)));
//parse tag argument key. any normal text character up until we hit a "="
- private static readonly Parser TagArgKey = LetterOrDigit.Until(Char('=')).Select(string.Concat).Labelled("tag argument key");
+ private static readonly Parser TagArgKey =
+ LetterOrDigit.Until(Char('=')).Select(string.Concat).Labelled("tag argument key");
// parser for a singular tag argument. Note that each TryQuoteOrChar will consume a whole quoted block before the Until() looks for whitespace
- private static readonly Parser TagArgParser = Map((key, value) => (key, value), TagArgKey, QuotedText).Before(SkipWhitespaces);
+ private static readonly Parser TagArgParser =
+ Map((key, value) => (key, value), TagArgKey, QuotedText).Before(SkipWhitespaces);
// parser for all tag arguments
- private static readonly Parser> TagArgsParser = TagArgParser.Until(TryLookTagEnd);
+ private static readonly Parser> TagArgsParser =
+ TagArgParser.Until(TryLookTagEnd);
// parser for an opening tag.
private static readonly Parser TryOpeningTag =
Try(Char('<'))
- .Then(SkipWhitespaces)
- .Then(TextChar.Until(OneOf(Whitespace.SkipAtLeastOnce(), TryLookTagEnd)))
- .Select(string.Concat).Labelled($"opening tag");
+ .Then(SkipWhitespaces)
+ .Then(TextChar.Until(OneOf(Whitespace.SkipAtLeastOnce(), TryLookTagEnd)))
+ .Select(string.Concat)
+ .Labelled("opening tag");
private static Parser> ParseTagArgs(string tag)
{
@@ -138,5 +193,6 @@ private static Parser TryTagTerminator(string tag)
.Then(TagEnd)
.Labelled($"closing {tag} tag");
}
+
#endregion
}
diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs
index 7319b97b42..ffa6dfd29d 100644
--- a/Content.Client/Hands/Systems/HandsSystem.cs
+++ b/Content.Client/Hands/Systems/HandsSystem.cs
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using Content.Client.DisplacementMap;
using Content.Client.Examine;
using Content.Client.Strip;
using Content.Client.Verbs.UI;
@@ -28,6 +29,7 @@ public sealed class HandsSystem : SharedHandsSystem
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly StrippableSystem _stripSys = default!;
[Dependency] private readonly ExamineSystem _examine = default!;
+ [Dependency] private readonly DisplacementMapSystem _displacement = default!;
public event Action? OnPlayerAddHand;
public event Action? OnPlayerRemoveHand;
@@ -345,6 +347,10 @@ private void UpdateHandVisuals(EntityUid uid, EntityUid held, Hand hand, HandsCo
}
sprite.LayerSetData(index, layerData);
+
+ //Add displacement maps
+ if (handComp.HandDisplacement is not null)
+ _displacement.TryAddDisplacement(handComp.HandDisplacement, sprite, index, key, revealedLayers);
}
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerBoundUserInterface.cs b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerBoundUserInterface.cs
index dc0a3e9fcc..38760f4aa3 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerBoundUserInterface.cs
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerBoundUserInterface.cs
@@ -1,6 +1,6 @@
using Content.Shared.MedicalScanner;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.HealthAnalyzer.UI
{
@@ -17,12 +17,9 @@ public HealthAnalyzerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owne
protected override void Open()
{
base.Open();
- _window = new HealthAnalyzerWindow
- {
- Title = EntMan.GetComponent(Owner).EntityName,
- };
- _window.OnClose += Close;
- _window.OpenCentered();
+ _window = this.CreateWindow();
+
+ _window.Title = EntMan.GetComponent(Owner).EntityName;
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
@@ -35,17 +32,5 @@ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
_window.Populate(cast);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
-
- if (_window != null)
- _window.OnClose -= Close;
-
- _window?.Dispose();
- }
}
}
diff --git a/Content.Client/Humanoid/HumanoidMarkingModifierBoundUserInterface.cs b/Content.Client/Humanoid/HumanoidMarkingModifierBoundUserInterface.cs
index a8872604a4..53977eb636 100644
--- a/Content.Client/Humanoid/HumanoidMarkingModifierBoundUserInterface.cs
+++ b/Content.Client/Humanoid/HumanoidMarkingModifierBoundUserInterface.cs
@@ -1,5 +1,6 @@
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
+using Robust.Client.UserInterface;
namespace Content.Client.Humanoid;
@@ -20,8 +21,7 @@ protected override void Open()
{
base.Open();
- _window = new();
- _window.OnClose += Close;
+ _window = this.CreateWindow();
_window.OnMarkingAdded += SendMarkingSet;
_window.OnMarkingRemoved += SendMarkingSet;
_window.OnMarkingColorChange += SendMarkingSetNoResend;
diff --git a/Content.Client/IconSmoothing/ClientRandomIconSmoothSystem.cs b/Content.Client/IconSmoothing/ClientRandomIconSmoothSystem.cs
new file mode 100644
index 0000000000..73db9e1ab9
--- /dev/null
+++ b/Content.Client/IconSmoothing/ClientRandomIconSmoothSystem.cs
@@ -0,0 +1,29 @@
+using Content.Shared.IconSmoothing;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.IconSmoothing;
+
+public sealed class ClientRandomIconSmoothSystem : SharedRandomIconSmoothSystem
+{
+ [Dependency] private readonly IconSmoothSystem _iconSmooth = default!;
+ [Dependency] private readonly AppearanceSystem _appearance = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnAppearanceChange);
+ }
+
+ private void OnAppearanceChange(Entity ent, ref AppearanceChangeEvent args)
+ {
+ if (!TryComp(ent, out var smooth))
+ return;
+
+ if (!_appearance.TryGetData(ent, RandomIconSmoothState.State, out var state, args.Component))
+ return;
+
+ smooth.StateBase = state;
+ _iconSmooth.SetStateBase(ent, smooth, state);
+ }
+}
diff --git a/Content.Client/IconSmoothing/IconSmoothComponent.cs b/Content.Client/IconSmoothing/IconSmoothComponent.cs
index 88b1f613cb..040198529c 100644
--- a/Content.Client/IconSmoothing/IconSmoothComponent.cs
+++ b/Content.Client/IconSmoothing/IconSmoothComponent.cs
@@ -30,7 +30,7 @@ public sealed partial class IconSmoothComponent : Component
/// Prepended to the RSI state.
///
[ViewVariables(VVAccess.ReadWrite), DataField("base")]
- public string StateBase { get; private set; } = string.Empty;
+ public string StateBase { get; set; } = string.Empty;
[DataField("shader", customTypeSerializer:typeof(PrototypeIdSerializer))]
public string? Shader;
diff --git a/Content.Client/IconSmoothing/IconSmoothSystem.cs b/Content.Client/IconSmoothing/IconSmoothSystem.cs
index 4b02560846..11ca75bc82 100644
--- a/Content.Client/IconSmoothing/IconSmoothSystem.cs
+++ b/Content.Client/IconSmoothing/IconSmoothSystem.cs
@@ -55,6 +55,33 @@ private void OnStartup(EntityUid uid, IconSmoothComponent component, ComponentSt
if (component.Mode != IconSmoothingMode.Corners || !TryComp(uid, out SpriteComponent? sprite))
return;
+ SetCornerLayers(sprite, component);
+
+ if (component.Shader != null)
+ {
+ sprite.LayerSetShader(CornerLayers.SE, component.Shader);
+ sprite.LayerSetShader(CornerLayers.NE, component.Shader);
+ sprite.LayerSetShader(CornerLayers.NW, component.Shader);
+ sprite.LayerSetShader(CornerLayers.SW, component.Shader);
+ }
+ }
+
+ public void SetStateBase(EntityUid uid, IconSmoothComponent component, string newState)
+ {
+ if (!TryComp(uid, out var sprite))
+ return;
+
+ component.StateBase = newState;
+ SetCornerLayers(sprite, component);
+ }
+
+ private void SetCornerLayers(SpriteComponent sprite, IconSmoothComponent component)
+ {
+ sprite.LayerMapRemove(CornerLayers.SE);
+ sprite.LayerMapRemove(CornerLayers.NE);
+ sprite.LayerMapRemove(CornerLayers.NW);
+ sprite.LayerMapRemove(CornerLayers.SW);
+
var state0 = $"{component.StateBase}0";
sprite.LayerMapSet(CornerLayers.SE, sprite.AddLayerState(state0));
sprite.LayerSetDirOffset(CornerLayers.SE, DirectionOffset.None);
@@ -64,14 +91,6 @@ private void OnStartup(EntityUid uid, IconSmoothComponent component, ComponentSt
sprite.LayerSetDirOffset(CornerLayers.NW, DirectionOffset.Flip);
sprite.LayerMapSet(CornerLayers.SW, sprite.AddLayerState(state0));
sprite.LayerSetDirOffset(CornerLayers.SW, DirectionOffset.Clockwise);
-
- if (component.Shader != null)
- {
- sprite.LayerSetShader(CornerLayers.SE, component.Shader);
- sprite.LayerSetShader(CornerLayers.NE, component.Shader);
- sprite.LayerSetShader(CornerLayers.NW, component.Shader);
- sprite.LayerSetShader(CornerLayers.SW, component.Shader);
- }
}
private void OnShutdown(EntityUid uid, IconSmoothComponent component, ComponentShutdown args)
diff --git a/Content.Client/Instruments/UI/BandMenu.xaml.cs b/Content.Client/Instruments/UI/BandMenu.xaml.cs
index 5fb293a194..26cd1369e5 100644
--- a/Content.Client/Instruments/UI/BandMenu.xaml.cs
+++ b/Content.Client/Instruments/UI/BandMenu.xaml.cs
@@ -11,7 +11,9 @@ public sealed partial class BandMenu : DefaultWindow
{
private readonly InstrumentBoundUserInterface _owner;
- public BandMenu(InstrumentBoundUserInterface owner) : base()
+ public EntityUid? Master;
+
+ public BandMenu(InstrumentBoundUserInterface owner)
{
RobustXamlLoader.Load(this);
@@ -40,7 +42,7 @@ public void Populate((NetEntity, string)[] nearby, IEntityManager entManager)
{
var uid = entManager.GetEntity(nent);
var item = BandList.AddItem(name, null, true, uid);
- item.Selected = _owner.Instrument?.Master == uid;
+ item.Selected = Master == uid;
}
}
}
diff --git a/Content.Client/Instruments/UI/ChannelsMenu.xaml.cs b/Content.Client/Instruments/UI/ChannelsMenu.xaml.cs
index 2814d41536..c175e67842 100644
--- a/Content.Client/Instruments/UI/ChannelsMenu.xaml.cs
+++ b/Content.Client/Instruments/UI/ChannelsMenu.xaml.cs
@@ -51,7 +51,7 @@ private void OnClearPressed(BaseButton.ButtonEventArgs obj)
}
}
- public void Populate()
+ public void Populate(InstrumentComponent? instrument)
{
ChannelList.Clear();
@@ -60,7 +60,8 @@ public void Populate()
var item = ChannelList.AddItem(_owner.Loc.GetString("instrument-component-channel-name",
("number", i)), null, true, i);
- item.Selected = !_owner.Instrument?.FilteredChannels[i] ?? false;
+
+ item.Selected = !instrument?.FilteredChannels[i] ?? false;
}
}
}
diff --git a/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs b/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs
index 0f5729f55b..4816ce8c36 100644
--- a/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs
+++ b/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs
@@ -24,8 +24,6 @@ public sealed class InstrumentBoundUserInterface : BoundUserInterface
[ViewVariables] private BandMenu? _bandMenu;
[ViewVariables] private ChannelsMenu? _channelsMenu;
- [ViewVariables] public InstrumentComponent? Instrument { get; private set; }
-
public InstrumentBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
@@ -43,14 +41,20 @@ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
protected override void Open()
{
- if (!EntMan.TryGetComponent(Owner, out InstrumentComponent? instrument))
- return;
+ _instrumentMenu = this.CreateWindow();
+ _instrumentMenu.Title = EntMan.GetComponent(Owner).EntityName;
- Instrument = instrument;
- _instrumentMenu = new InstrumentMenu(this);
- _instrumentMenu.OnClose += Close;
+ _instrumentMenu.OnOpenBand += OpenBandMenu;
+ _instrumentMenu.OnOpenChannels += OpenChannelsMenu;
+ _instrumentMenu.OnCloseChannels += CloseChannelsMenu;
+ _instrumentMenu.OnCloseBands += CloseBandMenu;
- _instrumentMenu.OpenCentered();
+ _instrumentMenu.SetMIDI(MidiManager.IsAvailable);
+
+ if (EntMan.TryGetComponent(Owner, out InstrumentComponent? instrument))
+ {
+ _instrumentMenu.SetInstrument((Owner, instrument));
+ }
}
protected override void Dispose(bool disposing)
@@ -58,7 +62,12 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
if (!disposing)
return;
- _instrumentMenu?.Dispose();
+
+ if (EntMan.TryGetComponent(Owner, out InstrumentComponent? instrument))
+ {
+ _instrumentMenu?.RemoveInstrument(instrument);
+ }
+
_bandMenu?.Dispose();
_channelsMenu?.Dispose();
}
@@ -72,6 +81,11 @@ public void OpenBandMenu()
{
_bandMenu ??= new BandMenu(this);
+ if (EntMan.TryGetComponent(Owner, out InstrumentComponent? instrument))
+ {
+ _bandMenu.Master = instrument.Master;
+ }
+
// Refresh cache...
RefreshBands();
@@ -87,7 +101,9 @@ public void CloseBandMenu()
public void OpenChannelsMenu()
{
_channelsMenu ??= new ChannelsMenu(this);
- _channelsMenu.Populate();
+ EntMan.TryGetComponent(Owner, out InstrumentComponent? instrument);
+
+ _channelsMenu.Populate(instrument);
_channelsMenu.OpenCenteredRight();
}
diff --git a/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs b/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs
index da443e3fb5..9b14e01fb5 100644
--- a/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs
+++ b/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs
@@ -1,7 +1,10 @@
using System.IO;
using System.Numerics;
using System.Threading.Tasks;
+using Content.Client.Interactable;
+using Content.Shared.ActionBlocker;
using Robust.Client.AutoGenerated;
+using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
@@ -16,33 +19,23 @@ namespace Content.Client.Instruments.UI
[GenerateTypedNameReferences]
public sealed partial class InstrumentMenu : DefaultWindow
{
- private readonly InstrumentBoundUserInterface _owner;
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ [Dependency] private readonly IFileDialogManager _dialogs = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
private bool _isMidiFileDialogueWindowOpen;
- public InstrumentMenu(InstrumentBoundUserInterface owner)
- {
- RobustXamlLoader.Load(this);
-
- _owner = owner;
+ public event Action? OnOpenBand;
+ public event Action? OnOpenChannels;
+ public event Action? OnCloseBands;
+ public event Action? OnCloseChannels;
- if (_owner.Instrument != null)
- {
- _owner.Instrument.OnMidiPlaybackEnded += InstrumentOnMidiPlaybackEnded;
- Title = _owner.Entities.GetComponent(_owner.Owner).EntityName;
- LoopButton.Disabled = !_owner.Instrument.IsMidiOpen;
- LoopButton.Pressed = _owner.Instrument.LoopMidi;
- ChannelsButton.Disabled = !_owner.Instrument.IsRendererAlive;
- StopButton.Disabled = !_owner.Instrument.IsMidiOpen;
- PlaybackSlider.MouseFilter = _owner.Instrument.IsMidiOpen ? MouseFilterMode.Pass : MouseFilterMode.Ignore;
- }
+ public EntityUid Entity;
- if (!_owner.MidiManager.IsAvailable)
- {
- UnavailableOverlay.Visible = true;
- // We return early as to not give the buttons behavior.
- return;
- }
+ public InstrumentMenu()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
InputButton.OnToggled += MidiInputButtonOnOnToggled;
BandButton.OnPressed += BandButtonOnPressed;
@@ -57,12 +50,34 @@ public InstrumentMenu(InstrumentBoundUserInterface owner)
MinSize = SetSize = new Vector2(400, 150);
}
+ public void SetInstrument(Entity entity)
+ {
+ Entity = entity;
+ var component = entity.Comp;
+ component.OnMidiPlaybackEnded += InstrumentOnMidiPlaybackEnded;
+ LoopButton.Disabled = !component.IsMidiOpen;
+ LoopButton.Pressed = component.LoopMidi;
+ ChannelsButton.Disabled = !component.IsRendererAlive;
+ StopButton.Disabled = !component.IsMidiOpen;
+ PlaybackSlider.MouseFilter = component.IsMidiOpen ? MouseFilterMode.Pass : MouseFilterMode.Ignore;
+ }
+
+ public void RemoveInstrument(InstrumentComponent component)
+ {
+ component.OnMidiPlaybackEnded -= InstrumentOnMidiPlaybackEnded;
+ }
+
+ public void SetMIDI(bool available)
+ {
+ UnavailableOverlay.Visible = !available;
+ }
+
private void BandButtonOnPressed(ButtonEventArgs obj)
{
if (!PlayCheck())
return;
- _owner.OpenBandMenu();
+ OnOpenBand?.Invoke();
}
private void BandButtonOnToggled(ButtonToggledEventArgs obj)
@@ -70,12 +85,15 @@ private void BandButtonOnToggled(ButtonToggledEventArgs obj)
if (obj.Pressed)
return;
- _owner.Instruments.SetMaster(_owner.Owner, null);
+ if (_entManager.TryGetComponent(Entity, out InstrumentComponent? instrument))
+ {
+ _entManager.System().SetMaster(Entity, instrument.Master);
+ }
}
private void ChannelsButtonOnPressed(ButtonEventArgs obj)
{
- _owner.OpenChannelsMenu();
+ OnOpenChannels?.Invoke();
}
private void InstrumentOnMidiPlaybackEnded()
@@ -85,8 +103,10 @@ private void InstrumentOnMidiPlaybackEnded()
public void MidiPlaybackSetButtonsDisabled(bool disabled)
{
- if(disabled)
- _owner.CloseChannelsMenu();
+ if (disabled)
+ {
+ OnCloseChannels?.Invoke();
+ }
LoopButton.Disabled = disabled;
StopButton.Disabled = disabled;
@@ -100,7 +120,7 @@ private async void MidiFileButtonOnOnPressed(ButtonEventArgs obj)
if (_isMidiFileDialogueWindowOpen)
return;
- _owner.CloseBandMenu();
+ OnCloseBands?.Invoke();
var filters = new FileDialogFilters(new FileDialogFilters.Group("mid", "midi"));
@@ -108,7 +128,7 @@ private async void MidiFileButtonOnOnPressed(ButtonEventArgs obj)
// or focus the previously-opened window.
_isMidiFileDialogueWindowOpen = true;
- await using var file = await _owner.FileDialogManager.OpenFile(filters);
+ await using var file = await _dialogs.OpenFile(filters);
_isMidiFileDialogueWindowOpen = false;
@@ -129,9 +149,18 @@ private async void MidiFileButtonOnOnPressed(ButtonEventArgs obj)
await file.CopyToAsync(memStream);
- if (_owner.Instrument is not {} instrument
- || !_owner.Instruments.OpenMidi(_owner.Owner, memStream.GetBuffer().AsSpan(0, (int) memStream.Length), instrument))
+ if (!_entManager.TryGetComponent(Entity, out var instrument))
+ {
return;
+ }
+
+ if (!_entManager.System()
+ .OpenMidi(Entity,
+ memStream.GetBuffer().AsSpan(0, (int) memStream.Length),
+ instrument))
+ {
+ return;
+ }
MidiPlaybackSetButtonsDisabled(false);
if (InputButton.Pressed)
@@ -140,7 +169,7 @@ private async void MidiFileButtonOnOnPressed(ButtonEventArgs obj)
private void MidiInputButtonOnOnToggled(ButtonToggledEventArgs obj)
{
- _owner.CloseBandMenu();
+ OnCloseBands?.Invoke();
if (obj.Pressed)
{
@@ -148,109 +177,99 @@ private void MidiInputButtonOnOnToggled(ButtonToggledEventArgs obj)
return;
MidiStopButtonOnPressed(null);
- if(_owner.Instrument is {} instrument)
- _owner.Instruments.OpenInput(_owner.Owner, instrument);
+
+ if (_entManager.TryGetComponent(Entity, out InstrumentComponent? instrument))
+ _entManager.System().OpenInput(Entity, instrument);
}
- else if (_owner.Instrument is { } instrument)
+ else
{
- _owner.Instruments.CloseInput(_owner.Owner, false, instrument);
- _owner.CloseChannelsMenu();
+ _entManager.System().CloseInput(Entity, false);
+ OnCloseChannels?.Invoke();
}
}
private bool PlayCheck()
{
// TODO all of these checks should also be done server-side.
-
- var instrumentEnt = _owner.Owner;
- var instrument = _owner.Instrument;
-
- if (instrument == null)
+ if (!_entManager.TryGetComponent(Entity, out InstrumentComponent? instrument))
return false;
- var localEntity = _owner.PlayerManager.LocalEntity;
+ var localEntity = _player.LocalEntity;
// If we don't have a player or controlled entity, we return.
if (localEntity == null)
return false;
// By default, allow an instrument to play itself and skip all other checks
- if (localEntity == instrumentEnt)
+ if (localEntity == Entity)
return true;
- var container = _owner.Entities.System();
+ var container = _entManager.System();
// If we're a handheld instrument, we might be in a container. Get it just in case.
- container.TryGetContainingContainer(instrumentEnt, out var conMan);
+ container.TryGetContainingContainer((Entity, null, null), out var conMan);
// If the instrument is handheld and we're not holding it, we return.
- if ((instrument.Handheld && (conMan == null || conMan.Owner != localEntity)))
+ if (instrument.Handheld && (conMan == null || conMan.Owner != localEntity))
return false;
- if (!_owner.ActionBlocker.CanInteract(localEntity.Value, instrumentEnt))
+ if (!_entManager.System().CanInteract(localEntity.Value, Entity))
return false;
// We check that we're in range unobstructed just in case.
- return _owner.Interactions.InRangeUnobstructed(localEntity.Value, instrumentEnt);
+ return _entManager.System().InRangeUnobstructed(localEntity.Value, Entity);
}
private void MidiStopButtonOnPressed(ButtonEventArgs? obj)
{
MidiPlaybackSetButtonsDisabled(true);
- if (_owner.Instrument is not {} instrument)
- return;
-
- _owner.Instruments.CloseMidi(_owner.Owner, false, instrument);
- _owner.CloseChannelsMenu();
+ _entManager.System().CloseMidi(Entity, false);
+ OnCloseChannels?.Invoke();
}
private void MidiLoopButtonOnOnToggled(ButtonToggledEventArgs obj)
{
- if (_owner.Instrument == null)
- return;
+ var instrument = _entManager.System();
+
+ if (_entManager.TryGetComponent(Entity, out InstrumentComponent? instrumentComp))
+ {
+ instrumentComp.LoopMidi = obj.Pressed;
+ }
- _owner.Instrument.LoopMidi = obj.Pressed;
- _owner.Instruments.UpdateRenderer(_owner.Owner, _owner.Instrument);
+ instrument.UpdateRenderer(Entity);
}
private void PlaybackSliderSeek(Range _)
{
// Do not seek while still grabbing.
- if (PlaybackSlider.Grabbed || _owner.Instrument is not {} instrument)
+ if (PlaybackSlider.Grabbed)
return;
- _owner.Instruments.SetPlayerTick(_owner.Owner, (int)Math.Ceiling(PlaybackSlider.Value), instrument);
+ _entManager.System().SetPlayerTick(Entity, (int)Math.Ceiling(PlaybackSlider.Value));
}
private void PlaybackSliderKeyUp(GUIBoundKeyEventArgs args)
{
- if (args.Function != EngineKeyFunctions.UIClick || _owner.Instrument is not {} instrument)
+ if (args.Function != EngineKeyFunctions.UIClick)
return;
- _owner.Instruments.SetPlayerTick(_owner.Owner, (int)Math.Ceiling(PlaybackSlider.Value), instrument);
- }
-
- public override void Close()
- {
- base.Close();
- _owner.CloseBandMenu();
- _owner.CloseChannelsMenu();
+ _entManager.System().SetPlayerTick(Entity, (int)Math.Ceiling(PlaybackSlider.Value));
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
- if (_owner.Instrument == null)
+ if (!_entManager.TryGetComponent(Entity, out InstrumentComponent? instrument))
return;
- var hasMaster = _owner.Instrument.Master != null;
+ var hasMaster = instrument.Master != null;
BandButton.ToggleMode = hasMaster;
BandButton.Pressed = hasMaster;
- BandButton.Disabled = _owner.Instrument.IsMidiOpen || _owner.Instrument.IsInputOpen;
- ChannelsButton.Disabled = !_owner.Instrument.IsRendererAlive;
+ BandButton.Disabled = instrument.IsMidiOpen || instrument.IsInputOpen;
+ ChannelsButton.Disabled = !instrument.IsRendererAlive;
- if (!_owner.Instrument.IsMidiOpen)
+ if (!instrument.IsMidiOpen)
{
PlaybackSlider.MaxValue = 1;
PlaybackSlider.SetValueWithoutEvent(0);
@@ -260,8 +279,8 @@ protected override void FrameUpdate(FrameEventArgs args)
if (PlaybackSlider.Grabbed)
return;
- PlaybackSlider.MaxValue = _owner.Instrument.PlayerTotalTick;
- PlaybackSlider.SetValueWithoutEvent(_owner.Instrument.PlayerTick);
+ PlaybackSlider.MaxValue = instrument.PlayerTotalTick;
+ PlaybackSlider.SetValueWithoutEvent(instrument.PlayerTick);
}
}
}
diff --git a/Content.Client/Interaction/DragDropSystem.cs b/Content.Client/Interaction/DragDropSystem.cs
index d249766bbc..4145999579 100644
--- a/Content.Client/Interaction/DragDropSystem.cs
+++ b/Content.Client/Interaction/DragDropSystem.cs
@@ -42,6 +42,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
// how often to recheck possible targets (prevents calling expensive
// check logic each update)
@@ -89,7 +90,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
///
private bool _isReplaying;
- private float _deadzone;
+ public float Deadzone;
private DragState _state = DragState.NotDragging;
@@ -121,7 +122,7 @@ public override void Initialize()
private void SetDeadZone(float deadZone)
{
- _deadzone = deadZone;
+ Deadzone = deadZone;
}
public override void Shutdown()
@@ -211,7 +212,7 @@ private bool OnUseMouseDown(in PointerInputCmdHandler.PointerInputCmdArgs args)
_draggedEntity = entity;
_state = DragState.MouseDown;
- _mouseDownScreenPos = _inputManager.MouseScreenPosition;
+ _mouseDownScreenPos = args.ScreenCoordinates;
_mouseDownTime = 0;
// don't want anything else to process the click,
@@ -239,8 +240,13 @@ private void StartDrag()
if (TryComp(_draggedEntity, out var draggedSprite))
{
+ var screenPos = _inputManager.MouseScreenPosition;
+ // No _draggedEntity in null window (Happens in tests)
+ if (!screenPos.IsValid)
+ return;
+
// pop up drag shadow under mouse
- var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition);
+ var mousePos = _eyeManager.PixelToMap(screenPos);
_dragShadow = EntityManager.SpawnEntity("dragshadow", mousePos);
var dragSprite = Comp(_dragShadow.Value);
dragSprite.CopyFrom(draggedSprite);
@@ -517,6 +523,9 @@ private void RemoveHighlights()
if (dropEv2.Handled)
return dropEv2.CanDrop;
+ if (dropEv.Handled && dropEv.CanDrop)
+ return true;
+
return null;
}
@@ -530,7 +539,7 @@ public override void Update(float frameTime)
case DragState.MouseDown:
{
var screenPos = _inputManager.MouseScreenPosition;
- if ((_mouseDownScreenPos!.Value.Position - screenPos.Position).Length() > _deadzone)
+ if ((_mouseDownScreenPos!.Value.Position - screenPos.Position).Length() > Deadzone)
{
StartDrag();
}
@@ -551,7 +560,7 @@ public override void FrameUpdate(float frameTime)
if (Exists(_dragShadow))
{
var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition);
- Transform(_dragShadow.Value).WorldPosition = mousePos.Position;
+ _transformSystem.SetWorldPosition(_dragShadow.Value, mousePos.Position);
}
}
}
diff --git a/Content.Client/Inventory/StrippableBoundUserInterface.cs b/Content.Client/Inventory/StrippableBoundUserInterface.cs
index 7e50eb1c68..132c5ed654 100644
--- a/Content.Client/Inventory/StrippableBoundUserInterface.cs
+++ b/Content.Client/Inventory/StrippableBoundUserInterface.cs
@@ -41,7 +41,7 @@ public sealed class StrippableBoundUserInterface : BoundUserInterface
public const string HiddenPocketEntityId = "StrippingHiddenEntity";
[ViewVariables]
- private readonly StrippingMenu? _strippingMenu;
+ private StrippingMenu? _strippingMenu;
[ViewVariables]
private readonly EntityUid _virtualHiddenEntity;
@@ -51,33 +51,30 @@ public StrippableBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, u
_examine = EntMan.System();
_inv = EntMan.System();
_cuffable = EntMan.System();
-
- // TODO update name when identity changes
- var title = Loc.GetString("strippable-bound-user-interface-stripping-menu-title", ("ownerName", Identity.Name(Owner, EntMan)));
- _strippingMenu = new StrippingMenu(title, this);
- _strippingMenu.OnClose += Close;
-
- // TODO use global entity
- // BUIs are opened and closed while applying comp sates, so spawning entities here is probably not the best idea.
_virtualHiddenEntity = EntMan.SpawnEntity(HiddenPocketEntityId, MapCoordinates.Nullspace);
}
protected override void Open()
{
base.Open();
+
+ _strippingMenu = this.CreateWindow();
+ _strippingMenu.OnDirty += UpdateMenu;
+ _strippingMenu.Title = Loc.GetString("strippable-bound-user-interface-stripping-menu-title", ("ownerName", Identity.Name(Owner, EntMan)));
+
_strippingMenu?.OpenCenteredLeft();
}
protected override void Dispose(bool disposing)
{
- base.Dispose(disposing);
-
- EntMan.DeleteEntity(_virtualHiddenEntity);
-
if (!disposing)
return;
- _strippingMenu?.Dispose();
+ if (_strippingMenu != null)
+ _strippingMenu.OnDirty -= UpdateMenu;
+
+ EntMan.DeleteEntity(_virtualHiddenEntity);
+ base.Dispose(disposing);
}
public void DirtyMenu()
diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs
index 328cf41d0d..1fd237cf3e 100644
--- a/Content.Client/IoC/ClientContentIoC.cs
+++ b/Content.Client/IoC/ClientContentIoC.cs
@@ -4,23 +4,23 @@
using Content.Client.Clickable;
using Content.Client.DebugMon;
using Content.Client.Eui;
+using Content.Client.Fullscreen;
using Content.Client.GhostKick;
+using Content.Client.Guidebook;
using Content.Client.Launcher;
+using Content.Client.Mapping;
using Content.Client.Parallax.Managers;
using Content.Client.Players.PlayTimeTracking;
+using Content.Client.Replay;
using Content.Client.Screenshot;
-using Content.Client.Fullscreen;
using Content.Client.Stylesheets;
using Content.Client.Viewport;
using Content.Client.Voting;
using Content.Shared.Administration.Logs;
-using Content.Client.Guidebook;
using Content.Client.Lobby;
-using Content.Client.Replay;
using Content.Shared.Administration.Managers;
using Content.Shared.Players.PlayTimeTracking;
-
namespace Content.Client.IoC
{
internal static class ClientContentIoC
@@ -49,6 +49,7 @@ public static void Register()
collection.Register();
collection.Register();
collection.Register();
+ collection.Register();
collection.Register();
}
}
diff --git a/Content.Client/Items/Systems/ItemSystem.cs b/Content.Client/Items/Systems/ItemSystem.cs
index 5e60d06d0c..2b5a41c6ce 100644
--- a/Content.Client/Items/Systems/ItemSystem.cs
+++ b/Content.Client/Items/Systems/ItemSystem.cs
@@ -43,7 +43,7 @@ private void OnEquipped(EntityUid uid, SpriteComponent component, GotEquippedEve
public override void VisualsChanged(EntityUid uid)
{
// if the item is in a container, it might be equipped to hands or inventory slots --> update visuals.
- if (Container.TryGetContainingContainer(uid, out var container))
+ if (Container.TryGetContainingContainer((uid, null, null), out var container))
RaiseLocalEvent(container.Owner, new VisualsChangedEvent(GetNetEntity(uid), container.ID));
}
diff --git a/Content.Client/Items/Systems/ItemToggleSystem.cs b/Content.Client/Items/Systems/ItemToggleSystem.cs
deleted file mode 100644
index 46d6f1b464..0000000000
--- a/Content.Client/Items/Systems/ItemToggleSystem.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Content.Shared.Item.ItemToggle;
-
-namespace Content.Shared.Item;
-
-///
-public sealed class ItemToggleSystem : SharedItemToggleSystem
-{
-
-}
diff --git a/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs b/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
index f97d8a7330..7884268c42 100644
--- a/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
+++ b/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
@@ -12,42 +12,34 @@ namespace Content.Client.Kitchen.UI
[GenerateTypedNameReferences]
public sealed partial class GrinderMenu : FancyWindow
{
- private readonly IEntityManager _entityManager;
- private readonly IPrototypeManager _prototypeManager;
- private readonly ReagentGrinderBoundUserInterface _owner;
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly Dictionary _chamberVisualContents = new();
- public GrinderMenu(ReagentGrinderBoundUserInterface owner, IEntityManager entityManager, IPrototypeManager prototypeManager)
+ public event Action? OnToggleAuto;
+ public event Action? OnGrind;
+ public event Action? OnJuice;
+ public event Action? OnEjectAll;
+ public event Action? OnEjectBeaker;
+ public event Action? OnEjectChamber;
+
+ public GrinderMenu()
{
RobustXamlLoader.Load(this);
- _entityManager = entityManager;
- _prototypeManager = prototypeManager;
- _owner = owner;
- AutoModeButton.OnPressed += owner.ToggleAutoMode;
- GrindButton.OnPressed += owner.StartGrinding;
- JuiceButton.OnPressed += owner.StartJuicing;
- ChamberContentBox.EjectButton.OnPressed += owner.EjectAll;
- BeakerContentBox.EjectButton.OnPressed += owner.EjectBeaker;
+ IoCManager.InjectDependencies(this);
+ AutoModeButton.OnPressed += _ => OnToggleAuto?.Invoke();
+ GrindButton.OnPressed += _ => OnGrind?.Invoke();
+ JuiceButton.OnPressed += _ => OnJuice?.Invoke();
+ ChamberContentBox.EjectButton.OnPressed += _ => OnEjectAll?.Invoke();
+ BeakerContentBox.EjectButton.OnPressed += _ => OnEjectBeaker?.Invoke();
ChamberContentBox.BoxContents.OnItemSelected += OnChamberBoxContentsItemSelected;
BeakerContentBox.BoxContents.SelectMode = ItemList.ItemListSelectMode.None;
}
private void OnChamberBoxContentsItemSelected(ItemList.ItemListSelectedEventArgs args)
{
- _owner.EjectChamberContent(_chamberVisualContents[args.ItemIndex]);
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- _chamberVisualContents.Clear();
- GrindButton.OnPressed -= _owner.StartGrinding;
- JuiceButton.OnPressed -= _owner.StartJuicing;
- ChamberContentBox.EjectButton.OnPressed -= _owner.EjectAll;
- BeakerContentBox.EjectButton.OnPressed -= _owner.EjectBeaker;
- ChamberContentBox.BoxContents.OnItemSelected -= OnChamberBoxContentsItemSelected;
+ OnEjectChamber?.Invoke(_chamberVisualContents[args.ItemIndex]);
}
public void UpdateState(ReagentGrinderInterfaceState state)
diff --git a/Content.Client/Kitchen/UI/MicrowaveBoundUserInterface.cs b/Content.Client/Kitchen/UI/MicrowaveBoundUserInterface.cs
index 7e7dd2d693..643ac47054 100644
--- a/Content.Client/Kitchen/UI/MicrowaveBoundUserInterface.cs
+++ b/Content.Client/Kitchen/UI/MicrowaveBoundUserInterface.cs
@@ -3,6 +3,7 @@
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
@@ -19,28 +20,15 @@ public sealed class MicrowaveBoundUserInterface : BoundUserInterface
[ViewVariables]
private readonly Dictionary _reagents = new();
- [Dependency] private readonly IGameTiming _gameTiming = default!;
-
- public MicrowaveUpdateUserInterfaceState currentState = default!;
-
- private IEntityManager _entManager;
public MicrowaveBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
- _entManager = IoCManager.Resolve();
- }
-
- public TimeSpan GetCurrentTime()
- {
- return _gameTiming.CurTime;
}
protected override void Open()
{
base.Open();
- _menu = new MicrowaveMenu(this);
- _menu.OpenCentered();
- _menu.OnClose += Close;
+ _menu = this.CreateWindow();
_menu.StartButton.OnPressed += _ => SendPredictedMessage(new MicrowaveStartCookMessage());
_menu.EjectButton.OnPressed += _ => SendPredictedMessage(new MicrowaveEjectMessage());
_menu.IngredientsList.OnItemSelected += args =>
@@ -74,38 +62,23 @@ protected override void Open()
};
}
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (!disposing)
- {
- return;
- }
-
- _solids.Clear();
- _menu?.Dispose();
- }
-
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
- if (state is not MicrowaveUpdateUserInterfaceState cState)
+ if (state is not MicrowaveUpdateUserInterfaceState cState || _menu == null)
{
return;
}
+ _menu.IsBusy = cState.IsMicrowaveBusy;
+ _menu.CurrentCooktimeEnd = cState.CurrentCookTimeEnd;
- _menu?.ToggleBusyDisableOverlayPanel(cState.IsMicrowaveBusy || cState.ContainedSolids.Length == 0);
- currentState = cState;
-
+ _menu.ToggleBusyDisableOverlayPanel(cState.IsMicrowaveBusy || cState.ContainedSolids.Length == 0);
// TODO move this to a component state and ensure the net ids.
- RefreshContentsDisplay(_entManager.GetEntityArray(cState.ContainedSolids));
-
- if (_menu == null) return;
+ RefreshContentsDisplay(EntMan.GetEntityArray(cState.ContainedSolids));
//Set the cook time info label
- var cookTime = cState.ActiveButtonIndex == 0
+ var cookTime = cState.ActiveButtonIndex == 0
? Loc.GetString("microwave-menu-instant-button")
: cState.CurrentCookTime.ToString();
diff --git a/Content.Client/Kitchen/UI/MicrowaveMenu.xaml.cs b/Content.Client/Kitchen/UI/MicrowaveMenu.xaml.cs
index b292e9f146..13029e3846 100644
--- a/Content.Client/Kitchen/UI/MicrowaveMenu.xaml.cs
+++ b/Content.Client/Kitchen/UI/MicrowaveMenu.xaml.cs
@@ -9,22 +9,21 @@ namespace Content.Client.Kitchen.UI
[GenerateTypedNameReferences]
public sealed partial class MicrowaveMenu : FancyWindow
{
- public sealed class MicrowaveCookTimeButton : Button
- {
- public uint CookTime;
- }
+ [Dependency] private readonly IGameTiming _timing = default!;
public event Action? OnCookTimeSelected;
public ButtonGroup CookTimeButtonGroup { get; }
- private readonly MicrowaveBoundUserInterface _owner;
- public MicrowaveMenu(MicrowaveBoundUserInterface owner)
+ public bool IsBusy;
+ public TimeSpan CurrentCooktimeEnd;
+
+ public MicrowaveMenu()
{
RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
CookTimeButtonGroup = new ButtonGroup();
InstantCookButton.Group = CookTimeButtonGroup;
- _owner = owner;
InstantCookButton.OnPressed += args =>
{
OnCookTimeSelected?.Invoke(args, 0);
@@ -65,14 +64,20 @@ public void ToggleBusyDisableOverlayPanel(bool shouldDisable)
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
- if(!_owner.currentState.IsMicrowaveBusy)
+
+ if (!IsBusy)
return;
- if(_owner.currentState.CurrentCookTimeEnd > _owner.GetCurrentTime())
+ if (CurrentCooktimeEnd > _timing.CurTime)
{
CookTimeInfoLabel.Text = Loc.GetString("microwave-bound-user-interface-cook-time-label",
- ("time",_owner.currentState.CurrentCookTimeEnd.Subtract(_owner.GetCurrentTime()).Seconds));
+ ("time", CurrentCooktimeEnd.Subtract(_timing.CurTime).Seconds));
}
}
+
+ public sealed class MicrowaveCookTimeButton : Button
+ {
+ public uint CookTime;
+ }
}
}
diff --git a/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs b/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs
index e6f108b305..bc4cc75b4d 100644
--- a/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs
+++ b/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs
@@ -1,6 +1,7 @@
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Kitchen;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Prototypes;
@@ -8,8 +9,6 @@ namespace Content.Client.Kitchen.UI
{
public sealed class ReagentGrinderBoundUserInterface : BoundUserInterface
{
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
-
[ViewVariables]
private GrinderMenu? _menu;
@@ -21,20 +20,13 @@ protected override void Open()
{
base.Open();
- _menu = new GrinderMenu(this, EntMan, _prototypeManager);
- _menu.OpenCentered();
- _menu.OnClose += Close;
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- {
- return;
- }
-
- _menu?.Dispose();
+ _menu = this.CreateWindow();
+ _menu.OnToggleAuto += ToggleAutoMode;
+ _menu.OnGrind += StartGrinding;
+ _menu.OnJuice += StartJuicing;
+ _menu.OnEjectAll += EjectAll;
+ _menu.OnEjectBeaker += EjectBeaker;
+ _menu.OnEjectChamber += EjectChamberContent;
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -52,27 +44,27 @@ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
_menu?.HandleMessage(message);
}
- public void ToggleAutoMode(BaseButton.ButtonEventArgs args)
+ public void ToggleAutoMode()
{
SendMessage(new ReagentGrinderToggleAutoModeMessage());
}
- public void StartGrinding(BaseButton.ButtonEventArgs? _ = null)
+ public void StartGrinding()
{
SendMessage(new ReagentGrinderStartMessage(GrinderProgram.Grind));
}
- public void StartJuicing(BaseButton.ButtonEventArgs? _ = null)
+ public void StartJuicing()
{
SendMessage(new ReagentGrinderStartMessage(GrinderProgram.Juice));
}
- public void EjectAll(BaseButton.ButtonEventArgs? _ = null)
+ public void EjectAll()
{
SendMessage(new ReagentGrinderEjectChamberAllMessage());
}
- public void EjectBeaker(BaseButton.ButtonEventArgs? _ = null)
+ public void EjectBeaker()
{
SendMessage(new ItemSlotButtonPressedEvent(SharedReagentGrinder.BeakerSlotId));
}
diff --git a/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs b/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs
index 555f1ff09e..6b65612341 100644
--- a/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs
+++ b/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs
@@ -1,6 +1,7 @@
using Content.Shared.Labels;
using Content.Shared.Labels.Components;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
namespace Content.Client.Labels.UI
{
@@ -23,13 +24,8 @@ protected override void Open()
{
base.Open();
- _window = new HandLabelerWindow();
- if (State != null)
- UpdateState(State);
+ _window = this.CreateWindow();
- _window.OpenCentered();
-
- _window.OnClose += Close;
_window.OnLabelChanged += OnLabelChanged;
Reload();
}
@@ -51,13 +47,5 @@ public void Reload()
_window.SetCurrentLabel(component.AssignedLabel);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing) return;
- _window?.Dispose();
- }
}
-
}
diff --git a/Content.Client/LateJoin/LateJoinGui.cs b/Content.Client/LateJoin/LateJoinGui.cs
index 62a06629f2..13cf281513 100644
--- a/Content.Client/LateJoin/LateJoinGui.cs
+++ b/Content.Client/LateJoin/LateJoinGui.cs
@@ -2,9 +2,11 @@
using System.Numerics;
using Content.Client.CrewManifest;
using Content.Client.GameTicking.Managers;
+using Content.Client.Lobby;
using Content.Client.UserInterface.Controls;
using Content.Client.Players.PlayTimeTracking;
using Content.Shared.CCVar;
+using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.StatusIcon;
using Robust.Client.Console;
@@ -26,6 +28,7 @@ public sealed class LateJoinGui : DefaultWindow
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
[Dependency] private readonly JobRequirementsManager _jobRequirements = default!;
+ [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
public event Action<(NetEntity, string)> SelectedId;
@@ -254,7 +257,7 @@ private void RebuildUI()
jobButton.OnPressed += _ => SelectedId.Invoke((id, jobButton.JobId));
- if (!_jobRequirements.IsAllowed(prototype, out var reason))
+ if (!_jobRequirements.IsAllowed(prototype, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
{
jobButton.Disabled = true;
diff --git a/Content.Client/Lathe/LatheSystem.cs b/Content.Client/Lathe/LatheSystem.cs
index c72f01b39b..386520c198 100644
--- a/Content.Client/Lathe/LatheSystem.cs
+++ b/Content.Client/Lathe/LatheSystem.cs
@@ -22,21 +22,29 @@ private void OnAppearanceChange(EntityUid uid, LatheComponent component, ref App
if (args.Sprite == null)
return;
+ // Lathe specific stuff
+ if (_appearance.TryGetData(uid, LatheVisuals.IsRunning, out var isRunning, args.Component))
+ {
+ if (args.Sprite.LayerMapTryGet(LatheVisualLayers.IsRunning, out var runningLayer) &&
+ component.RunningState != null &&
+ component.IdleState != null)
+ {
+ var state = isRunning ? component.RunningState : component.IdleState;
+ args.Sprite.LayerSetState(runningLayer, state);
+ }
+ }
+
if (_appearance.TryGetData(uid, PowerDeviceVisuals.Powered, out var powered, args.Component) &&
args.Sprite.LayerMapTryGet(PowerDeviceVisualLayers.Powered, out var powerLayer))
{
args.Sprite.LayerSetVisible(powerLayer, powered);
- }
- // Lathe specific stuff
- if (_appearance.TryGetData(uid, LatheVisuals.IsRunning, out var isRunning, args.Component) &&
- args.Sprite.LayerMapTryGet(LatheVisualLayers.IsRunning, out var runningLayer) &&
- component.RunningState != null &&
- component.IdleState != null)
- {
- var state = isRunning ? component.RunningState : component.IdleState;
- args.Sprite.LayerSetAnimationTime(runningLayer, 0f);
- args.Sprite.LayerSetState(runningLayer, state);
+ if (component.UnlitIdleState != null &&
+ component.UnlitRunningState != null)
+ {
+ var state = isRunning ? component.UnlitRunningState : component.UnlitIdleState;
+ args.Sprite.LayerSetState(powerLayer, state);
+ }
}
}
diff --git a/Content.Client/Lathe/UI/LatheBoundUserInterface.cs b/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
index 6e6d1b9176..a599f79152 100644
--- a/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
+++ b/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
@@ -1,6 +1,7 @@
using Content.Shared.Lathe;
using Content.Shared.Research.Components;
using JetBrains.Annotations;
+using Robust.Client.UserInterface;
namespace Content.Client.Lathe.UI
{
@@ -17,9 +18,9 @@ protected override void Open()
{
base.Open();
- _menu = new LatheMenu(this);
- _menu.OnClose += Close;
-
+ _menu = this.CreateWindow();
+ _menu.SetEntity(Owner);
+ _menu.OpenCenteredRight();
_menu.OnServerListButtonPressed += _ =>
{
@@ -30,8 +31,6 @@ protected override void Open()
{
SendMessage(new LatheQueueRecipeMessage(recipe, amount));
};
-
- _menu.OpenCenteredRight();
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -50,13 +49,5 @@ protected override void UpdateState(BoundUserInterfaceState state)
break;
}
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
- _menu?.Dispose();
- }
}
}
diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml b/Content.Client/Lathe/UI/LatheMenu.xaml
index e3247fe703..5b21f0bae6 100644
--- a/Content.Client/Lathe/UI/LatheMenu.xaml
+++ b/Content.Client/Lathe/UI/LatheMenu.xaml
@@ -100,11 +100,9 @@
Margin="5 0 0 0"
Text="{Loc 'lathe-menu-fabricating-message'}">
-
+