diff --git a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs
index de51b2fb192..8512107b69d 100644
--- a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs
+++ b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs
@@ -58,7 +58,7 @@ await _pair.Server.WaitPost(() =>
for (var i = 0; i < N; i++)
{
_entity = server.EntMan.SpawnAttachedTo(Mob, _coords);
- _spawnSys.EquipStartingGear(_entity, _gear, null);
+ _spawnSys.EquipStartingGear(_entity, _gear);
server.EntMan.DeleteEntity(_entity);
}
});
diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj
index 956f2fd0351..02fefa5cf20 100644
--- a/Content.Client/Content.Client.csproj
+++ b/Content.Client/Content.Client.csproj
@@ -26,6 +26,9 @@
+
+
+
diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs
index 70fe1916584..65e95b76f08 100644
--- a/Content.Client/IoC/ClientContentIoC.cs
+++ b/Content.Client/IoC/ClientContentIoC.cs
@@ -21,6 +21,7 @@
using Content.Client.Guidebook;
using Content.Client.Replay;
using Content.Shared.Administration.Managers;
+using Content.Shared.Players.PlayTimeTracking;
namespace Content.Client.IoC
@@ -29,26 +30,29 @@ internal static class ClientContentIoC
{
public static void Register()
{
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
+ var collection = IoCManager.Instance!;
+
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
}
}
}
diff --git a/Content.Client/Lobby/LobbyState.cs b/Content.Client/Lobby/LobbyState.cs
index fe31dce0629..91730020a4e 100644
--- a/Content.Client/Lobby/LobbyState.cs
+++ b/Content.Client/Lobby/LobbyState.cs
@@ -64,13 +64,19 @@ protected override void Startup()
_characterSetup.CloseButton.OnPressed += _ =>
{
+ // Reset sliders etc.
+ _characterSetup?.UpdateControls();
+
+ var controller = _userInterfaceManager.GetUIController();
+ controller.SetClothes(true);
+ controller.UpdateProfile();
_lobby.SwitchState(LobbyGui.LobbyGuiState.Default);
};
_characterSetup.SaveButton.OnPressed += _ =>
{
_characterSetup.Save();
- _lobby.CharacterPreview.UpdateUI();
+ _userInterfaceManager.GetUIController().ReloadProfile();
};
LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide);
@@ -84,10 +90,6 @@ protected override void Startup()
_gameTicker.InfoBlobUpdated += UpdateLobbyUi;
_gameTicker.LobbyStatusUpdated += LobbyStatusUpdated;
_gameTicker.LobbyLateJoinStatusUpdated += LobbyLateJoinStatusUpdated;
-
- _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
-
- _lobby.CharacterPreview.UpdateUI();
}
protected override void Shutdown()
@@ -109,13 +111,6 @@ protected override void Shutdown()
_characterSetup?.Dispose();
_characterSetup = null;
-
- _preferencesManager.OnServerDataLoaded -= PreferencesDataLoaded;
- }
-
- private void PreferencesDataLoaded()
- {
- _lobby?.CharacterPreview.UpdateUI();
}
private void OnSetupPressed(BaseButton.ButtonEventArgs args)
diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs
new file mode 100644
index 00000000000..9eb259657dc
--- /dev/null
+++ b/Content.Client/Lobby/LobbyUIController.cs
@@ -0,0 +1,286 @@
+using System.Linq;
+using Content.Client.Humanoid;
+using Content.Client.Inventory;
+using Content.Client.Lobby.UI;
+using Content.Client.Preferences;
+using Content.Client.Preferences.UI;
+using Content.Client.Station;
+using Content.Shared.Clothing;
+using Content.Shared.GameTicking;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
+using Content.Shared.Roles;
+using Robust.Client.State;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controllers;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Lobby;
+
+public sealed class LobbyUIController : UIController, IOnStateEntered, IOnStateExited
+{
+ [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
+ [Dependency] private readonly IStateManager _stateManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
+ [UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
+ [UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
+
+ private LobbyCharacterPreviewPanel? _previewPanel;
+
+ private bool _showClothes = true;
+
+ /*
+ * Each character profile has its own dummy. There is also a dummy for the lobby screen + character editor
+ * that is shared too.
+ */
+
+ ///
+ /// Preview dummy for role gear.
+ ///
+ private EntityUid? _previewDummy;
+
+ ///
+ /// If we currently have a job prototype selected.
+ ///
+ private JobPrototype? _dummyJob;
+
+ // TODO: Load the species directly and don't update entity ever.
+ public event Action? PreviewDummyUpdated;
+
+ private HumanoidCharacterProfile? _profile;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
+ }
+
+ private void PreferencesDataLoaded()
+ {
+ UpdateProfile();
+ }
+
+ public void OnStateEntered(LobbyState state)
+ {
+ }
+
+ public void OnStateExited(LobbyState state)
+ {
+ EntityManager.DeleteEntity(_previewDummy);
+ _previewDummy = null;
+ }
+
+ public void SetPreviewPanel(LobbyCharacterPreviewPanel? panel)
+ {
+ _previewPanel = panel;
+ ReloadProfile();
+ }
+
+ public void SetClothes(bool value)
+ {
+ if (_showClothes == value)
+ return;
+
+ _showClothes = value;
+ ReloadCharacterUI();
+ }
+
+ public void SetDummyJob(JobPrototype? job)
+ {
+ _dummyJob = job;
+ ReloadCharacterUI();
+ }
+
+ ///
+ /// Updates the character only with the specified profile change.
+ ///
+ public void ReloadProfile()
+ {
+ // Test moment
+ if (_profile == null || _stateManager.CurrentState is not LobbyState)
+ return;
+
+ // Ignore job clothes and the likes so we don't spam entities out every frame of color changes.
+ var previewDummy = EnsurePreviewDummy(_profile);
+ _humanoid.LoadProfile(previewDummy, _profile);
+ }
+
+ ///
+ /// Updates the currently selected character's preview.
+ ///
+ public void ReloadCharacterUI()
+ {
+ // Test moment
+ if (_profile == null || _stateManager.CurrentState is not LobbyState)
+ return;
+
+ EntityManager.DeleteEntity(_previewDummy);
+ _previewDummy = null;
+ _previewDummy = EnsurePreviewDummy(_profile);
+ _previewPanel?.SetSprite(_previewDummy.Value);
+ _previewPanel?.SetSummaryText(_profile.Summary);
+ _humanoid.LoadProfile(_previewDummy.Value, _profile);
+
+ if (_showClothes)
+ GiveDummyJobClothesLoadout(_previewDummy.Value, _profile);
+ }
+
+ ///
+ /// Updates character profile to the default.
+ ///
+ public void UpdateProfile()
+ {
+ if (!_preferencesManager.ServerDataLoaded)
+ {
+ _profile = null;
+ return;
+ }
+
+ if (_preferencesManager.Preferences?.SelectedCharacter is HumanoidCharacterProfile selectedCharacter)
+ {
+ _profile = selectedCharacter;
+ _previewPanel?.SetLoaded(true);
+ }
+ else
+ {
+ _previewPanel?.SetSummaryText(string.Empty);
+ _previewPanel?.SetLoaded(false);
+ }
+
+ ReloadCharacterUI();
+ }
+
+ public void UpdateProfile(HumanoidCharacterProfile? profile)
+ {
+ if (_profile?.Equals(profile) == true)
+ return;
+
+ if (_stateManager.CurrentState is not LobbyState)
+ return;
+
+ _profile = profile;
+ }
+
+ private EntityUid EnsurePreviewDummy(HumanoidCharacterProfile profile)
+ {
+ if (_previewDummy != null)
+ return _previewDummy.Value;
+
+ _previewDummy = EntityManager.SpawnEntity(_prototypeManager.Index(profile.Species).DollPrototype, MapCoordinates.Nullspace);
+ PreviewDummyUpdated?.Invoke(_previewDummy.Value);
+ return _previewDummy.Value;
+ }
+
+ ///
+ /// Applies the highest priority job's clothes to the dummy.
+ ///
+ public void GiveDummyJobClothesLoadout(EntityUid dummy, HumanoidCharacterProfile profile)
+ {
+ var job = _dummyJob ?? GetPreferredJob(profile);
+ GiveDummyJobClothes(dummy, profile, job);
+
+ if (_prototypeManager.HasIndex(LoadoutSystem.GetJobPrototype(job.ID)))
+ {
+ var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), EntityManager, _prototypeManager);
+ GiveDummyLoadout(dummy, loadout);
+ }
+ }
+
+ ///
+ /// Gets the highest priority job for the profile.
+ ///
+ public JobPrototype GetPreferredJob(HumanoidCharacterProfile profile)
+ {
+ var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
+ // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
+ return _prototypeManager.Index(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
+ }
+
+ public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout)
+ {
+ if (roleLoadout == null)
+ return;
+
+ foreach (var group in roleLoadout.SelectedLoadouts.Values)
+ {
+ foreach (var loadout in group)
+ {
+ if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
+ continue;
+
+ _spawn.EquipStartingGear(uid, _prototypeManager.Index(loadoutProto.Equipment));
+ }
+ }
+ }
+
+ ///
+ /// Applies the specified job's clothes to the dummy.
+ ///
+ public void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profile, JobPrototype job)
+ {
+ if (!_inventory.TryGetSlots(dummy, out var slots))
+ return;
+
+ // Apply loadout
+ if (profile.Loadouts.TryGetValue(job.ID, out var jobLoadout))
+ {
+ foreach (var loadouts in jobLoadout.SelectedLoadouts.Values)
+ {
+ foreach (var loadout in loadouts)
+ {
+ if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
+ continue;
+
+ // TODO: Need some way to apply starting gear to an entity coz holy fucking shit dude.
+ var loadoutGear = _prototypeManager.Index(loadoutProto.Equipment);
+
+ foreach (var slot in slots)
+ {
+ var itemType = loadoutGear.GetGear(slot.Name);
+
+ if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
+ {
+ EntityManager.DeleteEntity(unequippedItem.Value);
+ }
+
+ if (itemType != string.Empty)
+ {
+ var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
+ _inventory.TryEquip(dummy, item, slot.Name, true, true);
+ }
+ }
+ }
+ }
+ }
+
+ if (job.StartingGear == null)
+ return;
+
+ var gear = _prototypeManager.Index(job.StartingGear);
+
+ foreach (var slot in slots)
+ {
+ var itemType = gear.GetGear(slot.Name);
+
+ if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
+ {
+ EntityManager.DeleteEntity(unequippedItem.Value);
+ }
+
+ if (itemType != string.Empty)
+ {
+ var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
+ _inventory.TryEquip(dummy, item, slot.Name, true, true);
+ }
+ }
+ }
+
+ public EntityUid? GetPreviewDummy()
+ {
+ return _previewDummy;
+ }
+}
diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs
deleted file mode 100644
index f9481caa3bb..00000000000
--- a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs
+++ /dev/null
@@ -1,166 +0,0 @@
-using System.Linq;
-using System.Numerics;
-using Content.Client.Alerts;
-using Content.Client.Humanoid;
-using Content.Client.Inventory;
-using Content.Client.Preferences;
-using Content.Client.UserInterface.Controls;
-using Content.Shared.GameTicking;
-using Content.Shared.Humanoid.Prototypes;
-using Content.Shared.Inventory;
-using Content.Shared.Preferences;
-using Content.Shared.Roles;
-using Robust.Client.GameObjects;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Map;
-using Robust.Shared.Prototypes;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
-
-namespace Content.Client.Lobby.UI
-{
- public sealed class LobbyCharacterPreviewPanel : Control
- {
- [Dependency] private readonly IEntityManager _entityManager = default!;
- [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
-
-
- private EntityUid? _previewDummy;
- private readonly Label _summaryLabel;
- private readonly BoxContainer _loaded;
- private readonly BoxContainer _viewBox;
- private readonly Label _unloaded;
-
- public LobbyCharacterPreviewPanel()
- {
- IoCManager.InjectDependencies(this);
- var header = new NanoHeading
- {
- Text = Loc.GetString("lobby-character-preview-panel-header")
- };
-
- CharacterSetupButton = new Button
- {
- Text = Loc.GetString("lobby-character-preview-panel-character-setup-button"),
- HorizontalAlignment = HAlignment.Center,
- Margin = new Thickness(0, 5, 0, 0),
- };
-
- _summaryLabel = new Label
- {
- HorizontalAlignment = HAlignment.Center,
- Margin = new Thickness(3, 3),
- };
-
- var vBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical
- };
- _unloaded = new Label { Text = Loc.GetString("lobby-character-preview-panel-unloaded-preferences-label") };
-
- _loaded = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- Visible = false
- };
- _viewBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- HorizontalAlignment = HAlignment.Center,
- };
- var _vSpacer = new VSpacer();
-
- _loaded.AddChild(_summaryLabel);
- _loaded.AddChild(_viewBox);
- _loaded.AddChild(_vSpacer);
- _loaded.AddChild(CharacterSetupButton);
-
- vBox.AddChild(header);
- vBox.AddChild(_loaded);
- vBox.AddChild(_unloaded);
- AddChild(vBox);
-
- UpdateUI();
- }
-
- public Button CharacterSetupButton { get; }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (!disposing) return;
- if (_previewDummy != null) _entityManager.DeleteEntity(_previewDummy.Value);
- _previewDummy = default;
- }
-
- public void UpdateUI()
- {
- if (!_preferencesManager.ServerDataLoaded)
- {
- _loaded.Visible = false;
- _unloaded.Visible = true;
- }
- else
- {
- _loaded.Visible = true;
- _unloaded.Visible = false;
- if (_preferencesManager.Preferences?.SelectedCharacter is not HumanoidCharacterProfile selectedCharacter)
- {
- _summaryLabel.Text = string.Empty;
- }
- else
- {
- _previewDummy = _entityManager.SpawnEntity(_prototypeManager.Index(selectedCharacter.Species).DollPrototype, MapCoordinates.Nullspace);
- _viewBox.DisposeAllChildren();
- var spriteView = new SpriteView
- {
- OverrideDirection = Direction.South,
- Scale = new Vector2(4f, 4f),
- MaxSize = new Vector2(112, 112),
- Stretch = SpriteView.StretchMode.Fill,
- };
- spriteView.SetEntity(_previewDummy.Value);
- _viewBox.AddChild(spriteView);
- _summaryLabel.Text = selectedCharacter.Summary;
- _entityManager.System().LoadProfile(_previewDummy.Value, selectedCharacter);
- GiveDummyJobClothes(_previewDummy.Value, selectedCharacter);
- }
- }
- }
-
- public static void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profile)
- {
- var protoMan = IoCManager.Resolve();
- var entMan = IoCManager.Resolve();
- var invSystem = EntitySystem.Get();
-
- var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
-
- // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
- var job = protoMan.Index(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
-
- if (job.StartingGear != null && invSystem.TryGetSlots(dummy, out var slots))
- {
- var gear = protoMan.Index(job.StartingGear);
-
- foreach (var slot in slots)
- {
- var itemType = gear.GetGear(slot.Name, profile);
-
- if (invSystem.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
- {
- entMan.DeleteEntity(unequippedItem.Value);
- }
-
- if (itemType != string.Empty)
- {
- var item = entMan.SpawnEntity(itemType, MapCoordinates.Nullspace);
- invSystem.TryEquip(dummy, item, slot.Name, true, true);
- }
- }
- }
- }
- }
-}
diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml
new file mode 100644
index 00000000000..997507414cd
--- /dev/null
+++ b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs
new file mode 100644
index 00000000000..b0dcbc25fdb
--- /dev/null
+++ b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs
@@ -0,0 +1,45 @@
+using System.Numerics;
+using Content.Client.UserInterface.Controls;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Lobby.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class LobbyCharacterPreviewPanel : Control
+{
+ public Button CharacterSetupButton => CharacterSetup;
+
+ public LobbyCharacterPreviewPanel()
+ {
+ RobustXamlLoader.Load(this);
+ UserInterfaceManager.GetUIController().SetPreviewPanel(this);
+ }
+
+ public void SetLoaded(bool value)
+ {
+ Loaded.Visible = value;
+ Unloaded.Visible = !value;
+ }
+
+ public void SetSummaryText(string value)
+ {
+ Summary.Text = string.Empty;
+ }
+
+ public void SetSprite(EntityUid uid)
+ {
+ ViewBox.DisposeAllChildren();
+ var spriteView = new SpriteView
+ {
+ OverrideDirection = Direction.South,
+ Scale = new Vector2(4f, 4f),
+ MaxSize = new Vector2(112, 112),
+ Stretch = SpriteView.StretchMode.Fill,
+ };
+ spriteView.SetEntity(uid);
+ ViewBox.AddChild(spriteView);
+ }
+}
diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml.cs b/Content.Client/Lobby/UI/LobbyGui.xaml.cs
index 69867ea90cb..5a0b580262e 100644
--- a/Content.Client/Lobby/UI/LobbyGui.xaml.cs
+++ b/Content.Client/Lobby/UI/LobbyGui.xaml.cs
@@ -1,23 +1,9 @@
-using Content.Client.Chat.UI;
-using Content.Client.Info;
using Content.Client.Message;
-using Content.Client.Preferences;
-using Content.Client.Preferences.UI;
-using Content.Client.UserInterface.Screens;
-using Content.Client.UserInterface.Systems.Chat.Widgets;
using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
-using Robust.Client.Graphics;
-using Robust.Client.State;
using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
-using Robust.Shared.Prototypes;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Lobby.UI
{
diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
index b8267ea2955..58b48aa7d12 100644
--- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
+++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
@@ -7,12 +7,13 @@
using Robust.Client.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
+using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Players.PlayTimeTracking;
-public sealed partial class JobRequirementsManager
+public sealed partial class JobRequirementsManager : ISharedPlaytimeManager
{
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IClientNetManager _net = default!;
@@ -134,5 +135,13 @@ public IEnumerable> FetchPlaytimeByRoles()
}
}
+ public IReadOnlyDictionary GetPlayTimes(ICommonSession session)
+ {
+ if (session != _playerManager.LocalSession)
+ {
+ return new Dictionary();
+ }
+ return _roles;
+ }
}
diff --git a/Content.Client/Preferences/ClientPreferencesManager.cs b/Content.Client/Preferences/ClientPreferencesManager.cs
index b518493c9d4..89cee7bf79b 100644
--- a/Content.Client/Preferences/ClientPreferencesManager.cs
+++ b/Content.Client/Preferences/ClientPreferencesManager.cs
@@ -1,10 +1,8 @@
-using System;
-using System.Collections.Generic;
using System.Linq;
using Content.Shared.Preferences;
using Robust.Client;
+using Robust.Client.Player;
using Robust.Shared.Configuration;
-using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -20,8 +18,7 @@ public sealed class ClientPreferencesManager : IClientPreferencesManager
{
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IBaseClient _baseClient = default!;
- [Dependency] private readonly IConfigurationManager _cfg = default!;
- [Dependency] private readonly IPrototypeManager _prototypes = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
public event Action? OnServerDataLoaded;
@@ -64,7 +61,8 @@ public void SelectCharacter(int slot)
public void UpdateCharacter(ICharacterProfile profile, int slot)
{
- profile.EnsureValid(_cfg, _prototypes);
+ var collection = IoCManager.Instance!;
+ profile.EnsureValid(_playerManager.LocalSession!, collection);
var characters = new Dictionary(Preferences.Characters) {[slot] = profile};
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor);
var msg = new MsgUpdateCharacter
diff --git a/Content.Client/Preferences/UI/AntagPreferenceSelector.cs b/Content.Client/Preferences/UI/AntagPreferenceSelector.cs
new file mode 100644
index 00000000000..654c393b267
--- /dev/null
+++ b/Content.Client/Preferences/UI/AntagPreferenceSelector.cs
@@ -0,0 +1,41 @@
+using Content.Client.Players.PlayTimeTracking;
+using Content.Shared.Roles;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client.Preferences.UI;
+
+public sealed class AntagPreferenceSelector : RequirementsSelector
+{
+ // 0 is yes and 1 is no
+ public bool Preference
+ {
+ get => Options.SelectedValue == 0;
+ set => Options.Select((value && !Disabled) ? 0 : 1);
+ }
+
+ public event Action? PreferenceChanged;
+
+ public AntagPreferenceSelector(AntagPrototype proto, ButtonGroup btnGroup)
+ : base(proto, btnGroup)
+ {
+ Options.OnItemSelected += args => PreferenceChanged?.Invoke(Preference);
+
+ var items = new[]
+ {
+ ("humanoid-profile-editor-antag-preference-yes-button", 0),
+ ("humanoid-profile-editor-antag-preference-no-button", 1)
+ };
+ var title = Loc.GetString(proto.Name);
+ var description = Loc.GetString(proto.Objective);
+ // Not supported yet get fucked.
+ Setup(null, items, title, 250, description);
+
+ // immediately lock requirements if they arent met.
+ // another function checks Disabled after creating the selector so this has to be done now
+ var requirements = IoCManager.Resolve();
+ if (proto.Requirements != null && !requirements.CheckRoleTime(proto.Requirements, out var reason))
+ {
+ LockRequirements(reason);
+ }
+ }
+}
diff --git a/Content.Client/Preferences/UI/CharacterSetupGui.xaml b/Content.Client/Preferences/UI/CharacterSetupGui.xaml
index 9a76029ce0b..35067eebfd1 100644
--- a/Content.Client/Preferences/UI/CharacterSetupGui.xaml
+++ b/Content.Client/Preferences/UI/CharacterSetupGui.xaml
@@ -40,7 +40,7 @@
-
+
diff --git a/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs b/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs
index f1052086de6..8dda0220a98 100644
--- a/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs
+++ b/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs
@@ -3,27 +3,23 @@
using Content.Client.Humanoid;
using Content.Client.Info;
using Content.Client.Info.PlaytimeStats;
-using Content.Client.Lobby.UI;
+using Content.Client.Lobby;
using Content.Client.Resources;
using Content.Client.Stylesheets;
+using Content.Shared.Clothing;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles;
using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
using Robust.Shared.Map;
-using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using Direction = Robust.Shared.Maths.Direction;
@@ -36,7 +32,6 @@ public sealed partial class CharacterSetupGui : Control
private readonly IClientPreferencesManager _preferencesManager;
private readonly IEntityManager _entityManager;
private readonly IPrototypeManager _prototypeManager;
- private readonly IConfigurationManager _configurationManager;
private readonly Button _createNewCharacterButton;
private readonly HumanoidProfileEditor _humanoidProfileEditor;
@@ -51,7 +46,6 @@ public CharacterSetupGui(
_entityManager = entityManager;
_prototypeManager = prototypeManager;
_preferencesManager = preferencesManager;
- _configurationManager = configurationManager;
var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
var back = new StyleBoxTexture
@@ -74,7 +68,7 @@ public CharacterSetupGui(
args.Event.Handle();
};
- _humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, entityManager, configurationManager);
+ _humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, configurationManager);
_humanoidProfileEditor.OnProfileChanged += ProfileChanged;
CharEditor.AddChild(_humanoidProfileEditor);
@@ -103,6 +97,12 @@ private void ProfileChanged(ICharacterProfile profile, int profileSlot)
UpdateUI();
}
+ public void UpdateControls()
+ {
+ // Reset sliders etc. upon going going back to GUI.
+ _humanoidProfileEditor.LoadServerData();
+ }
+
private void UpdateUI()
{
var numberOfFullSlots = 0;
@@ -120,11 +120,6 @@ private void UpdateUI()
foreach (var (slot, character) in _preferencesManager.Preferences!.Characters)
{
- if (character is null)
- {
- continue;
- }
-
numberOfFullSlots++;
var characterPickerButton = new CharacterPickerButton(_entityManager,
_preferencesManager,
@@ -140,6 +135,9 @@ private void UpdateUI()
_humanoidProfileEditor.CharacterSlot = characterIndexCopy;
_humanoidProfileEditor.UpdateControls();
_preferencesManager.SelectCharacter(character);
+ var controller = UserInterfaceManager.GetUIController();
+ controller.UpdateProfile(_humanoidProfileEditor.Profile);
+ controller.ReloadCharacterUI();
UpdateUI();
args.Event.Handle();
};
@@ -148,8 +146,12 @@ private void UpdateUI()
_createNewCharacterButton.Disabled =
numberOfFullSlots >= _preferencesManager.Settings.MaxCharacterSlots;
Characters.AddChild(_createNewCharacterButton);
+ // TODO: Move this shit to the Lobby UI controller
}
+ ///
+ /// Shows individual characters on the side of the character GUI.
+ ///
private sealed class CharacterPickerButton : ContainerButton
{
private EntityUid _previewDummy;
@@ -180,7 +182,15 @@ public CharacterPickerButton(
if (humanoid != null)
{
- LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy, humanoid);
+ var controller = UserInterfaceManager.GetUIController();
+ var job = controller.GetPreferredJob(humanoid);
+ controller.GiveDummyJobClothes(_previewDummy, humanoid, job);
+
+ if (prototypeManager.HasIndex(LoadoutSystem.GetJobPrototype(job.ID)))
+ {
+ var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), entityManager, prototypeManager);
+ controller.GiveDummyLoadout(_previewDummy, loadout);
+ }
}
var isSelectedCharacter = profile == preferencesManager.Preferences?.SelectedCharacter;
diff --git a/Content.Client/Preferences/UI/HighlightedContainer.xaml b/Content.Client/Preferences/UI/HighlightedContainer.xaml
new file mode 100644
index 00000000000..8cf6e2da05b
--- /dev/null
+++ b/Content.Client/Preferences/UI/HighlightedContainer.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/Content.Client/Preferences/UI/HighlightedContainer.xaml.cs b/Content.Client/Preferences/UI/HighlightedContainer.xaml.cs
new file mode 100644
index 00000000000..68294d0f059
--- /dev/null
+++ b/Content.Client/Preferences/UI/HighlightedContainer.xaml.cs
@@ -0,0 +1,14 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Preferences.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class HighlightedContainer : PanelContainer
+{
+ public HighlightedContainer()
+ {
+ RobustXamlLoader.Load(this);
+ }
+}
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs
index c9e184dfc23..750006bf7a9 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs
@@ -5,8 +5,6 @@ namespace Content.Client.Preferences.UI
{
public sealed partial class HumanoidProfileEditor
{
- private readonly IPrototypeManager _prototypeManager;
-
private void RandomizeEverything()
{
Profile = HumanoidCharacterProfile.Random();
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
index d0dd02a58ad..5926aee8987 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
@@ -1,11 +1,11 @@
-
-
+ xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
+ HorizontalExpand="True">
-
+
@@ -58,7 +58,9 @@
-
+
@@ -85,18 +87,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
@@ -151,7 +141,7 @@
-
+
@@ -159,5 +149,4 @@
-
-
+
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
index fc07253b32a..2b9af05e924 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
@@ -2,69 +2,48 @@
using System.Numerics;
using Content.Client.Guidebook;
using Content.Client.Humanoid;
-using Content.Client.Lobby.UI;
+using Content.Client.Lobby;
using Content.Client.Message;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.CCVar;
+using Content.Shared.Clothing;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
-using Content.Shared.Inventory;
using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Roles;
-using Content.Shared.StatusIcon;
using Content.Shared.Traits;
using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
-using Robust.Shared.Map;
using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Robust.Shared.Timing;
using Robust.Shared.Utility;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
using Direction = Robust.Shared.Maths.Direction;
namespace Content.Client.Preferences.UI
{
- public sealed class HighlightedContainer : PanelContainer
- {
- public HighlightedContainer()
- {
- PanelOverride = new StyleBoxFlat()
- {
- BackgroundColor = new Color(47, 47, 53),
- ContentMarginTopOverride = 10,
- ContentMarginBottomOverride = 10,
- ContentMarginLeftOverride = 10,
- ContentMarginRightOverride = 10
- };
- }
- }
-
[GenerateTypedNameReferences]
- public sealed partial class HumanoidProfileEditor : Control
+ public sealed partial class HumanoidProfileEditor : BoxContainer
{
private readonly IClientPreferencesManager _preferencesManager;
- private readonly IEntityManager _entMan;
- private readonly IConfigurationManager _configurationManager;
+ private readonly IPrototypeManager _prototypeManager;
private readonly MarkingManager _markingManager;
private readonly JobRequirementsManager _requirements;
private LineEdit _ageEdit => CAgeEdit;
private LineEdit _nameEdit => CNameEdit;
- private TextEdit _flavorTextEdit = null!;
+ private TextEdit? _flavorTextEdit;
private Button _nameRandomButton => CNameRandomize;
private Button _randomizeEverythingButton => CRandomizeEverything;
private RichTextLabel _warningLabel => CWarningLabel;
@@ -72,8 +51,6 @@ public sealed partial class HumanoidProfileEditor : Control
private OptionButton _sexButton => CSexButton;
private OptionButton _genderButton => CPronounsButton;
private Slider _skinColor => CSkin;
- private OptionButton _clothingButton => CClothingButton;
- private OptionButton _backpackButton => CBackpackButton;
private OptionButton _spawnPriorityButton => CSpawnPriorityButton;
private SingleMarkingPicker _hairPicker => CHairStylePicker;
private SingleMarkingPicker _facialHairPicker => CFacialHairPicker;
@@ -88,44 +65,39 @@ public sealed partial class HumanoidProfileEditor : Control
private readonly Dictionary _jobCategories;
// Mildly hacky, as I don't trust prototype order to stay consistent and don't want the UI to break should a new one get added mid-edit. --moony
private readonly List _speciesList;
- private readonly List _antagPreferences;
+ private readonly List _antagPreferences = new();
private readonly List _traitPreferences;
private SpriteView _previewSpriteView => CSpriteView;
private Button _previewRotateLeftButton => CSpriteRotateLeft;
private Button _previewRotateRightButton => CSpriteRotateRight;
private Direction _previewRotation = Direction.North;
- private EntityUid? _previewDummy;
private BoxContainer _rgbSkinColorContainer => CRgbSkinColorContainer;
private ColorSelectorSliders _rgbSkinColorSelector;
private bool _isDirty;
- private bool _needUpdatePreview;
public int CharacterSlot;
public HumanoidCharacterProfile? Profile;
- private MarkingSet _markingSet = new(); // storing this here feels iffy but a few things need it this high up
public event Action? OnProfileChanged;
- public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager,
- IEntityManager entityManager, IConfigurationManager configurationManager)
+ [ValidatePrototypeId]
+ private const string DefaultSpeciesGuidebook = "Species";
+
+ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager, IConfigurationManager configurationManager)
{
RobustXamlLoader.Load(this);
_prototypeManager = prototypeManager;
- _entMan = entityManager;
_preferencesManager = preferencesManager;
- _configurationManager = configurationManager;
_markingManager = IoCManager.Resolve();
+ var controller = UserInterfaceManager.GetUIController();
+ controller.PreviewDummyUpdated += OnDummyUpdate;
- SpeciesInfoButton.ToolTip = Loc.GetString("humanoid-profile-editor-guidebook-button-tooltip");
+ _previewSpriteView.SetEntity(controller.GetPreviewDummy());
#region Left
- #region Randomize
-
- #endregion Randomize
-
#region Name
_nameEdit.OnTextChanged += args => { SetName(args.Text); };
@@ -139,8 +111,6 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
_tabContainer.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-appearance-tab"));
- ShowClothes.OnPressed += ToggleClothes;
-
#region Sex
_sexButton.OnItemSelected += args =>
@@ -220,7 +190,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithHairStyleName(newStyle.id));
- IsDirty = true;
+ SetDirty();
};
_hairPicker.OnColorChanged += newColor =>
@@ -230,7 +200,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
UpdateCMarkingsHair();
- IsDirty = true;
+ SetDirty();
};
_facialHairPicker.OnMarkingSelect += newStyle =>
@@ -239,7 +209,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithFacialHairStyleName(newStyle.id));
- IsDirty = true;
+ SetDirty();
};
_facialHairPicker.OnColorChanged += newColor =>
@@ -249,7 +219,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
UpdateCMarkingsFacialHair();
- IsDirty = true;
+ SetDirty();
};
_hairPicker.OnSlotRemove += _ =>
@@ -261,7 +231,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
);
UpdateHairPickers();
UpdateCMarkingsHair();
- IsDirty = true;
+ SetDirty();
};
_facialHairPicker.OnSlotRemove += _ =>
@@ -273,7 +243,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
);
UpdateHairPickers();
UpdateCMarkingsFacialHair();
- IsDirty = true;
+ SetDirty();
};
_hairPicker.OnSlotAdd += delegate()
@@ -293,7 +263,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
UpdateHairPickers();
UpdateCMarkingsHair();
- IsDirty = true;
+ SetDirty();
};
_facialHairPicker.OnSlotAdd += delegate()
@@ -313,38 +283,11 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
UpdateHairPickers();
UpdateCMarkingsFacialHair();
- IsDirty = true;
+ SetDirty();
};
#endregion Hair
- #region Clothing
-
- _clothingButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-jumpsuit"), (int) ClothingPreference.Jumpsuit);
- _clothingButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-jumpskirt"), (int) ClothingPreference.Jumpskirt);
-
- _clothingButton.OnItemSelected += args =>
- {
- _clothingButton.SelectId(args.Id);
- SetClothing((ClothingPreference) args.Id);
- };
-
- #endregion Clothing
-
- #region Backpack
-
- _backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-backpack"), (int) BackpackPreference.Backpack);
- _backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-satchel"), (int) BackpackPreference.Satchel);
- _backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-duffelbag"), (int) BackpackPreference.Duffelbag);
-
- _backpackButton.OnItemSelected += args =>
- {
- _backpackButton.SelectId(args.Id);
- SetBackpack((BackpackPreference) args.Id);
- };
-
- #endregion Backpack
-
#region SpawnPriority
foreach (var value in Enum.GetValues())
@@ -369,7 +312,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithEyeColor(newColor));
CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor;
- IsDirty = true;
+ SetDirty();
};
#endregion Eyes
@@ -393,46 +336,22 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
_preferenceUnavailableButton.SelectId(args.Id);
Profile = Profile?.WithPreferenceUnavailable((PreferenceUnavailableMode) args.Id);
- IsDirty = true;
+ SetDirty();
};
_jobPriorities = new List();
_jobCategories = new Dictionary();
_requirements = IoCManager.Resolve();
+ // TODO: Move this to the LobbyUIController instead of being spaghetti everywhere.
+ _requirements.Updated += UpdateAntagRequirements;
_requirements.Updated += UpdateRoleRequirements;
+ UpdateAntagRequirements();
UpdateRoleRequirements();
#endregion Jobs
- #region Antags
-
_tabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab"));
- _antagPreferences = new List();
-
- foreach (var antag in prototypeManager.EnumeratePrototypes().OrderBy(a => Loc.GetString(a.Name)))
- {
- if (!antag.SetPreference)
- continue;
-
- var selector = new AntagPreferenceSelector(antag);
- _antagList.AddChild(selector);
- _antagPreferences.Add(selector);
- if (selector.Disabled)
- {
- Profile = Profile?.WithAntagPreference(antag.ID, false);
- IsDirty = true;
- }
-
- selector.PreferenceChanged += preference =>
- {
- Profile = Profile?.WithAntagPreference(antag.ID, preference);
- IsDirty = true;
- };
- }
-
- #endregion Antags
-
#region Traits
var traits = prototypeManager.EnumeratePrototypes().OrderBy(t => Loc.GetString(t.Name)).ToList();
@@ -450,7 +369,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
selector.PreferenceChanged += preference =>
{
Profile = Profile?.WithTraitPreference(trait.ID, preference);
- IsDirty = true;
+ SetDirty();
};
}
}
@@ -483,7 +402,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
#region FlavorText
- if (_configurationManager.GetCVar(CCVars.FlavorText))
+ if (configurationManager.GetCVar(CCVars.FlavorText))
{
var flavorText = new FlavorText.FlavorText();
_tabContainer.AddChild(flavorText);
@@ -500,22 +419,14 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
_previewRotateLeftButton.OnPressed += _ =>
{
_previewRotation = _previewRotation.TurnCw();
- _needUpdatePreview = true;
+ SetPreviewRotation(_previewRotation);
};
_previewRotateRightButton.OnPressed += _ =>
{
_previewRotation = _previewRotation.TurnCcw();
- _needUpdatePreview = true;
+ SetPreviewRotation(_previewRotation);
};
- var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
- var dollProto = _prototypeManager.Index(species).DollPrototype;
-
- if (_previewDummy != null)
- _entMan.DeleteEntity(_previewDummy!.Value);
-
- _previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
- _previewSpriteView.SetEntity(_previewDummy);
#endregion Dummy
#endregion Left
@@ -525,6 +436,13 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
LoadServerData();
}
+ ShowClothes.OnToggled += args =>
+ {
+ var lobby = UserInterfaceManager.GetUIController();
+ lobby.SetClothes(args.Pressed);
+ SetDirty();
+ };
+
preferencesManager.OnServerDataLoaded += LoadServerData;
SpeciesInfoButton.OnPressed += OnSpeciesInfoButtonPressed;
@@ -532,28 +450,69 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
UpdateSpeciesGuidebookIcon();
IsDirty = false;
+ controller.UpdateProfile();
+ }
+
+ private void SetDirty()
+ {
+ var controller = UserInterfaceManager.GetUIController();
+ controller.UpdateProfile(Profile);
+ controller.ReloadCharacterUI();
+ IsDirty = true;
}
private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
{
var guidebookController = UserInterfaceManager.GetUIController();
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
- var page = "Species";
+ var page = DefaultSpeciesGuidebook;
if (_prototypeManager.HasIndex(species))
page = species;
- if (_prototypeManager.TryIndex("Species", out var guideRoot))
+ if (_prototypeManager.TryIndex(DefaultSpeciesGuidebook, out var guideRoot))
{
var dict = new Dictionary();
- dict.Add("Species", guideRoot);
+ dict.Add(DefaultSpeciesGuidebook, guideRoot);
//TODO: Don't close the guidebook if its already open, just go to the correct page
guidebookController.ToggleGuidebook(dict, includeChildren:true, selected: page);
}
}
- private void ToggleClothes(BaseButton.ButtonEventArgs obj)
+ private void OnDummyUpdate(EntityUid value)
{
- RebuildSpriteView();
+ _previewSpriteView.SetEntity(value);
+ }
+
+ private void UpdateAntagRequirements()
+ {
+ _antagList.DisposeAllChildren();
+ _antagPreferences.Clear();
+ var btnGroup = new ButtonGroup();
+
+ foreach (var antag in _prototypeManager.EnumeratePrototypes().OrderBy(a => Loc.GetString(a.Name)))
+ {
+ if (!antag.SetPreference)
+ continue;
+
+ var selector = new AntagPreferenceSelector(antag, btnGroup)
+ {
+ Margin = new Thickness(3f, 3f, 3f, 0f),
+ };
+ _antagList.AddChild(selector);
+ _antagPreferences.Add(selector);
+ if (selector.Disabled)
+ {
+ Profile = Profile?.WithAntagPreference(antag.ID, false);
+ SetDirty();
+ }
+
+ selector.PreferenceChanged += preference =>
+ {
+ Profile = Profile?.WithAntagPreference(antag.ID, preference);
+ SetDirty();
+ };
+ }
+
}
private void UpdateRoleRequirements()
@@ -614,10 +573,19 @@ private void UpdateRoleRequirements()
.Where(job => job.SetPreference)
.ToArray();
Array.Sort(jobs, JobUIComparer.Instance);
+ var jobLoadoutGroup = new ButtonGroup();
foreach (var job in jobs)
{
- var selector = new JobPrioritySelector(job, _prototypeManager);
+ RoleLoadout? loadout = null;
+
+ // Clone so we don't modify the underlying loadout.
+ Profile?.Loadouts.TryGetValue(LoadoutSystem.GetJobPrototype(job.ID), out loadout);
+ loadout = loadout?.Clone();
+ var selector = new JobPrioritySelector(loadout, job, jobLoadoutGroup, _prototypeManager)
+ {
+ Margin = new Thickness(3f, 3f, 3f, 0f),
+ };
if (!_requirements.IsAllowed(job, out var reason))
{
@@ -626,12 +594,16 @@ private void UpdateRoleRequirements()
category.AddChild(selector);
_jobPriorities.Add(selector);
- EnsureJobRequirementsValid(); // DeltaV
+
+ selector.LoadoutUpdated += args =>
+ {
+ Profile = Profile?.WithLoadout(args);
+ SetDirty();
+ };
selector.PriorityChanged += priority =>
{
Profile = Profile?.WithJobPriority(job.ID, priority);
- IsDirty = true;
foreach (var jobSelector in _jobPriorities)
{
@@ -647,6 +619,8 @@ private void UpdateRoleRequirements()
Profile = Profile?.WithJobPriority(jobSelector.Proto.ID, JobPriority.Medium);
}
}
+
+ SetDirty();
};
}
@@ -658,35 +632,13 @@ private void UpdateRoleRequirements()
}
}
- ///
- /// DeltaV - Make sure that no invalid job priorities get through.
- ///
- private void EnsureJobRequirementsValid()
- {
- var changed = false;
- foreach (var selector in _jobPriorities)
- {
- if (_requirements.IsAllowed(selector.Proto, out var _) || selector.Priority == JobPriority.Never)
- continue;
-
- selector.Priority = JobPriority.Never;
- Profile = Profile?.WithJobPriority(selector.Proto.ID, JobPriority.Never);
- changed = true;
- }
- if (!changed)
- return;
-
- _needUpdatePreview = true;
- Save();
- }
-
private void OnFlavorTextChange(string content)
{
if (Profile is null)
return;
Profile = Profile.WithFlavorText(content);
- IsDirty = true;
+ SetDirty();
}
private void OnMarkingChange(MarkingSet markings)
@@ -695,20 +647,12 @@ private void OnMarkingChange(MarkingSet markings)
return;
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList()));
- _needUpdatePreview = true;
- IsDirty = true;
- }
-
- private void OnMarkingColorChange(List markings)
- {
- if (Profile is null)
- return;
-
- Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings));
IsDirty = true;
+ var controller = UserInterfaceManager.GetUIController();
+ controller.UpdateProfile(Profile);
+ controller.ReloadProfile();
}
-
private void OnSkinColorOnValueChanged()
{
if (Profile is null) return;
@@ -782,6 +726,9 @@ private void OnSkinColorOnValueChanged()
}
IsDirty = true;
+ var controller = UserInterfaceManager.GetUIController();
+ controller.UpdateProfile(Profile);
+ controller.ReloadProfile();
}
protected override void Dispose(bool disposing)
@@ -790,40 +737,27 @@ protected override void Dispose(bool disposing)
if (!disposing)
return;
- if (_previewDummy != null)
- _entMan.DeleteEntity(_previewDummy.Value);
-
+ var controller = UserInterfaceManager.GetUIController();
+ controller.PreviewDummyUpdated -= OnDummyUpdate;
+ _requirements.Updated -= UpdateAntagRequirements;
_requirements.Updated -= UpdateRoleRequirements;
_preferencesManager.OnServerDataLoaded -= LoadServerData;
}
- private void RebuildSpriteView()
- {
- var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
- var dollProto = _prototypeManager.Index(species).DollPrototype;
-
- if (_previewDummy != null)
- _entMan.DeleteEntity(_previewDummy!.Value);
-
- _previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
- _previewSpriteView.SetEntity(_previewDummy);
- _needUpdatePreview = true;
- }
-
- private void LoadServerData()
+ public void LoadServerData()
{
Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter;
CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex;
+ UpdateAntagRequirements();
UpdateControls();
- EnsureJobRequirementsValid(); // DeltaV
- _needUpdatePreview = true;
+ ShowClothes.Pressed = true;
}
private void SetAge(int newAge)
{
Profile = Profile?.WithAge(newAge);
- IsDirty = true;
+ SetDirty();
}
private void SetSex(Sex newSex)
@@ -844,13 +778,13 @@ private void SetSex(Sex newSex)
}
UpdateGenderControls();
CMarkings.SetSex(newSex);
- IsDirty = true;
+ SetDirty();
}
private void SetGender(Gender newGender)
{
Profile = Profile?.WithGender(newGender);
- IsDirty = true;
+ SetDirty();
}
private void SetSpecies(string newSpecies)
@@ -859,46 +793,34 @@ private void SetSpecies(string newSpecies)
OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
CMarkings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
UpdateSexControls(); // update sex for new species
- RebuildSpriteView(); // they might have different inv so we need a new dummy
UpdateSpeciesGuidebookIcon();
- IsDirty = true;
- _needUpdatePreview = true;
+ SetDirty();
+ UpdatePreview();
}
private void SetName(string newName)
{
Profile = Profile?.WithName(newName);
- IsDirty = true;
- }
-
- private void SetClothing(ClothingPreference newClothing)
- {
- Profile = Profile?.WithClothingPreference(newClothing);
- IsDirty = true;
- }
-
- private void SetBackpack(BackpackPreference newBackpack)
- {
- Profile = Profile?.WithBackpackPreference(newBackpack);
- IsDirty = true;
+ SetDirty();
}
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
{
Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority);
- IsDirty = true;
+ SetDirty();
}
public void Save()
{
IsDirty = false;
- if (Profile != null)
- {
- _preferencesManager.UpdateCharacter(Profile, CharacterSlot);
- OnProfileChanged?.Invoke(Profile, CharacterSlot);
- _needUpdatePreview = true;
- }
+ if (Profile == null)
+ return;
+
+ _preferencesManager.UpdateCharacter(Profile, CharacterSlot);
+ OnProfileChanged?.Invoke(Profile, CharacterSlot);
+ // Reset profile to default.
+ UserInterfaceManager.GetUIController().UpdateProfile();
}
private bool IsDirty
@@ -907,7 +829,6 @@ private bool IsDirty
set
{
_isDirty = value;
- _needUpdatePreview = true;
UpdateSaveButton();
}
}
@@ -1039,7 +960,7 @@ public void UpdateSpeciesGuidebookIcon()
if (!_prototypeManager.HasIndex(species))
return;
- var style = speciesProto.GuideBookIcon;
+ const string style = "SpeciesInfoDefault";
SpeciesInfoButton.StyleClasses.Add(style);
}
@@ -1075,26 +996,6 @@ private void UpdateGenderControls()
_genderButton.SelectId((int) Profile.Gender);
}
- private void UpdateClothingControls()
- {
- if (Profile == null)
- {
- return;
- }
-
- _clothingButton.SelectId((int) Profile.Clothing);
- }
-
- private void UpdateBackpackControls()
- {
- if (Profile == null)
- {
- return;
- }
-
- _backpackButton.SelectId((int) Profile.Backpack);
- }
-
private void UpdateSpawnPriorityControls()
{
if (Profile == null)
@@ -1224,13 +1125,13 @@ private void UpdatePreview()
if (Profile is null)
return;
- var humanoid = _entMan.System();
- humanoid.LoadProfile(_previewDummy!.Value, Profile);
-
- if (ShowClothes.Pressed)
- LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy!.Value, Profile);
+ UserInterfaceManager.GetUIController().ReloadProfile();
+ SetPreviewRotation(_previewRotation);
+ }
- _previewSpriteView.OverrideDirection = (Direction) ((int) _previewRotation % 4 * 2);
+ private void SetPreviewRotation(Direction direction)
+ {
+ _previewSpriteView.OverrideDirection = (Direction) ((int) direction % 4 * 2);
}
public void UpdateControls()
@@ -1242,17 +1143,16 @@ public void UpdateControls()
UpdateGenderControls();
UpdateSkinColor();
UpdateSpecies();
- UpdateClothingControls();
- UpdateBackpackControls();
UpdateSpawnPriorityControls();
UpdateAgeEdit();
UpdateEyePickers();
UpdateSaveButton();
+ UpdateLoadouts();
+ UpdateRoleRequirements();
UpdateJobPriorities();
UpdateAntagPreferences();
UpdateTraitPreferences();
UpdateMarkings();
- RebuildSpriteView();
UpdateHairPickers();
UpdateCMarkingsHair();
UpdateCMarkingsFacialHair();
@@ -1260,17 +1160,6 @@ public void UpdateControls()
_preferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable);
}
- protected override void FrameUpdate(FrameEventArgs args)
- {
- base.FrameUpdate(args);
-
- if (_needUpdatePreview)
- {
- UpdatePreview();
- _needUpdatePreview = false;
- }
- }
-
private void UpdateJobPriorities()
{
foreach (var prioritySelector in _jobPriorities)
@@ -1283,143 +1172,11 @@ private void UpdateJobPriorities()
}
}
- private abstract class RequirementsSelector : Control
+ private void UpdateLoadouts()
{
- public T Proto { get; }
- public bool Disabled => _lockStripe.Visible;
-
- protected readonly RadioOptions Options;
- private StripeBack _lockStripe;
- private Label _requirementsLabel;
-
- protected RequirementsSelector(T proto)
- {
- Proto = proto;
-
- Options = new RadioOptions(RadioOptionsLayout.Horizontal)
- {
- FirstButtonStyle = StyleBase.ButtonOpenRight,
- ButtonStyle = StyleBase.ButtonOpenBoth,
- LastButtonStyle = StyleBase.ButtonOpenLeft
- };
- //Override default radio option button width
- Options.GenerateItem = GenerateButton;
-
- Options.OnItemSelected += args => Options.Select(args.Id);
-
- _requirementsLabel = new Label()
- {
- Text = Loc.GetString("role-timer-locked"),
- Visible = true,
- HorizontalAlignment = HAlignment.Center,
- StyleClasses = {StyleBase.StyleClassLabelSubText},
- };
-
- _lockStripe = new StripeBack()
- {
- Visible = false,
- HorizontalExpand = true,
- MouseFilter = MouseFilterMode.Stop,
- Children =
- {
- _requirementsLabel
- }
- };
-
- // Setup must be called after
- }
-
- ///
- /// Actually adds the controls, must be called in the inheriting class' constructor.
- ///
- protected void Setup((string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
- {
- foreach (var (text, value) in items)
- {
- Options.AddItem(Loc.GetString(text), value);
- }
-
- var titleLabel = new Label()
- {
- Margin = new Thickness(5f, 0, 5f, 0),
- Text = title,
- MinSize = new Vector2(titleSize, 0),
- MouseFilter = MouseFilterMode.Stop,
- ToolTip = description
- };
-
- var container = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- };
-
- if (icon != null)
- container.AddChild(icon);
- container.AddChild(titleLabel);
- container.AddChild(Options);
- container.AddChild(_lockStripe);
-
- AddChild(container);
- }
-
- public void LockRequirements(FormattedMessage requirements)
- {
- var tooltip = new Tooltip();
- tooltip.SetMessage(requirements);
- _lockStripe.TooltipSupplier = _ => tooltip;
- _lockStripe.Visible = true;
- Options.Visible = false;
- }
-
- // TODO: Subscribe to roletimers event. I am too lazy to do this RN But I doubt most people will notice fn
- public void UnlockRequirements()
- {
- _lockStripe.Visible = false;
- Options.Visible = true;
- }
-
- private Button GenerateButton(string text, int value)
- {
- return new Button
- {
- Text = text,
- MinWidth = 90
- };
- }
- }
-
- private sealed class JobPrioritySelector : RequirementsSelector
- {
- public JobPriority Priority
- {
- get => (JobPriority) Options.SelectedValue;
- set => Options.SelectByValue((int) value);
- }
-
- public event Action? PriorityChanged;
-
- public JobPrioritySelector(JobPrototype proto, IPrototypeManager protoMan)
- : base(proto)
+ foreach (var prioritySelector in _jobPriorities)
{
- Options.OnItemSelected += args => PriorityChanged?.Invoke(Priority);
-
- var items = new[]
- {
- ("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High),
- ("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium),
- ("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low),
- ("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never),
- };
-
- var icon = new TextureRect
- {
- TextureScale = new Vector2(2, 2),
- VerticalAlignment = VAlignment.Center
- };
- var jobIcon = protoMan.Index(proto.Icon);
- icon.Texture = jobIcon.Icon.Frame0();
-
- Setup(items, proto.LocalizedName, 200, proto.LocalizedDescription, icon);
+ prioritySelector.CloseLoadout();
}
}
@@ -1444,41 +1201,6 @@ private void UpdateTraitPreferences()
}
}
- private sealed class AntagPreferenceSelector : RequirementsSelector
- {
- // 0 is yes and 1 is no
- public bool Preference
- {
- get => Options.SelectedValue == 0;
- set => Options.Select((value && !Disabled) ? 0 : 1);
- }
-
- public event Action? PreferenceChanged;
-
- public AntagPreferenceSelector(AntagPrototype proto)
- : base(proto)
- {
- Options.OnItemSelected += args => PreferenceChanged?.Invoke(Preference);
-
- var items = new[]
- {
- ("humanoid-profile-editor-antag-preference-yes-button", 0),
- ("humanoid-profile-editor-antag-preference-no-button", 1)
- };
- var title = Loc.GetString(proto.Name);
- var description = Loc.GetString(proto.Objective);
- Setup(items, title, 250, description);
-
- // immediately lock requirements if they arent met.
- // another function checks Disabled after creating the selector so this has to be done now
- var requirements = IoCManager.Resolve();
- if (proto.Requirements != null && !requirements.CheckRoleTime(proto.Requirements, out var reason))
- {
- LockRequirements(reason);
- }
- }
- }
-
private sealed class TraitPreferenceSelector : Control
{
public TraitPrototype Trait { get; }
diff --git a/Content.Client/Preferences/UI/JobPrioritySelector.cs b/Content.Client/Preferences/UI/JobPrioritySelector.cs
new file mode 100644
index 00000000000..243c78f07eb
--- /dev/null
+++ b/Content.Client/Preferences/UI/JobPrioritySelector.cs
@@ -0,0 +1,46 @@
+using System.Numerics;
+using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
+using Content.Shared.Roles;
+using Content.Shared.StatusIcon;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.Utility;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Preferences.UI;
+
+public sealed class JobPrioritySelector : RequirementsSelector
+{
+ public JobPriority Priority
+ {
+ get => (JobPriority) Options.SelectedValue;
+ set => Options.SelectByValue((int) value);
+ }
+
+ public event Action? PriorityChanged;
+
+ public JobPrioritySelector(RoleLoadout? loadout, JobPrototype proto, ButtonGroup btnGroup, IPrototypeManager protoMan)
+ : base(proto, btnGroup)
+ {
+ Options.OnItemSelected += args => PriorityChanged?.Invoke(Priority);
+
+ var items = new[]
+ {
+ ("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High),
+ ("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium),
+ ("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low),
+ ("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never),
+ };
+
+ var icon = new TextureRect
+ {
+ TextureScale = new Vector2(2, 2),
+ VerticalAlignment = VAlignment.Center
+ };
+ var jobIcon = protoMan.Index(proto.Icon);
+ icon.Texture = jobIcon.Icon.Frame0();
+
+ Setup(loadout, items, proto.LocalizedName, 200, proto.LocalizedDescription, icon);
+ }
+}
diff --git a/Content.Client/Preferences/UI/LoadoutContainer.xaml b/Content.Client/Preferences/UI/LoadoutContainer.xaml
new file mode 100644
index 00000000000..a84a4a96401
--- /dev/null
+++ b/Content.Client/Preferences/UI/LoadoutContainer.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Preferences/UI/LoadoutContainer.xaml.cs b/Content.Client/Preferences/UI/LoadoutContainer.xaml.cs
new file mode 100644
index 00000000000..45a982b5a89
--- /dev/null
+++ b/Content.Client/Preferences/UI/LoadoutContainer.xaml.cs
@@ -0,0 +1,74 @@
+using Content.Shared.Clothing;
+using Content.Shared.Preferences.Loadouts;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Preferences.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class LoadoutContainer : BoxContainer
+{
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ [Dependency] private readonly IPrototypeManager _protoManager = default!;
+
+ private readonly EntityUid? _entity;
+
+ public Button Select => SelectButton;
+
+ public LoadoutContainer(ProtoId proto, bool disabled, FormattedMessage? reason)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ SelectButton.Disabled = disabled;
+
+ if (disabled && reason != null)
+ {
+ var tooltip = new Tooltip();
+ tooltip.SetMessage(reason);
+ SelectButton.TooltipSupplier = _ => tooltip;
+ }
+
+ if (_protoManager.TryIndex(proto, out var loadProto))
+ {
+ var ent = _entManager.System().GetFirstOrNull(loadProto);
+
+ if (ent != null)
+ {
+ _entity = _entManager.SpawnEntity(ent, MapCoordinates.Nullspace);
+ Sprite.SetEntity(_entity);
+
+ var spriteTooltip = new Tooltip();
+ spriteTooltip.SetMessage(FormattedMessage.FromUnformatted(_entManager.GetComponent(_entity.Value).EntityDescription));
+ Sprite.TooltipSupplier = _ => spriteTooltip;
+ }
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (!disposing)
+ return;
+
+ _entManager.DeleteEntity(_entity);
+ }
+
+ public bool Pressed
+ {
+ get => SelectButton.Pressed;
+ set => SelectButton.Pressed = value;
+ }
+
+ public string? Text
+ {
+ get => SelectButton.Text;
+ set => SelectButton.Text = value;
+ }
+}
diff --git a/Content.Client/Preferences/UI/LoadoutGroupContainer.xaml b/Content.Client/Preferences/UI/LoadoutGroupContainer.xaml
new file mode 100644
index 00000000000..1e3eb14d3fc
--- /dev/null
+++ b/Content.Client/Preferences/UI/LoadoutGroupContainer.xaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Preferences/UI/LoadoutGroupContainer.xaml.cs b/Content.Client/Preferences/UI/LoadoutGroupContainer.xaml.cs
new file mode 100644
index 00000000000..8dc1c405394
--- /dev/null
+++ b/Content.Client/Preferences/UI/LoadoutGroupContainer.xaml.cs
@@ -0,0 +1,93 @@
+using System.Linq;
+using Content.Shared.Clothing;
+using Content.Shared.Preferences.Loadouts;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Preferences.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class LoadoutGroupContainer : BoxContainer
+{
+ private readonly LoadoutGroupPrototype _groupProto;
+
+ public event Action>? OnLoadoutPressed;
+ public event Action>? OnLoadoutUnpressed;
+
+ public LoadoutGroupContainer(RoleLoadout loadout, LoadoutGroupPrototype groupProto, ICommonSession session, IDependencyCollection collection)
+ {
+ RobustXamlLoader.Load(this);
+ _groupProto = groupProto;
+
+ RefreshLoadouts(loadout, session, collection);
+ }
+
+ ///
+ /// Updates button availabilities and buttons.
+ ///
+ public void RefreshLoadouts(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
+ {
+ var protoMan = collection.Resolve();
+ var loadoutSystem = collection.Resolve().System();
+ RestrictionsContainer.DisposeAllChildren();
+
+ if (_groupProto.MinLimit > 0)
+ {
+ RestrictionsContainer.AddChild(new Label()
+ {
+ Text = Loc.GetString("loadouts-min-limit", ("count", _groupProto.MinLimit)),
+ Margin = new Thickness(5, 0, 5, 5),
+ });
+ }
+
+ if (_groupProto.MaxLimit > 0)
+ {
+ RestrictionsContainer.AddChild(new Label()
+ {
+ Text = Loc.GetString("loadouts-max-limit", ("count", _groupProto.MaxLimit)),
+ Margin = new Thickness(5, 0, 5, 5),
+ });
+ }
+
+ if (protoMan.TryIndex(loadout.Role, out var roleProto) && roleProto.Points != null && loadout.Points != null)
+ {
+ RestrictionsContainer.AddChild(new Label()
+ {
+ Text = Loc.GetString("loadouts-points-limit", ("count", loadout.Points.Value), ("max", roleProto.Points.Value)),
+ Margin = new Thickness(5, 0, 5, 5),
+ });
+ }
+
+ LoadoutsContainer.DisposeAllChildren();
+ // Didn't use options because this is more robust in future.
+
+ var selected = loadout.SelectedLoadouts[_groupProto.ID];
+
+ foreach (var loadoutProto in _groupProto.Loadouts)
+ {
+ if (!protoMan.TryIndex(loadoutProto, out var loadProto))
+ continue;
+
+ var matchingLoadout = selected.FirstOrDefault(e => e.Prototype == loadoutProto);
+ var pressed = matchingLoadout != null;
+
+ var enabled = loadout.IsValid(session, loadoutProto, collection, out var reason);
+ var loadoutContainer = new LoadoutContainer(loadoutProto, !enabled, reason);
+ loadoutContainer.Select.Pressed = pressed;
+ loadoutContainer.Text = loadoutSystem.GetName(loadProto);
+
+ loadoutContainer.Select.OnPressed += args =>
+ {
+ if (args.Button.Pressed)
+ OnLoadoutPressed?.Invoke(loadoutProto);
+ else
+ OnLoadoutUnpressed?.Invoke(loadoutProto);
+ };
+
+ LoadoutsContainer.AddChild(loadoutContainer);
+ }
+ }
+}
diff --git a/Content.Client/Preferences/UI/LoadoutWindow.xaml b/Content.Client/Preferences/UI/LoadoutWindow.xaml
new file mode 100644
index 00000000000..afa783c7aa9
--- /dev/null
+++ b/Content.Client/Preferences/UI/LoadoutWindow.xaml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/Content.Client/Preferences/UI/LoadoutWindow.xaml.cs b/Content.Client/Preferences/UI/LoadoutWindow.xaml.cs
new file mode 100644
index 00000000000..8e1ef0f1697
--- /dev/null
+++ b/Content.Client/Preferences/UI/LoadoutWindow.xaml.cs
@@ -0,0 +1,60 @@
+using Content.Client.Lobby;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Preferences.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class LoadoutWindow : FancyWindow
+{
+ public event Action, ProtoId>? OnLoadoutPressed;
+ public event Action, ProtoId>? OnLoadoutUnpressed;
+
+ private List _groups = new();
+
+ public LoadoutWindow(RoleLoadout loadout, RoleLoadoutPrototype proto, ICommonSession session, IDependencyCollection collection)
+ {
+ RobustXamlLoader.Load(this);
+ var protoManager = collection.Resolve();
+
+ foreach (var group in proto.Groups)
+ {
+ if (!protoManager.TryIndex(group, out var groupProto))
+ continue;
+
+ var container = new LoadoutGroupContainer(loadout, protoManager.Index(group), session, collection);
+ LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
+ _groups.Add(container);
+
+ container.OnLoadoutPressed += args =>
+ {
+ OnLoadoutPressed?.Invoke(group, args);
+ };
+
+ container.OnLoadoutUnpressed += args =>
+ {
+ OnLoadoutUnpressed?.Invoke(group, args);
+ };
+ }
+ }
+
+ public override void Close()
+ {
+ base.Close();
+ var controller = UserInterfaceManager.GetUIController();
+ controller.SetDummyJob(null);
+ }
+
+ public void RefreshLoadouts(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
+ {
+ foreach (var group in _groups)
+ {
+ group.RefreshLoadouts(loadout, session, collection);
+ }
+ }
+}
diff --git a/Content.Client/Preferences/UI/RequirementsSelector.cs b/Content.Client/Preferences/UI/RequirementsSelector.cs
new file mode 100644
index 00000000000..e016661ee6c
--- /dev/null
+++ b/Content.Client/Preferences/UI/RequirementsSelector.cs
@@ -0,0 +1,222 @@
+using System.Numerics;
+using Content.Client.Lobby;
+using Content.Client.Stylesheets;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Clothing;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
+using Content.Shared.Roles;
+using Robust.Client.Player;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Preferences.UI;
+
+public abstract class RequirementsSelector : BoxContainer where T : IPrototype
+{
+ private ButtonGroup _loadoutGroup;
+
+ public T Proto { get; }
+ public bool Disabled => _lockStripe.Visible;
+
+ protected readonly RadioOptions Options;
+ private readonly StripeBack _lockStripe;
+ private LoadoutWindow? _loadoutWindow;
+
+ private RoleLoadout? _loadout;
+
+ ///
+ /// Raised if a loadout has been updated.
+ ///
+ public event Action? LoadoutUpdated;
+
+ protected RequirementsSelector(T proto, ButtonGroup loadoutGroup)
+ {
+ _loadoutGroup = loadoutGroup;
+ Proto = proto;
+
+ Options = new RadioOptions(RadioOptionsLayout.Horizontal)
+ {
+ FirstButtonStyle = StyleBase.ButtonOpenRight,
+ ButtonStyle = StyleBase.ButtonOpenBoth,
+ LastButtonStyle = StyleBase.ButtonOpenLeft,
+ HorizontalExpand = true,
+ };
+ //Override default radio option button width
+ Options.GenerateItem = GenerateButton;
+
+ Options.OnItemSelected += args => Options.Select(args.Id);
+
+ var requirementsLabel = new Label()
+ {
+ Text = Loc.GetString("role-timer-locked"),
+ Visible = true,
+ HorizontalAlignment = HAlignment.Center,
+ StyleClasses = {StyleBase.StyleClassLabelSubText},
+ };
+
+ _lockStripe = new StripeBack()
+ {
+ Visible = false,
+ HorizontalExpand = true,
+ HasMargins = false,
+ MouseFilter = MouseFilterMode.Stop,
+ Children =
+ {
+ requirementsLabel
+ }
+ };
+
+ // Setup must be called after
+ }
+
+ ///
+ /// Actually adds the controls, must be called in the inheriting class' constructor.
+ ///
+ protected void Setup(RoleLoadout? loadout, (string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
+ {
+ _loadout = loadout;
+
+ foreach (var (text, value) in items)
+ {
+ Options.AddItem(Loc.GetString(text), value);
+ }
+
+ var titleLabel = new Label()
+ {
+ Margin = new Thickness(5f, 0, 5f, 0),
+ Text = title,
+ MinSize = new Vector2(titleSize, 0),
+ MouseFilter = MouseFilterMode.Stop,
+ ToolTip = description
+ };
+
+ if (icon != null)
+ AddChild(icon);
+
+ AddChild(titleLabel);
+ AddChild(Options);
+ AddChild(_lockStripe);
+
+ var loadoutWindowBtn = new Button()
+ {
+ Text = Loc.GetString("loadout-window"),
+ HorizontalAlignment = HAlignment.Right,
+ Group = _loadoutGroup,
+ Margin = new Thickness(3f, 0f, 0f, 0f),
+ };
+
+ var collection = IoCManager.Instance!;
+ var protoManager = collection.Resolve();
+
+ // If no loadout found then disabled button
+ if (!protoManager.HasIndex(LoadoutSystem.GetJobPrototype(Proto.ID)))
+ {
+ loadoutWindowBtn.Disabled = true;
+ }
+ // else
+ else
+ {
+ var session = collection.Resolve().LocalSession!;
+ // TODO: Most of lobby state should be a uicontroller
+ // trying to handle all this shit is a big-ass mess.
+ // Every time I touch it I try to make it slightly better but it needs a howitzer dropped on it.
+ loadoutWindowBtn.OnPressed += args =>
+ {
+ if (args.Button.Pressed)
+ {
+ // We only create a loadout when necessary to avoid unnecessary DB entries.
+ _loadout ??= new RoleLoadout(LoadoutSystem.GetJobPrototype(Proto.ID));
+ _loadout.SetDefault(protoManager);
+
+ _loadoutWindow = new LoadoutWindow(_loadout, protoManager.Index(_loadout.Role), session, collection)
+ {
+ Title = Loc.GetString(Proto.ID + "-loadout"),
+ };
+
+ _loadoutWindow.RefreshLoadouts(_loadout, session, collection);
+
+ // If it's a job preview then refresh it.
+ if (Proto is JobPrototype jobProto)
+ {
+ var controller = UserInterfaceManager.GetUIController();
+ controller.SetDummyJob(jobProto);
+ }
+
+ _loadoutWindow.OnLoadoutUnpressed += (selectedGroup, selectedLoadout) =>
+ {
+ if (!_loadout.RemoveLoadout(selectedGroup, selectedLoadout, protoManager))
+ return;
+
+ _loadout.EnsureValid(session, collection);
+ _loadoutWindow.RefreshLoadouts(_loadout, session, collection);
+ var controller = UserInterfaceManager.GetUIController();
+ controller.ReloadProfile();
+ LoadoutUpdated?.Invoke(_loadout);
+ };
+
+ _loadoutWindow.OnLoadoutPressed += (selectedGroup, selectedLoadout) =>
+ {
+ if (!_loadout.AddLoadout(selectedGroup, selectedLoadout, protoManager))
+ return;
+
+ _loadout.EnsureValid(session, collection);
+ _loadoutWindow.RefreshLoadouts(_loadout, session, collection);
+ var controller = UserInterfaceManager.GetUIController();
+ controller.ReloadProfile();
+ LoadoutUpdated?.Invoke(_loadout);
+ };
+
+ _loadoutWindow.OpenCenteredLeft();
+ _loadoutWindow.OnClose += () =>
+ {
+ loadoutWindowBtn.Pressed = false;
+ _loadoutWindow?.Dispose();
+ _loadoutWindow = null;
+ };
+ }
+ else
+ {
+ CloseLoadout();
+ }
+ };
+ }
+
+ AddChild(loadoutWindowBtn);
+ }
+
+ public void CloseLoadout()
+ {
+ _loadoutWindow?.Close();
+ _loadoutWindow?.Dispose();
+ _loadoutWindow = null;
+ }
+
+ public void LockRequirements(FormattedMessage requirements)
+ {
+ var tooltip = new Tooltip();
+ tooltip.SetMessage(requirements);
+ _lockStripe.TooltipSupplier = _ => tooltip;
+ _lockStripe.Visible = true;
+ Options.Visible = false;
+ }
+
+ // TODO: Subscribe to roletimers event. I am too lazy to do this RN But I doubt most people will notice fn
+ public void UnlockRequirements()
+ {
+ _lockStripe.Visible = false;
+ Options.Visible = true;
+ }
+
+ private Button GenerateButton(string text, int value)
+ {
+ return new Button
+ {
+ Text = text,
+ MinWidth = 90,
+ HorizontalExpand = true,
+ };
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs b/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs
index 980559cc817..7bc62dfe2bc 100644
--- a/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs
+++ b/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs
@@ -19,7 +19,7 @@ public async Task DeleteAllThenGhost()
await using var pair = await PoolManager.GetServerClient(settings);
// Client is connected with a valid entity & mind
- Assert.That(pair.Client.EntMan.EntityExists(pair.Client.Player?.ControlledEntity));
+ Assert.That(pair.Client.EntMan.EntityExists(pair.Client.AttachedEntity));
Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind));
// Delete **everything**
@@ -28,6 +28,12 @@ public async Task DeleteAllThenGhost()
await pair.RunTicksSync(5);
Assert.That(pair.Server.EntMan.EntityCount, Is.EqualTo(0));
+
+ foreach (var ent in pair.Client.EntMan.GetEntities())
+ {
+ Console.WriteLine(pair.Client.EntMan.ToPrettyString(ent));
+ }
+
Assert.That(pair.Client.EntMan.EntityCount, Is.EqualTo(0));
// Create a new map.
@@ -36,7 +42,7 @@ public async Task DeleteAllThenGhost()
await pair.RunTicksSync(5);
// Client is not attached to anything
- Assert.That(pair.Client.Player?.ControlledEntity, Is.Null);
+ Assert.That(pair.Client.AttachedEntity, Is.Null);
Assert.That(pair.PlayerData?.Mind, Is.Null);
// Attempt to ghost
@@ -45,9 +51,9 @@ public async Task DeleteAllThenGhost()
await pair.RunTicksSync(10);
// Client should be attached to a ghost placed on the new map.
- Assert.That(pair.Client.EntMan.EntityExists(pair.Client.Player?.ControlledEntity));
+ Assert.That(pair.Client.EntMan.EntityExists(pair.Client.AttachedEntity));
Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind));
- var xform = pair.Client.Transform(pair.Client.Player!.ControlledEntity!.Value);
+ var xform = pair.Client.Transform(pair.Client.AttachedEntity!.Value);
Assert.That(xform.MapID, Is.EqualTo(new MapId(mapId)));
await pair.CleanReturnAsync();
diff --git a/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs b/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs
new file mode 100644
index 00000000000..72e35dac057
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs
@@ -0,0 +1,44 @@
+using Content.Server.Station.Systems;
+using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Roles.Jobs;
+using Robust.Shared.GameObjects;
+
+namespace Content.IntegrationTests.Tests.Preferences;
+
+[TestFixture]
+[Ignore("HumanoidAppearance crashes upon loading default profiles.")]
+public sealed class LoadoutTests
+{
+ ///
+ /// Checks that an empty loadout still spawns with default gear and not naked.
+ ///
+ [Test]
+ public async Task TestEmptyLoadout()
+ {
+ var pair = await PoolManager.GetServerClient(new PoolSettings()
+ {
+ Dirty = true,
+ });
+ var server = pair.Server;
+
+ var entManager = server.ResolveDependency();
+
+ // Check that an empty role loadout spawns gear
+ var stationSystem = 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();
+
+ profile.SetLoadout(new RoleLoadout("TestRoleLoadout"));
+
+ stationSystem.SpawnPlayerMob(testMap.GridCoords, job: new JobComponent()
+ {
+ // Sue me, there's so much involved in setting up jobs
+ Prototype = "CargoTechnician"
+ }, profile, station: null);
+
+ await pair.CleanReturnAsync();
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs b/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs
index c57504764dc..0fb9c6a361b 100644
--- a/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs
+++ b/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs
@@ -4,6 +4,8 @@
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Preferences.Loadouts.Effects;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Robust.Shared.Configuration;
@@ -53,8 +55,6 @@ private static HumanoidCharacterProfile CharlieCharlieson()
Color.Beige,
new ()
),
- ClothingPreference.Jumpskirt,
- BackpackPreference.Backpack,
SpawnPriorityPreference.None,
new Dictionary
{
@@ -62,7 +62,8 @@ private static HumanoidCharacterProfile CharlieCharlieson()
},
PreferenceUnavailableMode.StayInLobby,
new List (),
- new List()
+ new List(),
+ new Dictionary()
);
}
diff --git a/Content.Server.Database/Migrations/Postgres/20240301130641_ClothingRemoval.Designer.cs b/Content.Server.Database/Migrations/Postgres/20240301130641_ClothingRemoval.Designer.cs
new file mode 100644
index 00000000000..06b7fecf5ff
--- /dev/null
+++ b/Content.Server.Database/Migrations/Postgres/20240301130641_ClothingRemoval.Designer.cs
@@ -0,0 +1,1838 @@
+//
+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("20240301130641_ClothingRemoval")]
+ partial class ClothingRemoval
+ {
+ ///
+ 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("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", (string)null);
+ });
+
+ 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("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