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/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/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..821389150d6 100644
--- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml
@@ -5,8 +5,8 @@
-
+ Text="{Loc object-tab-object-type}" />
+
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
index c8606ca80d5..67e0db01eeb 100644
--- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
@@ -41,7 +41,7 @@ public ObjectsTab()
foreach (var type in Enum.GetValues(typeof(ObjectsTabSelection)))
{
_selections.Add((ObjectsTabSelection)type!);
- ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!);
+ ObjectTypeOptions.AddItem(Loc.GetString($"object-tab-object-type-{((Enum.GetName((ObjectsTabSelection)type))!.ToString().ToLower())}"));
}
ListHeader.OnHeaderClicked += HeaderClicked;
diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml
index 25a96df1d37..e0723d4bdc5 100644
--- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml
+++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml
@@ -4,7 +4,7 @@
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
-
+
-
-
+
+
+
+
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();
+ if (_prototypeManager.TryIndex(recipe.Result, out EntityPrototype? entityProto) && entityProto != null)
+ queuedRecipeProto.SetPrototype(entityProto);
+
+ var queuedRecipeLabel = new Label();
+ queuedRecipeLabel.Text = $"{idx}. {recipe.Name}";
+ queuedRecipeBox.AddChild(queuedRecipeProto);
+ 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/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs
index 2737eef1f1a..d029eb1223d 100644
--- a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs
+++ b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs
@@ -29,6 +29,9 @@ public LoadoutWindow(HumanoidCharacterProfile profile, RoleLoadout loadout, Role
if (!protoManager.TryIndex(group, out var groupProto))
continue;
+ if (groupProto.Hidden)
+ continue;
+
var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection);
LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
_groups.Add(container);
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 4f624c1bb69..90486a196ad 100644
--- a/Content.Client/Options/UI/OptionsMenu.xaml
+++ b/Content.Client/Options/UI/OptionsMenu.xaml
@@ -7,5 +7,6 @@
+
diff --git a/Content.Client/Options/UI/OptionsMenu.xaml.cs b/Content.Client/Options/UI/OptionsMenu.xaml.cs
index 35a3f751bbf..61037f4e4af 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,13 +16,17 @@ 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-accessibility"));
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 6490c85dfed..c374af31c58 100644
--- a/Content.Client/Options/UI/Tabs/AudioTab.xaml
+++ b/Content.Client/Options/UI/Tabs/AudioTab.xaml
@@ -1,141 +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 faa9a3d18db..78186d446c7 100644
--- a/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs
@@ -1,220 +1,74 @@
using Content.Client.Audio;
using Content.Shared.CCVar;
-using Content.Shared.Corvax.CCCVars;
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;
- // TtsVolumeSlider.OnValueChanged += OnTtsVolumeSliderChanged; // Corvax-TTS
- 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;
- // TtsVolumeSlider.OnValueChanged -= OnTtsVolumeSliderChanged; // Corvax-TTS
- 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();
- }
-
- private void OnMidiVolumeSliderChanged(Range range)
- {
- UpdateChanges();
- }
+namespace Content.Client.Options.UI.Tabs;
- // Corvax-TTS-Start
- // private void OnTtsVolumeSliderChanged(Range obj)
- // {
- // UpdateChanges();
- // }
- // Corvax-TTS-End
-
- 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(CCCVars.TTSVolume, TtsVolumeSlider.Value / 100f * ContentAudioSystem.LobbyMultiplier);
-
- _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;
-
- // TtsVolumeSlider.Value = _cfg.GetCVar(CCCVars.TTSVolume) * 100f / ContentAudioSystem.LobbyMultiplier;
- 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 isTtsVolumeSame =
- // Math.Abs(TtsVolumeSlider.Value - _cfg.GetCVar(CCCVars.TTSVolume) * 100f / ContentAudioSystem.LobbyMultiplier) < 0.01f; // Corvax-TTS
- 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;
- // isEverythingSame = isEverythingSame && isTtsVolumeSame; // Corvax-TTS
- 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));
- // TtsVolumeLabel.Text =
- // Loc.GetString("ui-options-volume-percent", ("volume", TtsVolumeSlider.Value / 100)); // Corvax-TTS
- 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/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.IntegrationTests/Tests/Buckle/BuckleDragTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleDragTest.cs
new file mode 100644
index 00000000000..8df151d5a0e
--- /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/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/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/Interaction/InteractionTest.Helpers.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
index f4826cb2495..a61a0593017 100644
--- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
+++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
@@ -84,8 +84,9 @@ protected async Task CraftItem(string prototype, bool shouldSucceed = true)
///
/// Spawn an entity entity and set it as the target.
///
- [MemberNotNull(nameof(Target))]
- protected async Task SpawnTarget(string prototype)
+ [MemberNotNull(nameof(Target), nameof(STarget), nameof(CTarget))]
+#pragma warning disable CS8774 // Member must have a non-null value when exiting.
+ protected async Task SpawnTarget(string prototype)
{
Target = NetEntity.Invalid;
await Server.WaitPost(() =>
@@ -95,7 +96,9 @@ await Server.WaitPost(() =>
await RunTicks(5);
AssertPrototype(prototype);
+ return Target!.Value;
}
+#pragma warning restore CS8774 // Member must have a non-null value when exiting.
///
/// Spawn an entity in preparation for deconstruction
@@ -1170,14 +1173,17 @@ await Server.WaitPost(() =>
#region Inputs
+
+
///
/// Make the client press and then release a key. This assumes the key is currently released.
+ /// This will default to using the entity and coordinates.
///
protected async Task PressKey(
BoundKeyFunction key,
int ticks = 1,
NetCoordinates? coordinates = null,
- NetEntity cursorEntity = default)
+ NetEntity? cursorEntity = null)
{
await SetKey(key, BoundKeyState.Down, coordinates, cursorEntity);
await RunTicks(ticks);
@@ -1186,15 +1192,17 @@ protected async Task PressKey(
}
///
- /// Make the client press or release a key
+ /// Make the client press or release a key.
+ /// This will default to using the entity and coordinates.
///
protected async Task SetKey(
BoundKeyFunction key,
BoundKeyState state,
NetCoordinates? coordinates = null,
- NetEntity cursorEntity = default)
+ NetEntity? cursorEntity = null)
{
var coords = coordinates ?? TargetCoords;
+ var target = cursorEntity ?? Target ?? default;
ScreenCoordinates screen = default;
var funcId = InputManager.NetworkBindMap.KeyFunctionID(key);
@@ -1203,7 +1211,7 @@ protected async Task SetKey(
State = state,
Coordinates = CEntMan.GetCoordinates(coords),
ScreenCoordinates = screen,
- Uid = CEntMan.GetEntity(cursorEntity),
+ Uid = CEntMan.GetEntity(target),
};
await Client.WaitPost(() => InputSystem.HandleInputCommand(ClientSession, key, message));
diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
index 089addfaefd..37102481ed0 100644
--- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
+++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
@@ -84,6 +84,7 @@ public abstract partial class InteractionTest
protected NetEntity? Target;
protected EntityUid? STarget => ToServer(Target);
+
protected EntityUid? CTarget => ToClient(Target);
///
@@ -128,7 +129,6 @@ public abstract partial class InteractionTest
public float TickPeriod => (float) STiming.TickPeriod.TotalSeconds;
-
// Simple mob that has one hand and can perform misc interactions.
[TestPrototypes]
private const string TestPrototypes = @"
@@ -142,6 +142,8 @@ public abstract partial class InteractionTest
- type: ComplexInteraction
- type: MindContainer
- type: Stripping
+ - type: Puller
+ - type: Physics
- type: Tag
tags:
- CanPilot
@@ -207,8 +209,8 @@ await Server.WaitPost(() =>
SEntMan.System().WipeMind(ServerSession.ContentData()?.Mind);
old = cPlayerMan.LocalEntity;
- Player = SEntMan.GetNetEntity(SEntMan.SpawnEntity(PlayerPrototype, SEntMan.GetCoordinates(PlayerCoords)));
- SPlayer = SEntMan.GetEntity(Player);
+ SPlayer = SEntMan.SpawnEntity(PlayerPrototype, SEntMan.GetCoordinates(PlayerCoords));
+ Player = SEntMan.GetNetEntity(SPlayer);
Server.PlayerMan.SetAttachedEntity(ServerSession, SPlayer);
Hands = SEntMan.GetComponent(SPlayer);
DoAfters = SEntMan.GetComponent(SPlayer);
diff --git a/Content.IntegrationTests/Tests/Movement/BuckleMovementTest.cs b/Content.IntegrationTests/Tests/Movement/BuckleMovementTest.cs
new file mode 100644
index 00000000000..8d91855098f
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Movement/BuckleMovementTest.cs
@@ -0,0 +1,63 @@
+using Content.Shared.Alert;
+using Content.Shared.Buckle.Components;
+using Robust.Shared.Maths;
+
+namespace Content.IntegrationTests.Tests.Movement;
+
+public sealed class BuckleMovementTest : MovementTest
+{
+ // Check that interacting with a chair straps you to it and prevents movement.
+ [Test]
+ public async Task ChairTest()
+ {
+ await SpawnTarget("Chair");
+
+ var cAlert = Client.System();
+ var sAlert = Server.System();
+ var buckle = Comp(Player);
+ var strap = Comp(Target);
+
+#pragma warning disable RA0002
+ buckle.Delay = TimeSpan.Zero;
+#pragma warning restore RA0002
+
+ // Initially not buckled to the chair, and standing off to the side
+ Assert.That(Delta(), Is.InRange(0.9f, 1.1f));
+ Assert.That(buckle.Buckled, Is.False);
+ Assert.That(buckle.BuckledTo, Is.Null);
+ Assert.That(strap.BuckledEntities, Is.Empty);
+ Assert.That(cAlert.IsShowingAlert(CPlayer, strap.BuckledAlertType), Is.False);
+ Assert.That(sAlert.IsShowingAlert(SPlayer, strap.BuckledAlertType), Is.False);
+
+ // Interact results in being buckled to the chair
+ await Interact();
+ Assert.That(Delta(), Is.InRange(-0.01f, 0.01f));
+ Assert.That(buckle.Buckled, Is.True);
+ Assert.That(buckle.BuckledTo, Is.EqualTo(STarget));
+ Assert.That(strap.BuckledEntities, Is.EquivalentTo(new[]{SPlayer}));
+ Assert.That(cAlert.IsShowingAlert(CPlayer, strap.BuckledAlertType), Is.True);
+ Assert.That(sAlert.IsShowingAlert(SPlayer, strap.BuckledAlertType), Is.True);
+
+ // Attempting to walk away does nothing
+ await Move(DirectionFlag.East, 1);
+ Assert.That(Delta(), Is.InRange(-0.01f, 0.01f));
+ Assert.That(buckle.Buckled, Is.True);
+ Assert.That(buckle.BuckledTo, Is.EqualTo(STarget));
+ Assert.That(strap.BuckledEntities, Is.EquivalentTo(new[]{SPlayer}));
+ Assert.That(cAlert.IsShowingAlert(CPlayer, strap.BuckledAlertType), Is.True);
+ Assert.That(sAlert.IsShowingAlert(SPlayer, strap.BuckledAlertType), Is.True);
+
+ // Interacting again will unbuckle the player
+ await Interact();
+ Assert.That(Delta(), Is.InRange(-0.5f, 0.5f));
+ Assert.That(buckle.Buckled, Is.False);
+ Assert.That(buckle.BuckledTo, Is.Null);
+ Assert.That(strap.BuckledEntities, Is.Empty);
+ Assert.That(cAlert.IsShowingAlert(CPlayer, strap.BuckledAlertType), Is.False);
+ Assert.That(sAlert.IsShowingAlert(SPlayer, strap.BuckledAlertType), Is.False);
+
+ // And now they can move away
+ await Move(DirectionFlag.SouthEast, 1);
+ Assert.That(Delta(), Is.LessThan(-1));
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Interaction/MovementTest.cs b/Content.IntegrationTests/Tests/Movement/MovementTest.cs
similarity index 95%
rename from Content.IntegrationTests/Tests/Interaction/MovementTest.cs
rename to Content.IntegrationTests/Tests/Movement/MovementTest.cs
index dc5aec92cfc..ad7b1d0459f 100644
--- a/Content.IntegrationTests/Tests/Interaction/MovementTest.cs
+++ b/Content.IntegrationTests/Tests/Movement/MovementTest.cs
@@ -1,8 +1,9 @@
#nullable enable
using System.Numerics;
+using Content.IntegrationTests.Tests.Interaction;
using Robust.Shared.GameObjects;
-namespace Content.IntegrationTests.Tests.Interaction;
+namespace Content.IntegrationTests.Tests.Movement;
///
/// This is a variation of that sets up the player with a normal human entity and a simple
diff --git a/Content.IntegrationTests/Tests/Movement/PullingTest.cs b/Content.IntegrationTests/Tests/Movement/PullingTest.cs
new file mode 100644
index 00000000000..d96c4ea0e56
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Movement/PullingTest.cs
@@ -0,0 +1,73 @@
+#nullable enable
+using Content.Shared.Alert;
+using Content.Shared.Input;
+using Content.Shared.Movement.Pulling.Components;
+using Robust.Shared.Maths;
+
+namespace Content.IntegrationTests.Tests.Movement;
+
+public sealed class PullingTest : MovementTest
+{
+ protected override int Tiles => 4;
+
+ [Test]
+ public async Task PullTest()
+ {
+ var cAlert = Client.System();
+ var sAlert = Server.System();
+ await SpawnTarget("MobHuman");
+
+ var puller = Comp(Player);
+ var pullable = Comp(Target);
+
+ // Player is initially to the left of the target and not pulling anything
+ Assert.That(Delta(), Is.InRange(0.9f, 1.1f));
+ Assert.That(puller.Pulling, Is.Null);
+ Assert.That(pullable.Puller, Is.Null);
+ Assert.That(pullable.BeingPulled, Is.False);
+ Assert.That(cAlert.IsShowingAlert(CPlayer, puller.PullingAlert), Is.False);
+ Assert.That(sAlert.IsShowingAlert(SPlayer, puller.PullingAlert), Is.False);
+
+ // Start pulling
+ await PressKey(ContentKeyFunctions.TryPullObject);
+ await RunTicks(5);
+ Assert.That(puller.Pulling, Is.EqualTo(STarget));
+ Assert.That(pullable.Puller, Is.EqualTo(SPlayer));
+ Assert.That(pullable.BeingPulled, Is.True);
+ Assert.That(cAlert.IsShowingAlert(CPlayer, puller.PullingAlert), Is.True);
+ Assert.That(sAlert.IsShowingAlert(SPlayer, puller.PullingAlert), Is.True);
+
+ // Move to the left and check that the target moves with the player and is still being pulled.
+ await Move(DirectionFlag.West, 1);
+ Assert.That(Delta(), Is.InRange(0.9f, 1.3f));
+ Assert.That(puller.Pulling, Is.EqualTo(STarget));
+ Assert.That(pullable.Puller, Is.EqualTo(SPlayer));
+ Assert.That(pullable.BeingPulled, Is.True);
+ Assert.That(cAlert.IsShowingAlert(CPlayer, puller.PullingAlert), Is.True);
+ Assert.That(sAlert.IsShowingAlert(SPlayer, puller.PullingAlert), Is.True);
+
+ // Move in the other direction
+ await Move(DirectionFlag.East, 2);
+ Assert.That(Delta(), Is.InRange(-1.3f, -0.9f));
+ Assert.That(puller.Pulling, Is.EqualTo(STarget));
+ Assert.That(pullable.Puller, Is.EqualTo(SPlayer));
+ Assert.That(pullable.BeingPulled, Is.True);
+ Assert.That(cAlert.IsShowingAlert(CPlayer, puller.PullingAlert), Is.True);
+ Assert.That(sAlert.IsShowingAlert(SPlayer, puller.PullingAlert), Is.True);
+
+ // Stop pulling
+ await PressKey(ContentKeyFunctions.ReleasePulledObject);
+ await RunTicks(5);
+ Assert.That(Delta(), Is.InRange(-1.3f, -0.9f));
+ Assert.That(puller.Pulling, Is.Null);
+ Assert.That(pullable.Puller, Is.Null);
+ Assert.That(pullable.BeingPulled, Is.False);
+ Assert.That(cAlert.IsShowingAlert(CPlayer, puller.PullingAlert), Is.False);
+ Assert.That(sAlert.IsShowingAlert(SPlayer, puller.PullingAlert), Is.False);
+
+ // Move back to the left and ensure the target is no longer following us.
+ await Move(DirectionFlag.West, 2);
+ Assert.That(Delta(), Is.GreaterThan(2f));
+ }
+}
+
diff --git a/Content.IntegrationTests/Tests/Slipping/SlippingTest.cs b/Content.IntegrationTests/Tests/Movement/SlippingTest.cs
similarity index 91%
rename from Content.IntegrationTests/Tests/Slipping/SlippingTest.cs
rename to Content.IntegrationTests/Tests/Movement/SlippingTest.cs
index 7f77146f455..7ee895d7c27 100644
--- a/Content.IntegrationTests/Tests/Slipping/SlippingTest.cs
+++ b/Content.IntegrationTests/Tests/Movement/SlippingTest.cs
@@ -8,7 +8,7 @@
using Robust.Shared.Input;
using Robust.Shared.Maths;
-namespace Content.IntegrationTests.Tests.Slipping;
+namespace Content.IntegrationTests.Tests.Movement;
public sealed class SlippingTest : MovementTest
{
@@ -36,18 +36,14 @@ public async Task BananaSlipTest()
Assert.That(modifier, Is.EqualTo(1), "Player is not moving at full speed.");
// Player is to the left of the banana peel and has not slipped.
-#pragma warning disable NUnit2045
Assert.That(Delta(), Is.GreaterThan(0.5f));
Assert.That(sys.Slipped, Does.Not.Contain(SEntMan.GetEntity(Player)));
-#pragma warning restore NUnit2045
// Walking over the banana slowly does not trigger a slip.
await SetKey(EngineKeyFunctions.Walk, BoundKeyState.Down);
await Move(DirectionFlag.East, 1f);
-#pragma warning disable NUnit2045
Assert.That(Delta(), Is.LessThan(0.5f));
Assert.That(sys.Slipped, Does.Not.Contain(SEntMan.GetEntity(Player)));
-#pragma warning restore NUnit2045
AssertComp(false, Player);
// Moving at normal speeds does trigger a slip.
diff --git a/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs b/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs
index 72e35dac057..a66cecc90f9 100644
--- a/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs
+++ b/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs
@@ -1,15 +1,52 @@
+using System.Collections.Generic;
using Content.Server.Station.Systems;
+using Content.Shared.Inventory;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles.Jobs;
using Robust.Shared.GameObjects;
+using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Preferences;
[TestFixture]
-[Ignore("HumanoidAppearance crashes upon loading default profiles.")]
public sealed class LoadoutTests
{
+ [TestPrototypes]
+ private const string Prototypes = @"
+- type: playTimeTracker
+ id: PlayTimeLoadoutTester
+
+- type: loadout
+ id: TestJumpsuit
+ equipment: TestJumpsuit
+
+- type: startingGear
+ id: TestJumpsuit
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitColorGrey
+
+- type: loadoutGroup
+ id: LoadoutTesterJumpsuit
+ name: generic-unknown
+ loadouts:
+ - TestJumpsuit
+
+- type: roleLoadout
+ id: JobLoadoutTester
+ groups:
+ - LoadoutTesterJumpsuit
+
+- type: job
+ id: LoadoutTester
+ playTimeTracker: PlayTimeLoadoutTester
+";
+
+ private readonly Dictionary _expectedEquipment = new()
+ {
+ ["jumpsuit"] = "ClothingUniformJumpsuitColorGrey"
+ };
+
///
/// Checks that an empty loadout still spawns with default gear and not naked.
///
@@ -26,18 +63,38 @@ public async Task TestEmptyLoadout()
// Check that an empty role loadout spawns gear
var stationSystem = entManager.System();
+ var inventorySystem = entManager.System();
var testMap = await pair.CreateTestMap();
- // That's right I can't even spawn a dummy profile without station spawning / humanoidappearance code crashing.
- var profile = new HumanoidCharacterProfile();
+ await server.WaitAssertion(() =>
+ {
+ var profile = new HumanoidCharacterProfile();
- profile.SetLoadout(new RoleLoadout("TestRoleLoadout"));
+ profile.SetLoadout(new RoleLoadout("LoadoutTester"));
- stationSystem.SpawnPlayerMob(testMap.GridCoords, job: new JobComponent()
- {
- // Sue me, there's so much involved in setting up jobs
- Prototype = "CargoTechnician"
- }, profile, station: null);
+ var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: new JobComponent()
+ {
+ Prototype = "LoadoutTester"
+ }, profile, station: null);
+
+ var slotQuery = inventorySystem.GetSlotEnumerator(tester);
+ var checkedCount = 0;
+ while (slotQuery.NextItem(out var item, out var slot))
+ {
+ // Make sure the slot is valid
+ Assert.That(_expectedEquipment.TryGetValue(slot.Name, out var expectedItem), $"Spawned item in unexpected slot: {slot.Name}");
+
+ // Make sure that the item is the right one
+ var meta = entManager.GetComponent(item);
+ Assert.That(meta.EntityPrototype.ID, Is.EqualTo(expectedItem.Id), $"Spawned wrong item in slot {slot.Name}!");
+
+ checkedCount++;
+ }
+ // Make sure the number of items is the same
+ Assert.That(checkedCount, Is.EqualTo(_expectedEquipment.Count), "Number of items does not match expected!");
+
+ entManager.DeleteEntity(tester);
+ });
await pair.CleanReturnAsync();
}
diff --git a/Content.Server.Database/Migrations/Postgres/20240621120713_ConnectionLogTimeIndex.Designer.cs b/Content.Server.Database/Migrations/Postgres/20240621120713_ConnectionLogTimeIndex.Designer.cs
new file mode 100644
index 00000000000..8bd837236af
--- /dev/null
+++ b/Content.Server.Database/Migrations/Postgres/20240621120713_ConnectionLogTimeIndex.Designer.cs
@@ -0,0 +1,1915 @@
+//
+using System;
+using System.Net;
+using System.Text.Json;
+using Content.Server.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using NpgsqlTypes;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Postgres
+{
+ [DbContext(typeof(PostgresServerDbContext))]
+ [Migration("20240621120713_ConnectionLogTimeIndex")]
+ partial class ConnectionLogTimeIndex
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Property("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property("Title")
+ .HasColumnType("text")
+ .HasColumnName("title");
+
+ b.HasKey("UserId")
+ .HasName("PK_admin");
+
+ b.HasIndex("AdminRankId")
+ .HasDatabaseName("IX_admin_admin_rank_id");
+
+ b.ToTable("admin", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AdminId")
+ .HasColumnType("uuid")
+ .HasColumnName("admin_id");
+
+ b.Property("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.Property("Negative")
+ .HasColumnType("boolean")
+ .HasColumnName("negative");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_flag");
+
+ b.HasIndex("AdminId")
+ .HasDatabaseName("IX_admin_flag_admin_id");
+
+ b.HasIndex("Flag", "AdminId")
+ .IsUnique();
+
+ b.ToTable("admin_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property("Id")
+ .HasColumnType("integer")
+ .HasColumnName("admin_log_id");
+
+ b.Property("Date")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("date");
+
+ b.Property("Impact")
+ .HasColumnType("smallint")
+ .HasColumnName("impact");
+
+ b.Property("Json")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("json");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("message");
+
+ b.Property("Type")
+ .HasColumnType("integer")
+ .HasColumnName("type");
+
+ b.HasKey("RoundId", "Id")
+ .HasName("PK_admin_log");
+
+ b.HasIndex("Date");
+
+ b.HasIndex("Message")
+ .HasAnnotation("Npgsql:TsVectorConfig", "english");
+
+ NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_admin_log_type");
+
+ b.ToTable("admin_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property("LogId")
+ .HasColumnType("integer")
+ .HasColumnName("log_id");
+
+ b.Property("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.HasKey("RoundId", "LogId", "PlayerUserId")
+ .HasName("PK_admin_log_player");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_log_player_player_user_id");
+
+ b.ToTable("admin_log_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_messages_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property("Dismissed")
+ .HasColumnType("boolean")
+ .HasColumnName("dismissed");
+
+ b.Property("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property("Seen")
+ .HasColumnType("boolean")
+ .HasColumnName("seen");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_messages");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_messages_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_messages_round_id");
+
+ b.ToTable("admin_messages", null, t =>
+ {
+ t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_notes_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property("Secret")
+ .HasColumnType("boolean")
+ .HasColumnName("secret");
+
+ b.Property("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_notes");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_notes_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_notes_round_id");
+
+ b.ToTable("admin_notes", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank");
+
+ b.ToTable("admin_rank", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank_flag");
+
+ b.HasIndex("AdminRankId");
+
+ b.HasIndex("Flag", "AdminRankId")
+ .IsUnique();
+
+ b.ToTable("admin_rank_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_watchlists_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_watchlists");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_watchlists_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_watchlists_round_id");
+
+ b.ToTable("admin_watchlists", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("antag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AntagName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("antag_name");
+
+ b.Property("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_antag");
+
+ b.HasIndex("ProfileId", "AntagName")
+ .IsUnique();
+
+ b.ToTable("antag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AssignedUserId", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("assigned_user_id_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property("UserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_assigned_user_id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.HasIndex("UserName")
+ .IsUnique();
+
+ b.ToTable("assigned_user_id", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("connection_log_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Address")
+ .IsRequired()
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property("Denied")
+ .HasColumnType("smallint")
+ .HasColumnName("denied");
+
+ b.Property("HWId")
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b.Property("ServerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(0)
+ .HasColumnName("server_id");
+
+ b.Property("Time")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("time");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property("UserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_connection_log");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_connection_log_server_id");
+
+ b.HasIndex("Time");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("connection_log", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("job_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("JobName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("job_name");
+
+ b.Property("Priority")
+ .HasColumnType("integer")
+ .HasColumnName("priority");
+
+ b.Property("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_job");
+
+ b.HasIndex("ProfileId");
+
+ b.HasIndex("ProfileId", "JobName")
+ .IsUnique();
+
+ b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority")
+ .IsUnique()
+ .HasFilter("priority = 3");
+
+ b.ToTable("job", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.PlayTime", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("play_time_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("PlayerId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_id");
+
+ b.Property("TimeSpent")
+ .HasColumnType("interval")
+ .HasColumnName("time_spent");
+
+ b.Property("Tracker")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("tracker");
+
+ b.HasKey("Id")
+ .HasName("PK_play_time");
+
+ b.HasIndex("PlayerId", "Tracker")
+ .IsUnique();
+
+ b.ToTable("play_time", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("player_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("FirstSeenTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("first_seen_time");
+
+ b.Property("LastReadRules")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_read_rules");
+
+ b.Property("LastSeenAddress")
+ .IsRequired()
+ .HasColumnType("inet")
+ .HasColumnName("last_seen_address");
+
+ b.Property("LastSeenHWId")
+ .HasColumnType("bytea")
+ .HasColumnName("last_seen_hwid");
+
+ b.Property("LastSeenTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_seen_time");
+
+ b.Property("LastSeenUserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("last_seen_user_name");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_player");
+
+ b.HasAlternateKey("UserId")
+ .HasName("ak_player_user_id");
+
+ b.HasIndex("LastSeenUserName");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("player", null, t =>
+ {
+ t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("preference_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AdminOOCColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("admin_ooc_color");
+
+ b.Property("SelectedCharacterSlot")
+ .HasColumnType("integer")
+ .HasColumnName("selected_character_slot");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_preference");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("preference", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Age")
+ .HasColumnType("integer")
+ .HasColumnName("age");
+
+ b.Property("CharacterName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("char_name");
+
+ b.Property("EyeColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("eye_color");
+
+ b.Property("FacialHairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("facial_hair_color");
+
+ b.Property("FacialHairName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("facial_hair_name");
+
+ b.Property("FlavorText")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flavor_text");
+
+ b.Property("Gender")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("gender");
+
+ b.Property("HairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("hair_color");
+
+ b.Property("HairName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("hair_name");
+
+ b.Property("Markings")
+ .HasColumnType("jsonb")
+ .HasColumnName("markings");
+
+ b.Property("PreferenceId")
+ .HasColumnType("integer")
+ .HasColumnName("preference_id");
+
+ b.Property("PreferenceUnavailable")
+ .HasColumnType("integer")
+ .HasColumnName("pref_unavailable");
+
+ b.Property("Sex")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("sex");
+
+ b.Property("SkinColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("skin_color");
+
+ b.Property("Slot")
+ .HasColumnType("integer")
+ .HasColumnName("slot");
+
+ b.Property("SpawnPriority")
+ .HasColumnType("integer")
+ .HasColumnName("spawn_priority");
+
+ b.Property("Species")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("species");
+
+ b.HasKey("Id")
+ .HasName("PK_profile");
+
+ b.HasIndex("PreferenceId")
+ .HasDatabaseName("IX_profile_preference_id");
+
+ b.HasIndex("Slot", "PreferenceId")
+ .IsUnique();
+
+ b.ToTable("profile", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("LoadoutName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("loadout_name");
+
+ b.Property("ProfileLoadoutGroupId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_group_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout");
+
+ b.HasIndex("ProfileLoadoutGroupId");
+
+ b.ToTable("profile_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_group_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("GroupName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("group_name");
+
+ b.Property("ProfileRoleLoadoutId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout_group");
+
+ b.HasIndex("ProfileRoleLoadoutId");
+
+ b.ToTable("profile_loadout_group", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_role_loadout_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.Property("RoleName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("role_name");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_role_loadout");
+
+ b.HasIndex("ProfileId");
+
+ b.ToTable("profile_role_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b =>
+ {
+ b.Property("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property("RoleId")
+ .HasColumnType("text")
+ .HasColumnName("role_id");
+
+ b.HasKey("PlayerUserId", "RoleId")
+ .HasName("PK_role_whitelists");
+
+ b.ToTable("role_whitelists", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ServerId")
+ .HasColumnType("integer")
+ .HasColumnName("server_id");
+
+ b.Property("StartDate")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("start_date");
+
+ b.HasKey("Id")
+ .HasName("PK_round");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_round_server_id");
+
+ b.HasIndex("StartDate");
+
+ b.ToTable("round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_server");
+
+ b.ToTable("server", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_ban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Address")
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property("AutoDelete")
+ .HasColumnType("boolean")
+ .HasColumnName("auto_delete");
+
+ b.Property("BanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("ban_time");
+
+ b.Property("BanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("banning_admin");
+
+ b.Property("ExemptFlags")
+ .HasColumnType("integer")
+ .HasColumnName("exempt_flags");
+
+ b.Property("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property("HWId")
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b.Property("Hidden")
+ .HasColumnType("boolean")
+ .HasColumnName("hidden");
+
+ b.Property("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property("Reason")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("reason");
+
+ b.Property