diff --git a/.editorconfig b/.editorconfig
index 59ca35cc9a..872a068c7c 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -338,7 +338,10 @@ dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter
resharper_braces_for_ifelse = required_for_multiline
resharper_keep_existing_attribute_arrangement = true
-[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets}]
+[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}]
+indent_size = 2
+
+[nuget.config]
indent_size = 2
[{*.yaml,*.yml}]
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 1ff4c49d90..4ba70f3504 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Install dependencies
- run: sudo apt-get install -y python3-paramiko
+ run: sudo apt-get install -y python3-paramiko python3-lxml
- uses: actions/checkout@v3.6.0
with:
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 4d7ba6748e..5390b91409 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -13,6 +13,15 @@
"console": "internalConsole",
"stopAtEntry": false
},
+ {
+ "name": "Client (Compatibility renderer)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "${workspaceFolder}/bin/Content.Client/Content.Client.dll",
+ "args": "--cvar display.compat=true",
+ "console": "internalConsole",
+ "stopAtEntry": false
+ },
{
"name": "Server",
"type": "coreclr",
diff --git a/Content.Benchmarks/Content.Benchmarks.csproj b/Content.Benchmarks/Content.Benchmarks.csproj
index 049d6f5b6f..c3b60a1c69 100644
--- a/Content.Benchmarks/Content.Benchmarks.csproj
+++ b/Content.Benchmarks/Content.Benchmarks.csproj
@@ -11,7 +11,7 @@
12
-
+
diff --git a/Content.Benchmarks/EntityManagerGetAllComponents.cs b/Content.Benchmarks/EntityManagerGetAllComponents.cs
index 14edf54643..0b9683a4ab 100644
--- a/Content.Benchmarks/EntityManagerGetAllComponents.cs
+++ b/Content.Benchmarks/EntityManagerGetAllComponents.cs
@@ -48,6 +48,7 @@ public void Setup()
var componentFactory = new Mock();
componentFactory.Setup(p => p.GetComponent()).Returns(new DummyComponent());
componentFactory.Setup(p => p.GetRegistration(It.IsAny())).Returns(dummyReg);
+ componentFactory.Setup(p => p.GetAllRegistrations()).Returns(new[] { dummyReg });
componentFactory.Setup(p => p.GetAllRefTypes()).Returns(new[] { CompIdx.Index() });
IoCManager.RegisterInstance(componentFactory.Object);
diff --git a/Content.Benchmarks/MapLoadBenchmark.cs b/Content.Benchmarks/MapLoadBenchmark.cs
index 5d94ef85cb..7caa995836 100644
--- a/Content.Benchmarks/MapLoadBenchmark.cs
+++ b/Content.Benchmarks/MapLoadBenchmark.cs
@@ -46,7 +46,7 @@ public async Task Cleanup()
PoolManager.Shutdown();
}
- public static IEnumerable MapsSource { get; set; }
+ 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" };
[ParamsSource(nameof(MapsSource))]
public string Map;
diff --git a/Content.Benchmarks/Program.cs b/Content.Benchmarks/Program.cs
index 65b5abaf73..0beb0a613d 100644
--- a/Content.Benchmarks/Program.cs
+++ b/Content.Benchmarks/Program.cs
@@ -23,13 +23,6 @@ public static void Main(string[] args)
public static async Task MainAsync(string[] args)
{
- PoolManager.Startup(typeof(Program).Assembly);
- var pair = await PoolManager.GetServerClient();
- var gameMaps = pair.Server.ResolveDependency().EnumeratePrototypes().ToList();
- MapLoadBenchmark.MapsSource = gameMaps.Select(x => x.ID);
- await pair.CleanReturnAsync();
- PoolManager.Shutdown();
-
#if DEBUG
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("\nWARNING: YOU ARE RUNNING A DEBUG BUILD, USE A RELEASE BUILD FOR AN ACCURATE BENCHMARK");
diff --git a/Content.Benchmarks/PvsBenchmark.cs b/Content.Benchmarks/PvsBenchmark.cs
new file mode 100644
index 0000000000..c7f22bdb0c
--- /dev/null
+++ b/Content.Benchmarks/PvsBenchmark.cs
@@ -0,0 +1,187 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using BenchmarkDotNet.Attributes;
+using Content.IntegrationTests;
+using Content.IntegrationTests.Pair;
+using Content.Server.Warps;
+using Robust.Server.GameObjects;
+using Robust.Shared;
+using Robust.Shared.Analyzers;
+using Robust.Shared.Enums;
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameStates;
+using Robust.Shared.Map;
+using Robust.Shared.Network;
+using Robust.Shared.Player;
+using Robust.Shared.Random;
+
+namespace Content.Benchmarks;
+
+// This benchmark probably benefits from some accidental cache locality. I,e. the order in which entities in a pvs
+// chunk are sent to players matches the order in which the entities were spawned.
+//
+// in a real mid-late game round, this is probably no longer the case.
+// One way to somewhat offset this is to update the NetEntity assignment to assign random (but still unique) NetEntity uids to entities.
+// This makes the benchmark run noticeably slower.
+
+[Virtual]
+public class PvsBenchmark
+{
+ public const string Map = "Maps/box.yml";
+
+ [Params(1, 8, 80)]
+ public int PlayerCount { get; set; }
+
+ private TestPair _pair = default!;
+ private IEntityManager _entMan = default!;
+ private MapId _mapId = new(10);
+ private ICommonSession[] _players = default!;
+ private EntityCoordinates[] _spawns = default!;
+ public int _cycleOffset = 0;
+ private SharedTransformSystem _sys = default!;
+ private EntityCoordinates[] _locations = default!;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+#if !DEBUG
+ ProgramShared.PathOffset = "../../../../";
+#endif
+ PoolManager.Startup(null);
+
+ _pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
+ _entMan = _pair.Server.ResolveDependency();
+ _pair.Server.CfgMan.SetCVar(CVars.NetPVS, true);
+ _pair.Server.CfgMan.SetCVar(CVars.ThreadParallelCount, 0);
+ _pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false);
+ _sys = _entMan.System();
+
+ // Spawn the map
+ _pair.Server.ResolveDependency().SetSeed(42);
+ _pair.Server.WaitPost(() =>
+ {
+ var success = _entMan.System().TryLoad(_mapId, Map, out _);
+ if (!success)
+ throw new Exception("Map load failed");
+ _pair.Server.MapMan.DoMapInitialize(_mapId);
+ }).Wait();
+
+ // Get list of ghost warp positions
+ _spawns = _entMan.AllComponentsList()
+ .OrderBy(x => x.Component.Location)
+ .Select(x => _entMan.GetComponent(x.Uid).Coordinates)
+ .ToArray();
+
+ Array.Resize(ref _players, PlayerCount);
+
+ // Spawn "Players".
+ _pair.Server.WaitPost(() =>
+ {
+ for (var i = 0; i < PlayerCount; i++)
+ {
+ var pos = _spawns[i % _spawns.Length];
+ var uid =_entMan.SpawnEntity("MobHuman", pos);
+ _pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear");
+ _players[i] = new DummySession{AttachedEntity = uid};
+ }
+ }).Wait();
+
+ // Repeatedly move players around so that they "explore" the map and see lots of entities.
+ // This will populate their PVS data with out-of-view entities.
+ var rng = new Random(42);
+ ShufflePlayers(rng, 100);
+
+ _pair.Server.PvsTick(_players);
+ _pair.Server.PvsTick(_players);
+
+ var ents = _players.Select(x => x.AttachedEntity!.Value).ToArray();
+ _locations = ents.Select(x => _entMan.GetComponent(x).Coordinates).ToArray();
+ }
+
+ private void ShufflePlayers(Random rng, int count)
+ {
+ while (count > 0)
+ {
+ ShufflePlayers(rng);
+ count--;
+ }
+ }
+
+ private void ShufflePlayers(Random rng)
+ {
+ _pair.Server.PvsTick(_players);
+
+ var ents = _players.Select(x => x.AttachedEntity!.Value).ToArray();
+ var locations = ents.Select(x => _entMan.GetComponent(x).Coordinates).ToArray();
+
+ // Shuffle locations
+ var n = locations.Length;
+ while (n > 1)
+ {
+ n -= 1;
+ var k = rng.Next(n + 1);
+ (locations[k], locations[n]) = (locations[n], locations[k]);
+ }
+
+ _pair.Server.WaitPost(() =>
+ {
+ for (var i = 0; i < PlayerCount; i++)
+ {
+ _sys.SetCoordinates(ents[i], locations[i]);
+ }
+ }).Wait();
+
+ _pair.Server.PvsTick(_players);
+ }
+
+ ///
+ /// Basic benchmark for PVS in a static situation where nothing moves or gets dirtied..
+ /// This effectively provides a lower bound on "real" pvs tick time, as it is missing:
+ /// - PVS chunks getting dirtied and needing to be rebuilt
+ /// - Fetching component states for dirty components
+ /// - Compressing & sending network messages
+ /// - Sending PVS leave messages
+ ///
+ [Benchmark]
+ public void StaticTick()
+ {
+ _pair.Server.PvsTick(_players);
+ }
+
+ ///
+ /// Basic benchmark for PVS in a situation where players are teleporting all over the place. This isn't very
+ /// realistic, but unlike this will actually also measure the speed of processing dirty
+ /// chunks and sending PVS leave messages.
+ ///
+ [Benchmark]
+ public void CycleTick()
+ {
+ _cycleOffset = (_cycleOffset + 1) % _players.Length;
+ _pair.Server.WaitPost(() =>
+ {
+ for (var i = 0; i < PlayerCount; i++)
+ {
+ _sys.SetCoordinates(_players[i].AttachedEntity!.Value, _locations[(i + _cycleOffset) % _players.Length]);
+ }
+ }).Wait();
+ _pair.Server.PvsTick(_players);
+ }
+
+ private sealed class DummySession : ICommonSession
+ {
+ public SessionStatus Status => SessionStatus.InGame;
+ public EntityUid? AttachedEntity {get; set; }
+ public NetUserId UserId => default;
+ public string Name => string.Empty;
+ public short Ping => default;
+ public INetChannel Channel { get; set; } = default!;
+ public LoginType AuthType => default;
+ public HashSet ViewSubscriptions { get; } = new();
+ public DateTime ConnectedTime { get; set; }
+ public SessionState State => default!;
+ public SessionData Data => default!;
+ public bool ClientSide { get; set; }
+ }
+}
diff --git a/Content.Client/Administration/QuickDialogSystem.cs b/Content.Client/Administration/QuickDialogSystem.cs
index 69a9218267..84236c5a3e 100644
--- a/Content.Client/Administration/QuickDialogSystem.cs
+++ b/Content.Client/Administration/QuickDialogSystem.cs
@@ -6,6 +6,8 @@
namespace Content.Client.Administration;
+// mfw they ported input() from BYOND
+
///
/// This handles the client portion of quick dialogs.
///
@@ -32,39 +34,48 @@ private void OpenDialog(QuickDialogOpenEvent ev)
var promptsDict = new Dictionary();
- foreach (var entry in ev.Prompts)
+ for (var index = 0; index < ev.Prompts.Count; index++)
{
+ var entry = ev.Prompts[index];
var entryBox = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal
};
entryBox.AddChild(new Label { Text = entry.Prompt, HorizontalExpand = true, SizeFlagsStretchRatio = 0.5f });
- var edit = new LineEdit() { HorizontalExpand = true};
+ var edit = new LineEdit() { HorizontalExpand = true };
entryBox.AddChild(edit);
switch (entry.Type)
{
case QuickDialogEntryType.Integer:
edit.IsValid += VerifyInt;
- edit.PlaceHolder = "Integer..";
+ edit.PlaceHolder = Loc.GetString("quick-dialog-ui-integer");
break;
case QuickDialogEntryType.Float:
edit.IsValid += VerifyFloat;
- edit.PlaceHolder = "Float..";
+ edit.PlaceHolder = Loc.GetString("quick-dialog-ui-float");
break;
case QuickDialogEntryType.ShortText:
edit.IsValid += VerifyShortText;
- edit.PlaceHolder = "Short text..";
+ edit.PlaceHolder = Loc.GetString("quick-dialog-ui-short-text");
break;
case QuickDialogEntryType.LongText:
edit.IsValid += VerifyLongText;
- edit.PlaceHolder = "Long text..";
+ edit.PlaceHolder = Loc.GetString("quick-dialog-ui-long-text");
break;
default:
throw new ArgumentOutOfRangeException();
}
+
promptsDict.Add(entry.FieldId, edit);
entryContainer.AddChild(entryBox);
+
+ if (index == ev.Prompts.Count - 1)
+ {
+ // Last text box gets enter confirmation.
+ // Only the last so you don't accidentally confirm early.
+ edit.OnTextEntered += _ => Confirm();
+ }
}
var buttonsBox = new BoxContainer()
@@ -79,17 +90,10 @@ private void OpenDialog(QuickDialogOpenEvent ev)
{
var okButton = new Button()
{
- Text = "Ok",
+ Text = Loc.GetString("quick-dialog-ui-ok"),
};
- okButton.OnPressed += _ =>
- {
- RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId,
- promptsDict.Select(x => (x.Key, x.Value.Text)).ToDictionary(x => x.Key, x => x.Text),
- QuickDialogButtonFlag.OkButton));
- alreadyReplied = true;
- window.Close();
- };
+ okButton.OnPressed += _ => Confirm();
buttonsBox.AddChild(okButton);
}
@@ -98,7 +102,7 @@ private void OpenDialog(QuickDialogOpenEvent ev)
{
var cancelButton = new Button()
{
- Text = "Cancel",
+ Text = Loc.GetString("quick-dialog-ui-cancel"),
};
cancelButton.OnPressed += _ =>
@@ -130,6 +134,17 @@ private void OpenDialog(QuickDialogOpenEvent ev)
window.MinWidth *= 2; // Just double it.
window.OpenCentered();
+
+ return;
+
+ void Confirm()
+ {
+ RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId,
+ promptsDict.Select(x => (x.Key, x.Value.Text)).ToDictionary(x => x.Key, x => x.Text),
+ QuickDialogButtonFlag.OkButton));
+ alreadyReplied = true;
+ window.Close();
+ }
}
private bool VerifyInt(string input)
diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml
index 28b55f987f..e5269c027a 100644
--- a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml
+++ b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml
@@ -14,7 +14,7 @@
-
+
diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs
index 5d760d9ab8..af977f763c 100644
--- a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs
+++ b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs
@@ -4,6 +4,7 @@
using Content.Client.Administration.UI.CustomControls;
using Content.Client.UserInterface.Systems.Bwoink;
using Content.Shared.Administration;
+using Content.Shared.CCVar;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface;
@@ -11,6 +12,8 @@
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Network;
using Robust.Shared.Utility;
+using Robust.Shared.Timing;
+using Robust.Shared.Configuration;
namespace Content.Client.Administration.UI.Bwoink
{
@@ -23,6 +26,7 @@ public sealed partial class BwoinkControl : Control
[Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
public AdminAHelpUIHandler AHelpHelper = default!;
private PlayerInfo? _currentPlayer;
@@ -71,6 +75,9 @@ public BwoinkControl()
if (info.Antag && info.ActiveThisRound)
sb.Append(new Rune(0x1F5E1)); // 🗡
+ if (info.OverallPlaytime <= TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
+ sb.Append(new Rune(0x23F2)); // ⏲
+
sb.AppendFormat("\"{0}\"", text);
return sb.ToString();
@@ -131,10 +138,10 @@ public BwoinkControl()
_console.ExecuteCommand($"kick \"{_currentPlayer.Username}\"");
};
- Teleport.OnPressed += _ =>
+ Follow.OnPressed += _ =>
{
if (_currentPlayer is not null)
- _console.ExecuteCommand($"tpto \"{_currentPlayer.Username}\"");
+ _console.ExecuteCommand($"follow \"{_currentPlayer.NetEntity}\"");
};
Respawn.OnPressed += _ =>
@@ -197,8 +204,8 @@ public void UpdateButtons()
Respawn.Visible = _adminManager.CanCommand("respawn");
Respawn.Disabled = !Respawn.Visible || disabled;
- Teleport.Visible = _adminManager.CanCommand("tpto");
- Teleport.Disabled = !Teleport.Visible || disabled;
+ Follow.Visible = _adminManager.CanCommand("follow");
+ Follow.Disabled = !Follow.Visible || disabled;
}
private string FormatTabTitle(ItemList.Item li, PlayerInfo? pl = default)
@@ -219,6 +226,9 @@ private string FormatTabTitle(ItemList.Item li, PlayerInfo? pl = default)
if (pl.Antag)
sb.Append(new Rune(0x1F5E1)); // 🗡
+ if (pl.OverallPlaytime <= TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
+ sb.Append(new Rune(0x23F2)); // ⏲
+
sb.AppendFormat("\"{0}\"", pl.CharacterName);
if (pl.IdentityName != pl.CharacterName && pl.IdentityName != string.Empty)
diff --git a/Content.Client/Administration/UI/ManageSolutions/AddReagentWindow.xaml.cs b/Content.Client/Administration/UI/ManageSolutions/AddReagentWindow.xaml.cs
index cf7c8c5058..1ded5cebb3 100644
--- a/Content.Client/Administration/UI/ManageSolutions/AddReagentWindow.xaml.cs
+++ b/Content.Client/Administration/UI/ManageSolutions/AddReagentWindow.xaml.cs
@@ -4,9 +4,6 @@
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
using Robust.Shared.Prototypes;
namespace Content.Client.Administration.UI.ManageSolutions
diff --git a/Content.Client/Administration/UI/ManageSolutions/EditSolutionsEui.cs b/Content.Client/Administration/UI/ManageSolutions/EditSolutionsEui.cs
index 4bee78fa09..625dafa23b 100644
--- a/Content.Client/Administration/UI/ManageSolutions/EditSolutionsEui.cs
+++ b/Content.Client/Administration/UI/ManageSolutions/EditSolutionsEui.cs
@@ -34,9 +34,7 @@ public override void Closed()
public override void HandleState(EuiStateBase baseState)
{
var state = (EditSolutionsEuiState) baseState;
- _window.SetTargetEntity(state.Target);
- _window.UpdateSolutions(state.Solutions);
- _window.UpdateReagents();
+ _window.SetState(state);
}
}
}
diff --git a/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml.cs b/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml.cs
index d2dda0ccbd..812f2de3a0 100644
--- a/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml.cs
+++ b/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml.cs
@@ -1,10 +1,13 @@
+using Content.Shared.Administration;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
+using Robust.Client.Timing;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Timing;
namespace Content.Client.Administration.UI.ManageSolutions
{
@@ -16,11 +19,13 @@ public sealed partial class EditSolutionsWindow : DefaultWindow
{
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IClientGameTiming _timing = default!;
private NetEntity _target = NetEntity.Invalid;
private string? _selectedSolution;
private AddReagentWindow? _addReagentWindow;
- private Dictionary? _solutions;
+ private Dictionary? _solutions;
+ private EditSolutionsEuiState? _nextState;
public EditSolutionsWindow()
{
@@ -60,9 +65,11 @@ public void UpdateReagents()
if (_selectedSolution == null || _solutions == null)
return;
- if (!_solutions.TryGetValue(_selectedSolution, out var solution))
+ if (!_solutions.TryGetValue(_selectedSolution, out var solutionId) ||
+ !_entityManager.TryGetComponent(solutionId, out SolutionComponent? solutionComp))
return;
+ var solution = solutionComp.Solution;
UpdateVolumeBox(solution);
UpdateThermalBox(solution);
@@ -198,10 +205,13 @@ private void AddReagentEntry(ReagentQuantity reagentQuantity)
///
private void SetReagent(FloatSpinBox.FloatSpinBoxEventArgs args, string prototype)
{
- if (_solutions == null || _selectedSolution == null)
+ if (_solutions == null || _selectedSolution == null ||
+ !_solutions.TryGetValue(_selectedSolution, out var solutionId) ||
+ !_entityManager.TryGetComponent(solutionId, out SolutionComponent? solutionComp))
return;
- var current = _solutions[_selectedSolution].GetTotalPrototypeQuantity(prototype);
+ var solution = solutionComp.Solution;
+ var current = solution.GetTotalPrototypeQuantity(prototype);
var delta = args.Value - current.Float();
if (MathF.Abs(delta) < 0.01)
@@ -275,22 +285,38 @@ private void SolutionSelected(OptionButton.ItemSelectedEventArgs args)
///
/// Update the solution options.
///
- public void UpdateSolutions(Dictionary? solutions)
+ public void UpdateSolutions(List<(string, NetEntity)>? solutions)
{
SolutionOption.Clear();
- _solutions = solutions;
+
+ if (solutions is { Count: > 0 })
+ {
+ if (_solutions is { Count: > 0 })
+ _solutions.Clear();
+ else
+ _solutions = new(solutions.Count);
+
+ foreach (var (name, netSolution) in solutions)
+ {
+ if (_entityManager.TryGetEntity(netSolution, out var solution))
+ _solutions.Add(name, solution.Value);
+ }
+ }
+ else
+ _solutions = null;
if (_solutions == null)
return;
int i = 0;
- foreach (var solution in _solutions.Keys)
+ int selectedIndex = 0; // Default to the first solution if none are found.
+ foreach (var (name, _) in _solutions)
{
- SolutionOption.AddItem(solution, i);
- SolutionOption.SetItemMetadata(i, solution);
+ SolutionOption.AddItem(name, i);
+ SolutionOption.SetItemMetadata(i, name);
- if (solution == _selectedSolution)
- SolutionOption.Select(i);
+ if (name == _selectedSolution)
+ selectedIndex = i;
i++;
}
@@ -300,14 +326,33 @@ public void UpdateSolutions(Dictionary? solutions)
// No applicable solutions
Close();
Dispose();
+ return;
}
- if (_selectedSolution == null || !_solutions.ContainsKey(_selectedSolution))
+ SolutionOption.Select(selectedIndex);
+ _selectedSolution = (string?) SolutionOption.SelectedMetadata;
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ // TODO: THIS IS FUCKING TERRIBLE.
+ // Ok so the problem is that this shouldn't be via an EUI at all. Why?
+ // The EUI update notification comes in *before* the game state it updates from.
+ // So the UI doesn't update properly. Heck.
+ // I didn't wanna completely rewrite this thing to work properly so instead you get terrible hacks.
+
+ if (_nextState != null && _timing.LastRealTick >= _nextState.Tick)
{
- // the previously selected solution is no longer valid.
- SolutionOption.Select(0);
- _selectedSolution = (string?) SolutionOption.SelectedMetadata;
+ SetTargetEntity(_nextState.Target);
+ UpdateSolutions(_nextState.Solutions);
+ UpdateReagents();
+ _nextState = null;
}
}
+
+ public void SetState(EditSolutionsEuiState state)
+ {
+ _nextState = state;
+ }
}
}
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
index dcb184b308..a5c3008436 100644
--- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
@@ -3,6 +3,7 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map.Components;
+using Robust.Shared.Timing;
namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
@@ -10,12 +11,20 @@ namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
public sealed partial class ObjectsTab : Control
{
[Dependency] private readonly EntityManager _entityManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
private readonly List _objects = new();
private List _selections = new();
public event Action? OnEntryKeyBindDown;
+ // Listen I could either have like 4 different event subscribers (for map / grid / station changes) and manage their lifetimes in AdminUIController
+ // OR
+ // I can do this.
+ private TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
+
+ private TimeSpan _nextUpdate = TimeSpan.FromSeconds(2);
+
public ObjectsTab()
{
RobustXamlLoader.Load(this);
@@ -33,12 +42,17 @@ public ObjectsTab()
ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!);
}
+ RefreshObjectList();
+ }
+
+ private void RefreshObjectList()
+ {
RefreshObjectList(_selections[ObjectTypeOptions.SelectedId]);
}
private void RefreshObjectList(ObjectsTabSelection selection)
{
- var entities = new List();
+ var entities = new List<(string Name, NetEntity Entity)>();
switch (selection)
{
case ObjectsTabSelection.Stations:
@@ -46,20 +60,20 @@ private void RefreshObjectList(ObjectsTabSelection selection)
break;
case ObjectsTabSelection.Grids:
{
- var query = _entityManager.AllEntityQueryEnumerator();
- while (query.MoveNext(out var uid, out _))
+ var query = _entityManager.AllEntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out _, out var metadata))
{
- entities.Add(uid);
+ entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
}
break;
}
case ObjectsTabSelection.Maps:
{
- var query = _entityManager.AllEntityQueryEnumerator();
- while (query.MoveNext(out var uid, out _))
+ var query = _entityManager.AllEntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out _, out var metadata))
{
- entities.Add(uid);
+ entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
}
break;
}
@@ -74,17 +88,28 @@ private void RefreshObjectList(ObjectsTabSelection selection)
_objects.Clear();
- foreach (var entity in entities)
+ foreach (var (name, nent) in entities)
{
- // TODO the server eitehr needs to send the entity's name, or it needs to ensure the client knows about the entity.
- var name = _entityManager.GetComponentOrNull(entity)?.EntityName ?? "Unknown Entity"; // this should be fixed, so I CBF localizing.
- var ctrl = new ObjectsTabEntry(name, entity);
+ var ctrl = new ObjectsTabEntry(name, nent);
_objects.Add(ctrl);
ObjectList.AddChild(ctrl);
ctrl.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(ctrl, args);
}
}
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ if (_timing.CurTime < _nextUpdate)
+ return;
+
+ // I do not care for precision.
+ _nextUpdate = _timing.CurTime + _updateFrequency;
+
+ RefreshObjectList();
+ }
+
private enum ObjectsTabSelection
{
Grids,
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs
index 98cfe53af1..c9b2cd8b57 100644
--- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs
@@ -7,13 +7,13 @@ namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
[GenerateTypedNameReferences]
public sealed partial class ObjectsTabEntry : ContainerButton
{
- public EntityUid AssocEntity;
+ public NetEntity AssocEntity;
- public ObjectsTabEntry(string name, EntityUid euid)
+ public ObjectsTabEntry(string name, NetEntity nent)
{
RobustXamlLoader.Load(this);
- AssocEntity = euid;
- EIDLabel.Text = euid.ToString();
+ AssocEntity = nent;
+ EIDLabel.Text = nent.ToString();
NameLabel.Text = name;
}
}
diff --git a/Content.Client/Atmos/Rotting/RottingSystem.cs b/Content.Client/Atmos/Rotting/RottingSystem.cs
new file mode 100644
index 0000000000..da030ed117
--- /dev/null
+++ b/Content.Client/Atmos/Rotting/RottingSystem.cs
@@ -0,0 +1,7 @@
+using Content.Shared.Atmos.Rotting;
+
+namespace Content.Client.Atmos.Rotting;
+
+public sealed class RottingSystem : SharedRottingSystem
+{
+}
diff --git a/Content.Client/Atmos/UI/GasFilterBoundUserInterface.cs b/Content.Client/Atmos/UI/GasFilterBoundUserInterface.cs
index 1087efd51d..1904e2b340 100644
--- a/Content.Client/Atmos/UI/GasFilterBoundUserInterface.cs
+++ b/Content.Client/Atmos/UI/GasFilterBoundUserInterface.cs
@@ -1,8 +1,8 @@
using Content.Client.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Trinary.Components;
+using Content.Shared.Localizations;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
namespace Content.Client.Atmos.UI
{
@@ -50,7 +50,7 @@ private void OnToggleStatusButtonPressed()
private void OnFilterTransferRatePressed(string value)
{
- float rate = float.TryParse(value, out var parsed) ? parsed : 0f;
+ var rate = UserInputParser.TryFloat(value, out var parsed) ? parsed : 0f;
SendMessage(new GasFilterChangeRateMessage(rate));
}
diff --git a/Content.Client/Atmos/UI/GasFilterWindow.xaml b/Content.Client/Atmos/UI/GasFilterWindow.xaml
index 6963a71d3d..861d447308 100644
--- a/Content.Client/Atmos/UI/GasFilterWindow.xaml
+++ b/Content.Client/Atmos/UI/GasFilterWindow.xaml
@@ -9,7 +9,7 @@
-
+
diff --git a/Content.Client/Atmos/UI/GasFilterWindow.xaml.cs b/Content.Client/Atmos/UI/GasFilterWindow.xaml.cs
index 74cef4b39b..28766c688a 100644
--- a/Content.Client/Atmos/UI/GasFilterWindow.xaml.cs
+++ b/Content.Client/Atmos/UI/GasFilterWindow.xaml.cs
@@ -49,7 +49,7 @@ public GasFilterWindow(IEnumerable gases)
public void SetTransferRate(float rate)
{
- FilterTransferRateInput.Text = rate.ToString(CultureInfo.InvariantCulture);
+ FilterTransferRateInput.Text = rate.ToString(CultureInfo.CurrentCulture);
}
public void SetFilterStatus(bool enabled)
diff --git a/Content.Client/Atmos/UI/GasMixerBoundUserInteface.cs b/Content.Client/Atmos/UI/GasMixerBoundUserInteface.cs
index cfec8c3495..709c06517c 100644
--- a/Content.Client/Atmos/UI/GasMixerBoundUserInteface.cs
+++ b/Content.Client/Atmos/UI/GasMixerBoundUserInteface.cs
@@ -1,5 +1,6 @@
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Trinary.Components;
+using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@@ -47,7 +48,7 @@ private void OnToggleStatusButtonPressed()
private void OnMixerOutputPressurePressed(string value)
{
- var pressure = float.TryParse(value, out var parsed) ? parsed : 0f;
+ var pressure = UserInputParser.TryFloat(value, out var parsed) ? parsed : 0f;
if (pressure > MaxPressure)
pressure = MaxPressure;
@@ -57,7 +58,7 @@ private void OnMixerOutputPressurePressed(string value)
private void OnMixerSetPercentagePressed(string value)
{
// We don't need to send both nodes because it's just 100.0f - node
- var node = float.TryParse(value, out var parsed) ? parsed : 1.0f;
+ var node = UserInputParser.TryFloat(value, out var parsed) ? parsed : 1.0f;
node = Math.Clamp(node, 0f, 100.0f);
diff --git a/Content.Client/Atmos/UI/GasMixerWindow.xaml b/Content.Client/Atmos/UI/GasMixerWindow.xaml
index 7e74348962..cebca59747 100644
--- a/Content.Client/Atmos/UI/GasMixerWindow.xaml
+++ b/Content.Client/Atmos/UI/GasMixerWindow.xaml
@@ -10,7 +10,7 @@
-
+
diff --git a/Content.Client/Atmos/UI/GasMixerWindow.xaml.cs b/Content.Client/Atmos/UI/GasMixerWindow.xaml.cs
index f3f8293bc8..8ff7b8cfa3 100644
--- a/Content.Client/Atmos/UI/GasMixerWindow.xaml.cs
+++ b/Content.Client/Atmos/UI/GasMixerWindow.xaml.cs
@@ -45,7 +45,7 @@ public GasMixerWindow()
SetMaxPressureButton.OnPressed += _ =>
{
- MixerPressureOutputInput.Text = Atmospherics.MaxOutputPressure.ToString(CultureInfo.InvariantCulture);
+ MixerPressureOutputInput.Text = Atmospherics.MaxOutputPressure.ToString(CultureInfo.CurrentCulture);
SetOutputPressureButton.Disabled = false;
};
@@ -69,16 +69,16 @@ public GasMixerWindow()
public void SetOutputPressure(float pressure)
{
- MixerPressureOutputInput.Text = pressure.ToString(CultureInfo.InvariantCulture);
+ MixerPressureOutputInput.Text = pressure.ToString(CultureInfo.CurrentCulture);
}
public void SetNodePercentages(float nodeOne)
{
nodeOne *= 100.0f;
- MixerNodeOneInput.Text = nodeOne.ToString("0.##", CultureInfo.InvariantCulture);
+ MixerNodeOneInput.Text = nodeOne.ToString("0.##", CultureInfo.CurrentCulture);
float nodeTwo = 100.0f - nodeOne;
- MixerNodeTwoInput.Text = nodeTwo.ToString("0.##", CultureInfo.InvariantCulture);
+ MixerNodeTwoInput.Text = nodeTwo.ToString("0.##", CultureInfo.CurrentCulture);
}
public void SetMixerStatus(bool enabled)
diff --git a/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs b/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs
index 69cd6cb587..6eba2e0d21 100644
--- a/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs
+++ b/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs
@@ -1,5 +1,6 @@
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Binary.Components;
+using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@@ -46,7 +47,7 @@ private void OnToggleStatusButtonPressed()
private void OnPumpOutputPressurePressed(string value)
{
- float pressure = float.TryParse(value, out var parsed) ? parsed : 0f;
+ var pressure = UserInputParser.TryFloat(value, out var parsed) ? parsed : 0f;
if (pressure > MaxPressure) pressure = MaxPressure;
SendMessage(new GasPressurePumpChangeOutputPressureMessage(pressure));
diff --git a/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml b/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml
index 8be7a84e86..a0896a7b41 100644
--- a/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml
+++ b/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml
@@ -11,7 +11,7 @@
-
+
diff --git a/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml.cs b/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml.cs
index 22d8a1e9df..b5ffcd1072 100644
--- a/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml.cs
+++ b/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml.cs
@@ -39,14 +39,14 @@ public GasPressurePumpWindow()
SetMaxPressureButton.OnPressed += _ =>
{
- PumpPressureOutputInput.Text = Atmospherics.MaxOutputPressure.ToString(CultureInfo.InvariantCulture);
+ PumpPressureOutputInput.Text = Atmospherics.MaxOutputPressure.ToString(CultureInfo.CurrentCulture);
SetOutputPressureButton.Disabled = false;
};
}
public void SetOutputPressure(float pressure)
{
- PumpPressureOutputInput.Text = pressure.ToString(CultureInfo.InvariantCulture);
+ PumpPressureOutputInput.Text = pressure.ToString(CultureInfo.CurrentCulture);
}
public void SetPumpStatus(bool enabled)
diff --git a/Content.Client/Atmos/UI/GasThermomachineWindow.xaml.cs b/Content.Client/Atmos/UI/GasThermomachineWindow.xaml.cs
index 12e0617a55..bc8cb14336 100644
--- a/Content.Client/Atmos/UI/GasThermomachineWindow.xaml.cs
+++ b/Content.Client/Atmos/UI/GasThermomachineWindow.xaml.cs
@@ -17,7 +17,7 @@ public GasThermomachineWindow()
RobustXamlLoader.Load(this);
SpinboxHBox.AddChild(
- TemperatureSpinbox = new FloatSpinBox(.1f, 2) { MaxWidth = 150, HorizontalExpand = true }
+ TemperatureSpinbox = new FloatSpinBox(.1f, 2) { MinWidth = 150, HorizontalExpand = true }
);
}
diff --git a/Content.Client/Atmos/UI/GasVolumePumpBoundUserInterface.cs b/Content.Client/Atmos/UI/GasVolumePumpBoundUserInterface.cs
index 5ccc79a410..1b39306181 100644
--- a/Content.Client/Atmos/UI/GasVolumePumpBoundUserInterface.cs
+++ b/Content.Client/Atmos/UI/GasVolumePumpBoundUserInterface.cs
@@ -1,5 +1,6 @@
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Binary.Components;
+using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@@ -46,7 +47,7 @@ private void OnToggleStatusButtonPressed()
private void OnPumpTransferRatePressed(string value)
{
- var rate = float.TryParse(value, out var parsed) ? parsed : 0f;
+ var rate = UserInputParser.TryFloat(value, out var parsed) ? parsed : 0f;
if (rate > MaxTransferRate)
rate = MaxTransferRate;
diff --git a/Content.Client/Atmos/UI/GasVolumePumpWindow.xaml b/Content.Client/Atmos/UI/GasVolumePumpWindow.xaml
index 93ebc35a93..7275b34b3a 100644
--- a/Content.Client/Atmos/UI/GasVolumePumpWindow.xaml
+++ b/Content.Client/Atmos/UI/GasVolumePumpWindow.xaml
@@ -11,7 +11,7 @@
-
+
diff --git a/Content.Client/Atmos/UI/GasVolumePumpWindow.xaml.cs b/Content.Client/Atmos/UI/GasVolumePumpWindow.xaml.cs
index 2ca567302d..83a530c5c5 100644
--- a/Content.Client/Atmos/UI/GasVolumePumpWindow.xaml.cs
+++ b/Content.Client/Atmos/UI/GasVolumePumpWindow.xaml.cs
@@ -39,14 +39,14 @@ public GasVolumePumpWindow()
SetMaxRateButton.OnPressed += _ =>
{
- PumpTransferRateInput.Text = Atmospherics.MaxTransferRate.ToString(CultureInfo.InvariantCulture);
+ PumpTransferRateInput.Text = Atmospherics.MaxTransferRate.ToString(CultureInfo.CurrentCulture);
SetTransferRateButton.Disabled = false;
};
}
public void SetTransferRate(float rate)
{
- PumpTransferRateInput.Text = rate.ToString(CultureInfo.InvariantCulture);
+ PumpTransferRateInput.Text = rate.ToString(CultureInfo.CurrentCulture);
}
public void SetPumpStatus(bool enabled)
diff --git a/Content.Client/Audio/AmbientSoundSystem.cs b/Content.Client/Audio/AmbientSoundSystem.cs
index d66ee434a2..d39073fa33 100644
--- a/Content.Client/Audio/AmbientSoundSystem.cs
+++ b/Content.Client/Audio/AmbientSoundSystem.cs
@@ -28,6 +28,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
{
[Dependency] private readonly AmbientSoundTreeSystem _treeSys = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
@@ -172,7 +173,7 @@ public override void Update(float frameTime)
_targetTime = _gameTiming.CurTime+TimeSpan.FromSeconds(_cooldown);
- var player = _playerManager.LocalPlayer?.ControlledEntity;
+ var player = _playerManager.LocalEntity;
if (!EntityManager.TryGetComponent(player, out TransformComponent? xform))
{
ClearSounds();
@@ -198,13 +199,13 @@ private readonly struct QueryState
public readonly Dictionary> SourceDict = new();
public readonly Vector2 MapPos;
public readonly TransformComponent Player;
- public readonly EntityQuery Query;
+ public readonly SharedTransformSystem TransformSystem;
- public QueryState(Vector2 mapPos, TransformComponent player, EntityQuery query)
+ public QueryState(Vector2 mapPos, TransformComponent player, SharedTransformSystem transformSystem)
{
MapPos = mapPos;
Player = player;
- Query = query;
+ TransformSystem = transformSystem;
}
}
@@ -218,7 +219,7 @@ private static bool Callback(
var delta = xform.ParentUid == state.Player.ParentUid
? xform.LocalPosition - state.Player.LocalPosition
- : xform.WorldPosition - state.MapPos;
+ : state.TransformSystem.GetWorldPosition(xform) - state.MapPos;
var range = delta.Length();
if (range >= ambientComp.Range)
@@ -244,7 +245,7 @@ private void ProcessNearbyAmbience(TransformComponent playerXform)
{
var query = GetEntityQuery();
var metaQuery = GetEntityQuery();
- var mapPos = playerXform.MapPosition;
+ var mapPos = _xformSystem.GetMapCoordinates(playerXform);
// Remove out-of-range ambiences
foreach (var (comp, sound) in _playingSounds)
@@ -258,9 +259,10 @@ private void ProcessNearbyAmbience(TransformComponent playerXform)
xform.MapID == playerXform.MapID &&
!metaQuery.GetComponent(entity).EntityPaused)
{
+ // TODO: This is just trydistance for coordinates.
var distance = (xform.ParentUid == playerXform.ParentUid)
? xform.LocalPosition - playerXform.LocalPosition
- : xform.WorldPosition - mapPos.Position;
+ : _xformSystem.GetWorldPosition(xform) - mapPos.Position;
if (distance.LengthSquared() < comp.Range * comp.Range)
continue;
@@ -277,7 +279,7 @@ private void ProcessNearbyAmbience(TransformComponent playerXform)
return;
var pos = mapPos.Position;
- var state = new QueryState(pos, playerXform, query);
+ var state = new QueryState(pos, playerXform, _xformSystem);
var worldAabb = new Box2(pos - MaxAmbientVector, pos + MaxAmbientVector);
_treeSys.QueryAabb(ref state, Callback, mapPos.MapId, worldAabb);
diff --git a/Content.Client/Audio/AudioUIController.cs b/Content.Client/Audio/AudioUIController.cs
new file mode 100644
index 0000000000..ef903672fd
--- /dev/null
+++ b/Content.Client/Audio/AudioUIController.cs
@@ -0,0 +1,98 @@
+using Content.Shared.CCVar;
+using Robust.Client.Audio;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface.Controllers;
+using Robust.Shared.Audio.Sources;
+using Robust.Shared.Configuration;
+
+namespace Content.Client.Audio;
+
+public sealed class AudioUIController : UIController
+{
+ [Dependency] private readonly IAudioManager _audioManager = default!;
+ [Dependency] private readonly IConfigurationManager _configManager = default!;
+ [Dependency] private readonly IResourceCache _cache = default!;
+
+ private float _interfaceGain;
+ private IAudioSource? _clickSource;
+ private IAudioSource? _hoverSource;
+
+ private const float ClickGain = 0.25f;
+ private const float HoverGain = 0.05f;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ /*
+ * This exists to load UI sounds outside of the game sim.
+ */
+
+ // No unsub coz never shuts down until program exit.
+ _configManager.OnValueChanged(CCVars.UIClickSound, SetClickSound, true);
+ _configManager.OnValueChanged(CCVars.UIHoverSound, SetHoverSound, true);
+
+ _configManager.OnValueChanged(CCVars.InterfaceVolume, SetInterfaceVolume, true);
+ }
+
+ private void SetInterfaceVolume(float obj)
+ {
+ _interfaceGain = obj;
+
+ if (_clickSource != null)
+ {
+ _clickSource.Gain = ClickGain * _interfaceGain;
+ }
+
+ if (_hoverSource != null)
+ {
+ _hoverSource.Gain = HoverGain * _interfaceGain;
+ }
+ }
+
+ private void SetClickSound(string value)
+ {
+ if (!string.IsNullOrEmpty(value))
+ {
+ var resource = _cache.GetResource(value);
+ var source =
+ _audioManager.CreateAudioSource(resource);
+
+ if (source != null)
+ {
+ source.Gain = ClickGain * _interfaceGain;
+ source.Global = true;
+ }
+
+ _clickSource = source;
+ UIManager.SetClickSound(source);
+ }
+ else
+ {
+ UIManager.SetClickSound(null);
+ }
+ }
+
+ private void SetHoverSound(string value)
+ {
+ if (!string.IsNullOrEmpty(value))
+ {
+ var hoverResource = _cache.GetResource(value);
+ var hoverSource =
+ _audioManager.CreateAudioSource(hoverResource);
+
+ if (hoverSource != null)
+ {
+ hoverSource.Gain = HoverGain * _interfaceGain;
+ hoverSource.Global = true;
+ }
+
+ _hoverSource = hoverSource;
+ UIManager.SetHoverSound(hoverSource);
+ }
+ else
+ {
+ UIManager.SetHoverSound(null);
+ }
+ }
+}
diff --git a/Content.Client/Audio/BackgroundAudioSystem.cs b/Content.Client/Audio/BackgroundAudioSystem.cs
index 09ac1efcd6..27b2dcb1b7 100644
--- a/Content.Client/Audio/BackgroundAudioSystem.cs
+++ b/Content.Client/Audio/BackgroundAudioSystem.cs
@@ -1,6 +1,7 @@
using Content.Client.GameTicking.Managers;
using Content.Client.Lobby;
using Content.Shared.CCVar;
+using Content.Shared.GameTicking;
using JetBrains.Annotations;
using Robust.Client;
using Robust.Client.State;
@@ -39,6 +40,8 @@ public override void Initialize()
_client.PlayerLeaveServer += OnLeave;
_gameTicker.LobbySongUpdated += LobbySongUpdated;
+
+ SubscribeNetworkEvent(PlayRestartSound);
}
public override void Shutdown()
@@ -129,4 +132,22 @@ private void EndLobbyMusic()
{
LobbyStream = _audio.Stop(LobbyStream);
}
+
+ private void PlayRestartSound(RoundRestartCleanupEvent ev)
+ {
+ if (!_configManager.GetCVar(CCVars.LobbyMusicEnabled))
+ return;
+
+ var file = _gameTicker.RestartSound;
+ if (string.IsNullOrEmpty(file))
+ {
+ return;
+ }
+
+ var volume = _lobbyParams.WithVolume(_lobbyParams.Volume +
+ SharedAudioSystem.GainToVolume(
+ _configManager.GetCVar(CCVars.LobbyMusicVolume)));
+
+ _audio.PlayGlobal(file, Filter.Local(), false, volume);
+ }
}
diff --git a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
index aafd0ea630..89324f2225 100644
--- a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
+++ b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
@@ -26,7 +26,6 @@ public sealed partial class ContentAudioSystem
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
- [Dependency] private readonly IResourceCache _resource = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IStateManager _state = default!;
[Dependency] private readonly RulesSystem _rules = default!;
@@ -153,8 +152,7 @@ private void UpdateAmbientMusic()
// Update still runs in lobby so just ignore it.
if (_state.CurrentState is not GameplayState)
{
- Audio.Stop(_ambientMusicStream);
- _ambientMusicStream = null;
+ _ambientMusicStream = Audio.Stop(_ambientMusicStream);
_musicProto = null;
return;
}
diff --git a/Content.Client/Audio/ContentAudioSystem.cs b/Content.Client/Audio/ContentAudioSystem.cs
index 603b1086d8..ae881766ed 100644
--- a/Content.Client/Audio/ContentAudioSystem.cs
+++ b/Content.Client/Audio/ContentAudioSystem.cs
@@ -1,15 +1,18 @@
using Content.Shared.Audio;
-using Content.Shared.CCVar;
using Content.Shared.GameTicking;
-using Robust.Client.GameObjects;
-using Robust.Shared;
-using Robust.Shared.Audio;
+using Robust.Client.Audio;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
using AudioComponent = Robust.Shared.Audio.Components.AudioComponent;
namespace Content.Client.Audio;
public sealed partial class ContentAudioSystem : SharedContentAudioSystem
{
+ [Dependency] private readonly IAudioManager _audioManager = default!;
+ [Dependency] private readonly IResourceCache _cache = default!;
+ [Dependency] private readonly IUserInterfaceManager _uiManager = default!;
+
// Need how much volume to change per tick and just remove it when it drops below "0"
private readonly Dictionary _fadingOut = new();
@@ -32,6 +35,7 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem
public const float AmbienceMultiplier = 3f;
public const float AmbientMusicMultiplier = 3f;
public const float LobbyMultiplier = 3f;
+ public const float InterfaceMultiplier = 2f;
public override void Initialize()
{
@@ -114,7 +118,8 @@ private void UpdateFades(float frameTime)
}
var volume = component.Volume - change * frameTime;
- component.Volume = MathF.Max(MinVolume, volume);
+ volume = MathF.Max(MinVolume, volume);
+ _audio.SetVolume(stream, volume, component);
if (component.Volume.Equals(MinVolume))
{
@@ -140,7 +145,8 @@ private void UpdateFades(float frameTime)
}
var volume = component.Volume + change * frameTime;
- component.Volume = MathF.Min(target, volume);
+ volume = MathF.Max(target, volume);
+ _audio.SetVolume(stream, volume, component);
if (component.Volume.Equals(target))
{
diff --git a/Content.Client/Bed/Cryostorage/CryostorageBoundUserInterface.cs b/Content.Client/Bed/Cryostorage/CryostorageBoundUserInterface.cs
new file mode 100644
index 0000000000..ffab162548
--- /dev/null
+++ b/Content.Client/Bed/Cryostorage/CryostorageBoundUserInterface.cs
@@ -0,0 +1,56 @@
+using Content.Shared.Bed.Cryostorage;
+using JetBrains.Annotations;
+
+namespace Content.Client.Bed.Cryostorage;
+
+[UsedImplicitly]
+public sealed class CryostorageBoundUserInterface : BoundUserInterface
+{
+ [ViewVariables]
+ private CryostorageMenu? _menu;
+
+ public CryostorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _menu = new();
+
+ _menu.OnClose += Close;
+
+ _menu.SlotRemoveButtonPressed += (ent, slot) =>
+ {
+ SendMessage(new CryostorageRemoveItemBuiMessage(ent, slot, CryostorageRemoveItemBuiMessage.RemovalType.Inventory));
+ };
+
+ _menu.HandRemoveButtonPressed += (ent, hand) =>
+ {
+ SendMessage(new CryostorageRemoveItemBuiMessage(ent, hand, CryostorageRemoveItemBuiMessage.RemovalType.Hand));
+ };
+
+ _menu.OpenCentered();
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ switch (state)
+ {
+ case CryostorageBuiState msg:
+ _menu?.UpdateState(msg);
+ break;
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+ _menu?.Dispose();
+ }
+}
diff --git a/Content.Client/Bed/Cryostorage/CryostorageEntryControl.xaml b/Content.Client/Bed/Cryostorage/CryostorageEntryControl.xaml
new file mode 100644
index 0000000000..176acbf29b
--- /dev/null
+++ b/Content.Client/Bed/Cryostorage/CryostorageEntryControl.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Bed/Cryostorage/CryostorageEntryControl.xaml.cs b/Content.Client/Bed/Cryostorage/CryostorageEntryControl.xaml.cs
new file mode 100644
index 0000000000..09e418fd86
--- /dev/null
+++ b/Content.Client/Bed/Cryostorage/CryostorageEntryControl.xaml.cs
@@ -0,0 +1,46 @@
+using Content.Shared.Bed.Cryostorage;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Bed.Cryostorage;
+
+[GenerateTypedNameReferences]
+public sealed partial class CryostorageEntryControl : BoxContainer
+{
+ public event Action? SlotRemoveButtonPressed;
+ public event Action? HandRemoveButtonPressed;
+
+ public NetEntity Entity;
+ public bool LastOpenState;
+
+ public CryostorageEntryControl(CryostorageContainedPlayerData data)
+ {
+ RobustXamlLoader.Load(this);
+ Entity = data.PlayerEnt;
+ Update(data);
+ }
+
+ public void Update(CryostorageContainedPlayerData data)
+ {
+ LastOpenState = Collapsible.BodyVisible;
+ Heading.Title = data.PlayerName;
+ Body.Visible = data.ItemSlots.Count != 0 && data.HeldItems.Count != 0;
+
+ ItemsContainer.Children.Clear();
+ foreach (var (name, itemName) in data.ItemSlots)
+ {
+ var control = new CryostorageSlotControl(name, itemName);
+ control.Button.OnPressed += _ => SlotRemoveButtonPressed?.Invoke(name);
+ ItemsContainer.AddChild(control);
+ }
+
+ foreach (var (name, held) in data.HeldItems)
+ {
+ var control = new CryostorageSlotControl(Loc.GetString("cryostorage-ui-filler-hand"), held);
+ control.Button.OnPressed += _ => HandRemoveButtonPressed?.Invoke(name);
+ ItemsContainer.AddChild(control);
+ }
+ Collapsible.BodyVisible = LastOpenState;
+ }
+}
diff --git a/Content.Client/Bed/Cryostorage/CryostorageMenu.xaml b/Content.Client/Bed/Cryostorage/CryostorageMenu.xaml
new file mode 100644
index 0000000000..5360cdb38e
--- /dev/null
+++ b/Content.Client/Bed/Cryostorage/CryostorageMenu.xaml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Bed/Cryostorage/CryostorageMenu.xaml.cs b/Content.Client/Bed/Cryostorage/CryostorageMenu.xaml.cs
new file mode 100644
index 0000000000..51f1561939
--- /dev/null
+++ b/Content.Client/Bed/Cryostorage/CryostorageMenu.xaml.cs
@@ -0,0 +1,54 @@
+using System.Linq;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Bed.Cryostorage;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Collections;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Bed.Cryostorage;
+
+[GenerateTypedNameReferences]
+public sealed partial class CryostorageMenu : FancyWindow
+{
+ public event Action? SlotRemoveButtonPressed;
+ public event Action? HandRemoveButtonPressed;
+
+ public CryostorageMenu()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ public void UpdateState(CryostorageBuiState state)
+ {
+ var data = state.PlayerData;
+ var nonexistentEntries = new ValueList(data);
+
+ var children = new ValueList(EntriesContainer.Children);
+ foreach (var control in children)
+ {
+ if (control is not CryostorageEntryControl entryControl)
+ continue;
+
+ if (data.Where(p => p.PlayerEnt == entryControl.Entity).FirstOrNull() is not { } datum)
+ {
+ EntriesContainer.Children.Remove(entryControl);
+ continue;
+ }
+
+ nonexistentEntries.Remove(datum);
+ entryControl.Update(datum);
+ }
+
+ foreach (var player in nonexistentEntries)
+ {
+ var control = new CryostorageEntryControl(player);
+ control.SlotRemoveButtonPressed += a => SlotRemoveButtonPressed?.Invoke(player.PlayerEnt, a);
+ control.HandRemoveButtonPressed += a => HandRemoveButtonPressed?.Invoke(player.PlayerEnt, a);
+ EntriesContainer.Children.Add(control);
+ }
+
+ EmptyLabel.Visible = data.Count == 0;
+ }
+}
diff --git a/Content.Client/Bed/Cryostorage/CryostorageSlotControl.xaml b/Content.Client/Bed/Cryostorage/CryostorageSlotControl.xaml
new file mode 100644
index 0000000000..b45e77cd1a
--- /dev/null
+++ b/Content.Client/Bed/Cryostorage/CryostorageSlotControl.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Bed/Cryostorage/CryostorageSlotControl.xaml.cs b/Content.Client/Bed/Cryostorage/CryostorageSlotControl.xaml.cs
new file mode 100644
index 0000000000..629b958262
--- /dev/null
+++ b/Content.Client/Bed/Cryostorage/CryostorageSlotControl.xaml.cs
@@ -0,0 +1,18 @@
+using Content.Client.Message;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Bed.Cryostorage;
+
+[GenerateTypedNameReferences]
+public sealed partial class CryostorageSlotControl : BoxContainer
+{
+ public CryostorageSlotControl(string name, string itemName)
+ {
+ RobustXamlLoader.Load(this);
+
+ SlotLabel.SetMarkup(Loc.GetString("cryostorage-ui-label-slot-name", ("slot", name)));
+ ItemLabel.Text = itemName;
+ }
+}
diff --git a/Content.Client/Bed/Cryostorage/CryostorageSystem.cs b/Content.Client/Bed/Cryostorage/CryostorageSystem.cs
new file mode 100644
index 0000000000..882f433841
--- /dev/null
+++ b/Content.Client/Bed/Cryostorage/CryostorageSystem.cs
@@ -0,0 +1,9 @@
+using Content.Shared.Bed.Cryostorage;
+
+namespace Content.Client.Bed.Cryostorage;
+
+///
+public sealed class CryostorageSystem : SharedCryostorageSystem
+{
+
+}
diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs
index db5fa2bd99..8536c3c429 100644
--- a/Content.Client/Buckle/BuckleSystem.cs
+++ b/Content.Client/Buckle/BuckleSystem.cs
@@ -1,6 +1,7 @@
using Content.Client.Rotation;
using Content.Shared.Buckle;
using Content.Shared.Buckle.Components;
+using Content.Shared.Rotation;
using Content.Shared.Vehicle.Components;
using Robust.Client.GameObjects;
@@ -56,17 +57,15 @@ private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref Ap
if (!TryComp(uid, out var rotVisuals))
return;
- if (!Appearance.TryGetData(uid, StrapVisuals.RotationAngle, out var angle, args.Component) ||
- !Appearance.TryGetData(uid, BuckleVisuals.Buckled, out var buckled, args.Component) ||
+ if (!Appearance.TryGetData(uid, BuckleVisuals.Buckled, out var buckled, args.Component) ||
!buckled ||
args.Sprite == null)
{
- _rotationVisualizerSystem.SetHorizontalAngle(uid, rotVisuals.DefaultRotation, rotVisuals);
+ _rotationVisualizerSystem.SetHorizontalAngle((uid, rotVisuals), rotVisuals.DefaultRotation);
return;
}
// Animate strapping yourself to something at a given angle
- _rotationVisualizerSystem.SetHorizontalAngle(uid, Angle.FromDegrees(angle), rotVisuals);
// TODO: Dump this when buckle is better
_rotationVisualizerSystem.AnimateSpriteRotation(uid, args.Sprite, rotVisuals.HorizontalRotation, 0.125f);
}
diff --git a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
index 669f64fae4..482acb3c87 100644
--- a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
+++ b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
@@ -1,7 +1,6 @@
using Content.Client.Cargo.UI;
using Content.Shared.Cargo.Components;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
namespace Content.Client.Cargo.BUI;
diff --git a/Content.Client/Cargo/BUI/CargoOrderConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoOrderConsoleBoundUserInterface.cs
index 6c9af85a2c..0be3ebd97f 100644
--- a/Content.Client/Cargo/BUI/CargoOrderConsoleBoundUserInterface.cs
+++ b/Content.Client/Cargo/BUI/CargoOrderConsoleBoundUserInterface.cs
@@ -50,8 +50,9 @@ protected override void Open()
base.Open();
var spriteSystem = EntMan.System();
- _menu = new CargoConsoleMenu(IoCManager.Resolve(), spriteSystem);
- var localPlayer = IoCManager.Resolve()?.LocalPlayer?.ControlledEntity;
+ var dependencies = IoCManager.Instance!;
+ _menu = new CargoConsoleMenu(Owner, EntMan, dependencies.Resolve(), spriteSystem);
+ var localPlayer = dependencies.Resolve().LocalEntity;
var description = new FormattedMessage();
string orderRequester;
diff --git a/Content.Client/Cargo/UI/BountyEntry.xaml b/Content.Client/Cargo/UI/BountyEntry.xaml
index e570b03746..60446327b3 100644
--- a/Content.Client/Cargo/UI/BountyEntry.xaml
+++ b/Content.Client/Cargo/UI/BountyEntry.xaml
@@ -8,14 +8,13 @@
HorizontalExpand="True">
-
-
+
diff --git a/Content.Client/Cargo/UI/BountyEntry.xaml.cs b/Content.Client/Cargo/UI/BountyEntry.xaml.cs
index 54d1110840..05c5673ddd 100644
--- a/Content.Client/Cargo/UI/BountyEntry.xaml.cs
+++ b/Content.Client/Cargo/UI/BountyEntry.xaml.cs
@@ -27,8 +27,6 @@ public BountyEntry(CargoBountyData bounty)
if (!_prototype.TryIndex(bounty.Bounty, out var bountyPrototype))
return;
- EndTime = bounty.EndTime;
-
var items = new List();
foreach (var entry in bountyPrototype.Entries)
{
@@ -39,16 +37,8 @@ public BountyEntry(CargoBountyData bounty)
ManifestLabel.SetMarkup(Loc.GetString("bounty-console-manifest-label", ("item", string.Join(", ", items))));
RewardLabel.SetMarkup(Loc.GetString("bounty-console-reward-label", ("reward", bountyPrototype.Reward)));
DescriptionLabel.SetMarkup(Loc.GetString("bounty-console-description-label", ("description", Loc.GetString(bountyPrototype.Description))));
- IdLabel.Text = Loc.GetString("bounty-console-id-label", ("id", bounty.Id));
+ IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
PrintButton.OnPressed += _ => OnButtonPressed?.Invoke();
}
-
- protected override void FrameUpdate(FrameEventArgs args)
- {
- base.FrameUpdate(args);
-
- var remaining = TimeSpan.FromSeconds(Math.Max((EndTime - _timing.CurTime).TotalSeconds, 0));
- TimeLabel.SetMarkup(Loc.GetString("bounty-console-time-label", ("time", remaining.ToString("mm':'ss"))));
- }
}
diff --git a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
index 62ff31df89..2f1756dd18 100644
--- a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
+++ b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
@@ -9,7 +9,7 @@ namespace Content.Client.Cargo.UI;
[GenerateTypedNameReferences]
public sealed partial class CargoBountyMenu : FancyWindow
{
- public Action? OnLabelButtonPressed;
+ public Action? OnLabelButtonPressed;
public CargoBountyMenu()
{
diff --git a/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs b/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs
index 0cc17a6246..5402a24667 100644
--- a/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs
+++ b/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs
@@ -1,6 +1,7 @@
using System.Linq;
using Content.Client.UserInterface.Controls;
using Content.Shared.Cargo;
+using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
@@ -14,8 +15,10 @@ namespace Content.Client.Cargo.UI
[GenerateTypedNameReferences]
public sealed partial class CargoConsoleMenu : FancyWindow
{
+ private IEntityManager _entityManager;
private IPrototypeManager _protoManager;
private SpriteSystem _spriteSystem;
+ private EntityUid _owner;
public event Action? OnItemSelected;
public event Action? OnOrderApproved;
@@ -24,11 +27,13 @@ public sealed partial class CargoConsoleMenu : FancyWindow
private readonly List _categoryStrings = new();
private string? _category;
- public CargoConsoleMenu(IPrototypeManager protoManager, SpriteSystem spriteSystem)
+ public CargoConsoleMenu(EntityUid owner, IEntityManager entMan, IPrototypeManager protoManager, SpriteSystem spriteSystem)
{
RobustXamlLoader.Load(this);
+ _entityManager = entMan;
_protoManager = protoManager;
_spriteSystem = spriteSystem;
+ _owner = owner;
Title = Loc.GetString("cargo-console-menu-title");
@@ -53,7 +58,21 @@ private void SetCategoryText(int id)
Categories.SelectId(id);
}
- public IEnumerable ProductPrototypes => _protoManager.EnumeratePrototypes();
+ public IEnumerable ProductPrototypes
+ {
+ get
+ {
+ var allowedGroups = _entityManager.GetComponentOrNull(_owner)?.AllowedGroups;
+
+ foreach (var cargoPrototype in _protoManager.EnumeratePrototypes())
+ {
+ if (!allowedGroups?.Contains(cargoPrototype.Group) ?? false)
+ continue;
+
+ yield return cargoPrototype;
+ }
+ }
+ }
///
/// Populates the list of products that will actually be shown, using the current filters.
@@ -80,7 +99,7 @@ public void PopulateProducts()
Product = prototype,
ProductName = { Text = prototype.Name },
MainButton = { ToolTip = prototype.Description },
- PointCost = { Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", prototype.PointCost.ToString())) },
+ PointCost = { Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", prototype.Cost.ToString())) },
Icon = { Texture = _spriteSystem.Frame0(prototype.Icon) },
};
button.MainButton.OnPressed += args =>
diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs b/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs
new file mode 100644
index 0000000000..d28d3228c9
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs
@@ -0,0 +1,28 @@
+using Content.Client.UserInterface.Fragments;
+using Content.Shared.CartridgeLoader.Cartridges;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.CartridgeLoader.Cartridges;
+
+public sealed partial class LogProbeUi : UIFragment
+{
+ private LogProbeUiFragment? _fragment;
+
+ public override Control GetUIFragmentRoot()
+ {
+ return _fragment!;
+ }
+
+ public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
+ {
+ _fragment = new LogProbeUiFragment();
+ }
+
+ public override void UpdateState(BoundUserInterfaceState state)
+ {
+ if (state is not LogProbeUiState logProbeUiState)
+ return;
+
+ _fragment?.UpdateState(logProbeUiState.PulledLogs);
+ }
+}
diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiEntry.xaml b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiEntry.xaml
new file mode 100644
index 0000000000..5712b301c3
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiEntry.xaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiEntry.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiEntry.xaml.cs
new file mode 100644
index 0000000000..369042d991
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiEntry.xaml.cs
@@ -0,0 +1,17 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.CartridgeLoader.Cartridges;
+
+[GenerateTypedNameReferences]
+public sealed partial class LogProbeUiEntry : BoxContainer
+{
+ public LogProbeUiEntry(int numberLabel, string timeText, string accessorText)
+ {
+ RobustXamlLoader.Load(this);
+ NumberLabel.Text = numberLabel.ToString();
+ TimeLabel.Text = timeText;
+ AccessorLabel.Text = accessorText;
+ }
+}
diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml
new file mode 100644
index 0000000000..d369a33c6c
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs
new file mode 100644
index 0000000000..b22e0bc196
--- /dev/null
+++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs
@@ -0,0 +1,39 @@
+using Content.Shared.CartridgeLoader.Cartridges;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.CartridgeLoader.Cartridges;
+
+[GenerateTypedNameReferences]
+public sealed partial class LogProbeUiFragment : BoxContainer
+{
+ public LogProbeUiFragment()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ public void UpdateState(List logs)
+ {
+ ProbedDeviceContainer.RemoveAllChildren();
+
+ //Reverse the list so the oldest entries appear at the bottom
+ logs.Reverse();
+
+ var count = 1;
+ foreach (var log in logs)
+ {
+ AddAccessLog(log, count);
+ count++;
+ }
+ }
+
+ private void AddAccessLog(PulledAccessLog log, int numberLabelText)
+ {
+ var timeLabelText = TimeSpan.FromSeconds(Math.Truncate(log.Time.TotalSeconds)).ToString();
+ var accessorLabelText = log.Accessor;
+ var entry = new LogProbeUiEntry(numberLabelText, timeLabelText, accessorLabelText);
+
+ ProbedDeviceContainer.AddChild(entry);
+ }
+}
diff --git a/Content.Client/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs b/Content.Client/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs
new file mode 100644
index 0000000000..0c38d605d2
--- /dev/null
+++ b/Content.Client/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs
@@ -0,0 +1,7 @@
+using Content.Shared.Chemistry.EntitySystems;
+
+namespace Content.Client.Chemistry.Containers.EntitySystems;
+
+public sealed partial class SolutionContainerSystem : SharedSolutionContainerSystem
+{
+}
diff --git a/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs b/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs
index 736e22f834..a3cedb5f2f 100644
--- a/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs
+++ b/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs
@@ -1,16 +1,42 @@
-using Content.Shared.Chemistry;
+using System.Linq;
+using Content.Client.Chemistry.Containers.EntitySystems;
+using Content.Shared.Atmos.Prototypes;
+using Content.Shared.Body.Part;
+using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Components.SolutionManager;
+using Content.Shared.Chemistry.Reaction;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Kitchen.Components;
+using Content.Shared.Prototypes;
+using Robust.Shared.Prototypes;
namespace Content.Client.Chemistry.EntitySystems;
///
public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
{
+ [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
+
+ [ValidatePrototypeId]
+ private const string DefaultMixingCategory = "DummyMix";
+ [ValidatePrototypeId]
+ private const string DefaultGrindCategory = "DummyGrind";
+ [ValidatePrototypeId]
+ private const string DefaultJuiceCategory = "DummyJuice";
+ [ValidatePrototypeId]
+ private const string DefaultCondenseCategory = "DummyCondense";
+
+ private readonly Dictionary> _reagentSources = new();
+
///
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent(OnReceiveRegistryUpdate);
+ SubscribeLocalEvent(OnPrototypesReloaded);
+ OnPrototypesReloaded(null);
}
private void OnReceiveRegistryUpdate(ReagentGuideRegistryChangedEvent message)
@@ -26,4 +52,177 @@ private void OnReceiveRegistryUpdate(ReagentGuideRegistryChangedEvent message)
Registry[key] = val;
}
}
+
+ private void OnPrototypesReloaded(PrototypesReloadedEventArgs? ev)
+ {
+ // this doesn't check what prototypes are being reloaded because, to be frank, we use a lot of them.
+ _reagentSources.Clear();
+ foreach (var reagent in PrototypeManager.EnumeratePrototypes())
+ {
+ _reagentSources.Add(reagent.ID, new());
+ }
+
+ foreach (var reaction in PrototypeManager.EnumeratePrototypes())
+ {
+ if (!reaction.Source)
+ continue;
+
+ var data = new ReagentReactionSourceData(
+ reaction.MixingCategories ?? new () { DefaultMixingCategory },
+ reaction);
+ foreach (var product in reaction.Products.Keys)
+ {
+ _reagentSources[product].Add(data);
+ }
+ }
+
+ foreach (var gas in PrototypeManager.EnumeratePrototypes())
+ {
+ if (gas.Reagent == null)
+ continue;
+
+ var data = new ReagentGasSourceData(
+ new () { DefaultCondenseCategory },
+ gas);
+ _reagentSources[gas.Reagent].Add(data);
+ }
+
+ // store the names of the entities used so we don't get repeats in the guide.
+ var usedNames = new List();
+ foreach (var entProto in PrototypeManager.EnumeratePrototypes())
+ {
+ if (entProto.Abstract || usedNames.Contains(entProto.Name))
+ continue;
+
+ if (!entProto.TryGetComponent(out var extractableComponent))
+ continue;
+
+ //these bloat the hell out of blood/fat
+ if (entProto.HasComponent())
+ continue;
+
+ //these feel obvious...
+ if (entProto.HasComponent())
+ continue;
+
+ if (extractableComponent.JuiceSolution is { } juiceSolution)
+ {
+ var data = new ReagentEntitySourceData(
+ new() { DefaultJuiceCategory },
+ entProto,
+ juiceSolution);
+ foreach (var (id, _) in juiceSolution.Contents)
+ {
+ _reagentSources[id.Prototype].Add(data);
+ }
+
+ usedNames.Add(entProto.Name);
+ }
+
+
+ if (extractableComponent.GrindableSolution is { } grindableSolutionId &&
+ entProto.TryGetComponent(out var manager) &&
+ _solutionContainer.TryGetSolution(manager, grindableSolutionId, out var grindableSolution))
+ {
+ var data = new ReagentEntitySourceData(
+ new() { DefaultGrindCategory },
+ entProto,
+ grindableSolution);
+ foreach (var (id, _) in grindableSolution.Contents)
+ {
+ _reagentSources[id.Prototype].Add(data);
+ }
+ usedNames.Add(entProto.Name);
+ }
+ }
+ }
+
+ public List GetReagentSources(string id)
+ {
+ return _reagentSources.GetValueOrDefault(id) ?? new List();
+ }
}
+
+///
+/// A generic class meant to hold information about a reagent source.
+///
+public abstract class ReagentSourceData
+{
+ ///
+ /// The mixing type that applies to this source.
+ ///
+ public readonly IReadOnlyList> MixingType;
+
+ ///
+ /// The number of distinct outputs. Used for primary ordering.
+ ///
+ public abstract int OutputCount { get; }
+
+ ///
+ /// A text string corresponding to this source. Typically a name. Used for secondary ordering.
+ ///
+ public abstract string IdentifierString { get; }
+
+ protected ReagentSourceData(List> mixingType)
+ {
+ MixingType = mixingType;
+ }
+}
+
+///
+/// Used to store a reagent source that's an entity with a corresponding solution.
+///
+public sealed class ReagentEntitySourceData : ReagentSourceData
+{
+ public readonly EntityPrototype SourceEntProto;
+
+ public readonly Solution Solution;
+
+ public override int OutputCount => Solution.Contents.Count;
+
+ public override string IdentifierString => SourceEntProto.Name;
+
+ public ReagentEntitySourceData(List> mixingType, EntityPrototype sourceEntProto, Solution solution)
+ : base(mixingType)
+ {
+ SourceEntProto = sourceEntProto;
+ Solution = solution;
+ }
+}
+
+///
+/// Used to store a reagent source that comes from a reaction between multiple reagents.
+///
+public sealed class ReagentReactionSourceData : ReagentSourceData
+{
+ public readonly ReactionPrototype ReactionPrototype;
+
+ public override int OutputCount => ReactionPrototype.Products.Count + ReactionPrototype.Reactants.Count(r => r.Value.Catalyst);
+
+ public override string IdentifierString => ReactionPrototype.ID;
+
+ public ReagentReactionSourceData(List> mixingType, ReactionPrototype reactionPrototype)
+ : base(mixingType)
+ {
+ ReactionPrototype = reactionPrototype;
+ }
+}
+
+///
+/// Used to store a reagent source that comes from gas condensation.
+///
+public sealed class ReagentGasSourceData : ReagentSourceData
+{
+ public readonly GasPrototype GasPrototype;
+
+ public override int OutputCount => 1;
+
+ public override string IdentifierString => Loc.GetString(GasPrototype.Name);
+
+ public ReagentGasSourceData(List> mixingType, GasPrototype gasPrototype)
+ : base(mixingType)
+ {
+ GasPrototype = gasPrototype;
+ }
+}
+
diff --git a/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs b/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs
index eeca31220f..896349a161 100644
--- a/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs
+++ b/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs
@@ -12,9 +12,9 @@ public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnHandleInjectorState);
- SubscribeLocalEvent(OnItemInjectorStatus);
+ Subs.ItemStatus(ent => new InjectorStatusControl(ent));
SubscribeLocalEvent(OnHandleHyposprayState);
- SubscribeLocalEvent(OnItemHyposprayStatus);
+ Subs.ItemStatus(ent => new HyposprayStatusControl(ent));
}
private void OnHandleInjectorState(EntityUid uid, InjectorComponent component, ref ComponentHandleState args)
@@ -30,11 +30,6 @@ private void OnHandleInjectorState(EntityUid uid, InjectorComponent component, r
component.UiUpdateNeeded = true;
}
- private void OnItemInjectorStatus(EntityUid uid, InjectorComponent component, ItemStatusCollectMessage args)
- {
- args.Controls.Add(new InjectorStatusControl(component));
- }
-
private void OnHandleHyposprayState(EntityUid uid, HyposprayComponent component, ref ComponentHandleState args)
{
if (args.Current is not HyposprayComponentState cState)
@@ -44,9 +39,4 @@ private void OnHandleHyposprayState(EntityUid uid, HyposprayComponent component,
component.TotalVolume = cState.MaxVolume;
component.UiUpdateNeeded = true;
}
-
- private void OnItemHyposprayStatus(EntityUid uid, HyposprayComponent component, ItemStatusCollectMessage args)
- {
- args.Controls.Add(new HyposprayStatusControl(component));
- }
}
diff --git a/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs b/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs
index c94759a6c1..8244e3e6ed 100644
--- a/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs
+++ b/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs
@@ -57,13 +57,23 @@ protected override void Open()
_window.OnDispenseReagentButtonMouseEntered += (args, button) =>
{
if (_lastState is not null)
- _window.UpdateContainerInfo(_lastState, button.ReagentId);
+ _window.UpdateContainerInfo(_lastState);
};
_window.OnDispenseReagentButtonMouseExited += (args, button) =>
{
if (_lastState is not null)
_window.UpdateContainerInfo(_lastState);
};
+
+ _window.OnEjectJugButtonPressed += (args, button) => SendMessage(new ItemSlotButtonPressedEvent(button.ReagentId));
+ _window.OnEjectJugButtonMouseEntered += (args, button) => {
+ if (_lastState is not null)
+ _window.UpdateContainerInfo(_lastState);
+ };
+ _window.OnEjectJugButtonMouseExited += (args, button) => {
+ if (_lastState is not null)
+ _window.UpdateContainerInfo(_lastState);
+ };
}
///
diff --git a/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml b/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml
index e17586db14..d9e480f132 100644
--- a/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml
+++ b/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml
@@ -1,8 +1,7 @@
-
+ MinSize="680 450">
@@ -18,10 +17,8 @@
-
-
-
-
+
+
diff --git a/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml.cs b/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml.cs
index bf08d4978e..a36cc2fe54 100644
--- a/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml.cs
+++ b/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml.cs
@@ -23,6 +23,10 @@ public sealed partial class ReagentDispenserWindow : DefaultWindow
public event Action? OnDispenseReagentButtonMouseEntered;
public event Action? OnDispenseReagentButtonMouseExited;
+ public event Action? OnEjectJugButtonPressed;
+ public event Action? OnEjectJugButtonMouseEntered;
+ public event Action? OnEjectJugButtonMouseExited;
+
///
/// Create and initialize the dispenser UI client-side. Creates the basic layout,
/// actual data isn't filled in until the server sends data about the dispenser.
@@ -48,25 +52,27 @@ public ReagentDispenserWindow()
/// Update the button grid of reagents which can be dispensed.
///
/// Reagents which can be dispensed by this dispenser
- public void UpdateReagentsList(List inventory)
+ public void UpdateReagentsList(List>> inventory)
{
if (ChemicalList == null)
return;
ChemicalList.Children.Clear();
+ //Sort inventory by reagentLabel
+ inventory.Sort((x, y) => x.Value.Key.CompareTo(y.Value.Key));
- foreach (var entry in inventory
- .OrderBy(r => {_prototypeManager.TryIndex(r.Prototype, out ReagentPrototype? p); return p?.LocalizedName;}))
+ foreach (KeyValuePair> entry in inventory)
{
- var localizedName = _prototypeManager.TryIndex(entry.Prototype, out ReagentPrototype? p)
- ? p.LocalizedName
- : Loc.GetString("reagent-dispenser-window-reagent-name-not-found-text");
-
- var button = new DispenseReagentButton(entry, localizedName);
+ var button = new DispenseReagentButton(entry.Key, entry.Value.Key, entry.Value.Value);
button.OnPressed += args => OnDispenseReagentButtonPressed?.Invoke(args, button);
button.OnMouseEntered += args => OnDispenseReagentButtonMouseEntered?.Invoke(args, button);
button.OnMouseExited += args => OnDispenseReagentButtonMouseExited?.Invoke(args, button);
ChemicalList.AddChild(button);
+ var ejectButton = new EjectJugButton(entry.Key);
+ ejectButton.OnPressed += args => OnEjectJugButtonPressed?.Invoke(args, ejectButton);
+ ejectButton.OnMouseEntered += args => OnEjectJugButtonMouseEntered?.Invoke(args, ejectButton);
+ ejectButton.OnMouseExited += args => OnEjectJugButtonMouseExited?.Invoke(args, ejectButton);
+ ChemicalList.AddChild(ejectButton);
}
}
@@ -121,9 +127,8 @@ public void UpdateState(BoundUserInterfaceState state)
/// Also highlights a reagent if it's dispense button is being mouse hovered.
///
/// State data for the dispenser.
- /// Prototype ID of the reagent whose dispense button is currently being mouse hovered,
/// or null if no button is being hovered.
- public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state, ReagentId? highlightedReagentId = null)
+ public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state)
{
ContainerInfo.Children.Clear();
@@ -161,12 +166,6 @@ public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state, R
StyleClasses = {StyleNano.StyleClassLabelSecondaryColor},
};
- // Check if the reagent is being moused over. If so, color it green.
- if (reagent == highlightedReagentId) {
- nameLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateGood);
- quantityLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateGood);
- }
-
ContainerInfo.Children.Add(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
@@ -180,13 +179,27 @@ public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state, R
}
}
- public sealed class DispenseReagentButton : Button {
- public ReagentId ReagentId { get; }
+ public sealed class DispenseReagentButton : Button
+ {
+ public string ReagentId { get; }
+
+ public DispenseReagentButton(string reagentId, string text, string amount)
+ {
+ AddStyleClass("OpenRight");
+ ReagentId = reagentId;
+ Text = text + " " + amount;
+ }
+ }
+
+ public sealed class EjectJugButton : Button
+ {
+ public string ReagentId { get; }
- public DispenseReagentButton(ReagentId reagentId, string text)
+ public EjectJugButton(string reagentId)
{
+ AddStyleClass("OpenLeft");
ReagentId = reagentId;
- Text = text;
+ Text = "⏏";
}
}
}
diff --git a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs
index 44a24595ba..20693408ae 100644
--- a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs
+++ b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs
@@ -1,4 +1,5 @@
using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Rounding;
using Robust.Client.GameObjects;
diff --git a/Content.Client/CombatMode/CombatModeIndicatorsOverlay.cs b/Content.Client/CombatMode/CombatModeIndicatorsOverlay.cs
index 9732a67753..b2bdf2893d 100644
--- a/Content.Client/CombatMode/CombatModeIndicatorsOverlay.cs
+++ b/Content.Client/CombatMode/CombatModeIndicatorsOverlay.cs
@@ -4,6 +4,7 @@
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
+using Robust.Client.Serialization;
using Robust.Client.UserInterface;
using Robust.Shared.Enums;
using Robust.Shared.Graphics;
@@ -26,6 +27,7 @@ public sealed class CombatModeIndicatorsOverlay : Overlay
private readonly HandsSystem _hands = default!;
private readonly Texture _gunSight;
+ private readonly Texture _gunBoltSight;
private readonly Texture _meleeSight;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
@@ -46,6 +48,8 @@ public CombatModeIndicatorsOverlay(IInputManager input, IEntityManager entMan,
var spriteSys = _entMan.EntitySysManager.GetEntitySystem();
_gunSight = spriteSys.Frame0(new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/crosshair_pointers.rsi"),
"gun_sight"));
+ _gunBoltSight = spriteSys.Frame0(new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/crosshair_pointers.rsi"),
+ "gun_bolt_sight"));
_meleeSight = spriteSys.Frame0(new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/crosshair_pointers.rsi"),
"melee_sight"));
}
@@ -67,12 +71,16 @@ protected override void Draw(in OverlayDrawArgs args)
var handEntity = _hands.GetActiveHandEntity();
var isHandGunItem = _entMan.HasComponent(handEntity);
+ var isGunBolted = true;
+ if (_entMan.TryGetComponent(handEntity, out ChamberMagazineAmmoProviderComponent? chamber))
+ isGunBolted = chamber.BoltClosed ?? true;
+
var mousePos = mouseScreenPosition.Position;
var uiScale = (args.ViewportControl as Control)?.UIScale ?? 1f;
var limitedScale = uiScale > 1.25f ? 1.25f : uiScale;
- var sight = isHandGunItem ? _gunSight : _meleeSight;
+ var sight = isHandGunItem ? (isGunBolted ? _gunSight : _gunBoltSight) : _meleeSight;
DrawSight(sight, args.ScreenHandle, mousePos, limitedScale * Scale);
}
diff --git a/Content.Client/Commands/ActionsCommands.cs b/Content.Client/Commands/ActionsCommands.cs
index f8eb67d78a..3d8e906e09 100644
--- a/Content.Client/Commands/ActionsCommands.cs
+++ b/Content.Client/Commands/ActionsCommands.cs
@@ -1,4 +1,4 @@
-using Content.Client.Actions;
+using Content.Client.Actions;
using Content.Client.Mapping;
using Content.Shared.Administration;
using Robust.Shared.Console;
@@ -12,11 +12,8 @@ namespace Content.Client.Commands;
public sealed class SaveActionsCommand : IConsoleCommand
{
public string Command => "saveacts";
-
public string Description => "Saves the current action toolbar assignments to a file";
-
public string Help => $"Usage: {Command} ";
-
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
@@ -38,15 +35,15 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
*/
[AnyCommand]
-public sealed class LoadActionsCommand : IConsoleCommand
+public sealed class LoadActionsCommand : LocalizedCommands
{
- public string Command => "loadacts";
+ [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
- public string Description => "Loads action toolbar assignments from a user-file.";
+ public override string Command => "loadacts";
- public string Help => $"Usage: {Command} ";
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
- public void Execute(IConsoleShell shell, string argStr, string[] args)
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
@@ -56,33 +53,35 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
try
{
- EntitySystem.Get().LoadActionAssignments(args[0], true);
+ _entitySystemManager.GetEntitySystem().LoadActionAssignments(args[0], true);
}
catch
{
- shell.WriteLine("Failed to load action assignments");
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error"));
}
}
}
[AnyCommand]
-public sealed class LoadMappingActionsCommand : IConsoleCommand
+public sealed class LoadMappingActionsCommand : LocalizedCommands
{
- public string Command => "loadmapacts";
+ [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
- public string Description => "Loads the mapping preset action toolbar assignments.";
+ public const string CommandName = "loadmapacts";
- public string Help => $"Usage: {Command}";
+ public override string Command => CommandName;
- public void Execute(IConsoleShell shell, string argStr, string[] args)
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
try
{
- EntitySystem.Get().LoadMappingActions();
+ _entitySystemManager.GetEntitySystem().LoadMappingActions();
}
catch
{
- shell.WriteLine("Failed to load action assignments");
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error"));
}
}
}
diff --git a/Content.Client/Commands/AtmosDebugCommands.cs b/Content.Client/Commands/AtmosDebugCommands.cs
index ba95e74287..b6f1aab09b 100644
--- a/Content.Client/Commands/AtmosDebugCommands.cs
+++ b/Content.Client/Commands/AtmosDebugCommands.cs
@@ -1,122 +1,128 @@
-using JetBrains.Annotations;
-using Content.Shared.Atmos;
-using System;
using Content.Client.Atmos.EntitySystems;
+using Content.Shared.Atmos;
+using JetBrains.Annotations;
using Robust.Shared.Console;
-using Robust.Shared.GameObjects;
-namespace Content.Client.Commands
+namespace Content.Client.Commands;
+
+[UsedImplicitly]
+internal sealed class AtvRangeCommand : LocalizedCommands
{
- [UsedImplicitly]
- internal sealed class AtvRangeCommand : IConsoleCommand
+ [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
+
+ public override string Command => "atvrange";
+
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
- public string Command => "atvrange";
- public string Description => "Sets the atmos debug range (as two floats, start [red] and end [blue])";
- public string Help => "atvrange ";
- public void Execute(IConsoleShell shell, string argStr, string[] args)
+ if (args.Length != 2)
{
- if (args.Length != 2)
- {
- shell.WriteLine(Help);
- return;
- }
- if (!float.TryParse(args[0], out var xStart))
- {
- shell.WriteLine("Bad float START");
- return;
- }
- if (!float.TryParse(args[1], out var xEnd))
- {
- shell.WriteLine("Bad float END");
- return;
- }
- if (xStart == xEnd)
- {
- shell.WriteLine("Scale cannot be zero, as this would cause a division by zero in AtmosDebugOverlay.");
- return;
- }
- var sys = EntitySystem.Get();
- sys.CfgBase = xStart;
- sys.CfgScale = xEnd - xStart;
+ shell.WriteLine(Help);
+ return;
}
+ if (!float.TryParse(args[0], out var xStart))
+ {
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error-start"));
+ return;
+ }
+ if (!float.TryParse(args[1], out var xEnd))
+ {
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error-end"));
+ return;
+ }
+ if (xStart == xEnd)
+ {
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error-zero"));
+ return;
+ }
+ var sys = _entitySystemManager.GetEntitySystem();
+ sys.CfgBase = xStart;
+ sys.CfgScale = xEnd - xStart;
}
+}
- [UsedImplicitly]
- internal sealed class AtvModeCommand : IConsoleCommand
+[UsedImplicitly]
+internal sealed class AtvModeCommand : LocalizedCommands
+{
+ [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
+
+ public override string Command => "atvmode";
+
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
- public string Command => "atvmode";
- public string Description => "Sets the atmos debug mode. This will automatically reset the scale.";
- public string Help => "atvmode []";
- public void Execute(IConsoleShell shell, string argStr, string[] args)
+ if (args.Length < 1)
+ {
+ shell.WriteLine(Help);
+ return;
+ }
+ if (!Enum.TryParse(args[0], out var xMode))
+ {
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error-invalid"));
+ return;
+ }
+ int xSpecificGas = 0;
+ float xBase = 0;
+ float xScale = Atmospherics.MolesCellStandard * 2;
+ if (xMode == AtmosDebugOverlayMode.GasMoles)
{
- if (args.Length < 1)
+ if (args.Length != 2)
{
- shell.WriteLine(Help);
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error-target-gas"));
return;
}
- if (!Enum.TryParse(args[0], out var xMode))
+ if (!AtmosCommandUtils.TryParseGasID(args[1], out xSpecificGas))
{
- shell.WriteLine("Invalid mode");
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error-out-of-range"));
return;
}
- int xSpecificGas = 0;
- float xBase = 0;
- float xScale = Atmospherics.MolesCellStandard * 2;
- if (xMode == AtmosDebugOverlayMode.GasMoles)
+ }
+ else
+ {
+ if (args.Length != 1)
{
- if (args.Length != 2)
- {
- shell.WriteLine("A target gas must be provided for this mode.");
- return;
- }
- if (!AtmosCommandUtils.TryParseGasID(args[1], out xSpecificGas))
- {
- shell.WriteLine("Gas ID not parsable or out of range.");
- return;
- }
+ shell.WriteLine(LocalizationManager.GetString($"cmd-{Command}-error-info"));
+ return;
}
- else
+ if (xMode == AtmosDebugOverlayMode.Temperature)
{
- if (args.Length != 1)
- {
- shell.WriteLine("No further information is required for this mode.");
- return;
- }
- if (xMode == AtmosDebugOverlayMode.Temperature)
- {
- // Red is 100C, Green is 20C, Blue is -60C
- xBase = Atmospherics.T20C + 80;
- xScale = -160;
- }
+ // Red is 100C, Green is 20C, Blue is -60C
+ xBase = Atmospherics.T20C + 80;
+ xScale = -160;
}
- var sys = EntitySystem.Get();
- sys.CfgMode = xMode;
- sys.CfgSpecificGas = xSpecificGas;
- sys.CfgBase = xBase;
- sys.CfgScale = xScale;
}
+ var sys = _entitySystemManager.GetEntitySystem();
+ sys.CfgMode = xMode;
+ sys.CfgSpecificGas = xSpecificGas;
+ sys.CfgBase = xBase;
+ sys.CfgScale = xScale;
}
+}
- [UsedImplicitly]
- internal sealed class AtvCBMCommand : IConsoleCommand
+[UsedImplicitly]
+internal sealed class AtvCBMCommand : LocalizedCommands
+{
+ [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
+
+ public override string Command => "atvcbm";
+
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
- public string Command => "atvcbm";
- public string Description => "Changes from red/green/blue to greyscale";
- public string Help => "atvcbm ";
- public void Execute(IConsoleShell shell, string argStr, string[] args)
+ if (args.Length != 1)
{
- if (args.Length != 1)
- {
- shell.WriteLine(Help);
- return;
- }
- if (!bool.TryParse(args[0], out var xFlag))
- {
- shell.WriteLine("Invalid flag");
- return;
- }
- var sys = EntitySystem.Get();
- sys.CfgCBM = xFlag;
+ shell.WriteLine(Help);
+ return;
+ }
+ if (!bool.TryParse(args[0], out var xFlag))
+ {
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error"));
+ return;
}
+ var sys = _entitySystemManager.GetEntitySystem();
+ sys.CfgCBM = xFlag;
}
}
diff --git a/Content.Client/Commands/CreditsCommand.cs b/Content.Client/Commands/CreditsCommand.cs
index f61c80c1d5..12f461cefe 100644
--- a/Content.Client/Commands/CreditsCommand.cs
+++ b/Content.Client/Commands/CreditsCommand.cs
@@ -1,21 +1,19 @@
using Content.Client.Credits;
-using Content.Client.UserInterface;
using Content.Shared.Administration;
using JetBrains.Annotations;
using Robust.Shared.Console;
-namespace Content.Client.Commands
+namespace Content.Client.Commands;
+
+[UsedImplicitly, AnyCommand]
+public sealed class CreditsCommand : LocalizedCommands
{
- [UsedImplicitly, AnyCommand]
- public sealed class CreditsCommand : IConsoleCommand
- {
- public string Command => "credits";
- public string Description => "Opens the credits window";
- public string Help => "credits";
+ public override string Command => "credits";
- public void Execute(IConsoleShell shell, string argStr, string[] args)
- {
- new CreditsWindow().Open();
- }
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ new CreditsWindow().Open();
}
}
diff --git a/Content.Client/Commands/DebugCommands.cs b/Content.Client/Commands/DebugCommands.cs
index a29a090ce0..20d763a0e9 100644
--- a/Content.Client/Commands/DebugCommands.cs
+++ b/Content.Client/Commands/DebugCommands.cs
@@ -6,66 +6,71 @@
using Robust.Shared.Console;
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
-namespace Content.Client.Commands
+namespace Content.Client.Commands;
+
+internal sealed class ShowMarkersCommand : LocalizedCommands
{
- internal sealed class ShowMarkersCommand : IConsoleCommand
+ [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
+
+ public override string Command => "showmarkers";
+
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
- // ReSharper disable once StringLiteralTypo
- public string Command => "showmarkers";
- public string Description => "Toggles visibility of markers such as spawn points.";
- public string Help => "";
+ _entitySystemManager.GetEntitySystem().MarkersVisible ^= true;
+ }
+}
- public void Execute(IConsoleShell shell, string argStr, string[] args)
- {
- IoCManager.Resolve().GetEntitySystem().MarkersVisible ^= true;
- }
+internal sealed class ShowSubFloor : LocalizedCommands
+{
+ [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
+
+ public override string Command => "showsubfloor";
+
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ _entitySystemManager.GetEntitySystem().ShowAll ^= true;
}
+}
- internal sealed class ShowSubFloor : IConsoleCommand
+internal sealed class ShowSubFloorForever : LocalizedCommands
+{
+ [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
+
+ public const string CommandName = "showsubfloorforever";
+ public override string Command => CommandName;
+
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
- // ReSharper disable once StringLiteralTypo
- public string Command => "showsubfloor";
- public string Description => "Makes entities below the floor always visible.";
- public string Help => $"Usage: {Command}";
+ _entitySystemManager.GetEntitySystem().ShowAll = true;
+
+ var entMan = IoCManager.Resolve();
+ var components = entMan.EntityQuery(true);
- public void Execute(IConsoleShell shell, string argStr, string[] args)
+ foreach (var (_, sprite) in components)
{
- IoCManager.Resolve().GetEntitySystem().ShowAll ^= true;
+ sprite.DrawDepth = (int) DrawDepth.Overlays;
}
}
+}
- internal sealed class ShowSubFloorForever : IConsoleCommand
- {
- // ReSharper disable once StringLiteralTypo
- public string Command => "showsubfloorforever";
- public string Description => "Makes entities below the floor always visible until the client is restarted.";
- public string Help => $"Usage: {Command}";
-
- public void Execute(IConsoleShell shell, string argStr, string[] args)
- {
- EntitySystem.Get().ShowAll = true;
+internal sealed class NotifyCommand : LocalizedCommands
+{
+ [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
- var entMan = IoCManager.Resolve();
- var components = entMan.EntityQuery(true);
+ public override string Command => "notify";
- foreach (var (_, sprite) in components)
- {
- sprite.DrawDepth = (int) DrawDepth.Overlays;
- }
- }
- }
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
- internal sealed class NotifyCommand : IConsoleCommand
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
- public string Command => "notify";
- public string Description => "Send a notify client side.";
- public string Help => "notify ";
+ var message = args[0];
- public void Execute(IConsoleShell shell, string argStr, string[] args)
- {
- var message = args[0];
-
- IoCManager.Resolve().GetEntitySystem().PopupCursor(message);
- }
+ _entitySystemManager.GetEntitySystem().PopupCursor(message);
}
}
diff --git a/Content.Client/Commands/DebugPathfindingCommand.cs b/Content.Client/Commands/DebugPathfindingCommand.cs
index 9071ea40a7..e02b6dcbbd 100644
--- a/Content.Client/Commands/DebugPathfindingCommand.cs
+++ b/Content.Client/Commands/DebugPathfindingCommand.cs
@@ -1,61 +1,61 @@
-using System.Linq;
using Content.Client.NPC;
using Content.Shared.NPC;
using JetBrains.Annotations;
using Robust.Shared.Console;
+using System.Linq;
+
+namespace Content.Client.Commands;
-namespace Content.Client.Commands
+[UsedImplicitly]
+public sealed class DebugPathfindingCommand : LocalizedCommands
{
- [UsedImplicitly]
- public sealed class DebugPathfindingCommand : IConsoleCommand
+ [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
+
+ public override string Command => "pathfinder";
+
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
- // ReSharper disable once StringLiteralTypo
- public string Command => "pathfinder";
- public string Description => "Toggles visibility of pathfinding debuggers.";
- public string Help => "pathfinder [options]";
+ var system = _entitySystemManager.GetEntitySystem();
- public void Execute(IConsoleShell shell, string argStr, string[] args)
+ if (args.Length == 0)
{
- var system = IoCManager.Resolve().GetEntitySystem();
+ system.Modes = PathfindingDebugMode.None;
+ return;
+ }
- if (args.Length == 0)
+ foreach (var arg in args)
+ {
+ if (!Enum.TryParse(arg, out var mode))
{
- system.Modes = PathfindingDebugMode.None;
- return;
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error", ("arg", arg)));
+ continue;
}
- foreach (var arg in args)
- {
- if (!Enum.TryParse(arg, out var mode))
- {
- shell.WriteError($"Unrecognised pathfinder args {arg}");
- continue;
- }
-
- system.Modes ^= mode;
- shell.WriteLine($"Toggled {arg} to {(system.Modes & mode) != 0x0}");
- }
+ system.Modes ^= mode;
+ shell.WriteLine(LocalizationManager.GetString($"cmd-{Command}-notify", ("arg", arg), ("newMode", (system.Modes & mode) != 0x0)));
}
+ }
- public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
+ public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
+ {
+ if (args.Length > 1)
{
- if (args.Length > 1)
- {
- return CompletionResult.Empty;
- }
-
- var values = Enum.GetValues().ToList();
- var options = new List();
+ return CompletionResult.Empty;
+ }
- foreach (var val in values)
- {
- if (val == PathfindingDebugMode.None)
- continue;
+ var values = Enum.GetValues().ToList();
+ var options = new List();
- options.Add(new CompletionOption(val.ToString()));
- }
+ foreach (var val in values)
+ {
+ if (val == PathfindingDebugMode.None)
+ continue;
- return CompletionResult.FromOptions(options);
+ options.Add(new CompletionOption(val.ToString()));
}
+
+ return CompletionResult.FromOptions(options);
}
}
diff --git a/Content.Client/Commands/GroupingEntityMenuCommand.cs b/Content.Client/Commands/GroupingEntityMenuCommand.cs
index 92315735ec..3bd2ca41e6 100644
--- a/Content.Client/Commands/GroupingEntityMenuCommand.cs
+++ b/Content.Client/Commands/GroupingEntityMenuCommand.cs
@@ -2,42 +2,40 @@
using Content.Shared.CCVar;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
-using Robust.Shared.IoC;
-namespace Content.Client.Commands
+namespace Content.Client.Commands;
+
+public sealed class GroupingEntityMenuCommand : LocalizedCommands
{
- public sealed class GroupingEntityMenuCommand : IConsoleCommand
+ [Dependency] private readonly IConfigurationManager _configurationManager = default!;
+
+ public override string Command => "entitymenug";
+
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command), ("groupingTypesCount", EntityMenuUIController.GroupingTypesCount));
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
- public string Command => "entitymenug";
+ if (args.Length != 1)
+ {
+ shell.WriteLine(Help);
+ return;
+ }
- public string Description => "Sets the entity menu grouping type.";
+ if (!int.TryParse(args[0], out var id))
+ {
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error", ("arg", args[0])));
+ return;
+ }
- public string Help => $"Usage: entitymenug <0:{EntityMenuUIController.GroupingTypesCount}>";
- public void Execute(IConsoleShell shell, string argStr, string[] args)
+ if (id < 0 || id > EntityMenuUIController.GroupingTypesCount - 1)
{
- if (args.Length != 1)
- {
- shell.WriteLine(Help);
- return;
- }
-
- if (!int.TryParse(args[0], out var id))
- {
- shell.WriteLine($"{args[0]} is not a valid integer.");
- return;
- }
-
- if (id < 0 ||id > EntityMenuUIController.GroupingTypesCount - 1)
- {
- shell.WriteLine($"{args[0]} is not a valid integer.");
- return;
- }
-
- var configurationManager = IoCManager.Resolve();
- var cvar = CCVars.EntityMenuGroupingType;
-
- configurationManager.SetCVar(cvar, id);
- shell.WriteLine($"Context Menu Grouping set to type: {configurationManager.GetCVar(cvar)}");
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error", ("arg", args[0])));
+ return;
}
+
+ var cvar = CCVars.EntityMenuGroupingType;
+
+ _configurationManager.SetCVar(cvar, id);
+ shell.WriteLine(LocalizationManager.GetString($"cmd-{Command}-notify", ("cvar", _configurationManager.GetCVar(cvar))));
}
}
diff --git a/Content.Client/Commands/HideMechanismsCommand.cs b/Content.Client/Commands/HideMechanismsCommand.cs
index 28433d2337..5f9afc78b9 100644
--- a/Content.Client/Commands/HideMechanismsCommand.cs
+++ b/Content.Client/Commands/HideMechanismsCommand.cs
@@ -1,46 +1,45 @@
-using Content.Shared.Body.Organ;
-using Robust.Client.Console;
+using Content.Shared.Body.Organ;
using Robust.Client.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.Containers;
-namespace Content.Client.Commands
+namespace Content.Client.Commands;
+
+public sealed class HideMechanismsCommand : LocalizedCommands
{
- public sealed class HideMechanismsCommand : IConsoleCommand
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+
+ public override string Command => "hidemechanisms";
+
+ public override string Description => LocalizationManager.GetString($"cmd-{Command}-desc", ("showMechanismsCommand", ShowMechanismsCommand.CommandName));
+
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
- public string Command => "hidemechanisms";
- public string Description => $"Reverts the effects of {ShowMechanismsCommand.CommandName}";
- public string Help => $"{Command}";
+ var containerSys = _entityManager.System();
+ var query = _entityManager.AllEntityQueryEnumerator();
- public void Execute(IConsoleShell shell, string argStr, string[] args)
+ while (query.MoveNext(out var uid, out _))
{
- var entityManager = IoCManager.Resolve();
- var containerSys = entityManager.System();
- var query = entityManager.AllEntityQueryEnumerator();
-
- while (query.MoveNext(out var uid, out _))
+ if (!_entityManager.TryGetComponent(uid, out SpriteComponent? sprite))
{
- if (!entityManager.TryGetComponent(uid, out SpriteComponent? sprite))
- {
- continue;
- }
+ continue;
+ }
- sprite.ContainerOccluded = false;
+ sprite.ContainerOccluded = false;
- var tempParent = uid;
- while (containerSys.TryGetContainingContainer(tempParent, out var container))
+ var tempParent = uid;
+ while (containerSys.TryGetContainingContainer(tempParent, out var container))
+ {
+ if (!container.ShowContents)
{
- if (!container.ShowContents)
- {
- sprite.ContainerOccluded = true;
- break;
- }
-
- tempParent = container.Owner;
+ sprite.ContainerOccluded = true;
+ break;
}
- }
- IoCManager.Resolve().ExecuteCommand("hidecontainedcontext");
+ tempParent = container.Owner;
+ }
}
}
}
diff --git a/Content.Client/Commands/MappingClientSideSetupCommand.cs b/Content.Client/Commands/MappingClientSideSetupCommand.cs
index a90afc5b9c..39268c6284 100644
--- a/Content.Client/Commands/MappingClientSideSetupCommand.cs
+++ b/Content.Client/Commands/MappingClientSideSetupCommand.cs
@@ -1,33 +1,28 @@
-using JetBrains.Annotations;
-using System;
using Content.Client.Markers;
+using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Shared.Console;
-using Robust.Shared.GameObjects;
namespace Content.Client.Commands;
-///
-/// Sent by mapping command to client.
-/// This is because the debug commands for some of these options are on toggles.
-///
[UsedImplicitly]
-internal sealed class MappingClientSideSetupCommand : IConsoleCommand
+internal sealed class MappingClientSideSetupCommand : LocalizedCommands
{
- // ReSharper disable once StringLiteralTypo
- public string Command => "mappingclientsidesetup";
- public string Description => "Sets up the lighting control and such settings client-side. Sent by 'mapping' to client.";
- public string Help => "";
+ [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
+ [Dependency] private readonly ILightManager _lightManager = default!;
+
+ public override string Command => "mappingclientsidesetup";
+
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
- public void Execute(IConsoleShell shell, string argStr, string[] args)
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
- var mgr = IoCManager.Resolve();
- if (!mgr.LockConsoleAccess)
+ if (!_lightManager.LockConsoleAccess)
{
- EntitySystem.Get().MarkersVisible = true;
- mgr.Enabled = false;
- shell.ExecuteCommand("showsubfloorforever");
- shell.ExecuteCommand("loadmapacts");
+ _entitySystemManager.GetEntitySystem().MarkersVisible = true;
+ _lightManager.Enabled = false;
+ shell.ExecuteCommand(ShowSubFloorForever.CommandName);
+ shell.ExecuteCommand(LoadMappingActionsCommand.CommandName);
}
}
}
diff --git a/Content.Client/Commands/OpenAHelpCommand.cs b/Content.Client/Commands/OpenAHelpCommand.cs
index 3d098f28a7..114ca51bc9 100644
--- a/Content.Client/Commands/OpenAHelpCommand.cs
+++ b/Content.Client/Commands/OpenAHelpCommand.cs
@@ -1,45 +1,41 @@
-using System;
-using Content.Client.Administration;
-using Content.Client.Administration.Systems;
using Content.Client.UserInterface.Systems.Bwoink;
-using Content.Client.UserInterface.Systems.EscapeMenu;
using Content.Shared.Administration;
using Robust.Client.UserInterface;
using Robust.Shared.Console;
-using Robust.Shared.GameObjects;
using Robust.Shared.Network;
-namespace Content.Client.Commands
+namespace Content.Client.Commands;
+
+[AnyCommand]
+public sealed class OpenAHelpCommand : LocalizedCommands
{
- [AnyCommand]
- public sealed class OpenAHelpCommand : IConsoleCommand
- {
- public string Command => "openahelp";
- public string Description => $"Opens AHelp channel for a given NetUserID, or your personal channel if none given.";
- public string Help => $"{Command} []";
+ [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
+
+ public override string Command => "openahelp";
- public void Execute(IConsoleShell shell, string argStr, string[] args)
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ if (args.Length >= 2)
{
- if (args.Length >= 2)
- {
- shell.WriteLine(Help);
- return;
- }
- if (args.Length == 0)
+ shell.WriteLine(Help);
+ return;
+ }
+ if (args.Length == 0)
+ {
+ _userInterfaceManager.GetUIController().Open();
+ }
+ else
+ {
+ if (Guid.TryParse(args[0], out var guid))
{
- IoCManager.Resolve().GetUIController().Open();
+ var targetUser = new NetUserId(guid);
+ _userInterfaceManager.GetUIController().Open(targetUser);
}
else
{
- if (Guid.TryParse(args[0], out var guid))
- {
- var targetUser = new NetUserId(guid);
- IoCManager.Resolve().GetUIController().Open(targetUser);
- }
- else
- {
- shell.WriteLine("Bad GUID!");
- }
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error"));
}
}
}
diff --git a/Content.Client/Commands/SetMenuVisibilityCommand.cs b/Content.Client/Commands/SetMenuVisibilityCommand.cs
index 91df58a374..ddfb0b1692 100644
--- a/Content.Client/Commands/SetMenuVisibilityCommand.cs
+++ b/Content.Client/Commands/SetMenuVisibilityCommand.cs
@@ -1,54 +1,54 @@
using Content.Client.Verbs;
using JetBrains.Annotations;
using Robust.Shared.Console;
-using Robust.Shared.GameObjects;
-namespace Content.Client.Commands
+namespace Content.Client.Commands;
+
+[UsedImplicitly]
+internal sealed class SetMenuVisibilityCommand : LocalizedCommands
{
- [UsedImplicitly]
- internal sealed class SetMenuVisibilityCommand : IConsoleCommand
- {
- public const string CommandName = "menuvis";
+ [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
- public string Command => CommandName;
- public string Description => "Set restrictions about what entities to show on the entity context menu.";
- public string Help => $"Usage: {Command} [NoFoV] [InContainer] [Invisible] [All]";
+ public override string Command => "menuvis";
- public void Execute(IConsoleShell shell, string argStr, string[] args)
- {
- if (!TryParseArguments(shell, args, out var visibility))
- return;
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
- EntitySystem.Get().Visibility = visibility;
- }
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ if (!TryParseArguments(shell, args, out var visibility))
+ return;
- private bool TryParseArguments(IConsoleShell shell, string[] args, out MenuVisibility visibility)
- {
- visibility = MenuVisibility.Default;
+ _entitySystemManager.GetEntitySystem().Visibility = visibility;
+ }
+
+ private bool TryParseArguments(IConsoleShell shell, string[] args, out MenuVisibility visibility)
+ {
+ visibility = MenuVisibility.Default;
- foreach (var arg in args)
+ foreach (var arg in args)
+ {
+ switch (arg.ToLower())
{
- switch (arg.ToLower())
- {
- case "nofov":
- visibility |= MenuVisibility.NoFov;
- break;
- case "incontainer":
- visibility |= MenuVisibility.InContainer;
- break;
- case "invisible":
- visibility |= MenuVisibility.Invisible;
- break;
- case "all":
- visibility |= MenuVisibility.All;
- break;
- default:
- shell.WriteLine($"Unknown visibility argument '{arg}'. Only 'NoFov', 'InContainer', 'Invisible' or 'All' are valid. Provide no arguments to set to default.");
- return false;
- }
+ // ReSharper disable once StringLiteralTypo
+ case "nofov":
+ visibility |= MenuVisibility.NoFov;
+ break;
+ // ReSharper disable once StringLiteralTypo
+ case "incontainer":
+ visibility |= MenuVisibility.InContainer;
+ break;
+ case "invisible":
+ visibility |= MenuVisibility.Invisible;
+ break;
+ case "all":
+ visibility |= MenuVisibility.All;
+ break;
+ default:
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error", ("arg", arg)));
+ return false;
}
-
- return true;
}
+
+ return true;
}
}
diff --git a/Content.Client/Commands/ShowHealthBarsCommand.cs b/Content.Client/Commands/ShowHealthBarsCommand.cs
new file mode 100644
index 0000000000..bd3e21718f
--- /dev/null
+++ b/Content.Client/Commands/ShowHealthBarsCommand.cs
@@ -0,0 +1,54 @@
+using Content.Shared.Overlays;
+using Robust.Client.Player;
+using Robust.Shared.Console;
+using System.Linq;
+
+namespace Content.Client.Commands;
+
+public sealed class ShowHealthBarsCommand : LocalizedCommands
+{
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+
+ public override string Command => "showhealthbars";
+
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ var player = _playerManager.LocalSession;
+ if (player == null)
+ {
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error-not-player"));
+ return;
+ }
+
+ var playerEntity = player?.AttachedEntity;
+ if (!playerEntity.HasValue)
+ {
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error-no-entity"));
+ return;
+ }
+
+ if (!_entityManager.HasComponent(playerEntity))
+ {
+ var showHealthBarsComponent = new ShowHealthBarsComponent
+ {
+ DamageContainers = args.ToList(),
+ NetSyncEnabled = false
+ };
+
+ _entityManager.AddComponent(playerEntity.Value, showHealthBarsComponent, true);
+
+ shell.WriteLine(LocalizationManager.GetString($"cmd-{Command}-notify-enabled", ("args", string.Join(", ", args))));
+ return;
+ }
+ else
+ {
+ _entityManager.RemoveComponentDeferred(playerEntity.Value);
+ shell.WriteLine(LocalizationManager.GetString($"cmd-{Command}-notify-disabled"));
+ }
+
+ return;
+ }
+}
diff --git a/Content.Client/Commands/ShowMechanismsCommand.cs b/Content.Client/Commands/ShowMechanismsCommand.cs
index b94278f8c9..4e3bb17cb6 100644
--- a/Content.Client/Commands/ShowMechanismsCommand.cs
+++ b/Content.Client/Commands/ShowMechanismsCommand.cs
@@ -1,30 +1,26 @@
-using Content.Shared.Body.Organ;
-using Robust.Client.Console;
+using Content.Shared.Body.Organ;
using Robust.Client.GameObjects;
using Robust.Shared.Console;
-namespace Content.Client.Commands
+namespace Content.Client.Commands;
+
+public sealed class ShowMechanismsCommand : LocalizedCommands
{
- public sealed class ShowMechanismsCommand : IConsoleCommand
- {
- public const string CommandName = "showmechanisms";
+ [Dependency] private readonly IEntityManager _entManager = default!;
- // ReSharper disable once StringLiteralTypo
- public string Command => CommandName;
- public string Description => "Makes mechanisms visible, even when they shouldn't be.";
- public string Help => $"{Command}";
+ public const string CommandName = "showmechanisms";
- public void Execute(IConsoleShell shell, string argStr, string[] args)
- {
- var entityManager = IoCManager.Resolve();
- var query = entityManager.AllEntityQueryEnumerator();
+ public override string Command => CommandName;
- while (query.MoveNext(out _, out var sprite))
- {
- sprite.ContainerOccluded = false;
- }
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
- IoCManager.Resolve().ExecuteCommand("showcontainedcontext");
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ var query = _entManager.AllEntityQueryEnumerator();
+
+ while (query.MoveNext(out _, out var sprite))
+ {
+ sprite.ContainerOccluded = false;
}
}
}
diff --git a/Content.Client/Commands/ToggleHealthOverlayCommand.cs b/Content.Client/Commands/ToggleHealthOverlayCommand.cs
deleted file mode 100644
index 76000f42a1..0000000000
--- a/Content.Client/Commands/ToggleHealthOverlayCommand.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Content.Client.HealthOverlay;
-using Robust.Shared.Console;
-using Robust.Shared.GameObjects;
-
-namespace Content.Client.Commands
-{
- public sealed class ToggleHealthOverlayCommand : IConsoleCommand
- {
- public string Command => "togglehealthoverlay";
- public string Description => "Toggles a health bar above mobs.";
- public string Help => $"Usage: {Command}";
-
- public void Execute(IConsoleShell shell, string argStr, string[] args)
- {
- var system = EntitySystem.Get();
- system.Enabled = !system.Enabled;
-
- shell.WriteLine($"Health overlay system {(system.Enabled ? "enabled" : "disabled")}.");
- }
- }
-}
diff --git a/Content.Client/Commands/ToggleOutlineCommand.cs b/Content.Client/Commands/ToggleOutlineCommand.cs
index 3f02435493..834c3cc995 100644
--- a/Content.Client/Commands/ToggleOutlineCommand.cs
+++ b/Content.Client/Commands/ToggleOutlineCommand.cs
@@ -2,27 +2,24 @@
using Content.Shared.CCVar;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
-using Robust.Shared.IoC;
-namespace Content.Client.Commands
+namespace Content.Client.Commands;
+
+[AnyCommand]
+public sealed class ToggleOutlineCommand : LocalizedCommands
{
- [AnyCommand]
- public sealed class ToggleOutlineCommand : IConsoleCommand
- {
- public string Command => "toggleoutline";
+ [Dependency] private readonly IConfigurationManager _configurationManager = default!;
- public string Description => "Toggles outline drawing on entities.";
+ public override string Command => "toggleoutline";
- public string Help => "";
+ public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
- public void Execute(IConsoleShell shell, string argStr, string[] args)
- {
- var configurationManager = IoCManager.Resolve();
- var cvar = CCVars.OutlineEnabled;
- var old = configurationManager.GetCVar(cvar);
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ var cvar = CCVars.OutlineEnabled;
+ var old = _configurationManager.GetCVar(cvar);
- configurationManager.SetCVar(cvar, !old);
- shell.WriteLine($"Draw outlines set to: {configurationManager.GetCVar(cvar)}");
- }
+ _configurationManager.SetCVar(cvar, !old);
+ shell.WriteLine(LocalizationManager.GetString($"cmd-{Command}-notify", ("state", _configurationManager.GetCVar(cvar))));
}
}
diff --git a/Content.Client/Commands/ZoomCommand.cs b/Content.Client/Commands/ZoomCommand.cs
index 2b8cdcbca9..2bdc85e1fe 100644
--- a/Content.Client/Commands/ZoomCommand.cs
+++ b/Content.Client/Commands/ZoomCommand.cs
@@ -1,26 +1,23 @@
-using System.Numerics;
using Content.Client.Movement.Systems;
using Content.Shared.Movement.Components;
-using Content.Shared.Movement.Systems;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Console;
+using System.Numerics;
namespace Content.Client.Commands;
[UsedImplicitly]
-public sealed class ZoomCommand : IConsoleCommand
+public sealed class ZoomCommand : LocalizedCommands
{
- [Dependency] private readonly IEntityManager _entManager = default!;
- [Dependency] private readonly IEyeManager _eyeMan = default!;
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
- public string Command => "zoom";
- public string Description => Loc.GetString("zoom-command-description");
- public string Help => Loc.GetString("zoom-command-help");
+ public override string Command => "zoom";
- public void Execute(IConsoleShell shell, string argStr, string[] args)
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
Vector2 zoom;
if (args.Length is not (1 or 2))
@@ -31,7 +28,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
if (!float.TryParse(args[0], out var arg0))
{
- shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[0])));
+ shell.WriteError(LocalizationManager.GetString("cmd-parse-failure-float", ("arg", args[0])));
return;
}
@@ -39,7 +36,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
zoom = new(arg0, arg0);
else
{
- shell.WriteError(Loc.GetString("zoom-command-error"));
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error"));
return;
}
@@ -47,7 +44,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (!float.TryParse(args[1], out var arg1))
{
- shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[1])));
+ shell.WriteError(LocalizationManager.GetString("cmd-parse-failure-float", ("arg", args[1])));
return;
}
@@ -55,19 +52,19 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
zoom.Y = arg1;
else
{
- shell.WriteError(Loc.GetString("zoom-command-error"));
+ shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error"));
return;
}
}
- var player = _playerManager.LocalPlayer?.ControlledEntity;
+ var player = _playerManager.LocalSession?.AttachedEntity;
- if (_entManager.TryGetComponent(player, out var content))
+ if (_entityManager.TryGetComponent(player, out var content))
{
- _entManager.System().RequestZoom(player.Value, zoom, true, content);
+ _entityManager.System().RequestZoom(player.Value, zoom, true, content);
return;
}
- _eyeMan.CurrentEye.Zoom = zoom;
+ _eyeManager.CurrentEye.Zoom = zoom;
}
}
diff --git a/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs b/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs
index dc7448aab1..1c94d32bf8 100644
--- a/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs
+++ b/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs
@@ -1,5 +1,7 @@
-using Content.Shared.Communications;
-using Robust.Client.GameObjects;
+using Content.Shared.CCVar;
+using Content.Shared.Chat;
+using Content.Shared.Communications;
+using Robust.Shared.Configuration;
using Robust.Shared.Timing;
namespace Content.Client.Communications.UI
@@ -7,12 +9,15 @@ namespace Content.Client.Communications.UI
public sealed class CommunicationsConsoleBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
[ViewVariables]
private CommunicationsConsoleMenu? _menu;
[ViewVariables]
public bool CanAnnounce { get; private set; }
+ [ViewVariables]
+ public bool CanBroadcast { get; private set; }
[ViewVariables]
public bool CanCall { get; private set; }
@@ -63,22 +68,14 @@ public void EmergencyShuttleButtonPressed()
public void AnnounceButtonPressed(string message)
{
- var msg = (message.Length <= 256 ? message.Trim() : $"{message.Trim().Substring(0, 256)}...").ToCharArray();
-
- // No more than 2 newlines, other replaced to spaces
- var newlines = 0;
- for (var i = 0; i < msg.Length; i++)
- {
- if (msg[i] != '\n')
- continue;
-
- if (newlines >= 2)
- msg[i] = ' ';
-
- newlines++;
- }
+ var maxLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength);
+ var msg = SharedChatSystem.SanitizeAnnouncement(message, maxLength);
+ SendMessage(new CommunicationsConsoleAnnounceMessage(msg));
+ }
- SendMessage(new CommunicationsConsoleAnnounceMessage(new string(msg)));
+ public void BroadcastButtonPressed(string message)
+ {
+ SendMessage(new CommunicationsConsoleBroadcastMessage(message));
}
public void CallShuttle()
@@ -99,6 +96,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
return;
CanAnnounce = commsState.CanAnnounce;
+ CanBroadcast = commsState.CanBroadcast;
CanCall = commsState.CanCall;
_expectedCountdownTime = commsState.ExpectedCountdownEnd;
CountdownStarted = commsState.CountdownStarted;
@@ -112,6 +110,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
_menu.AlertLevelButton.Disabled = !AlertLevelSelectable;
_menu.EmergencyShuttleButton.Disabled = !CanCall;
_menu.AnnounceButton.Disabled = !CanAnnounce;
+ _menu.BroadcastButton.Disabled = !CanBroadcast;
}
}
diff --git a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml
index 86c0b4e2d5..ea2f77d457 100644
--- a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml
+++ b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml
@@ -5,6 +5,7 @@
+
diff --git a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs
index 8ab444f9ba..90643e45cf 100644
--- a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs
+++ b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs
@@ -23,9 +23,12 @@ public CommunicationsConsoleMenu(CommunicationsConsoleBoundUserInterface owner)
var loc = IoCManager.Resolve();
MessageInput.Placeholder = new Rope.Leaf(loc.GetString("comms-console-menu-announcement-placeholder"));
- AnnounceButton.OnPressed += (_) => Owner.AnnounceButtonPressed(Rope.Collapse(MessageInput.TextRope).Trim());
+ AnnounceButton.OnPressed += (_) => Owner.AnnounceButtonPressed(Rope.Collapse(MessageInput.TextRope));
AnnounceButton.Disabled = !owner.CanAnnounce;
+ BroadcastButton.OnPressed += (_) => Owner.BroadcastButtonPressed(Rope.Collapse(MessageInput.TextRope));
+ BroadcastButton.Disabled = !owner.CanBroadcast;
+
AlertLevelButton.OnItemSelected += args =>
{
var metadata = AlertLevelButton.GetItemMetadata(args.Id);
diff --git a/Content.Client/Construction/ConstructionSystem.cs b/Content.Client/Construction/ConstructionSystem.cs
index 4035c68cc7..940538670c 100644
--- a/Content.Client/Construction/ConstructionSystem.cs
+++ b/Content.Client/Construction/ConstructionSystem.cs
@@ -81,27 +81,30 @@ public override void Shutdown()
private void HandleConstructionGhostExamined(EntityUid uid, ConstructionGhostComponent component, ExaminedEvent args)
{
- if (component.Prototype == null) return;
+ if (component.Prototype == null)
+ return;
- args.PushMarkup(Loc.GetString(
- "construction-ghost-examine-message",
- ("name", component.Prototype.Name)));
+ using (args.PushGroup(nameof(ConstructionGhostComponent)))
+ {
+ args.PushMarkup(Loc.GetString(
+ "construction-ghost-examine-message",
+ ("name", component.Prototype.Name)));
- if (!_prototypeManager.TryIndex(component.Prototype.Graph, out ConstructionGraphPrototype? graph))
- return;
+ if (!_prototypeManager.TryIndex(component.Prototype.Graph, out ConstructionGraphPrototype? graph))
+ return;
- var startNode = graph.Nodes[component.Prototype.StartNode];
+ var startNode = graph.Nodes[component.Prototype.StartNode];
- if (!graph.TryPath(component.Prototype.StartNode, component.Prototype.TargetNode, out var path) ||
- !startNode.TryGetEdge(path[0].Name, out var edge))
- {
- return;
- }
+ if (!graph.TryPath(component.Prototype.StartNode, component.Prototype.TargetNode, out var path) ||
+ !startNode.TryGetEdge(path[0].Name, out var edge))
+ {
+ return;
+ }
- foreach (ConstructionGraphStep step in edge.Steps)
- {
- args.Message.PushNewline();
- step.DoExamine(args);
+ foreach (var step in edge.Steps)
+ {
+ step.DoExamine(args);
+ }
}
}
diff --git a/Content.Client/Construction/FlatpackSystem.cs b/Content.Client/Construction/FlatpackSystem.cs
new file mode 100644
index 0000000000..911ff1279c
--- /dev/null
+++ b/Content.Client/Construction/FlatpackSystem.cs
@@ -0,0 +1,48 @@
+using Content.Shared.Construction;
+using Content.Shared.Construction.Components;
+using Robust.Client.GameObjects;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Construction;
+
+///
+public sealed class FlatpackSystem : SharedFlatpackSystem
+{
+ [Dependency] private readonly AppearanceSystem _appearance = default!;
+
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnAppearanceChange);
+ }
+
+ private void OnAppearanceChange(Entity ent, ref AppearanceChangeEvent args)
+ {
+ var (_, comp) = ent;
+ if (!_appearance.TryGetData(ent, FlatpackVisuals.Machine, out var machineBoardId) || args.Sprite == null)
+ return;
+
+ if (!PrototypeManager.TryIndex(machineBoardId, out var machineBoardPrototype))
+ return;
+
+ if (!machineBoardPrototype.TryGetComponent(out var sprite))
+ return;
+
+ Color? color = null;
+ foreach (var layer in sprite.AllLayers)
+ {
+ if (layer.RsiState.Name is not { } spriteState)
+ continue;
+
+ if (!comp.BoardColors.TryGetValue(spriteState, out var c))
+ continue;
+ color = c;
+ break;
+ }
+
+ if (color != null)
+ args.Sprite.LayerSetColor(FlatpackVisualLayers.Overlay, color.Value);
+ }
+}
diff --git a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
index 28cf3ba16c..9a09436176 100644
--- a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
+++ b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
@@ -394,7 +394,7 @@ private void SystemOnToggleMenu(object? sender, EventArgs eventArgs)
if (IsAtFront)
{
WindowOpen = false;
- _uiManager.GetActiveUIWidget().CraftingButton.Pressed = false; // This does not call CraftingButtonToggled
+ _uiManager.GetActiveUIWidget().CraftingButton.SetClickPressed(false); // This does not call CraftingButtonToggled
}
else
_constructionView.MoveToFront();
@@ -402,7 +402,7 @@ private void SystemOnToggleMenu(object? sender, EventArgs eventArgs)
else
{
WindowOpen = true;
- _uiManager.GetActiveUIWidget().CraftingButton.Pressed = true; // This does not call CraftingButtonToggled
+ _uiManager.GetActiveUIWidget().CraftingButton.SetClickPressed(true); // This does not call CraftingButtonToggled
}
}
diff --git a/Content.Client/Construction/UI/FlatpackCreatorBoundUserInterface.cs b/Content.Client/Construction/UI/FlatpackCreatorBoundUserInterface.cs
new file mode 100644
index 0000000000..86f1b8b83c
--- /dev/null
+++ b/Content.Client/Construction/UI/FlatpackCreatorBoundUserInterface.cs
@@ -0,0 +1,40 @@
+using Content.Shared.Construction.Components;
+using JetBrains.Annotations;
+
+namespace Content.Client.Construction.UI
+{
+ [UsedImplicitly]
+ public sealed class FlatpackCreatorBoundUserInterface : BoundUserInterface
+ {
+ [ViewVariables]
+ private FlatpackCreatorMenu? _menu;
+
+ public FlatpackCreatorBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _menu = new FlatpackCreatorMenu(Owner);
+ _menu.OnClose += Close;
+
+ _menu.PackButtonPressed += () =>
+ {
+ SendMessage(new FlatpackCreatorStartPackBuiMessage());
+ };
+
+ _menu.OpenCentered();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+
+ _menu?.Dispose();
+ }
+ }
+}
diff --git a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml
new file mode 100644
index 0000000000..eec5c229cb
--- /dev/null
+++ b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
new file mode 100644
index 0000000000..ad19bc30f4
--- /dev/null
+++ b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
@@ -0,0 +1,168 @@
+using System.Linq;
+using Content.Client.Materials;
+using Content.Client.Message;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Construction.Components;
+using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Materials;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Construction.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class FlatpackCreatorMenu : FancyWindow
+{
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ private readonly ItemSlotsSystem _itemSlots;
+ private readonly FlatpackSystem _flatpack;
+ private readonly MaterialStorageSystem _materialStorage;
+ private readonly SpriteSystem _spriteSystem;
+
+ private readonly EntityUid _owner;
+
+ [ValidatePrototypeId]
+ public const string NoBoardEffectId = "FlatpackerNoBoardEffect";
+
+ private EntityUid? _currentBoard = EntityUid.Invalid;
+ private EntityUid? _machinePreview;
+
+ public event Action? PackButtonPressed;
+
+ public FlatpackCreatorMenu(EntityUid uid)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ _itemSlots = _entityManager.System();
+ _flatpack = _entityManager.System();
+ _materialStorage = _entityManager.System();
+ _spriteSystem = _entityManager.System();
+
+ _owner = uid;
+
+ PackButton.OnPressed += _ => PackButtonPressed?.Invoke();
+
+ MaterialStorageControl.SetOwner(uid);
+ InsertLabel.SetMarkup(Loc.GetString("flatpacker-ui-insert-board"));
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ if (_machinePreview is not { } && _entityManager.Deleted(_machinePreview))
+ {
+ _machinePreview = null;
+ MachineSprite.SetEntity(_machinePreview);
+ }
+
+ if (!_entityManager.TryGetComponent(_owner, out var flatpacker) ||
+ !_itemSlots.TryGetSlot(_owner, flatpacker.SlotId, out var itemSlot))
+ return;
+
+ MachineBoardComponent? machineBoardComp = null;
+ if (flatpacker.Packing)
+ {
+ PackButton.Disabled = true;
+ }
+ else if (_currentBoard != null)
+ {
+ Dictionary cost;
+ if (_entityManager.TryGetComponent(_currentBoard, out machineBoardComp) &&
+ machineBoardComp.Prototype is not null)
+ cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
+ else
+ cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker));
+
+ PackButton.Disabled = !_materialStorage.CanChangeMaterialAmount(_owner, cost);
+ }
+
+ if (_currentBoard == itemSlot.Item)
+ return;
+
+ if (_machinePreview != null)
+ _entityManager.DeleteEntity(_machinePreview);
+
+ _currentBoard = itemSlot.Item;
+ CostHeaderLabel.Visible = _currentBoard != null;
+ InsertLabel.Visible = _currentBoard == null;
+
+ if (_currentBoard is not null)
+ {
+ string? prototype = null;
+ Dictionary? cost = null;
+
+ if (machineBoardComp != null || _entityManager.TryGetComponent(_currentBoard, out machineBoardComp))
+ {
+ prototype = machineBoardComp.Prototype;
+ cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
+ }
+ else if (_entityManager.TryGetComponent(_currentBoard, out var computerBoard))
+ {
+ prototype = computerBoard.Prototype;
+ cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker));
+ }
+
+ if (prototype is not null && cost is not null)
+ {
+ var proto = _prototypeManager.Index(prototype);
+ _machinePreview = _entityManager.Spawn(proto.ID);
+ _spriteSystem.ForceUpdate(_machinePreview.Value);
+ MachineNameLabel.SetMessage(proto.Name);
+ CostLabel.SetMarkup(GetCostString(cost));
+ }
+ }
+ else
+ {
+ _machinePreview = _entityManager.Spawn(NoBoardEffectId);
+ CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
+ MachineNameLabel.SetMessage(" ");
+ PackButton.Disabled = true;
+ }
+
+ MachineSprite.SetEntity(_machinePreview);
+ }
+
+ private string GetCostString(Dictionary costs)
+ {
+ var orderedCosts = costs.OrderBy(p => p.Value).ToArray();
+ var msg = new FormattedMessage();
+ for (var i = 0; i < orderedCosts.Length; i++)
+ {
+ var (mat, amount) = orderedCosts[i];
+
+ var matProto = _prototypeManager.Index(mat);
+
+ var sheetVolume = _materialStorage.GetSheetVolume(matProto);
+ var sheets = (float) -amount / sheetVolume;
+ var amountText = Loc.GetString("lathe-menu-material-amount",
+ ("amount", sheets),
+ ("unit", Loc.GetString(matProto.Unit)));
+ var text = Loc.GetString("lathe-menu-tooltip-display",
+ ("amount", amountText),
+ ("material", Loc.GetString(matProto.Name)));
+
+ msg.AddMarkup(text);
+
+ if (i != orderedCosts.Length - 1)
+ msg.PushNewline();
+ }
+
+ return msg.ToMarkup();
+ }
+
+ public override void Close()
+ {
+ base.Close();
+
+ _entityManager.DeleteEntity(_machinePreview);
+ _machinePreview = null;
+ }
+}
diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj
index 33ee0e0a34..0e15fadfae 100644
--- a/Content.Client/Content.Client.csproj
+++ b/Content.Client/Content.Client.csproj
@@ -13,8 +13,8 @@
AnyCPU
-
-
+
+
diff --git a/Content.Client/ContextMenu/UI/EntityMenuPresenterGrouping.cs b/Content.Client/ContextMenu/UI/EntityMenuPresenterGrouping.cs
index d8804a1218..3345c11499 100644
--- a/Content.Client/ContextMenu/UI/EntityMenuPresenterGrouping.cs
+++ b/Content.Client/ContextMenu/UI/EntityMenuPresenterGrouping.cs
@@ -18,7 +18,7 @@ private List> GroupEntities(IEnumerable entities, int
{
if (GroupingContextMenuType == 0)
{
- var newEntities = entities.GroupBy(e => Identity.Name(e, _entityManager) + (_entityManager.GetComponent(e).EntityPrototype?.ID ?? string.Empty)).ToList();
+ var newEntities = entities.GroupBy(e => Identity.Name(e, _entityManager)).ToList();
return newEntities.Select(grp => grp.ToList()).ToList();
}
else
diff --git a/Content.Client/ContextMenu/UI/EntityMenuUIController.cs b/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
index 03d5a9486b..039c03601a 100644
--- a/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
+++ b/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
@@ -7,6 +7,7 @@
using Content.Client.Verbs.UI;
using Content.Shared.CCVar;
using Content.Shared.Examine;
+using Content.Shared.IdentityManagement;
using Content.Shared.Input;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -91,7 +92,10 @@ public void OpenRootMenu(List entities)
var entitySpriteStates = GroupEntities(entities);
var orderedStates = entitySpriteStates.ToList();
- orderedStates.Sort((x, y) => string.CompareOrdinal(_entityManager.GetComponent(x.First()).EntityPrototype?.Name, _entityManager.GetComponent(y.First()).EntityPrototype?.Name));
+ orderedStates.Sort((x, y) => string.Compare(
+ Identity.Name(x.First(), _entityManager),
+ Identity.Name(y.First(), _entityManager),
+ StringComparison.CurrentCulture));
Elements.Clear();
AddToUI(orderedStates);
diff --git a/Content.Client/Crayon/CrayonSystem.cs b/Content.Client/Crayon/CrayonSystem.cs
index e6da15c3bc..dc03979481 100644
--- a/Content.Client/Crayon/CrayonSystem.cs
+++ b/Content.Client/Crayon/CrayonSystem.cs
@@ -18,7 +18,7 @@ public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnCrayonHandleState);
- SubscribeLocalEvent(OnCrayonItemStatus);
+ Subs.ItemStatus(ent => new StatusControl(ent));
}
private static void OnCrayonHandleState(EntityUid uid, CrayonComponent component, ref ComponentHandleState args)
@@ -33,11 +33,6 @@ private static void OnCrayonHandleState(EntityUid uid, CrayonComponent component
component.UIUpdateNeeded = true;
}
- private static void OnCrayonItemStatus(EntityUid uid, CrayonComponent component, ItemStatusCollectMessage args)
- {
- args.Controls.Add(new StatusControl(component));
- }
-
private sealed class StatusControl : Control
{
private readonly CrayonComponent _parent;
diff --git a/Content.Client/DebugMon/DebugMonitorSystem.cs b/Content.Client/DebugMon/DebugMonitorSystem.cs
index c64e111b65..fb5cd4f51a 100644
--- a/Content.Client/DebugMon/DebugMonitorSystem.cs
+++ b/Content.Client/DebugMon/DebugMonitorSystem.cs
@@ -19,7 +19,5 @@ public override void FrameUpdate(float frameTime)
{
if (!_admin.IsActive() && _cfg.GetCVar(CCVars.DebugCoordinatesAdminOnly))
_userInterface.DebugMonitors.SetMonitor(DebugMonitor.Coords, false);
- else
- _userInterface.DebugMonitors.SetMonitor(DebugMonitor.Coords, true);
}
-}
\ No newline at end of file
+}
diff --git a/Content.Client/Decals/Overlays/DecalOverlay.cs b/Content.Client/Decals/Overlays/DecalOverlay.cs
index 6fcd48264b..60b24c154b 100644
--- a/Content.Client/Decals/Overlays/DecalOverlay.cs
+++ b/Content.Client/Decals/Overlays/DecalOverlay.cs
@@ -2,18 +2,17 @@
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
+using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Client.Decals.Overlays
{
- public sealed class DecalOverlay : Overlay
+ public sealed class DecalOverlay : GridOverlay
{
private readonly SpriteSystem _sprites;
private readonly IEntityManager _entManager;
private readonly IPrototypeManager _prototypeManager;
- public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities;
-
private readonly Dictionary _cachedTextures = new(64);
public DecalOverlay(
@@ -28,52 +27,58 @@ public DecalOverlay(
protected override void Draw(in OverlayDrawArgs args)
{
+ if (args.MapId == MapId.Nullspace)
+ return;
+
+ var grid = Grid;
+
+ if (!_entManager.TryGetComponent(grid, out DecalGridComponent? decalGrid) ||
+ !_entManager.TryGetComponent(grid, out TransformComponent? xform))
+ {
+ return;
+ }
+
+ if (xform.MapID != args.MapId)
+ return;
+
// Shouldn't need to clear cached textures unless the prototypes get reloaded.
var handle = args.WorldHandle;
var xformSystem = _entManager.System();
var eyeAngle = args.Viewport.Eye?.Rotation ?? Angle.Zero;
- var gridQuery = _entManager.AllEntityQueryEnumerator();
+ var zIndexDictionary = decalGrid.DecalRenderIndex;
- while (gridQuery.MoveNext(out var decalGrid, out var xform))
- {
- if (xform.MapID != args.MapId)
- continue;
+ if (zIndexDictionary.Count == 0)
+ return;
- var zIndexDictionary = decalGrid.DecalRenderIndex;
+ var (_, worldRot, worldMatrix) = xformSystem.GetWorldPositionRotationMatrix(xform);
- if (zIndexDictionary.Count == 0)
- continue;
+ handle.SetTransform(worldMatrix);
- var (_, worldRot, worldMatrix) = xformSystem.GetWorldPositionRotationMatrix(xform);
+ foreach (var decals in zIndexDictionary.Values)
+ {
+ foreach (var decal in decals.Values)
+ {
+ if (!_cachedTextures.TryGetValue(decal.Id, out var cache) && _prototypeManager.TryIndex(decal.Id, out var decalProto))
+ {
+ cache = (_sprites.Frame0(decalProto.Sprite), decalProto.SnapCardinals);
+ _cachedTextures[decal.Id] = cache;
+ }
- handle.SetTransform(worldMatrix);
+ var cardinal = Angle.Zero;
- foreach (var decals in zIndexDictionary.Values)
- {
- foreach (var decal in decals.Values)
+ if (cache.SnapCardinals)
{
- if (!_cachedTextures.TryGetValue(decal.Id, out var cache) && _prototypeManager.TryIndex(decal.Id, out var decalProto))
- {
- cache = (_sprites.Frame0(decalProto.Sprite), decalProto.SnapCardinals);
- _cachedTextures[decal.Id] = cache;
- }
-
- var cardinal = Angle.Zero;
-
- if (cache.SnapCardinals)
- {
- var worldAngle = eyeAngle + worldRot;
- cardinal = worldAngle.GetCardinalDir().ToAngle();
- }
-
- var angle = decal.Angle - cardinal;
-
- if (angle.Equals(Angle.Zero))
- handle.DrawTexture(cache.Texture, decal.Coordinates, decal.Color);
- else
- handle.DrawTexture(cache.Texture, decal.Coordinates, angle, decal.Color);
+ var worldAngle = eyeAngle + worldRot;
+ cardinal = worldAngle.GetCardinalDir().ToAngle();
}
+
+ var angle = decal.Angle - cardinal;
+
+ if (angle.Equals(Angle.Zero))
+ handle.DrawTexture(cache.Texture, decal.Coordinates, decal.Color);
+ else
+ handle.DrawTexture(cache.Texture, decal.Coordinates, angle, decal.Color);
}
}
diff --git a/Content.Client/Doors/AirlockSystem.cs b/Content.Client/Doors/AirlockSystem.cs
index cc68d09039..4837adf460 100644
--- a/Content.Client/Doors/AirlockSystem.cs
+++ b/Content.Client/Doors/AirlockSystem.cs
@@ -95,14 +95,16 @@ private void OnAppearanceChange(EntityUid uid, AirlockComponent comp, ref Appear
if (_appearanceSystem.TryGetData(uid, DoorVisuals.Powered, out var powered, args.Component) && powered)
{
boltedVisible = _appearanceSystem.TryGetData(uid, DoorVisuals.BoltLights, out var lights, args.Component)
- && lights && state == DoorState.Closed;
+ && lights && (state == DoorState.Closed || state == DoorState.Welded);
+
emergencyLightsVisible = _appearanceSystem.TryGetData(uid, DoorVisuals.EmergencyLights, out var eaLights, args.Component) && eaLights;
unlitVisible =
- state == DoorState.Closing
+ (state == DoorState.Closing
|| state == DoorState.Opening
|| state == DoorState.Denying
|| (state == DoorState.Open && comp.OpenUnlitVisible)
- || (_appearanceSystem.TryGetData(uid, DoorVisuals.ClosedLights, out var closedLights, args.Component) && closedLights);
+ || (_appearanceSystem.TryGetData(uid, DoorVisuals.ClosedLights, out var closedLights, args.Component) && closedLights))
+ && !boltedVisible && !emergencyLightsVisible; ;
}
args.Sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible);
@@ -115,6 +117,7 @@ private void OnAppearanceChange(EntityUid uid, AirlockComponent comp, ref Appear
&& state != DoorState.Open
&& state != DoorState.Opening
&& state != DoorState.Closing
+ && !boltedVisible
);
}
}
diff --git a/Content.Client/Effects/ColorFlashEffectSystem.cs b/Content.Client/Effects/ColorFlashEffectSystem.cs
index 9a84401948..af0bd4b600 100644
--- a/Content.Client/Effects/ColorFlashEffectSystem.cs
+++ b/Content.Client/Effects/ColorFlashEffectSystem.cs
@@ -11,6 +11,7 @@ public sealed class ColorFlashEffectSystem : SharedColorFlashEffectSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
+ [Dependency] private readonly IComponentFactory _factory = default!;
///
/// It's a little on the long side but given we use multiple colours denoting what happened it makes it easier to register.
@@ -86,8 +87,13 @@ private void OnColorFlashEffect(ColorFlashEffectEvent ev)
continue;
}
- var player = EnsureComp(ent);
- player.NetSyncEnabled = false;
+ if (!TryComp(ent, out AnimationPlayerComponent? player))
+ {
+ player = (AnimationPlayerComponent) _factory.GetComponent(typeof(AnimationPlayerComponent));
+ player.Owner = ent;
+ player.NetSyncEnabled = false;
+ AddComp(ent, player);
+ }
// Need to stop the existing animation first to ensure the sprite color is fixed.
// Otherwise we might lerp to a red colour instead.
@@ -111,8 +117,14 @@ private void OnColorFlashEffect(ColorFlashEffectEvent ev)
if (animation == null)
continue;
- var comp = EnsureComp(ent);
- comp.NetSyncEnabled = false;
+ if (!TryComp(ent, out ColorFlashEffectComponent? comp))
+ {
+ comp = (ColorFlashEffectComponent) _factory.GetComponent(typeof(ColorFlashEffectComponent));
+ comp.Owner = ent;
+ comp.NetSyncEnabled = false;
+ AddComp(ent, comp);
+ }
+
comp.Color = sprite.Color;
_animation.Play((ent, player), animation, AnimationKey);
}
diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs
index f757726f14..08ee77dad2 100644
--- a/Content.Client/Entry/EntryPoint.cs
+++ b/Content.Client/Entry/EntryPoint.cs
@@ -108,7 +108,6 @@ public override void Init()
_prototypeManager.RegisterIgnore("npcFaction");
_prototypeManager.RegisterIgnore("lobbyBackground");
_prototypeManager.RegisterIgnore("advertisementsPack");
- _prototypeManager.RegisterIgnore("salvageMap");
_prototypeManager.RegisterIgnore("gamePreset");
_prototypeManager.RegisterIgnore("noiseChannel");
_prototypeManager.RegisterIgnore("spaceBiome");
diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs
index 59460cec56..32ba78085a 100644
--- a/Content.Client/Examine/ExamineSystem.cs
+++ b/Content.Client/Examine/ExamineSystem.cs
@@ -366,13 +366,14 @@ public void DoExamine(EntityUid entity, bool centeredOnCursor = true, EntityUid?
var canSeeClearly = !HasComp(playerEnt);
OpenTooltip(playerEnt.Value, entity, centeredOnCursor, false, knowTarget: canSeeClearly);
- if (IsClientSide(entity)
- || _client.RunLevel == ClientRunLevel.SinglePlayerGame) // i.e. a replay
- {
- message = GetExamineText(entity, playerEnt);
- UpdateTooltipInfo(playerEnt.Value, entity, message);
- }
- else
+
+ // Always update tooltip info from client first.
+ // If we get it wrong, server will correct us later anyway.
+ // This will usually be correct (barring server-only components, which generally only adds, not replaces text)
+ message = GetExamineText(entity, playerEnt);
+ UpdateTooltipInfo(playerEnt.Value, entity, message);
+
+ if (!IsClientSide(entity))
{
// Ask server for extra examine info.
if (entity != _lastExaminedEntity)
@@ -383,7 +384,6 @@ public void DoExamine(EntityUid entity, bool centeredOnCursor = true, EntityUid?
}
RaiseLocalEvent(entity, new ClientExaminedEvent(entity, playerEnt.Value));
-
_lastExaminedEntity = entity;
}
diff --git a/Content.Client/Explosion/ExplosionOverlaySystem.cs b/Content.Client/Explosion/ExplosionOverlaySystem.cs
index 064b068a97..7ced95d2c1 100644
--- a/Content.Client/Explosion/ExplosionOverlaySystem.cs
+++ b/Content.Client/Explosion/ExplosionOverlaySystem.cs
@@ -3,8 +3,8 @@
using Robust.Client.ResourceManagement;
using Robust.Shared.GameStates;
using Robust.Shared.Graphics.RSI;
+using Robust.Shared.Map;
using Robust.Shared.Prototypes;
-using Robust.Shared.Utility;
namespace Content.Client.Explosion;
@@ -18,6 +18,7 @@ public sealed class ExplosionOverlaySystem : EntitySystem
[Dependency] private readonly IResourceCache _resCache = default!;
[Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly SharedPointLightSystem _lights = default!;
+ [Dependency] private readonly IMapManager _mapMan = default!;
public override void Initialize()
{
@@ -51,7 +52,7 @@ private void OnExplosionHandleState(EntityUid uid, ExplosionVisualsComponent com
private void OnCompRemove(EntityUid uid, ExplosionVisualsComponent component, ComponentRemove args)
{
- if (TryComp(uid, out ExplosionVisualsTexturesComponent? textures))
+ if (TryComp(uid, out ExplosionVisualsTexturesComponent? textures) && !Deleted(textures.LightEntity))
QueueDel(textures.LightEntity);
}
@@ -65,15 +66,20 @@ private void OnExplosionInit(EntityUid uid, ExplosionVisualsComponent component,
return;
}
- // spawn in a client-side light source at the epicenter
- var lightEntity = Spawn("ExplosionLight", component.Epicenter);
- var light = _lights.EnsureLight(lightEntity);
+ // Map may have been deleted.
+ if (_mapMan.MapExists(component.Epicenter.MapId))
+ {
+ // spawn in a client-side light source at the epicenter
+ var lightEntity = Spawn("ExplosionLight", component.Epicenter);
+ var light = _lights.EnsureLight(lightEntity);
+
+ _lights.SetRadius(lightEntity, component.Intensity.Count, light);
+ _lights.SetEnergy(lightEntity, component.Intensity.Count, light);
+ _lights.SetColor(lightEntity, type.LightColor, light);
+ textures.LightEntity = lightEntity;
+ }
- _lights.SetRadius(lightEntity, component.Intensity.Count, light);
- _lights.SetEnergy(lightEntity, component.Intensity.Count, light);
- _lights.SetColor(lightEntity, type.LightColor, light);
- textures.LightEntity = lightEntity;
textures.FireColor = type.FireColor;
textures.IntensityPerState = type.IntensityPerState;
diff --git a/Content.Client/Eye/Blinding/BlindOverlay.cs b/Content.Client/Eye/Blinding/BlindOverlay.cs
index 83c6cd23e6..1949b170ab 100644
--- a/Content.Client/Eye/Blinding/BlindOverlay.cs
+++ b/Content.Client/Eye/Blinding/BlindOverlay.cs
@@ -1,9 +1,12 @@
+using Content.Client.Movement.Systems;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Content.Shared.Eye.Blinding;
using Content.Shared.Eye.Blinding.Components;
+using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Systems;
namespace Content.Client.Eye.Blinding
{
@@ -11,9 +14,8 @@ public sealed class BlindOverlay : Overlay
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] IEntityManager _entityManager = default!;
- [Dependency] ILightManager _lightManager = default!;
-
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly ILightManager _lightManager = default!;
public override bool RequestScreenTexture => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
@@ -30,13 +32,13 @@ public BlindOverlay()
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
- if (!_entityManager.TryGetComponent(_playerManager.LocalPlayer?.ControlledEntity, out EyeComponent? eyeComp))
+ if (!_entityManager.TryGetComponent(_playerManager.LocalSession?.AttachedEntity, out EyeComponent? eyeComp))
return false;
if (args.Viewport.Eye != eyeComp.Eye)
return false;
- var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
+ var playerEntity = _playerManager.LocalSession?.AttachedEntity;
if (playerEntity == null)
return false;
@@ -64,6 +66,11 @@ protected override void Draw(in OverlayDrawArgs args)
if (ScreenTexture == null)
return;
+ var playerEntity = _playerManager.LocalSession?.AttachedEntity;
+
+ if (playerEntity == null)
+ return;
+
if (!_blindableComponent.GraceFrame)
{
_blindableComponent.LightSetup = true; // Ok we touched the lights
@@ -73,6 +80,11 @@ protected override void Draw(in OverlayDrawArgs args)
_blindableComponent.GraceFrame = false;
}
+ if (_entityManager.TryGetComponent(playerEntity, out var content))
+ {
+ _circleMaskShader?.SetParameter("Zoom", content.Zoom.X);
+ }
+
_greyscaleShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
var worldHandle = args.WorldHandle;
diff --git a/Content.Client/Eye/Blinding/BlurryVisionOverlay.cs b/Content.Client/Eye/Blinding/BlurryVisionOverlay.cs
index 7d0e7916da..b56b091309 100644
--- a/Content.Client/Eye/Blinding/BlurryVisionOverlay.cs
+++ b/Content.Client/Eye/Blinding/BlurryVisionOverlay.cs
@@ -1,9 +1,10 @@
using Robust.Client.Graphics;
using Robust.Client.Player;
+using Content.Shared.CCVar;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
-using Content.Shared.Eye.Blinding;
using Content.Shared.Eye.Blinding.Components;
+using Robust.Shared.Configuration;
namespace Content.Client.Eye.Blinding
{
@@ -11,24 +12,45 @@ public sealed class BlurryVisionOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IConfigurationManager _configManager = default!;
+ public override bool RequestScreenTexture => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
+ private readonly ShaderInstance _cataractsShader;
+ private readonly ShaderInstance _circleMaskShader;
private float _magnitude;
+ private float _correctionPower = 2.0f;
+
+ private const float Distortion_Pow = 2.0f; // Exponent for the distortion effect
+ private const float Cloudiness_Pow = 1.0f; // Exponent for the cloudiness effect
+
+ private const float NoMotion_Radius = 30.0f; // Base radius for the nomotion variant at its full strength
+ private const float NoMotion_Pow = 0.2f; // Exponent for the nomotion variant's gradient
+ private const float NoMotion_Max = 8.0f; // Max value for the nomotion variant's gradient
+ private const float NoMotion_Mult = 0.75f; // Multiplier for the nomotion variant
public BlurryVisionOverlay()
{
IoCManager.InjectDependencies(this);
+ _cataractsShader = _prototypeManager.Index("Cataracts").InstanceUnique();
+ _circleMaskShader = _prototypeManager.Index("CircleMask").InstanceUnique();
+
+ _circleMaskShader.SetParameter("CircleMinDist", 0.0f);
+ _circleMaskShader.SetParameter("CirclePow", NoMotion_Pow);
+ _circleMaskShader.SetParameter("CircleMax", NoMotion_Max);
+ _circleMaskShader.SetParameter("CircleMult", NoMotion_Mult);
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
- if (!_entityManager.TryGetComponent(_playerManager.LocalPlayer?.ControlledEntity, out EyeComponent? eyeComp))
+ if (!_entityManager.TryGetComponent(_playerManager.LocalSession?.AttachedEntity, out EyeComponent? eyeComp))
return false;
if (args.Viewport.Eye != eyeComp.Eye)
return false;
- var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
+ var playerEntity = _playerManager.LocalSession?.AttachedEntity;
if (playerEntity == null)
return false;
@@ -44,21 +66,52 @@ protected override bool BeforeDraw(in OverlayDrawArgs args)
return false;
_magnitude = blurComp.Magnitude;
+ _correctionPower = blurComp.CorrectionPower;
return true;
}
protected override void Draw(in OverlayDrawArgs args)
{
- // TODO make this better.
- // This is a really shitty effect.
- // Maybe gradually shrink the view-size?
- // Make the effect only apply to the edge of the viewport?
- // Actually make it blurry??
- var opacity = 1f * _magnitude / BlurryVisionComponent.MaxMagnitude;
+ if (ScreenTexture == null)
+ return;
+
+ var playerEntity = _playerManager.LocalSession?.AttachedEntity;
+
var worldHandle = args.WorldHandle;
var viewport = args.WorldBounds;
- worldHandle.SetTransform(Matrix3.Identity);
- worldHandle.DrawRect(viewport, Color.Black.WithAlpha(opacity));
+ var strength = (float) Math.Pow(Math.Min(_magnitude / BlurryVisionComponent.MaxMagnitude, 1.0f), _correctionPower);
+
+ var zoom = 1.0f;
+ if (_entityManager.TryGetComponent(playerEntity, out var eyeComponent))
+ {
+ zoom = eyeComponent.Zoom.X;
+ }
+
+ // While the cataracts shader is designed to be tame enough to keep motion sickness at bay, the general waviness means that those who are particularly sensitive to motion sickness will probably hurl.
+ // So the reasonable alternative here is to replace it with a static effect! Specifically, one that replicates the blindness effect seen across most SS13 servers.
+ if (_configManager.GetCVar(CCVars.ReducedMotion))
+ {
+ _circleMaskShader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
+ _circleMaskShader.SetParameter("Zoom", zoom);
+ _circleMaskShader.SetParameter("CircleRadius", NoMotion_Radius / strength);
+
+ worldHandle.UseShader(_circleMaskShader);
+ worldHandle.DrawRect(viewport, Color.White);
+ worldHandle.UseShader(null);
+ return;
+ }
+
+ _cataractsShader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
+ _cataractsShader.SetParameter("LIGHT_TEXTURE", args.Viewport.LightRenderTarget.Texture); // this is a little hacky but we spent way longer than we'd like to admit trying to do this a cleaner way to no avail
+
+ _cataractsShader.SetParameter("Zoom", zoom);
+
+ _cataractsShader.SetParameter("DistortionScalar", (float) Math.Pow(strength, Distortion_Pow));
+ _cataractsShader.SetParameter("CloudinessScalar", (float) Math.Pow(strength, Cloudiness_Pow));
+
+ worldHandle.UseShader(_cataractsShader);
+ worldHandle.DrawRect(viewport, Color.White);
+ worldHandle.UseShader(null);
}
}
}
diff --git a/Content.Client/Fax/UI/FaxBoundUi.cs b/Content.Client/Fax/UI/FaxBoundUi.cs
index ab6f170639..ef6661b3ef 100644
--- a/Content.Client/Fax/UI/FaxBoundUi.cs
+++ b/Content.Client/Fax/UI/FaxBoundUi.cs
@@ -22,6 +22,7 @@ protected override void Open()
_window.OpenCentered();
_window.OnClose += Close;
+ _window.CopyButtonPressed += OnCopyButtonPressed;
_window.SendButtonPressed += OnSendButtonPressed;
_window.RefreshButtonPressed += OnRefreshButtonPressed;
_window.PeerSelected += OnPeerSelected;
@@ -32,6 +33,11 @@ private void OnSendButtonPressed()
SendMessage(new FaxSendMessage());
}
+ private void OnCopyButtonPressed()
+ {
+ SendMessage(new FaxCopyMessage());
+ }
+
private void OnRefreshButtonPressed()
{
SendMessage(new FaxRefreshMessage());
diff --git a/Content.Client/Fax/UI/FaxWindow.xaml b/Content.Client/Fax/UI/FaxWindow.xaml
index 7b68316b11..1e6ef23376 100644
--- a/Content.Client/Fax/UI/FaxWindow.xaml
+++ b/Content.Client/Fax/UI/FaxWindow.xaml
@@ -20,6 +20,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs
index cf5a1b6e59..e2b09386df 100644
--- a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs
+++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs
@@ -6,10 +6,8 @@
using Content.Client.UserInterface.ControlExtensions;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent;
-using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -99,7 +97,7 @@ private void GenerateControl(ReagentPrototype reagent)
#region Recipe
var reactions = _prototype.EnumeratePrototypes()
- .Where(p => p.Products.ContainsKey(reagent.ID))
+ .Where(p => !p.Source && p.Products.ContainsKey(reagent.ID))
.OrderBy(p => p.Priority)
.ThenBy(p => p.Products.Count)
.ToList();
@@ -108,8 +106,7 @@ private void GenerateControl(ReagentPrototype reagent)
{
foreach (var reactionPrototype in reactions)
{
- var ctrl = GetRecipeGuide(reactionPrototype);
- RecipesDescriptionContainer.AddChild(ctrl);
+ RecipesDescriptionContainer.AddChild(new GuideReagentReaction(reactionPrototype, _prototype, _systemManager));
}
}
else
@@ -159,6 +156,8 @@ private void GenerateControl(ReagentPrototype reagent)
}
#endregion
+ GenerateSources(reagent);
+
FormattedMessage description = new();
description.AddText(reagent.LocalizedDescription);
description.PushNewline();
@@ -167,64 +166,45 @@ private void GenerateControl(ReagentPrototype reagent)
ReagentDescription.SetMessage(description);
}
- private GuideReagentReaction GetRecipeGuide(ReactionPrototype reactionPrototype)
+ private void GenerateSources(ReagentPrototype reagent)
{
- var control = new GuideReagentReaction();
-
- var reactantMsg = new FormattedMessage();
- var reactantsCount = reactionPrototype.Reactants.Count;
- var i = 0;
- foreach (var (product, reactant) in reactionPrototype.Reactants)
- {
- reactantMsg.AddMarkup(Loc.GetString("guidebook-reagent-recipes-reagent-display",
- ("reagent", _prototype.Index(product).LocalizedName), ("ratio", reactant.Amount)));
- i++;
- if (i < reactantsCount)
- reactantMsg.PushNewline();
- }
- reactantMsg.Pop();
- control.ReactantsLabel.SetMessage(reactantMsg);
-
- var productMsg = new FormattedMessage();
- var productCount = reactionPrototype.Products.Count;
- var u = 0;
- foreach (var (product, ratio) in reactionPrototype.Products)
+ var sources = _chemistryGuideData.GetReagentSources(reagent.ID);
+ if (sources.Count == 0)
{
- productMsg.AddMarkup(Loc.GetString("guidebook-reagent-recipes-reagent-display",
- ("reagent", _prototype.Index(product).LocalizedName), ("ratio", ratio)));
- u++;
- if (u < productCount)
- productMsg.PushNewline();
+ SourcesContainer.Visible = false;
+ return;
}
- productMsg.Pop();
- control.ProductsLabel.SetMessage(productMsg);
+ SourcesContainer.Visible = true;
- var mixingCategories = new List();
- if (reactionPrototype.MixingCategories != null)
+ var orderedSources = sources
+ .OrderBy(o => o.OutputCount)
+ .ThenBy(o => o.IdentifierString);
+ foreach (var source in orderedSources)
{
- foreach (var category in reactionPrototype.MixingCategories)
+ if (source is ReagentEntitySourceData entitySourceData)
{
- mixingCategories.Add(_prototype.Index(category));
+ SourcesDescriptionContainer.AddChild(new GuideReagentReaction(
+ entitySourceData.SourceEntProto,
+ entitySourceData.Solution,
+ entitySourceData.MixingType,
+ _prototype,
+ _systemManager));
+ }
+ else if (source is ReagentReactionSourceData reactionSourceData)
+ {
+ SourcesDescriptionContainer.AddChild(new GuideReagentReaction(
+ reactionSourceData.ReactionPrototype,
+ _prototype,
+ _systemManager));
+ }
+ else if (source is ReagentGasSourceData gasSourceData)
+ {
+ SourcesDescriptionContainer.AddChild(new GuideReagentReaction(
+ gasSourceData.GasPrototype,
+ gasSourceData.MixingType,
+ _prototype,
+ _systemManager));
}
}
-
- // only use the first one for the icon.
- if (mixingCategories.FirstOrDefault() is { } primaryCategory)
- {
- control.MixTexture.Texture = _systemManager.GetEntitySystem().Frame0(primaryCategory.Icon);
- }
-
- var mixingVerb = mixingCategories.Count == 0
- ? Loc.GetString("guidebook-reagent-recipes-mix")
- : ContentLocalizationManager.FormatList(mixingCategories.Select(p => Loc.GetString(p.VerbText)).ToList());
-
- var text = Loc.GetString("guidebook-reagent-recipes-mix-info",
- ("verb", mixingVerb),
- ("minTemp", reactionPrototype.MinimumTemperature),
- ("maxTemp", reactionPrototype.MaximumTemperature),
- ("hasMax", !float.IsPositiveInfinity(reactionPrototype.MaximumTemperature)));
-
- control.MixLabel.SetMarkup(text);
- return control;
}
}
diff --git a/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml b/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml
index 69c14a59af..becffbdc6d 100644
--- a/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml
+++ b/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml
@@ -1,12 +1,14 @@
-
+ HorizontalExpand="True"
+ Margin="0 0 0 5">
+
+ Access="Public"
+ Visible="False"/>
+ Access="Public"
+ Visible="False"/>
diff --git a/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs
index fbc6bf13fc..168f352d1a 100644
--- a/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs
+++ b/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs
@@ -1,13 +1,206 @@
+using System.Linq;
+using Content.Client.Message;
using Content.Client.UserInterface.ControlExtensions;
+using Content.Shared.Atmos.Prototypes;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Reaction;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
+using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Graphics.RSI;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
namespace Content.Client.Guidebook.Controls;
[UsedImplicitly, GenerateTypedNameReferences]
public sealed partial class GuideReagentReaction : BoxContainer, ISearchableControl
{
+ [ValidatePrototypeId]
+ private const string DefaultMixingCategory = "DummyMix";
+
+ private readonly IPrototypeManager _protoMan;
+
+ public GuideReagentReaction(IPrototypeManager protoMan)
+ {
+ RobustXamlLoader.Load(this);
+ _protoMan = protoMan;
+ }
+
+ public GuideReagentReaction(ReactionPrototype prototype, IPrototypeManager protoMan, IEntitySystemManager sysMan) : this(protoMan)
+ {
+ var reactantsLabel = ReactantsLabel;
+ SetReagents(prototype.Reactants, ref reactantsLabel, protoMan);
+ var productLabel = ProductsLabel;
+ var products = new Dictionary(prototype.Products);
+ foreach (var (reagent, reactantProto) in prototype.Reactants)
+ {
+ if (reactantProto.Catalyst)
+ products.Add(reagent, reactantProto.Amount);
+ }
+ SetReagents(products, ref productLabel, protoMan);
+
+ var mixingCategories = new List();
+ if (prototype.MixingCategories != null)
+ {
+ foreach (var category in prototype.MixingCategories)
+ {
+ mixingCategories.Add(protoMan.Index(category));
+ }
+ }
+ else
+ {
+ mixingCategories.Add(protoMan.Index(DefaultMixingCategory));
+ }
+ SetMixingCategory(mixingCategories, prototype, sysMan);
+ }
+
+ public GuideReagentReaction(EntityPrototype prototype,
+ Solution solution,
+ IReadOnlyList> categories,
+ IPrototypeManager protoMan,
+ IEntitySystemManager sysMan) : this(protoMan)
+ {
+ var icon = sysMan.GetEntitySystem().GetPrototypeIcon(prototype).GetFrame(RsiDirection.South, 0);
+ var entContainer = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ HorizontalExpand = true,
+ HorizontalAlignment = HAlignment.Center,
+ Children =
+ {
+ new TextureRect
+ {
+ Texture = icon
+ }
+ }
+ };
+ var nameLabel = new RichTextLabel();
+ nameLabel.SetMarkup(Loc.GetString("guidebook-reagent-sources-ent-wrapper", ("name", prototype.Name)));
+ entContainer.AddChild(nameLabel);
+ ReactantsContainer.AddChild(entContainer);
+
+ var productLabel = ProductsLabel;
+ SetReagents(solution.Contents, ref productLabel, protoMan);
+ SetMixingCategory(categories, null, sysMan);
+ }
+
+ public GuideReagentReaction(GasPrototype prototype,
+ IReadOnlyList> categories,
+ IPrototypeManager protoMan,
+ IEntitySystemManager sysMan) : this(protoMan)
+ {
+ ReactantsLabel.Visible = true;
+ ReactantsLabel.SetMarkup(Loc.GetString("guidebook-reagent-sources-gas-wrapper",
+ ("name", Loc.GetString(prototype.Name).ToLower())));
+
+ if (prototype.Reagent != null)
+ {
+ var quantity = new Dictionary
+ {
+ { prototype.Reagent, FixedPoint2.New(0.21f) }
+ };
+ var productLabel = ProductsLabel;
+ SetReagents(quantity, ref productLabel, protoMan);
+ }
+ SetMixingCategory(categories, null, sysMan);
+ }
+
+ private void SetReagents(List reagents, ref RichTextLabel label, IPrototypeManager protoMan)
+ {
+ var amounts = new Dictionary();
+ foreach (var (reagent, quantity) in reagents)
+ {
+ amounts.Add(reagent.Prototype, quantity);
+ }
+ SetReagents(amounts, ref label, protoMan);
+ }
+
+ private void SetReagents(
+ Dictionary reactants,
+ ref RichTextLabel label,
+ IPrototypeManager protoMan)
+ {
+ var amounts = new Dictionary();
+ foreach (var (reagent, reactantPrototype) in reactants)
+ {
+ amounts.Add(reagent, reactantPrototype.Amount);
+ }
+ SetReagents(amounts, ref label, protoMan);
+ }
+
+ [PublicAPI]
+ private void SetReagents(
+ Dictionary, ReactantPrototype> reactants,
+ ref RichTextLabel label,
+ IPrototypeManager protoMan)
+ {
+ var amounts = new Dictionary();
+ foreach (var (reagent, reactantPrototype) in reactants)
+ {
+ amounts.Add(reagent, reactantPrototype.Amount);
+ }
+ SetReagents(amounts, ref label, protoMan);
+ }
+
+ private void SetReagents(Dictionary reagents, ref RichTextLabel label, IPrototypeManager protoMan)
+ {
+ var msg = new FormattedMessage();
+ var reagentCount = reagents.Count;
+ var i = 0;
+ foreach (var (product, amount) in reagents.OrderByDescending(p => p.Value))
+ {
+ msg.AddMarkup(Loc.GetString("guidebook-reagent-recipes-reagent-display",
+ ("reagent", protoMan.Index(product).LocalizedName), ("ratio", amount)));
+ i++;
+ if (i < reagentCount)
+ msg.PushNewline();
+ }
+ msg.Pop();
+ label.SetMessage(msg);
+ label.Visible = true;
+ }
+
+ private void SetMixingCategory(IReadOnlyList> mixingCategories, ReactionPrototype? prototype, IEntitySystemManager sysMan)
+ {
+ var foo = new List();
+ foreach (var cat in mixingCategories)
+ {
+ foo.Add(_protoMan.Index(cat));
+ }
+ SetMixingCategory(foo, prototype, sysMan);
+ }
+
+ private void SetMixingCategory(IReadOnlyList mixingCategories, ReactionPrototype? prototype, IEntitySystemManager sysMan)
+ {
+ if (mixingCategories.Count == 0)
+ return;
+
+ // only use the first one for the icon.
+ if (mixingCategories.First() is { } primaryCategory)
+ {
+ MixTexture.Texture = sysMan.GetEntitySystem().Frame0(primaryCategory.Icon);
+ }
+
+ var mixingVerb = ContentLocalizationManager.FormatList(mixingCategories
+ .Select(p => Loc.GetString(p.VerbText)).ToList());
+
+ var minTemp = prototype?.MinimumTemperature ?? 0;
+ var maxTemp = prototype?.MaximumTemperature ?? float.PositiveInfinity;
+ var text = Loc.GetString("guidebook-reagent-recipes-mix-info",
+ ("verb", mixingVerb),
+ ("minTemp", minTemp),
+ ("maxTemp", maxTemp),
+ ("hasMax", !float.IsPositiveInfinity(maxTemp)));
+
+ MixLabel.SetMarkup(text);
+ }
+
public bool CheckMatchesSearch(string query)
{
return this.ChildrenContainText(query);
diff --git a/Content.Client/Hands/Systems/HandVirtualItemSystem.cs b/Content.Client/Hands/Systems/HandVirtualItemSystem.cs
deleted file mode 100644
index 496b8cea1f..0000000000
--- a/Content.Client/Hands/Systems/HandVirtualItemSystem.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Content.Client.Hands.UI;
-using Content.Client.Items;
-using Content.Shared.Hands;
-using Content.Shared.Hands.Components;
-using JetBrains.Annotations;
-using Robust.Shared.GameObjects;
-
-namespace Content.Client.Hands.Systems
-{
- [UsedImplicitly]
- public sealed class HandVirtualItemSystem : SharedHandVirtualItemSystem
- {
- public override void Initialize()
- {
- base.Initialize();
-
- Subs.ItemStatus(_ => new HandVirtualItemStatus());
- }
- }
-}
diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs
index b403c66dd7..9ea094a73a 100644
--- a/Content.Client/Hands/Systems/HandsSystem.cs
+++ b/Content.Client/Hands/Systems/HandsSystem.cs
@@ -6,6 +6,7 @@
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Item;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@@ -252,7 +253,7 @@ protected override void HandleEntityInserted(EntityUid uid, HandsComponent hands
OnPlayerItemAdded?.Invoke(hand.Name, args.Entity);
- if (HasComp(args.Entity))
+ if (HasComp(args.Entity))
OnPlayerHandBlocked?.Invoke(hand.Name);
}
@@ -270,7 +271,7 @@ protected override void HandleEntityRemoved(EntityUid uid, HandsComponent hands,
OnPlayerItemRemoved?.Invoke(hand.Name, args.Entity);
- if (HasComp(args.Entity))
+ if (HasComp(args.Entity))
OnPlayerHandUnblocked?.Invoke(hand.Name);
}
diff --git a/Content.Client/HealthOverlay/HealthOverlaySystem.cs b/Content.Client/HealthOverlay/HealthOverlaySystem.cs
deleted file mode 100644
index 29ac937199..0000000000
--- a/Content.Client/HealthOverlay/HealthOverlaySystem.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-using Content.Client.HealthOverlay.UI;
-using Content.Shared.Damage;
-using Content.Shared.GameTicking;
-using Content.Shared.Mobs.Components;
-using JetBrains.Annotations;
-using Robust.Client.Graphics;
-using Robust.Client.Player;
-
-namespace Content.Client.HealthOverlay
-{
- [UsedImplicitly]
- public sealed class HealthOverlaySystem : EntitySystem
- {
- [Dependency] private readonly IEyeManager _eyeManager = default!;
- [Dependency] private readonly IEntityManager _entities = default!;
- [Dependency] private readonly IPlayerManager _player = default!;
-
- private readonly Dictionary _guis = new();
- private bool _enabled;
-
- public bool Enabled
- {
- get => _enabled;
- set
- {
- if (_enabled == value)
- {
- return;
- }
-
- _enabled = value;
-
- foreach (var gui in _guis.Values)
- {
- gui.SetVisibility(value);
- }
- }
- }
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeNetworkEvent(Reset);
- }
-
- public void Reset(RoundRestartCleanupEvent ev)
- {
- foreach (var gui in _guis.Values)
- {
- gui.Dispose();
- }
-
- _guis.Clear();
- }
-
- public override void FrameUpdate(float frameTime)
- {
- base.Update(frameTime);
-
- if (!_enabled)
- {
- return;
- }
-
- if (_player.LocalEntity is not {} ent || Deleted(ent))
- {
- return;
- }
-
- var viewBox = _eyeManager.GetWorldViewport().Enlarged(2.0f);
-
- var query = EntityQueryEnumerator();
- while (query.MoveNext(out var entity, out var mobState, out _))
- {
- if (_entities.GetComponent(ent).MapID != _entities.GetComponent(entity).MapID ||
- !viewBox.Contains(_entities.GetComponent(entity).WorldPosition))
- {
- if (_guis.TryGetValue(entity, out var oldGui))
- {
- _guis.Remove(entity);
- oldGui.Dispose();
- }
-
- continue;
- }
-
- if (_guis.ContainsKey(entity))
- {
- continue;
- }
-
- var gui = new HealthOverlayGui(entity);
- _guis.Add(entity, gui);
- }
- }
- }
-}
diff --git a/Content.Client/HealthOverlay/UI/HealthOverlayBar.cs b/Content.Client/HealthOverlay/UI/HealthOverlayBar.cs
deleted file mode 100644
index be22f64c08..0000000000
--- a/Content.Client/HealthOverlay/UI/HealthOverlayBar.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
-using Robust.Shared.Prototypes;
-
-namespace Content.Client.HealthOverlay.UI
-{
- public sealed class HealthOverlayBar : Control
- {
- public const byte HealthBarScale = 2;
-
- private const int XPixelDiff = 20 * HealthBarScale;
-
- public HealthOverlayBar()
- {
- IoCManager.InjectDependencies(this);
- Shader = IoCManager.Resolve().Index("unshaded").Instance();
- }
-
- private ShaderInstance Shader { get; }
-
- ///
- /// From -1 (dead) to 0 (crit) and 1 (alive)
- ///
- public float Ratio { get; set; }
-
- public Color Color { get; set; }
-
- protected override void Draw(DrawingHandleScreen handle)
- {
- base.Draw(handle);
-
- handle.UseShader(Shader);
-
- var leftOffset = 2 * HealthBarScale;
- var box = new UIBox2i(
- leftOffset,
- -2 + 2 * HealthBarScale,
- leftOffset + (int) (XPixelDiff * Ratio * UIScale),
- -2);
-
- handle.DrawRect(box, Color);
- }
- }
-}
diff --git a/Content.Client/HealthOverlay/UI/HealthOverlayGui.cs b/Content.Client/HealthOverlay/UI/HealthOverlayGui.cs
deleted file mode 100644
index e8ec77e540..0000000000
--- a/Content.Client/HealthOverlay/UI/HealthOverlayGui.cs
+++ /dev/null
@@ -1,164 +0,0 @@
-using System.Numerics;
-using Content.Client.IoC;
-using Content.Client.Resources;
-using Content.Shared.Damage;
-using Content.Shared.FixedPoint;
-using Content.Shared.Mobs;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Mobs.Systems;
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Timing;
-
-namespace Content.Client.HealthOverlay.UI
-{
- public sealed class HealthOverlayGui : BoxContainer
- {
- [Dependency] private readonly IEyeManager _eyeManager = default!;
- [Dependency] private readonly IEntityManager _entities = default!;
-
- public HealthOverlayGui(EntityUid entity)
- {
- IoCManager.InjectDependencies(this);
- UserInterfaceManager.WindowRoot.AddChild(this);
- SeparationOverride = 0;
- Orientation = LayoutOrientation.Vertical;
-
- CritBar = new HealthOverlayBar
- {
- Visible = false,
- VerticalAlignment = VAlignment.Center,
- Color = Color.Red
- };
-
- HealthBar = new HealthOverlayBar
- {
- Visible = false,
- VerticalAlignment = VAlignment.Center,
- Color = Color.LimeGreen
- };
-
- AddChild(Panel = new PanelContainer
- {
- Children =
- {
- new TextureRect
- {
- Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/Misc/health_bar.rsi/icon.png"),
- TextureScale = Vector2.One * HealthOverlayBar.HealthBarScale,
- VerticalAlignment = VAlignment.Center,
- },
- CritBar,
- HealthBar
- }
- });
-
- Entity = entity;
- }
-
- public PanelContainer Panel { get; }
-
- public HealthOverlayBar HealthBar { get; }
-
- public HealthOverlayBar CritBar { get; }
-
- public EntityUid Entity { get; }
-
- public void SetVisibility(bool val)
- {
- Visible = val;
- Panel.Visible = val;
- }
-
- private void MoreFrameUpdate()
- {
- if (_entities.Deleted(Entity))
- {
- return;
- }
-
- if (!_entities.TryGetComponent(Entity, out MobStateComponent? mobState) ||
- !_entities.TryGetComponent(Entity, out DamageableComponent? damageable))
- {
- CritBar.Visible = false;
- HealthBar.Visible = false;
- return;
- }
-
- var mobStateSystem = _entities.EntitySysManager.GetEntitySystem();
- var mobThresholdSystem = _entities.EntitySysManager.GetEntitySystem();
- if (mobStateSystem.IsAlive(Entity, mobState))
- {
- if (!mobThresholdSystem.TryGetThresholdForState(Entity,MobState.Critical, out var threshold))
- {
- CritBar.Visible = false;
- HealthBar.Visible = false;
- return;
- }
-
- CritBar.Ratio = 1;
- CritBar.Visible = true;
- HealthBar.Ratio = 1 - ((FixedPoint2)(damageable.TotalDamage / threshold)).Float();
- HealthBar.Visible = true;
- }
- else if (mobStateSystem.IsCritical(Entity, mobState))
- {
- HealthBar.Ratio = 0;
- HealthBar.Visible = false;
-
- if (!mobThresholdSystem.TryGetThresholdForState(Entity, MobState.Critical, out var critThreshold) ||
- !mobThresholdSystem.TryGetThresholdForState(Entity, MobState.Dead, out var deadThreshold))
- {
- CritBar.Visible = false;
- return;
- }
-
- CritBar.Visible = true;
- CritBar.Ratio = 1 -
- ((damageable.TotalDamage - critThreshold) /
- (deadThreshold - critThreshold)).Value.Float();
- }
- else if (mobStateSystem.IsDead(Entity, mobState))
- {
- CritBar.Ratio = 0;
- CritBar.Visible = false;
- HealthBar.Ratio = 0;
- HealthBar.Visible = true;
- }
- else
- {
- CritBar.Visible = false;
- HealthBar.Visible = false;
- }
- }
-
- protected override void FrameUpdate(FrameEventArgs args)
- {
- base.FrameUpdate(args);
-
- MoreFrameUpdate();
-
- if (_entities.Deleted(Entity) || _eyeManager.CurrentMap != _entities.GetComponent(Entity).MapID)
- {
- Visible = false;
- return;
- }
-
- Visible = true;
-
- var screenCoordinates = _eyeManager.CoordinatesToScreen(_entities.GetComponent(Entity).Coordinates);
- var playerPosition = UserInterfaceManager.ScreenToUIPosition(screenCoordinates);
- LayoutContainer.SetPosition(this, new Vector2(playerPosition.X - Width / 2, playerPosition.Y - Height - 30.0f));
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (!disposing)
- return;
-
- HealthBar.Dispose();
- }
- }
-}
diff --git a/Content.Client/Holiday/HolidayRsiSwapComponent.cs b/Content.Client/Holiday/HolidayRsiSwapComponent.cs
new file mode 100644
index 0000000000..d84018d3e7
--- /dev/null
+++ b/Content.Client/Holiday/HolidayRsiSwapComponent.cs
@@ -0,0 +1,14 @@
+namespace Content.Client.Holiday;
+
+///
+/// This is used for a component that swaps an entity's RSI based on HolidayVisuals
+///
+[RegisterComponent]
+public sealed partial class HolidayRsiSwapComponent : Component
+{
+ ///
+ /// A dictionary of arbitrary visual keys to an rsi to swap the sprite to.
+ ///
+ [DataField]
+ public Dictionary Sprite = new();
+}
diff --git a/Content.Client/Holiday/HolidaySystem.cs b/Content.Client/Holiday/HolidaySystem.cs
new file mode 100644
index 0000000000..07ae98f449
--- /dev/null
+++ b/Content.Client/Holiday/HolidaySystem.cs
@@ -0,0 +1,34 @@
+using Content.Shared.Holiday;
+using Content.Shared.Item;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.ResourceManagement;
+using Robust.Shared.Serialization.TypeSerializers.Implementations;
+
+namespace Content.Client.Holiday;
+
+public sealed class HolidaySystem : EntitySystem
+{
+ [Dependency] private readonly IResourceCache _rescache = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnAppearanceChange);
+ }
+
+ private void OnAppearanceChange(Entity ent, ref AppearanceChangeEvent args)
+ {
+ if (!_appearance.TryGetData(ent, HolidayVisuals.Holiday, out var data, args.Component))
+ return;
+
+ var comp = ent.Comp;
+ if (!comp.Sprite.TryGetValue(data, out var rsistring) || args.Sprite == null)
+ return;
+
+ var path = SpriteSpecifierSerializer.TextureRoot / rsistring;
+ if (_rescache.TryGetResource(path, out RSIResource? rsi))
+ args.Sprite.BaseRSI = rsi.RSI;
+ }
+}
diff --git a/Content.Client/Implants/ImplanterSystem.cs b/Content.Client/Implants/ImplanterSystem.cs
index 6949a94d67..13a90f3e39 100644
--- a/Content.Client/Implants/ImplanterSystem.cs
+++ b/Content.Client/Implants/ImplanterSystem.cs
@@ -12,16 +12,11 @@ public override void Initialize()
base.Initialize();
SubscribeLocalEvent(OnHandleImplanterState);
- SubscribeLocalEvent(OnItemImplanterStatus);
+ Subs.ItemStatus(ent => new ImplanterStatusControl(ent));
}
private void OnHandleImplanterState(EntityUid uid, ImplanterComponent component, ref AfterAutoHandleStateEvent args)
{
component.UiUpdateNeeded = true;
}
-
- private void OnItemImplanterStatus(EntityUid uid, ImplanterComponent component, ItemStatusCollectMessage args)
- {
- args.Controls.Add(new ImplanterStatusControl(component));
- }
}
diff --git a/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs b/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs
index 201b7b6304..85c9cc1447 100644
--- a/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs
+++ b/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs
@@ -18,6 +18,8 @@ public sealed partial class InstrumentMenu : DefaultWindow
{
private readonly InstrumentBoundUserInterface _owner;
+ private bool _isMidiFileDialogueWindowOpen;
+
public InstrumentMenu(InstrumentBoundUserInterface owner)
{
RobustXamlLoader.Load(this);
@@ -95,11 +97,21 @@ public void MidiPlaybackSetButtonsDisabled(bool disabled)
private async void MidiFileButtonOnOnPressed(ButtonEventArgs obj)
{
+ if (_isMidiFileDialogueWindowOpen)
+ return;
+
_owner.CloseBandMenu();
var filters = new FileDialogFilters(new FileDialogFilters.Group("mid", "midi"));
+
+ // TODO: Once the file dialogue manager can handle focusing or closing windows, improve this logic to close
+ // or focus the previously-opened window.
+ _isMidiFileDialogueWindowOpen = true;
+
await using var file = await _owner.FileDialogManager.OpenFile(filters);
+ _isMidiFileDialogueWindowOpen = false;
+
// did the instrument menu get closed while waiting for the user to select a file?
if (Disposed)
return;
@@ -110,20 +122,12 @@ private async void MidiFileButtonOnOnPressed(ButtonEventArgs obj)
if (file == null)
return;
- /*if (!_midiManager.IsMidiFile(filename))
- {
- Logger.Warning($"Not a midi file! Chosen file: {filename}");
- return;
- }*/
-
if (!PlayCheck())
return;
- MidiStopButtonOnPressed(null);
await using var memStream = new MemoryStream((int) file.Length);
- // 100ms delay is due to a race condition or something idk.
- // While we're waiting, load it into memory.
- await Task.WhenAll(Timer.Delay(100), file.CopyToAsync(memStream));
+
+ await file.CopyToAsync(memStream);
if (_owner.Instrument is not {} instrument
|| !_owner.Instruments.OpenMidi(_owner.Owner, memStream.GetBuffer().AsSpan(0, (int) memStream.Length), instrument))
diff --git a/Content.Client/Interaction/DragDropHelper.cs b/Content.Client/Interaction/DragDropHelper.cs
index ce5e08207c..abe35bf6d9 100644
--- a/Content.Client/Interaction/DragDropHelper.cs
+++ b/Content.Client/Interaction/DragDropHelper.cs
@@ -1,4 +1,6 @@
-using Robust.Client.Input;
+using Content.Shared.CCVar;
+using Robust.Client.Input;
+using Robust.Shared.Configuration;
using Robust.Shared.Map;
namespace Content.Client.Interaction;
@@ -20,12 +22,13 @@ namespace Content.Client.Interaction;
/// thing being dragged and dropped
public sealed class DragDropHelper
{
- private readonly IInputManager _inputManager;
+ [Dependency] private readonly IInputManager _inputManager = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly OnBeginDrag _onBeginDrag;
private readonly OnEndDrag _onEndDrag;
private readonly OnContinueDrag _onContinueDrag;
- public float Deadzone = 2f;
+ private float _deadzone;
///
/// Convenience method, current mouse screen position as provided by inputmanager.
@@ -63,10 +66,16 @@ private enum DragState : byte
///
public DragDropHelper(OnBeginDrag onBeginDrag, OnContinueDrag onContinueDrag, OnEndDrag onEndDrag)
{
- _inputManager = IoCManager.Resolve();
+ IoCManager.InjectDependencies(this);
_onBeginDrag = onBeginDrag;
_onEndDrag = onEndDrag;
_onContinueDrag = onContinueDrag;
+ _cfg.OnValueChanged(CCVars.DragDropDeadZone, SetDeadZone, true);
+ }
+
+ ~DragDropHelper()
+ {
+ _cfg.UnsubValueChanged(CCVars.DragDropDeadZone, SetDeadZone);
}
///
@@ -121,7 +130,7 @@ public void Update(float frameTime)
case DragState.MouseDown:
{
var screenPos = _inputManager.MouseScreenPosition;
- if ((_mouseDownScreenPos.Position - screenPos.Position).Length() > Deadzone)
+ if ((_mouseDownScreenPos.Position - screenPos.Position).Length() > _deadzone)
{
StartDragging();
}
@@ -139,6 +148,11 @@ public void Update(float frameTime)
}
}
}
+
+ private void SetDeadZone(float value)
+ {
+ _deadzone = value;
+ }
}
///
diff --git a/Content.Client/Inventory/ClientInventorySystem.cs b/Content.Client/Inventory/ClientInventorySystem.cs
index d4615210f2..7b98513a92 100644
--- a/Content.Client/Inventory/ClientInventorySystem.cs
+++ b/Content.Client/Inventory/ClientInventorySystem.cs
@@ -1,9 +1,7 @@
using Content.Client.Clothing;
using Content.Client.Examine;
using Content.Client.Verbs.UI;
-using Content.Shared.Clothing.Components;
using Content.Shared.Interaction;
-using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Storage;
@@ -48,8 +46,6 @@ public override void Initialize()
_equipEventsQueue.Enqueue((comp, args)));
SubscribeLocalEvent((_, comp, args) =>
_equipEventsQueue.Enqueue((comp, args)));
-
- SubscribeLocalEvent(OnUseInHand);
}
public override void Update(float frameTime)
@@ -74,14 +70,6 @@ public override void Update(float frameTime)
}
}
- private void OnUseInHand(EntityUid uid, ClothingComponent component, UseInHandEvent args)
- {
- if (args.Handled || !component.QuickEquip)
- return;
-
- QuickEquip(uid, component, args);
- }
-
private void OnDidUnequip(InventorySlotsComponent component, DidUnequipEvent args)
{
UpdateSlot(args.Equipee, component, args.Slot);
diff --git a/Content.Client/Inventory/StrippableBoundUserInterface.cs b/Content.Client/Inventory/StrippableBoundUserInterface.cs
index 9fbb64309f..028c035e8a 100644
--- a/Content.Client/Inventory/StrippableBoundUserInterface.cs
+++ b/Content.Client/Inventory/StrippableBoundUserInterface.cs
@@ -13,6 +13,7 @@
using Content.Shared.IdentityManagement;
using Content.Shared.Input;
using Content.Shared.Inventory;
+using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Strip.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@@ -159,7 +160,7 @@ private void AddHandButton(Hand hand)
button.Pressed += SlotPressed;
- if (EntMan.TryGetComponent(hand.HeldEntity, out var virt))
+ if (EntMan.TryGetComponent(hand.HeldEntity, out var virt))
{
button.Blocked = true;
if (EntMan.TryGetComponent(Owner, out var cuff) && _cuffable.GetAllCuffs(cuff).Contains(virt.BlockingEntity))
@@ -224,7 +225,7 @@ private void UpdateEntityIcon(SlotControl button, EntityUid? entity)
}
EntityUid? viewEnt;
- if (EntMan.TryGetComponent(entity, out var virt))
+ if (EntMan.TryGetComponent(entity, out var virt))
viewEnt = EntMan.HasComponent(virt.BlockingEntity) ? virt.BlockingEntity : null;
else if (EntMan.HasComponent(entity))
viewEnt = entity;
diff --git a/Content.Client/Inventory/VirtualItemSystem.cs b/Content.Client/Inventory/VirtualItemSystem.cs
new file mode 100644
index 0000000000..721aa11eb9
--- /dev/null
+++ b/Content.Client/Inventory/VirtualItemSystem.cs
@@ -0,0 +1,15 @@
+using Content.Client.Hands.UI;
+using Content.Client.Items;
+using Content.Shared.Inventory.VirtualItem;
+
+namespace Content.Client.Inventory;
+
+public sealed class VirtualItemSystem : SharedVirtualItemSystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ Subs.ItemStatus(_ => new HandVirtualItemStatus());
+ }
+}
diff --git a/Content.Client/Items/ItemStatusMessages.cs b/Content.Client/Items/ItemStatusMessages.cs
index 58a302e882..3af02f0a4f 100644
--- a/Content.Client/Items/ItemStatusMessages.cs
+++ b/Content.Client/Items/ItemStatusMessages.cs
@@ -2,27 +2,51 @@
namespace Content.Client.Items
{
+ ///
+ /// Raised by the HUD logic to collect item status controls for a held entity.
+ ///
+ ///
+ /// Handlers should add any controls they want to add to .
+ ///
+ ///
public sealed class ItemStatusCollectMessage : EntityEventArgs
{
+ ///
+ /// A list of controls that will be displayed on the HUD. Handlers should add their controls here.
+ ///
public List Controls = new();
}
+ ///
+ /// Extension methods for registering item status controls.
+ ///
+ ///
public static class ItemStatusRegisterExt
{
///
/// Register an item status control for a component.
///
+ ///
+ /// This is a convenience wrapper around .
+ ///
/// The handle from within entity system initialize.
- /// A delegate to create the actual control.
+ ///
+ /// A delegate to create the actual control.
+ /// If the delegate returns null, no control will be added to the item status.
+ ///
/// The type of component for which this control should be made.
public static void ItemStatus(
this EntitySystem.Subscriptions subs,
- Func createControl)
+ Func, Control?> createControl)
where TComp : IComponent
{
- subs.SubscribeLocalEvent((uid, _, args) =>
+ subs.SubscribeLocalEvent((Entity entity, ref ItemStatusCollectMessage args) =>
{
- args.Controls.Add(createControl(uid));
+ var control = createControl(entity);
+ if (control == null)
+ return;
+
+ args.Controls.Add(control);
});
}
}
diff --git a/Content.Client/Items/Systems/ItemToggleSystem.cs b/Content.Client/Items/Systems/ItemToggleSystem.cs
new file mode 100644
index 0000000000..46d6f1b464
--- /dev/null
+++ b/Content.Client/Items/Systems/ItemToggleSystem.cs
@@ -0,0 +1,9 @@
+using Content.Shared.Item.ItemToggle;
+
+namespace Content.Shared.Item;
+
+///
+public sealed class ItemToggleSystem : SharedItemToggleSystem
+{
+
+}
diff --git a/Content.Client/Jittering/JitteringSystem.cs b/Content.Client/Jittering/JitteringSystem.cs
index 079fd60a46..185bd490d3 100644
--- a/Content.Client/Jittering/JitteringSystem.cs
+++ b/Content.Client/Jittering/JitteringSystem.cs
@@ -30,6 +30,7 @@ private void OnStartup(EntityUid uid, JitteringComponent jittering, ComponentSta
var animationPlayer = EnsureComp(uid);
+ jittering.StartOffset = sprite.Offset;
_animationPlayer.Play(uid, animationPlayer, GetAnimation(jittering, sprite), _jitterAnimationKey);
}
@@ -39,7 +40,7 @@ private void OnShutdown(EntityUid uid, JitteringComponent jittering, ComponentSh
_animationPlayer.Stop(uid, animationPlayer, _jitterAnimationKey);
if (TryComp(uid, out SpriteComponent? sprite))
- sprite.Offset = Vector2.Zero;
+ sprite.Offset = jittering.StartOffset;
}
private void OnAnimationCompleted(EntityUid uid, JitteringComponent jittering, AnimationCompletedEvent args)
@@ -91,7 +92,7 @@ private Animation GetAnimation(JitteringComponent jittering, SpriteComponent spr
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(sprite.Offset, 0f),
- new AnimationTrackProperty.KeyFrame(offset, length),
+ new AnimationTrackProperty.KeyFrame(jittering.StartOffset + offset, length),
}
}
}
diff --git a/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs b/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
index 1822dd7fff..6e4b7a7618 100644
--- a/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
+++ b/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
@@ -1,11 +1,9 @@
using Content.Client.UserInterface.Controls;
-using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Kitchen;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
@@ -15,7 +13,7 @@ namespace Content.Client.Kitchen.UI
public sealed partial class GrinderMenu : FancyWindow
{
private readonly IEntityManager _entityManager;
- private readonly IPrototypeManager _prototypeManager ;
+ private readonly IPrototypeManager _prototypeManager;
private readonly ReagentGrinderBoundUserInterface _owner;
private readonly Dictionary