diff --git a/Content.Benchmarks/DeltaPressureBenchmark.cs b/Content.Benchmarks/DeltaPressureBenchmark.cs
new file mode 100644
index 00000000000..b31b3ed1a24
--- /dev/null
+++ b/Content.Benchmarks/DeltaPressureBenchmark.cs
@@ -0,0 +1,174 @@
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Diagnosers;
+using Content.IntegrationTests;
+using Content.IntegrationTests.Pair;
+using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.Atmos.Components;
+using Content.Shared.CCVar;
+using Robust.Shared;
+using Robust.Shared.Analyzers;
+using Robust.Shared.Configuration;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Maths;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Benchmarks;
+
+///
+/// Spawns N number of entities with a and
+/// simulates them for a number of ticks M.
+///
+[Virtual]
+[GcServer(true)]
+//[MemoryDiagnoser]
+//[ThreadingDiagnoser]
+public class DeltaPressureBenchmark
+{
+ ///
+ /// Number of entities (windows, really) to spawn with a .
+ ///
+ [Params(1, 10, 100, 1000, 5000, 10000, 50000, 100000)]
+ public int EntityCount;
+
+ ///
+ /// Number of entities that each parallel processing job will handle.
+ ///
+ // [Params(1, 10, 100, 1000, 5000, 10000)] For testing how multithreading parameters affect performance (THESE TESTS TAKE 16+ HOURS TO RUN)
+ [Params(10)]
+ public int BatchSize;
+
+ ///
+ /// Number of entities to process per iteration in the DeltaPressure
+ /// processing loop.
+ ///
+ // [Params(100, 1000, 5000, 10000, 50000)]
+ [Params(1000)]
+ public int EntitiesPerIteration;
+
+ private readonly EntProtoId _windowProtoId = "Window";
+ private readonly EntProtoId _wallProtoId = "WallPlastitaniumIndestructible";
+
+ private TestPair _pair = default!;
+ private IEntityManager _entMan = default!;
+ private SharedMapSystem _map = default!;
+ private IRobustRandom _random = default!;
+ private IConfigurationManager _cvar = default!;
+ private ITileDefinitionManager _tileDefMan = default!;
+ private AtmosphereSystem _atmospereSystem = default!;
+
+ private Entity
+ _testEnt;
+
+ [GlobalSetup]
+ public async Task SetupAsync()
+ {
+ ProgramShared.PathOffset = "../../../../";
+ PoolManager.Startup();
+ _pair = await PoolManager.GetServerClient();
+ var server = _pair.Server;
+
+ var mapdata = await _pair.CreateTestMap();
+
+ _entMan = server.ResolveDependency();
+ _map = _entMan.System();
+ _random = server.ResolveDependency();
+ _cvar = server.ResolveDependency();
+ _tileDefMan = server.ResolveDependency();
+ _atmospereSystem = _entMan.System();
+
+ _random.SetSeed(69420); // Randomness needs to be deterministic for benchmarking.
+
+ _cvar.SetCVar(CCVars.DeltaPressureParallelToProcessPerIteration, EntitiesPerIteration);
+ _cvar.SetCVar(CCVars.DeltaPressureParallelBatchSize, BatchSize);
+
+ var plating = _tileDefMan["Plating"].TileId;
+
+ /*
+ Basically, we want to have a 5-wide grid of tiles.
+ Edges are walled, and the length of the grid is determined by N + 2.
+ Windows should only touch the top and bottom walls, and each other.
+ */
+
+ var length = EntityCount + 2; // ensures we can spawn exactly N windows between side walls
+ const int height = 5;
+
+ await server.WaitPost(() =>
+ {
+ // Fill required tiles (extend grid) with plating
+ for (var x = 0; x < length; x++)
+ {
+ for (var y = 0; y < height; y++)
+ {
+ _map.SetTile(mapdata.Grid, mapdata.Grid, new Vector2i(x, y), new Tile(plating));
+ }
+ }
+
+ // Spawn perimeter walls and windows row in the middle (y = 2)
+ const int midY = height / 2;
+ for (var x = 0; x < length; x++)
+ {
+ for (var y = 0; y < height; y++)
+ {
+ var coords = new EntityCoordinates(mapdata.Grid, x + 0.5f, y + 0.5f);
+
+ var isPerimeter = x == 0 || x == length - 1 || y == 0 || y == height - 1;
+ if (isPerimeter)
+ {
+ _entMan.SpawnEntity(_wallProtoId, coords);
+ continue;
+ }
+
+ // Spawn windows only on the middle row, spanning interior (excluding side walls)
+ if (y == midY)
+ {
+ _entMan.SpawnEntity(_windowProtoId, coords);
+ }
+ }
+ }
+ });
+
+ // Next we run the fixgridatmos command to ensure that we have some air on our grid.
+ // Wait a little bit as well.
+ // TODO: Unhardcode command magic string when fixgridatmos is an actual command we can ref and not just
+ // a stamp-on in AtmosphereSystem.
+ await _pair.WaitCommand("fixgridatmos " + mapdata.Grid.Owner, 1);
+
+ var uid = mapdata.Grid.Owner;
+ _testEnt = new Entity(
+ uid,
+ _entMan.GetComponent(uid),
+ _entMan.GetComponent(uid),
+ _entMan.GetComponent(uid),
+ _entMan.GetComponent(uid));
+ }
+
+ [Benchmark]
+ public async Task PerformFullProcess()
+ {
+ await _pair.Server.WaitPost(() =>
+ {
+ while (!_atmospereSystem.RunProcessingStage(_testEnt, AtmosphereProcessingState.DeltaPressure)) { }
+ });
+ }
+
+ [Benchmark]
+ public async Task PerformSingleRunProcess()
+ {
+ await _pair.Server.WaitPost(() =>
+ {
+ _atmospereSystem.RunProcessingStage(_testEnt, AtmosphereProcessingState.DeltaPressure);
+ });
+ }
+
+ [GlobalCleanup]
+ public async Task CleanupAsync()
+ {
+ await _pair.DisposeAsync();
+ PoolManager.Shutdown();
+ }
+}
diff --git a/Content.Benchmarks/MapLoadBenchmark.cs b/Content.Benchmarks/MapLoadBenchmark.cs
index f763ee321e6..126ac99774f 100644
--- a/Content.Benchmarks/MapLoadBenchmark.cs
+++ b/Content.Benchmarks/MapLoadBenchmark.cs
@@ -47,7 +47,7 @@ public class MapLoadBenchmark
PoolManager.Shutdown();
}
- public static readonly string[] MapsSource = { "Empty", "Box", "Aspid", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Saltern", "Packed", "Omega", "Cluster", "Gemini", "Reach", "Origin", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Barratry" };
+ public static string[] MapsSource { get; } = { "Empty", "Saltern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Convex"};
[ParamsSource(nameof(MapsSource))]
public string Map;
diff --git a/Content.Benchmarks/PvsBenchmark.cs b/Content.Benchmarks/PvsBenchmark.cs
index 1edbcb64487..51a013539e0 100644
--- a/Content.Benchmarks/PvsBenchmark.cs
+++ b/Content.Benchmarks/PvsBenchmark.cs
@@ -6,7 +6,6 @@ using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Mind;
-using Content.Server.Warps;
using Content.Shared.Warps;
using Robust.Shared;
using Robust.Shared.Analyzers;
diff --git a/Content.Client/Actions/UI/ActionAlertTooltip.cs b/Content.Client/Actions/UI/ActionAlertTooltip.cs
index 2425cdefb91..664a67b406b 100644
--- a/Content.Client/Actions/UI/ActionAlertTooltip.cs
+++ b/Content.Client/Actions/UI/ActionAlertTooltip.cs
@@ -21,7 +21,7 @@ namespace Content.Client.Actions.UI
///
public (TimeSpan Start, TimeSpan End)? Cooldown { get; set; }
- public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = null, FormattedMessage? charges = null)
+ public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = null)
{
_gameTiming = IoCManager.Resolve();
@@ -52,17 +52,6 @@ namespace Content.Client.Actions.UI
vbox.AddChild(description);
}
- if (charges != null && !string.IsNullOrWhiteSpace(charges.ToString()))
- {
- var chargesLabel = new RichTextLabel
- {
- MaxWidth = TooltipTextMaxWidth,
- StyleClasses = { StyleNano.StyleClassTooltipActionCharges }
- };
- chargesLabel.SetMessage(charges);
- vbox.AddChild(chargesLabel);
- }
-
vbox.AddChild(_cooldownLabel = new RichTextLabel
{
MaxWidth = TooltipTextMaxWidth,
diff --git a/Content.Client/Administration/Components/HeadstandComponent.cs b/Content.Client/Administration/Components/HeadstandComponent.cs
deleted file mode 100644
index a4e3bfc5aaf..00000000000
--- a/Content.Client/Administration/Components/HeadstandComponent.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Content.Shared.Administration.Components;
-using Robust.Shared.GameStates;
-
-namespace Content.Client.Administration.Components;
-
-[RegisterComponent]
-public sealed partial class HeadstandComponent : SharedHeadstandComponent
-{
-
-}
diff --git a/Content.Client/Administration/Managers/ClientAdminManager.cs b/Content.Client/Administration/Managers/ClientAdminManager.cs
index 0f740c81045..3f072691de6 100644
--- a/Content.Client/Administration/Managers/ClientAdminManager.cs
+++ b/Content.Client/Administration/Managers/ClientAdminManager.cs
@@ -15,6 +15,7 @@ namespace Content.Client.Administration.Managers
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IClientNetManager _netMgr = default!;
[Dependency] private readonly IClientConGroupController _conGroup = default!;
+ [Dependency] private readonly IClientConsoleHost _host = default!;
[Dependency] private readonly IResourceManager _res = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterface = default!;
@@ -86,12 +87,12 @@ namespace Content.Client.Administration.Managers
private void UpdateMessageRx(MsgUpdateAdminStatus message)
{
_availableCommands.Clear();
- var host = IoCManager.Resolve();
// Anything marked as Any we'll just add even if the server doesn't know about it.
- foreach (var (command, instance) in host.AvailableCommands)
+ foreach (var (command, instance) in _host.AvailableCommands)
{
- if (Attribute.GetCustomAttribute(instance.GetType(), typeof(AnyCommandAttribute)) == null) continue;
+ if (Attribute.GetCustomAttribute(instance.GetType(), typeof(AnyCommandAttribute)) == null)
+ continue;
_availableCommands.Add(command);
}
diff --git a/Content.Client/Administration/Systems/AdminFrozenSystem.cs b/Content.Client/Administration/Systems/AdminFrozenSystem.cs
deleted file mode 100644
index 885585f985c..00000000000
--- a/Content.Client/Administration/Systems/AdminFrozenSystem.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using Content.Shared.Administration;
-
-namespace Content.Client.Administration.Systems;
-
-public sealed class AdminFrozenSystem : SharedAdminFrozenSystem
-{
-}
diff --git a/Content.Client/Administration/Systems/HeadstandSystem.cs b/Content.Client/Administration/Systems/HeadstandSystem.cs
index d0634e4ddd6..8ab337f3ccf 100644
--- a/Content.Client/Administration/Systems/HeadstandSystem.cs
+++ b/Content.Client/Administration/Systems/HeadstandSystem.cs
@@ -1,4 +1,4 @@
-using Content.Client.Administration.Components;
+using Content.Shared.Administration.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Administration.Systems;
diff --git a/Content.Client/Administration/UI/CustomControls/AdminLogLabel.cs b/Content.Client/Administration/UI/CustomControls/AdminLogLabel.cs
deleted file mode 100644
index 0de38ce234f..00000000000
--- a/Content.Client/Administration/UI/CustomControls/AdminLogLabel.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Content.Shared.Administration.Logs;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-
-namespace Content.Client.Administration.UI.CustomControls;
-
-public sealed class AdminLogLabel : RichTextLabel
-{
- public AdminLogLabel(ref SharedAdminLog log, HSeparator separator)
- {
- Log = log;
- Separator = separator;
-
- SetMessage($"{log.Date:HH:mm:ss}: {log.Message}");
- OnVisibilityChanged += VisibilityChanged;
- }
-
- public SharedAdminLog Log { get; }
-
- public HSeparator Separator { get; }
-
- private void VisibilityChanged(Control control)
- {
- Separator.Visible = Visible;
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- OnVisibilityChanged -= VisibilityChanged;
- }
-}
diff --git a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs
index c7fbf6c2dc0..8027a00c540 100644
--- a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs
+++ b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs
@@ -1,16 +1,15 @@
using System.Linq;
+using System.Text.RegularExpressions;
using Content.Client.Administration.Systems;
using Content.Client.UserInterface.Controls;
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;
@@ -96,13 +95,26 @@ public sealed partial class PlayerListControl : BoxContainer
private void FilterList()
{
_sortedPlayerList.Clear();
+
+ Regex filterRegex;
+ // There is no neat way to handle invalid regex being submitted other than
+ // catching and ignoring the exception which gets thrown when it's invalid.
+ try
+ {
+ filterRegex = new Regex(FilterLineEdit.Text, RegexOptions.IgnoreCase);
+ }
+ catch (ArgumentException)
+ {
+ return;
+ }
+
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()))
+ && !filterRegex.IsMatch(displayName))
continue;
_sortedPlayerList.Add(info);
}
diff --git a/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml.cs b/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml.cs
index cd6a56ea71e..f62a6c71e49 100644
--- a/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml.cs
+++ b/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml.cs
@@ -1,10 +1,8 @@
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;
diff --git a/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml b/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml
index 145a224ddec..9b5da778017 100644
--- a/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml
+++ b/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml
@@ -1,5 +1,6 @@
+ xmlns:aui="clr-namespace:Content.Client.Administration.UI.CustomControls"
+ xmlns:ui="clr-namespace:Content.Client.Options.UI">
@@ -56,6 +57,13 @@
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml.cs b/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml.cs
index 826ec038799..00b5e40e9ad 100644
--- a/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml.cs
+++ b/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml.cs
@@ -1,6 +1,8 @@
using System.Linq;
using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
using Content.Client.Administration.UI.CustomControls;
+using Content.Client.Administration.UI.Logs.Entries;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Robust.Client.AutoGenerated;
@@ -38,6 +40,9 @@ public sealed partial class AdminLogsControl : Control
SelectAllPlayersButton.OnPressed += SelectAllPlayers;
SelectNoPlayersButton.OnPressed += SelectNoPlayers;
+ RenderRichTextButton.OnPressed += RenderRichTextChanged;
+ RemoveMarkupButton.OnPressed += RemoveMarkupChanged;
+
RoundSpinBox.IsValid = i => i > 0 && i <= CurrentRound;
RoundSpinBox.ValueChanged += RoundSpinBoxChanged;
RoundSpinBox.InitDefaultButtons();
@@ -58,6 +63,8 @@ public sealed partial class AdminLogsControl : Control
private int CurrentRound { get; set; }
+ private Regex LogSearchRegex { get; set; } = new("");
+
public int SelectedRoundId => RoundSpinBox.Value;
public string Search => LogSearch.Text;
private int ShownLogs { get; set; }
@@ -77,6 +84,8 @@ public sealed partial class AdminLogsControl : Control
///
private List RawAdminLogs { get; set; } = new();
+ private bool RenderRichText { get; set; }
+ private bool RemoveMarkup { get; set; }
public HashSet SelectedTypes { get; } = new();
public HashSet SelectedPlayers { get; } = new();
@@ -123,6 +132,19 @@ public sealed partial class AdminLogsControl : Control
private void LogSearchChanged(LineEditEventArgs args)
{
+ // This exception is thrown if the regex is invalid, which happens often, so we ignore it.
+ try
+ {
+ LogSearchRegex = new Regex(
+ "(" + LogSearch.Text + ")",
+ RegexOptions.IgnoreCase,
+ TimeSpan.FromSeconds(1));
+ }
+ catch (ArgumentException)
+ {
+ return;
+ }
+
UpdateLogs();
}
@@ -204,6 +226,26 @@ public sealed partial class AdminLogsControl : Control
UpdateLogs();
}
+ private void RenderRichTextChanged(ButtonEventArgs args)
+ {
+ RenderRichText = args.Button.Pressed;
+
+ RemoveMarkup = RemoveMarkup && !RenderRichText;
+ RemoveMarkupButton.Pressed = RemoveMarkup;
+
+ UpdateLogs();
+ }
+
+ private void RemoveMarkupChanged(ButtonEventArgs args)
+ {
+ RemoveMarkup = args.Button.Pressed;
+
+ RenderRichText = !RemoveMarkup && RenderRichText;
+ RenderRichTextButton.Pressed = RenderRichText;
+
+ UpdateLogs();
+ }
+
public void SetTypesSelection(HashSet selectedTypes, bool invert = false)
{
SelectedTypes.Clear();
@@ -262,16 +304,15 @@ public sealed partial class AdminLogsControl : Control
foreach (var child in LogsContainer.Children)
{
- if (child is not AdminLogLabel log)
- {
+ if (child is not AdminLogEntry log)
continue;
- }
child.Visible = ShouldShowLog(log);
- if (child.Visible)
- {
- ShownLogs++;
- }
+ if (!child.Visible)
+ continue;
+
+ log.RenderResults(LogSearchRegex, RenderRichText, RemoveMarkup);
+ ShownLogs++;
}
UpdateCount();
@@ -289,30 +330,30 @@ public sealed partial class AdminLogsControl : Control
button.Text.Contains(PlayerSearch.Text, StringComparison.OrdinalIgnoreCase);
}
- private bool LogMatchesPlayerFilter(AdminLogLabel label)
+ private bool LogMatchesPlayerFilter(AdminLogEntry entry)
{
- if (label.Log.Players.Length == 0)
+ if (entry.Log.Players.Length == 0)
return SelectedPlayers.Count == 0 || IncludeNonPlayerLogs;
- return SelectedPlayers.Overlaps(label.Log.Players);
+ return SelectedPlayers.Overlaps(entry.Log.Players);
}
- private bool ShouldShowLog(AdminLogLabel label)
+ private bool ShouldShowLog(AdminLogEntry entry)
{
// Check log type
- if (!SelectedTypes.Contains(label.Log.Type))
+ if (!SelectedTypes.Contains(entry.Log.Type))
return false;
// Check players
- if (!LogMatchesPlayerFilter(label))
+ if (!LogMatchesPlayerFilter(entry))
return false;
// Check impact
- if (!SelectedImpacts.Contains(label.Log.Impact))
+ if (!SelectedImpacts.Contains(entry.Log.Impact))
return false;
// Check search
- if (!label.Log.Message.Contains(LogSearch.Text, StringComparison.OrdinalIgnoreCase))
+ if (!LogSearchRegex.IsMatch(entry.Log.Message))
return false;
return true;
@@ -386,19 +427,31 @@ public sealed partial class AdminLogsControl : Control
{
var log = tempLogs[i];
ref var logRef = ref log; // It didn't like me doing this as one line lmao
- var separator = new HSeparator();
- var label = new AdminLogLabel(ref logRef, separator);
- label.Visible = ShouldShowLog(label);
+ var entry = new AdminLogEntry(ref log);
+
+ //var label = new AdminLogLabel(ref logRef, separator);
+ //label.Visible = ShouldShowLog(label);
TotalLogs++;
- if (label.Visible) ShownLogs++;
+ //if (label.Visible) ShownLogs++;
- LogsContainer.AddChild(label);
- LogsContainer.AddChild(separator);
+ //LogsContainer.AddChild(label);
+ LogsContainer.AddChild(entry);
}
-
- UpdateCount();
+ UpdateLogs();
}
+
+ /*
+ var span = CollectionsMarshal.AsSpan(logs);
+ for (var i = 0; i < span.Length; i++)
+ {
+ ref var log = ref span[i];
+ var entry = new AdminLogEntry(ref log);
+ TotalLogs++;
+ LogsContainer.AddChild(entry);
+ }
+ UpdateLogs();
+ */
// END DEN - Admin Log Sorting
private void ImpactButtonPressed(ButtonEventArgs args)
@@ -583,6 +636,7 @@ public sealed partial class AdminLogsControl : Control
SelectAllTypesButton.OnPressed -= SelectAllTypes;
SelectNoTypesButton.OnPressed -= SelectNoTypes;
+ IncludeNonPlayersButton.OnPressed -= IncludeNonPlayers;
IncludeNonPlayersButton.OnPressed -= IncludeNonPlayers;
SelectAllPlayersButton.OnPressed -= SelectAllPlayers;
SelectNoPlayersButton.OnPressed -= SelectNoPlayers;
diff --git a/Content.Client/Administration/UI/Logs/AdminLogsEui.cs b/Content.Client/Administration/UI/Logs/AdminLogsEui.cs
index be1bddcd370..014b58651e7 100644
--- a/Content.Client/Administration/UI/Logs/AdminLogsEui.cs
+++ b/Content.Client/Administration/UI/Logs/AdminLogsEui.cs
@@ -1,4 +1,6 @@
-using System.Linq;
+using System.IO;
+using System.Linq;
+using Content.Client.Administration.UI.Logs.Entries;
using Content.Client.Eui;
using Content.Shared.Administration.Logs;
using Content.Shared.Eui;
diff --git a/Content.Client/Administration/UI/Logs/Entries/AdminLogEntry.xaml b/Content.Client/Administration/UI/Logs/Entries/AdminLogEntry.xaml
new file mode 100644
index 00000000000..7733c2b1cba
--- /dev/null
+++ b/Content.Client/Administration/UI/Logs/Entries/AdminLogEntry.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/Logs/Entries/AdminLogEntry.xaml.cs b/Content.Client/Administration/UI/Logs/Entries/AdminLogEntry.xaml.cs
new file mode 100644
index 00000000000..fc9f960b661
--- /dev/null
+++ b/Content.Client/Administration/UI/Logs/Entries/AdminLogEntry.xaml.cs
@@ -0,0 +1,79 @@
+using System.Text.RegularExpressions;
+using Content.Client.Message;
+using Content.Shared.Administration.Logs;
+using Content.Shared.CCVar;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Configuration;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Administration.UI.Logs.Entries;
+
+[GenerateTypedNameReferences]
+public sealed partial class AdminLogEntry : BoxContainer
+{
+ private readonly IConfigurationManager _cfgManager;
+
+ public SharedAdminLog Log { get; }
+
+ private readonly string _rawMessage;
+
+ public AdminLogEntry(ref SharedAdminLog log)
+ {
+ _cfgManager = IoCManager.Resolve();
+
+ RobustXamlLoader.Load(this);
+
+ Log = log;
+
+ _rawMessage = $"{log.Date:HH:mm:ss}: {log.Message}";
+ Message.SetMessage(_rawMessage);
+
+ DetailsHeading.OnToggled += DetailsToggled;
+ }
+
+ ///
+ /// Sets text to be highlighted from a search result, and renders rich text, or removes all rich text markup.
+ ///
+ public void RenderResults(Regex highlightRegex, bool renderRichText, bool removeMarkup)
+ {
+ var color = _cfgManager.GetCVar(CCVars.AdminLogsHighlightColor);
+ var formattedMessage = renderRichText
+ ? _rawMessage
+ : removeMarkup
+ ? FormattedMessage.RemoveMarkupPermissive(_rawMessage)
+ : FormattedMessage.EscapeText(_rawMessage);
+
+ // Want to avoid highlighting smaller strings
+ if (highlightRegex.ToString().Length > 4)
+ {
+ try
+ {
+ formattedMessage = highlightRegex.Replace(formattedMessage, $"[color={color}]$1[/color]", 3);
+ }
+ catch (RegexMatchTimeoutException)
+ {
+ // if we time out then don't bother highlighting results
+ }
+ }
+
+ if (!FormattedMessage.TryFromMarkup(formattedMessage, out var outputMessage))
+ return;
+
+ Message.SetMessage(outputMessage);
+ }
+
+ ///
+ /// We perform some extra calculations in the dropdown, so we want to render that only when
+ /// the dropdown is actually opened.
+ /// This also removes itself from the event listener so it doesn't trigger again.
+ ///
+ private void DetailsToggled(BaseButton.ButtonToggledEventArgs args)
+ {
+ if (!args.Pressed || DetailsBody.ChildCount > 0)
+ return;
+ DetailsBody.AddChild(new AdminLogEntryDetails(Log));
+ DetailsHeading.OnToggled -= DetailsToggled;
+ }
+}
diff --git a/Content.Client/Administration/UI/Logs/Entries/AdminLogEntryDetails.xaml b/Content.Client/Administration/UI/Logs/Entries/AdminLogEntryDetails.xaml
new file mode 100644
index 00000000000..23391046b9b
--- /dev/null
+++ b/Content.Client/Administration/UI/Logs/Entries/AdminLogEntryDetails.xaml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/Logs/Entries/AdminLogEntryDetails.xaml.cs b/Content.Client/Administration/UI/Logs/Entries/AdminLogEntryDetails.xaml.cs
new file mode 100644
index 00000000000..611626e544a
--- /dev/null
+++ b/Content.Client/Administration/UI/Logs/Entries/AdminLogEntryDetails.xaml.cs
@@ -0,0 +1,98 @@
+using System.Linq;
+using Content.Client.Administration.Systems;
+using Content.Client.Administration.UI.CustomControls;
+using Content.Client.UserInterface.Controls;
+using Content.Client.Verbs.UI;
+using Content.Shared.Administration.Logs;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Input;
+using Robust.Shared.Network;
+
+namespace Content.Client.Administration.UI.Logs.Entries;
+
+[GenerateTypedNameReferences]
+public sealed partial class AdminLogEntryDetails : BoxContainer
+{
+ private readonly AdminSystem _adminSystem;
+ private readonly IUserInterfaceManager _uiManager;
+ private readonly IEntityManager _entManager;
+
+ public AdminLogEntryDetails(SharedAdminLog log)
+ {
+ RobustXamlLoader.Load(this);
+ _entManager = IoCManager.Resolve();
+ _uiManager = IoCManager.Resolve();
+ _adminSystem = _entManager.System();
+
+ Type.Text = log.Type.ToString();
+ Impact.Text = log.Impact.ToString();
+
+ LocalTime.Text = $"{log.Date.ToLocalTime():HH:mm:ss}";
+ UTCTime.Text = $"{log.Date:HH:mm:ss}";
+ // TimeSpan and DateTime use different formatting string conventions for some completely logical reason
+ // that mere mortals such as myself will never be able to understand.
+ CurTime.Text = new TimeSpan(log.CurTime).ToString(@"hh\:mm\:ss");
+
+ PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
+ PlayerListContainer.GenerateItem += GenerateButton;
+ PopulateList(log.Players);
+ }
+
+ private void PopulateList(Guid[] players)
+ {
+ if (players.Length == 0)
+ return;
+
+ if (_adminSystem.PlayerList is not { } allPlayers || allPlayers.Count == 0)
+ return;
+
+ var listData = new List();
+ foreach (var playerGuid in players)
+ {
+ var netUserId = new NetUserId(playerGuid);
+
+ // Linq here is fine since this runs in response to admin input in the UI and
+ // this loop only tends to go through 1-4 iterations.
+ if (allPlayers.FirstOrDefault(player => player.SessionId == netUserId) is not { } playerInfo)
+ continue;
+
+ listData.Add(new PlayerListData(playerInfo));
+ }
+
+ if (listData.Count == 0)
+ return;
+
+ PlayerListContainer.PopulateList(listData);
+ }
+
+ private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data)
+ {
+ if (args == null || data is not PlayerListData { Info: var selectedPlayer })
+ return;
+
+ if (!(args.Function == EngineKeyFunctions.UIRightClick
+ || args.Function == EngineKeyFunctions.UIClick)
+ || selectedPlayer.NetEntity == null)
+ return;
+
+ _uiManager.GetUIController().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
+ args.Handle();
+ }
+
+ private void GenerateButton(ListData data, ListContainerButton button)
+ {
+ if (data is not PlayerListData { Info: var info })
+ return;
+
+ var entryLabel = new Label();
+ entryLabel.Text = $"{info.CharacterName} ({info.Username})";
+ var entry = new BoxContainer();
+ entry.AddChild(entryLabel);
+
+ button.AddChild(entry);
+ button.AddStyleClass(ListContainer.StyleClassListContainerButton);
+ }
+}
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
index 59e0e6040b7..fa92a3e18fb 100644
--- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
@@ -57,12 +57,43 @@ public sealed partial class ObjectsTab : Control
private void TeleportTo(NetEntity nent)
{
- _console.ExecuteCommand($"tpto {nent}");
+ var selection = _selections[ObjectTypeOptions.SelectedId];
+ switch (selection)
+ {
+ case ObjectsTabSelection.Grids:
+ {
+ // directly teleport to the entity
+ _console.ExecuteCommand($"tpto {nent}");
+ }
+ break;
+ case ObjectsTabSelection.Maps:
+ {
+ // teleport to the map, not to the map entity (which is in nullspace)
+ if (!_entityManager.TryGetEntity(nent, out var map) || !_entityManager.TryGetComponent(map, out var mapComp))
+ break;
+ _console.ExecuteCommand($"tp 0 0 {mapComp.MapId}");
+ break;
+ }
+ case ObjectsTabSelection.Stations:
+ {
+ // teleport to the station's largest grid, not to the station entity (which is in nullspace)
+ if (!_entityManager.TryGetEntity(nent, out var station))
+ break;
+ var largestGrid = _entityManager.EntitySysManager.GetEntitySystem().GetLargestGrid(station.Value);
+ if (largestGrid == null)
+ break;
+ _console.ExecuteCommand($"tpto {largestGrid.Value}");
+ break;
+ }
+ default:
+ throw new NotImplementedException();
+ }
}
private void Delete(NetEntity nent)
{
_console.ExecuteCommand($"delete {nent}");
+ RefreshObjectList();
}
public void RefreshObjectList()
@@ -76,28 +107,24 @@ public sealed partial class ObjectsTab : Control
switch (selection)
{
case ObjectsTabSelection.Stations:
- entities.AddRange(_entityManager.EntitySysManager.GetEntitySystem().Stations);
+ entities.AddRange(_entityManager.EntitySysManager.GetEntitySystem().GetStationNames());
break;
case ObjectsTabSelection.Grids:
- {
- var query = _entityManager.AllEntityQueryEnumerator();
- while (query.MoveNext(out var uid, out _, out var metadata))
{
- entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
- }
+ var query = _entityManager.AllEntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out _, out var metadata))
+ entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
- break;
- }
+ break;
+ }
case ObjectsTabSelection.Maps:
- {
- var query = _entityManager.AllEntityQueryEnumerator();
- while (query.MoveNext(out var uid, out _, out var metadata))
{
- entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
- }
+ var query = _entityManager.AllEntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out _, out var metadata))
+ entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
- break;
- }
+ break;
+ }
default:
throw new ArgumentOutOfRangeException(nameof(selection), selection, null);
}
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml
index c561125a30c..7c9dde79b58 100644
--- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml
@@ -1,5 +1,6 @@
-
-
+
-
+
+
+
+
+
+
+
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs
index c64424756fa..4fb5c2788d6 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs
@@ -1,23 +1,26 @@
using Content.Shared.Atmos;
-using Content.Shared.Atmos.EntitySystems; // DeltaV
+using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
+using Content.Shared.Atmos.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
namespace Content.Client.Atmos.Monitor.UI.Widgets;
[GenerateTypedNameReferences]
public sealed partial class ScrubberControl : BoxContainer
{
- [Dependency] private readonly IEntityManager _entMan = default!; // DeltaV
- private readonly SharedAtmosphereSystem _atmos; // DeltaV
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IEntityManager _entMan = default!;
+
private GasVentScrubberData _data;
private string _address;
public event Action? ScrubberDataChanged;
- public event Action? ScrubberDataCopied;
+ public event Action? ScrubberDataCopied;
private CheckBox _enabled => CEnableDevice;
private CollapsibleHeading _addressLabel => CAddress;
@@ -25,15 +28,18 @@ public sealed partial class ScrubberControl : BoxContainer
private FloatSpinBox _volumeRate => CVolumeRate;
private CheckBox _wideNet => CWideNet;
private Button _copySettings => CCopySettings;
+ private Button _selectAll => CSelectAll;
+ private Button _deselectAll => CDeselectAll;
private GridContainer _gases => CGasContainer;
private Dictionary _gasControls = new();
public ScrubberControl(GasVentScrubberData data, string address)
{
+ IoCManager.InjectDependencies(this);
+ var atmosphereSystem = _entMan.System();
+
RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this); // DeltaV
- _atmos = _entMan.System(); // DeltaV
Name = address;
@@ -66,7 +72,7 @@ public sealed partial class ScrubberControl : BoxContainer
foreach (var value in Enum.GetValues())
{
- _pumpDirection.AddItem(Loc.GetString($"{value}"), (int) value);
+ _pumpDirection.AddItem(Loc.GetString($"air-alarm-ui-pump-direction-{value.ToString().ToLower()}"), (int) value);
}
_pumpDirection.SelectId((int) _data.PumpDirection);
@@ -83,12 +89,28 @@ public sealed partial class ScrubberControl : BoxContainer
ScrubberDataCopied?.Invoke(_data);
};
- foreach (var value in Enum.GetValues())
+ var allGases = Enum.GetValues();
+ _selectAll.OnPressed += _ =>
{
+ _data.FilterGases = new HashSet(allGases);
+ ScrubberDataChanged?.Invoke(_address, _data);
+ };
+
+ _deselectAll.OnPressed += _ =>
+ {
+ _data.FilterGases = [];
+ ScrubberDataChanged?.Invoke(_address, _data);
+ };
+
+ foreach (var value in allGases)
+ {
+ ProtoId gasProtoId = atmosphereSystem.GetGas(value);
+ var gasName = _prototypeManager.Index(gasProtoId).Name;
+
var gasButton = new Button
{
Name = value.ToString(),
- Text = Loc.GetString(_atmos.GetGas(value).Name), // DeltaV - localize it instead of showing the enum name
+ Text = Loc.GetString(gasName),
ToggleMode = true,
HorizontalExpand = true,
Pressed = _data.FilterGases.Contains(value)
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml.cs
index e59c590b398..d9945b1accb 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml.cs
@@ -1,19 +1,22 @@
using Content.Client.Message;
using Content.Shared.Atmos;
-using Content.Shared.Atmos.EntitySystems; // DeltaV
+using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Monitor;
+using Content.Shared.Atmos.Prototypes;
using Content.Shared.Temperature;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
namespace Content.Client.Atmos.Monitor.UI.Widgets;
[GenerateTypedNameReferences]
public sealed partial class SensorInfo : BoxContainer
{
- [Dependency] private readonly IEntityManager _entMan = default!; // DeltaV
- private readonly SharedAtmosphereSystem _atmos; // DeltaV
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IEntityManager _entMan = default!;
+
public Action? OnThresholdUpdate;
public event Action? SensorDataCopied;
private string _address;
@@ -26,9 +29,10 @@ public sealed partial class SensorInfo : BoxContainer
public SensorInfo(AtmosSensorData data, string address)
{
+ IoCManager.InjectDependencies(this);
+ var atmosphereSystem = _entMan.System();
+
RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this); // DeltaV
- _atmos = _entMan.System(); // DeltaV
_address = address;
@@ -50,8 +54,12 @@ public sealed partial class SensorInfo : BoxContainer
var label = new RichTextLabel();
var fractionGas = amount / data.TotalMoles;
+
+ ProtoId gasProtoId = atmosphereSystem.GetGas(gas);
+ var gasName = _prototypeManager.Index(gasProtoId).Name;
+
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator",
- ("gas", Loc.GetString(_atmos.GetGas(gas).Name)), // DeltaV - localize it instead of showing the enum name
+ ("gas", Loc.GetString(gasName)),
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
("amount", $"{amount:0.####}"),
("percentage", $"{(100 * fractionGas):0.##}")));
@@ -59,7 +67,7 @@ public sealed partial class SensorInfo : BoxContainer
_gasLabels.Add(gas, label);
var threshold = data.GasThresholds[gas];
- var gasThresholdControl = new ThresholdControl(Loc.GetString($"air-alarm-ui-thresholds-gas-title", ("gas", $"{gas}")), threshold, AtmosMonitorThresholdType.Gas, gas, 100);
+ var gasThresholdControl = new ThresholdControl(Loc.GetString($"air-alarm-ui-thresholds-gas-title"), threshold, AtmosMonitorThresholdType.Gas, gas, 100);
gasThresholdControl.Margin = new Thickness(20, 2, 2, 2);
gasThresholdControl.ThresholdDataChanged += (type, alarmThreshold, arg3) =>
{
@@ -95,6 +103,9 @@ public sealed partial class SensorInfo : BoxContainer
public void ChangeData(AtmosSensorData data)
{
+ IoCManager.InjectDependencies(this);
+ var atmosphereSystem = _entMan.System();
+
SensorAddress.Title = Loc.GetString("air-alarm-ui-window-listing-title", ("address", _address), ("state", data.AlarmState));
AlarmStateLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state-indicator",
@@ -117,8 +128,12 @@ public sealed partial class SensorInfo : BoxContainer
}
var fractionGas = amount / data.TotalMoles;
+
+ ProtoId gasProtoId = atmosphereSystem.GetGas(gas);
+ var gasName = _prototypeManager.Index(gasProtoId).Name;
+
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator",
- ("gas", Loc.GetString(_atmos.GetGas(gas).Name)), // DeltaV - localize it instead of showing the enum name
+ ("gas", Loc.GetString(gasName)),
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
("amount", $"{amount:0.####}"),
("percentage", $"{(100 * fractionGas):0.##}")));
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdBoundControl.xaml b/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdBoundControl.xaml
index 0d5e741d0f6..7ce21fdd780 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdBoundControl.xaml
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdBoundControl.xaml
@@ -2,6 +2,6 @@
HorizontalExpand="True" Orientation="Vertical"
Margin = "20 0 0 0" MinSize="160 0" >
-
+
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml b/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml
index 635a70f532c..76fc1621abb 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml
@@ -6,7 +6,7 @@
-
+
diff --git a/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs b/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs
index e280523e43e..63b4e6b0c6f 100644
--- a/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs
+++ b/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs
@@ -208,7 +208,7 @@ namespace Content.Client.Atmos.UI
});
presBox.AddChild(new Label
{
- Text = Loc.GetString("gas-analyzer-window-pressure-val-text", ("pressure", $"{gasMix.Pressure:0.##}")),
+ Text = Loc.GetString("gas-analyzer-window-pressure-val-text", ("pressure", $"{gasMix.Pressure:0.00}")),
Align = Label.AlignMode.Right,
HorizontalExpand = true
});
@@ -232,8 +232,8 @@ namespace Content.Client.Atmos.UI
tempBox.AddChild(new Label
{
Text = Loc.GetString("gas-analyzer-window-temperature-val-text",
- ("tempK", $"{gasMix.Temperature:0.#}"),
- ("tempC", $"{TemperatureHelpers.KelvinToCelsius(gasMix.Temperature):0.#}")),
+ ("tempK", $"{gasMix.Temperature:0.0}"),
+ ("tempC", $"{TemperatureHelpers.KelvinToCelsius(gasMix.Temperature):0.0}")),
Align = Label.AlignMode.Right,
HorizontalExpand = true
});
diff --git a/Content.Client/Audio/AmbientSoundSystem.cs b/Content.Client/Audio/AmbientSoundSystem.cs
index e6ea94c3a68..9929751b22d 100644
--- a/Content.Client/Audio/AmbientSoundSystem.cs
+++ b/Content.Client/Audio/AmbientSoundSystem.cs
@@ -3,17 +3,13 @@ using Content.Shared.CCVar;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Audio;
-using Robust.Shared.Log;
using Robust.Shared.Configuration;
-using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Linq;
using System.Numerics;
-using Robust.Client.GameObjects;
-using Robust.Shared.Audio.Effects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
@@ -31,6 +27,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
@@ -65,18 +62,19 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
get => _overlayEnabled;
set
{
- if (_overlayEnabled == value) return;
+ if (_overlayEnabled == value)
+ return;
+
_overlayEnabled = value;
- var overlayManager = IoCManager.Resolve();
if (_overlayEnabled)
{
_overlay = new AmbientSoundOverlay(EntityManager, this, EntityManager.System());
- overlayManager.AddOverlay(_overlay);
+ _overlayManager.AddOverlay(_overlay);
}
else
{
- overlayManager.RemoveOverlay(_overlay!);
+ _overlayManager.RemoveOverlay(_overlay!);
_overlay = null;
}
}
diff --git a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
index bf7ab26cba2..d82f6b07fbb 100644
--- a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
+++ b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
@@ -3,11 +3,8 @@ using Content.Client.Gameplay;
using Content.Shared.Audio;
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;
using Robust.Client.State;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Components;
@@ -25,6 +22,7 @@ public sealed partial class ContentAudioSystem
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
@@ -61,7 +59,7 @@ public sealed partial class ContentAudioSystem
private void InitializeAmbientMusic()
{
Subs.CVar(_configManager, CCVars.AmbientMusicVolume, AmbienceCVarChanged, true);
- _sawmill = IoCManager.Resolve().GetSawmill("audio.ambience");
+ _sawmill = _logManager.GetSawmill("audio.ambience");
// Reset audio
_nextAudio = TimeSpan.MaxValue;
diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs
index 691d955486b..536c60ed7a2 100644
--- a/Content.Client/Buckle/BuckleSystem.cs
+++ b/Content.Client/Buckle/BuckleSystem.cs
@@ -125,7 +125,7 @@ internal sealed class BuckleSystem : SharedBuckleSystem
!buckled ||
args.Sprite == null)
{
- //_rotationVisualizerSystem.SetHorizontalAngle((uid, rotVisuals), rotVisuals.DefaultRotation); // WD EDIT
+ _rotationVisualizerSystem.SetHorizontalAngle((uid, rotVisuals), rotVisuals.DefaultRotation);
return;
}
diff --git a/Content.Client/CardboardBox/CardboardBoxSystem.cs b/Content.Client/CardboardBox/CardboardBoxSystem.cs
index e52ce03a767..ecebe167277 100644
--- a/Content.Client/CardboardBox/CardboardBoxSystem.cs
+++ b/Content.Client/CardboardBox/CardboardBoxSystem.cs
@@ -1,8 +1,8 @@
using System.Numerics;
-using Content.Shared.Body.Components;
using Content.Shared.CardboardBox;
using Content.Shared.CardboardBox.Components;
using Content.Shared.Examine;
+using Content.Shared.Mobs.Components;
using Content.Shared.Movement.Components;
using Robust.Client.GameObjects;
@@ -15,13 +15,13 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
- private EntityQuery _bodyQuery;
+ private EntityQuery _mobStateQuery;
public override void Initialize()
{
base.Initialize();
- _bodyQuery = GetEntityQuery();
+ _mobStateQuery = GetEntityQuery();
SubscribeNetworkEvent(OnBoxEffect);
}
@@ -66,8 +66,8 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem
if (!_examine.InRangeUnOccluded(sourcePos, mapPos, box.Distance, null))
continue;
- // no effect for anything too exotic
- if (!_bodyQuery.HasComp(mob))
+ // no effect for non-mobs that have MobMover, such as mechs and vehicles.
+ if (!_mobStateQuery.HasComp(mob))
continue;
var ent = Spawn(box.Effect, mapPos);
diff --git a/Content.Client/Changeling/Systems/ChangelingIdentitySystem.cs b/Content.Client/Changeling/Systems/ChangelingIdentitySystem.cs
new file mode 100644
index 00000000000..348cfee0f81
--- /dev/null
+++ b/Content.Client/Changeling/Systems/ChangelingIdentitySystem.cs
@@ -0,0 +1,30 @@
+using Content.Shared.Changeling.Components;
+using Content.Shared.Changeling.Systems;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Changeling.Systems;
+
+public sealed class ChangelingIdentitySystem : SharedChangelingIdentitySystem
+{
+ [Dependency] private readonly UserInterfaceSystem _ui = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnAfterAutoHandleState);
+ }
+
+ private void OnAfterAutoHandleState(Entity ent, ref AfterAutoHandleStateEvent args)
+ {
+ UpdateUi(ent);
+ }
+
+ public void UpdateUi(EntityUid uid)
+ {
+ if (_ui.TryGetOpenUi(uid, ChangelingTransformUiKey.Key, out var bui))
+ {
+ bui.Update();
+ }
+ }
+}
diff --git a/Content.Client/Changeling/UI/ChangelingTransformBoundUserInterface.cs b/Content.Client/Changeling/UI/ChangelingTransformBoundUserInterface.cs
new file mode 100644
index 00000000000..8220e187089
--- /dev/null
+++ b/Content.Client/Changeling/UI/ChangelingTransformBoundUserInterface.cs
@@ -0,0 +1,35 @@
+using Content.Shared.Changeling.Systems;
+using JetBrains.Annotations;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Changeling.UI;
+
+[UsedImplicitly]
+public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
+{
+ private ChangelingTransformMenu? _window;
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _window = this.CreateWindow();
+
+ _window.OnIdentitySelect += SendIdentitySelect;
+
+ _window.Update(Owner);
+ }
+
+ public override void Update()
+ {
+ if (_window == null)
+ return;
+
+ _window.Update(Owner);
+ }
+
+ public void SendIdentitySelect(NetEntity identityId)
+ {
+ SendPredictedMessage(new ChangelingTransformIdentitySelectMessage(identityId));
+ }
+}
diff --git a/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml b/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml
new file mode 100644
index 00000000000..38ae0ec7153
--- /dev/null
+++ b/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml.cs b/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml.cs
new file mode 100644
index 00000000000..ebd4e904400
--- /dev/null
+++ b/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml.cs
@@ -0,0 +1,62 @@
+using System.Numerics;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Changeling.Components;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Changeling.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class ChangelingTransformMenu : RadialMenu
+{
+ [Dependency] private readonly IEntityManager _entity = default!;
+ public event Action? OnIdentitySelect;
+
+ public ChangelingTransformMenu()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ }
+
+ public void Update(EntityUid uid)
+ {
+ Main.DisposeAllChildren();
+
+ if (!_entity.TryGetComponent(uid, out var identityComp))
+ return;
+
+ foreach (var identityUid in identityComp.ConsumedIdentities)
+ {
+ if (!_entity.TryGetComponent(identityUid, out var metadata))
+ continue;
+
+ var identityName = metadata.EntityName;
+
+ var button = new ChangelingTransformMenuButton()
+ {
+ StyleClasses = { "RadialMenuButton" },
+ SetSize = new Vector2(64, 64),
+ ToolTip = identityName,
+ };
+
+ var entView = new SpriteView()
+ {
+ SetSize = new Vector2(48, 48),
+ VerticalAlignment = VAlignment.Center,
+ HorizontalAlignment = HAlignment.Center,
+ Stretch = SpriteView.StretchMode.Fill,
+ };
+ entView.SetEntity(identityUid);
+ button.OnButtonUp += _ =>
+ {
+ OnIdentitySelect?.Invoke(_entity.GetNetEntity(identityUid));
+ Close();
+ };
+ button.AddChild(entView);
+ Main.AddChild(button);
+ }
+ }
+}
+
+public sealed class ChangelingTransformMenuButton : RadialMenuTextureButtonWithSector;
diff --git a/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs b/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs
index 0131a283c8c..58cb5330a2d 100644
--- a/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs
+++ b/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs
@@ -2,7 +2,6 @@ using Content.Client.Chemistry.UI;
using Content.Client.Items;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
-using Robust.Shared.GameStates;
namespace Content.Client.Chemistry.EntitySystems;
@@ -11,6 +10,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
public override void Initialize()
{
base.Initialize();
- Subs.ItemStatus(ent => new InjectorStatusControl(ent, SolutionContainers));
+
+ Subs.ItemStatus(ent => new InjectorStatusControl(ent, SolutionContainer));
}
}
diff --git a/Content.Client/Chemistry/UI/InjectorStatusControl.cs b/Content.Client/Chemistry/UI/InjectorStatusControl.cs
index f9b0d90e205..0358876b76e 100644
--- a/Content.Client/Chemistry/UI/InjectorStatusControl.cs
+++ b/Content.Client/Chemistry/UI/InjectorStatusControl.cs
@@ -38,13 +38,13 @@ public sealed class InjectorStatusControl : Control
// only updates the UI if any of the details are different than they previously were
if (PrevVolume == solution.Volume
&& PrevMaxVolume == solution.MaxVolume
- && PrevTransferAmount == _parent.Comp.TransferAmount
+ && PrevTransferAmount == _parent.Comp.CurrentTransferAmount
&& PrevToggleState == _parent.Comp.ToggleState)
return;
PrevVolume = solution.Volume;
PrevMaxVolume = solution.MaxVolume;
- PrevTransferAmount = _parent.Comp.TransferAmount;
+ PrevTransferAmount = _parent.Comp.CurrentTransferAmount;
PrevToggleState = _parent.Comp.ToggleState;
// Update current volume and injector state
@@ -59,6 +59,6 @@ public sealed class InjectorStatusControl : Control
("currentVolume", solution.Volume),
("totalVolume", solution.MaxVolume),
("modeString", modeStringLocalized),
- ("transferVolume", _parent.Comp.TransferAmount)));
+ ("transferVolume", _parent.Comp.CurrentTransferAmount)));
}
}
diff --git a/Content.Client/Cloning/CloningSystem.cs b/Content.Client/Cloning/CloningSystem.cs
new file mode 100644
index 00000000000..9bfa2302955
--- /dev/null
+++ b/Content.Client/Cloning/CloningSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Cloning;
+
+namespace Content.Client.Cloning;
+
+public sealed partial class CloningSystem : SharedCloningSystem;
diff --git a/Content.Client/Clothing/ClientClothingSystem.cs b/Content.Client/Clothing/ClientClothingSystem.cs
index 958bab343a6..b06ec8f8e3c 100644
--- a/Content.Client/Clothing/ClientClothingSystem.cs
+++ b/Content.Client/Clothing/ClientClothingSystem.cs
@@ -1,13 +1,11 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
-using System.Numerics;
using Content.Client.DisplacementMap;
using Content.Client.Inventory;
using Content.Shared._DV.Silicon.IPC; // DeltaV - IPC Snouts
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;
@@ -15,7 +13,6 @@ using Content.Shared.Item;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
-using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Robust.Shared.Utility;
using static Robust.Client.GameObjects.SpriteComponent;
@@ -185,6 +182,7 @@ public sealed class ClientClothingSystem : ClothingSystem
var layer = new PrototypeLayerData();
layer.RsiPath = rsi.Path.ToString();
layer.State = state;
+ layer.Scale = clothing.Scale;
layers = new() { layer };
return true;
diff --git a/Content.Client/Credits/CreditsWindow.xaml.cs b/Content.Client/Credits/CreditsWindow.xaml.cs
index bc466a089bb..f287e08a967 100644
--- a/Content.Client/Credits/CreditsWindow.xaml.cs
+++ b/Content.Client/Credits/CreditsWindow.xaml.cs
@@ -83,7 +83,7 @@ public sealed partial class CreditsWindow : DefaultWindow
private async void PopulateAttributions(BoxContainer attributionsContainer, int count)
{
- attributionsContainer.DisposeAllChildren();
+ attributionsContainer.RemoveAllChildren();
if (_attributions.Count == 0)
{
@@ -256,6 +256,8 @@ public sealed partial class CreditsWindow : DefaultWindow
private void PopulateLicenses(BoxContainer licensesContainer)
{
+ licensesContainer.RemoveAllChildren();
+
foreach (var entry in CreditsManager.GetLicenses(_resourceManager).OrderBy(p => p.Name))
{
licensesContainer.AddChild(new Label
@@ -272,6 +274,8 @@ public sealed partial class CreditsWindow : DefaultWindow
private void PopulatePatrons(BoxContainer patronsContainer)
{
+ patronsContainer.RemoveAllChildren();
+
var patrons = LoadPatrons();
// Do not show "become a patron" button on Steam builds
@@ -321,6 +325,8 @@ public sealed partial class CreditsWindow : DefaultWindow
private void PopulateContributors(BoxContainer ss14ContributorsContainer)
{
+ ss14ContributorsContainer.RemoveAllChildren();
+
Button contributeButton;
ss14ContributorsContainer.AddChild(new BoxContainer
@@ -359,6 +365,7 @@ public sealed partial class CreditsWindow : DefaultWindow
AddSection(Loc.GetString("credits-window-contributors-section-title"), "GitHub.txt");
AddSection(Loc.GetString("credits-window-codebases-section-title"), "SpaceStation13.txt");
AddSection(Loc.GetString("credits-window-original-remake-team-section-title"), "OriginalRemake.txt");
+ AddSection(Loc.GetString("credits-window-immortals-title"), "Immortals.txt", true);
AddSection(Loc.GetString("credits-window-special-thanks-section-title"), "SpecialThanks.txt", true);
var linkGithub = _cfg.GetCVar(CCVars.InfoLinksGithub);
diff --git a/Content.Client/Damage/Systems/StaminaSystem.cs b/Content.Client/Damage/Systems/StaminaSystem.cs
index 046c72a8fa2..67abb776740 100644
--- a/Content.Client/Damage/Systems/StaminaSystem.cs
+++ b/Content.Client/Damage/Systems/StaminaSystem.cs
@@ -4,6 +4,7 @@ using Content.Shared.Damage.Systems;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Systems;
using Robust.Client.GameObjects;
+using Robust.Shared.Utility;
namespace Content.Client.Damage.Systems;
@@ -104,6 +105,8 @@ public sealed partial class StaminaSystem : SharedStaminaSystem
private void PlayAnimation(Entity entity)
{
+ DebugTools.Assert(entity.Comp1.CritThreshold > entity.Comp1.AnimationThreshold, $"Animation threshold on {ToPrettyString(entity)} was not less than the crit threshold. This will cause errors, animation has been cancelled.");
+
var step = Math.Clamp((entity.Comp1.StaminaDamage - entity.Comp1.AnimationThreshold) /
(entity.Comp1.CritThreshold - entity.Comp1.AnimationThreshold),
0f,
diff --git a/Content.Client/Drunk/DrunkOverlay.cs b/Content.Client/Drunk/DrunkOverlay.cs
index 1f26e75ffc9..c806cdad660 100644
--- a/Content.Client/Drunk/DrunkOverlay.cs
+++ b/Content.Client/Drunk/DrunkOverlay.cs
@@ -1,5 +1,6 @@
using Content.Shared.Drunk;
using Content.Shared.StatusEffect;
+using Content.Shared.StatusEffectNew;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
@@ -43,19 +44,21 @@ public sealed class DrunkOverlay : Overlay
if (playerEntity == null)
return;
- if (!_entityManager.HasComponent(playerEntity)
- || !_entityManager.TryGetComponent(playerEntity, out var status))
+ var statusSys = _sysMan.GetEntitySystem();
+ if (!statusSys.TryGetMaxTime(playerEntity.Value, out var status))
return;
- var statusSys = _sysMan.GetEntitySystem();
- if (!statusSys.TryGetTime(playerEntity.Value, SharedDrunkSystem.DrunkKey, out var time, status))
- return;
+ var time = status.Item2;
- var curTime = _timing.CurTime;
- var timeLeft = (float) (time.Value.Item2 - curTime).TotalSeconds;
+ var power = SharedDrunkSystem.MagicNumber;
+ if (time != null)
+ {
+ var curTime = _timing.CurTime;
+ power = (float) (time - curTime).Value.TotalSeconds;
+ }
- CurrentBoozePower += 8f * (0.5f*timeLeft - CurrentBoozePower) * args.DeltaSeconds / (timeLeft+1);
+ CurrentBoozePower += 8f * (power * 0.5f - CurrentBoozePower) * args.DeltaSeconds / (power+1);
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
diff --git a/Content.Client/Drunk/DrunkSystem.cs b/Content.Client/Drunk/DrunkSystem.cs
index d9c6bb192fd..c5fab75e7dd 100644
--- a/Content.Client/Drunk/DrunkSystem.cs
+++ b/Content.Client/Drunk/DrunkSystem.cs
@@ -1,4 +1,5 @@
using Content.Shared.Drunk;
+using Content.Shared.StatusEffectNew;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;
@@ -16,38 +17,41 @@ public sealed class DrunkSystem : SharedDrunkSystem
{
base.Initialize();
- SubscribeLocalEvent(OnDrunkInit);
- SubscribeLocalEvent(OnDrunkShutdown);
+ SubscribeLocalEvent(OnStatusApplied);
+ SubscribeLocalEvent(OnStatusRemoved);
- SubscribeLocalEvent(OnPlayerAttached);
- SubscribeLocalEvent(OnPlayerDetached);
+ SubscribeLocalEvent>(OnPlayerAttached);
+ SubscribeLocalEvent>(OnPlayerDetached);
_overlay = new();
}
- private void OnPlayerAttached(EntityUid uid, DrunkComponent component, LocalPlayerAttachedEvent args)
+ private void OnStatusApplied(Entity entity, ref StatusEffectAppliedEvent args)
{
- _overlayMan.AddOverlay(_overlay);
+ if (!_overlayMan.HasOverlay())
+ _overlayMan.AddOverlay(_overlay);
}
- private void OnPlayerDetached(EntityUid uid, DrunkComponent component, LocalPlayerDetachedEvent args)
+ private void OnStatusRemoved(Entity entity, ref StatusEffectRemovedEvent args)
{
+ if (Status.HasEffectComp(args.Target))
+ return;
+
+ if (_player.LocalEntity != args.Target)
+ return;
+
_overlay.CurrentBoozePower = 0;
_overlayMan.RemoveOverlay(_overlay);
}
- private void OnDrunkInit(EntityUid uid, DrunkComponent component, ComponentInit args)
+ private void OnPlayerAttached(Entity entity, ref StatusEffectRelayedEvent args)
{
- if (_player.LocalEntity == uid)
- _overlayMan.AddOverlay(_overlay);
+ _overlayMan.AddOverlay(_overlay);
}
- private void OnDrunkShutdown(EntityUid uid, DrunkComponent component, ComponentShutdown args)
+ private void OnPlayerDetached(Entity entity, ref StatusEffectRelayedEvent args)
{
- if (_player.LocalEntity == uid)
- {
- _overlay.CurrentBoozePower = 0;
- _overlayMan.RemoveOverlay(_overlay);
- }
+ _overlay.CurrentBoozePower = 0;
+ _overlayMan.RemoveOverlay(_overlay);
}
}
diff --git a/Content.Client/Explosion/SmokeOnTriggerSystem.cs b/Content.Client/Explosion/SmokeOnTriggerSystem.cs
deleted file mode 100644
index cac255e1ba9..00000000000
--- a/Content.Client/Explosion/SmokeOnTriggerSystem.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using Content.Shared.Explosion.EntitySystems;
-
-namespace Content.Client.Explosion;
-
-public sealed class SmokeOnTriggerSystem : SharedSmokeOnTriggerSystem
-{
-}
\ No newline at end of file
diff --git a/Content.Client/Explosion/TriggerOnProximityComponent.cs b/Content.Client/Explosion/TriggerOnProximityComponent.cs
deleted file mode 100644
index 5fa9bbfd237..00000000000
--- a/Content.Client/Explosion/TriggerOnProximityComponent.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using Content.Shared.Explosion;
-using Content.Shared.Explosion.Components;
-
-namespace Content.Client.Explosion;
-
-[RegisterComponent, Access(typeof(TriggerSystem))]
-public sealed partial class TriggerOnProximityComponent : SharedTriggerOnProximityComponent {}
diff --git a/Content.Client/Explosion/TriggerSystem.cs b/Content.Client/Explosion/TriggerSystem.cs
deleted file mode 100644
index e18569a18e7..00000000000
--- a/Content.Client/Explosion/TriggerSystem.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Content.Client.Explosion;
-
-public sealed partial class TriggerSystem : EntitySystem
-{
- public override void Initialize()
- {
- base.Initialize();
- InitializeProximity();
- }
-}
diff --git a/Content.Client/Forensics/Systems/ForensicsSystem.cs b/Content.Client/Forensics/Systems/ForensicsSystem.cs
new file mode 100644
index 00000000000..048fff600ee
--- /dev/null
+++ b/Content.Client/Forensics/Systems/ForensicsSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Forensics.Systems;
+
+namespace Content.Client.Forensics.Systems;
+
+public sealed class ForensicsSystem : SharedForensicsSystem;
diff --git a/Content.Client/GameTicking/Managers/ClientGameTicker.cs b/Content.Client/GameTicking/Managers/ClientGameTicker.cs
index 170b24c02a5..4bd91bd06c3 100644
--- a/Content.Client/GameTicking/Managers/ClientGameTicker.cs
+++ b/Content.Client/GameTicking/Managers/ClientGameTicker.cs
@@ -34,6 +34,8 @@ namespace Content.Client.GameTicking.Managers
[ViewVariables] public TimeSpan StartTime { get; private set; }
[ViewVariables] public new bool Paused { get; private set; }
+ public override IReadOnlyList<(TimeSpan, string)> AllPreviousGameRules => new List<(TimeSpan, string)>();
+
[ViewVariables] public IReadOnlyDictionary, int?>> JobsAvailable => _jobsAvailable;
[ViewVariables] public IReadOnlyDictionary StationNames => _stationNames;
diff --git a/Content.Client/HotPotato/HotPotatoSystem.cs b/Content.Client/HotPotato/HotPotatoSystem.cs
index 028a3b70d9f..a1495ab9945 100644
--- a/Content.Client/HotPotato/HotPotatoSystem.cs
+++ b/Content.Client/HotPotato/HotPotatoSystem.cs
@@ -1,5 +1,6 @@
using Content.Shared.HotPotato;
using Robust.Shared.Random;
+using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.HotPotato;
@@ -10,6 +11,9 @@ public sealed class HotPotatoSystem : SharedHotPotatoSystem
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
+ private readonly EntProtoId _hotPotatoEffectId = "HotPotatoEffect";
+
+ // TODO: particle system
public override void Update(float frameTime)
{
base.Update(frameTime);
@@ -23,7 +27,7 @@ public sealed class HotPotatoSystem : SharedHotPotatoSystem
if (_timing.CurTime < comp.TargetTime)
continue;
comp.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(comp.EffectCooldown);
- Spawn("HotPotatoEffect", _transform.GetMapCoordinates(uid).Offset(_random.NextVector2(0.25f)));
+ Spawn(_hotPotatoEffectId, _transform.GetMapCoordinates(uid).Offset(_random.NextVector2(0.25f)));
}
}
}
diff --git a/Content.Client/Implants/ImplanterSystem.cs b/Content.Client/Implants/ImplanterSystem.cs
index cca09f5dad1..4ba4d015ca5 100644
--- a/Content.Client/Implants/ImplanterSystem.cs
+++ b/Content.Client/Implants/ImplanterSystem.cs
@@ -23,6 +23,8 @@ public sealed class ImplanterSystem : SharedImplanterSystem
{
if (_uiSystem.TryGetOpenUi(uid, DeimplantUiKey.Key, out var bui))
{
+ // TODO: Don't use protoId for deimplanting
+ // and especially not raw strings!
Dictionary implants = new();
foreach (var implant in component.DeimplantWhitelist)
{
diff --git a/Content.Client/Implants/SubdermalImplantSystem.cs b/Content.Client/Implants/SubdermalImplantSystem.cs
new file mode 100644
index 00000000000..5d814eddf81
--- /dev/null
+++ b/Content.Client/Implants/SubdermalImplantSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Implants;
+
+namespace Content.Client.Implants;
+
+public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem;
diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs
index 61c0fc6c878..157951e335f 100644
--- a/Content.Client/Input/ContentContexts.cs
+++ b/Content.Client/Input/ContentContexts.cs
@@ -34,7 +34,6 @@ namespace Content.Client.Input
common.AddFunction(ContentKeyFunctions.RotateStoredItem);
common.AddFunction(ContentKeyFunctions.SaveItemLocation);
common.AddFunction(ContentKeyFunctions.Point);
- common.AddFunction(ContentKeyFunctions.ToggleStanding); // WD EDIT
common.AddFunction(ContentKeyFunctions.ZoomOut);
common.AddFunction(ContentKeyFunctions.ZoomIn);
common.AddFunction(ContentKeyFunctions.ResetZoom);
@@ -63,6 +62,7 @@ namespace Content.Client.Input
human.AddFunction(EngineKeyFunctions.MoveLeft);
human.AddFunction(EngineKeyFunctions.MoveRight);
human.AddFunction(EngineKeyFunctions.Walk);
+ human.AddFunction(ContentKeyFunctions.ToggleKnockdown);
human.AddFunction(ContentKeyFunctions.SwapHands);
human.AddFunction(ContentKeyFunctions.SwapHandsReverse);
human.AddFunction(ContentKeyFunctions.Drop);
diff --git a/Content.Client/Instruments/InstrumentSystem.cs b/Content.Client/Instruments/InstrumentSystem.cs
index d861f4163b0..c01845bb133 100644
--- a/Content.Client/Instruments/InstrumentSystem.cs
+++ b/Content.Client/Instruments/InstrumentSystem.cs
@@ -294,7 +294,6 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
SetMaster(uid, null);
TrySetChannels(uid, data);
- instrument.MidiEventBuffer.Clear();
instrument.Renderer.OnMidiEvent += instrument.MidiEventBuffer.Add;
return true;
}
diff --git a/Content.Client/Inventory/ClientInventorySystem.cs b/Content.Client/Inventory/ClientInventorySystem.cs
index 799c29232cf..73f2e8da702 100644
--- a/Content.Client/Inventory/ClientInventorySystem.cs
+++ b/Content.Client/Inventory/ClientInventorySystem.cs
@@ -22,7 +22,6 @@ namespace Content.Client.Inventory
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ClientClothingSystem _clothingVisualsSystem = default!;
[Dependency] private readonly ExamineSystem _examine = default!;
@@ -116,6 +115,13 @@ namespace Content.Client.Inventory
OnLinkInventorySlots?.Invoke(uid, component);
}
+ protected override void OnInit(Entity ent, ref ComponentInit args)
+ {
+ base.OnInit(ent, ref args);
+
+ _clothingVisualsSystem.InitClothing(ent.Owner, ent.Comp);
+ }
+
public override void Shutdown()
{
CommandBinds.Unregister();
@@ -262,7 +268,6 @@ namespace Content.Client.Inventory
TryAddSlotData((ent.Owner, inventorySlots), (SlotData)slot);
}
- _clothingVisualsSystem.InitClothing(ent, ent.Comp);
if (ent.Owner == _playerManager.LocalEntity)
ReloadInventory(inventorySlots);
}
diff --git a/Content.Client/Kitchen/KitchenSpikeSystem.cs b/Content.Client/Kitchen/KitchenSpikeSystem.cs
deleted file mode 100644
index 3627a29fa9f..00000000000
--- a/Content.Client/Kitchen/KitchenSpikeSystem.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using Content.Shared.Kitchen;
-
-namespace Content.Client.Kitchen;
-
-public sealed class KitchenSpikeSystem : SharedKitchenSpikeSystem
-{
-
-}
diff --git a/Content.Client/Lathe/UI/LatheBoundUserInterface.cs b/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
index 0ab738f694c..248da38e588 100644
--- a/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
+++ b/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
@@ -33,6 +33,10 @@ namespace Content.Client.Lathe.UI
};
_menu.OnClaimMiningPoints += () => SendMessage(new LatheClaimMiningPointsMessage()); // DeltaV
+ _menu.QueueDeleteAction += index => SendMessage(new LatheDeleteRequestMessage(index));
+ _menu.QueueMoveUpAction += index => SendMessage(new LatheMoveRequestMessage(index, -1));
+ _menu.QueueMoveDownAction += index => SendMessage(new LatheMoveRequestMessage(index, 1));
+ _menu.DeleteFabricatingAction += () => SendMessage(new LatheAbortFabricationMessage());
}
protected override void UpdateState(BoundUserInterfaceState state)
diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml b/Content.Client/Lathe/UI/LatheMenu.xaml
index 0d51c2b9ece..2193fccc842 100644
--- a/Content.Client/Lathe/UI/LatheMenu.xaml
+++ b/Content.Client/Lathe/UI/LatheMenu.xaml
@@ -1,6 +1,7 @@
+
diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml.cs b/Content.Client/Lathe/UI/LatheMenu.xaml.cs
index 0475962321a..3b6a52c9499 100644
--- a/Content.Client/Lathe/UI/LatheMenu.xaml.cs
+++ b/Content.Client/Lathe/UI/LatheMenu.xaml.cs
@@ -33,6 +33,10 @@ public sealed partial class LatheMenu : DefaultWindow
public event Action? OnServerListButtonPressed;
public event Action? RecipeQueueAction;
public event Action? OnClaimMiningPoints; // DeltaV
+ public event Action? QueueDeleteAction;
+ public event Action? QueueMoveUpAction;
+ public event Action? QueueMoveDownAction;
+ public event Action? DeleteFabricatingAction;
public List> Recipes = new();
@@ -60,12 +64,21 @@ public sealed partial class LatheMenu : DefaultWindow
};
AmountLineEdit.OnTextChanged += _ =>
{
+ if (int.TryParse(AmountLineEdit.Text, out var amount))
+ {
+ if (amount > LatheSystem.MaxItemsPerRequest)
+ AmountLineEdit.Text = LatheSystem.MaxItemsPerRequest.ToString();
+ else if (amount < 0)
+ AmountLineEdit.Text = "0";
+ }
+
PopulateRecipes();
};
FilterOption.OnItemSelected += OnItemSelected;
ServerListButton.OnPressed += a => OnServerListButtonPressed?.Invoke(a);
+ DeleteFabricating.OnPressed += _ => DeleteFabricatingAction?.Invoke();
}
public void SetEntity(EntityUid uid)
@@ -266,22 +279,27 @@ public sealed partial class LatheMenu : DefaultWindow
/// Populates the build queue list with all queued items
///
///
- public void PopulateQueueList(IReadOnlyCollection> queue)
+ public void PopulateQueueList(IReadOnlyCollection queue)
{
QueueList.DisposeAllChildren();
var idx = 1;
- foreach (var recipeProto in queue)
+ foreach (var batch in queue)
{
- var recipe = _prototypeManager.Index(recipeProto);
- var queuedRecipeBox = new BoxContainer();
- queuedRecipeBox.Orientation = BoxContainer.LayoutOrientation.Horizontal;
+ var recipe = _prototypeManager.Index(batch.Recipe);
- queuedRecipeBox.AddChild(GetRecipeDisplayControl(recipe));
+ var itemName = _lathe.GetRecipeName(batch.Recipe);
+ string displayText;
+ if (batch.ItemsRequested > 1)
+ displayText = Loc.GetString("lathe-menu-item-batch", ("index", idx), ("name", itemName), ("printed", batch.ItemsPrinted), ("total", batch.ItemsRequested));
+ else
+ displayText = Loc.GetString("lathe-menu-item-single", ("index", idx), ("name", itemName));
+
+ var queuedRecipeBox = new QueuedRecipeControl(displayText, idx - 1, GetRecipeDisplayControl(recipe));
+ queuedRecipeBox.OnDeletePressed += s => QueueDeleteAction?.Invoke(s);
+ queuedRecipeBox.OnMoveUpPressed += s => QueueMoveUpAction?.Invoke(s);
+ queuedRecipeBox.OnMoveDownPressed += s => QueueMoveDownAction?.Invoke(s);
- var queuedRecipeLabel = new Label();
- queuedRecipeLabel.Text = $"{idx}. {_lathe.GetRecipeName(recipe)}";
- queuedRecipeBox.AddChild(queuedRecipeLabel);
QueueList.AddChild(queuedRecipeBox);
idx++;
}
diff --git a/Content.Client/Lathe/UI/QueuedRecipeControl.xaml b/Content.Client/Lathe/UI/QueuedRecipeControl.xaml
new file mode 100644
index 00000000000..b1d4b496a14
--- /dev/null
+++ b/Content.Client/Lathe/UI/QueuedRecipeControl.xaml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Lathe/UI/QueuedRecipeControl.xaml.cs b/Content.Client/Lathe/UI/QueuedRecipeControl.xaml.cs
new file mode 100644
index 00000000000..c4ba9803b0d
--- /dev/null
+++ b/Content.Client/Lathe/UI/QueuedRecipeControl.xaml.cs
@@ -0,0 +1,36 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Lathe.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class QueuedRecipeControl : Control
+{
+ public Action? OnDeletePressed;
+ public Action? OnMoveUpPressed;
+ public Action? OnMoveDownPressed;
+
+ public QueuedRecipeControl(string displayText, int index, Control displayControl)
+ {
+ RobustXamlLoader.Load(this);
+
+ RecipeName.Text = displayText;
+ RecipeDisplayContainer.AddChild(displayControl);
+
+ MoveUp.OnPressed += (_) =>
+ {
+ OnMoveUpPressed?.Invoke(index);
+ };
+
+ MoveDown.OnPressed += (_) =>
+ {
+ OnMoveDownPressed?.Invoke(index);
+ };
+
+ Delete.OnPressed += (_) =>
+ {
+ OnDeletePressed?.Invoke(index);
+ };
+ }
+}
diff --git a/Content.Client/Light/EntitySystems/LightBulbSystem.cs b/Content.Client/Light/EntitySystems/LightBulbSystem.cs
index c028cc64c6f..a3698fc199d 100644
--- a/Content.Client/Light/EntitySystems/LightBulbSystem.cs
+++ b/Content.Client/Light/EntitySystems/LightBulbSystem.cs
@@ -1,36 +1,46 @@
using Content.Shared.Light.Components;
+using Content.Shared.Light.EntitySystems;
using Robust.Client.GameObjects;
-namespace Content.Client.Light.Visualizers;
+namespace Content.Client.Light.EntitySystems;
-public sealed class LightBulbSystem : VisualizerSystem
+public sealed class LightBulbSystem : SharedLightBulbSystem
{
- protected override void OnAppearanceChange(EntityUid uid, LightBulbComponent comp, ref AppearanceChangeEvent args)
+ [Dependency] private readonly AppearanceSystem _appearance = default!;
+ [Dependency] private readonly SpriteSystem _sprite = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnAppearanceChange);
+ }
+
+ private void OnAppearanceChange(EntityUid uid, LightBulbComponent comp, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
// update sprite state
- if (AppearanceSystem.TryGetData(uid, LightBulbVisuals.State, out var state, args.Component))
+ if (_appearance.TryGetData(uid, LightBulbVisuals.State, out var state, args.Component))
{
switch (state)
{
case LightBulbState.Normal:
- SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.NormalSpriteState);
+ _sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.NormalSpriteState);
break;
case LightBulbState.Broken:
- SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BrokenSpriteState);
+ _sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BrokenSpriteState);
break;
case LightBulbState.Burned:
- SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BurnedSpriteState);
+ _sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BurnedSpriteState);
break;
}
}
// also update sprites color
- if (AppearanceSystem.TryGetData(uid, LightBulbVisuals.Color, out var color, args.Component))
+ if (_appearance.TryGetData(uid, LightBulbVisuals.Color, out var color, args.Component))
{
- SpriteSystem.SetColor((uid, args.Sprite), color);
+ _sprite.SetColor((uid, args.Sprite), color);
}
}
}
diff --git a/Content.Client/Light/EntitySystems/PoweredLightSystem.cs b/Content.Client/Light/EntitySystems/PoweredLightSystem.cs
new file mode 100644
index 00000000000..b8a6b16da4e
--- /dev/null
+++ b/Content.Client/Light/EntitySystems/PoweredLightSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Light.EntitySystems;
+
+namespace Content.Client.Light.EntitySystems;
+
+public sealed class PoweredLightSystem : SharedPoweredLightSystem;
diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs
index 121e8dbe71f..ec052adea55 100644
--- a/Content.Client/Lobby/LobbyUIController.cs
+++ b/Content.Client/Lobby/LobbyUIController.cs
@@ -72,6 +72,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered RefreshProfileEditor());
+ _configurationManager.OnValueChanged(CCVars.GameRoleLoadoutTimers, _ => RefreshProfileEditor());
_configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor());
}
diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
index 590f9ceae87..dada2ab5ca9 100644
--- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
+++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
@@ -1093,7 +1093,7 @@ namespace Content.Client.Lobby.UI
_loadoutWindow = new LoadoutWindow(Profile, roleLoadout, roleLoadoutProto, _playerManager.LocalSession, collection)
{
- Title = jobProto?.ID + "-loadout",
+ Title = Loc.GetString("loadout-window-title-loadout", ("job", $"{jobProto?.LocalizedName}")),
};
// Refresh the buttons etc.
diff --git a/Content.Client/Medical/Cryogenics/CryoPodSystem.cs b/Content.Client/Medical/Cryogenics/CryoPodSystem.cs
index 13db5a84215..c1cbfc573ee 100644
--- a/Content.Client/Medical/Cryogenics/CryoPodSystem.cs
+++ b/Content.Client/Medical/Cryogenics/CryoPodSystem.cs
@@ -1,7 +1,5 @@
using System.Numerics;
-using Content.Shared.Emag.Systems;
using Content.Shared.Medical.Cryogenics;
-using Content.Shared.Verbs;
using Robust.Client.GameObjects;
namespace Content.Client.Medical.Cryogenics;
@@ -15,11 +13,6 @@ public sealed class CryoPodSystem : SharedCryoPodSystem
{
base.Initialize();
- SubscribeLocalEvent(OnComponentInit);
- SubscribeLocalEvent>(AddAlternativeVerbs);
- SubscribeLocalEvent(OnEmagged);
- SubscribeLocalEvent(OnCryoPodPryFinished);
-
SubscribeLocalEvent(OnAppearanceChange);
SubscribeLocalEvent(OnCryoPodInsertion);
SubscribeLocalEvent(OnCryoPodRemoval);
@@ -53,8 +46,8 @@ public sealed class CryoPodSystem : SharedCryoPodSystem
return;
}
- if (!_appearance.TryGetData(uid, CryoPodComponent.CryoPodVisuals.ContainsEntity, out var isOpen, args.Component)
- || !_appearance.TryGetData(uid, CryoPodComponent.CryoPodVisuals.IsOn, out var isOn, args.Component))
+ if (!_appearance.TryGetData(uid, CryoPodVisuals.ContainsEntity, out var isOpen, args.Component)
+ || !_appearance.TryGetData(uid, CryoPodVisuals.IsOn, out var isOn, args.Component))
{
return;
}
diff --git a/Content.Client/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Client/Medical/SuitSensors/SuitSensorSystem.cs
new file mode 100644
index 00000000000..75868e08d9b
--- /dev/null
+++ b/Content.Client/Medical/SuitSensors/SuitSensorSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Medical.SuitSensors;
+
+namespace Content.Client.Medical.SuitSensors;
+
+public sealed class SuitSensorSystem : SharedSuitSensorSystem;
diff --git a/Content.Client/Morgue/CrematoriumSystem.cs b/Content.Client/Morgue/CrematoriumSystem.cs
new file mode 100644
index 00000000000..66eac263c25
--- /dev/null
+++ b/Content.Client/Morgue/CrematoriumSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Morgue;
+
+namespace Content.Client.Morgue;
+
+public sealed class CrematoriumSystem : SharedCrematoriumSystem;
diff --git a/Content.Client/Morgue/MorgueSystem.cs b/Content.Client/Morgue/MorgueSystem.cs
new file mode 100644
index 00000000000..b8d2f109fbe
--- /dev/null
+++ b/Content.Client/Morgue/MorgueSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Morgue;
+
+namespace Content.Client.Morgue;
+
+public sealed class MorgueSystem : SharedMorgueSystem;
diff --git a/Content.Client/Movement/Systems/ContentEyeSystem.cs b/Content.Client/Movement/Systems/ContentEyeSystem.cs
index 518a4a1bd40..a332d25f9a1 100644
--- a/Content.Client/Movement/Systems/ContentEyeSystem.cs
+++ b/Content.Client/Movement/Systems/ContentEyeSystem.cs
@@ -1,7 +1,6 @@
using System.Numerics;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
-using Robust.Client.GameObjects;
using Robust.Client.Player;
namespace Content.Client.Movement.Systems;
@@ -63,4 +62,15 @@ public sealed class ContentEyeSystem : SharedContentEyeSystem
UpdateEyeOffset((entity, eyeComponent));
}
}
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+ // TODO: Ideally we wouldn't want this to run in both FrameUpdate and Update, but we kind of have to since the visual update happens in FrameUpdate, but interaction update happens in Update. It's a workaround and a better solution should be found.
+ var eyeEntities = AllEntityQuery();
+ while (eyeEntities.MoveNext(out var entity, out ContentEyeComponent? contentComponent, out EyeComponent? eyeComponent))
+ {
+ UpdateEyeOffset((entity, eyeComponent));
+ }
+ }
}
diff --git a/Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs b/Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs
index 96a04710974..5227e892545 100644
--- a/Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs
+++ b/Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs
@@ -1,10 +1,10 @@
using System.Numerics;
using Content.Shared.Movement.Components; // DeltaV - make EyeCursorOffsetComponent entirely Shared
+using Content.Client.Viewport;
using Content.Shared.Camera;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Shared.Map;
-using Robust.Client.Player;
namespace Content.Client.Movement.Systems;
@@ -12,13 +12,10 @@ public sealed partial class EyeCursorOffsetSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
- [Dependency] private readonly IPlayerManager _player = default!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly IClyde _clyde = default!;
// This value is here to make sure the user doesn't have to move their mouse
// all the way out to the edge of the screen to get the full offset.
- static private float _edgeOffset = 0.9f;
+ private static float _edgeOffset = 0.8f;
public override void Initialize()
{
@@ -46,25 +43,29 @@ public sealed partial class EyeCursorOffsetSystem : EntitySystem
public Vector2? OffsetAfterMouse(EntityUid uid, EyeCursorOffsetComponent? component)
{
- var localPlayer = _player.LocalEntity;
- var mousePos = _inputManager.MouseScreenPosition;
- var screenSize = _clyde.MainWindow.Size;
- var minValue = MathF.Min(screenSize.X / 2, screenSize.Y / 2) * _edgeOffset;
-
- var mouseNormalizedPos = new Vector2(-(mousePos.X - screenSize.X / 2) / minValue, (mousePos.Y - screenSize.Y / 2) / minValue); // X needs to be inverted here for some reason, otherwise it ends up flipped.
-
- if (localPlayer == null)
+ // We need the main viewport where the game content is displayed, as certain UI layouts (e.g. Separated HUD) can make it a different size to the game window.
+ if (_eyeManager.MainViewport is not ScalingViewport vp)
return null;
- var playerPos = _transform.GetWorldPosition(localPlayer.Value);
+ var mousePos = _inputManager.MouseScreenPosition.Position; // TODO: If we ever get a right-aligned Separated HUD setting, this might need to be adjusted for that.
+
+ var viewportSize = vp.PixelSize; // The size of the game viewport, including black bars - does not include the chatbox in Separated HUD view.
+ var scalingViewportSize = vp.ViewportSize * vp.CurrentRenderScale; // The size of the viewport in which the game is rendered (i.e. not including black bars). Note! Can extend outside the game window with certain zoom settings!
+ var visibleViewportSize = Vector2.Min(viewportSize, scalingViewportSize); // The size of the game viewport that is "actually visible" to the player, cutting off over-extensions and not counting black bar padding.
+
+ Matrix3x2.Invert(_eyeManager.MainViewport.GetLocalToScreenMatrix(), out var matrix);
+ var mouseCoords = Vector2.Transform(mousePos, matrix); // Gives the mouse position inside of the *scaling viewport*, i.e. 0,0 is inside the black bars. Note! 0,0 can be outside the game window with certain zoom settings!
+
+ var boundedMousePos = Vector2.Clamp(Vector2.Min(mouseCoords, mousePos), Vector2.Zero, visibleViewportSize); // Mouse position inside the visible game viewport's bounds.
+
+ var offsetRadius = MathF.Min(visibleViewportSize.X / 2f, visibleViewportSize.Y / 2f) * _edgeOffset;
+ var mouseNormalizedPos = new Vector2(-(boundedMousePos.X - visibleViewportSize.X / 2f) / offsetRadius, (boundedMousePos.Y - visibleViewportSize.Y / 2f) / offsetRadius);
if (component == null)
- {
component = EnsureComp(uid);
- }
// Doesn't move the offset if the mouse has left the game window!
- if (mousePos.Window != WindowId.Invalid)
+ if (_inputManager.MouseScreenPosition.Window != WindowId.Invalid)
{
// The offset must account for the in-world rotation.
var eyeRotation = _eyeManager.CurrentEye.Rotation;
@@ -85,7 +86,7 @@ public sealed partial class EyeCursorOffsetSystem : EntitySystem
Vector2 vectorOffset = component.TargetPosition - component.CurrentPosition;
if (vectorOffset.Length() > component.OffsetSpeed)
{
- vectorOffset = vectorOffset.Normalized() * component.OffsetSpeed;
+ vectorOffset = vectorOffset.Normalized() * component.OffsetSpeed; // TODO: Probably needs to properly account for time delta or something.
}
component.CurrentPosition += vectorOffset;
}
diff --git a/Content.Client/NPC/PathfindingSystem.cs b/Content.Client/NPC/PathfindingSystem.cs
index 0c72a8f99ff..dc8fd984331 100644
--- a/Content.Client/NPC/PathfindingSystem.cs
+++ b/Content.Client/NPC/PathfindingSystem.cs
@@ -20,6 +20,7 @@ namespace Content.Client.NPC
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
+ [Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IResourceCache _cache = default!;
[Dependency] private readonly NPCSteeringSystem _steering = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
@@ -30,17 +31,15 @@ namespace Content.Client.NPC
get => _modes;
set
{
- var overlayManager = IoCManager.Resolve();
-
if (value == PathfindingDebugMode.None)
{
Breadcrumbs.Clear();
Polys.Clear();
- overlayManager.RemoveOverlay();
+ _overlayManager.RemoveOverlay();
}
- else if (!overlayManager.HasOverlay())
+ else if (!_overlayManager.HasOverlay())
{
- overlayManager.AddOverlay(new PathfindingOverlay(EntityManager, _eyeManager, _inputManager, _mapManager, _cache, this, _mapSystem, _transformSystem));
+ _overlayManager.AddOverlay(new PathfindingOverlay(EntityManager, _eyeManager, _inputManager, _mapManager, _cache, this, _mapSystem, _transformSystem));
}
if ((value & PathfindingDebugMode.Steering) != 0x0)
diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml b/Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml
index 469faf209d3..da0b9f60d8b 100644
--- a/Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml
+++ b/Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml
@@ -5,15 +5,15 @@
-
-
+
+
-
+
-
-
+
+
diff --git a/Content.Client/_DV/Options/UI/OptionColorSlider.xaml b/Content.Client/Options/UI/OptionColorSlider.xaml
similarity index 100%
rename from Content.Client/_DV/Options/UI/OptionColorSlider.xaml
rename to Content.Client/Options/UI/OptionColorSlider.xaml
diff --git a/Content.Client/_DV/Options/UI/OptionColorSlider.xaml.cs b/Content.Client/Options/UI/OptionColorSlider.xaml.cs
similarity index 91%
rename from Content.Client/_DV/Options/UI/OptionColorSlider.xaml.cs
rename to Content.Client/Options/UI/OptionColorSlider.xaml.cs
index ec30ada8181..6f8f46a3b44 100644
--- a/Content.Client/_DV/Options/UI/OptionColorSlider.xaml.cs
+++ b/Content.Client/Options/UI/OptionColorSlider.xaml.cs
@@ -1,7 +1,8 @@
+using Content.Client.Options.UI;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
-namespace Content.Client._DV.Options.UI;
+namespace Content.Client.Options.UI;
///
/// Standard UI control used for color sliders in the options menu. Intended for use with .
diff --git a/Content.Client/Options/UI/OptionsTabControlRow.xaml.cs b/Content.Client/Options/UI/OptionsTabControlRow.xaml.cs
index 31dd9897f4e..ad262f94a26 100644
--- a/Content.Client/Options/UI/OptionsTabControlRow.xaml.cs
+++ b/Content.Client/Options/UI/OptionsTabControlRow.xaml.cs
@@ -121,6 +121,19 @@ public sealed partial class OptionsTabControlRow : Control
return AddOption(new OptionSliderFloatCVar(this, _cfg, cVar, slider, min, max, scale, FormatPercent));
}
+ ///
+ /// Add a color slider option, backed by a simple string CVar.
+ ///
+ /// The CVar represented by the slider.
+ /// The UI control for the option.
+ /// The option instance backing the added option.
+ public OptionColorSliderCVar AddOptionColorSlider(
+ CVarDef cVar,
+ OptionColorSlider slider)
+ {
+ return AddOption(new OptionColorSliderCVar(this, _cfg, cVar, slider));
+ }
+
///
/// Add a slider option, backed by a simple integer CVar.
///
@@ -518,6 +531,58 @@ public sealed class OptionSliderFloatCVar : BaseOptionCVar
}
}
+///
+/// Implementation of a CVar option that simply corresponds with a string .
+///
+///
+public sealed class OptionColorSliderCVar : BaseOptionCVar
+{
+ private readonly OptionColorSlider _slider;
+
+ protected override string Value
+ {
+ get => _slider.Slider.Color.ToHex();
+ set
+ {
+ _slider.Slider.Color = Color.FromHex(value);
+ UpdateLabelColor();
+ }
+ }
+
+ ///
+ /// Creates a new instance of this type.
+ ///
+ ///
+ ///
+ /// It is generally more convenient to call overloads on
+ /// such as instead of instantiating this type directly.
+ ///
+ ///
+ /// The control row that owns this option.
+ /// The configuration manager to get and set values from.
+ /// The CVar that is being controlled by this option.
+ /// The UI control for the option.
+ public OptionColorSliderCVar(
+ OptionsTabControlRow controller,
+ IConfigurationManager cfg,
+ CVarDef cVar,
+ OptionColorSlider slider) : base(controller, cfg, cVar)
+ {
+ _slider = slider;
+
+ slider.Slider.OnColorChanged += _ =>
+ {
+ ValueChanged();
+ UpdateLabelColor();
+ };
+ }
+
+ private void UpdateLabelColor()
+ {
+ _slider.ExampleLabel.FontColorOverride = Color.FromHex(Value);
+ }
+}
+
///
/// Implementation of a CVar option that simply corresponds with an integer .
///
diff --git a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
index 5041b498a0b..41fac83c598 100644
--- a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
+++ b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
@@ -14,6 +14,10 @@
+
+
diff --git a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs
index f87cda746c1..0c88eae50d3 100644
--- a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs
@@ -20,6 +20,8 @@ public sealed partial class AccessibilityTab : Control
Control.AddOptionPercentSlider(CCVars.SpeechBubbleTextOpacity, SpeechBubbleTextOpacitySlider);
Control.AddOptionPercentSlider(CCVars.SpeechBubbleSpeakerOpacity, SpeechBubbleSpeakerOpacitySlider);
Control.AddOptionPercentSlider(CCVars.SpeechBubbleBackgroundOpacity, SpeechBubbleBackgroundOpacitySlider);
+ Control.AddOptionCheckBox(CCVars.ChatAutoFillHighlights, AutoFillHighlightsCheckBox);
+ Control.AddOptionColorSlider(CCVars.ChatHighlightsColor, HighlightsColorSlider);
Control.AddOptionCheckBox(CCVars.AccessibilityClientCensorNudity, CensorNudityCheckBox);
diff --git a/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml b/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml
index ca22d7206b6..0fa5ee1754e 100644
--- a/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml
+++ b/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml
@@ -9,6 +9,9 @@
+
+
diff --git a/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml.cs b/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml.cs
index a25d83d337d..99eb2ec6a52 100644
--- a/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml.cs
@@ -51,6 +51,8 @@ public sealed partial class AdminOptionsTab : Control
playerTabSymbolSettings.Add(new OptionDropDownCVar.ValueOption(setting.ToString()!, Loc.GetString($"ui-options-admin-player-tab-symbol-setting-{setting.ToString()!.ToLower()}")));
}
+ Control.AddOptionColorSlider(CCVars.AdminLogsHighlightColor, ColorSliderLogsHighlight);
+
Control.AddOptionDropDown(CCVars.AdminPlayerTabSymbolSetting, DropDownPlayerTabSymbolSetting, playerTabSymbolSettings);
Control.AddOptionDropDown(CCVars.AdminPlayerTabRoleSetting, DropDownPlayerTabRoleSetting, playerTabRoleSettings);
Control.AddOptionDropDown(CCVars.AdminPlayerTabColorSetting, DropDownPlayerTabColorSetting, playerTabColorSettings);
diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
index cb8e4f6f34e..c4972e6f87f 100644
--- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
@@ -104,12 +104,6 @@ namespace Content.Client.Options.UI.Tabs
_cfg.SaveToFile();
}
- private void HandleToggleAutoGetUp(BaseButton.ButtonToggledEventArgs args) // WD EDIT
- {
- _cfg.SetCVar(GoobCVars.AutoGetUp, args.Pressed);
- _cfg.SaveToFile();
- }
-
public KeyRebindTab()
{
IoCManager.InjectDependencies(this);
@@ -168,9 +162,8 @@ namespace Content.Client.Options.UI.Tabs
AddButton(EngineKeyFunctions.MoveRight);
AddButton(EngineKeyFunctions.Walk);
AddCheckBox("ui-options-hotkey-toggle-walk", _cfg.GetCVar(CCVars.ToggleWalk), HandleToggleWalk);
- AddButton(ContentKeyFunctions.ToggleStanding);
- AddCheckBox("ui-options-function-auto-get-up", _cfg.GetCVar(GoobCVars.AutoGetUp), HandleToggleAutoGetUp); // WD EDIT
InitToggleWalk();
+ AddButton(ContentKeyFunctions.ToggleKnockdown);
AddHeader("ui-options-header-camera");
AddButton(EngineKeyFunctions.CameraRotateLeft);
diff --git a/Content.Client/Playtime/ClientsidePlaytimeTrackingManager.cs b/Content.Client/Playtime/ClientsidePlaytimeTrackingManager.cs
index 330ff9122ff..9a4f75d5965 100644
--- a/Content.Client/Playtime/ClientsidePlaytimeTrackingManager.cs
+++ b/Content.Client/Playtime/ClientsidePlaytimeTrackingManager.cs
@@ -94,7 +94,7 @@ public sealed class ClientsidePlaytimeTrackingManager
return;
}
- // At less than 1 minute of time diff, there's not much point, and saving regardless will brick tests
+ // At less than 1 minute of time diff, there's not much point
// The reason this isn't checking for 0 is because TotalMinutes is fractional, rather than solely whole minutes
if (timeDiffMinutes < 1)
return;
diff --git a/Content.Client/Power/Components/ApcPowerReceiverComponent.cs b/Content.Client/Power/Components/ApcPowerReceiverComponent.cs
index fbebcb7cf83..ead686189ed 100644
--- a/Content.Client/Power/Components/ApcPowerReceiverComponent.cs
+++ b/Content.Client/Power/Components/ApcPowerReceiverComponent.cs
@@ -5,4 +5,5 @@ namespace Content.Client.Power.Components;
[RegisterComponent]
public sealed partial class ApcPowerReceiverComponent : SharedApcPowerReceiverComponent
{
+ public override float Load { get; set; }
}
diff --git a/Content.Client/Radiation/Overlays/RadiationDebugOverlay.cs b/Content.Client/Radiation/Overlays/RadiationDebugOverlay.cs
index 784c39a6ced..1f060e532df 100644
--- a/Content.Client/Radiation/Overlays/RadiationDebugOverlay.cs
+++ b/Content.Client/Radiation/Overlays/RadiationDebugOverlay.cs
@@ -11,6 +11,8 @@ namespace Content.Client.Radiation.Overlays;
public sealed class RadiationDebugOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IResourceCache _cache = default!;
+
private readonly SharedMapSystem _mapSystem;
private readonly RadiationSystem _radiation;
@@ -24,8 +26,7 @@ public sealed class RadiationDebugOverlay : Overlay
_radiation = _entityManager.System();
_mapSystem = _entityManager.System();
- var cache = IoCManager.Resolve();
- _font = new VectorFont(cache.GetResource("/Fonts/NotoSans/NotoSans-Regular.ttf"), 8);
+ _font = new VectorFont(_cache.GetResource("/Fonts/NotoSans/NotoSans-Regular.ttf"), 8);
}
protected override void Draw(in OverlayDrawArgs args)
diff --git a/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs b/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs
index 06e5674d9cb..24583d2776b 100644
--- a/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs
+++ b/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs
@@ -28,6 +28,8 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
public EntityUid Entity;
+ private bool _allowBorgControl = true;
+
public RoboticsConsoleWindow()
{
RobustXamlLoader.Load(this);
@@ -72,6 +74,7 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
public void UpdateState(RoboticsConsoleState state)
{
_cyborgs = state.Cyborgs;
+ _allowBorgControl = state.AllowBorgControl;
// clear invalid selection
if (_selected is {} selected && !_cyborgs.ContainsKey(selected))
@@ -85,8 +88,8 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
PopulateData();
var locked = _lock.IsLocked(Entity);
- DangerZone.Visible = !locked;
- LockedMessage.Visible = locked;
+ DangerZone.Visible = !locked && _allowBorgControl;
+ LockedMessage.Visible = locked && _allowBorgControl; // Only show if locked AND control is allowed
}
private void PopulateCyborgs()
@@ -120,11 +123,19 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
BorgSprite.Texture = _sprite.Frame0(data.ChassisSprite!);
var batteryColor = data.Charge switch {
- < 0.2f => "red",
- < 0.4f => "orange",
- < 0.6f => "yellow",
- < 0.8f => "green",
- _ => "blue"
+ < 0.2f => "#FF6C7F", // red
+ < 0.4f => "#EF973C", // orange
+ < 0.6f => "#E8CB2D", // yellow
+ < 0.8f => "#30CC19", // green
+ _ => "#00D3B8" // cyan
+ };
+
+ var hpPercentColor = data.HpPercent switch {
+ < 0.2f => "#FF6C7F", // red
+ < 0.4f => "#EF973C", // orange
+ < 0.6f => "#E8CB2D", // yellow
+ < 0.8f => "#30CC19", // green
+ _ => "#00D3B8" // cyan
};
var text = new FormattedMessage();
@@ -132,12 +143,14 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
text.AddMarkupOrThrow(Loc.GetString("robotics-console-designation"));
text.AddText($" {data.Name}\n"); // prevent players trolling by naming borg [color=red]satan[/color]
text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-battery", ("charge", (int)(data.Charge * 100f)), ("color", batteryColor))}\n");
+ text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-hp", ("hp", (int)(data.HpPercent * 100f)), ("color", hpPercentColor))}\n");
text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-brain", ("brain", data.HasBrain))}\n");
text.AddMarkupOrThrow(Loc.GetString("robotics-console-modules", ("count", data.ModuleCount)));
BorgInfo.SetMessage(text);
// how the turntables
- DisableButton.Disabled = !(data.HasBrain && data.CanDisable);
+ DisableButton.Disabled = !_allowBorgControl || !(data.HasBrain && data.CanDisable);
+ DestroyButton.Disabled = !_allowBorgControl;
}
protected override void FrameUpdate(FrameEventArgs args)
diff --git a/Content.Client/Rotation/RotationVisualizerSystem.cs b/Content.Client/Rotation/RotationVisualizerSystem.cs
index 8dbcf973201..b5be4d0b179 100644
--- a/Content.Client/Rotation/RotationVisualizerSystem.cs
+++ b/Content.Client/Rotation/RotationVisualizerSystem.cs
@@ -24,7 +24,7 @@ public sealed class RotationVisualizerSystem : SharedRotationVisualsSystem
return;
if (!_appearance.TryGetData(uid, RotationVisuals.RotationState, out var state, args.Component))
- return;
+ state = RotationState.Vertical;
switch (state)
{
diff --git a/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs b/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs
index 56b54c176a8..f6e6594af52 100644
--- a/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs
+++ b/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs
@@ -16,20 +16,20 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
get => _enableShuttlePosition;
set
{
- if (_enableShuttlePosition == value) return;
+ if (_enableShuttlePosition == value)
+ return;
_enableShuttlePosition = value;
- var overlayManager = IoCManager.Resolve();
if (_enableShuttlePosition)
{
_overlay = new EmergencyShuttleOverlay(EntityManager.TransformQuery, XformSystem);
- overlayManager.AddOverlay(_overlay);
+ _overlays.AddOverlay(_overlay);
RaiseNetworkEvent(new EmergencyShuttleRequestPositionMessage());
}
else
{
- overlayManager.RemoveOverlay(_overlay!);
+ _overlays.RemoveOverlay(_overlay!);
_overlay = null;
}
}
diff --git a/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs
index 2b575b48059..449323c7467 100644
--- a/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs
+++ b/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs
@@ -40,6 +40,8 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
private readonly HashSet _drawnDocks = new();
private readonly Dictionary _dockButtons = new();
+ private readonly Color _fallbackHighlightedColor = Color.Magenta;
+
///
/// Store buttons for every other dock
///
@@ -213,11 +215,11 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
if (HighlightedDock == dock.Entity)
{
- otherDockColor = Color.ToSrgb(Color.Magenta);
+ otherDockColor = Color.ToSrgb(dock.HighlightedColor);
}
else
{
- otherDockColor = Color.ToSrgb(Color.Purple);
+ otherDockColor = Color.ToSrgb(dock.Color);
}
/*
@@ -311,7 +313,7 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
ScalePosition(Vector2.Transform(new Vector2(-0.5f, 0.5f), rotation)),
ScalePosition(Vector2.Transform(new Vector2(0.5f, -0.5f), rotation)));
- var dockColor = Color.Magenta;
+ var dockColor = _viewedState?.HighlightedColor ?? _fallbackHighlightedColor;
var connectionColor = Color.Pink;
handle.DrawRect(ourDockConnection, connectionColor.WithAlpha(0.2f));
diff --git a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs
index b0c8cb53319..540192bba73 100644
--- a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs
+++ b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs
@@ -310,7 +310,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
-dockRadius * UIScale,
(Size.X + dockRadius) * UIScale,
(Size.Y + dockRadius) * UIScale);
-
+
if (_docks.TryGetValue(nent, out var docks))
{
foreach (var state in docks)
@@ -323,7 +323,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
continue;
}
- var color = Color.ToSrgb(Color.Magenta);
+ var color = Color.ToSrgb(state.HighlightedColor);
var verts = new[]
{
diff --git a/Content.Client/Singularity/Systems/EmitterSystem.cs b/Content.Client/Singularity/Systems/EmitterSystem.cs
index e9e08463404..b35c3297a17 100644
--- a/Content.Client/Singularity/Systems/EmitterSystem.cs
+++ b/Content.Client/Singularity/Systems/EmitterSystem.cs
@@ -12,6 +12,8 @@ public sealed class EmitterSystem : SharedEmitterSystem
///
public override void Initialize()
{
+ base.Initialize();
+
SubscribeLocalEvent(OnAppearanceChange);
}
diff --git a/Content.Client/Stack/StackSystem.cs b/Content.Client/Stack/StackSystem.cs
index d12e9900a6e..182daa73a50 100644
--- a/Content.Client/Stack/StackSystem.cs
+++ b/Content.Client/Stack/StackSystem.cs
@@ -12,7 +12,6 @@ namespace Content.Client.Stack
{
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly ItemCounterSystem _counterSystem = default!;
- [Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
@@ -28,22 +27,8 @@ namespace Content.Client.Stack
base.SetCount(uid, amount, component);
- if (component.Lingering &&
- TryComp(uid, out var sprite))
- {
- // tint the stack gray and make it transparent if it's lingering.
- var color = component.Count == 0 && component.Lingering
- ? Color.DarkGray.WithAlpha(0.65f)
- : Color.White;
-
- for (var i = 0; i < sprite.AllLayers.Count(); i++)
- {
- _sprite.LayerSetColor((uid, sprite), i, color);
- }
- }
-
// TODO PREDICT ENTITY DELETION: This should really just be a normal entity deletion call.
- if (component.Count <= 0 && !component.Lingering)
+ if (component.Count <= 0)
{
Xform.DetachEntity(uid, Transform(uid));
return;
diff --git a/Content.Client/Station/StationSystem.cs b/Content.Client/Station/StationSystem.cs
index 5a4a1853d24..e1ccd14d7be 100644
--- a/Content.Client/Station/StationSystem.cs
+++ b/Content.Client/Station/StationSystem.cs
@@ -2,34 +2,5 @@
namespace Content.Client.Station;
-///
-/// This handles letting the client know stations are a thing. Only really used by an admin menu.
-///
-public sealed partial class StationSystem : SharedStationSystem
-{
- private readonly List<(string Name, NetEntity Entity)> _stations = new();
-
- ///
- /// All stations that currently exist.
- ///
- ///
- /// I'd have this just invoke an entity query, but we're on the client and the client barely knows about stations.
- ///
- // TODO: Stations have a global PVS override now, this can probably be changed into a query.
- public IReadOnlyList<(string Name, NetEntity Entity)> Stations => _stations;
-
- ///
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeNetworkEvent(StationsUpdated);
- }
-
- private void StationsUpdated(StationsUpdatedEvent ev)
- {
- _stations.Clear();
- // TODO this needs to be done in component states and with the Ensure() methods
- _stations.AddRange(ev.Stations);
- }
-}
+///
+public sealed partial class StationSystem : SharedStationSystem;
diff --git a/Content.Client/Storage/Components/EntityStorageComponent.cs b/Content.Client/Storage/Components/EntityStorageComponent.cs
deleted file mode 100644
index 514d5d65957..00000000000
--- a/Content.Client/Storage/Components/EntityStorageComponent.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Content.Shared.Storage.Components;
-using Robust.Shared.GameStates;
-
-namespace Content.Client.Storage.Components;
-
-[RegisterComponent]
-public sealed partial class EntityStorageComponent : SharedEntityStorageComponent
-{
-
-}
diff --git a/Content.Client/Storage/Systems/EntityStorageSystem.cs b/Content.Client/Storage/Systems/EntityStorageSystem.cs
index dd3f8d38605..ca2b986667e 100644
--- a/Content.Client/Storage/Systems/EntityStorageSystem.cs
+++ b/Content.Client/Storage/Systems/EntityStorageSystem.cs
@@ -31,7 +31,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
SubscribeLocalEvent(OnHandleState);
}
- public override bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref SharedEntityStorageComponent? component)
+ public override bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref EntityStorageComponent? component)
{
if (component != null)
return true;
diff --git a/Content.Client/Storage/Systems/StorageSystem.cs b/Content.Client/Storage/Systems/StorageSystem.cs
index bd6659de01c..1a21de4b999 100644
--- a/Content.Client/Storage/Systems/StorageSystem.cs
+++ b/Content.Client/Storage/Systems/StorageSystem.cs
@@ -40,6 +40,11 @@ public sealed class StorageSystem : SharedStorageSystem
component.MaxItemSize = state.MaxItemSize;
component.Whitelist = state.Whitelist;
component.Blacklist = state.Blacklist;
+ component.StorageInsertSound = state.StorageInsertSound;
+ component.StorageRemoveSound = state.StorageRemoveSound;
+ component.StorageOpenSound = state.StorageOpenSound;
+ component.StorageCloseSound = state.StorageCloseSound;
+ component.DefaultStorageOrientation = state.DefaultStorageOrientation;
_oldStoredItems.Clear();
diff --git a/Content.Client/Store/Ui/StoreMenu.xaml b/Content.Client/Store/Ui/StoreMenu.xaml
index 843c9dc0296..1ee86a5a68f 100644
--- a/Content.Client/Store/Ui/StoreMenu.xaml
+++ b/Content.Client/Store/Ui/StoreMenu.xaml
@@ -21,7 +21,7 @@
Name="RefundButton"
MinWidth="64"
HorizontalAlignment="Right"
- Text="Refund" />
+ Text="{Loc 'store-ui-refund-text'}" />
diff --git a/Content.Client/Stunnable/StunSystem.cs b/Content.Client/Stunnable/StunSystem.cs
index cb59eda4824..6947b80664b 100644
--- a/Content.Client/Stunnable/StunSystem.cs
+++ b/Content.Client/Stunnable/StunSystem.cs
@@ -1,17 +1,19 @@
-using System.Numerics;
-using Content.Shared.Mobs;
+using System.Numerics;
+using Content.Shared.CombatMode;
+using Content.Shared.Interaction;
using Content.Shared.Stunnable;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
+using Robust.Shared.Input;
+using Robust.Shared.Input.Binding;
using Robust.Shared.Random;
-using Robust.Shared.Timing;
-using Robust.Shared.Utility;
namespace Content.Client.Stunnable;
public sealed class StunSystem : SharedStunSystem
{
+ [Dependency] private readonly SharedCombatModeSystem _combat = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SpriteSystem _spriteSystem = default!;
@@ -23,6 +25,22 @@ public sealed class StunSystem : SharedStunSystem
SubscribeLocalEvent(OnComponentInit);
SubscribeLocalEvent(OnAppearanceChanged);
+
+ CommandBinds.Builder
+ .BindAfter(EngineKeyFunctions.UseSecondary, new PointerInputCmdHandler(OnUseSecondary, true, true), typeof(SharedInteractionSystem))
+ .Register();
+ }
+
+ private bool OnUseSecondary(in PointerInputCmdHandler.PointerInputCmdArgs args)
+ {
+ if (args.Session?.AttachedEntity is not {Valid: true} uid)
+ return false;
+
+ if (args.EntityUid != uid || !HasComp(uid) || !_combat.IsInCombatMode(uid))
+ return false;
+
+ RaisePredictiveEvent(new ForceStandUpEvent());
+ return true;
}
///
diff --git a/Content.Client/Trigger/TimerTriggerVisualizerComponent.cs b/Content.Client/Trigger/Components/TimerTriggerVisualizerComponent.cs
similarity index 84%
rename from Content.Client/Trigger/TimerTriggerVisualizerComponent.cs
rename to Content.Client/Trigger/Components/TimerTriggerVisualizerComponent.cs
index 0e5a74b83b1..0cb89edd895 100644
--- a/Content.Client/Trigger/TimerTriggerVisualizerComponent.cs
+++ b/Content.Client/Trigger/Components/TimerTriggerVisualizerComponent.cs
@@ -1,7 +1,8 @@
+using Content.Client.Trigger.Systems;
using Robust.Client.Animations;
using Robust.Shared.Audio;
-namespace Content.Client.Trigger;
+namespace Content.Client.Trigger.Components;
[RegisterComponent]
[Access(typeof(TimerTriggerVisualizerSystem))]
@@ -16,28 +17,27 @@ public sealed partial class TimerTriggerVisualsComponent : Component
///
/// The RSI state used while the device has not been primed.
///
- [DataField("unprimedSprite")]
- [ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public string UnprimedSprite = "icon";
///
/// The RSI state used when the device is primed.
/// Not VVWrite-able because it's only used at component init to construct the priming animation.
///
- [DataField("primingSprite")]
+ [DataField]
public string PrimingSprite = "primed";
///
/// The sound played when the device is primed.
/// Not VVWrite-able because it's only used at component init to construct the priming animation.
///
- [DataField("primingSound")]
+ [DataField, ViewVariables]
public SoundSpecifier? PrimingSound;
///
/// The actual priming animation.
/// Constructed at component init from the sprite and sound.
///
- [ViewVariables(VVAccess.ReadWrite)]
+ [ViewVariables]
public Animation PrimingAnimation = default!;
}
diff --git a/Content.Client/Explosion/TriggerSystem.Proximity.cs b/Content.Client/Trigger/Systems/ProximityTriggerAnimationSystem.cs
similarity index 91%
rename from Content.Client/Explosion/TriggerSystem.Proximity.cs
rename to Content.Client/Trigger/Systems/ProximityTriggerAnimationSystem.cs
index 03e74369714..79543995051 100644
--- a/Content.Client/Explosion/TriggerSystem.Proximity.cs
+++ b/Content.Client/Trigger/Systems/ProximityTriggerAnimationSystem.cs
@@ -1,11 +1,12 @@
using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Triggers;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
-namespace Content.Client.Explosion;
+namespace Content.Client.Trigger.Systems;
-public sealed partial class TriggerSystem
+public sealed class ProximityTriggerAnimationSystem : EntitySystem
{
[Dependency] private readonly AnimationPlayerSystem _player = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
@@ -18,7 +19,7 @@ public sealed partial class TriggerSystem
private const string AnimKey = "proximity";
- private static readonly Animation _flasherAnimation = new Animation
+ private static readonly Animation FlasherAnimation = new Animation
{
Length = TimeSpan.FromSeconds(0.6f),
AnimationTracks = {
@@ -42,8 +43,10 @@ public sealed partial class TriggerSystem
}
};
- private void InitializeProximity()
+ public override void Initialize()
{
+ base.Initialize();
+
SubscribeLocalEvent(OnProximityInit);
SubscribeLocalEvent(OnProxAppChange);
SubscribeLocalEvent(OnProxAnimation);
@@ -94,7 +97,7 @@ public sealed partial class TriggerSystem
break;
case ProximityTriggerVisuals.Active:
if (_player.HasRunningAnimation(uid, player, AnimKey)) return;
- _player.Play((uid, player), _flasherAnimation, AnimKey);
+ _player.Play((uid, player), FlasherAnimation, AnimKey);
break;
case ProximityTriggerVisuals.Off:
default:
diff --git a/Content.Client/Trigger/Systems/ReleaseGasOnTriggerSystem.cs b/Content.Client/Trigger/Systems/ReleaseGasOnTriggerSystem.cs
new file mode 100644
index 00000000000..a183282ddea
--- /dev/null
+++ b/Content.Client/Trigger/Systems/ReleaseGasOnTriggerSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Client.Trigger.Systems;
+
+public sealed class ReleaseGasOnTriggerSystem : SharedReleaseGasOnTriggerSystem;
diff --git a/Content.Client/Trigger/TimerTriggerVisualizerSystem.cs b/Content.Client/Trigger/Systems/TimerTriggerVisualizerSystem.cs
similarity index 80%
rename from Content.Client/Trigger/TimerTriggerVisualizerSystem.cs
rename to Content.Client/Trigger/Systems/TimerTriggerVisualizerSystem.cs
index b3d85f2017a..7c977c75897 100644
--- a/Content.Client/Trigger/TimerTriggerVisualizerSystem.cs
+++ b/Content.Client/Trigger/Systems/TimerTriggerVisualizerSystem.cs
@@ -1,11 +1,10 @@
+using Content.Client.Trigger.Components;
using Content.Shared.Trigger;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
-using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
-using Robust.Shared.GameObjects;
-namespace Content.Client.Trigger;
+namespace Content.Client.Trigger.Systems;
public sealed class TimerTriggerVisualizerSystem : VisualizerSystem
{
@@ -17,25 +16,26 @@ public sealed class TimerTriggerVisualizerSystem : VisualizerSystem(OnComponentInit);
}
- private void OnComponentInit(EntityUid uid, TimerTriggerVisualsComponent comp, ComponentInit args)
+ private void OnComponentInit(Entity ent, ref ComponentInit args)
{
- comp.PrimingAnimation = new Animation
+ ent.Comp.PrimingAnimation = new Animation
{
Length = TimeSpan.MaxValue,
AnimationTracks = {
- new AnimationTrackSpriteFlick() {
+ new AnimationTrackSpriteFlick()
+ {
LayerKey = TriggerVisualLayers.Base,
- KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.PrimingSprite, 0f) }
+ KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(ent.Comp.PrimingSprite, 0f) }
}
},
};
- if (comp.PrimingSound != null)
+ if (ent.Comp.PrimingSound != null)
{
- comp.PrimingAnimation.AnimationTracks.Add(
+ ent.Comp.PrimingAnimation.AnimationTracks.Add(
new AnimationTrackPlaySound()
{
- KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(comp.PrimingSound), 0) }
+ KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(ent.Comp.PrimingSound), 0) }
}
);
}
diff --git a/Content.Client/UserInterface/Controls/MenuButton.cs b/Content.Client/UserInterface/Controls/MenuButton.cs
index ff41bb7ff52..25888b24e45 100644
--- a/Content.Client/UserInterface/Controls/MenuButton.cs
+++ b/Content.Client/UserInterface/Controls/MenuButton.cs
@@ -25,7 +25,7 @@ public sealed class MenuButton : ContainerButton
private Color NormalColor => HasStyleClass(StyleClassRedTopButton) ? ColorRedNormal : ColorNormal;
private Color HoveredColor => HasStyleClass(StyleClassRedTopButton) ? ColorRedHovered : ColorHovered;
- private BoundKeyFunction _function;
+ private BoundKeyFunction? _function;
private readonly BoxContainer _root;
private readonly TextureRect? _buttonIcon;
private readonly Label? _buttonLabel;
@@ -33,13 +33,13 @@ public sealed class MenuButton : ContainerButton
public string AppendStyleClass { set => AddStyleClass(value); }
public Texture? Icon { get => _buttonIcon!.Texture; set => _buttonIcon!.Texture = value; }
- public BoundKeyFunction BoundKey
+ public BoundKeyFunction? BoundKey
{
get => _function;
set
{
_function = value;
- _buttonLabel!.Text = BoundKeyHelper.ShortKeyName(value);
+ _buttonLabel!.Text = _function == null ? "" : BoundKeyHelper.ShortKeyName(_function.Value);
}
}
@@ -95,12 +95,12 @@ public sealed class MenuButton : ContainerButton
private void OnKeyBindingChanged(IKeyBinding obj)
{
- _buttonLabel!.Text = BoundKeyHelper.ShortKeyName(_function);
+ _buttonLabel!.Text = _function == null ? "" : BoundKeyHelper.ShortKeyName(_function.Value);
}
private void OnKeyBindingChanged()
{
- _buttonLabel!.Text = BoundKeyHelper.ShortKeyName(_function);
+ _buttonLabel!.Text = _function == null ? "" : BoundKeyHelper.ShortKeyName(_function.Value);
}
protected override void StylePropertiesChanged()
diff --git a/Content.Client/UserInterface/Controls/SlotControl.cs b/Content.Client/UserInterface/Controls/SlotControl.cs
index a684bb05efd..2b43f2397de 100644
--- a/Content.Client/UserInterface/Controls/SlotControl.cs
+++ b/Content.Client/UserInterface/Controls/SlotControl.cs
@@ -1,9 +1,11 @@
using System.Numerics;
using Content.Client.Cooldown;
using Content.Client.UserInterface.Systems.Inventory.Controls;
+using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
+using Robust.Shared.Prototypes;
namespace Content.Client.UserInterface.Controls
{
@@ -20,6 +22,7 @@ namespace Content.Client.UserInterface.Controls
public CooldownGraphic CooldownDisplay { get; }
private SpriteView SpriteView { get; }
+ private EntityPrototypeView ProtoView { get; }
public EntityUid? Entity => SpriteView.Entity;
@@ -141,6 +144,13 @@ namespace Content.Client.UserInterface.Controls
SetSize = new Vector2(DefaultButtonSize, DefaultButtonSize),
OverrideDirection = Direction.South
});
+ AddChild(ProtoView = new EntityPrototypeView
+ {
+ Visible = false,
+ Scale = new Vector2(2, 2),
+ SetSize = new Vector2(DefaultButtonSize, DefaultButtonSize),
+ OverrideDirection = Direction.South
+ });
AddChild(HoverSpriteView = new SpriteView
{
@@ -209,12 +219,35 @@ namespace Content.Client.UserInterface.Controls
HoverSpriteView.SetEntity(null);
}
+ ///
+ /// Causes the control to display a placeholder prototype, optionally faded
+ ///
public void SetEntity(EntityUid? ent)
{
SpriteView.SetEntity(ent);
+ SpriteView.Visible = true;
+ ProtoView.Visible = false;
UpdateButtonTexture();
}
+ ///
+ /// Causes the control to display a placeholder prototype, optionally faded
+ ///
+ public void SetPrototype(EntProtoId? proto, bool fade)
+ {
+ ProtoView.SetPrototype(proto);
+ SpriteView.Visible = false;
+ ProtoView.Visible = true;
+
+ UpdateButtonTexture();
+
+ if (ProtoView.Entity is not { } ent || !fade)
+ return;
+
+ var sprites = IoCManager.Resolve().GetEntitySystem();
+ sprites.SetColor((ent.Owner, ent.Comp1), Color.DarkGray.WithAlpha(0.65f));
+ }
+
private void UpdateButtonTexture()
{
var fullTexture = Theme.ResolveTextureOrNull(_fullButtonTexturePath);
diff --git a/Content.Client/UserInterface/StatsWindow.xaml b/Content.Client/UserInterface/StatsWindow.xaml
index 9cda4e41c6f..8a287b4aa77 100644
--- a/Content.Client/UserInterface/StatsWindow.xaml
+++ b/Content.Client/UserInterface/StatsWindow.xaml
@@ -5,7 +5,7 @@
-
+
diff --git a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs
index 917cbbde214..be3af28b157 100644
--- a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs
+++ b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs
@@ -3,12 +3,12 @@ using Content.Client.Actions;
using Content.Client.Actions.UI;
using Content.Client.Cooldown;
using Content.Client.Stylesheets;
-using Content.Shared.Actions;
using Content.Shared.Actions.Components;
-using Content.Shared.Charges.Components;
using Content.Shared.Charges.Systems;
+using Content.Shared.Examine;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
+using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
@@ -23,9 +23,9 @@ namespace Content.Client.UserInterface.Systems.Actions.Controls;
public sealed class ActionButton : Control, IEntityControl
{
private IEntityManager _entities;
+ private IPlayerManager _player;
private SpriteSystem? _spriteSys;
private ActionUIController? _controller;
- private SharedChargesSystem _sharedChargesSys;
private bool _beingHovered;
private bool _depressed;
private bool _toggled;
@@ -67,8 +67,8 @@ public sealed class ActionButton : Control, IEntityControl
// TODO why is this constructor so slooooow. The rest of the code is fine
_entities = entities;
+ _player = IoCManager.Resolve();
_spriteSys = spriteSys;
- _sharedChargesSys = _entities.System();
_controller = controller;
MouseFilter = MouseFilterMode.Pass;
@@ -197,23 +197,17 @@ public sealed class ActionButton : Control, IEntityControl
return null;
var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityName));
- var decr = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityDescription));
- FormattedMessage? chargesText = null;
+ var desc = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityDescription));
- // TODO: Don't touch this use an event make callers able to add their own shit for actions or I kill you.
- if (_entities.TryGetComponent(Action, out LimitedChargesComponent? actionCharges))
- {
- var charges = _sharedChargesSys.GetCurrentCharges((Action.Value, actionCharges, null));
- chargesText = FormattedMessage.FromMarkupPermissive(Loc.GetString($"Charges: {charges.ToString()}/{actionCharges.MaxCharges}"));
+ if (_player.LocalEntity is null)
+ return null;
- if (_entities.TryGetComponent(Action, out AutoRechargeComponent? autoRecharge))
- {
- var chargeTimeRemaining = _sharedChargesSys.GetNextRechargeTime((Action.Value, actionCharges, autoRecharge));
- chargesText.AddText(Loc.GetString($"{Environment.NewLine}Time Til Recharge: {chargeTimeRemaining.TotalSeconds:F1} seconds")); // DeltaV - Better formatted, easier to read.
- }
- }
+ var ev = new ExaminedEvent(desc, Action.Value, _player.LocalEntity.Value, true, !desc.IsEmpty);
+ _entities.EventBus.RaiseLocalEvent(Action.Value.Owner, ev);
- return new ActionAlertTooltip(name, decr, charges: chargesText);
+ var newDesc = ev.GetTotalMessage();
+
+ return new ActionAlertTooltip(name, newDesc);
}
protected override void ControlFocusExited()
diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.Highlighting.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.Highlighting.cs
new file mode 100644
index 00000000000..be95d73de33
--- /dev/null
+++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.Highlighting.cs
@@ -0,0 +1,170 @@
+using System.Linq;
+using System.Text.RegularExpressions;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controllers;
+using Content.Shared._DV.CCVars; // DeltaV
+using Content.Shared.CCVar;
+using Content.Client.CharacterInfo;
+using static Content.Client.CharacterInfo.CharacterInfoSystem;
+using System.Collections;
+using Content.Shared.Mobs;
+
+namespace Content.Client.UserInterface.Systems.Chat;
+
+///
+/// A partial class of ChatUIController that handles the saving and loading of highlights for the chatbox.
+/// It also makes use of the CharacterInfoSystem to optionally generate highlights based on the character's info.
+///
+public sealed partial class ChatUIController : IOnSystemChanged
+{
+ [Dependency] private readonly ILocalizationManager _loc = default!;
+ [UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!;
+
+ private static readonly Regex StartDoubleQuote = new("\"$");
+ private static readonly Regex EndDoubleQuote = new("^\"|(?<=^@)\"");
+ private static readonly Regex StartAtSign = new("^@");
+
+ ///
+ /// The list of words to be highlighted in the chatbox.
+ ///
+ private readonly List _highlights = new();
+
+ ///
+ /// The string holding the hex color used to highlight words.
+ ///
+ private string? _highlightsColor;
+
+ private bool _autoFillHighlightsEnabled;
+
+ ///
+ /// The boolean that keeps track of the 'OnCharacterUpdated' event, whenever it's a player attaching or opening the character info panel.
+ ///
+ private bool _charInfoIsAttach = false;
+
+ public event Action? HighlightsUpdated;
+
+ private void InitializeHighlights()
+ {
+ MigrateDVHighlightSettings(); // TODO: SUM - Remove (DeltaV)
+
+ _config.OnValueChanged(CCVars.ChatAutoFillHighlights, (value) => { _autoFillHighlightsEnabled = value; }, true);
+
+ _config.OnValueChanged(CCVars.ChatHighlightsColor, (value) => { _highlightsColor = value; }, true);
+
+ // Load highlights if any were saved.
+ var highlights = _config.GetCVar(DCCVars.ChatHighlights); // DeltaV - Switched to use our CVar
+
+ if (!string.IsNullOrEmpty(highlights) || AutoHighlightsEnabled) // DeltaV - Message auto-highlighting
+ {
+ UpdateHighlights(highlights, true);
+ }
+ }
+
+ public void OnSystemLoaded(CharacterInfoSystem system)
+ {
+ system.OnCharacterUpdate += OnCharacterUpdated;
+ }
+
+ public void OnSystemUnloaded(CharacterInfoSystem system)
+ {
+ system.OnCharacterUpdate -= OnCharacterUpdated;
+ }
+
+ private void UpdateAutoFillHighlights()
+ {
+ if (!_autoFillHighlightsEnabled)
+ return;
+
+ // If auto highlights are enabled generate a request for new character info
+ // that will be used to determine the highlights.
+ _charInfoIsAttach = true;
+ _characterInfo.RequestCharacterInfo();
+ }
+
+ public void UpdateHighlights(string newHighlights, bool firstLoad = false)
+ {
+ // DeltaV - Heavily modified to split out the auto-generated highlights from the user-defined ones.
+ if (!firstLoad && !_config.GetCVar(DCCVars.ChatHighlights).Equals(newHighlights, StringComparison.CurrentCultureIgnoreCase))
+ {
+ _config.SetCVar(DCCVars.ChatHighlights, newHighlights); // DV - Moved inside this if because the comparison
+ _config.SaveToFile();
+ }
+ _highlights.Clear();
+
+ // We first subdivide the highlights based on newlines to prevent replacing
+ // a valid "\n" tag and adding it to the final regex.
+ var allHighlights = $"{newHighlights}\n{AutoHighlights}"; // Even if AutoHighlights is empty, the split below will remove it.
+ var splittedHighlights = allHighlights.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+ // END DeltaV
+
+ for (var i = 0; i < splittedHighlights.Length; i++)
+ {
+ // Replace every "\" character with a "\\" to prevent "\n", "\0", etc...
+ var keyword = splittedHighlights[i].Replace(@"\", @"\\");
+
+ // Escape the keyword to prevent special characters like "(" and ")" to be considered valid regex.
+ keyword = Regex.Escape(keyword);
+
+ // 1. Since the "["s in WrappedMessage are already sanitized, add 2 extra "\"s
+ // to make sure it matches the literal "\" before the square bracket.
+ keyword = keyword.Replace(@"\[", @"\\\[");
+
+ // If present, replace the double quotes at the edges with tags
+ // that make sure the words to match are separated by spaces or punctuation.
+ // NOTE: The reason why we don't use \b tags is that \b doesn't match reverse slash characters "\" so
+ // a pre-sanitized (see 1.) string like "\[test]" wouldn't get picked up by the \b.
+ if (keyword.Any(c => c == '"'))
+ {
+ // Matches the last double quote character.
+ keyword = StartDoubleQuote.Replace(keyword, "(?!\\w)");
+ // When matching for the first double quote character we also consider the possibility
+ // of the double quote being preceded by a @ character.
+ keyword = EndDoubleQuote.Replace(keyword, "(? y.Length.CompareTo(x.Length));
+ }
+
+ // DeltaV - Modified and moved to Content.Client/_DV/UserInterfaces/Systems/Chat/ChatUIController.cs
+ // Commented out so that if you get a conflict, you actually put that change in the updated method.
+ /*private void OnCharacterUpdated(CharacterData data)
+ {
+ // If _charInfoIsAttach is false then the opening of the character panel was the one
+ // to generate the event, dismiss it.
+ if (!_charInfoIsAttach)
+ return;
+
+ var (_, job, _, _, entityName) = data;
+
+ // Mark this entity's name as our character name for the "UpdateHighlights" function.
+ var newHighlights = "@" + entityName;
+
+ // Subdivide the character's name based on spaces or hyphens so that every word gets highlighted.
+ if (newHighlights.Count(c => (c == ' ' || c == '-')) == 1)
+ newHighlights = newHighlights.Replace("-", "\n@").Replace(" ", "\n@");
+
+ // If the character has a name with more than one hyphen assume it is a lizard name and extract the first and
+ // last name eg. "Eats-The-Food" -> "@Eats" "@Food"
+ if (newHighlights.Count(c => c == '-') > 1)
+ newHighlights = newHighlights.Split('-')[0] + "\n@" + newHighlights.Split('-')[^1];
+
+ // Convert the job title to kebab-case and use it as a key for the loc file.
+ var jobKey = job.Replace(' ', '-').ToLower();
+
+ if (_loc.TryGetString($"highlights-{jobKey}", out var jobMatches))
+ newHighlights += '\n' + jobMatches.Replace(", ", "\n");
+
+ UpdateHighlights(newHighlights);
+ HighlightsUpdated?.Invoke(newHighlights);
+ _charInfoIsAttach = false;
+ }*/
+}
diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs
index dfe661d957b..100841f053f 100644
--- a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs
+++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs
@@ -42,9 +42,9 @@ using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Content.Client.Nyanotrasen.Chat; //Nyano - Summary: chat namespace.
+
namespace Content.Client.UserInterface.Systems.Chat;
-// DeltaV - Make partial to implement message highlighting
public sealed partial class ChatUIController : UIController
{
[Dependency] private readonly IClientAdminManager _admin = default!;
@@ -245,7 +245,7 @@ public sealed partial class ChatUIController : UIController
_config.OnValueChanged(CCVars.ChatWindowOpacity, OnChatWindowOpacityChanged);
- InitializeChatHighlights(); // DeltaV - Message highlighting
+ InitializeHighlights();
}
public void OnScreenLoad()
@@ -432,6 +432,8 @@ public sealed partial class ChatUIController : UIController
private void OnAttachedChanged(EntityUid uid)
{
UpdateChannelPermissions();
+
+ UpdateAutoFillHighlights();
}
private void AddSpeechBubble(ChatMessage msg, SpeechBubble.SpeechType speechType)
@@ -840,13 +842,11 @@ public sealed partial class ChatUIController : UIController
msg.WrappedMessage = SharedChatSystem.InjectTagInsideTag(msg, "Name", "color", GetNameColor(SharedChatSystem.GetStringInsideTag(msg, "Name")));
}
- // DeltaV - Message highlighting start
- // Color any words choosen by the client.
+ // Color any words chosen by the client.
foreach (var highlight in _highlights)
{
msg.WrappedMessage = SharedChatSystem.InjectTagAroundString(msg, highlight, "color", _highlightsColor);
}
- // DeltaV - Message highlighting end
// Color any codewords for minds that have roles that use them
if (_player.LocalUser != null && _mindSystem != null && _roleCodewordSystem != null)
diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml
index d7c485fcb26..3e1aad659a3 100644
--- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml
+++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml
@@ -3,22 +3,19 @@
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Systems.Chat.Controls">
-
-
-
-
-
-
+
-
+
+
+
@@ -26,9 +23,9 @@
+
-
diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs
index f8b4f0ee335..ebdc74726f6 100644
--- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs
+++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs
@@ -1,4 +1,8 @@
+using Content.Shared._DV.CCVars; // DeltaV
using Content.Shared.Chat;
+using Content.Shared.CCVar;
+using Robust.Shared.Utility;
+using Robust.Shared.Configuration;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -30,11 +34,25 @@ public sealed partial class ChannelFilterPopup : Popup
private readonly Dictionary _filterStates = new();
public event Action? OnChannelFilter;
+ public event Action? OnNewHighlights;
public ChannelFilterPopup()
{
RobustXamlLoader.Load(this);
- InitializeChatHighlights(); // DeltaV - Message highlighting
+
+ HighlightButton.OnPressed += HighlightsEntered;
+ // Add a placeholder text to the highlights TextEdit.
+ HighlightEdit.Placeholder = new Rope.Leaf(Loc.GetString("hud-chatbox-highlights-placeholder"));
+
+ // Load highlights if any were saved.
+ var cfg = IoCManager.Resolve();
+ string highlights = cfg.GetCVar(DCCVars.ChatHighlights); // DeltaV - Use our own CVar
+ var autoHighlightsEnabled = cfg.GetCVar(CCVars.ChatAutoFillHighlights); // DeltaV - Auto-highlighting
+
+ if (!string.IsNullOrEmpty(highlights) || autoHighlightsEnabled) // DeltaV - Auto-highlighting
+ {
+ UpdateHighlights(highlights);
+ }
}
public bool IsActive(ChatChannel channel)
@@ -94,12 +112,22 @@ public sealed partial class ChannelFilterPopup : Popup
}
}
+ public void UpdateHighlights(string highlights)
+ {
+ HighlightEdit.TextRope = new Rope.Leaf(highlights);
+ }
+
private void CheckboxPressed(ButtonEventArgs args)
{
var checkbox = (ChannelFilterCheckbox) args.Button;
OnChannelFilter?.Invoke(checkbox.Channel, checkbox.Pressed);
}
+ private void HighlightsEntered(ButtonEventArgs _args)
+ {
+ OnNewHighlights?.Invoke(Rope.Collapse(HighlightEdit.TextRope));
+ }
+
public void UpdateUnread(ChatChannel channel, int? unread)
{
if (_filterStates.TryGetValue(channel, out var checkbox))
diff --git a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs
index 2b3b4b4aac2..f131cc06049 100644
--- a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs
+++ b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs
@@ -51,10 +51,11 @@ public partial class ChatBox : UIWidget
ChatInput.Input.OnFocusExit += OnFocusExit;
ChatInput.ChannelSelector.OnChannelSelect += OnChannelSelect;
ChatInput.FilterButton.Popup.OnChannelFilter += OnChannelFilter;
- ChatInput.FilterButton.Popup.OnNewHighlights += OnNewHighlights; // DeltaV - Message highlighting
+ ChatInput.FilterButton.Popup.OnNewHighlights += OnNewHighlights;
_controller = UserInterfaceManager.GetUIController();
_controller.OnAutoHighlightsUpdated += ChatInput.FilterButton.Popup.SetAutoHighlights; // DeltaV - Message highlighting
_controller.MessageAdded += OnMessageAdded;
+ _controller.HighlightsUpdated += OnHighlightsUpdated;
_controller.RegisterChat(this);
// EE - Chat stacking
@@ -140,6 +141,11 @@ public partial class ChatBox : UIWidget
_chatStackList.Insert(0, new ChatStackData(wrappedMessage, colorOverride));
}
+ private void OnHighlightsUpdated(string highlights)
+ {
+ ChatInput.FilterButton.Popup.UpdateHighlights(highlights);
+ }
+
private void OnChannelSelect(ChatSelectChannel channel)
{
_controller.UpdateSelectedChannel(this);
@@ -170,7 +176,12 @@ public partial class ChatBox : UIWidget
}
}
- public void AddLine(string message, Color color, int repeat = 0) // EE - Chat stacking - repeat
+ private void OnNewHighlights(string highlighs)
+ {
+ _controller.UpdateHighlights(highlighs);
+ }
+
+ public void AddLine(string message, Color color, int repeat = 0) // EE - Chat stacking - repea
{
var formatted = new FormattedMessage(4); // EE - Chat stacking - up from 3
formatted.PushColor(color);
diff --git a/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs b/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs
index e54446a47b0..f1622d40608 100644
--- a/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs
+++ b/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs
@@ -12,6 +12,7 @@ using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Shared.Input;
+using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Content.Shared._NF.Interaction.Components;
@@ -74,7 +75,8 @@ public sealed class HandsUIController : UIController, IOnStateEntered entity, string name)
@@ -140,7 +142,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered(_entity, out var meta) || meta.Deleted)
{
- Update(null);
+ Update(null, hand);
return;
}
diff --git a/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs b/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs
index b2160c085a5..ec6293811f3 100644
--- a/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs
+++ b/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs
@@ -194,8 +194,6 @@ public sealed class InventoryUIController : UIController, IOnStateEntered Entity;
}
-///
-/// This event gets raised before a sprite gets drawn in a grid and lets to change the sprite color for several gameobjects that have special sprites to render in containers.
-///
-public sealed class BeforeRenderInGridEvent : EntityEventArgs
-{
- public Color Color { get; set; }
-
- public BeforeRenderInGridEvent(Color color)
- {
- Color = color;
- }
-}
-
public enum ItemGridPieceMarks
{
First,
diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs
index 6abebda6ee4..c27e81b5c79 100644
--- a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs
+++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs
@@ -30,6 +30,7 @@ public sealed partial class GunSystem : SharedGunSystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
+ [Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IStateManager _state = default!;
[Dependency] private readonly AnimationPlayerSystem _animPlayer = default!;
@@ -50,11 +51,10 @@ public sealed partial class GunSystem : SharedGunSystem
return;
_spreadOverlay = value;
- var overlayManager = IoCManager.Resolve();
if (_spreadOverlay)
{
- overlayManager.AddOverlay(new GunSpreadOverlay(
+ _overlayManager.AddOverlay(new GunSpreadOverlay(
EntityManager,
_eyeManager,
Timing,
@@ -65,7 +65,7 @@ public sealed partial class GunSystem : SharedGunSystem
}
else
{
- overlayManager.RemoveOverlay();
+ _overlayManager.RemoveOverlay();
}
}
}
diff --git a/Content.Client/Wieldable/WieldableSystem.cs b/Content.Client/Wieldable/WieldableSystem.cs
index cdf9bc486dd..0198af284be 100644
--- a/Content.Client/Wieldable/WieldableSystem.cs
+++ b/Content.Client/Wieldable/WieldableSystem.cs
@@ -29,7 +29,10 @@ public sealed class WieldableSystem : SharedWieldableSystem
return;
if (_gameTiming.IsFirstTimePredicted)
+ {
cursorOffsetComp.CurrentPosition = Vector2.Zero;
+ cursorOffsetComp.TargetPosition = Vector2.Zero;
+ }
}
public void OnGetEyeOffset(Entity entity, ref HeldRelayedEvent args)
diff --git a/Content.Client/_DV/Options/UI/OptionsTabControlRow.xaml.cs b/Content.Client/_DV/Options/UI/OptionsTabControlRow.xaml.cs
deleted file mode 100644
index 9e121c57a29..00000000000
--- a/Content.Client/_DV/Options/UI/OptionsTabControlRow.xaml.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Content.Client._DV.Options.UI;
-using Robust.Shared.Configuration;
-
-namespace Content.Client.Options.UI;
-
-public sealed partial class OptionsTabControlRow
-{
- ///
- /// Add a color slider option, backed by a simple string CVar.
- ///
- /// The CVar represented by the slider.
- /// The UI control for the option.
- /// The option instance backing the added option.
- public OptionColorSliderCVar AddOptionColorSlider(
- CVarDef cVar,
- OptionColorSlider slider)
- {
- return AddOption(new OptionColorSliderCVar(this, _cfg, cVar, slider));
- }
-}
diff --git a/Content.Client/_DV/Options/UI/OptionsTabControls.cs b/Content.Client/_DV/Options/UI/OptionsTabControls.cs
deleted file mode 100644
index f3d9fd3c7f1..00000000000
--- a/Content.Client/_DV/Options/UI/OptionsTabControls.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using Content.Client.Options.UI;
-using Robust.Shared.Configuration;
-
-namespace Content.Client._DV.Options.UI;
-
-///
-/// Implementation of a CVar option that simply corresponds with a string .
-///
-///
-public sealed class OptionColorSliderCVar : BaseOptionCVar
-{
- private readonly OptionColorSlider _slider;
-
- protected override string Value
- {
- get => _slider.Slider.Color.ToHex();
- set
- {
- _slider.Slider.Color = Color.FromHex(value);
- UpdateLabelColor();
- }
- }
-
- ///
- /// Creates a new instance of this type.
- ///
- ///
- ///
- /// It is generally more convenient to call overloads on
- /// such as instead of instantiating this type directly.
- ///
- ///
- /// The control row that owns this option.
- /// The configuration manager to get and set values from.
- /// The CVar that is being controlled by this option.
- /// The UI control for the option.
- public OptionColorSliderCVar(
- OptionsTabControlRow controller,
- IConfigurationManager cfg,
- CVarDef cVar,
- OptionColorSlider slider) : base(controller, cfg, cVar)
- {
- _slider = slider;
-
- slider.Slider.OnColorChanged += _ =>
- {
- ValueChanged();
- UpdateLabelColor();
- };
- }
-
- private void UpdateLabelColor()
- {
- _slider.ExampleLabel.FontColorOverride = Color.FromHex(Value);
- }
-}
diff --git a/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml b/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml
index 87f12300309..940b78d4be8 100644
--- a/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml
+++ b/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml
@@ -2,6 +2,7 @@
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:tabs="clr-namespace:Content.Client._DV.Options.UI.Tabs"
xmlns:xNamespace="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:optionsUI="clr-namespace:Content.Client.Options.UI"
xmlns:s="clr-namespace:Content.Client.Stylesheets"
xmlns:ui="clr-namespace:Content.Client.Options.UI"
xmlns:dvui="clr-namespace:Content.Client._DV.Options.UI">
@@ -15,10 +16,6 @@
StyleClasses="LabelKeyText"/>
-
-
diff --git a/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml.cs b/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml.cs
index 3482980f29e..86d3c99f447 100644
--- a/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml.cs
+++ b/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml.cs
@@ -1,4 +1,5 @@
using Content.Shared._DV.CCVars;
+using Content.Client.Options.UI;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
@@ -15,8 +16,8 @@ public sealed partial class DeltaTab : Control
Control.AddOptionCheckBox(DCCVars.NoVisionFilters, DisableFiltersCheckBox);
Control.AddOptionCheckBox(DCCVars.DisableGlimmerShader, DisableGlimmerEffectCheckBox);
- Control.AddOptionCheckBox(DCCVars.ChatAutoFillHighlights, AutoFillHighlightsCheckBox);
- Control.AddOptionColorSlider(DCCVars.ChatHighlightsColor, HighlightsColorSlider);
+ //Control.AddOptionCheckBox(DCCVars.ChatAutoFillHighlights, AutoFillHighlightsCheckBox); // Migrating to the new system
+ //Control.AddOptionColorSlider(DCCVars.ChatHighlightsColor, HighlightsColorSlider); // Migrating to the new system
Control.Initialize();
}
diff --git a/Content.Client/_DV/UserInterfaces/Systems/Chat/ChatUIController.cs b/Content.Client/_DV/UserInterfaces/Systems/Chat/ChatUIController.cs
index 2a6a46b2308..0fd37a6069e 100644
--- a/Content.Client/_DV/UserInterfaces/Systems/Chat/ChatUIController.cs
+++ b/Content.Client/_DV/UserInterfaces/Systems/Chat/ChatUIController.cs
@@ -1,6 +1,7 @@
using System.Linq;
using System.Text.RegularExpressions;
using Content.Client.CharacterInfo;
+using Content.Shared.CCVar;
using Content.Shared._DV.CCVars;
using Content.Shared.Dataset;
using Robust.Client.UserInterface;
@@ -17,116 +18,84 @@ public sealed partial class ChatUIController : IOnSystemChanged
public event Action? OnAutoHighlightsUpdated;
- [UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!;
-
- ///
- /// A list of words to be highlighted in the chatbox.
- /// User-specified.
- ///
- private readonly List _highlights = [];
-
///
/// A list of words to be highlighted in the chatbox.
/// Auto-generated from users's character information.
///
- private readonly List _autoHighlights = [];
+ private string _autoHighlights = String.Empty;
///
- /// The color (hex) in witch the words will be highlighted as.
+ /// Returns the list of auto-generated highlights based on the character's info (job, name, etc). Returns an empty list if the option is disabled.
///
- private string? _highlightsColor;
+ internal string AutoHighlights => _autoFillHighlightsEnabled ? _autoHighlights : String.Empty;
- private bool _autoFillHighlightsEnabled;
+ internal string CurrentUserHighlights => _config.GetCVar(DCCVars.ChatHighlights);
- private void InitializeChatHighlights()
+ ///
+ /// Gets whether the player has auto-generated highlights enabled or not.
+ ///
+ internal bool AutoHighlightsEnabled => _autoFillHighlightsEnabled;
+
+ internal void OnCharacterUpdated(CharacterData data)
{
+ // If _charInfoIsAttach is false then the opening of the character panel was the one
+ // to generate the event, dismiss it.
+ if (!_charInfoIsAttach)
+ return;
- _player.LocalPlayerAttached += _ => _characterInfo.RequestCharacterInfo();
- _player.LocalPlayerDetached += _ => _characterInfo.RequestCharacterInfo();
-
- _config.OnValueChanged(DCCVars.ChatAutoFillHighlights, value => { _autoFillHighlightsEnabled = value; UpdateHighlights(); });
- _autoFillHighlightsEnabled = _config.GetCVar(DCCVars.ChatAutoFillHighlights);
-
- _config.OnValueChanged(DCCVars.ChatHighlightsColor, value => _highlightsColor = value);
- _highlightsColor = _config.GetCVar(DCCVars.ChatHighlightsColor);
-
- _config.OnValueChanged(DCCVars.ChatHighlights, UpdateHighlights);
- UpdateHighlights(_config.GetCVar(DCCVars.ChatHighlights));
- }
-
-
- public void OnSystemLoaded(CharacterInfoSystem system)
- {
- system.OnCharacterUpdate += UpdateAutoHighlights;
- }
-
- public void OnSystemUnloaded(CharacterInfoSystem system)
- {
- system.OnCharacterUpdate -= UpdateAutoHighlights;
- }
-
- private void UpdateAutoHighlights(CharacterData data)
- {
var (_, job, _, _, entityName) = data;
- _autoHighlights.Clear();
+ // Mark this entity's name as our character name for the "UpdateHighlights" function.
+ string newHighlights = "@" + entityName;
- // If the character has a normal name (eg. "Name Surname" and not "Name Initial Surname" or a particular species name)
- // subdivide it so that the name and surname individually get highlighted.
- if (entityName.Count(c => c == ' ') == 1)
- _autoHighlights.AddRange(entityName.Split(' '));
- _autoHighlights.Add(entityName);
+ // Subdivide the character's name based on spaces or hyphens so that every word gets highlighted.
+ if (newHighlights.Count(c => (c == ' ' || c == '-')) == 1)
+ newHighlights = newHighlights.Replace("-", "\n@").Replace(" ", "\n@");
- var jobKey = "ChatHighlight" + job.Replace(" ", "");
- if (_prototypeManager.TryIndex(jobKey, out var jobMatches))
- _autoHighlights.AddRange(jobMatches.Values.Select(Loc.GetString));
- else
- _sawmill.Debug("Missing LocalizedDataset for Job: " + jobKey);
- UpdateHighlights();
+ // If the character has a name with more than one hyphen assume it is a lizard name and extract the first and
+ // last name eg. "Eats-The-Food" -> "@Eats" "@Food"
+ if (newHighlights.Count(c => c == '-') > 1)
+ newHighlights = newHighlights.Split('-')[0] + "\n@" + newHighlights.Split('-')[^1];
+
+ // Convert the job title to kebab-case and use it as a key for the loc file.
+ string jobKey = job.Replace(' ', '-').ToLower();
+
+ if (Loc.TryGetString($"highlights-{jobKey}", out var jobMatches))
+ newHighlights += '\n' + jobMatches.Replace(", ", "\n");
+
+ _autoHighlights = newHighlights; // DeltaV
+ UpdateHighlights(CurrentUserHighlights); // DeltaV
+ OnAutoHighlightsUpdated?.Invoke($"{AutoHighlights}"); // DeltaV
+ _charInfoIsAttach = false;
}
- public void UpdateHighlights(string? newHighlights = null)
+ ///
+ /// This is ugly but it's only going to be around until the next upstream merge.
+ /// We'll still use DCCVars.ChatHighlights.
+ ///
+ private void MigrateDVHighlightSettings()
{
- var configuredHighlights = _config.GetCVar(DCCVars.ChatHighlights);
- var highlights = newHighlights ?? configuredHighlights;
- // Save the newly provided list of highlights if different.
- if (newHighlights is not null && !string.Equals(configuredHighlights, highlights, StringComparison.CurrentCultureIgnoreCase))
+ // AutoHightligt Checkbox
+ bool shouldSave = false;
+ var oldDCCAutofillFlag = _config.GetCVar(DCCVars.ChatAutoFillHighlights);
+ if (oldDCCAutofillFlag)
{
- _config.SetCVar(DCCVars.ChatHighlights, highlights);
+ _config.SetCVar(CCVars.ChatAutoFillHighlights, oldDCCAutofillFlag);
+ _config.SetCVar(DCCVars.ChatAutoFillHighlights, false); // Next time this runs, it won't migrate this value again since CCVars.ChatAutoFillHighlights defaults to false.
+ shouldSave = true;
+ }
+
+ // Chat Color
+ var oldDCCVarColor = _config.GetCVar(DCCVars.ChatHighlightsColor);
+ var defaultColor = "#17FFC1FF";
+ if (!oldDCCVarColor.Equals(defaultColor)) // Default value
+ {
+ _config.SetCVar(CCVars.ChatHighlightsColor, oldDCCVarColor); // Next time, it should equal those words
+ _config.SetCVar(DCCVars.ChatHighlightsColor, defaultColor); // Prevents it from running again
+ shouldSave = true;
+ }
+
+ if (shouldSave)
_config.SaveToFile();
- }
-
- var effectiveAutoHighlights = _autoFillHighlightsEnabled
- ? string.Join("\n", _autoHighlights)
- : string.Empty;
- OnAutoHighlightsUpdated?.Invoke(effectiveAutoHighlights);
-
- // If `highlights` is an empty string, this gives a single empty string, which breaks stuff, so check for that separately when adding it to `_highlights`
- var allHighlights = _autoFillHighlightsEnabled
- ? highlights.Split("\n").Concat(_autoHighlights)
- : highlights.Split("\n");
-
- _highlights.Clear();
-
- void AddHighlights(IEnumerable highlights)
- {
- foreach (var highlight in highlights)
- {
- if (string.IsNullOrWhiteSpace(highlight))
- continue;
- // Use `"` as layman symbol for Regex `\b`, ignore all other special sequences
- // (Without that escape, a name like `Robert'); DROP TABLE users; --` breaks all messsages)
- // Turn `\` into `\\` or else it'll escape the tags inside the actual chat message for reasons I can barely intuit but not explain.
- _highlights.Add(Regex.Escape(highlight.Replace(@"\", @"\\")).Replace("\"", "\\b"));
- }
- }
-
- AddHighlights(highlights.Split("\n"));
- if (_autoFillHighlightsEnabled)
- AddHighlights(_autoHighlights);
-
- // Arrange the list in descending order so that when highlighting,
- // the full word (eg. "Security") appears before the abbreviation (eg. "Sec").
- _highlights.Sort((x, y) => y.Length.CompareTo(x.Length));
}
}
diff --git a/Content.Client/_DV/UserInterfaces/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs b/Content.Client/_DV/UserInterfaces/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs
index a76c4e7dc4a..4926f3dbd0e 100644
--- a/Content.Client/_DV/UserInterfaces/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs
+++ b/Content.Client/_DV/UserInterfaces/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs
@@ -7,24 +7,6 @@ namespace Content.Client.UserInterface.Systems.Chat.Controls;
public sealed partial class ChannelFilterPopup
{
- public event Action? OnNewHighlights;
-
- private void InitializeChatHighlights()
- {
- HighlightButton.OnPressed += HighlightsEntered;
-
- HighlightEdit.Placeholder = new Rope.Leaf(Loc.GetString("hud-chatbox-highlights-placeholder"));
-
- // Load highlights if any were saved.
- var cfg = IoCManager.Resolve();
- var highlights = cfg.GetCVar(DCCVars.ChatHighlights);
-
- if (!string.IsNullOrEmpty(highlights))
- {
- HighlightEdit.TextRope = new Rope.Leaf(highlights);
- }
- }
-
public void SetAutoHighlights(string autoHighlights)
{
var anyAutohighlights = !string.IsNullOrWhiteSpace(autoHighlights);
@@ -32,9 +14,4 @@ public sealed partial class ChannelFilterPopup
AutoHighlightLabel.Visible = anyAutohighlights;
AutoHighlightEdit.TextRope = new Rope.Leaf(autoHighlights);
}
-
- private void HighlightsEntered(ButtonEventArgs args)
- {
- OnNewHighlights?.Invoke(Rope.Collapse(HighlightEdit.TextRope));
- }
}
diff --git a/Content.Client/_DV/UserInterfaces/Systems/Chat/Controls/Widgets/ChatBox.xaml.cs b/Content.Client/_DV/UserInterfaces/Systems/Chat/Controls/Widgets/ChatBox.xaml.cs
deleted file mode 100644
index fe8608ded45..00000000000
--- a/Content.Client/_DV/UserInterfaces/Systems/Chat/Controls/Widgets/ChatBox.xaml.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Client.UserInterface.Systems.Chat.Widgets;
-
-public partial class ChatBox
-{
- private void OnNewHighlights(string highlights)
- {
- _controller.UpdateHighlights(highlights);
- }
-}
diff --git a/Content.Client/_White/Standing/LayingDownSystem.cs b/Content.Client/_White/Standing/LayingDownSystem.cs
deleted file mode 100644
index c60a07f5c18..00000000000
--- a/Content.Client/_White/Standing/LayingDownSystem.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using Content.Shared._White.Standing;
-using Content.Shared.Buckle;
-using Content.Shared.Rotation;
-using Content.Shared.Standing;
-using Robust.Client.GameObjects;
-using Robust.Client.Graphics;
-using Robust.Shared.Timing;
-
-namespace Content.Client._White.Standing;
-
-public sealed class LayingDownSystem : SharedLayingDownSystem
-{
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IEyeManager _eyeManager = default!;
- [Dependency] private readonly StandingStateSystem _standing = default!;
- [Dependency] private readonly AnimationPlayerSystem _animation = default!;
- [Dependency] private readonly SharedBuckleSystem _buckle = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnMovementInput);
-
- SubscribeNetworkEvent(OnCheckAutoGetUp);
- }
-
- private void OnMovementInput(EntityUid uid, LayingDownComponent component, MoveEvent args)
- {
- if (!_timing.IsFirstTimePredicted)
- return;
-
- if (!_standing.IsDown(uid))
- return;
-
- if (_buckle.IsBuckled(uid))
- return;
-
- if (_animation.HasRunningAnimation(uid, "rotate"))
- return;
-
- if (!TryComp(uid, out var transform)
- || !TryComp(uid, out var sprite)
- || !TryComp(uid, out var rotationVisuals))
- {
- return;
- }
-
- var rotation = transform.LocalRotation + (_eyeManager.CurrentEye.Rotation - (transform.LocalRotation - transform.WorldRotation));
-
- if (rotation.GetDir() is Direction.SouthEast or Direction.East or Direction.NorthEast or Direction.North)
- {
- rotationVisuals.HorizontalRotation = Angle.FromDegrees(270);
- sprite.Rotation = Angle.FromDegrees(270);
- return;
- }
-
- rotationVisuals.HorizontalRotation = Angle.FromDegrees(90);
- sprite.Rotation = Angle.FromDegrees(90);
- }
-
- private void OnCheckAutoGetUp(CheckAutoGetUpEvent ev, EntitySessionEventArgs args)
- {
- if (!_timing.IsFirstTimePredicted)
- return;
-
- var uid = GetEntity(ev.User);
-
- if (!TryComp(uid, out var transform) || !TryComp(uid, out var rotationVisuals))
- return;
-
- var rotation = transform.LocalRotation + (_eyeManager.CurrentEye.Rotation - (transform.LocalRotation - transform.WorldRotation));
-
- if (rotation.GetDir() is Direction.SouthEast or Direction.East or Direction.NorthEast or Direction.North)
- {
- rotationVisuals.HorizontalRotation = Angle.FromDegrees(270);
- return;
- }
-
- rotationVisuals.HorizontalRotation = Angle.FromDegrees(90);
- }
-}
diff --git a/Content.IntegrationTests/PoolManager.Cvars.cs b/Content.IntegrationTests/PoolManager.Cvars.cs
index 2c51bdbc3a3..8cf2b626dc6 100644
--- a/Content.IntegrationTests/PoolManager.Cvars.cs
+++ b/Content.IntegrationTests/PoolManager.Cvars.cs
@@ -21,6 +21,7 @@ public static partial class PoolManager
(CCVars.NPCMaxUpdates.Name, "999999"),
(CVars.ThreadParallelCount.Name, "1"),
(CCVars.GameRoleTimers.Name, "false"),
+ (CCVars.GameRoleLoadoutTimers.Name, "false"),
(CCVars.GameRoleWhitelist.Name, "false"),
(CCVars.GridFill.Name, "false"),
(CCVars.PreloadGrids.Name, "false"),
diff --git a/Content.IntegrationTests/Tests/Atmos/DeltaPressureTest.cs b/Content.IntegrationTests/Tests/Atmos/DeltaPressureTest.cs
new file mode 100644
index 00000000000..839e0ea8972
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Atmos/DeltaPressureTest.cs
@@ -0,0 +1,423 @@
+using System.Linq;
+using System.Numerics;
+using Content.Server.Atmos;
+using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.Atmos;
+using Robust.Shared.EntitySerialization;
+using Robust.Shared.EntitySerialization.Systems;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Utility;
+
+namespace Content.IntegrationTests.Tests.Atmos;
+
+///
+/// Tests for AtmosphereSystem.DeltaPressure and surrounding systems
+/// handling the DeltaPressureComponent.
+///
+[TestFixture]
+[TestOf(typeof(DeltaPressureSystem))]
+public sealed class DeltaPressureTest
+{
+ #region Prototypes
+
+ [TestPrototypes]
+ private const string Prototypes = @"
+- type: entity
+ parent: BaseStructure
+ id: DeltaPressureSolidTest
+ placement:
+ mode: SnapgridCenter
+ snap:
+ - Wall
+ components:
+ - type: Physics
+ bodyType: Static
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape:
+ !type:PhysShapeAabb
+ bounds: ""-0.5,-0.5,0.5,0.5""
+ mask:
+ - FullTileMask
+ layer:
+ - WallLayer
+ density: 1000
+ - type: Airtight
+ - type: DeltaPressure
+ minPressure: 15000
+ minPressureDelta: 10000
+ scalingType: Threshold
+ baseDamage:
+ types:
+ Structural: 1000
+ - type: Damageable
+ - type: Destructible
+ thresholds:
+ - trigger:
+ !type:DamageTrigger
+ damage: 300
+ behaviors:
+ - !type:SpawnEntitiesBehavior
+ spawn:
+ Girder:
+ min: 1
+ max: 1
+ - !type:DoActsBehavior
+ acts: [ ""Destruction"" ]
+
+- type: entity
+ parent: DeltaPressureSolidTest
+ id: DeltaPressureSolidTestNoAutoJoin
+ components:
+ - type: DeltaPressure
+ autoJoinProcessingList: false
+
+- type: entity
+ parent: DeltaPressureSolidTest
+ id: DeltaPressureSolidTestAbsolute
+ components:
+ - type: DeltaPressure
+ minPressure: 10000
+ minPressureDelta: 15000
+ scalingType: Threshold
+ baseDamage:
+ types:
+ Structural: 1000
+";
+
+ #endregion
+
+ private readonly ResPath _testMap = new("Maps/Test/Atmospherics/DeltaPressure/deltapressuretest.yml");
+
+ // TODO ATMOS TESTS
+ // - Check for directional windows (partial airtight ents) properly computing pressure differences
+ // - Check for multi-tick damage (window with n damage threshold should take n ticks to destroy)
+ // - Check that all maps do not explode into a million pieces on load due to dP
+ // - Ensure that all tests work for a map that has an origin at a non zero coordinate
+
+ ///
+ /// Asserts that an entity with a DeltaPressureComponent with autoJoinProcessingList
+ /// set to true is automatically added to the DeltaPressure processing list
+ /// on the grid's GridAtmosphereComponent.
+ ///
+ /// Also asserts that an entity with a DeltaPressureComponent with autoJoinProcessingList
+ /// set to false is not automatically added to the DeltaPressure processing list.
+ ///
+ [Test]
+ public async Task ProcessingListAutoJoinTest()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var entMan = server.EntMan;
+ var mapLoader = entMan.System();
+ var atmosphereSystem = entMan.System();
+ var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
+
+ Entity grid = default;
+ Entity dpEnt;
+
+ // Load our test map in and assert that it exists.
+ await server.WaitPost(() =>
+ {
+#pragma warning disable NUnit2045
+ Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
+ $"Failed to load map {_testMap}.");
+ Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
+#pragma warning restore NUnit2045
+
+ grid = gridSet.First();
+ });
+
+ await server.WaitAssertion(() =>
+ {
+ var uid = entMan.SpawnAtPosition("DeltaPressureSolidTest", new EntityCoordinates(grid.Owner, Vector2.Zero));
+ dpEnt = new Entity(uid, entMan.GetComponent(uid));
+
+ Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have automatically joined!");
+ entMan.DeleteEntity(uid);
+ Assert.That(!atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was still in processing list after deletion!");
+ });
+
+ await pair.CleanReturnAsync();
+ }
+
+ ///
+ /// Asserts that an entity that doesn't need to be damaged by DeltaPressure
+ /// is not damaged by DeltaPressure.
+ ///
+ [Test]
+ public async Task ProcessingDeltaStandbyTest()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var entMan = server.EntMan;
+ var mapLoader = entMan.System();
+ var atmosphereSystem = entMan.System();
+ var transformSystem = entMan.System();
+ var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
+
+ Entity grid = default;
+ Entity dpEnt = default;
+ TileAtmosphere tile = null!;
+ AtmosDirection direction = default;
+
+ // Load our test map in and assert that it exists.
+ await server.WaitPost(() =>
+ {
+#pragma warning disable NUnit2045
+ Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
+ $"Failed to load map {_testMap}.");
+ Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
+#pragma warning restore NUnit2045
+
+ grid = gridSet.First();
+ var uid = entMan.SpawnAtPosition("DeltaPressureSolidTest", new EntityCoordinates(grid.Owner, Vector2.Zero));
+ dpEnt = new Entity(uid, entMan.GetComponent(uid));
+ Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have been added!");
+ });
+
+ for (var i = 0; i < Atmospherics.Directions; i++)
+ {
+ await server.WaitPost(() =>
+ {
+ var indices = transformSystem.GetGridOrMapTilePosition(dpEnt);
+ var gridAtmosComp = entMan.GetComponent(grid);
+
+ direction = (AtmosDirection)(1 << i);
+ var offsetIndices = indices.Offset(direction);
+ tile = gridAtmosComp.Tiles[offsetIndices];
+
+ Assert.That(tile.Air, Is.Not.Null, $"Tile at {offsetIndices} should have air!");
+
+ var toPressurize = dpEnt.Comp!.MinPressureDelta - 10;
+ var moles = (toPressurize * tile.Air.Volume) / (Atmospherics.R * Atmospherics.T20C);
+
+ tile.Air!.AdjustMoles(Gas.Nitrogen, moles);
+ });
+
+ await server.WaitRunTicks(30);
+
+ // Entity should exist, if it took one tick of damage then it should be instantly destroyed.
+ await server.WaitAssertion(() =>
+ {
+ Assert.That(!entMan.Deleted(dpEnt), $"{dpEnt} should still exist after experiencing non-threshold pressure from {direction} side!");
+ tile.Air!.Clear();
+ });
+
+ await server.WaitRunTicks(30);
+ }
+
+ await pair.CleanReturnAsync();
+ }
+
+ ///
+ /// Asserts that an entity that needs to be damaged by DeltaPressure
+ /// is damaged by DeltaPressure when the pressure is above the threshold.
+ ///
+ [Test]
+ public async Task ProcessingDeltaDamageTest()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var entMan = server.EntMan;
+ var mapLoader = entMan.System();
+ var atmosphereSystem = entMan.System();
+ var transformSystem = entMan.System();
+ var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
+
+ Entity grid = default;
+ Entity dpEnt = default;
+ TileAtmosphere tile = null!;
+ AtmosDirection direction = default;
+
+ // Load our test map in and assert that it exists.
+ await server.WaitPost(() =>
+ {
+#pragma warning disable NUnit2045
+ Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
+ $"Failed to load map {_testMap}.");
+ Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
+#pragma warning restore NUnit2045
+
+ grid = gridSet.First();
+ });
+
+ for (var i = 0; i < Atmospherics.Directions; i++)
+ {
+ await server.WaitPost(() =>
+ {
+ // Need to spawn an entity each run to ensure it works for all directions.
+ var uid = entMan.SpawnAtPosition("DeltaPressureSolidTest", new EntityCoordinates(grid.Owner, Vector2.Zero));
+ dpEnt = new Entity(uid, entMan.GetComponent(uid));
+ Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have been added!");
+
+ var indices = transformSystem.GetGridOrMapTilePosition(dpEnt);
+ var gridAtmosComp = entMan.GetComponent(grid);
+
+ direction = (AtmosDirection)(1 << i);
+ var offsetIndices = indices.Offset(direction);
+ tile = gridAtmosComp.Tiles[offsetIndices];
+
+ Assert.That(tile.Air, Is.Not.Null, $"Tile at {offsetIndices} should have air!");
+
+ var toPressurize = dpEnt.Comp!.MinPressureDelta + 10;
+ var moles = (toPressurize * tile.Air.Volume) / (Atmospherics.R * Atmospherics.T20C);
+
+ tile.Air!.AdjustMoles(Gas.Nitrogen, moles);
+ });
+
+ await server.WaitRunTicks(30);
+
+ // Entity should exist, if it took one tick of damage then it should be instantly destroyed.
+ await server.WaitAssertion(() =>
+ {
+ Assert.That(entMan.Deleted(dpEnt), $"{dpEnt} still exists after experiencing threshold pressure from {direction} side!");
+ tile.Air!.Clear();
+ });
+
+ await server.WaitRunTicks(30);
+ }
+
+ await pair.CleanReturnAsync();
+ }
+
+ ///
+ /// Asserts that an entity that doesn't need to be damaged by DeltaPressure
+ /// is not damaged by DeltaPressure when using absolute pressure thresholds.
+ ///
+ [Test]
+ public async Task ProcessingAbsoluteStandbyTest()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var entMan = server.EntMan;
+ var mapLoader = entMan.System();
+ var atmosphereSystem = entMan.System();
+ var transformSystem = entMan.System();
+ var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
+
+ Entity grid = default;
+ Entity dpEnt = default;
+ TileAtmosphere tile = null!;
+ AtmosDirection direction = default;
+
+ await server.WaitPost(() =>
+ {
+#pragma warning disable NUnit2045
+ Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
+ $"Failed to load map {_testMap}.");
+ Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
+#pragma warning restore NUnit2045
+ grid = gridSet.First();
+ var uid = entMan.SpawnAtPosition("DeltaPressureSolidTestAbsolute", new EntityCoordinates(grid.Owner, Vector2.Zero));
+ dpEnt = new Entity(uid, entMan.GetComponent(uid));
+ Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have been added!");
+ });
+
+ for (var i = 0; i < Atmospherics.Directions; i++)
+ {
+ await server.WaitPost(() =>
+ {
+ var indices = transformSystem.GetGridOrMapTilePosition(dpEnt);
+ var gridAtmosComp = entMan.GetComponent(grid);
+
+ direction = (AtmosDirection)(1 << i);
+ var offsetIndices = indices.Offset(direction);
+ tile = gridAtmosComp.Tiles[offsetIndices];
+ Assert.That(tile.Air, Is.Not.Null, $"Tile at {offsetIndices} should have air!");
+
+ var toPressurize = dpEnt.Comp!.MinPressure - 10; // just below absolute threshold
+ var moles = (toPressurize * tile.Air.Volume) / (Atmospherics.R * Atmospherics.T20C);
+ tile.Air!.AdjustMoles(Gas.Nitrogen, moles);
+ });
+
+ await server.WaitRunTicks(30);
+
+ await server.WaitAssertion(() =>
+ {
+ Assert.That(!entMan.Deleted(dpEnt), $"{dpEnt} should still exist after experiencing non-threshold absolute pressure from {direction} side!");
+ tile.Air!.Clear();
+ });
+
+ await server.WaitRunTicks(30);
+ }
+
+ await pair.CleanReturnAsync();
+ }
+
+ ///
+ /// Asserts that an entity that needs to be damaged by DeltaPressure
+ /// is damaged by DeltaPressure when the pressure is above the absolute threshold.
+ ///
+ [Test]
+ public async Task ProcessingAbsoluteDamageTest()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var entMan = server.EntMan;
+ var mapLoader = entMan.System();
+ var atmosphereSystem = entMan.System();
+ var transformSystem = entMan.System();
+ var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
+
+ Entity grid = default;
+ Entity dpEnt = default;
+ TileAtmosphere tile = null!;
+ AtmosDirection direction = default;
+
+ await server.WaitPost(() =>
+ {
+#pragma warning disable NUnit2045
+ Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
+ $"Failed to load map {_testMap}.");
+ Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
+#pragma warning restore NUnit2045
+ grid = gridSet.First();
+ });
+
+ for (var i = 0; i < Atmospherics.Directions; i++)
+ {
+ await server.WaitPost(() =>
+ {
+ // Spawn fresh entity each iteration to verify all directions work
+ var uid = entMan.SpawnAtPosition("DeltaPressureSolidTestAbsolute", new EntityCoordinates(grid.Owner, Vector2.Zero));
+ dpEnt = new Entity(uid, entMan.GetComponent(uid));
+ Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have been added!");
+
+ var indices = transformSystem.GetGridOrMapTilePosition(dpEnt);
+ var gridAtmosComp = entMan.GetComponent(grid);
+
+ direction = (AtmosDirection)(1 << i);
+ var offsetIndices = indices.Offset(direction);
+ tile = gridAtmosComp.Tiles[offsetIndices];
+ Assert.That(tile.Air, Is.Not.Null, $"Tile at {offsetIndices} should have air!");
+
+ // Above absolute threshold but below delta threshold to ensure absolute alone causes damage
+ var toPressurize = dpEnt.Comp!.MinPressure + 10;
+ var moles = (toPressurize * tile.Air.Volume) / (Atmospherics.R * Atmospherics.T20C);
+ tile.Air!.AdjustMoles(Gas.Nitrogen, moles);
+ });
+
+ await server.WaitRunTicks(30);
+
+ await server.WaitAssertion(() =>
+ {
+ Assert.That(entMan.Deleted(dpEnt), $"{dpEnt} still exists after experiencing threshold absolute pressure from {direction} side!");
+ tile.Air!.Clear();
+ });
+
+ await server.WaitRunTicks(30);
+ }
+
+ await pair.CleanReturnAsync();
+ }
+}
diff --git a/Content.IntegrationTests/Tests/CargoTest.cs b/Content.IntegrationTests/Tests/CargoTest.cs
index fd0cedac73e..80d2cdb3b83 100644
--- a/Content.IntegrationTests/Tests/CargoTest.cs
+++ b/Content.IntegrationTests/Tests/CargoTest.cs
@@ -6,6 +6,7 @@ using Content.Server.Cargo.Systems;
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Shared.Cargo.Prototypes;
+using Content.Shared.Mobs.Components;
using Content.Shared.Prototypes;
using Content.Shared.Stacks;
using Content.Shared.Whitelist;
@@ -254,4 +255,25 @@ public sealed class CargoTest
await pair.CleanReturnAsync();
}
+
+ [Test]
+ public async Task MobPrice()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+
+ var componentFactory = pair.Server.ResolveDependency();
+
+ await pair.Server.WaitAssertion(() =>
+ {
+ Assert.Multiple(() =>
+ {
+ foreach (var (proto, comp) in pair.GetPrototypesWithComponent())
+ {
+ Assert.That(proto.TryGetComponent(out _, componentFactory), $"Found MobPriceComponent on {proto.ID}, but no MobStateComponent!");
+ }
+ });
+ });
+
+ await pair.CleanReturnAsync();
+ }
}
diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/EdgeClobbering.cs b/Content.IntegrationTests/Tests/Construction/Interaction/EdgeClobbering.cs
new file mode 100644
index 00000000000..9f578148cfb
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Construction/Interaction/EdgeClobbering.cs
@@ -0,0 +1,49 @@
+using Content.IntegrationTests.Tests.Interaction;
+using Content.Server.Construction.Components;
+using Content.Shared.Temperature;
+
+namespace Content.IntegrationTests.Tests.Construction.Interaction;
+
+public sealed class EdgeClobbering : InteractionTest
+{
+ [TestPrototypes]
+ private const string Prototypes = @"
+- type: constructionGraph
+ id: ExampleGraph
+ start: A
+ graph:
+ - node: A
+ edges:
+ - to: B
+ steps:
+ - tool: Anchoring
+ doAfter: 1
+ - to: C
+ steps:
+ - tool: Screwing
+ doAfter: 1
+ - node: B
+ - node: C
+
+- type: entity
+ id: ExampleEntity
+ components:
+ - type: Construction
+ graph: ExampleGraph
+ node: A
+
+ ";
+
+ [Test]
+ public async Task EnsureNoEdgeClobbering()
+ {
+ await SpawnTarget("ExampleEntity");
+ var sTarget = SEntMan.GetEntity(Target!.Value);
+
+ await InteractUsing(Screw, false);
+ SEntMan.EventBus.RaiseLocalEvent(sTarget, new OnTemperatureChangeEvent(0f, 0f, 0f));
+ await AwaitDoAfters();
+
+ Assert.That(SEntMan.GetComponent(sTarget).Node, Is.EqualTo("C"));
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Damageable/StaminaComponentTest.cs b/Content.IntegrationTests/Tests/Damageable/StaminaComponentTest.cs
new file mode 100644
index 00000000000..f0a594dbd46
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Damageable/StaminaComponentTest.cs
@@ -0,0 +1,29 @@
+using Content.Shared.Damage.Components;
+
+namespace Content.IntegrationTests.Tests.Damageable;
+
+public sealed class StaminaComponentTest
+{
+ [Test]
+ public async Task ValidatePrototypes()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var protos = pair.GetPrototypesWithComponent();
+
+ await server.WaitAssertion(() =>
+ {
+ Assert.Multiple(() =>
+ {
+ foreach (var (proto, comp) in protos)
+ {
+ Assert.That(comp.AnimationThreshold, Is.LessThan(comp.CritThreshold),
+ $"Animation threshold on {proto.ID} must be less than its crit threshold.");
+ }
+ });
+ });
+
+ await pair.CleanReturnAsync();
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Disposal/DisposalUnitInteractionTest.cs b/Content.IntegrationTests/Tests/Disposal/DisposalUnitInteractionTest.cs
new file mode 100644
index 00000000000..d4b20a55a86
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Disposal/DisposalUnitInteractionTest.cs
@@ -0,0 +1,54 @@
+using Content.IntegrationTests.Tests.Interaction;
+using Content.Server.Containers;
+using Robust.Shared.Containers;
+using Robust.Shared.Prototypes;
+
+namespace Content.IntegrationTests.Tests.Disposal;
+
+public sealed class DisposalUnitInteractionTest : InteractionTest
+{
+ private static readonly EntProtoId DisposalUnit = "DisposalUnit";
+ private static readonly EntProtoId TrashItem = "BrokenBottle";
+
+ private const string TestDisposalUnitId = "TestDisposalUnit";
+
+ [TestPrototypes]
+ private static readonly string TestPrototypes = $@"
+# A modified disposal unit with a 100% chance of a thrown item being inserted
+- type: entity
+ parent: {DisposalUnit.Id}
+ id: {TestDisposalUnitId}
+ components:
+ - type: ThrowInsertContainer
+ probability: 1
+";
+
+ ///
+ /// Spawns a disposal unit, gives the player a trash item, and makes the
+ /// player throw the item at the disposal unit.
+ /// After a short delay, verifies that the thrown item is contained inside
+ /// the disposal unit.
+ ///
+ [Test]
+ public async Task ThrowItemIntoDisposalUnitTest()
+ {
+ var containerSys = Server.System();
+
+ // Spawn the target disposal unit
+ var disposalUnit = await SpawnTarget(TestDisposalUnitId);
+
+ // Give the player some trash to throw
+ var trash = await PlaceInHands(TrashItem);
+
+ // Throw the item at the disposal unit
+ await ThrowItem();
+
+ // Wait a moment
+ await RunTicks(10);
+
+ // Make sure the trash is in the disposal unit
+ var throwInsertComp = Comp();
+ var container = containerSys.GetContainer(ToServer(disposalUnit), throwInsertComp.ContainerId);
+ Assert.That(container.ContainedEntities, Contains.Item(ToServer(trash)));
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs
index e47c73611a4..69fe66039b8 100644
--- a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs
+++ b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs
@@ -21,6 +21,7 @@ namespace Content.IntegrationTests.Tests.Doors
components:
- type: Physics
bodyType: Dynamic
+ - type: GravityAffected
- type: Fixtures
fixtures:
fix1:
diff --git a/Content.IntegrationTests/Tests/Engineering/InflatablesDeflateTest.cs b/Content.IntegrationTests/Tests/Engineering/InflatablesDeflateTest.cs
new file mode 100644
index 00000000000..a7203d9259d
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Engineering/InflatablesDeflateTest.cs
@@ -0,0 +1,20 @@
+using Content.IntegrationTests.Tests.Interaction;
+using Content.Shared.Engineering.Systems;
+
+namespace Content.IntegrationTests.Tests.Engineering;
+
+[TestFixture]
+[TestOf(typeof(InflatableSafeDisassemblySystem))]
+public sealed class InflatablesDeflateTest : InteractionTest
+{
+ [Test]
+ public async Task Test()
+ {
+ await SpawnTarget(InflatableWall);
+
+ await InteractUsing(Needle);
+
+ AssertDeleted();
+ await AssertEntityLookup(new EntitySpecifier(InflatableWallStack.Id, 1));
+ }
+}
diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs
index 2570e2246a6..dae3203f9ff 100644
--- a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs
+++ b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs
@@ -1,11 +1,9 @@
#nullable enable
using Content.Server.Cuffs;
-using Content.Shared.Body.Components;
using Content.Shared.Cuffs.Components;
using Content.Shared.Hands.Components;
using Robust.Server.Console;
using Robust.Shared.GameObjects;
-using Robust.Shared.Map;
namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
{
@@ -22,9 +20,15 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
components:
- type: Cuffable
- type: Hands
+ hands:
+ hand_right:
+ location: Right
+ hand_left:
+ location: Left
+ sortedHands:
+ - hand_right
+ - hand_left
- type: ComplexInteraction
- - type: Body
- prototype: Human
- type: entity
name: HandcuffsDummy
@@ -47,7 +51,6 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
HandsComponent hands = default!;
var entityManager = server.ResolveDependency();
- var mapManager = server.ResolveDependency();
var host = server.ResolveDependency();
var map = await pair.CreateTestMap();
@@ -73,7 +76,6 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
{
Assert.That(entityManager.TryGetComponent(human, out cuffed!), $"Human has no {nameof(CuffableComponent)}");
Assert.That(entityManager.TryGetComponent(human, out hands!), $"Human has no {nameof(HandsComponent)}");
- Assert.That(entityManager.TryGetComponent(human, out BodyComponent? _), $"Human has no {nameof(BodyComponent)}");
Assert.That(entityManager.TryGetComponent(cuffs, out HandcuffComponent? _), $"Handcuff has no {nameof(HandcuffComponent)}");
Assert.That(entityManager.TryGetComponent(secondCuffs, out HandcuffComponent? _), $"Second handcuffs has no {nameof(HandcuffComponent)}");
});
diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs
index 1661266ba5d..cbf7237f33b 100644
--- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs
+++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs
@@ -9,7 +9,6 @@ using Content.Server.Mind;
using Content.Server.Roles;
using Content.Server.RoundEnd;
using Content.Server.Shuttles.Components;
-using Content.Server.Station.Components;
using Content.Shared.CCVar;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
@@ -20,6 +19,7 @@ using Content.Shared.NPC.Prototypes;
using Content.Shared.NPC.Systems;
using Content.Shared.NukeOps;
using Content.Shared.Pinpointer;
+using Content.Shared.Roles.Components;
using Content.Shared.Station.Components;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
diff --git a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs
index 6aa2763888d..0951e7e2608 100644
--- a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs
+++ b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs
@@ -19,6 +19,7 @@ namespace Content.IntegrationTests.Tests.Gravity
- type: Alerts
- type: Physics
bodyType: Dynamic
+ - type: GravityAffected
- type: entity
name: WeightlessGravityGeneratorDummy
diff --git a/Content.IntegrationTests/Tests/GravityGridTest.cs b/Content.IntegrationTests/Tests/GravityGridTest.cs
index b32d6c2b8d8..8257035de66 100644
--- a/Content.IntegrationTests/Tests/GravityGridTest.cs
+++ b/Content.IntegrationTests/Tests/GravityGridTest.cs
@@ -76,8 +76,8 @@ namespace Content.IntegrationTests.Tests
Assert.Multiple(() =>
{
Assert.That(generatorComponent.GravityActive, Is.True);
- Assert.That(!entityMan.GetComponent(grid1).EnabledVV);
- Assert.That(entityMan.GetComponent(grid2).EnabledVV);
+ Assert.That(!entityMan.GetComponent(grid1).Enabled);
+ Assert.That(entityMan.GetComponent(grid2).Enabled);
});
// Re-enable needs power so it turns off again.
@@ -94,7 +94,7 @@ namespace Content.IntegrationTests.Tests
Assert.Multiple(() =>
{
Assert.That(generatorComponent.GravityActive, Is.False);
- Assert.That(entityMan.GetComponent(grid2).EnabledVV, Is.False);
+ Assert.That(entityMan.GetComponent(grid2).Enabled, Is.False);
});
});
diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs
index 5db5d91d0d4..8917ba7ead5 100644
--- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs
+++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs
@@ -1,3 +1,6 @@
+using Content.Shared.Stacks;
+using Robust.Shared.Prototypes;
+
namespace Content.IntegrationTests.Tests.Interaction;
// This partial class contains various constant prototype IDs common to interaction tests.
@@ -32,4 +35,9 @@ public abstract partial class InteractionTest
protected const string Manipulator1 = "MicroManipulatorStockPart";
protected const string Battery1 = "PowerCellSmall";
protected const string Battery4 = "PowerCellHyper";
+
+ // Inflatables & Needle used to pop them
+ protected static readonly EntProtoId InflatableWall = "InflatableWall";
+ protected static readonly EntProtoId Needle = "WeaponMeleeNeedle";
+ protected static readonly ProtoId InflatableWallStack = "InflatableWall";
}
diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
index 79756ea5b41..0ed42d34764 100644
--- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
+++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
@@ -144,6 +144,7 @@ public abstract partial class InteractionTest
- type: Stripping
- type: Puller
- type: Physics
+ - type: GravityAffected
- type: Tag
tags:
- CanPilot
diff --git a/Content.IntegrationTests/Tests/InventoryHelpersTest.cs b/Content.IntegrationTests/Tests/InventoryHelpersTest.cs
index 4a07177f77a..39761ad0898 100644
--- a/Content.IntegrationTests/Tests/InventoryHelpersTest.cs
+++ b/Content.IntegrationTests/Tests/InventoryHelpersTest.cs
@@ -1,4 +1,4 @@
-using Content.Server.Stunnable;
+using Content.Server.Stunnable;
using Content.Shared.Inventory;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -17,9 +17,7 @@ namespace Content.IntegrationTests.Tests
components:
- type: Inventory
- type: ContainerContainer
- - type: StatusEffects
- allowed:
- - Stun
+ - type: MobState
- type: entity
name: InventoryJumpsuitJanitorDummy
@@ -70,7 +68,7 @@ namespace Content.IntegrationTests.Tests
});
#pragma warning restore NUnit2045
- systemMan.GetEntitySystem().TryStun(human, TimeSpan.FromSeconds(1f), true);
+ systemMan.GetEntitySystem().TryUpdateStunDuration(human, TimeSpan.FromSeconds(1f));
#pragma warning disable NUnit2045
// Since the mob is stunned, they can't equip this.
diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.cs b/Content.IntegrationTests/Tests/Minds/MindTests.cs
index 48e11e46480..2f775198293 100644
--- a/Content.IntegrationTests/Tests/Minds/MindTests.cs
+++ b/Content.IntegrationTests/Tests/Minds/MindTests.cs
@@ -3,7 +3,6 @@ using System.Linq;
using Content.Server.Ghost.Roles;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Mind;
-using Content.Server.Roles;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
@@ -11,7 +10,7 @@ using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Players;
using Content.Shared.Roles;
-using Content.Shared.Roles.Jobs;
+using Content.Shared.Roles.Components;
using Robust.Server.Console;
using Robust.Server.GameObjects;
using Robust.Server.Player;
diff --git a/Content.IntegrationTests/Tests/Minds/RoleTests.cs b/Content.IntegrationTests/Tests/Minds/RoleTests.cs
index 8acfff3fb96..f0a7268a3d7 100644
--- a/Content.IntegrationTests/Tests/Minds/RoleTests.cs
+++ b/Content.IntegrationTests/Tests/Minds/RoleTests.cs
@@ -1,7 +1,5 @@
using System.Linq;
-using Content.Server.Roles;
-using Content.Shared.Roles;
-using Content.Shared.Roles.Jobs;
+using Content.Shared.Roles.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Reflection;
diff --git a/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs b/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs
index 4db79373d38..f5a15295fd6 100644
--- a/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs
+++ b/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs
@@ -1,6 +1,6 @@
using Content.IntegrationTests.Tests.Interaction;
-using Content.Server.Explosion.Components;
-using Content.Shared.Explosion.Components;
+using Content.Shared.Trigger.Components;
+using Content.Shared.Trigger.Systems;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
@@ -25,19 +25,19 @@ public sealed class ModularGrenadeTests : InteractionTest
await InteractUsing(Cable);
// Insert & remove trigger
- AssertComp(false);
+ AssertComp(false);
await InteractUsing(Trigger);
- AssertComp();
+ AssertComp();
await FindEntity(Trigger, LookupFlags.Uncontained, shouldSucceed: false);
await InteractUsing(Pry);
- AssertComp(false);
+ AssertComp(false);
// Trigger was dropped to floor, not deleted.
await FindEntity(Trigger, LookupFlags.Uncontained);
// Re-insert
await InteractUsing(Trigger);
- AssertComp();
+ AssertComp();
// Insert & remove payload.
await InteractUsing(Payload);
@@ -56,13 +56,14 @@ public sealed class ModularGrenadeTests : InteractionTest
await Pickup();
AssertComp(false);
await UseInHand();
+ AssertComp(true);
// So uhhh grenades in hands don't destroy themselves when exploding. Maybe that will be fixed eventually.
await Drop();
// Wait until grenade explodes
- var timer = Comp();
- while (timer.TimeRemaining >= 0)
+ var triggerSys = SEntMan.System();
+ while (Target != null && triggerSys.GetRemainingTime(SEntMan.GetEntity(Target.Value))?.TotalSeconds >= 0.0)
{
await RunTicks(10);
}
diff --git a/Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs b/Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs
index 7e73047ab4f..cffdb1945d7 100644
--- a/Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs
+++ b/Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs
@@ -2,9 +2,9 @@ using System.Linq;
using Content.Server.GameTicking;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Systems;
-using Content.Server.Station.Components;
using Content.Shared.CCVar;
using Content.Shared.Shuttles.Components;
+using Content.Shared.Station.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
diff --git a/Content.IntegrationTests/Tests/StoreTests.cs b/Content.IntegrationTests/Tests/StoreTests.cs
index 1f7d357bc05..cbbd163bec9 100644
--- a/Content.IntegrationTests/Tests/StoreTests.cs
+++ b/Content.IntegrationTests/Tests/StoreTests.cs
@@ -122,7 +122,7 @@ public sealed class StoreTests
var buyMsg = new StoreBuyListingMessage(discountedListingItem.ID){Actor = human};
- server.EntMan.EventBus.RaiseComponentEvent(pda, storeComponent, buyMsg);
+ server.EntMan.EventBus.RaiseLocalEvent(pda, buyMsg);
var newBalance = storeComponent.Balance[UplinkSystem.TelecrystalCurrencyPrototype];
Assert.That(newBalance.Value, Is.EqualTo((originalBalance - plainDiscountedCost).Value), "Expected to have balance reduced by discounted cost");
@@ -135,7 +135,7 @@ public sealed class StoreTests
Assert.That(costAfterBuy.Value, Is.EqualTo(prototypeCost.Value), "Expected cost after discount refund to be equal to prototype cost.");
var refundMsg = new StoreRequestRefundMessage { Actor = human };
- server.EntMan.EventBus.RaiseComponentEvent(pda, storeComponent, refundMsg);
+ server.EntMan.EventBus.RaiseLocalEvent(pda, refundMsg);
// get refreshed item after refund re-generated items
discountedListingItem = storeComponent.FullListingsCatalog.First(x => x.ID == itemId);
diff --git a/Content.Server.Database/Migrations/Postgres/20250723055137_AdminLogsCurtime.Designer.cs b/Content.Server.Database/Migrations/Postgres/20250723055137_AdminLogsCurtime.Designer.cs
new file mode 100644
index 00000000000..ea06967c890
--- /dev/null
+++ b/Content.Server.Database/Migrations/Postgres/20250723055137_AdminLogsCurtime.Designer.cs
@@ -0,0 +1,2125 @@
+//
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text.Json;
+using Content.Server.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using NpgsqlTypes;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Postgres
+{
+ [DbContext(typeof(PostgresServerDbContext))]
+ [Migration("20250723055137_AdminLogsCurtime")]
+ partial class AdminLogsCurtime
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.1")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Property("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property("Deadminned")
+ .HasColumnType("boolean")
+ .HasColumnName("deadminned");
+
+ b.Property("Suspended")
+ .HasColumnType("boolean")
+ .HasColumnName("suspended");
+
+ b.Property("Title")
+ .HasColumnType("text")
+ .HasColumnName("title");
+
+ b.HasKey("UserId")
+ .HasName("PK_admin");
+
+ b.HasIndex("AdminRankId")
+ .HasDatabaseName("IX_admin_admin_rank_id");
+
+ b.ToTable("admin", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AdminId")
+ .HasColumnType("uuid")
+ .HasColumnName("admin_id");
+
+ b.Property("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.Property("Negative")
+ .HasColumnType("boolean")
+ .HasColumnName("negative");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_flag");
+
+ b.HasIndex("AdminId")
+ .HasDatabaseName("IX_admin_flag_admin_id");
+
+ b.HasIndex("Flag", "AdminId")
+ .IsUnique();
+
+ b.ToTable("admin_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property("Id")
+ .HasColumnType("integer")
+ .HasColumnName("admin_log_id");
+
+ b.Property("CurTime")
+ .HasColumnType("bigint")
+ .HasColumnName("cur_time");
+
+ b.Property("Date")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("date");
+
+ b.Property("Impact")
+ .HasColumnType("smallint")
+ .HasColumnName("impact");
+
+ b.Property("Json")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("json");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("message");
+
+ b.Property("Type")
+ .HasColumnType("integer")
+ .HasColumnName("type");
+
+ b.HasKey("RoundId", "Id")
+ .HasName("PK_admin_log");
+
+ b.HasIndex("Date");
+
+ b.HasIndex("Message")
+ .HasAnnotation("Npgsql:TsVectorConfig", "english");
+
+ NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_admin_log_type");
+
+ b.ToTable("admin_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property("LogId")
+ .HasColumnType("integer")
+ .HasColumnName("log_id");
+
+ b.Property("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.HasKey("RoundId", "LogId", "PlayerUserId")
+ .HasName("PK_admin_log_player");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_log_player_player_user_id");
+
+ b.ToTable("admin_log_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_messages_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property("Dismissed")
+ .HasColumnType("boolean")
+ .HasColumnName("dismissed");
+
+ b.Property("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property("Seen")
+ .HasColumnType("boolean")
+ .HasColumnName("seen");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_messages");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_messages_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_messages_round_id");
+
+ b.ToTable("admin_messages", null, t =>
+ {
+ t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_notes_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property("Secret")
+ .HasColumnType("boolean")
+ .HasColumnName("secret");
+
+ b.Property("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_notes");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_notes_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_notes_round_id");
+
+ b.ToTable("admin_notes", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank");
+
+ b.ToTable("admin_rank", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank_flag");
+
+ b.HasIndex("AdminRankId");
+
+ b.HasIndex("Flag", "AdminRankId")
+ .IsUnique();
+
+ b.ToTable("admin_rank_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_watchlists_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property