diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 45187260126..f43952d457d 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -55,3 +55,10 @@
#Jezi
#/Content.*/Medical @Jezithyr
#/Content.*/Body @Jezithyr
+
+# Sloth
+#/Content.*/Audio @metalgearsloth
+#/Content.*/Movement @metalgearsloth
+#/Content.*/NPC @metalgearsloth
+#/Content.*/Shuttles @metalgearsloth
+#/Content.*/Weapons @metalgearsloth
diff --git a/.github/workflows/labeler-untriaged.yml b/.github/workflows/labeler-untriaged.yml
index 630122aa087..775aab26546 100644
--- a/.github/workflows/labeler-untriaged.yml
+++ b/.github/workflows/labeler-untriaged.yml
@@ -9,5 +9,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions-ecosystem/action-add-labels@v1
+ if: join(github.event.issue.labels) == ''
with:
labels: "Status: Untriaged"
diff --git a/.github/workflows/rsi-diff.yml b/.github/workflows/rsi-diff.yml
index 1f122526d73..98cc97e9221 100644
--- a/.github/workflows/rsi-diff.yml
+++ b/.github/workflows/rsi-diff.yml
@@ -15,9 +15,12 @@ jobs:
- name: Get changed files
id: files
- uses: Ana06/get-changed-files@v1.2
+ uses: Ana06/get-changed-files@v2.3.0
with:
format: 'space-delimited'
+ filter: |
+ **.rsi
+ **.png
- name: Diff changed RSIs
id: diff
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 0e0d3ae890c..00000000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "omnisharp.analyzeOpenDocumentsOnly": true,
- "dotnet.defaultSolution": "SpaceStation14.sln"
-}
diff --git a/Content.Client/Actions/UI/ActionAlertTooltip.cs b/Content.Client/Actions/UI/ActionAlertTooltip.cs
index ddc498b6e91..f805f6643d2 100644
--- a/Content.Client/Actions/UI/ActionAlertTooltip.cs
+++ b/Content.Client/Actions/UI/ActionAlertTooltip.cs
@@ -1,4 +1,4 @@
-using Content.Client.Stylesheets;
+using Content.Client.Stylesheets;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -77,9 +77,12 @@ public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string?
MaxWidth = TooltipTextMaxWidth,
StyleClasses = {StyleNano.StyleClassTooltipActionRequirements}
};
- requiresLabel.SetMessage(FormattedMessage.FromMarkup("[color=#635c5c]" +
- requires +
- "[/color]"));
+
+ if (!FormattedMessage.TryFromMarkup("[color=#635c5c]" + requires + "[/color]", out var markup))
+ return;
+
+ requiresLabel.SetMessage(markup);
+
vbox.AddChild(requiresLabel);
}
}
@@ -97,8 +100,11 @@ protected override void FrameUpdate(FrameEventArgs args)
if (timeLeft > TimeSpan.Zero)
{
var duration = Cooldown.Value.End - Cooldown.Value.Start;
- _cooldownLabel.SetMessage(FormattedMessage.FromMarkup(
- $"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]"));
+
+ if (!FormattedMessage.TryFromMarkup($"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]", out var markup))
+ return;
+
+ _cooldownLabel.SetMessage(markup);
_cooldownLabel.Visible = true;
}
else
diff --git a/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs b/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
index f3aa2572f2f..d5c43e2a500 100644
--- a/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
+++ b/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
@@ -3,38 +3,57 @@
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-namespace Content.Client.Administration.UI
+namespace Content.Client.Administration.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class AdminMenuWindow : DefaultWindow
{
- [GenerateTypedNameReferences]
- public sealed partial class AdminMenuWindow : DefaultWindow
+ public event Action? OnDisposed;
+
+ public AdminMenuWindow()
+ {
+ MinSize = new Vector2(650, 250);
+ Title = Loc.GetString("admin-menu-title");
+ RobustXamlLoader.Load(this);
+ MasterTabContainer.SetTabTitle((int) TabIndex.Admin, Loc.GetString("admin-menu-admin-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.Adminbus, Loc.GetString("admin-menu-adminbus-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.Atmos, Loc.GetString("admin-menu-atmos-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.Round, Loc.GetString("admin-menu-round-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.Server, Loc.GetString("admin-menu-server-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.PanicBunker, Loc.GetString("admin-menu-panic-bunker-tab"));
+ /*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+ MasterTabContainer.SetTabTitle((int) TabIndex.BabyJail, Loc.GetString("admin-menu-baby-jail-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.Players, Loc.GetString("admin-menu-players-tab"));
+ MasterTabContainer.SetTabTitle((int) TabIndex.Objects, Loc.GetString("admin-menu-objects-tab"));
+ MasterTabContainer.OnTabChanged += OnTabChanged;
+ }
+
+ private void OnTabChanged(int tabIndex)
{
- public event Action? OnDisposed;
+ var tabEnum = (TabIndex)tabIndex;
+ if (tabEnum == TabIndex.Objects)
+ ObjectsTabControl.RefreshObjectList();
+ }
- public AdminMenuWindow()
- {
- MinSize = new Vector2(650, 250);
- Title = Loc.GetString("admin-menu-title");
- RobustXamlLoader.Load(this);
- MasterTabContainer.SetTabTitle(0, Loc.GetString("admin-menu-admin-tab"));
- MasterTabContainer.SetTabTitle(1, Loc.GetString("admin-menu-adminbus-tab"));
- MasterTabContainer.SetTabTitle(2, Loc.GetString("admin-menu-atmos-tab"));
- MasterTabContainer.SetTabTitle(3, Loc.GetString("admin-menu-round-tab"));
- MasterTabContainer.SetTabTitle(4, Loc.GetString("admin-menu-server-tab"));
- MasterTabContainer.SetTabTitle(5, Loc.GetString("admin-menu-panic-bunker-tab"));
- /*
- * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
- */
- MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-baby-jail-tab"));
- MasterTabContainer.SetTabTitle(7, Loc.GetString("admin-menu-players-tab"));
- MasterTabContainer.SetTabTitle(8, Loc.GetString("admin-menu-objects-tab"));
- }
+ protected override void Dispose(bool disposing)
+ {
+ OnDisposed?.Invoke();
+ base.Dispose(disposing);
+ OnDisposed = null;
+ }
- protected override void Dispose(bool disposing)
- {
- OnDisposed?.Invoke();
- base.Dispose(disposing);
- OnDisposed = null;
- }
+ private enum TabIndex
+ {
+ Admin = 0,
+ Adminbus,
+ Atmos,
+ Round,
+ Server,
+ PanicBunker,
+ BabyJail,
+ Players,
+ Objects,
}
}
-
diff --git a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml
index 5d630425aba..ef679e778d9 100644
--- a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml
+++ b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml
@@ -5,7 +5,7 @@
+ PlaceHolder="{Loc player-list-filter}"/>
diff --git a/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml b/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml
index f978138ca58..4a3c0ef7ace 100644
--- a/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml
+++ b/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml
@@ -1,10 +1,10 @@
-
+
-
+
-
+
diff --git a/Content.Client/Administration/UI/Tabs/AdminTab/PlayerActionsWindow.xaml b/Content.Client/Administration/UI/Tabs/AdminTab/PlayerActionsWindow.xaml
index dcc1a05bb54..3df57de57b3 100644
--- a/Content.Client/Administration/UI/Tabs/AdminTab/PlayerActionsWindow.xaml
+++ b/Content.Client/Administration/UI/Tabs/AdminTab/PlayerActionsWindow.xaml
@@ -4,7 +4,7 @@
Title="{Loc admin-player-actions-window-title}" MinSize="425 272">
-
+
diff --git a/Content.Client/Administration/UI/Tabs/AdminTab/TeleportWindow.xaml b/Content.Client/Administration/UI/Tabs/AdminTab/TeleportWindow.xaml
index 8f27b17d2dc..cceb21f14d7 100644
--- a/Content.Client/Administration/UI/Tabs/AdminTab/TeleportWindow.xaml
+++ b/Content.Client/Administration/UI/Tabs/AdminTab/TeleportWindow.xaml
@@ -1,9 +1,9 @@
+ Title="{Loc admin-ui-teleport}" MinSize="425 230">
-
+
diff --git a/Content.Client/Administration/UI/Tabs/AdminbusTab/LoadBlueprintsWindow.xaml b/Content.Client/Administration/UI/Tabs/AdminbusTab/LoadBlueprintsWindow.xaml
index 6157a96f42b..e06fc6e8ac5 100644
--- a/Content.Client/Administration/UI/Tabs/AdminbusTab/LoadBlueprintsWindow.xaml
+++ b/Content.Client/Administration/UI/Tabs/AdminbusTab/LoadBlueprintsWindow.xaml
@@ -1,33 +1,33 @@
+ xmlns="https://spacestation14.io" Title="{Loc admin-ui-blueprint-load}">
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/AtmosTab/AddAtmosWindow.xaml b/Content.Client/Administration/UI/Tabs/AtmosTab/AddAtmosWindow.xaml
index 4a1719cbf8a..a7c4c35494d 100644
--- a/Content.Client/Administration/UI/Tabs/AtmosTab/AddAtmosWindow.xaml
+++ b/Content.Client/Administration/UI/Tabs/AtmosTab/AddAtmosWindow.xaml
@@ -1,11 +1,11 @@
+ xmlns="https://spacestation14.io" Title="{Loc admin-ui-atmos-add}">
-
+
-
+
diff --git a/Content.Client/Administration/UI/Tabs/AtmosTab/AddAtmosWindow.xaml.cs b/Content.Client/Administration/UI/Tabs/AtmosTab/AddAtmosWindow.xaml.cs
index 03fd52f446a..72a594469d2 100644
--- a/Content.Client/Administration/UI/Tabs/AtmosTab/AddAtmosWindow.xaml.cs
+++ b/Content.Client/Administration/UI/Tabs/AtmosTab/AddAtmosWindow.xaml.cs
@@ -35,7 +35,7 @@ protected override void EnteredTree()
while (query.MoveNext(out var uid, out var grid))
{
_data.Add((uid, grid));
- GridOptions.AddItem($"{uid} {(playerGrid == uid ? " (Current)" : "")}");
+ GridOptions.AddItem($"{uid} {(playerGrid == uid ? Loc.GetString($"admin-ui-atmos-grid-current") : "")}");
}
GridOptions.OnItemSelected += eventArgs => GridOptions.SelectId(eventArgs.Id);
diff --git a/Content.Client/Administration/UI/Tabs/AtmosTab/AddGasWindow.xaml b/Content.Client/Administration/UI/Tabs/AtmosTab/AddGasWindow.xaml
index df1c7aee658..6420396788a 100644
--- a/Content.Client/Administration/UI/Tabs/AtmosTab/AddGasWindow.xaml
+++ b/Content.Client/Administration/UI/Tabs/AtmosTab/AddGasWindow.xaml
@@ -1,31 +1,31 @@
+ xmlns="https://spacestation14.io" Title="{Loc admin-ui-atmos-add-gas}">
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/Content.Client/Administration/UI/Tabs/AtmosTab/AddGasWindow.xaml.cs b/Content.Client/Administration/UI/Tabs/AtmosTab/AddGasWindow.xaml.cs
index c06d9161334..c516acda2a2 100644
--- a/Content.Client/Administration/UI/Tabs/AtmosTab/AddGasWindow.xaml.cs
+++ b/Content.Client/Administration/UI/Tabs/AtmosTab/AddGasWindow.xaml.cs
@@ -33,7 +33,7 @@ protected override void EnteredTree()
_gridData.Add(entManager.GetNetEntity(uid));
var player = playerManager.LocalEntity;
var playerGrid = entManager.GetComponentOrNull(player)?.GridUid;
- GridOptions.AddItem($"{uid} {(playerGrid == uid ? " (Current)" : "")}");
+ GridOptions.AddItem($"{uid} {(playerGrid == uid ? Loc.GetString("admin-ui-atmos-grid-current") : "")}");
}
GridOptions.OnItemSelected += eventArgs => GridOptions.SelectId(eventArgs.Id);
diff --git a/Content.Client/Administration/UI/Tabs/AtmosTab/AtmosTab.xaml b/Content.Client/Administration/UI/Tabs/AtmosTab/AtmosTab.xaml
index 9916972ac16..8a80e74c1f4 100644
--- a/Content.Client/Administration/UI/Tabs/AtmosTab/AtmosTab.xaml
+++ b/Content.Client/Administration/UI/Tabs/AtmosTab/AtmosTab.xaml
@@ -6,10 +6,10 @@
Margin="4"
MinSize="50 50">
-
-
-
-
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/AtmosTab/FillGasWindow.xaml b/Content.Client/Administration/UI/Tabs/AtmosTab/FillGasWindow.xaml
index 242fcf2b82e..f2dba57bff7 100644
--- a/Content.Client/Administration/UI/Tabs/AtmosTab/FillGasWindow.xaml
+++ b/Content.Client/Administration/UI/Tabs/AtmosTab/FillGasWindow.xaml
@@ -1,21 +1,21 @@
+ xmlns="https://spacestation14.io" Title="{Loc admin-ui-atmos-fill-gas}">
-
+
-
+
-
+
-
+
diff --git a/Content.Client/Administration/UI/Tabs/AtmosTab/FillGasWindow.xaml.cs b/Content.Client/Administration/UI/Tabs/AtmosTab/FillGasWindow.xaml.cs
index 3353d0873ef..302ca8f21fd 100644
--- a/Content.Client/Administration/UI/Tabs/AtmosTab/FillGasWindow.xaml.cs
+++ b/Content.Client/Administration/UI/Tabs/AtmosTab/FillGasWindow.xaml.cs
@@ -36,7 +36,7 @@ protected override void EnteredTree()
{
var player = playerManager.LocalEntity;
var playerGrid = entManager.GetComponentOrNull(player)?.GridUid;
- GridOptions.AddItem($"{uid} {(playerGrid == uid ? " (Current)" : "")}");
+ GridOptions.AddItem($"{uid} {(playerGrid == uid ? Loc.GetString($"admin-ui-atmos-grid-current") : "")}");
_gridData.Add(entManager.GetNetEntity(uid));
}
diff --git a/Content.Client/Administration/UI/Tabs/AtmosTab/SetTemperatureWindow.xaml b/Content.Client/Administration/UI/Tabs/AtmosTab/SetTemperatureWindow.xaml
index dbc65772019..4102912d693 100644
--- a/Content.Client/Administration/UI/Tabs/AtmosTab/SetTemperatureWindow.xaml
+++ b/Content.Client/Administration/UI/Tabs/AtmosTab/SetTemperatureWindow.xaml
@@ -1,26 +1,26 @@
+ xmlns="https://spacestation14.io" Title="{Loc admin-ui-atmos-set-temperature}">
-
+
-
+
-
+
-
+
-
+
diff --git a/Content.Client/Administration/UI/Tabs/AtmosTab/SetTemperatureWindow.xaml.cs b/Content.Client/Administration/UI/Tabs/AtmosTab/SetTemperatureWindow.xaml.cs
index 1183efb9b5b..b3c4a83ed41 100644
--- a/Content.Client/Administration/UI/Tabs/AtmosTab/SetTemperatureWindow.xaml.cs
+++ b/Content.Client/Administration/UI/Tabs/AtmosTab/SetTemperatureWindow.xaml.cs
@@ -32,7 +32,7 @@ protected override void EnteredTree()
{
var player = playerManager.LocalEntity;
var playerGrid = entManager.GetComponentOrNull(player)?.GridUid;
- GridOptions.AddItem($"{uid} {(playerGrid == uid ? " (Current)" : "")}");
+ GridOptions.AddItem($"{uid} {(playerGrid == uid ? Loc.GetString($"admin-ui-atmos-grid-current") : "")}");
_data.Add(entManager.GetNetEntity(uid));
}
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml
index ea89916ba8c..f4298bbc00f 100644
--- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml
@@ -4,18 +4,17 @@
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
-
-
-
+
+
+
+
-
-
-
+
-
-
-
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
index c8606ca80d5..78eefa34628 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.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map.Components;
using Robust.Shared.Timing;
@@ -15,17 +16,14 @@ public sealed partial class ObjectsTab : Control
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
- private readonly List _objects = new();
- private readonly List _selections = new();
- private bool _ascending = false; // Set to false for descending order by default
- private ObjectsTabHeader.Header _headerClicked = ObjectsTabHeader.Header.ObjectName;
private readonly Color _altColor = Color.FromHex("#292B38");
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
- public event Action? OnEntryKeyBindDown;
+ private bool _ascending;
+ private ObjectsTabHeader.Header _headerClicked = ObjectsTabHeader.Header.ObjectName;
- private readonly TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
- private TimeSpan _nextUpdate;
+ private readonly List _selections = [];
+ public event Action? OnEntryKeyBindDown;
public ObjectsTab()
{
@@ -38,40 +36,25 @@ public ObjectsTab()
RefreshObjectList(_selections[ev.Id]);
};
- foreach (var type in Enum.GetValues(typeof(ObjectsTabSelection)))
+ foreach (var type in Enum.GetValues())
{
- _selections.Add((ObjectsTabSelection)type!);
- ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!);
+ _selections.Add(type);
+ ObjectTypeOptions.AddItem(GetLocalizedEnumValue(type));
}
ListHeader.OnHeaderClicked += HeaderClicked;
SearchList.SearchBar = SearchLineEdit;
SearchList.GenerateItem += GenerateButton;
SearchList.DataFilterCondition += DataFilterCondition;
+ SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
+ RefreshListButton.OnPressed += _ => RefreshObjectList();
- RefreshObjectList();
- // Set initial selection and refresh the list to apply the initial sort order
var defaultSelection = ObjectsTabSelection.Grids;
- ObjectTypeOptions.SelectId((int)defaultSelection); // Set the default selection
- RefreshObjectList(defaultSelection); // Refresh the list with the default selection
-
- // Initialize the next update time
- _nextUpdate = TimeSpan.Zero;
- }
-
- protected override void FrameUpdate(FrameEventArgs args)
- {
- base.FrameUpdate(args);
-
- if (_timing.CurTime < _nextUpdate)
- return;
-
- _nextUpdate = _timing.CurTime + _updateFrequency;
-
- RefreshObjectList();
+ ObjectTypeOptions.SelectId((int) defaultSelection);
+ RefreshObjectList(defaultSelection);
}
- private void RefreshObjectList()
+ public void RefreshObjectList()
{
RefreshObjectList(_selections[ObjectTypeOptions.SelectedId]);
}
@@ -101,6 +84,7 @@ private void RefreshObjectList(ObjectsTabSelection selection)
{
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
}
+
break;
}
default:
@@ -111,14 +95,18 @@ private void RefreshObjectList(ObjectsTabSelection selection)
{
var valueA = GetComparableValue(a, _headerClicked);
var valueB = GetComparableValue(b, _headerClicked);
- return _ascending ? Comparer
-
-
+
+
+
+
public void PopulateRecipes()
{
- if (!_entityManager.TryGetComponent(_owner, out var component))
- return;
-
var recipesToShow = new List();
foreach (var recipe in Recipes)
{
@@ -108,21 +105,13 @@ public void PopulateRecipes()
RecipeList.Children.Clear();
foreach (var prototype in sortedRecipesToShow)
{
- List textures;
+ EntityPrototype? recipeProto = null;
if (_prototypeManager.TryIndex(prototype.Result, out EntityPrototype? entityProto) && entityProto != null)
- {
- textures = SpriteComponent.GetPrototypeTextures(entityProto, _resources).Select(o => o.Default).ToList();
- }
- else
- {
- textures = prototype.Icon == null
- ? new List { _spriteSystem.GetPrototypeIcon(prototype.Result).Default }
- : new List { _spriteSystem.Frame0(prototype.Icon) };
- }
+ recipeProto = entityProto;
var canProduce = _lathe.CanProduce(_owner, prototype, quantity);
- var control = new RecipeControl(prototype, () => GenerateTooltipText(prototype), canProduce, textures);
+ var control = new RecipeControl(prototype, () => GenerateTooltipText(prototype), canProduce, recipeProto);
control.OnButtonPressed += s =>
{
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
@@ -219,14 +208,23 @@ public void UpdateCategories()
///
public void PopulateQueueList(List queue)
{
- QueueList.Clear();
+ QueueList.DisposeAllChildren();
+
var idx = 1;
foreach (var recipe in queue)
{
- var icon = recipe.Icon == null
- ? _spriteSystem.GetPrototypeIcon(recipe.Result).Default
- : _spriteSystem.Frame0(recipe.Icon);
- QueueList.AddItem($"{idx}. {recipe.Name}", icon);
+ var queuedRecipeBox = new BoxContainer();
+ queuedRecipeBox.Orientation = BoxContainer.LayoutOrientation.Horizontal;
+
+ var queuedRecipeProto = new EntityPrototypeView();
+ queuedRecipeBox.AddChild(queuedRecipeProto);
+ if (_prototypeManager.TryIndex(recipe.Result, out EntityPrototype? entityProto) && entityProto != null)
+ queuedRecipeProto.SetPrototype(entityProto);
+
+ var queuedRecipeLabel = new Label();
+ queuedRecipeLabel.Text = $"{idx}. {recipe.Name}";
+ queuedRecipeBox.AddChild(queuedRecipeLabel);
+ QueueList.AddChild(queuedRecipeBox);
idx++;
}
}
@@ -236,9 +234,10 @@ public void SetQueueInfo(LatheRecipePrototype? recipe)
FabricatingContainer.Visible = recipe != null;
if (recipe == null)
return;
- Icon.Texture = recipe.Icon == null
- ? _spriteSystem.GetPrototypeIcon(recipe.Result).Default
- : _spriteSystem.Frame0(recipe.Icon);
+
+ if (_prototypeManager.TryIndex(recipe.Result, out EntityPrototype? entityProto) && entityProto != null)
+ FabricatingEntityProto.SetPrototype(entityProto);
+
NameLabel.Text = $"{recipe.Name}";
}
diff --git a/Content.Client/Lathe/UI/RecipeControl.xaml b/Content.Client/Lathe/UI/RecipeControl.xaml
index d1371a026a2..19e20c7c06d 100644
--- a/Content.Client/Lathe/UI/RecipeControl.xaml
+++ b/Content.Client/Lathe/UI/RecipeControl.xaml
@@ -5,14 +5,12 @@
Margin="0"
StyleClasses="ButtonSquare">
-
diff --git a/Content.Client/Lathe/UI/RecipeControl.xaml.cs b/Content.Client/Lathe/UI/RecipeControl.xaml.cs
index 47b6b5932c4..db428d3cf0e 100644
--- a/Content.Client/Lathe/UI/RecipeControl.xaml.cs
+++ b/Content.Client/Lathe/UI/RecipeControl.xaml.cs
@@ -4,6 +4,7 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
namespace Content.Client.Lathe.UI;
@@ -13,12 +14,13 @@ public sealed partial class RecipeControl : Control
public Action? OnButtonPressed;
public Func TooltipTextSupplier;
- public RecipeControl(LatheRecipePrototype recipe, Func tooltipTextSupplier, bool canProduce, List textures)
+ public RecipeControl(LatheRecipePrototype recipe, Func tooltipTextSupplier, bool canProduce, EntityPrototype? entityPrototype = null)
{
RobustXamlLoader.Load(this);
RecipeName.Text = recipe.Name;
- RecipeTextures.Textures = textures;
+ if (entityPrototype != null)
+ RecipePrototype.SetPrototype(entityPrototype);
Button.Disabled = !canProduce;
TooltipTextSupplier = tooltipTextSupplier;
Button.TooltipSupplier = SupplyTooltip;
diff --git a/Content.Client/Launcher/LauncherConnectingGui.xaml.cs b/Content.Client/Launcher/LauncherConnectingGui.xaml.cs
index ac74ad7b60d..5015b710eb4 100644
--- a/Content.Client/Launcher/LauncherConnectingGui.xaml.cs
+++ b/Content.Client/Launcher/LauncherConnectingGui.xaml.cs
@@ -116,7 +116,7 @@ private void HandleDisconnectReason(INetStructuredReason? reason)
private void ChangeLoginTip()
{
var tipsDataset = _cfg.GetCVar(CCVars.LoginTipsDataset);
- var loginTipsEnabled = _prototype.TryIndex(tipsDataset, out var tips);
+ var loginTipsEnabled = _prototype.TryIndex(tipsDataset, out var tips);
LoginTips.Visible = loginTipsEnabled;
if (!loginTipsEnabled)
@@ -131,7 +131,7 @@ private void ChangeLoginTip()
var randomIndex = _random.Next(tipList.Count);
var tip = tipList[randomIndex];
- LoginTip.SetMessage(tip);
+ LoginTip.SetMessage(Loc.GetString(tip));
LoginTipTitle.Text = Loc.GetString("connecting-window-tip", ("numberTip", randomIndex));
}
diff --git a/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs b/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs
index 11f69165cf6..ca19d8522c5 100644
--- a/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs
+++ b/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs
@@ -19,6 +19,9 @@ public override void Initialize()
private void OnBehaviorAnimationCompleted(EntityUid uid, LightBehaviourComponent component, AnimationCompletedEvent args)
{
+ if (!args.Finished)
+ return;
+
var container = component.Animations.FirstOrDefault(x => x.FullKey == args.Key);
if (container == null)
diff --git a/Content.Client/Light/EntitySystems/RotatingLightSystem.cs b/Content.Client/Light/EntitySystems/RotatingLightSystem.cs
index 842c13dedfe..5c2c4e4c875 100644
--- a/Content.Client/Light/EntitySystems/RotatingLightSystem.cs
+++ b/Content.Client/Light/EntitySystems/RotatingLightSystem.cs
@@ -69,6 +69,9 @@ private void OnAfterAutoHandleState(EntityUid uid, RotatingLightComponent comp,
private void OnAnimationComplete(EntityUid uid, RotatingLightComponent comp, AnimationCompletedEvent args)
{
+ if (!args.Finished)
+ return;
+
PlayAnimation(uid, comp);
}
diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs
index bf4fa9685da..0f937799f46 100644
--- a/Content.Client/Lobby/LobbyUIController.cs
+++ b/Content.Client/Lobby/LobbyUIController.cs
@@ -46,6 +46,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered
/// This is the characher preview panel in the chat. This should only update if their character updates.
@@ -216,6 +217,46 @@ private void SaveProfile()
ReloadCharacterSetup();
}
+ private void CloseProfileEditor()
+ {
+ if (_profileEditor == null)
+ return;
+
+ _profileEditor.SetProfile(null, null);
+ _profileEditor.Visible = false;
+
+ if (_stateManager.CurrentState is LobbyState lobbyGui)
+ {
+ lobbyGui.SwitchState(LobbyGui.LobbyGuiState.Default);
+ }
+ }
+
+ private void OpenSavePanel()
+ {
+ if (_savePanel is { IsOpen: true })
+ return;
+
+ _savePanel = new CharacterSetupGuiSavePanel();
+
+ _savePanel.SaveButton.OnPressed += _ =>
+ {
+ SaveProfile();
+
+ _savePanel.Close();
+
+ CloseProfileEditor();
+ };
+
+ _savePanel.NoSaveButton.OnPressed += _ =>
+ {
+ _savePanel.Close();
+
+ CloseProfileEditor();
+ };
+
+ _savePanel.OpenCentered();
+ }
+
private (CharacterSetupGui, HumanoidProfileEditor) EnsureGui()
{
if (_characterSetup != null && _profileEditor != null)
@@ -242,14 +283,16 @@ private void SaveProfile()
_characterSetup.CloseButton.OnPressed += _ =>
{
- // Reset sliders etc.
- _profileEditor.SetProfile(null, null);
- _profileEditor.Visible = false;
-
- if (_stateManager.CurrentState is LobbyState lobbyGui)
+ // Open the save panel if we have unsaved changes.
+ if (_profileEditor.Profile != null && _profileEditor.IsDirty)
{
- lobbyGui.SwitchState(LobbyGui.LobbyGuiState.Default);
+ OpenSavePanel();
+
+ return;
}
+
+ // Reset sliders etc.
+ CloseProfileEditor();
};
_profileEditor.Save += SaveProfile;
diff --git a/Content.Client/Lobby/UI/CharacterSetupGuiSavePanel.xaml b/Content.Client/Lobby/UI/CharacterSetupGuiSavePanel.xaml
new file mode 100644
index 00000000000..2dcf9143533
--- /dev/null
+++ b/Content.Client/Lobby/UI/CharacterSetupGuiSavePanel.xaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Lobby/UI/CharacterSetupGuiSavePanel.xaml.cs b/Content.Client/Lobby/UI/CharacterSetupGuiSavePanel.xaml.cs
new file mode 100644
index 00000000000..5f2690b7a89
--- /dev/null
+++ b/Content.Client/Lobby/UI/CharacterSetupGuiSavePanel.xaml.cs
@@ -0,0 +1,21 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Lobby.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class CharacterSetupGuiSavePanel : DefaultWindow
+{
+ public CharacterSetupGuiSavePanel()
+ {
+ RobustXamlLoader.Load(this);
+
+ CancelButton.OnPressed += _ =>
+ {
+ Close();
+ };
+
+ CloseButton.Visible = false;
+ }
+}
diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
index 5f21b20107c..2b1524eb8f3 100644
--- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
+++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
@@ -484,10 +484,10 @@ public void RefreshTraits()
return;
}
- //Setup model
- Dictionary> model = new();
+ // Setup model
+ Dictionary> traitGroups = new();
List defaultTraits = new();
- model.Add("default", defaultTraits);
+ traitGroups.Add(TraitCategoryPrototype.Default, defaultTraits);
foreach (var trait in traits)
{
@@ -497,18 +497,19 @@ public void RefreshTraits()
continue;
}
- if (!model.ContainsKey(trait.Category))
- {
- model.Add(trait.Category, new());
- }
- model[trait.Category].Add(trait.ID);
+ if (!_prototypeManager.HasIndex(trait.Category))
+ continue;
+
+ var group = traitGroups.GetOrNew(trait.Category);
+ group.Add(trait.ID);
}
- //Create UI view from model
- foreach (var (categoryId, traitId) in model)
+ // Create UI view from model
+ foreach (var (categoryId, categoryTraits) in traitGroups)
{
TraitCategoryPrototype? category = null;
- if (categoryId != "default")
+
+ if (categoryId != TraitCategoryPrototype.Default)
{
category = _prototypeManager.Index(categoryId);
// Label
@@ -523,7 +524,7 @@ public void RefreshTraits()
List selectors = new();
var selectionCount = 0;
- foreach (var traitProto in traitId)
+ foreach (var traitProto in categoryTraits)
{
var trait = _prototypeManager.Index(traitProto);
var selector = new TraitPreferenceSelector(trait);
@@ -534,7 +535,15 @@ public void RefreshTraits()
selector.PreferenceChanged += preference =>
{
- Profile = Profile?.WithTraitPreference(trait.ID, categoryId, preference);
+ if (preference)
+ {
+ Profile = Profile?.WithTraitPreference(trait.ID, _prototypeManager);
+ }
+ else
+ {
+ Profile = Profile?.WithoutTraitPreference(trait.ID, _prototypeManager);
+ }
+
SetDirty();
RefreshTraits(); // If too many traits are selected, they will be reset to the real value.
};
@@ -1186,7 +1195,7 @@ private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
SetDirty();
}
- private bool IsDirty
+ public bool IsDirty
{
get => _isDirty;
set
diff --git a/Content.Client/MouseRotator/MouseRotatorSystem.cs b/Content.Client/MouseRotator/MouseRotatorSystem.cs
index ce174c6144c..18d60d9a7b9 100644
--- a/Content.Client/MouseRotator/MouseRotatorSystem.cs
+++ b/Content.Client/MouseRotator/MouseRotatorSystem.cs
@@ -2,7 +2,6 @@
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
-using Robust.Client.Replays.Loading;
using Robust.Shared.Map;
using Robust.Shared.Timing;
@@ -46,13 +45,19 @@ public override void Update(float frameTime)
// only raise event if the cardinal direction has changed
if (rotator.Simple4DirMode)
{
- var angleDir = angle.GetCardinalDir();
- if (angleDir == curRot.GetCardinalDir())
+ var eyeRot = _eye.CurrentEye.Rotation; // camera rotation
+ var angleDir = (angle + eyeRot).GetCardinalDir(); // apply GetCardinalDir in the camera frame, not in the world frame
+ if (angleDir == (curRot + eyeRot).GetCardinalDir())
return;
- RaisePredictiveEvent(new RequestMouseRotatorRotationSimpleEvent()
+ var rotation = angleDir.ToAngle() - eyeRot; // convert back to world frame
+ if (rotation >= Math.PI) // convert to [-PI, +PI)
+ rotation -= 2 * Math.PI;
+ else if (rotation < -Math.PI)
+ rotation += 2 * Math.PI;
+ RaisePredictiveEvent(new RequestMouseRotatorRotationEvent
{
- Direction = angleDir,
+ Rotation = rotation
});
return;
diff --git a/Content.Client/Movement/Systems/ContentEyeSystem.cs b/Content.Client/Movement/Systems/ContentEyeSystem.cs
index 182ac92ae05..9fbd4b5c37d 100644
--- a/Content.Client/Movement/Systems/ContentEyeSystem.cs
+++ b/Content.Client/Movement/Systems/ContentEyeSystem.cs
@@ -9,7 +9,7 @@ public sealed class ContentEyeSystem : SharedContentEyeSystem
{
[Dependency] private readonly IPlayerManager _player = default!;
- public void RequestZoom(EntityUid uid, Vector2 zoom, bool ignoreLimit, ContentEyeComponent? content = null)
+ public void RequestZoom(EntityUid uid, Vector2 zoom, bool ignoreLimit, bool scalePvs, ContentEyeComponent? content = null)
{
if (!Resolve(uid, ref content, false))
return;
@@ -19,6 +19,14 @@ public void RequestZoom(EntityUid uid, Vector2 zoom, bool ignoreLimit, ContentEy
TargetZoom = zoom,
IgnoreLimit = ignoreLimit,
});
+
+ if (scalePvs)
+ RequestPvsScale(Math.Max(zoom.X, zoom.Y));
+ }
+
+ public void RequestPvsScale(float scale)
+ {
+ RaiseNetworkEvent(new RequestPvsScaleEvent(scale));
}
public void RequestToggleFov()
diff --git a/Content.Client/Ninja/Systems/ItemCreatorSystem.cs b/Content.Client/Ninja/Systems/ItemCreatorSystem.cs
new file mode 100644
index 00000000000..9ab62cc12db
--- /dev/null
+++ b/Content.Client/Ninja/Systems/ItemCreatorSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Ninja.Systems;
+
+namespace Content.Client.Ninja.Systems;
+
+public sealed class ItemCreatorSystem : SharedItemCreatorSystem;
diff --git a/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs b/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs
index 7758c3d7e2b..5b07b1588fd 100644
--- a/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs
+++ b/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs
@@ -2,9 +2,4 @@
namespace Content.Client.Ninja.Systems;
-///
-/// Does nothing special, only exists to provide a client implementation.
-///
-public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem
-{
-}
+public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem;
diff --git a/Content.Client/Ninja/Systems/NinjaSuitSystem.cs b/Content.Client/Ninja/Systems/NinjaSuitSystem.cs
index fde1801b37d..852ea8af46e 100644
--- a/Content.Client/Ninja/Systems/NinjaSuitSystem.cs
+++ b/Content.Client/Ninja/Systems/NinjaSuitSystem.cs
@@ -1,24 +1,5 @@
-using Content.Shared.Clothing.EntitySystems;
-using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
namespace Content.Client.Ninja.Systems;
-///
-/// Disables cloak prediction since client has no knowledge of battery power.
-/// Cloak will still be enabled after server tells it.
-///
-public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
-{
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnAttemptStealth);
- }
-
- private void OnAttemptStealth(EntityUid uid, NinjaSuitComponent comp, AttemptStealthEvent args)
- {
- args.Cancel();
- }
-}
+public sealed class NinjaSuitSystem : SharedNinjaSuitSystem;
diff --git a/Content.Client/Ninja/Systems/NinjaSystem.cs b/Content.Client/Ninja/Systems/NinjaSystem.cs
index aa2fa2047f1..958dc6a5d9a 100644
--- a/Content.Client/Ninja/Systems/NinjaSystem.cs
+++ b/Content.Client/Ninja/Systems/NinjaSystem.cs
@@ -2,11 +2,4 @@
namespace Content.Client.Ninja.Systems;
-///
-/// Currently does nothing special clientside.
-/// All functionality is in shared and server.
-/// Only exists to prevent crashing.
-///
-public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
-{
-}
+public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem;
diff --git a/Content.Client/Ninja/Systems/SpiderChargeSystem.cs b/Content.Client/Ninja/Systems/SpiderChargeSystem.cs
new file mode 100644
index 00000000000..b107fd3867d
--- /dev/null
+++ b/Content.Client/Ninja/Systems/SpiderChargeSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Ninja.Systems;
+
+namespace Content.Client.Ninja.Systems;
+
+public sealed class SpiderChargeSystem : SharedSpiderChargeSystem;
diff --git a/Content.Client/Options/UI/OptionDropDown.xaml b/Content.Client/Options/UI/OptionDropDown.xaml
new file mode 100644
index 00000000000..58dcdca6c8b
--- /dev/null
+++ b/Content.Client/Options/UI/OptionDropDown.xaml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Content.Client/Options/UI/OptionDropDown.xaml.cs b/Content.Client/Options/UI/OptionDropDown.xaml.cs
new file mode 100644
index 00000000000..506e241a06e
--- /dev/null
+++ b/Content.Client/Options/UI/OptionDropDown.xaml.cs
@@ -0,0 +1,21 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Options.UI;
+
+///
+/// Standard UI control used for drop-downs in the options menu. Intended for use with .
+///
+///
+[GenerateTypedNameReferences]
+public sealed partial class OptionDropDown : Control
+{
+ ///
+ /// The text describing what this drop-down controls.
+ ///
+ public string? Title
+ {
+ get => NameLabel.Text;
+ set => NameLabel.Text = value;
+ }
+}
diff --git a/Content.Client/Options/UI/OptionSlider.xaml b/Content.Client/Options/UI/OptionSlider.xaml
new file mode 100644
index 00000000000..fa2d78c67ff
--- /dev/null
+++ b/Content.Client/Options/UI/OptionSlider.xaml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/Content.Client/Options/UI/OptionSlider.xaml.cs b/Content.Client/Options/UI/OptionSlider.xaml.cs
new file mode 100644
index 00000000000..6a377f7ee19
--- /dev/null
+++ b/Content.Client/Options/UI/OptionSlider.xaml.cs
@@ -0,0 +1,22 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Options.UI;
+
+///
+/// Standard UI control used for sliders in the options menu. Intended for use with .
+///
+///
+///
+[GenerateTypedNameReferences]
+public sealed partial class OptionSlider : Control
+{
+ ///
+ /// The text describing what this slider controls.
+ ///
+ public string? Title
+ {
+ get => NameLabel.Text;
+ set => NameLabel.Text = value;
+ }
+}
diff --git a/Content.Client/Options/UI/OptionsMenu.xaml b/Content.Client/Options/UI/OptionsMenu.xaml
index f445f18a2ca..62184cf7774 100644
--- a/Content.Client/Options/UI/OptionsMenu.xaml
+++ b/Content.Client/Options/UI/OptionsMenu.xaml
@@ -8,6 +8,7 @@
+
diff --git a/Content.Client/Options/UI/OptionsMenu.xaml.cs b/Content.Client/Options/UI/OptionsMenu.xaml.cs
index f0b0cae7578..8ff7c2021e5 100644
--- a/Content.Client/Options/UI/OptionsMenu.xaml.cs
+++ b/Content.Client/Options/UI/OptionsMenu.xaml.cs
@@ -1,9 +1,6 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.IoC;
-using Content.Client.Options.UI.Tabs;
-
namespace Content.Client.Options.UI
{
@@ -19,15 +16,19 @@ public OptionsMenu()
Tabs.SetTabTitle(1, Loc.GetString("ui-options-tab-graphics"));
Tabs.SetTabTitle(2, Loc.GetString("ui-options-tab-controls"));
Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio"));
- Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-network"));
- Tabs.SetTabTitle(5, Loc.GetString("ui-options-tab-extra")); // Extra settings
+ Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-accessibility"));
+ Tabs.SetTabTitle(5, Loc.GetString("ui-options-tab-network"));
+ Tabs.SetTabTitle(6, Loc.GetString("ui-options-tab-extra")); // Extra settings
UpdateTabs();
}
public void UpdateTabs()
{
- GraphicsTab.UpdateProperties();
+ GraphicsTab.Control.ReloadValues();
+ MiscTab.Control.ReloadValues();
+ AccessibilityTab.Control.ReloadValues();
+ AudioTab.Control.ReloadValues();
}
}
}
diff --git a/Content.Client/Options/UI/OptionsTabControlRow.xaml b/Content.Client/Options/UI/OptionsTabControlRow.xaml
new file mode 100644
index 00000000000..fafdee4df76
--- /dev/null
+++ b/Content.Client/Options/UI/OptionsTabControlRow.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Options/UI/OptionsTabControlRow.xaml.cs b/Content.Client/Options/UI/OptionsTabControlRow.xaml.cs
new file mode 100644
index 00000000000..31dd9897f4e
--- /dev/null
+++ b/Content.Client/Options/UI/OptionsTabControlRow.xaml.cs
@@ -0,0 +1,684 @@
+using System.Linq;
+using Content.Client.Stylesheets;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Collections;
+using Robust.Shared.Configuration;
+
+namespace Content.Client.Options.UI;
+
+///
+/// Control used on all tabs of the in-game options menu,
+/// contains the "save" and "reset" buttons and controls the entire logic.
+///
+///
+///
+/// Basic operation is simple: options tabs put this control at the bottom of the tab,
+/// they bind UI controls to it with calls such as ,
+/// then they call . The rest is all handled by the control.
+///
+///
+/// Individual options are implementations of . See the type for details.
+/// Common implementations for building on top of CVars are already exist,
+/// but tabs can define their own if they need to.
+///
+///
+/// Generally, options are added via helper methods such as ,
+/// however it is totally possible to directly instantiate the backing types
+/// and add them via .
+///
+///
+/// The options system is general purpose enough that does not, itself,
+/// know what a CVar is. It does automatically save CVars to config when save is pressed, but otherwise CVar interaction
+/// is handled by implementations.
+///
+///
+/// Behaviorally, the row has 3 control buttons: save, reset changed, and reset to default.
+/// "Save" writes the configuration changes and saves the configuration.
+/// "Reset changed" discards changes made in the menu and re-loads the saved settings.
+/// "Reset to default" resets the settings on the menu to be the default, out-of-the-box values.
+/// Note that "Reset to default" does not save immediately, the user must still press save manually.
+///
+///
+/// The disabled state of the 3 buttons is updated dynamically based on the values of the options.
+///
+///
+[GenerateTypedNameReferences]
+public sealed partial class OptionsTabControlRow : Control
+{
+ [Dependency] private readonly ILocalizationManager _loc = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ private ValueList _options;
+
+ public OptionsTabControlRow()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ ResetButton.StyleClasses.Add(StyleBase.ButtonOpenRight);
+ ApplyButton.OnPressed += ApplyButtonPressed;
+ ResetButton.OnPressed += ResetButtonPressed;
+ DefaultButton.OnPressed += DefaultButtonPressed;
+ }
+
+ ///
+ /// Add a new option to be tracked by the control.
+ ///
+ /// The option object that manages this object's logic
+ ///
+ /// The type of option being passed in. Necessary to allow the return type to match the parameter type
+ /// for easy chaining.
+ ///
+ /// The same as passed in, for easy chaining.
+ public T AddOption(T option) where T : BaseOption
+ {
+ _options.Add(option);
+ return option;
+ }
+
+ ///
+ /// Add a checkbox option backed by a simple boolean CVar.
+ ///
+ /// The CVar represented by the checkbox.
+ /// The UI control for the option.
+ ///
+ /// If true, the checkbox is inverted relative to the CVar: if the CVar is true, the checkbox will be unchecked.
+ ///
+ /// The option instance backing the added option.
+ ///
+ public OptionCheckboxCVar AddOptionCheckBox(CVarDef cVar, CheckBox checkBox, bool invert = false)
+ {
+ return AddOption(new OptionCheckboxCVar(this, _cfg, cVar, checkBox, invert));
+ }
+
+ ///
+ /// Add a slider option, displayed in percent, backed by a simple float CVar.
+ ///
+ /// The CVar represented by the slider.
+ /// The UI control for the option.
+ /// The minimum value the slider should allow. The default value represents "0%"
+ /// The maximum value the slider should allow. The default value represents "100%"
+ ///
+ /// Scale with which to multiply slider values when mapped to the backing CVar.
+ /// For example, if a scale of 2 is set, a slider at 75% writes a value of 1.5 to the CVar.
+ ///
+ /// The option instance backing the added option.
+ ///
+ ///
+ /// Note that percentage values are represented as ratios in code, i.e. a value of 100% is "1".
+ ///
+ ///
+ public OptionSliderFloatCVar AddOptionPercentSlider(
+ CVarDef cVar,
+ OptionSlider slider,
+ float min = 0,
+ float max = 1,
+ float scale = 1)
+ {
+ return AddOption(new OptionSliderFloatCVar(this, _cfg, cVar, slider, min, max, scale, FormatPercent));
+ }
+
+ ///
+ /// Add a slider option, backed by a simple integer CVar.
+ ///
+ /// The CVar represented by the slider.
+ /// The UI control for the option.
+ /// The minimum value the slider should allow.
+ /// The maximum value the slider should allow.
+ ///
+ /// An optional delegate used to format the textual value display of the slider.
+ /// If not provided, the default behavior is to directly format the integer value as text.
+ ///
+ /// The option instance backing the added option.
+ public OptionSliderIntCVar AddOptionSlider(
+ CVarDef cVar,
+ OptionSlider slider,
+ int min,
+ int max,
+ Func? format = null)
+ {
+ return AddOption(new OptionSliderIntCVar(this, _cfg, cVar, slider, min, max, format ?? FormatInt));
+ }
+
+ ///
+ /// Add a drop-down option, backed by a CVar.
+ ///
+ /// The CVar represented by the drop-down.
+ /// The UI control for the option.
+ ///
+ /// The set of options that will be shown in the drop-down. Items are ordered as provided.
+ ///
+ /// The type of the CVar being controlled.
+ /// The option instance backing the added option.
+ public OptionDropDownCVar AddOptionDropDown(
+ CVarDef cVar,
+ OptionDropDown dropDown,
+ IReadOnlyCollection.ValueOption> options)
+ where T : notnull
+ {
+ return AddOption(new OptionDropDownCVar(this, _cfg, cVar, dropDown, options));
+ }
+
+ ///
+ /// Initializes the control row. This should be called after all options have been added.
+ ///
+ public void Initialize()
+ {
+ foreach (var option in _options)
+ {
+ option.LoadValue();
+ }
+
+ UpdateButtonState();
+ }
+
+ ///
+ /// Re-loads options in the settings from backing values.
+ /// Should be called when the options window is opened to make sure all values are up-to-date.
+ ///
+ public void ReloadValues()
+ {
+ Initialize();
+ }
+
+ ///
+ /// Called by to signal that an option's value changed through user interaction.
+ ///
+ ///
+ /// implementations should not call this function directly,
+ /// instead they should call .
+ ///
+ public void ValueChanged()
+ {
+ UpdateButtonState();
+ }
+
+ private void UpdateButtonState()
+ {
+ var anyModified = _options.Any(option => option.IsModified());
+ var anyModifiedFromDefault = _options.Any(option => option.IsModifiedFromDefault());
+
+ DefaultButton.Disabled = !anyModifiedFromDefault;
+ ApplyButton.Disabled = !anyModified;
+ ResetButton.Disabled = !anyModified;
+ }
+
+ private void ApplyButtonPressed(BaseButton.ButtonEventArgs obj)
+ {
+ foreach (var option in _options)
+ {
+ if (option.IsModified())
+ option.SaveValue();
+ }
+
+ _cfg.SaveToFile();
+ UpdateButtonState();
+ }
+
+ private void ResetButtonPressed(BaseButton.ButtonEventArgs obj)
+ {
+ foreach (var option in _options)
+ {
+ option.LoadValue();
+ }
+
+ UpdateButtonState();
+ }
+
+ private void DefaultButtonPressed(BaseButton.ButtonEventArgs obj)
+ {
+ foreach (var option in _options)
+ {
+ option.ResetToDefault();
+ }
+
+ UpdateButtonState();
+ }
+
+ private string FormatPercent(OptionSliderFloatCVar slider, float value)
+ {
+ return _loc.GetString("ui-options-value-percent", ("value", value));
+ }
+
+ private static string FormatInt(OptionSliderIntCVar slider, int value)
+ {
+ return value.ToString();
+ }
+}
+
+///
+/// Base class of a single "option" for .
+///
+///
+///
+/// Implementations of this class handle loading values from backing storage or defaults,
+/// handling UI controls, and saving. The main does not know what a CVar is.
+///
+///
+/// is a derived class that makes it easier to work with options
+/// backed by a single CVar.
+///
+///
+/// The control row that owns this option.
+///
+public abstract class BaseOption(OptionsTabControlRow controller)
+{
+ ///
+ /// Should be called by derived implementations to indicate that their value changed, due to user interaction.
+ ///
+ protected virtual void ValueChanged()
+ {
+ controller.ValueChanged();
+ }
+
+ ///
+ /// Loads the value represented by this option from its backing store, into the UI state.
+ ///
+ public abstract void LoadValue();
+
+ ///
+ /// Saves the value in the UI state to the backing store.
+ ///
+ public abstract void SaveValue();
+
+ ///
+ /// Resets the UI state to that of the factory-default value. This should not write to the backing store.
+ ///
+ public abstract void ResetToDefault();
+
+ ///
+ /// Called to check if this option's UI value is different from the backing store value.
+ ///
+ /// If true, the UI value is different and was modified by the user.
+ public abstract bool IsModified();
+
+ ///
+ /// Called to check if this option's UI value is different from the backing store's default value.
+ ///
+ /// If true, the UI value is different.
+ public abstract bool IsModifiedFromDefault();
+}
+
+///
+/// Derived class of intended for making mappings to simple CVars easier.
+///
+/// The type of the CVar.
+///
+public abstract class BaseOptionCVar : BaseOption
+ where TValue : notnull
+{
+ ///
+ /// Raised immediately when the UI value of this option is changed by the user, even before saving.
+ ///
+ ///
+ ///
+ /// This can be used to update parts of the options UI based on the state of a checkbox.
+ ///
+ ///
+ public event Action? ImmediateValueChanged;
+
+ private readonly IConfigurationManager _cfg;
+ private readonly CVarDef _cVar;
+
+ ///
+ /// Sets and gets the actual CVar value to/from the frontend UI state or control.
+ ///
+ ///
+ ///
+ /// In the simplest case, this function should set a UI control's state to represent the CVar,
+ /// and inversely conver the UI control's state to the CVar value. For simple controls like a checkbox or slider,
+ /// this just means passing through their value property.
+ ///
+ ///
+ protected abstract TValue Value { get; set; }
+
+ protected BaseOptionCVar(
+ OptionsTabControlRow controller,
+ IConfigurationManager cfg,
+ CVarDef cVar)
+ : base(controller)
+ {
+ _cfg = cfg;
+ _cVar = cVar;
+ }
+
+ public override void LoadValue()
+ {
+ Value = _cfg.GetCVar(_cVar);
+ }
+
+ public override void SaveValue()
+ {
+ _cfg.SetCVar(_cVar, Value);
+ }
+
+ public override void ResetToDefault()
+ {
+ Value = _cVar.DefaultValue;
+ }
+
+ public override bool IsModified()
+ {
+ return !IsValueEqual(Value, _cfg.GetCVar(_cVar));
+ }
+
+ public override bool IsModifiedFromDefault()
+ {
+ return !IsValueEqual(Value, _cVar.DefaultValue);
+ }
+
+ protected virtual bool IsValueEqual(TValue a, TValue b)
+ {
+ // Use different logic for floats so there's some error margin.
+ // This check is handled cleanly at compile-time by the JIT.
+ if (typeof(TValue) == typeof(float))
+ return MathHelper.CloseToPercent((float) (object) a, (float) (object) b);
+
+ return EqualityComparer.Default.Equals(a, b);
+ }
+
+ protected override void ValueChanged()
+ {
+ base.ValueChanged();
+
+ ImmediateValueChanged?.Invoke(Value);
+ }
+}
+
+///
+/// Implementation of a CVar option that simply corresponds with a .
+///
+///
+///
+/// Generally, you should just call AddOption methods on
+/// instead of instantiating this type directly.
+///
+///
+///
+public sealed class OptionCheckboxCVar : BaseOptionCVar
+{
+ private readonly CheckBox _checkBox;
+ private readonly bool _invert;
+
+ protected override bool Value
+ {
+ get => _checkBox.Pressed ^ _invert;
+ set => _checkBox.Pressed = value ^ _invert;
+ }
+
+ ///
+ /// Creates a new instance of this type.
+ ///
+ /// The control row that owns this option.
+ /// The configuration manager to get and set values from.
+ /// The CVar that is being controlled by this option.
+ /// The UI control for the option.
+ ///
+ /// If true, the checkbox is inverted relative to the CVar: if the CVar is true, the checkbox will be unchecked.
+ ///
+ ///
+ ///
+ /// It is generally more convenient to call overloads on
+ /// such as instead of instantiating this type directly.
+ ///
+ ///
+ public OptionCheckboxCVar(
+ OptionsTabControlRow controller,
+ IConfigurationManager cfg,
+ CVarDef cVar,
+ CheckBox checkBox,
+ bool invert)
+ : base(controller, cfg, cVar)
+ {
+ _checkBox = checkBox;
+ _invert = invert;
+ checkBox.OnToggled += _ =>
+ {
+ ValueChanged();
+ };
+ }
+}
+
+///
+/// Implementation of a CVar option that simply corresponds with a floating-point .
+///
+///
+public sealed class OptionSliderFloatCVar : BaseOptionCVar
+{
+ ///
+ /// Scale with which to multiply slider values when mapped to the backing CVar.
+ ///
+ ///
+ /// For example, if a scale of 2 is set, a slider at 75% writes a value of 1.5 to the CVar.
+ ///
+ public float Scale { get; }
+
+ private readonly OptionSlider _slider;
+ private readonly Func _format;
+
+ protected override float Value
+ {
+ get => _slider.Slider.Value * Scale;
+ set
+ {
+ _slider.Slider.Value = value / Scale;
+ UpdateLabelValue();
+ }
+ }
+
+ ///
+ /// Creates a new instance of this type.
+ ///
+ ///
+ ///
+ /// It is generally more convenient to call overloads on
+ /// such as instead of instantiating this type directly.
+ ///
+ ///
+ /// The control row that owns this option.
+ /// The configuration manager to get and set values from.
+ /// The CVar that is being controlled by this option.
+ /// The UI control for the option.
+ /// The minimum value the slider should allow.
+ /// The maximum value the slider should allow.
+ ///
+ /// Scale with which to multiply slider values when mapped to the backing CVar. See .
+ ///
+ /// Function that will be called to format the value display next to the slider.
+ public OptionSliderFloatCVar(
+ OptionsTabControlRow controller,
+ IConfigurationManager cfg,
+ CVarDef cVar,
+ OptionSlider slider,
+ float minValue,
+ float maxValue,
+ float scale,
+ Func format) : base(controller, cfg, cVar)
+ {
+ Scale = scale;
+ _slider = slider;
+ _format = format;
+
+ slider.Slider.MinValue = minValue;
+ slider.Slider.MaxValue = maxValue;
+
+ slider.Slider.OnValueChanged += _ =>
+ {
+ ValueChanged();
+ UpdateLabelValue();
+ };
+ }
+
+ private void UpdateLabelValue()
+ {
+ _slider.ValueLabel.Text = _format(this, _slider.Slider.Value);
+ }
+}
+
+///
+/// Implementation of a CVar option that simply corresponds with an integer .
+///
+///
+public sealed class OptionSliderIntCVar : BaseOptionCVar
+{
+ private readonly OptionSlider _slider;
+ private readonly Func _format;
+
+ protected override int Value
+ {
+ get => (int) _slider.Slider.Value;
+ set
+ {
+ _slider.Slider.Value = value;
+ UpdateLabelValue();
+ }
+ }
+
+ ///
+ /// Creates a new instance of this type.
+ ///
+ ///
+ ///
+ /// It is generally more convenient to call overloads on
+ /// such as instead of instantiating this type directly.
+ ///
+ ///
+ /// The control row that owns this option.
+ /// The configuration manager to get and set values from.
+ /// The CVar that is being controlled by this option.
+ /// The UI control for the option.
+ /// The minimum value the slider should allow.
+ /// The maximum value the slider should allow.
+ /// Function that will be called to format the value display next to the slider.
+ public OptionSliderIntCVar(
+ OptionsTabControlRow controller,
+ IConfigurationManager cfg,
+ CVarDef cVar,
+ OptionSlider slider,
+ int minValue,
+ int maxValue,
+ Func format) : base(controller, cfg, cVar)
+ {
+ _slider = slider;
+ _format = format;
+
+ slider.Slider.MinValue = minValue;
+ slider.Slider.MaxValue = maxValue;
+ slider.Slider.Rounded = true;
+
+ slider.Slider.OnValueChanged += _ =>
+ {
+ ValueChanged();
+ UpdateLabelValue();
+ };
+ }
+
+ private void UpdateLabelValue()
+ {
+ _slider.ValueLabel.Text = _format(this, (int) _slider.Slider.Value);
+ }
+}
+
+///
+/// Implementation of a CVar option via a drop-down.
+///
+///
+public sealed class OptionDropDownCVar : BaseOptionCVar where T : notnull
+{
+ private readonly OptionDropDown _dropDown;
+ private readonly ItemEntry[] _entries;
+
+ protected override T Value
+ {
+ get => (T) _dropDown.Button.SelectedMetadata!;
+ set => _dropDown.Button.SelectId(FindValueId(value));
+ }
+
+ ///
+ /// Creates a new instance of this type.
+ ///
+ ///
+ ///
+ /// It is generally more convenient to call overloads on
+ /// such as instead of instantiating this type directly.
+ ///
+ ///
+ /// The control row that owns this option.
+ /// The configuration manager to get and set values from.
+ /// The CVar that is being controlled by this option.
+ /// The UI control for the option.
+ /// The list of options shown to the user.
+ public OptionDropDownCVar(
+ OptionsTabControlRow controller,
+ IConfigurationManager cfg,
+ CVarDef cVar,
+ OptionDropDown dropDown,
+ IReadOnlyCollection options) : base(controller, cfg, cVar)
+ {
+ if (options.Count == 0)
+ throw new ArgumentException("Need at least one option!");
+
+ _dropDown = dropDown;
+ _entries = new ItemEntry[options.Count];
+
+ var button = dropDown.Button;
+ var i = 0;
+ foreach (var option in options)
+ {
+ _entries[i] = new ItemEntry
+ {
+ Key = option.Key,
+ };
+
+ button.AddItem(option.Label, i);
+ button.SetItemMetadata(button.GetIdx(i), option.Key);
+ i += 1;
+ }
+
+ dropDown.Button.OnItemSelected += args =>
+ {
+ dropDown.Button.SelectId(args.Id);
+ ValueChanged();
+ };
+ }
+
+ private int FindValueId(T value)
+ {
+ for (var i = 0; i < _entries.Length; i++)
+ {
+ if (IsValueEqual(_entries[i].Key, value))
+ return i;
+ }
+
+ // This will just default select the first entry or whatever.
+ return 0;
+ }
+
+ ///
+ /// A single option for a drop-down.
+ ///
+ /// The value that this option has. This is what will be written to the CVar if selected.
+ /// The visual text shown to the user for the option.
+ ///
+ ///
+ public sealed class ValueOption(T key, string label)
+ {
+ ///
+ /// The value that this option has. This is what will be written to the CVar if selected.
+ ///
+ public readonly T Key = key;
+
+ ///
+ /// The visual text shown to the user for the option.
+ ///
+ public readonly string Label = label;
+ }
+
+ private struct ItemEntry
+ {
+ public T Key;
+ }
+}
diff --git a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
new file mode 100644
index 00000000000..54d92b2b11c
--- /dev/null
+++ b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs
new file mode 100644
index 00000000000..15182fbf126
--- /dev/null
+++ b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs
@@ -0,0 +1,24 @@
+using Content.Shared.CCVar;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Options.UI.Tabs;
+
+[GenerateTypedNameReferences]
+public sealed partial class AccessibilityTab : Control
+{
+ public AccessibilityTab()
+ {
+ RobustXamlLoader.Load(this);
+
+ Control.AddOptionCheckBox(CCVars.ChatEnableColorName, EnableColorNameCheckBox);
+ Control.AddOptionCheckBox(CCVars.AccessibilityColorblindFriendly, ColorblindFriendlyCheckBox);
+ Control.AddOptionCheckBox(CCVars.ReducedMotion, ReducedMotionCheckBox);
+ Control.AddOptionPercentSlider(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider);
+ Control.AddOptionPercentSlider(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider);
+
+ Control.Initialize();
+ }
+}
+
diff --git a/Content.Client/Options/UI/Tabs/AudioTab.xaml b/Content.Client/Options/UI/Tabs/AudioTab.xaml
index e54b0dc34ee..c374af31c58 100644
--- a/Content.Client/Options/UI/Tabs/AudioTab.xaml
+++ b/Content.Client/Options/UI/Tabs/AudioTab.xaml
@@ -1,128 +1,26 @@
+ xmlns:ui="clr-namespace:Content.Client.Options.UI">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
diff --git a/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs b/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs
index 470ca7d799d..78186d446c7 100644
--- a/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs
@@ -3,200 +3,72 @@
using Robust.Client.Audio;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared;
using Robust.Shared.Configuration;
-using Range = Robust.Client.UserInterface.Controls.Range;
-namespace Content.Client.Options.UI.Tabs
-{
- [GenerateTypedNameReferences]
- public sealed partial class AudioTab : Control
- {
- [Dependency] private readonly IConfigurationManager _cfg = default!;
- private readonly IAudioManager _audio;
-
- public AudioTab()
- {
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
-
- _audio = IoCManager.Resolve();
- LobbyMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.LobbyMusicEnabled);
- RestartSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.RestartSoundsEnabled);
- EventMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.EventMusicEnabled);
- AdminSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.AdminSoundsEnabled);
-
- ApplyButton.OnPressed += OnApplyButtonPressed;
- ResetButton.OnPressed += OnResetButtonPressed;
- MasterVolumeSlider.OnValueChanged += OnMasterVolumeSliderChanged;
- MidiVolumeSlider.OnValueChanged += OnMidiVolumeSliderChanged;
- AmbientMusicVolumeSlider.OnValueChanged += OnAmbientMusicVolumeSliderChanged;
- AmbienceVolumeSlider.OnValueChanged += OnAmbienceVolumeSliderChanged;
- AmbienceSoundsSlider.OnValueChanged += OnAmbienceSoundsSliderChanged;
- LobbyVolumeSlider.OnValueChanged += OnLobbyVolumeSliderChanged;
- InterfaceVolumeSlider.OnValueChanged += OnInterfaceVolumeSliderChanged;
- LobbyMusicCheckBox.OnToggled += OnLobbyMusicCheckToggled;
- RestartSoundsCheckBox.OnToggled += OnRestartSoundsCheckToggled;
- EventMusicCheckBox.OnToggled += OnEventMusicCheckToggled;
- AdminSoundsCheckBox.OnToggled += OnAdminSoundsCheckToggled;
-
- AmbienceSoundsSlider.MinValue = _cfg.GetCVar(CCVars.MinMaxAmbientSourcesConfigured);
- AmbienceSoundsSlider.MaxValue = _cfg.GetCVar(CCVars.MaxMaxAmbientSourcesConfigured);
-
- Reset();
- }
-
- protected override void Dispose(bool disposing)
- {
- ApplyButton.OnPressed -= OnApplyButtonPressed;
- ResetButton.OnPressed -= OnResetButtonPressed;
- MasterVolumeSlider.OnValueChanged -= OnMasterVolumeSliderChanged;
- MidiVolumeSlider.OnValueChanged -= OnMidiVolumeSliderChanged;
- AmbientMusicVolumeSlider.OnValueChanged -= OnAmbientMusicVolumeSliderChanged;
- AmbienceVolumeSlider.OnValueChanged -= OnAmbienceVolumeSliderChanged;
- LobbyVolumeSlider.OnValueChanged -= OnLobbyVolumeSliderChanged;
- InterfaceVolumeSlider.OnValueChanged -= OnInterfaceVolumeSliderChanged;
- base.Dispose(disposing);
- }
-
- private void OnLobbyVolumeSliderChanged(Range obj)
- {
- UpdateChanges();
- }
-
- private void OnInterfaceVolumeSliderChanged(Range obj)
- {
- UpdateChanges();
- }
-
- private void OnAmbientMusicVolumeSliderChanged(Range obj)
- {
- UpdateChanges();
- }
-
- private void OnAmbienceVolumeSliderChanged(Range obj)
- {
- UpdateChanges();
- }
-
- private void OnAmbienceSoundsSliderChanged(Range obj)
- {
- UpdateChanges();
- }
-
- private void OnMasterVolumeSliderChanged(Range range)
- {
- _audio.SetMasterGain(MasterVolumeSlider.Value / 100f * ContentAudioSystem.MasterVolumeMultiplier);
- UpdateChanges();
- }
+namespace Content.Client.Options.UI.Tabs;
- private void OnMidiVolumeSliderChanged(Range range)
- {
- UpdateChanges();
- }
-
- private void OnLobbyMusicCheckToggled(BaseButton.ButtonEventArgs args)
- {
- UpdateChanges();
- }
- private void OnRestartSoundsCheckToggled(BaseButton.ButtonEventArgs args)
- {
- UpdateChanges();
- }
- private void OnEventMusicCheckToggled(BaseButton.ButtonEventArgs args)
- {
- UpdateChanges();
- }
-
- private void OnAdminSoundsCheckToggled(BaseButton.ButtonEventArgs args)
- {
- UpdateChanges();
- }
-
- private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
- {
- _cfg.SetCVar(CVars.AudioMasterVolume, MasterVolumeSlider.Value / 100f * ContentAudioSystem.MasterVolumeMultiplier);
- // Want the CVar updated values to have the multiplier applied
- // For the UI we just display 0-100 still elsewhere
- _cfg.SetCVar(CVars.MidiVolume, MidiVolumeSlider.Value / 100f * ContentAudioSystem.MidiVolumeMultiplier);
- _cfg.SetCVar(CCVars.AmbienceVolume, AmbienceVolumeSlider.Value / 100f * ContentAudioSystem.AmbienceMultiplier);
- _cfg.SetCVar(CCVars.AmbientMusicVolume, AmbientMusicVolumeSlider.Value / 100f * ContentAudioSystem.AmbientMusicMultiplier);
- _cfg.SetCVar(CCVars.LobbyMusicVolume, LobbyVolumeSlider.Value / 100f * ContentAudioSystem.LobbyMultiplier);
- _cfg.SetCVar(CCVars.InterfaceVolume, InterfaceVolumeSlider.Value / 100f * ContentAudioSystem.InterfaceMultiplier);
-
- _cfg.SetCVar(CCVars.MaxAmbientSources, (int)AmbienceSoundsSlider.Value);
-
- _cfg.SetCVar(CCVars.LobbyMusicEnabled, LobbyMusicCheckBox.Pressed);
- _cfg.SetCVar(CCVars.RestartSoundsEnabled, RestartSoundsCheckBox.Pressed);
- _cfg.SetCVar(CCVars.EventMusicEnabled, EventMusicCheckBox.Pressed);
- _cfg.SetCVar(CCVars.AdminSoundsEnabled, AdminSoundsCheckBox.Pressed);
- _cfg.SaveToFile();
- UpdateChanges();
- }
-
- private void OnResetButtonPressed(BaseButton.ButtonEventArgs args)
- {
- Reset();
- }
-
- private void Reset()
- {
- MasterVolumeSlider.Value = _cfg.GetCVar(CVars.AudioMasterVolume) * 100f / ContentAudioSystem.MasterVolumeMultiplier;
- MidiVolumeSlider.Value = _cfg.GetCVar(CVars.MidiVolume) * 100f / ContentAudioSystem.MidiVolumeMultiplier;
- AmbienceVolumeSlider.Value = _cfg.GetCVar(CCVars.AmbienceVolume) * 100f / ContentAudioSystem.AmbienceMultiplier;
- AmbientMusicVolumeSlider.Value = _cfg.GetCVar(CCVars.AmbientMusicVolume) * 100f / ContentAudioSystem.AmbientMusicMultiplier;
- LobbyVolumeSlider.Value = _cfg.GetCVar(CCVars.LobbyMusicVolume) * 100f / ContentAudioSystem.LobbyMultiplier;
- InterfaceVolumeSlider.Value = _cfg.GetCVar(CCVars.InterfaceVolume) * 100f / ContentAudioSystem.InterfaceMultiplier;
-
- AmbienceSoundsSlider.Value = _cfg.GetCVar(CCVars.MaxAmbientSources);
-
- LobbyMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.LobbyMusicEnabled);
- RestartSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.RestartSoundsEnabled);
- EventMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.EventMusicEnabled);
- AdminSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.AdminSoundsEnabled);
- UpdateChanges();
- }
+[GenerateTypedNameReferences]
+public sealed partial class AudioTab : Control
+{
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+ [Dependency] private readonly IAudioManager _audio = default!;
- private void UpdateChanges()
- {
- // y'all need jesus.
- var isMasterVolumeSame =
- Math.Abs(MasterVolumeSlider.Value - _cfg.GetCVar(CVars.AudioMasterVolume) * 100f / ContentAudioSystem.MasterVolumeMultiplier) < 0.01f;
- var isMidiVolumeSame =
- Math.Abs(MidiVolumeSlider.Value - _cfg.GetCVar(CVars.MidiVolume) * 100f / ContentAudioSystem.MidiVolumeMultiplier) < 0.01f;
- var isAmbientVolumeSame =
- Math.Abs(AmbienceVolumeSlider.Value - _cfg.GetCVar(CCVars.AmbienceVolume) * 100f / ContentAudioSystem.AmbienceMultiplier) < 0.01f;
- var isAmbientMusicVolumeSame =
- Math.Abs(AmbientMusicVolumeSlider.Value - _cfg.GetCVar(CCVars.AmbientMusicVolume) * 100f / ContentAudioSystem.AmbientMusicMultiplier) < 0.01f;
- var isLobbyVolumeSame =
- Math.Abs(LobbyVolumeSlider.Value - _cfg.GetCVar(CCVars.LobbyMusicVolume) * 100f / ContentAudioSystem.LobbyMultiplier) < 0.01f;
- var isInterfaceVolumeSame =
- Math.Abs(InterfaceVolumeSlider.Value - _cfg.GetCVar(CCVars.InterfaceVolume) * 100f / ContentAudioSystem.InterfaceMultiplier) < 0.01f;
+ public AudioTab()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ var masterVolume = Control.AddOptionPercentSlider(
+ CVars.AudioMasterVolume,
+ SliderVolumeMaster,
+ scale: ContentAudioSystem.MasterVolumeMultiplier);
+ masterVolume.ImmediateValueChanged += OnMasterVolumeSliderChanged;
+
+ Control.AddOptionPercentSlider(
+ CVars.MidiVolume,
+ SliderVolumeMidi,
+ scale: ContentAudioSystem.MidiVolumeMultiplier);
+
+ Control.AddOptionPercentSlider(
+ CCVars.AmbientMusicVolume,
+ SliderVolumeAmbientMusic,
+ scale: ContentAudioSystem.AmbientMusicMultiplier);
+
+ Control.AddOptionPercentSlider(
+ CCVars.AmbienceVolume,
+ SliderVolumeAmbience,
+ scale: ContentAudioSystem.AmbienceMultiplier);
+
+ Control.AddOptionPercentSlider(
+ CCVars.LobbyMusicVolume,
+ SliderVolumeLobby,
+ scale: ContentAudioSystem.LobbyMultiplier);
+
+ Control.AddOptionPercentSlider(
+ CCVars.InterfaceVolume,
+ SliderVolumeInterface,
+ scale: ContentAudioSystem.InterfaceMultiplier);
+
+ Control.AddOptionSlider(
+ CCVars.MaxAmbientSources,
+ SliderMaxAmbienceSounds,
+ _cfg.GetCVar(CCVars.MinMaxAmbientSourcesConfigured),
+ _cfg.GetCVar(CCVars.MaxMaxAmbientSourcesConfigured));
+
+ Control.AddOptionCheckBox(CCVars.LobbyMusicEnabled, LobbyMusicCheckBox);
+ Control.AddOptionCheckBox(CCVars.RestartSoundsEnabled, RestartSoundsCheckBox);
+ Control.AddOptionCheckBox(CCVars.EventMusicEnabled, EventMusicCheckBox);
+ Control.AddOptionCheckBox(CCVars.AdminSoundsEnabled, AdminSoundsCheckBox);
+
+ Control.Initialize();
+ }
- var isAmbientSoundsSame = (int)AmbienceSoundsSlider.Value == _cfg.GetCVar(CCVars.MaxAmbientSources);
- var isLobbySame = LobbyMusicCheckBox.Pressed == _cfg.GetCVar(CCVars.LobbyMusicEnabled);
- var isRestartSoundsSame = RestartSoundsCheckBox.Pressed == _cfg.GetCVar(CCVars.RestartSoundsEnabled);
- var isEventSame = EventMusicCheckBox.Pressed == _cfg.GetCVar(CCVars.EventMusicEnabled);
- var isAdminSoundsSame = AdminSoundsCheckBox.Pressed == _cfg.GetCVar(CCVars.AdminSoundsEnabled);
- var isEverythingSame = isMasterVolumeSame && isMidiVolumeSame && isAmbientVolumeSame && isAmbientMusicVolumeSame && isAmbientSoundsSame && isLobbySame && isRestartSoundsSame && isEventSame
- && isAdminSoundsSame && isLobbyVolumeSame && isInterfaceVolumeSame;
- ApplyButton.Disabled = isEverythingSame;
- ResetButton.Disabled = isEverythingSame;
- MasterVolumeLabel.Text =
- Loc.GetString("ui-options-volume-percent", ("volume", MasterVolumeSlider.Value / 100));
- MidiVolumeLabel.Text =
- Loc.GetString("ui-options-volume-percent", ("volume", MidiVolumeSlider.Value / 100));
- AmbientMusicVolumeLabel.Text =
- Loc.GetString("ui-options-volume-percent", ("volume", AmbientMusicVolumeSlider.Value / 100));
- AmbienceVolumeLabel.Text =
- Loc.GetString("ui-options-volume-percent", ("volume", AmbienceVolumeSlider.Value / 100));
- LobbyVolumeLabel.Text =
- Loc.GetString("ui-options-volume-percent", ("volume", LobbyVolumeSlider.Value / 100));
- InterfaceVolumeLabel.Text =
- Loc.GetString("ui-options-volume-percent", ("volume", InterfaceVolumeSlider.Value / 100));
- AmbienceSoundsLabel.Text = ((int)AmbienceSoundsSlider.Value).ToString();
- }
+ private void OnMasterVolumeSliderChanged(float value)
+ {
+ // TODO: I was thinking of giving OptionsTabControlRow a flag to "set CVar immediately", but I'm deferring that
+ // until there's a proper system for enforcing people don't close the window with pending changes.
+ _audio.SetMasterGain(value);
}
}
diff --git a/Content.Client/Options/UI/Tabs/GraphicsTab.xaml b/Content.Client/Options/UI/Tabs/GraphicsTab.xaml
index ec1b9aa002f..f1b9743cad3 100644
--- a/Content.Client/Options/UI/Tabs/GraphicsTab.xaml
+++ b/Content.Client/Options/UI/Tabs/GraphicsTab.xaml
@@ -1,53 +1,38 @@
+ xmlns:tabs="clr-namespace:Content.Client.Options.UI.Tabs"
+ xmlns:ui="clr-namespace:Content.Client.Options.UI">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
diff --git a/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs b/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs
index a22adf3e632..f53a2edd957 100644
--- a/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs
@@ -7,220 +7,141 @@
using Robust.Shared;
using Robust.Shared.Configuration;
-namespace Content.Client.Options.UI.Tabs
+namespace Content.Client.Options.UI.Tabs;
+
+[GenerateTypedNameReferences]
+public sealed partial class GraphicsTab : Control
{
- [GenerateTypedNameReferences]
- public sealed partial class GraphicsTab : Control
- {
- private static readonly float[] UIScaleOptions =
- {
- 0f,
- 0.75f,
- 1f,
- 1.25f,
- 1.50f,
- 1.75f,
- 2f
- };
-
- [Dependency] private readonly IConfigurationManager _cfg = default!;
-
- public GraphicsTab()
- {
- IoCManager.InjectDependencies(this);
- RobustXamlLoader.Load(this);
-
- VSyncCheckBox.OnToggled += OnCheckBoxToggled;
- FullscreenCheckBox.OnToggled += OnCheckBoxToggled;
-
- LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-very-low"));
- LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-low"));
- LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-medium"));
- LightingPresetOption.AddItem(Loc.GetString("ui-options-lighting-high"));
- LightingPresetOption.OnItemSelected += OnLightingQualityChanged;
-
- UIScaleOption.AddItem(Loc.GetString("ui-options-scale-auto",
- ("scale", UserInterfaceManager.DefaultUIScale)));
- UIScaleOption.AddItem(Loc.GetString("ui-options-scale-75"));
- UIScaleOption.AddItem(Loc.GetString("ui-options-scale-100"));
- UIScaleOption.AddItem(Loc.GetString("ui-options-scale-125"));
- UIScaleOption.AddItem(Loc.GetString("ui-options-scale-150"));
- UIScaleOption.AddItem(Loc.GetString("ui-options-scale-175"));
- UIScaleOption.AddItem(Loc.GetString("ui-options-scale-200"));
- UIScaleOption.OnItemSelected += OnUIScaleChanged;
-
- ViewportStretchCheckBox.OnToggled += _ =>
- {
- UpdateViewportScale();
- UpdateApplyButton();
- };
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
- ViewportScaleSlider.OnValueChanged += _ =>
- {
- UpdateApplyButton();
- UpdateViewportScale();
- };
+ public GraphicsTab()
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+
+ Control.AddOptionCheckBox(CVars.DisplayVSync, VSyncCheckBox);
+ Control.AddOption(new OptionFullscreen(Control, _cfg, FullscreenCheckBox));
+ Control.AddOption(new OptionLightingQuality(Control, _cfg, DropDownLightingQuality));
+
+ Control.AddOptionDropDown(
+ CVars.DisplayUIScale,
+ DropDownUIScale,
+ [
+ new OptionDropDownCVar.ValueOption(
+ 0f,
+ Loc.GetString("ui-options-scale-auto", ("scale", UserInterfaceManager.DefaultUIScale))),
+ new OptionDropDownCVar.ValueOption(0.75f, Loc.GetString("ui-options-scale-75")),
+ new OptionDropDownCVar.ValueOption(1.00f, Loc.GetString("ui-options-scale-100")),
+ new OptionDropDownCVar.ValueOption(1.25f, Loc.GetString("ui-options-scale-125")),
+ new OptionDropDownCVar.ValueOption(1.50f, Loc.GetString("ui-options-scale-150")),
+ new OptionDropDownCVar.ValueOption(1.75f, Loc.GetString("ui-options-scale-175")),
+ new OptionDropDownCVar.ValueOption(2.00f, Loc.GetString("ui-options-scale-200")),
+ ]);
+
+ var vpStretch = Control.AddOptionCheckBox(CCVars.ViewportStretch, ViewportStretchCheckBox);
+ var vpVertFit = Control.AddOptionCheckBox(CCVars.ViewportVerticalFit, ViewportVerticalFitCheckBox);
+ Control.AddOptionSlider(
+ CCVars.ViewportFixedScaleFactor,
+ ViewportScaleSlider,
+ 1,
+ 5,
+ (_, value) => Loc.GetString("ui-options-vp-scale-value", ("scale", value)));
+
+ vpStretch.ImmediateValueChanged += _ => UpdateViewportSettingsVisibility();
+ vpVertFit.ImmediateValueChanged += _ => UpdateViewportSettingsVisibility();
+
+ Control.AddOptionSlider(
+ CCVars.ViewportWidth,
+ ViewportWidthSlider,
+ (int)ViewportWidthSlider.Slider.MinValue,
+ (int)ViewportWidthSlider.Slider.MaxValue);
+
+ Control.AddOption(new OptionIntegerScaling(Control, _cfg, IntegerScalingCheckBox));
+ Control.AddOptionCheckBox(CCVars.ViewportScaleRender, ViewportLowResCheckBox, invert: true);
+ Control.AddOptionCheckBox(CCVars.ParallaxLowQuality, ParallaxLowQualityCheckBox);
+ Control.AddOptionCheckBox(CCVars.HudFpsCounterVisible, FpsCounterCheckBox);
+
+ Control.Initialize();
+
+ _cfg.OnValueChanged(CCVars.ViewportMinimumWidth, _ => UpdateViewportWidthRange());
+ _cfg.OnValueChanged(CCVars.ViewportMaximumWidth, _ => UpdateViewportWidthRange());
+
+ UpdateViewportWidthRange();
+ UpdateViewportSettingsVisibility();
+ }
- ViewportWidthSlider.OnValueChanged += _ =>
- {
- UpdateViewportWidthDisplay();
- UpdateApplyButton();
- };
+ private void UpdateViewportSettingsVisibility()
+ {
+ ViewportScaleSlider.Visible = !ViewportStretchCheckBox.Pressed;
+ IntegerScalingCheckBox.Visible = ViewportStretchCheckBox.Pressed;
+ ViewportVerticalFitCheckBox.Visible = ViewportStretchCheckBox.Pressed;
+ ViewportWidthSlider.Visible = !ViewportStretchCheckBox.Pressed || !ViewportVerticalFitCheckBox.Pressed;
+ }
- ViewportVerticalFitCheckBox.OnToggled += _ =>
- {
- UpdateViewportScale();
- UpdateApplyButton();
- };
+ private void UpdateViewportWidthRange()
+ {
+ var min = _cfg.GetCVar(CCVars.ViewportMinimumWidth);
+ var max = _cfg.GetCVar(CCVars.ViewportMaximumWidth);
- IntegerScalingCheckBox.OnToggled += OnCheckBoxToggled;
- ViewportLowResCheckBox.OnToggled += OnCheckBoxToggled;
- ParallaxLowQualityCheckBox.OnToggled += OnCheckBoxToggled;
- FpsCounterCheckBox.OnToggled += OnCheckBoxToggled;
- ApplyButton.OnPressed += OnApplyButtonPressed;
- VSyncCheckBox.Pressed = _cfg.GetCVar(CVars.DisplayVSync);
- FullscreenCheckBox.Pressed = ConfigIsFullscreen;
- LightingPresetOption.SelectId(GetConfigLightingQuality());
- UIScaleOption.SelectId(GetConfigUIScalePreset(ConfigUIScale));
- ViewportScaleSlider.Value = _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
- ViewportStretchCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportStretch);
- IntegerScalingCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportSnapToleranceMargin) != 0;
- ViewportVerticalFitCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportVerticalFit);
- ViewportLowResCheckBox.Pressed = !_cfg.GetCVar(CCVars.ViewportScaleRender);
- ParallaxLowQualityCheckBox.Pressed = _cfg.GetCVar(CCVars.ParallaxLowQuality);
- FpsCounterCheckBox.Pressed = _cfg.GetCVar(CCVars.HudFpsCounterVisible);
- ViewportWidthSlider.Value = _cfg.GetCVar(CCVars.ViewportWidth);
-
- _cfg.OnValueChanged(CCVars.ViewportMinimumWidth, _ => UpdateViewportWidthRange());
- _cfg.OnValueChanged(CCVars.ViewportMaximumWidth, _ => UpdateViewportWidthRange());
-
- UpdateViewportWidthRange();
- UpdateViewportWidthDisplay();
- UpdateViewportScale();
- UpdateApplyButton();
- }
+ ViewportWidthSlider.Slider.MinValue = min;
+ ViewportWidthSlider.Slider.MaxValue = max;
+ }
- private void OnUIScaleChanged(OptionButton.ItemSelectedEventArgs args)
- {
- UIScaleOption.SelectId(args.Id);
- UpdateApplyButton();
- }
+ private sealed class OptionLightingQuality : BaseOption
+ {
+ private readonly IConfigurationManager _cfg;
+ private readonly OptionDropDown _dropDown;
- private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
- {
- _cfg.SetCVar(CVars.DisplayVSync, VSyncCheckBox.Pressed);
- SetConfigLightingQuality(LightingPresetOption.SelectedId);
-
- _cfg.SetCVar(CVars.DisplayWindowMode,
- (int) (FullscreenCheckBox.Pressed ? WindowMode.Fullscreen : WindowMode.Windowed));
- _cfg.SetCVar(CVars.DisplayUIScale, UIScaleOptions[UIScaleOption.SelectedId]);
- _cfg.SetCVar(CCVars.ViewportStretch, ViewportStretchCheckBox.Pressed);
- _cfg.SetCVar(CCVars.ViewportFixedScaleFactor, (int) ViewportScaleSlider.Value);
- _cfg.SetCVar(CCVars.ViewportSnapToleranceMargin,
- IntegerScalingCheckBox.Pressed ? CCVars.ViewportSnapToleranceMargin.DefaultValue : 0);
- _cfg.SetCVar(CCVars.ViewportVerticalFit, ViewportVerticalFitCheckBox.Pressed);
- _cfg.SetCVar(CCVars.ViewportScaleRender, !ViewportLowResCheckBox.Pressed);
- _cfg.SetCVar(CCVars.ParallaxLowQuality, ParallaxLowQualityCheckBox.Pressed);
- _cfg.SetCVar(CCVars.HudFpsCounterVisible, FpsCounterCheckBox.Pressed);
- _cfg.SetCVar(CCVars.ViewportWidth, (int) ViewportWidthSlider.Value);
-
- _cfg.SaveToFile();
- UpdateApplyButton();
- }
+ private const int QualityVeryLow = 0;
+ private const int QualityLow = 1;
+ private const int QualityMedium = 2;
+ private const int QualityHigh = 3;
- private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
- {
- UpdateApplyButton();
- }
+ private const int QualityDefault = QualityMedium;
- private void OnLightingQualityChanged(OptionButton.ItemSelectedEventArgs args)
+ public OptionLightingQuality(OptionsTabControlRow controller, IConfigurationManager cfg, OptionDropDown dropDown) : base(controller)
{
- LightingPresetOption.SelectId(args.Id);
- UpdateApplyButton();
+ _cfg = cfg;
+ _dropDown = dropDown;
+ var button = dropDown.Button;
+ button.AddItem(Loc.GetString("ui-options-lighting-very-low"), QualityVeryLow);
+ button.AddItem(Loc.GetString("ui-options-lighting-low"), QualityLow);
+ button.AddItem(Loc.GetString("ui-options-lighting-medium"), QualityMedium);
+ button.AddItem(Loc.GetString("ui-options-lighting-high"), QualityHigh);
+ button.OnItemSelected += OnOptionSelected;
}
- private void UpdateApplyButton()
+ private void OnOptionSelected(OptionButton.ItemSelectedEventArgs obj)
{
- var isVSyncSame = VSyncCheckBox.Pressed == _cfg.GetCVar(CVars.DisplayVSync);
- var isFullscreenSame = FullscreenCheckBox.Pressed == ConfigIsFullscreen;
- var isLightingQualitySame = LightingPresetOption.SelectedId == GetConfigLightingQuality();
- var isUIScaleSame = MathHelper.CloseToPercent(UIScaleOptions[UIScaleOption.SelectedId], ConfigUIScale);
- var isVPStretchSame = ViewportStretchCheckBox.Pressed == _cfg.GetCVar(CCVars.ViewportStretch);
- var isVPScaleSame = (int) ViewportScaleSlider.Value == _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
- var isIntegerScalingSame = IntegerScalingCheckBox.Pressed == (_cfg.GetCVar(CCVars.ViewportSnapToleranceMargin) != 0);
- var isVPVerticalFitSame = ViewportVerticalFitCheckBox.Pressed == _cfg.GetCVar(CCVars.ViewportVerticalFit);
- var isVPResSame = ViewportLowResCheckBox.Pressed == !_cfg.GetCVar(CCVars.ViewportScaleRender);
- var isPLQSame = ParallaxLowQualityCheckBox.Pressed == _cfg.GetCVar(CCVars.ParallaxLowQuality);
- var isFpsCounterVisibleSame = FpsCounterCheckBox.Pressed == _cfg.GetCVar(CCVars.HudFpsCounterVisible);
- var isWidthSame = (int) ViewportWidthSlider.Value == _cfg.GetCVar(CCVars.ViewportWidth);
-
- ApplyButton.Disabled = isVSyncSame &&
- isFullscreenSame &&
- isLightingQualitySame &&
- isUIScaleSame &&
- isVPStretchSame &&
- isVPScaleSame &&
- isIntegerScalingSame &&
- isVPVerticalFitSame &&
- isVPResSame &&
- isPLQSame &&
- isFpsCounterVisibleSame &&
- isWidthSame;
+ _dropDown.Button.SelectId(obj.Id);
+ ValueChanged();
}
- private bool ConfigIsFullscreen =>
- _cfg.GetCVar(CVars.DisplayWindowMode) == (int) WindowMode.Fullscreen;
-
- public void UpdateProperties()
+ public override void LoadValue()
{
- FullscreenCheckBox.Pressed = ConfigIsFullscreen;
+ _dropDown.Button.SelectId(GetConfigLightingQuality());
}
-
- private float ConfigUIScale => _cfg.GetCVar(CVars.DisplayUIScale);
-
- private int GetConfigLightingQuality()
+ public override void SaveValue()
{
- var val = _cfg.GetCVar(CVars.LightResolutionScale);
- var soft = _cfg.GetCVar(CVars.LightSoftShadows);
- if (val <= 0.125)
- {
- return 0;
- }
- else if ((val <= 0.5) && !soft)
- {
- return 1;
- }
- else if (val <= 0.5)
+ switch (_dropDown.Button.SelectedId)
{
- return 2;
- }
- else
- {
- return 3;
- }
- }
-
- private void SetConfigLightingQuality(int value)
- {
- switch (value)
- {
- case 0:
+ case QualityVeryLow:
_cfg.SetCVar(CVars.LightResolutionScale, 0.125f);
_cfg.SetCVar(CVars.LightSoftShadows, false);
_cfg.SetCVar(CVars.LightBlur, false);
break;
- case 1:
+ case QualityLow:
_cfg.SetCVar(CVars.LightResolutionScale, 0.5f);
_cfg.SetCVar(CVars.LightSoftShadows, false);
_cfg.SetCVar(CVars.LightBlur, true);
break;
- case 2:
+ default: // = QualityMedium
_cfg.SetCVar(CVars.LightResolutionScale, 0.5f);
_cfg.SetCVar(CVars.LightSoftShadows, true);
_cfg.SetCVar(CVars.LightBlur, true);
break;
- case 3:
+ case QualityHigh:
_cfg.SetCVar(CVars.LightResolutionScale, 1);
_cfg.SetCVar(CVars.LightSoftShadows, true);
_cfg.SetCVar(CVars.LightBlur, true);
@@ -228,40 +149,83 @@ private void SetConfigLightingQuality(int value)
}
}
- private static int GetConfigUIScalePreset(float value)
+ public override void ResetToDefault()
{
- for (var i = 0; i < UIScaleOptions.Length; i++)
- {
- if (MathHelper.CloseToPercent(UIScaleOptions[i], value))
- {
- return i;
- }
- }
+ _dropDown.Button.SelectId(QualityDefault);
+ }
+
+ public override bool IsModified()
+ {
+ return _dropDown.Button.SelectedId != GetConfigLightingQuality();
+ }
+
+ public override bool IsModifiedFromDefault()
+ {
+ return _dropDown.Button.SelectedId != QualityDefault;
+ }
+
+ private int GetConfigLightingQuality()
+ {
+ var val = _cfg.GetCVar(CVars.LightResolutionScale);
+ var soft = _cfg.GetCVar(CVars.LightSoftShadows);
+ if (val <= 0.125)
+ return QualityVeryLow;
+
+ if ((val <= 0.5) && !soft)
+ return QualityLow;
- return 0;
+ if (val <= 0.5)
+ return QualityMedium;
+
+ return QualityHigh;
}
+ }
+
+ private sealed class OptionFullscreen : BaseOptionCVar
+ {
+ private readonly CheckBox _checkBox;
- private void UpdateViewportScale()
+ protected override int Value
{
- ViewportScaleBox.Visible = !ViewportStretchCheckBox.Pressed;
- IntegerScalingCheckBox.Visible = ViewportStretchCheckBox.Pressed;
- ViewportVerticalFitCheckBox.Visible = ViewportStretchCheckBox.Pressed;
- ViewportWidthSlider.Visible = ViewportWidthSliderDisplay.Visible = !ViewportStretchCheckBox.Pressed || ViewportStretchCheckBox.Pressed && !ViewportVerticalFitCheckBox.Pressed;
- ViewportScaleText.Text = Loc.GetString("ui-options-vp-scale", ("scale", ViewportScaleSlider.Value));
+ get => _checkBox.Pressed ? (int) WindowMode.Fullscreen : (int) WindowMode.Windowed;
+ set => _checkBox.Pressed = (value == (int) WindowMode.Fullscreen);
}
- private void UpdateViewportWidthRange()
+ public OptionFullscreen(
+ OptionsTabControlRow controller,
+ IConfigurationManager cfg,
+ CheckBox checkBox)
+ : base(controller, cfg, CVars.DisplayWindowMode)
{
- var min = _cfg.GetCVar(CCVars.ViewportMinimumWidth);
- var max = _cfg.GetCVar(CCVars.ViewportMaximumWidth);
+ _checkBox = checkBox;
+ _checkBox.OnToggled += _ =>
+ {
+ ValueChanged();
+ };
+ }
+ }
- ViewportWidthSlider.MinValue = min;
- ViewportWidthSlider.MaxValue = max;
+ private sealed class OptionIntegerScaling : BaseOptionCVar
+ {
+ private readonly CheckBox _checkBox;
+
+ protected override int Value
+ {
+ get => _checkBox.Pressed ? CCVars.ViewportSnapToleranceMargin.DefaultValue : 0;
+ set => _checkBox.Pressed = (value != 0);
}
- private void UpdateViewportWidthDisplay()
+ public OptionIntegerScaling(
+ OptionsTabControlRow controller,
+ IConfigurationManager cfg,
+ CheckBox checkBox)
+ : base(controller, cfg, CCVars.ViewportSnapToleranceMargin)
{
- ViewportWidthSliderDisplay.Text = Loc.GetString("ui-options-vp-width", ("width", (int) ViewportWidthSlider.Value));
+ _checkBox = checkBox;
+ _checkBox.OnToggled += _ =>
+ {
+ ValueChanged();
+ };
}
}
}
diff --git a/Content.Client/Options/UI/Tabs/MiscTab.xaml b/Content.Client/Options/UI/Tabs/MiscTab.xaml
index 0c6ec380424..c1733e209db 100644
--- a/Content.Client/Options/UI/Tabs/MiscTab.xaml
+++ b/Content.Client/Options/UI/Tabs/MiscTab.xaml
@@ -1,76 +1,34 @@
+ xmlns:tabs="clr-namespace:Content.Client.Options.UI.Tabs"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:ui="clr-namespace:Content.Client.Options.UI">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
+
diff --git a/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs b/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
index 13e3fd05f55..2aad4e1d0b6 100644
--- a/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
@@ -5,201 +5,54 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Player;
using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared;
-using Robust.Shared.Configuration;
-using Robust.Shared.Network;
-using Robust.Shared.Player;
using Robust.Shared.Prototypes;
-using Range = Robust.Client.UserInterface.Controls.Range;
-namespace Content.Client.Options.UI.Tabs
-{
- [GenerateTypedNameReferences]
- public sealed partial class MiscTab : Control
- {
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IConfigurationManager _cfg = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
-
- private readonly Dictionary _hudThemeIdToIndex = new();
-
- public MiscTab()
- {
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
-
- var themes = _prototypeManager.EnumeratePrototypes().ToList();
- themes.Sort();
- foreach (var gear in themes)
- {
- HudThemeOption.AddItem(Loc.GetString(gear.Name));
- _hudThemeIdToIndex.Add(gear.ID, HudThemeOption.GetItemId(HudThemeOption.ItemCount - 1));
- }
-
- var hudLayout = _cfg.GetCVar(CCVars.UILayout);
- var id = 0;
- foreach (var layout in Enum.GetValues(typeof(ScreenType)))
- {
- var name = layout.ToString()!;
- HudLayoutOption.AddItem(name, id);
- if (name == hudLayout)
- {
- HudLayoutOption.SelectId(id);
- }
- HudLayoutOption.SetItemMetadata(id, name);
-
- id++;
- }
-
- HudLayoutOption.OnItemSelected += args =>
- {
- HudLayoutOption.SelectId(args.Id);
- UpdateApplyButton();
- };
-
- // Channel can be null in replays so.
- // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
- ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { };
-
- HudThemeOption.OnItemSelected += OnHudThemeChanged;
- DiscordRich.OnToggled += OnCheckBoxToggled;
- ShowOocPatronColor.OnToggled += OnCheckBoxToggled;
- ShowLoocAboveHeadCheckBox.OnToggled += OnCheckBoxToggled;
- ShowHeldItemCheckBox.OnToggled += OnCheckBoxToggled;
- ShowCombatModeIndicatorsCheckBox.OnToggled += OnCheckBoxToggled;
- OpaqueStorageWindowCheckBox.OnToggled += OnCheckBoxToggled;
- FancySpeechBubblesCheckBox.OnToggled += OnCheckBoxToggled;
- FancyNameBackgroundsCheckBox.OnToggled += OnCheckBoxToggled;
- EnableColorNameCheckBox.OnToggled += OnCheckBoxToggled;
- ColorblindFriendlyCheckBox.OnToggled += OnCheckBoxToggled;
- ReducedMotionCheckBox.OnToggled += OnCheckBoxToggled;
- ChatWindowOpacitySlider.OnValueChanged += OnChatWindowOpacitySliderChanged;
- ScreenShakeIntensitySlider.OnValueChanged += OnScreenShakeIntensitySliderChanged;
- // ToggleWalk.OnToggled += OnCheckBoxToggled;
- StaticStorageUI.OnToggled += OnCheckBoxToggled;
-
- HudThemeOption.SelectId(_hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0));
- DiscordRich.Pressed = _cfg.GetCVar(CVars.DiscordEnabled);
- ShowOocPatronColor.Pressed = _cfg.GetCVar(CCVars.ShowOocPatronColor);
- ShowLoocAboveHeadCheckBox.Pressed = _cfg.GetCVar(CCVars.LoocAboveHeadShow);
- ShowHeldItemCheckBox.Pressed = _cfg.GetCVar(CCVars.HudHeldItemShow);
- ShowCombatModeIndicatorsCheckBox.Pressed = _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
- OpaqueStorageWindowCheckBox.Pressed = _cfg.GetCVar(CCVars.OpaqueStorageWindow);
- FancySpeechBubblesCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
- FancyNameBackgroundsCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatFancyNameBackground);
- EnableColorNameCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableColorName);
- ColorblindFriendlyCheckBox.Pressed = _cfg.GetCVar(CCVars.AccessibilityColorblindFriendly);
- ReducedMotionCheckBox.Pressed = _cfg.GetCVar(CCVars.ReducedMotion);
- ChatWindowOpacitySlider.Value = _cfg.GetCVar(CCVars.ChatWindowOpacity);
- ScreenShakeIntensitySlider.Value = _cfg.GetCVar(CCVars.ScreenShakeIntensity) * 100f;
- // ToggleWalk.Pressed = _cfg.GetCVar(CCVars.ToggleWalk);
- StaticStorageUI.Pressed = _cfg.GetCVar(CCVars.StaticStorageUI);
-
-
- ApplyButton.OnPressed += OnApplyButtonPressed;
- UpdateApplyButton();
- }
+namespace Content.Client.Options.UI.Tabs;
- private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
- {
- UpdateApplyButton();
- }
+[GenerateTypedNameReferences]
+public sealed partial class MiscTab : Control
+{
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- private void OnHudThemeChanged(OptionButton.ItemSelectedEventArgs args)
- {
- HudThemeOption.SelectId(args.Id);
- UpdateApplyButton();
- }
+ public MiscTab()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
- private void OnChatWindowOpacitySliderChanged(Range range)
+ var themes = _prototypeManager.EnumeratePrototypes().ToList();
+ themes.Sort();
+ var themeEntries = new List.ValueOption>();
+ foreach (var gear in themes)
{
- ChatWindowOpacityLabel.Text = Loc.GetString("ui-options-chat-window-opacity-percent",
- ("opacity", range.Value));
- UpdateApplyButton();
+ themeEntries.Add(new OptionDropDownCVar.ValueOption(gear.ID, Loc.GetString(gear.Name)));
}
- private void OnScreenShakeIntensitySliderChanged(Range obj)
+ var layoutEntries = new List.ValueOption>();
+ foreach (var layout in Enum.GetValues(typeof(ScreenType)))
{
- ScreenShakeIntensityLabel.Text = Loc.GetString("ui-options-screen-shake-percent", ("intensity", ScreenShakeIntensitySlider.Value / 100f));
- UpdateApplyButton();
+ layoutEntries.Add(new OptionDropDownCVar.ValueOption(layout.ToString()!, layout.ToString()!));
}
- private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
- {
- foreach (var theme in _prototypeManager.EnumeratePrototypes())
- {
- if (_hudThemeIdToIndex[theme.ID] != HudThemeOption.SelectedId)
- continue;
- _cfg.SetCVar(CVars.InterfaceTheme, theme.ID);
- break;
- }
-
- _cfg.SetCVar(CVars.DiscordEnabled, DiscordRich.Pressed);
- _cfg.SetCVar(CCVars.HudHeldItemShow, ShowHeldItemCheckBox.Pressed);
- _cfg.SetCVar(CCVars.CombatModeIndicatorsPointShow, ShowCombatModeIndicatorsCheckBox.Pressed);
- _cfg.SetCVar(CCVars.OpaqueStorageWindow, OpaqueStorageWindowCheckBox.Pressed);
- _cfg.SetCVar(CCVars.ShowOocPatronColor, ShowOocPatronColor.Pressed);
- _cfg.SetCVar(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox.Pressed);
- _cfg.SetCVar(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox.Pressed);
- _cfg.SetCVar(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox.Pressed);
- _cfg.SetCVar(CCVars.ChatEnableColorName, EnableColorNameCheckBox.Pressed);
- _cfg.SetCVar(CCVars.AccessibilityColorblindFriendly, ColorblindFriendlyCheckBox.Pressed);
- _cfg.SetCVar(CCVars.ReducedMotion, ReducedMotionCheckBox.Pressed);
- _cfg.SetCVar(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider.Value);
- _cfg.SetCVar(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider.Value / 100f);
- // _cfg.SetCVar(CCVars.ToggleWalk, ToggleWalk.Pressed);
- _cfg.SetCVar(CCVars.StaticStorageUI, StaticStorageUI.Pressed);
-
- if (HudLayoutOption.SelectedMetadata is string opt)
- {
- _cfg.SetCVar(CCVars.UILayout, opt);
- }
+ // Channel can be null in replays so.
+ // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
+ ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { };
- _cfg.SaveToFile();
- UpdateApplyButton();
- }
+ Control.AddOptionDropDown(CVars.InterfaceTheme, DropDownHudTheme, themeEntries);
+ Control.AddOptionDropDown(CCVars.UILayout, DropDownHudLayout, layoutEntries);
- private void UpdateApplyButton()
- {
- var isHudThemeSame = HudThemeOption.SelectedId == _hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0);
- var isLayoutSame = HudLayoutOption.SelectedMetadata is string opt && opt == _cfg.GetCVar(CCVars.UILayout);
- var isDiscordSame = DiscordRich.Pressed == _cfg.GetCVar(CVars.DiscordEnabled);
- var isShowHeldItemSame = ShowHeldItemCheckBox.Pressed == _cfg.GetCVar(CCVars.HudHeldItemShow);
- var isCombatModeIndicatorsSame = ShowCombatModeIndicatorsCheckBox.Pressed == _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
- var isOpaqueStorageWindow = OpaqueStorageWindowCheckBox.Pressed == _cfg.GetCVar(CCVars.OpaqueStorageWindow);
- var isOocPatronColorShowSame = ShowOocPatronColor.Pressed == _cfg.GetCVar(CCVars.ShowOocPatronColor);
- var isLoocShowSame = ShowLoocAboveHeadCheckBox.Pressed == _cfg.GetCVar(CCVars.LoocAboveHeadShow);
- var isFancyChatSame = FancySpeechBubblesCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
- var isFancyBackgroundSame = FancyNameBackgroundsCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatFancyNameBackground);
- var isEnableColorNameSame = EnableColorNameCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableColorName);
- var isColorblindFriendly = ColorblindFriendlyCheckBox.Pressed == _cfg.GetCVar(CCVars.AccessibilityColorblindFriendly);
- var isReducedMotionSame = ReducedMotionCheckBox.Pressed == _cfg.GetCVar(CCVars.ReducedMotion);
- var isChatWindowOpacitySame = Math.Abs(ChatWindowOpacitySlider.Value - _cfg.GetCVar(CCVars.ChatWindowOpacity)) < 0.01f;
- var isScreenShakeIntensitySame = Math.Abs(ScreenShakeIntensitySlider.Value / 100f - _cfg.GetCVar(CCVars.ScreenShakeIntensity)) < 0.01f;
- // var isToggleWalkSame = ToggleWalk.Pressed == _cfg.GetCVar(CCVars.ToggleWalk);
- var isStaticStorageUISame = StaticStorageUI.Pressed == _cfg.GetCVar(CCVars.StaticStorageUI);
-
- ApplyButton.Disabled = isHudThemeSame &&
- isLayoutSame &&
- isDiscordSame &&
- isShowHeldItemSame &&
- isCombatModeIndicatorsSame &&
- isOpaqueStorageWindow &&
- isOocPatronColorShowSame &&
- isLoocShowSame &&
- isFancyChatSame &&
- isFancyBackgroundSame &&
- isEnableColorNameSame &&
- isColorblindFriendly &&
- isReducedMotionSame &&
- isChatWindowOpacitySame &&
- isScreenShakeIntensitySame &&
- // isToggleWalkSame &&
- isStaticStorageUISame;
- }
+ Control.AddOptionCheckBox(CVars.DiscordEnabled, DiscordRich);
+ Control.AddOptionCheckBox(CCVars.ShowOocPatronColor, ShowOocPatronColor);
+ Control.AddOptionCheckBox(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox);
+ Control.AddOptionCheckBox(CCVars.HudHeldItemShow, ShowHeldItemCheckBox);
+ Control.AddOptionCheckBox(CCVars.CombatModeIndicatorsPointShow, ShowCombatModeIndicatorsCheckBox);
+ Control.AddOptionCheckBox(CCVars.OpaqueStorageWindow, OpaqueStorageWindowCheckBox);
+ Control.AddOptionCheckBox(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox);
+ Control.AddOptionCheckBox(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox);
+ Control.AddOptionCheckBox(CCVars.StaticStorageUI, StaticStorageUI);
+ Control.Initialize();
}
-
}
diff --git a/Content.Client/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Client/Radio/EntitySystems/RadioDeviceSystem.cs
new file mode 100644
index 00000000000..29d6c635ebc
--- /dev/null
+++ b/Content.Client/Radio/EntitySystems/RadioDeviceSystem.cs
@@ -0,0 +1,23 @@
+using Content.Client.Radio.Ui;
+using Content.Shared.Radio;
+using Content.Shared.Radio.Components;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Radio.EntitySystems;
+
+public sealed class RadioDeviceSystem : EntitySystem
+{
+ [Dependency] private readonly UserInterfaceSystem _ui = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnAfterHandleState);
+ }
+
+ private void OnAfterHandleState(Entity ent, ref AfterAutoHandleStateEvent args)
+ {
+ if (_ui.TryGetOpenUi(ent.Owner, IntercomUiKey.Key, out var bui))
+ bui.Update(ent);
+ }
+}
diff --git a/Content.Client/Radio/Ui/IntercomBoundUserInterface.cs b/Content.Client/Radio/Ui/IntercomBoundUserInterface.cs
index abbb1d58ec4..7b3e39aa084 100644
--- a/Content.Client/Radio/Ui/IntercomBoundUserInterface.cs
+++ b/Content.Client/Radio/Ui/IntercomBoundUserInterface.cs
@@ -1,6 +1,6 @@
using Content.Shared.Radio;
+using Content.Shared.Radio.Components;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
namespace Content.Client.Radio.Ui;
@@ -19,7 +19,9 @@ protected override void Open()
{
base.Open();
- _menu = new();
+ var comp = EntMan.GetComponent(Owner);
+
+ _menu = new((Owner, comp));
_menu.OnMicPressed += enabled =>
{
@@ -46,13 +48,8 @@ protected override void Dispose(bool disposing)
_menu?.Close();
}
- protected override void UpdateState(BoundUserInterfaceState state)
+ public void Update(Entity ent)
{
- base.UpdateState(state);
-
- if (state is not IntercomBoundUIState msg)
- return;
-
- _menu?.Update(msg);
+ _menu?.Update(ent);
}
}
diff --git a/Content.Client/Radio/Ui/IntercomMenu.xaml.cs b/Content.Client/Radio/Ui/IntercomMenu.xaml.cs
index 8b4b38753c1..2e08913051c 100644
--- a/Content.Client/Radio/Ui/IntercomMenu.xaml.cs
+++ b/Content.Client/Radio/Ui/IntercomMenu.xaml.cs
@@ -1,8 +1,9 @@
using Content.Client.UserInterface.Controls;
-using Content.Shared.Radio;
+using Content.Shared.Radio.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
namespace Content.Client.Radio.Ui;
@@ -17,38 +18,54 @@ public sealed partial class IntercomMenu : FancyWindow
private readonly List _channels = new();
- public IntercomMenu()
+ public IntercomMenu(Entity entity)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
MicButton.OnPressed += args => OnMicPressed?.Invoke(args.Button.Pressed);
SpeakerButton.OnPressed += args => OnSpeakerPressed?.Invoke(args.Button.Pressed);
+
+ Update(entity);
}
- public void Update(IntercomBoundUIState state)
+ public void Update(Entity entity)
{
- MicButton.Pressed = state.MicEnabled;
- SpeakerButton.Pressed = state.SpeakerEnabled;
+ MicButton.Pressed = entity.Comp.MicrophoneEnabled;
+ SpeakerButton.Pressed = entity.Comp.SpeakerEnabled;
+
+ MicButton.Disabled = entity.Comp.SupportedChannels.Count == 0;
+ SpeakerButton.Disabled = entity.Comp.SupportedChannels.Count == 0;
+ ChannelOptions.Disabled = entity.Comp.SupportedChannels.Count == 0;
ChannelOptions.Clear();
_channels.Clear();
- for (var i = 0; i < state.AvailableChannels.Count; i++)
+ for (var i = 0; i < entity.Comp.SupportedChannels.Count; i++)
{
- var channel = state.AvailableChannels[i];
- if (!_prototype.TryIndex(channel, out var prototype))
+ var channel = entity.Comp.SupportedChannels[i];
+ if (!_prototype.TryIndex(channel, out var prototype))
continue;
_channels.Add(channel);
ChannelOptions.AddItem(Loc.GetString(prototype.Name), i);
- if (channel == state.SelectedChannel)
+ if (channel == entity.Comp.CurrentChannel)
ChannelOptions.Select(i);
}
+
+ if (entity.Comp.SupportedChannels.Count == 0)
+ {
+ ChannelOptions.AddItem(Loc.GetString("intercom-options-none"), 0);
+ ChannelOptions.Select(0);
+ }
+
ChannelOptions.OnItemSelected += args =>
{
+ if (!_channels.TryGetValue(args.Id, out var proto))
+ return;
+
ChannelOptions.SelectId(args.Id);
- OnChannelSelected?.Invoke(_channels[args.Id]);
+ OnChannelSelected?.Invoke(proto);
};
}
}
diff --git a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs
index 2fa862f3df7..99d85350b5e 100644
--- a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs
+++ b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs
@@ -17,7 +17,7 @@ private void InitializeBlockers()
SubscribeLocalEvent(OnAttempt);
SubscribeLocalEvent(OnAttempt);
SubscribeLocalEvent(OnAttempt);
- SubscribeLocalEvent(OnAttempt);
+ SubscribeLocalEvent(OnInteractAttempt);
SubscribeLocalEvent(OnAttempt);
SubscribeLocalEvent(OnAttempt);
SubscribeLocalEvent(OnAttempt);
@@ -27,6 +27,11 @@ private void InitializeBlockers()
SubscribeLocalEvent(OnPullAttempt);
}
+ private void OnInteractAttempt(Entity ent, ref InteractionAttemptEvent args)
+ {
+ args.Cancelled = true;
+ }
+
private void OnAttempt(EntityUid uid, ReplaySpectatorComponent component, CancellableEntityEventArgs args)
{
args.Cancel();
diff --git a/Content.Client/Rotation/RotationVisualizerSystem.cs b/Content.Client/Rotation/RotationVisualizerSystem.cs
index 6105c10c803..6d3be4d1c05 100644
--- a/Content.Client/Rotation/RotationVisualizerSystem.cs
+++ b/Content.Client/Rotation/RotationVisualizerSystem.cs
@@ -23,8 +23,8 @@ private void OnAppearanceChange(EntityUid uid, RotationVisualsComponent componen
if (args.Sprite == null)
return;
- // If not defined, defaults to standing.
- _appearance.TryGetData(uid, RotationVisuals.RotationState, out var state, args.Component);
+ if (!_appearance.TryGetData(uid, RotationVisuals.RotationState, out var state, args.Component))
+ return;
switch (state)
{
diff --git a/Content.Client/SensorMonitoring/SensorMonitoringWindow.xaml.cs b/Content.Client/SensorMonitoring/SensorMonitoringWindow.xaml.cs
index 9fc132c7479..307307c687a 100644
--- a/Content.Client/SensorMonitoring/SensorMonitoringWindow.xaml.cs
+++ b/Content.Client/SensorMonitoring/SensorMonitoringWindow.xaml.cs
@@ -129,14 +129,7 @@ private void Update()
foreach (var stream in sensor.Streams.Values)
{
- var maxValue = stream.Unit switch
- {
- SensorUnit.PressureKpa => 5000, // 5 MPa
- SensorUnit.Ratio => 1,
- SensorUnit.PowerW => 1_000_000, // 1 MW
- SensorUnit.EnergyJ => 2_000_000, // 2 MJ
- _ => 1000
- };
+ var maxValue = stream.Samples.Max(x => x.Value);
// TODO: Better way to do this?
var lastSample = stream.Samples.Last();
@@ -151,7 +144,7 @@ private void Update()
}
});
- Asdf.AddChild(new GraphView(stream.Samples, startTime, curTime, maxValue) { MinHeight = 150 });
+ Asdf.AddChild(new GraphView(stream.Samples, startTime, curTime, maxValue * 1.1f) { MinHeight = 150 });
Asdf.AddChild(new PanelContainer { StyleClasses = { StyleBase.ClassLowDivider } });
}
}
diff --git a/Content.Client/Shuttles/FtlArrivalOverlay.cs b/Content.Client/Shuttles/FtlArrivalOverlay.cs
new file mode 100644
index 00000000000..f24a1e96488
--- /dev/null
+++ b/Content.Client/Shuttles/FtlArrivalOverlay.cs
@@ -0,0 +1,82 @@
+using System.Numerics;
+using Content.Shared.Shuttles.Components;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Shared.Enums;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Shuttles;
+
+///
+/// Plays a visualization whenever a shuttle is arriving from FTL.
+///
+public sealed class FtlArrivalOverlay : Overlay
+{
+ public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities;
+
+ private EntityLookupSystem _lookups;
+ private SharedMapSystem _maps;
+ private SharedTransformSystem _transforms;
+ private SpriteSystem _sprites;
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IPrototypeManager _protos = default!;
+
+ private readonly HashSet> _visualizers = new();
+
+ private ShaderInstance _shader;
+
+ public FtlArrivalOverlay()
+ {
+ IoCManager.InjectDependencies(this);
+ _lookups = _entManager.System();
+ _transforms = _entManager.System();
+ _maps = _entManager.System();
+ _sprites = _entManager.System();
+
+ _shader = _protos.Index("unshaded").Instance();
+ }
+
+ protected override bool BeforeDraw(in OverlayDrawArgs args)
+ {
+ _visualizers.Clear();
+ _lookups.GetEntitiesOnMap(args.MapId, _visualizers);
+
+ return _visualizers.Count > 0;
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ args.WorldHandle.UseShader(_shader);
+
+ foreach (var (uid, comp) in _visualizers)
+ {
+ var grid = comp.Grid;
+
+ if (!_entManager.TryGetComponent(grid, out MapGridComponent? mapGrid))
+ continue;
+
+ var texture = _sprites.GetFrame(comp.Sprite, TimeSpan.FromSeconds(comp.Elapsed), loop: false);
+ comp.Elapsed += (float) _timing.FrameTime.TotalSeconds;
+
+ // Need to manually transform the viewport in terms of the visualizer entity as the grid isn't in position.
+ var (_, _, worldMatrix, invMatrix) = _transforms.GetWorldPositionRotationMatrixWithInv(uid);
+ args.WorldHandle.SetTransform(worldMatrix);
+ var localAABB = invMatrix.TransformBox(args.WorldBounds);
+
+ var tilesEnumerator = _maps.GetLocalTilesEnumerator(grid, mapGrid, localAABB);
+
+ while (tilesEnumerator.MoveNext(out var tile))
+ {
+ var bounds = _lookups.GetLocalBounds(tile, mapGrid.TileSize);
+
+ args.WorldHandle.DrawTextureRect(texture, bounds);
+ }
+ }
+
+ args.WorldHandle.UseShader(null);
+ args.WorldHandle.SetTransform(Matrix3x2.Identity);
+ }
+}
diff --git a/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs b/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs
index d5154a87bef..73c11de2795 100644
--- a/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs
+++ b/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs
@@ -38,9 +38,8 @@ public bool EnableShuttlePosition
private bool _enableShuttlePosition;
private EmergencyShuttleOverlay? _overlay;
- public override void Initialize()
+ private void InitializeEmergency()
{
- base.Initialize();
SubscribeNetworkEvent(OnShuttlePosMessage);
}
diff --git a/Content.Client/Shuttles/Systems/ShuttleSystem.cs b/Content.Client/Shuttles/Systems/ShuttleSystem.cs
new file mode 100644
index 00000000000..a2c048ff90e
--- /dev/null
+++ b/Content.Client/Shuttles/Systems/ShuttleSystem.cs
@@ -0,0 +1,21 @@
+using Robust.Client.Graphics;
+
+namespace Content.Client.Shuttles.Systems;
+
+public sealed partial class ShuttleSystem
+{
+ [Dependency] private readonly IOverlayManager _overlays = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ InitializeEmergency();
+ _overlays.AddOverlay(new FtlArrivalOverlay());
+ }
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+ _overlays.RemoveOverlay();
+ }
+}
diff --git a/Content.Client/Shuttles/UI/MapScreen.xaml.cs b/Content.Client/Shuttles/UI/MapScreen.xaml.cs
index da217d954e1..3bc27d04b3e 100644
--- a/Content.Client/Shuttles/UI/MapScreen.xaml.cs
+++ b/Content.Client/Shuttles/UI/MapScreen.xaml.cs
@@ -272,7 +272,7 @@ private void RebuildMapObjects()
ourMap = shuttleXform.MapID;
}
- while (mapComps.MoveNext(out var mapComp, out var mapXform, out var mapMetadata))
+ while (mapComps.MoveNext(out var mapUid, out var mapComp, out var mapXform, out var mapMetadata))
{
if (_console != null && !_shuttles.CanFTLTo(_shuttleEntity.Value, mapComp.MapId, _console.Value))
{
@@ -322,7 +322,7 @@ private void RebuildMapObjects()
};
_mapHeadings.Add(mapComp.MapId, gridContents);
- foreach (var grid in _mapManager.GetAllMapGrids(mapComp.MapId))
+ foreach (var grid in _mapManager.GetAllGrids(mapComp.MapId))
{
_entManager.TryGetComponent(grid.Owner, out IFFComponent? iffComp);
@@ -338,8 +338,11 @@ private void RebuildMapObjects()
{
AddMapObject(mapComp.MapId, gridObj);
}
- else if (!_shuttles.IsBeaconMap(_mapManager.GetMapEntityId(mapComp.MapId)) && (iffComp == null ||
- (iffComp.Flags & IFFFlags.Hide | iffComp.Flags & IFFFlags.HideLabel) == 0x0)) // Frontier
+
+ // If we can show it then add it to pending.
+ else if (!_shuttles.IsBeaconMap(mapUid) && (iffComp == null ||
+ (iffComp.Flags & IFFFlags.Hide | iffComp.Flags & IFFFlags.HideLabel) == 0x0) && // Frontier: add HideLabel check
+ !gridObj.HideButton)
{
_pendingMapObjects.Add((mapComp.MapId, gridObj));
}
@@ -347,11 +350,17 @@ private void RebuildMapObjects()
foreach (var (beacon, _) in _shuttles.GetExclusions(mapComp.MapId, _exclusions))
{
+ if (beacon.HideButton)
+ continue;
+
_pendingMapObjects.Add((mapComp.MapId, beacon));
}
foreach (var (beacon, _) in _shuttles.GetBeacons(mapComp.MapId, _beacons))
{
+ if (beacon.HideButton)
+ continue;
+
_pendingMapObjects.Add((mapComp.MapId, beacon));
}
@@ -436,9 +445,6 @@ private void AddMapObject(MapId mapId, IMapObject mapObj)
var existing = _mapObjects.GetOrNew(mapId);
existing.Add(mapObj);
- if (mapObj.HideButton)
- return;
-
var gridContents = _mapHeadings[mapId];
var gridButton = new Button()
diff --git a/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs
index 6e4950c0fa6..140c2b4c540 100644
--- a/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs
+++ b/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs
@@ -108,7 +108,7 @@ protected override void Draw(DrawingHandleScreen handle)
DrawNorthLine(handle, _angle.Value); // Frontier Corvax: north line drawing
var gridNent = EntManager.GetNetEntity(GridEntity);
var mapPos = _xformSystem.ToMapCoordinates(_coordinates.Value);
- var ourGridMatrix = _xformSystem.GetWorldMatrix(gridXform.Owner);
+ var ourGridMatrix = _xformSystem.GetWorldMatrix(GridEntity.Value);
var dockMatrix = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, Angle.Zero);
var worldFromDock = Matrix3x2.Multiply(dockMatrix, ourGridMatrix);
diff --git a/Content.Client/Storage/Systems/StorageSystem.cs b/Content.Client/Storage/Systems/StorageSystem.cs
index b80a855f986..a74a118cd0f 100644
--- a/Content.Client/Storage/Systems/StorageSystem.cs
+++ b/Content.Client/Storage/Systems/StorageSystem.cs
@@ -1,4 +1,4 @@
-using System.Linq;
+using System.Linq;
using System.Numerics;
using Content.Client.Animations;
using Content.Shared.Hands;
@@ -69,7 +69,7 @@ public void OpenStorageWindow(Entity entity)
public void CloseStorageWindow(Entity entity)
{
- if (!Resolve(entity, ref entity.Comp))
+ if (!Resolve(entity, ref entity.Comp, false))
return;
if (!_openStorages.Contains((entity, entity.Comp)))
diff --git a/Content.Client/Thief/ThiefBackpackMenu.xaml b/Content.Client/Thief/ThiefBackpackMenu.xaml
index c1739eb321d..e46f18d4ed5 100644
--- a/Content.Client/Thief/ThiefBackpackMenu.xaml
+++ b/Content.Client/Thief/ThiefBackpackMenu.xaml
@@ -5,7 +5,7 @@
MinSize="700 700">
-
+
diff --git a/Content.Client/Thief/ThiefBackpackMenu.xaml.cs b/Content.Client/Thief/ThiefBackpackMenu.xaml.cs
index b2314cf3fe2..543772c704c 100644
--- a/Content.Client/Thief/ThiefBackpackMenu.xaml.cs
+++ b/Content.Client/Thief/ThiefBackpackMenu.xaml.cs
@@ -50,6 +50,7 @@ public void UpdateState(ThiefBackpackBoundUserInterfaceState state)
selectedNumber++;
}
+ Description.Text = Loc.GetString("thief-backpack-window-description", ("maxCount", state.MaxSelectedSets));
SelectedSets.Text = Loc.GetString("thief-backpack-window-selected", ("selectedCount", selectedNumber), ("maxCount", state.MaxSelectedSets));
ApproveButton.Disabled = selectedNumber == state.MaxSelectedSets ? false : true;
}
diff --git a/Content.Client/Throwing/ThrownItemVisualizerSystem.cs b/Content.Client/Throwing/ThrownItemVisualizerSystem.cs
index b25b4fbb7de..28a07ae94a7 100644
--- a/Content.Client/Throwing/ThrownItemVisualizerSystem.cs
+++ b/Content.Client/Throwing/ThrownItemVisualizerSystem.cs
@@ -59,7 +59,6 @@ private void OnShutdown(EntityUid uid, ThrownItemComponent component, ComponentS
if (length <= TimeSpan.Zero)
return null;
- length += TimeSpan.FromSeconds(ThrowingSystem.FlyTime);
var scale = ent.Comp2.Scale;
var lenFloat = (float) length.TotalSeconds;
diff --git a/Content.Client/UserInterface/Controls/RadialContainer.cs b/Content.Client/UserInterface/Controls/RadialContainer.cs
index be263d12772..be9b8817a06 100644
--- a/Content.Client/UserInterface/Controls/RadialContainer.cs
+++ b/Content.Client/UserInterface/Controls/RadialContainer.cs
@@ -67,11 +67,18 @@ public RadialContainer()
{
}
-
+
protected override void Draw(DrawingHandleScreen handle)
{
+
+ const float baseRadius = 100f;
+ const float radiusIncrement = 5f;
+
var children = ReserveSpaceForHiddenChildren ? Children : Children.Where(x => x.Visible);
var childCount = children.Count();
+
+ // Add padding from the center at higher child counts so they don't overlap.
+ Radius = baseRadius + (childCount * radiusIncrement);
// Determine the size of the arc, accounting for clockwise and anti-clockwise arrangements
var arc = AngularRange.Y - AngularRange.X;
diff --git a/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml b/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml
index 52346adba27..443b79242e7 100644
--- a/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml
+++ b/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml
@@ -3,7 +3,7 @@
xmlns:windows="clr-namespace:Content.Client.UserInterface.Systems.Actions.Windows"
Name="ActionsList"
HorizontalExpand="True"
- Title="Actions"
+ Title="{Loc ui-actionmenu-title}"
VerticalExpand="True"
Resizable="True"
MinHeight="300"
diff --git a/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml.cs b/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml.cs
index fbe1e71535d..f972a96eb74 100644
--- a/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml.cs
+++ b/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml.cs
@@ -26,7 +26,7 @@ public ActionsWindow()
foreach (var filter in Enum.GetValues())
{
- FilterButton.AddItem(filter.ToString(), filter);
+ FilterButton.AddItem(Loc.GetString($"ui-actionmenu-{filter.ToString().ToLower()}"), filter);
}
}
diff --git a/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs b/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs
index e12ce0ab011..d36a91c3733 100644
--- a/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs
+++ b/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs
@@ -230,7 +230,7 @@ private void ObjectsTabEntryKeyBindDown(GUIBoundKeyEventArgs args, ListData? dat
if (function == EngineKeyFunctions.UIClick)
_conHost.ExecuteCommand($"vv {uid}");
- else if (function == EngineKeyFunctions.UseSecondary)
+ else if (function == EngineKeyFunctions.UIRightClick)
_verb.OpenVerbMenu(uid, true);
else
return;
diff --git a/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs b/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs
index 6327757dec2..19432cd7992 100644
--- a/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs
+++ b/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs
@@ -52,20 +52,12 @@ public AlertControl(AlertPrototype alert, short? severity)
TooltipSupplier = SupplyTooltip;
Alert = alert;
_severity = severity;
-
- _spriteViewEntity = _entityManager.Spawn(Alert.AlertViewEntity);
- if (_entityManager.TryGetComponent(_spriteViewEntity, out var sprite))
- {
- var icon = Alert.GetIcon(_severity);
- if (sprite.LayerMapTryGet(AlertVisualLayers.Base, out var layer))
- sprite.LayerSetSprite(layer, icon);
- }
-
_icon = new SpriteView
{
Scale = new Vector2(2, 2)
};
- _icon.SetEntity(_spriteViewEntity);
+
+ SetupIcon();
Children.Add(_icon);
_cooldownGraphic = new CooldownGraphic
@@ -113,6 +105,36 @@ protected override void FrameUpdate(FrameEventArgs args)
_cooldownGraphic.FromTime(Cooldown.Value.Start, Cooldown.Value.End);
}
+ private void SetupIcon()
+ {
+ if (!_entityManager.Deleted(_spriteViewEntity))
+ _entityManager.QueueDeleteEntity(_spriteViewEntity);
+
+ _spriteViewEntity = _entityManager.Spawn(Alert.AlertViewEntity);
+ if (_entityManager.TryGetComponent(_spriteViewEntity, out var sprite))
+ {
+ var icon = Alert.GetIcon(_severity);
+ if (sprite.LayerMapTryGet(AlertVisualLayers.Base, out var layer))
+ sprite.LayerSetSprite(layer, icon);
+ }
+
+ _icon.SetEntity(_spriteViewEntity);
+ }
+
+ protected override void EnteredTree()
+ {
+ base.EnteredTree();
+ SetupIcon();
+ }
+
+ protected override void ExitedTree()
+ {
+ base.ExitedTree();
+
+ if (!_entityManager.Deleted(_spriteViewEntity))
+ _entityManager.QueueDeleteEntity(_spriteViewEntity);
+ }
+
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
diff --git a/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs b/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs
index fb747799170..5d7a775104c 100644
--- a/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs
+++ b/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs
@@ -132,6 +132,9 @@ private void UpdateInventoryHotbar(InventorySlotsComponent? clientInv)
if (clientInv == null)
{
_inventoryHotbar?.ClearButtons();
+ if (_inventoryButton != null)
+ _inventoryButton.Visible = false;
+
return;
}
@@ -409,6 +412,8 @@ private void UnloadSlots()
{
slotGroup.ClearButtons();
}
+
+ UpdateInventoryHotbar(null);
}
private void SpriteUpdated(SlotSpriteUpdate update)
diff --git a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs
index d612248ab7e..eec26dbecd7 100644
--- a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs
+++ b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs
@@ -7,12 +7,13 @@
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
-using Content.Client.Stylesheets;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Reagent;
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
+using Content.Shared.IdentityManagement;
+using Robust.Shared.Timing;
namespace Content.Client.VendingMachines.UI
{
@@ -20,6 +21,10 @@ namespace Content.Client.VendingMachines.UI
public sealed partial class VendingMachineMenu : FancyWindow
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ private readonly Dictionary _dummies = [];
public event Action? OnItemSelected;
public event Action? OnSearchChanged;
@@ -41,6 +46,22 @@ public VendingMachineMenu()
};
}
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ // Don't clean up dummies during disposal or we'll just have to spawn them again
+ if (!disposing)
+ return;
+
+ // Delete any dummy items we spawned
+ foreach (var entity in _dummies.Values)
+ {
+ _entityManager.QueueDeleteEntity(entity);
+ }
+ _dummies.Clear();
+ }
+
///
/// Populates the list of available items on the vending machine interface
/// and sets icons based on their prototypes
@@ -77,11 +98,16 @@ public void Populate(List inventory, float priceMo
vendingItem.Text = string.Empty;
vendingItem.Icon = null;
- var itemName = entry.ID;
+ if (!_dummies.TryGetValue(entry.ID, out var dummy))
+ {
+ dummy = _entityManager.Spawn(entry.ID);
+ _dummies.Add(entry.ID, dummy);
+ }
+
+ var itemName = Identity.Name(dummy, _entityManager);
Texture? icon = null;
if (_prototypeManager.TryIndex(entry.ID, out var prototype))
{
- itemName = prototype.Name;
icon = spriteSystem.GetPrototypeIcon(prototype).Default;
}
diff --git a/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs b/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs
index 32b15252261..c232e823132 100644
--- a/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs
+++ b/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs
@@ -18,7 +18,7 @@ public sealed class ActionsAddedTest
[Test]
public async Task TestCombatActionsAdded()
{
- await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, DummyTicker = false});
+ await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, DummyTicker = false });
var server = pair.Server;
var client = pair.Client;
var sEntMan = server.ResolveDependency();
diff --git a/Content.IntegrationTests/Tests/Atmos/GridJoinTest.cs b/Content.IntegrationTests/Tests/Atmos/GridJoinTest.cs
new file mode 100644
index 00000000000..3a1ec7fd40e
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Atmos/GridJoinTest.cs
@@ -0,0 +1,53 @@
+using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Atmos.Piping.Components;
+using Content.Server.Atmos.Piping.EntitySystems;
+using Robust.Shared.GameObjects;
+
+namespace Content.IntegrationTests.Tests.Atmos;
+
+[TestFixture]
+public sealed class GridJoinTest
+{
+ private const string CanisterProtoId = "AirCanister";
+
+ [Test]
+ public async Task TestGridJoinAtmosphere()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var entMan = server.EntMan;
+ var protoMan = server.ProtoMan;
+ var atmosSystem = entMan.System();
+ var atmosDeviceSystem = entMan.System();
+ var transformSystem = entMan.System();
+
+ var testMap = await pair.CreateTestMap();
+
+ await server.WaitPost(() =>
+ {
+ // Spawn an atmos device on the grid
+ var canister = entMan.Spawn(CanisterProtoId);
+ transformSystem.SetCoordinates(canister, testMap.GridCoords);
+ var deviceComp = entMan.GetComponent(canister);
+ var canisterEnt = (canister, deviceComp);
+
+ // Make sure the canister is tracked as an off-grid device
+ Assert.That(atmosDeviceSystem.IsJoinedOffGrid(canisterEnt));
+
+ // Add an atmosphere to the grid
+ entMan.AddComponent(testMap.Grid);
+
+ // Force AtmosDeviceSystem to update off-grid devices
+ // This means the canister is now considered on-grid,
+ // but it's still tracked as off-grid!
+ Assert.DoesNotThrow(() => atmosDeviceSystem.Update(atmosSystem.AtmosTime));
+
+ // Make sure that the canister is now properly tracked as on-grid
+ Assert.That(atmosDeviceSystem.IsJoinedOffGrid(canisterEnt), Is.False);
+ });
+
+ await pair.CleanReturnAsync();
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Body/GibTest.cs b/Content.IntegrationTests/Tests/Body/GibTest.cs
index c0032a85244..4627c79f64d 100644
--- a/Content.IntegrationTests/Tests/Body/GibTest.cs
+++ b/Content.IntegrationTests/Tests/Body/GibTest.cs
@@ -5,7 +5,7 @@
namespace Content.IntegrationTests.Tests.Body;
[TestFixture]
-public sealed class GibTest
+public sealed class GibTest
{
[Test]
public async Task TestGib()
diff --git a/Content.IntegrationTests/Tests/Body/LegTest.cs b/Content.IntegrationTests/Tests/Body/LegTest.cs
index e86966f8f54..7b49bbe84a3 100644
--- a/Content.IntegrationTests/Tests/Body/LegTest.cs
+++ b/Content.IntegrationTests/Tests/Body/LegTest.cs
@@ -5,7 +5,6 @@
using Content.Shared.Rotation;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
-using Robust.Shared.Maths;
namespace Content.IntegrationTests.Tests.Body
{
@@ -40,13 +39,14 @@ public async Task RemoveLegsFallTest()
var appearanceSystem = entityManager.System();
var xformSystem = entityManager.System();
+ var map = await pair.CreateTestMap();
+
await server.WaitAssertion(() =>
{
- var mapId = mapManager.CreateMap();
BodyComponent body = null;
human = entityManager.SpawnEntity("HumanBodyAndAppearanceDummy",
- new MapCoordinates(Vector2.Zero, mapId));
+ new MapCoordinates(Vector2.Zero, map.MapId));
Assert.Multiple(() =>
{
@@ -61,7 +61,7 @@ await server.WaitAssertion(() =>
foreach (var leg in legs)
{
- xformSystem.DetachParentToNull(leg.Id, entityManager.GetComponent(leg.Id));
+ xformSystem.DetachEntity(leg.Id, entityManager.GetComponent(leg.Id));
}
});
diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs
index dce3741c98d..9b5ee431f1f 100644
--- a/Content.IntegrationTests/Tests/Body/LungTest.cs
+++ b/Content.IntegrationTests/Tests/Body/LungTest.cs
@@ -60,8 +60,8 @@ public async Task AirConsistencyTest()
var mapManager = server.ResolveDependency();
var entityManager = server.ResolveDependency();
var mapLoader = entityManager.System();
+ var mapSys = entityManager.System();
- MapId mapId;
EntityUid? grid = null;
BodyComponent body = default;
RespiratorComponent resp = default;
@@ -73,7 +73,7 @@ public async Task AirConsistencyTest()
await server.WaitPost(() =>
{
- mapId = mapManager.CreateMap();
+ mapSys.CreateMap(out var mapId);
Assert.That(mapLoader.TryLoad(mapId, testMapName, out var roots));
var query = entityManager.GetEntityQuery();
@@ -142,8 +142,8 @@ public async Task NoSuffocationTest()
var entityManager = server.ResolveDependency();
var cfg = server.ResolveDependency();
var mapLoader = entityManager.System();
+ var mapSys = entityManager.System();
- MapId mapId;
EntityUid? grid = null;
RespiratorComponent respirator = null;
EntityUid human = default;
@@ -152,7 +152,7 @@ public async Task NoSuffocationTest()
await server.WaitPost(() =>
{
- mapId = mapManager.CreateMap();
+ mapSys.CreateMap(out var mapId);
Assert.That(mapLoader.TryLoad(mapId, testMapName, out var ents), Is.True);
var query = entityManager.GetEntityQuery();
diff --git a/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs b/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs
index 670ce1a474d..01482ba8ee2 100644
--- a/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs
+++ b/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs
@@ -33,10 +33,11 @@ public async Task Test()
var mapLoader = entities.System();
var bodySystem = entities.System();
var containerSystem = entities.System();
+ var mapSys = entities.System();
await server.WaitAssertion(() =>
{
- var mapId = maps.CreateMap();
+ mapSys.CreateMap(out var mapId);
maps.CreateGrid(mapId);
var human = entities.SpawnEntity("HumanBodyDummy", new MapCoordinates(0, 0, mapId));
@@ -115,7 +116,7 @@ await server.WaitAssertion(() =>
mapLoader.SaveMap(mapId, mapPath);
maps.DeleteMap(mapId);
- mapId = maps.CreateMap();
+ mapSys.CreateMap(out mapId);
Assert.That(mapLoader.TryLoad(mapId, mapPath, out _), Is.True);
var query = EnumerateQueryEnumerator(
diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleDragTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleDragTest.cs
new file mode 100644
index 00000000000..82d5d3baa04
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Buckle/BuckleDragTest.cs
@@ -0,0 +1,56 @@
+using Content.IntegrationTests.Tests.Interaction;
+using Content.Shared.Buckle;
+using Content.Shared.Buckle.Components;
+using Content.Shared.Input;
+using Content.Shared.Movement.Pulling.Components;
+
+namespace Content.IntegrationTests.Tests.Buckle;
+
+public sealed class BuckleDragTest : InteractionTest
+{
+ // Check that dragging a buckled player unbuckles them.
+ [Test]
+ public async Task BucklePullTest()
+ {
+ var urist = await SpawnTarget("MobHuman");
+ var sUrist = ToServer(urist);
+ await SpawnTarget("Chair");
+
+ var buckle = Comp(urist);
+ var strap = Comp(Target);
+ var puller = Comp(Player);
+ var pullable = Comp(urist);
+
+#pragma warning disable RA0002
+ buckle.Delay = TimeSpan.Zero;
+#pragma warning restore RA0002
+
+ // Initially not buckled to the chair and not pulling anything
+ Assert.That(buckle.Buckled, Is.False);
+ Assert.That(buckle.BuckledTo, Is.Null);
+ Assert.That(strap.BuckledEntities, Is.Empty);
+ Assert.That(puller.Pulling, Is.Null);
+ Assert.That(pullable.Puller, Is.Null);
+ Assert.That(pullable.BeingPulled, Is.False);
+
+ // Strap the human to the chair
+ Assert.That(Server.System().TryBuckle(sUrist, SPlayer, STarget.Value));
+ await RunTicks(5);
+ Assert.That(buckle.Buckled, Is.True);
+ Assert.That(buckle.BuckledTo, Is.EqualTo(STarget));
+ Assert.That(strap.BuckledEntities, Is.EquivalentTo(new[] { sUrist }));
+ Assert.That(puller.Pulling, Is.Null);
+ Assert.That(pullable.Puller, Is.Null);
+ Assert.That(pullable.BeingPulled, Is.False);
+
+ // Start pulling, and thus unbuckle them
+ await PressKey(ContentKeyFunctions.TryPullObject, cursorEntity: urist);
+ await RunTicks(5);
+ Assert.That(buckle.Buckled, Is.False);
+ Assert.That(buckle.BuckledTo, Is.Null);
+ Assert.That(strap.BuckledEntities, Is.Empty);
+ Assert.That(puller.Pulling, Is.EqualTo(sUrist));
+ Assert.That(pullable.Puller, Is.EqualTo(SPlayer));
+ Assert.That(pullable.BeingPulled, Is.True);
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs
index 57ac63b1247..156f42aac33 100644
--- a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs
+++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs
@@ -91,7 +91,6 @@ await server.WaitAssertion(() =>
{
Assert.That(strap, Is.Not.Null);
Assert.That(strap.BuckledEntities, Is.Empty);
- Assert.That(strap.OccupiedSize, Is.Zero);
});
// Side effects of buckling
@@ -111,8 +110,6 @@ await server.WaitAssertion(() =>
// Side effects of buckling for the strap
Assert.That(strap.BuckledEntities, Does.Contain(human));
- Assert.That(strap.OccupiedSize, Is.EqualTo(buckle.Size));
- Assert.That(strap.OccupiedSize, Is.Positive);
});
#pragma warning disable NUnit2045 // Interdependent asserts.
@@ -122,7 +119,7 @@ await server.WaitAssertion(() =>
// Trying to unbuckle too quickly fails
Assert.That(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle), Is.False);
Assert.That(buckle.Buckled);
- Assert.That(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle), Is.False);
+ Assert.That(buckleSystem.TryUnbuckle(human, human), Is.False);
Assert.That(buckle.Buckled);
#pragma warning restore NUnit2045
});
@@ -149,7 +146,6 @@ await server.WaitAssertion(() =>
// Unbuckle, strap
Assert.That(strap.BuckledEntities, Is.Empty);
- Assert.That(strap.OccupiedSize, Is.Zero);
});
#pragma warning disable NUnit2045 // Interdependent asserts.
@@ -160,9 +156,9 @@ await server.WaitAssertion(() =>
// On cooldown
Assert.That(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle), Is.False);
Assert.That(buckle.Buckled);
- Assert.That(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle), Is.False);
+ Assert.That(buckleSystem.TryUnbuckle(human, human), Is.False);
Assert.That(buckle.Buckled);
- Assert.That(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle), Is.False);
+ Assert.That(buckleSystem.TryUnbuckle(human, human), Is.False);
Assert.That(buckle.Buckled);
#pragma warning restore NUnit2045
});
@@ -189,7 +185,6 @@ await server.WaitAssertion(() =>
#pragma warning disable NUnit2045 // Interdependent asserts.
Assert.That(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle), Is.False);
Assert.That(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle), Is.False);
- Assert.That(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle), Is.False);
#pragma warning restore NUnit2045
// Move near the chair
@@ -202,12 +197,10 @@ await server.WaitAssertion(() =>
Assert.That(buckle.Buckled);
Assert.That(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle), Is.False);
Assert.That(buckle.Buckled);
- Assert.That(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle), Is.False);
- Assert.That(buckle.Buckled);
#pragma warning restore NUnit2045
// Force unbuckle
- Assert.That(buckleSystem.TryUnbuckle(human, human, true, buckleComp: buckle));
+ buckleSystem.Unbuckle(human, human);
Assert.Multiple(() =>
{
Assert.That(buckle.Buckled, Is.False);
@@ -311,7 +304,7 @@ await server.WaitAssertion(() =>
// Break our guy's kneecaps
foreach (var leg in legs)
{
- xformSystem.DetachParentToNull(leg.Id, entityManager.GetComponent(leg.Id));
+ entityManager.DeleteEntity(leg.Id);
}
});
@@ -328,7 +321,8 @@ await server.WaitAssertion(() =>
Assert.That(hand.HeldEntity, Is.Null);
}
- buckleSystem.TryUnbuckle(human, human, true, buckleComp: buckle);
+ buckleSystem.Unbuckle(human, human);
+ Assert.That(buckle.Buckled, Is.False);
});
await pair.CleanReturnAsync();
diff --git a/Content.IntegrationTests/Tests/CargoTest.cs b/Content.IntegrationTests/Tests/CargoTest.cs
index e7f6f462404..b82a0af3856 100644
--- a/Content.IntegrationTests/Tests/CargoTest.cs
+++ b/Content.IntegrationTests/Tests/CargoTest.cs
@@ -20,11 +20,11 @@ namespace Content.IntegrationTests.Tests;
[TestFixture]
public sealed class CargoTest
{
- public static HashSet> Ignored = new ()
- {
+ private static readonly HashSet> Ignored =
+ [
// This is ignored because it is explicitly intended to be able to sell for more than it costs.
new("FunCrateGambling")
- };
+ ];
[Test]
public async Task NoCargoOrderArbitrage()
diff --git a/Content.IntegrationTests/Tests/Chemistry/FixedPoint2SerializationTest.cs b/Content.IntegrationTests/Tests/Chemistry/FixedPoint2SerializationTest.cs
index 8e3b89bff11..0e3f89c2825 100644
--- a/Content.IntegrationTests/Tests/Chemistry/FixedPoint2SerializationTest.cs
+++ b/Content.IntegrationTests/Tests/Chemistry/FixedPoint2SerializationTest.cs
@@ -9,10 +9,10 @@ namespace Content.IntegrationTests.Tests.Chemistry
{
public sealed class FixedPoint2SerializationTest : SerializationTest
{
- protected override Assembly[] Assemblies => new[]
- {
+ protected override Assembly[] Assemblies =>
+ [
typeof(FixedPoint2SerializationTest).Assembly
- };
+ ];
[Test]
public void DeserializeNullTest()
@@ -53,6 +53,6 @@ public void DeserializeNullDefinitionTest()
[DataDefinition]
public sealed partial class FixedPoint2TestDefinition
{
- [DataField("unit")] public FixedPoint2? Unit { get; set; } = FixedPoint2.New(5);
+ [DataField] public FixedPoint2? Unit { get; set; } = FixedPoint2.New(5);
}
}
diff --git a/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs b/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs
index 4d19a96d9e7..89d33186a27 100644
--- a/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs
+++ b/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs
@@ -1,5 +1,5 @@
-using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
@@ -76,7 +76,7 @@ public async Task Test()
await server.WaitPost(() =>
{
- var system = server.System();
+ var system = server.System();
var beaker = server.EntMan.SpawnEntity("SolutionRoundingTestContainer", testMap.GridCoords);
system.TryGetSolution(beaker, "beaker", out var newSolutionEnt, out var newSolution);
diff --git a/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs b/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs
index d96a035b2dc..6b71dd08be0 100644
--- a/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs
+++ b/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs
@@ -1,5 +1,5 @@
-using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.FixedPoint;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
@@ -11,7 +11,7 @@ namespace Content.IntegrationTests.Tests.Chemistry;
// To ensure volume(A) + volume(B) = volume(A+B)
// reactions can change this assumption
[TestFixture]
-[TestOf(typeof(SolutionContainerSystem))]
+[TestOf(typeof(SharedSolutionContainerSystem))]
public sealed class SolutionSystemTests
{
[TestPrototypes]
@@ -51,7 +51,7 @@ public async Task TryAddTwoNonReactiveReagent()
var entityManager = server.ResolveDependency();
var protoMan = server.ResolveDependency();
- var containerSystem = entityManager.System();
+ var containerSystem = entityManager.System();
var testMap = await pair.CreateTestMap();
var coordinates = testMap.GridCoords;
@@ -97,7 +97,7 @@ public async Task TryAddTooMuchNonReactiveReagent()
var entityManager = server.ResolveDependency();
var protoMan = server.ResolveDependency();
- var containerSystem = entityManager.System();
+ var containerSystem = entityManager.System();
var coordinates = testMap.GridCoords;
EntityUid beaker;
@@ -141,7 +141,7 @@ public async Task TryMixAndOverflowTooMuchReagent()
var entityManager = server.ResolveDependency();
var protoMan = server.ResolveDependency();
var testMap = await pair.CreateTestMap();
- var containerSystem = entityManager.System();
+ var containerSystem = entityManager.System();
var coordinates = testMap.GridCoords;
EntityUid beaker;
@@ -194,7 +194,7 @@ public async Task TryMixAndOverflowTooBigOverflow()
var entityManager = server.ResolveDependency();
var protoMan = server.ResolveDependency();
- var containerSystem = entityManager.System();
+ var containerSystem = entityManager.System();
var testMap = await pair.CreateTestMap();
var coordinates = testMap.GridCoords;
diff --git a/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs b/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs
index ddfe7b3481e..3664cda922a 100644
--- a/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs
+++ b/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs
@@ -1,4 +1,3 @@
-using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Components;
using Robust.Shared.GameObjects;
@@ -6,6 +5,7 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Linq;
+using Content.Shared.Chemistry.EntitySystems;
namespace Content.IntegrationTests.Tests.Chemistry
{
@@ -34,7 +34,7 @@ public async Task TryAllTest()
var prototypeManager = server.ResolveDependency();
var testMap = await pair.CreateTestMap();
var coordinates = testMap.GridCoords;
- var solutionContainerSystem = entityManager.System();
+ var solutionContainerSystem = entityManager.System();
foreach (var reactionPrototype in prototypeManager.EnumeratePrototypes())
{
diff --git a/Content.IntegrationTests/Tests/Climbing/ClimbingTest.cs b/Content.IntegrationTests/Tests/Climbing/ClimbingTest.cs
index d8d3086520e..2db0a9acd3d 100644
--- a/Content.IntegrationTests/Tests/Climbing/ClimbingTest.cs
+++ b/Content.IntegrationTests/Tests/Climbing/ClimbingTest.cs
@@ -1,5 +1,6 @@
#nullable enable
using Content.IntegrationTests.Tests.Interaction;
+using Content.IntegrationTests.Tests.Movement;
using Robust.Shared.Maths;
using ClimbingComponent = Content.Shared.Climbing.Components.ClimbingComponent;
using ClimbSystem = Content.Shared.Climbing.Systems.ClimbSystem;
diff --git a/Content.IntegrationTests/Tests/Commands/ForceMapTest.cs b/Content.IntegrationTests/Tests/Commands/ForceMapTest.cs
new file mode 100644
index 00000000000..3fa7e64f1a4
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Commands/ForceMapTest.cs
@@ -0,0 +1,88 @@
+using Content.Server.Maps;
+using Content.Shared.CCVar;
+using Robust.Shared.Configuration;
+using Robust.Shared.Console;
+
+namespace Content.IntegrationTests.Tests.Commands;
+
+[TestFixture]
+public sealed class ForceMapTest
+{
+ private const string DefaultMapName = "Empty";
+ private const string BadMapName = "asdf_asd-fa__sdfAsd_f"; // Hopefully no one ever names a map this...
+ private const string TestMapEligibleName = "ForceMapTestEligible";
+ private const string TestMapIneligibleName = "ForceMapTestIneligible";
+
+ [TestPrototypes]
+ private static readonly string TestMaps = @$"
+- type: gameMap
+ id: {TestMapIneligibleName}
+ mapName: {TestMapIneligibleName}
+ mapPath: /Maps/Test/empty.yml
+ minPlayers: 20
+ maxPlayers: 80
+ stations:
+ Empty:
+ stationProto: StandardNanotrasenStation
+ components:
+ - type: StationNameSetup
+ mapNameTemplate: ""Empty""
+
+- type: gameMap
+ id: {TestMapEligibleName}
+ mapName: {TestMapEligibleName}
+ mapPath: /Maps/Test/empty.yml
+ minPlayers: 0
+ stations:
+ Empty:
+ stationProto: StandardNanotrasenStation
+ components:
+ - type: StationNameSetup
+ mapNameTemplate: ""Empty""
+";
+
+ [Test]
+ public async Task TestForceMapCommand()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var entMan = server.EntMan;
+ var configManager = server.ResolveDependency();
+ var consoleHost = server.ResolveDependency();
+ var gameMapMan = server.ResolveDependency();
+
+ await server.WaitAssertion(() =>
+ {
+ // Make sure we're set to the default map
+ Assert.That(gameMapMan.GetSelectedMap()?.ID, Is.EqualTo(DefaultMapName),
+ $"Test didn't start on expected map ({DefaultMapName})!");
+
+ // Try changing to a map that doesn't exist
+ consoleHost.ExecuteCommand($"forcemap {BadMapName}");
+ Assert.That(gameMapMan.GetSelectedMap()?.ID, Is.EqualTo(DefaultMapName),
+ $"Forcemap succeeded with a map that does not exist ({BadMapName})!");
+
+ // Try changing to a valid map
+ consoleHost.ExecuteCommand($"forcemap {TestMapEligibleName}");
+ Assert.That(gameMapMan.GetSelectedMap()?.ID, Is.EqualTo(TestMapEligibleName),
+ $"Forcemap failed with a valid map ({TestMapEligibleName})");
+
+ // Try changing to a map that exists but is ineligible
+ consoleHost.ExecuteCommand($"forcemap {TestMapIneligibleName}");
+ Assert.That(gameMapMan.GetSelectedMap()?.ID, Is.EqualTo(TestMapIneligibleName),
+ $"Forcemap failed with valid but ineligible map ({TestMapIneligibleName})!");
+
+ // Try clearing the force-selected map
+ consoleHost.ExecuteCommand("forcemap \"\"");
+ Assert.That(gameMapMan.GetSelectedMap(), Is.Null,
+ $"Running 'forcemap \"\"' did not clear the forced map!");
+
+ });
+
+ // Cleanup
+ configManager.SetCVar(CCVars.GameMap, DefaultMapName);
+
+ await pair.CleanReturnAsync();
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Commands/PardonCommand.cs b/Content.IntegrationTests/Tests/Commands/PardonCommand.cs
index b3a66e3211c..4db9eabf5c6 100644
--- a/Content.IntegrationTests/Tests/Commands/PardonCommand.cs
+++ b/Content.IntegrationTests/Tests/Commands/PardonCommand.cs
@@ -28,7 +28,7 @@ public async Task PardonTest()
Assert.That(netMan.IsConnected);
- Assert.That(sPlayerManager.Sessions.Count(), Is.EqualTo(1));
+ Assert.That(sPlayerManager.Sessions, Has.Length.EqualTo(1));
// No bans on record
Assert.Multiple(async () =>
{
@@ -50,7 +50,7 @@ public async Task PardonTest()
var banReason = "test";
- Assert.That(sPlayerManager.Sessions.Count(), Is.EqualTo(1));
+ Assert.That(sPlayerManager.Sessions, Has.Length.EqualTo(1));
// Ban the client for 24 hours
await server.WaitPost(() => sConsole.ExecuteCommand($"ban {clientSession.Name} {banReason} 1440"));
@@ -63,7 +63,7 @@ public async Task PardonTest()
});
await pair.RunTicksSync(5);
- Assert.That(sPlayerManager.Sessions.Count(), Is.EqualTo(0));
+ Assert.That(sPlayerManager.Sessions, Has.Length.EqualTo(0));
Assert.That(!netMan.IsConnected);
// Try to pardon a ban that does not exist
@@ -143,11 +143,11 @@ public async Task PardonTest()
});
// Reconnect client. Slightly faster than dirtying the pair.
- Assert.That(sPlayerManager.Sessions.Count(), Is.EqualTo(0));
+ Assert.That(sPlayerManager.Sessions, Is.Empty);
client.SetConnectTarget(server);
await client.WaitPost(() => netMan.ClientConnect(null!, 0, null!));
await pair.RunTicksSync(5);
- Assert.That(sPlayerManager.Sessions.Count(), Is.EqualTo(1));
+ Assert.That(sPlayerManager.Sessions, Has.Length.EqualTo(1));
await pair.CleanReturnAsync();
}
diff --git a/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs b/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs
index 2fda3ad58e6..cfc80073066 100644
--- a/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs
+++ b/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs
@@ -37,9 +37,9 @@ public async Task RejuvenateDeadTest()
var server = pair.Server;
var entManager = server.ResolveDependency();
var prototypeManager = server.ResolveDependency();
- var mobStateSystem = entManager.EntitySysManager.GetEntitySystem();
- var damSystem = entManager.EntitySysManager.GetEntitySystem();
- var rejuvenateSystem = entManager.EntitySysManager.GetEntitySystem();
+ var mobStateSystem = entManager.System();
+ var damSystem = entManager.System();
+ var rejuvenateSystem = entManager.System();
await server.WaitAssertion(() =>
{
diff --git a/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs b/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs
index ff24ec09686..72a05b5246f 100644
--- a/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs
+++ b/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs
@@ -26,7 +26,7 @@ public async Task RestartRoundAfterStart(bool lobbyEnabled)
var configManager = server.ResolveDependency();
var entityManager = server.ResolveDependency();
- var gameTicker = entityManager.EntitySysManager.GetEntitySystem();
+ var gameTicker = entityManager.System();
await pair.RunTicksSync(5);
diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/CraftingTests.cs b/Content.IntegrationTests/Tests/Construction/Interaction/CraftingTests.cs
index 76911eba5f7..74d0e924217 100644
--- a/Content.IntegrationTests/Tests/Construction/Interaction/CraftingTests.cs
+++ b/Content.IntegrationTests/Tests/Construction/Interaction/CraftingTests.cs
@@ -59,11 +59,6 @@ public async Task CraftSpear()
await AssertEntityLookup((Rod, 2), (Cable, 7), (ShardGlass, 2), (Spear, 1));
}
- // The following is wrapped in an if DEBUG. This is because of cursed state handling bugs. Tests don't (de)serialize
- // net messages and just copy objects by reference. This means that the server will directly modify cached server
- // states on the client's end. Crude fix at the moment is to used modified state handling while in debug mode
- // Otherwise, this test cannot work.
-#if DEBUG
///
/// Cancel crafting a complex recipe.
///
@@ -93,28 +88,22 @@ public async Task CancelCraft()
await RunTicks(1);
// DoAfter is in progress. Entity not spawned, stacks have been split and someingredients are in a container.
- Assert.Multiple(async () =>
- {
- Assert.That(ActiveDoAfters.Count(), Is.EqualTo(1));
- Assert.That(sys.IsEntityInContainer(shard), Is.True);
- Assert.That(sys.IsEntityInContainer(rods), Is.False);
- Assert.That(sys.IsEntityInContainer(wires), Is.False);
- Assert.That(rodStack, Has.Count.EqualTo(8));
- Assert.That(wireStack, Has.Count.EqualTo(7));
+ Assert.That(ActiveDoAfters.Count(), Is.EqualTo(1));
+ Assert.That(sys.IsEntityInContainer(shard), Is.True);
+ Assert.That(sys.IsEntityInContainer(rods), Is.False);
+ Assert.That(sys.IsEntityInContainer(wires), Is.False);
+ Assert.That(rodStack, Has.Count.EqualTo(8));
+ Assert.That(wireStack, Has.Count.EqualTo(7));
- await FindEntity(Spear, shouldSucceed: false);
- });
+ await FindEntity(Spear, shouldSucceed: false);
// Cancel the DoAfter. Should drop ingredients to the floor.
await CancelDoAfters();
- Assert.Multiple(async () =>
- {
- Assert.That(sys.IsEntityInContainer(rods), Is.False);
- Assert.That(sys.IsEntityInContainer(wires), Is.False);
- Assert.That(sys.IsEntityInContainer(shard), Is.False);
- await FindEntity(Spear, shouldSucceed: false);
- await AssertEntityLookup((Rod, 10), (Cable, 10), (ShardGlass, 1));
- });
+ Assert.That(sys.IsEntityInContainer(rods), Is.False);
+ Assert.That(sys.IsEntityInContainer(wires), Is.False);
+ Assert.That(sys.IsEntityInContainer(shard), Is.False);
+ await FindEntity(Spear, shouldSucceed: false);
+ await AssertEntityLookup((Rod, 10), (Cable, 10), (ShardGlass, 1));
// Re-attempt the do-after
#pragma warning disable CS4014 // Legacy construction code uses DoAfterAwait. See above.
@@ -123,24 +112,17 @@ public async Task CancelCraft()
await RunTicks(1);
// DoAfter is in progress. Entity not spawned, ingredients are in a container.
- Assert.Multiple(async () =>
- {
- Assert.That(ActiveDoAfters.Count(), Is.EqualTo(1));
- Assert.That(sys.IsEntityInContainer(shard), Is.True);
- await FindEntity(Spear, shouldSucceed: false);
- });
+ Assert.That(ActiveDoAfters.Count(), Is.EqualTo(1));
+ Assert.That(sys.IsEntityInContainer(shard), Is.True);
+ await FindEntity(Spear, shouldSucceed: false);
// Finish the DoAfter
await AwaitDoAfters();
// Spear has been crafted. Rods and wires are no longer contained. Glass has been consumed.
- Assert.Multiple(async () =>
- {
- await FindEntity(Spear);
- Assert.That(sys.IsEntityInContainer(rods), Is.False);
- Assert.That(sys.IsEntityInContainer(wires), Is.False);
- Assert.That(SEntMan.Deleted(shard));
- });
+ await FindEntity(Spear);
+ Assert.That(sys.IsEntityInContainer(rods), Is.False);
+ Assert.That(sys.IsEntityInContainer(wires), Is.False);
+ Assert.That(SEntMan.Deleted(shard));
}
-#endif
}
diff --git a/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs b/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs
index c61a70faf0b..37c4b0c9b57 100644
--- a/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs
+++ b/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs
@@ -43,11 +43,11 @@ public async Task TestA()
EntityUid dummy = default;
var mapManager = server.ResolveDependency();
- var mapId = mapManager.CreateMap();
+ var map = await pair.CreateTestMap();
await server.WaitPost(() =>
{
- var pos = new MapCoordinates(Vector2.Zero, mapId);
+ var pos = new MapCoordinates(Vector2.Zero, map.MapId);
var entStorage = serverEntManager.EntitySysManager.GetEntitySystem();
var container = serverEntManager.SpawnEntity("ContainerOcclusionA", pos);
dummy = serverEntManager.SpawnEntity("ContainerOcclusionDummy", pos);
@@ -85,11 +85,12 @@ public async Task TestB()
EntityUid dummy = default;
var mapManager = server.ResolveDependency();
- var mapId = mapManager.CreateMap();
+
+ var map = await pair.CreateTestMap();
await server.WaitPost(() =>
{
- var pos = new MapCoordinates(Vector2.Zero, mapId);
+ var pos = new MapCoordinates(Vector2.Zero, map.MapId);
var entStorage = serverEntManager.EntitySysManager.GetEntitySystem();
var container = serverEntManager.SpawnEntity("ContainerOcclusionB", pos);
dummy = serverEntManager.SpawnEntity("ContainerOcclusionDummy", pos);
@@ -127,11 +128,12 @@ public async Task TestAb()
EntityUid dummy = default;
var mapManager = server.ResolveDependency();
- var mapId = mapManager.CreateMap();
+
+ var map = await pair.CreateTestMap();
await server.WaitPost(() =>
{
- var pos = new MapCoordinates(Vector2.Zero, mapId);
+ var pos = new MapCoordinates(Vector2.Zero, map.MapId);
var entStorage = serverEntManager.EntitySysManager.GetEntitySystem();
var containerA = serverEntManager.SpawnEntity("ContainerOcclusionA", pos);
var containerB = serverEntManager.SpawnEntity("ContainerOcclusionB", pos);
diff --git a/Content.IntegrationTests/Tests/Damageable/DamageSpecifierTest.cs b/Content.IntegrationTests/Tests/Damageable/DamageSpecifierTest.cs
index 41d17ddedae..bd5cac05dd1 100644
--- a/Content.IntegrationTests/Tests/Damageable/DamageSpecifierTest.cs
+++ b/Content.IntegrationTests/Tests/Damageable/DamageSpecifierTest.cs
@@ -14,39 +14,39 @@ public void TestDamageSpecifierOperations()
// Test basic math operations.
// I've already nearly broken these once. When editing the operators.
- DamageSpecifier input1 = new() { DamageDict = _input1 };
- DamageSpecifier input2 = new() { DamageDict = _input2 };
- DamageSpecifier output1 = new() { DamageDict = _output1 };
- DamageSpecifier output2 = new() { DamageDict = _output2 };
- DamageSpecifier output3 = new() { DamageDict = _output3 };
- DamageSpecifier output4 = new() { DamageDict = _output4 };
- DamageSpecifier output5 = new() { DamageDict = _output5 };
+ DamageSpecifier input1 = new() { DamageDict = Input1 };
+ DamageSpecifier input2 = new() { DamageDict = Input2 };
+ DamageSpecifier output1 = new() { DamageDict = Output1 };
+ DamageSpecifier output2 = new() { DamageDict = Output2 };
+ DamageSpecifier output3 = new() { DamageDict = Output3 };
+ DamageSpecifier output4 = new() { DamageDict = Output4 };
+ DamageSpecifier output5 = new() { DamageDict = Output5 };
Assert.Multiple(() =>
{
- Assert.That((-input1).Equals(output1));
- Assert.That((input1 / 2).Equals(output2));
- Assert.That((input1 * 2).Equals(output3));
+ Assert.That(-input1, Is.EqualTo(output1));
+ Assert.That(input1 / 2, Is.EqualTo(output2));
+ Assert.That(input1 * 2, Is.EqualTo(output3));
});
- var difference = (input1 - input2);
- Assert.That(difference.Equals(output4));
+ var difference = input1 - input2;
+ Assert.That(difference, Is.EqualTo(output4));
- var difference2 = (-input2) + input1;
- Assert.That(difference.Equals(difference2));
+ var difference2 = -input2 + input1;
+ Assert.That(difference, Is.EqualTo(difference2));
difference.Clamp(-0.25f, 0.25f);
- Assert.That(difference.Equals(output5));
+ Assert.That(difference, Is.EqualTo(output5));
}
- static Dictionary _input1 = new()
+ private static readonly Dictionary Input1 = new()
{
{ "A", 1.5f },
{ "B", 2 },
{ "C", 3 }
};
- static Dictionary _input2 = new()
+ private static readonly Dictionary Input2 = new()
{
{ "A", 1 },
{ "B", 2 },
@@ -54,28 +54,28 @@ public void TestDamageSpecifierOperations()
{ "D", 0.05f }
};
- static Dictionary _output1 = new()
+ private static readonly Dictionary Output1 = new()
{
{ "A", -1.5f },
{ "B", -2 },
{ "C", -3 }
};
- static Dictionary _output2 = new()
+ private static readonly Dictionary Output2 = new()
{
{ "A", 0.75f },
{ "B", 1 },
{ "C", 1.5 }
};
- static Dictionary _output3 = new()
+ private static readonly Dictionary Output3 = new()
{
{ "A", 3f },
{ "B", 4 },
{ "C", 6 }
};
- static Dictionary _output4 = new()
+ private static readonly Dictionary Output4 = new()
{
{ "A", 0.5f },
{ "B", 0 },
@@ -83,7 +83,7 @@ public void TestDamageSpecifierOperations()
{ "D", -0.05f }
};
- static Dictionary _output5 = new()
+ private static readonly Dictionary Output5 = new()
{
{ "A", 0.25f },
{ "B", 0 },
diff --git a/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs b/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs
index c40b8ed286f..69069fc82fe 100644
--- a/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs
+++ b/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs
@@ -107,10 +107,11 @@ public async Task TestDamageableComponents()
FixedPoint2 typeDamage;
+ var map = await pair.CreateTestMap();
+
await server.WaitPost(() =>
{
- var map = sMapManager.CreateMap();
- var coordinates = new MapCoordinates(0, 0, map);
+ var coordinates = map.MapCoords;
sDamageableEntity = sEntityManager.SpawnEntity("TestDamageableEntityId", coordinates);
sDamageableComponent = sEntityManager.GetComponent(sDamageableEntity);
diff --git a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs
index 2fbaa91456f..e47c73611a4 100644
--- a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs
+++ b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs
@@ -123,24 +123,24 @@ public async Task AirlockBlockTest()
var xformSystem = entityManager.System();
PhysicsComponent physBody = null;
- EntityUid AirlockPhysicsDummy = default;
+ EntityUid airlockPhysicsDummy = default;
EntityUid airlock = default;
DoorComponent doorComponent = null;
- var AirlockPhysicsDummyStartingX = -1;
+ var airlockPhysicsDummyStartingX = -1;
+
+ var map = await pair.CreateTestMap();
await server.WaitAssertion(() =>
{
- var mapId = mapManager.CreateMap();
-
- var humanCoordinates = new MapCoordinates(new Vector2(AirlockPhysicsDummyStartingX, 0), mapId);
- AirlockPhysicsDummy = entityManager.SpawnEntity("AirlockPhysicsDummy", humanCoordinates);
+ var humanCoordinates = new MapCoordinates(new Vector2(airlockPhysicsDummyStartingX, 0), map.MapId);
+ airlockPhysicsDummy = entityManager.SpawnEntity("AirlockPhysicsDummy", humanCoordinates);
- airlock = entityManager.SpawnEntity("AirlockDummy", new MapCoordinates(new Vector2(0, 0), mapId));
+ airlock = entityManager.SpawnEntity("AirlockDummy", new MapCoordinates(new Vector2(0, 0), map.MapId));
Assert.Multiple(() =>
{
- Assert.That(entityManager.TryGetComponent(AirlockPhysicsDummy, out physBody), Is.True);
+ Assert.That(entityManager.TryGetComponent(airlockPhysicsDummy, out physBody), Is.True);
Assert.That(entityManager.TryGetComponent(airlock, out doorComponent), Is.True);
});
Assert.That(doorComponent.State, Is.EqualTo(DoorState.Closed));
@@ -152,7 +152,7 @@ await server.WaitAssertion(() =>
await server.WaitAssertion(() => Assert.That(physBody, Is.Not.EqualTo(null)));
await server.WaitPost(() =>
{
- physicsSystem.SetLinearVelocity(AirlockPhysicsDummy, new Vector2(0.5f, 0f), body: physBody);
+ physicsSystem.SetLinearVelocity(airlockPhysicsDummy, new Vector2(0.5f, 0f), body: physBody);
});
for (var i = 0; i < 240; i += 10)
@@ -176,7 +176,7 @@ await server.WaitPost(() =>
// Blocked by the airlock
await server.WaitAssertion(() =>
{
- Assert.That(Math.Abs(xformSystem.GetWorldPosition(AirlockPhysicsDummy).X - 1), Is.GreaterThan(0.01f));
+ Assert.That(Math.Abs(xformSystem.GetWorldPosition(airlockPhysicsDummy).X - 1), Is.GreaterThan(0.01f));
});
await pair.CleanReturnAsync();
}
diff --git a/Content.IntegrationTests/Tests/DummyIconTest.cs b/Content.IntegrationTests/Tests/DummyIconTest.cs
index a11191a51ea..df2d28a2ea2 100644
--- a/Content.IntegrationTests/Tests/DummyIconTest.cs
+++ b/Content.IntegrationTests/Tests/DummyIconTest.cs
@@ -21,7 +21,7 @@ await client.WaitAssertion(() =>
{
foreach (var proto in prototypeManager.EnumeratePrototypes())
{
- if (proto.NoSpawn || proto.Abstract || pair.IsTestPrototype(proto) || !proto.Components.ContainsKey("Sprite"))
+ if (proto.HideSpawnMenu || proto.Abstract || pair.IsTestPrototype(proto) || !proto.Components.ContainsKey("Sprite"))
continue;
Assert.DoesNotThrow(() =>
diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs
index 1fc739fb0c7..42bea8989cd 100644
--- a/Content.IntegrationTests/Tests/EntityTest.cs
+++ b/Content.IntegrationTests/Tests/EntityTest.cs
@@ -1,15 +1,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
-using Content.Server.Humanoid.Components;
-using Content.Shared.Coordinates;
-using Content.Shared.Prototypes;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Map;
-using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
@@ -47,7 +43,7 @@ await server.WaitPost(() =>
foreach (var protoId in protoIds)
{
- var mapId = mapManager.CreateMap();
+ mapSystem.CreateMap(out var mapId);
var grid = mapManager.CreateGridEntity(mapId);
// TODO: Fix this better in engine.
mapSystem.SetTile(grid.Owner, grid.Comp, Vector2i.Zero, new Tile(1));
@@ -155,6 +151,7 @@ public async Task SpawnAndDirtyAllEntities()
var prototypeMan = server.ResolveDependency();
var mapManager = server.ResolveDependency();
var sEntMan = server.ResolveDependency();
+ var mapSys = server.System();
Assert.That(cfg.GetCVar(CVars.NetPVS), Is.False);
@@ -170,7 +167,7 @@ await server.WaitPost(() =>
{
foreach (var protoId in protoIds)
{
- var mapId = mapManager.CreateMap();
+ mapSys.CreateMap(out var mapId);
var grid = mapManager.CreateGridEntity(mapId);
var ent = sEntMan.SpawnEntity(protoId, new EntityCoordinates(grid.Owner, 0.5f, 0.5f));
foreach (var (_, component) in sEntMan.GetNetComponents(ent))
@@ -227,6 +224,7 @@ public async Task SpawnAndDeleteEntityCountTest()
var settings = new PoolSettings { Connected = true, Dirty = true };
await using var pair = await PoolManager.GetServerClient(settings);
var mapManager = pair.Server.ResolveDependency();
+ var mapSys = pair.Server.System();
var server = pair.Server;
var client = pair.Client;
@@ -256,7 +254,7 @@ public async Task SpawnAndDeleteEntityCountTest()
await server.WaitPost(() =>
{
- mapId = mapManager.CreateMap();
+ mapSys.CreateMap(out mapId);
});
var coords = new MapCoordinates(Vector2.Zero, mapId);
diff --git a/Content.IntegrationTests/Tests/Fluids/FluidSpillTest.cs b/Content.IntegrationTests/Tests/Fluids/FluidSpillTest.cs
index be3cb08f181..53ca39992df 100644
--- a/Content.IntegrationTests/Tests/Fluids/FluidSpillTest.cs
+++ b/Content.IntegrationTests/Tests/Fluids/FluidSpillTest.cs
@@ -16,14 +16,15 @@ namespace Content.IntegrationTests.Tests.Fluids;
[TestOf(typeof(SpreaderSystem))]
public sealed class FluidSpill
{
- private static PuddleComponent? GetPuddle(IEntityManager entityManager, MapGridComponent mapGrid, Vector2i pos)
+ private static PuddleComponent? GetPuddle(IEntityManager entityManager, Entity mapGrid, Vector2i pos)
{
return GetPuddleEntity(entityManager, mapGrid, pos)?.Comp;
}
- private static Entity? GetPuddleEntity(IEntityManager entityManager, MapGridComponent mapGrid, Vector2i pos)
+ private static Entity? GetPuddleEntity(IEntityManager entityManager, Entity mapGrid, Vector2i pos)
{
- foreach (var uid in mapGrid.GetAnchoredEntities(pos))
+ var mapSys = entityManager.System();
+ foreach (var uid in mapSys.GetAnchoredEntities(mapGrid, mapGrid.Comp, pos))
{
if (entityManager.TryGetComponent(uid, out PuddleComponent? puddleComponent))
return (uid, puddleComponent);
@@ -39,9 +40,9 @@ public async Task SpillCorner()
var server = pair.Server;
var mapManager = server.ResolveDependency();
var entityManager = server.ResolveDependency();
- var puddleSystem = server.ResolveDependency().GetEntitySystem();
+ var puddleSystem = server.System();
+ var mapSystem = server.System();
var gameTiming = server.ResolveDependency();
- MapId mapId;
EntityUid gridId = default;
/*
@@ -52,7 +53,7 @@ . . .
*/
await server.WaitPost(() =>
{
- mapId = mapManager.CreateMap();
+ mapSystem.CreateMap(out var mapId);
var grid = mapManager.CreateGridEntity(mapId);
gridId = grid.Owner;
@@ -60,12 +61,12 @@ await server.WaitPost(() =>
{
for (var y = 0; y < 3; y++)
{
- grid.Comp.SetTile(new Vector2i(x, y), new Tile(1));
+ mapSystem.SetTile(grid, new Vector2i(x, y), new Tile(1));
}
}
- entityManager.SpawnEntity("WallReinforced", grid.Comp.GridTileToLocal(new Vector2i(0, 1)));
- entityManager.SpawnEntity("WallReinforced", grid.Comp.GridTileToLocal(new Vector2i(1, 0)));
+ entityManager.SpawnEntity("WallReinforced", mapSystem.GridTileToLocal(grid, grid.Comp, new Vector2i(0, 1)));
+ entityManager.SpawnEntity("WallReinforced", mapSystem.GridTileToLocal(grid, grid.Comp, new Vector2i(1, 0)));
});
@@ -74,10 +75,10 @@ await server.WaitAssertion(() =>
{
var grid = entityManager.GetComponent(gridId);
var solution = new Solution("Wine", FixedPoint2.New(100)); // Frontier - Blood to wine so test pass
- var tileRef = grid.GetTileRef(puddleOrigin);
+ var tileRef = mapSystem.GetTileRef(gridId, grid, puddleOrigin);
#pragma warning disable NUnit2045 // Interdependent tests
Assert.That(puddleSystem.TrySpillAt(tileRef, solution, out _), Is.True);
- Assert.That(GetPuddle(entityManager, grid, puddleOrigin), Is.Not.Null);
+ Assert.That(GetPuddle(entityManager, (gridId, grid), puddleOrigin), Is.Not.Null);
#pragma warning restore NUnit2045
});
@@ -87,7 +88,7 @@ await server.WaitAssertion(() =>
await server.WaitAssertion(() =>
{
var grid = entityManager.GetComponent(gridId);
- var puddle = GetPuddleEntity(entityManager, grid, puddleOrigin);
+ var puddle = GetPuddleEntity(entityManager, (gridId, grid), puddleOrigin);
#pragma warning disable NUnit2045 // Interdependent tests
Assert.That(puddle, Is.Not.Null);
@@ -104,7 +105,7 @@ await server.WaitAssertion(() =>
}
var newPos = new Vector2i(x, y);
- var sidePuddle = GetPuddle(entityManager, grid, newPos);
+ var sidePuddle = GetPuddle(entityManager, (gridId, grid), newPos);
Assert.That(sidePuddle, Is.Null);
}
}
diff --git a/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs b/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs
index a9069892dff..ee2d0cb1f7a 100644
--- a/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs
+++ b/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs
@@ -5,7 +5,6 @@
using Content.Shared.Fluids.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
-using Robust.Shared.Map.Components;
namespace Content.IntegrationTests.Tests.Fluids
{
@@ -21,8 +20,7 @@ public async Task TilePuddleTest()
var testMap = await pair.CreateTestMap();
- var entitySystemManager = server.ResolveDependency();
- var spillSystem = entitySystemManager.GetEntitySystem();
+ var spillSystem = server.System();
await server.WaitAssertion(() =>
{
@@ -46,17 +44,19 @@ public async Task SpaceNoPuddleTest()
var server = pair.Server;
var testMap = await pair.CreateTestMap();
- var grid = testMap.Grid.Comp;
+ var grid = testMap.Grid;
var entitySystemManager = server.ResolveDependency();
- var spillSystem = entitySystemManager.GetEntitySystem();
+ var spillSystem = server.System();
+ var mapSystem = server.System();
// Remove all tiles
await server.WaitPost(() =>
{
- foreach (var tile in grid.GetAllTiles())
+ var tiles = mapSystem.GetAllTiles(grid.Owner, grid.Comp);
+ foreach (var tile in tiles)
{
- grid.SetTile(tile.GridIndices, Tile.Empty);
+ mapSystem.SetTile(grid, tile.GridIndices, Tile.Empty);
}
});
diff --git a/Content.IntegrationTests/Tests/FollowerSystemTest.cs b/Content.IntegrationTests/Tests/FollowerSystemTest.cs
index 4d308c6d911..f4447426c77 100644
--- a/Content.IntegrationTests/Tests/FollowerSystemTest.cs
+++ b/Content.IntegrationTests/Tests/FollowerSystemTest.cs
@@ -22,6 +22,7 @@ public async Task FollowerMapDeleteTest()
var mapMan = server.ResolveDependency();
var sysMan = server.ResolveDependency();
var logMan = server.ResolveDependency();
+ var mapSys = server.System();
var logger = logMan.RootSawmill;
await server.WaitPost(() =>
@@ -29,7 +30,7 @@ await server.WaitPost(() =>
var followerSystem = sysMan.GetEntitySystem();
// Create a map to spawn the observers on.
- var map = mapMan.CreateMap();
+ mapSys.CreateMap(out var map);
// Spawn an observer to be followed.
var followed = entMan.SpawnEntity(GameTicker.ObserverPrototypeName, new MapCoordinates(0, 0, map));
@@ -41,7 +42,7 @@ await server.WaitPost(() =>
followerSystem.StartFollowingEntity(follower, followed);
- entMan.DeleteEntity(mapMan.GetMapEntityId(map));
+ entMan.DeleteEntity(mapSys.GetMap(map));
});
await pair.CleanReturnAsync();
}
diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs
index 0ac6b68a3ec..2570e2246a6 100644
--- a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs
+++ b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs
@@ -1,5 +1,4 @@
#nullable enable
-using System.Numerics;
using Content.Server.Cuffs;
using Content.Shared.Body.Components;
using Content.Shared.Cuffs.Components;
@@ -7,7 +6,6 @@
using Robust.Server.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
-using Robust.Shared.Maths;
namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
{
@@ -52,10 +50,11 @@ public async Task Test()
var mapManager = server.ResolveDependency();
var host = server.ResolveDependency();
+ var map = await pair.CreateTestMap();
+
await server.WaitAssertion(() =>
{
- var mapId = mapManager.CreateMap();
- var coordinates = new MapCoordinates(Vector2.Zero, mapId);
+ var coordinates = map.MapCoords;
var cuffableSys = entityManager.System();
var xformSys = entityManager.System();
diff --git a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs
index 1bea33a82bc..b215584c57a 100644
--- a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs
+++ b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs
@@ -47,7 +47,7 @@ public async Task TestLobbyPlayersValid()
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
// By default, traitor/antag preferences are disabled, so the pool should be empty.
- var sessions = new List{pair.Player!};
+ var sessions = new List { pair.Player! };
var pool = sys.GetPlayerPool(rule, sessions, def);
Assert.That(pool.Count, Is.EqualTo(0));
diff --git a/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs
index f660eccf30a..518166ed864 100644
--- a/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs
+++ b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs
@@ -110,7 +110,7 @@ public async Task FailAndStartTest()
player = pair.Player!.AttachedEntity!.Value;
Assert.That(entMan.EntityExists(player));
- ticker.SetGamePreset((GamePresetPrototype?)null);
+ ticker.SetGamePreset((GamePresetPrototype?) null);
server.CfgMan.SetCVar(CCVars.GridFill, false);
server.CfgMan.SetCVar(CCVars.GameLobbyFallbackEnabled, true);
server.CfgMan.SetCVar(CCVars.GameLobbyDefaultPreset, "secret");
diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs
index fc50d0bd334..7b3c9943029 100644
--- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs
+++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs
@@ -7,6 +7,7 @@
using Content.Server.Mind;
using Content.Server.Pinpointer;
using Content.Server.Roles;
+using Content.Server.RoundEnd;
using Content.Server.Shuttles.Components;
using Content.Server.Station.Components;
using Content.Shared.CCVar;
@@ -49,6 +50,7 @@ public async Task TryStopNukeOpsFromConstantlyFailing()
var roleSys = server.System();
var invSys = server.System();
var factionSys = server.System();
+ var roundEndSys = server.System();
server.CfgMan.SetCVar(CCVars.GridFill, true);
@@ -63,11 +65,11 @@ public async Task TryStopNukeOpsFromConstantlyFailing()
// Opt into the nukies role.
await pair.SetAntagPreference("NukeopsCommander", true);
- await pair.SetAntagPreference( "NukeopsMedic", true, dummies[1].UserId);
+ await pair.SetAntagPreference("NukeopsMedic", true, dummies[1].UserId);
// Initially, the players have no attached entities
Assert.That(pair.Player?.AttachedEntity, Is.Null);
- Assert.That(dummies.All(x => x.AttachedEntity == null));
+ Assert.That(dummies.All(x => x.AttachedEntity == null));
// There are no grids or maps
Assert.That(entMan.Count(), Is.Zero);
@@ -150,7 +152,8 @@ void CheckDummy(int i)
}
// The game rule exists, and all the stations/shuttles/maps are properly initialized
- var rule = entMan.AllComponents().Single().Component;
+ var rule = entMan.AllComponents().Single();
+ var ruleComp = rule.Component;
var gridsRule = entMan.AllComponents().Single().Component;
foreach (var grid in gridsRule.MapGrids)
{
@@ -158,12 +161,14 @@ void CheckDummy(int i)
Assert.That(entMan.HasComponent(grid));
Assert.That(entMan.HasComponent(grid));
}
- Assert.That(entMan.EntityExists(rule.TargetStation));
+ Assert.That(entMan.EntityExists(ruleComp.TargetStation));
- Assert.That(entMan.HasComponent