using Content.Client.Input;
using Content.Client.IoC;
using Content.Client.Launcher;
+using Content.Client.Lobby;
using Content.Client.MainMenu;
using Content.Client.Parallax.Managers;
using Content.Client.Players.PlayTimeTracking;
-using Content.Client.Preferences;
using Content.Client.Radiation.Overlays;
using Content.Client.Replay;
using Content.Client.Screenshot;
using Content.Client.Changelog;
using Content.Client.Chat.Managers;
using Content.Client.Clickable;
-using Content.Client.Options;
using Content.Client.Eui;
using Content.Client.GhostKick;
using Content.Client.Info;
using Content.Client.Launcher;
using Content.Client.Parallax.Managers;
using Content.Client.Players.PlayTimeTracking;
-using Content.Client.Preferences;
using Content.Client.Screenshot;
using Content.Client.Fullscreen;
using Content.Client.Stylesheets;
using Content.Client.Viewport;
using Content.Client.Voting;
-using Content.Shared.Administration;
using Content.Shared.Administration.Logs;
-using Content.Shared.Module;
using Content.Client.Guidebook;
+using Content.Client.Lobby;
using Content.Client.Replay;
using Content.Shared.Administration.Managers;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Preferences;
using Robust.Client;
using Robust.Client.Player;
-using Robust.Shared.Configuration;
using Robust.Shared.Network;
-using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
-namespace Content.Client.Preferences
+namespace Content.Client.Lobby
{
/// <summary>
/// Receives <see cref="PlayerPreferences" /> and <see cref="GameSettings" /> from the server during the initial
-using System;
using Content.Shared.Preferences;
-namespace Content.Client.Preferences
+namespace Content.Client.Lobby
{
public interface IClientPreferencesManager
{
using Content.Client.LateJoin;
using Content.Client.Lobby.UI;
using Content.Client.Message;
-using Content.Client.Preferences;
-using Content.Client.Preferences.UI;
using Content.Client.UserInterface.Systems.Chat;
using Content.Client.Voting;
using Robust.Client;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Configuration;
-using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
- [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IVoteManager _voteManager = default!;
- [Dependency] private readonly IConfigurationManager _configurationManager = default!;
-
- [ViewVariables] private CharacterSetupGui? _characterSetup;
private ClientGameTicker _gameTicker = default!;
private ContentAudioSystem _contentAudioSystem = default!;
protected override Type? LinkedScreenType { get; } = typeof(LobbyGui);
- private LobbyGui? _lobby;
+ public LobbyGui? Lobby;
protected override void Startup()
{
return;
}
- _lobby = (LobbyGui) _userInterfaceManager.ActiveScreen;
+ Lobby = (LobbyGui) _userInterfaceManager.ActiveScreen;
var chatController = _userInterfaceManager.GetUIController<ChatUIController>();
_gameTicker = _entityManager.System<ClientGameTicker>();
_contentAudioSystem = _entityManager.System<ContentAudioSystem>();
_contentAudioSystem.LobbySoundtrackChanged += UpdateLobbySoundtrackInfo;
- _characterSetup = new CharacterSetupGui(_entityManager, _resourceCache, _preferencesManager,
- _prototypeManager, _configurationManager);
- LayoutContainer.SetAnchorPreset(_characterSetup, LayoutContainer.LayoutPreset.Wide);
- _lobby.CharacterSetupState.AddChild(_characterSetup);
chatController.SetMainChat(true);
- _voteManager.SetPopupContainer(_lobby.VoteContainer);
-
- _characterSetup.CloseButton.OnPressed += _ =>
- {
- // Reset sliders etc.
- _characterSetup?.UpdateControls();
-
- var controller = _userInterfaceManager.GetUIController<LobbyUIController>();
- controller.SetClothes(true);
- controller.UpdateProfile();
- _lobby.SwitchState(LobbyGui.LobbyGuiState.Default);
- };
-
- _characterSetup.SaveButton.OnPressed += _ =>
- {
- _characterSetup.Save();
- _userInterfaceManager.GetUIController<LobbyUIController>().ReloadProfile();
- };
-
- LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide);
- _lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you...
+ _voteManager.SetPopupContainer(Lobby.VoteContainer);
+ LayoutContainer.SetAnchorPreset(Lobby, LayoutContainer.LayoutPreset.Wide);
+ Lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you...
UpdateLobbyUi();
- _lobby.CharacterPreview.CharacterSetupButton.OnPressed += OnSetupPressed;
- _lobby.ReadyButton.OnPressed += OnReadyPressed;
- _lobby.ReadyButton.OnToggled += OnReadyToggled;
+ Lobby.CharacterPreview.CharacterSetupButton.OnPressed += OnSetupPressed;
+ Lobby.ReadyButton.OnPressed += OnReadyPressed;
+ Lobby.ReadyButton.OnToggled += OnReadyToggled;
_gameTicker.InfoBlobUpdated += UpdateLobbyUi;
_gameTicker.LobbyStatusUpdated += LobbyStatusUpdated;
_voteManager.ClearPopupContainer();
- _lobby!.CharacterPreview.CharacterSetupButton.OnPressed -= OnSetupPressed;
- _lobby!.ReadyButton.OnPressed -= OnReadyPressed;
- _lobby!.ReadyButton.OnToggled -= OnReadyToggled;
+ Lobby!.CharacterPreview.CharacterSetupButton.OnPressed -= OnSetupPressed;
+ Lobby!.ReadyButton.OnPressed -= OnReadyPressed;
+ Lobby!.ReadyButton.OnToggled -= OnReadyToggled;
- _lobby = null;
+ Lobby = null;
+ }
- _characterSetup?.Dispose();
- _characterSetup = null;
+ public void SwitchState(LobbyGui.LobbyGuiState state)
+ {
+ // Yeah I hate this but LobbyState contains all the badness for now.
+ Lobby?.SwitchState(state);
}
private void OnSetupPressed(BaseButton.ButtonEventArgs args)
{
SetReady(false);
- _lobby!.SwitchState(LobbyGui.LobbyGuiState.CharacterSetup);
+ Lobby?.SwitchState(LobbyGui.LobbyGuiState.CharacterSetup);
}
private void OnReadyPressed(BaseButton.ButtonEventArgs args)
{
if (_gameTicker.IsGameStarted)
{
- _lobby!.StartTime.Text = string.Empty;
+ Lobby!.StartTime.Text = string.Empty;
var roundTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan);
- _lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-time", ("hours", roundTime.Hours), ("minutes", roundTime.Minutes));
+ Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-time", ("hours", roundTime.Hours), ("minutes", roundTime.Minutes));
return;
}
- _lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started");
+ Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started");
string text;
if (_gameTicker.Paused)
}
else if (_gameTicker.StartTime < _gameTiming.CurTime)
{
- _lobby!.StartTime.Text = Loc.GetString("lobby-state-soon");
+ Lobby!.StartTime.Text = Loc.GetString("lobby-state-soon");
return;
}
else
}
}
- _lobby!.StartTime.Text = Loc.GetString("lobby-state-round-start-countdown-text", ("timeLeft", text));
+ Lobby!.StartTime.Text = Loc.GetString("lobby-state-round-start-countdown-text", ("timeLeft", text));
}
private void LobbyStatusUpdated()
private void LobbyLateJoinStatusUpdated()
{
- _lobby!.ReadyButton.Disabled = _gameTicker.DisallowedLateJoin;
+ Lobby!.ReadyButton.Disabled = _gameTicker.DisallowedLateJoin;
}
private void UpdateLobbyUi()
{
if (_gameTicker.IsGameStarted)
{
- _lobby!.ReadyButton.Text = Loc.GetString("lobby-state-ready-button-join-state");
- _lobby!.ReadyButton.ToggleMode = false;
- _lobby!.ReadyButton.Pressed = false;
- _lobby!.ObserveButton.Disabled = false;
+ Lobby!.ReadyButton.Text = Loc.GetString("lobby-state-ready-button-join-state");
+ Lobby!.ReadyButton.ToggleMode = false;
+ Lobby!.ReadyButton.Pressed = false;
+ Lobby!.ObserveButton.Disabled = false;
}
else
{
- _lobby!.StartTime.Text = string.Empty;
- _lobby!.ReadyButton.Text = Loc.GetString(_lobby!.ReadyButton.Pressed ? "lobby-state-player-status-ready": "lobby-state-player-status-not-ready");
- _lobby!.ReadyButton.ToggleMode = true;
- _lobby!.ReadyButton.Disabled = false;
- _lobby!.ReadyButton.Pressed = _gameTicker.AreWeReady;
- _lobby!.ObserveButton.Disabled = true;
+ Lobby!.StartTime.Text = string.Empty;
+ Lobby!.ReadyButton.Text = Loc.GetString(Lobby!.ReadyButton.Pressed ? "lobby-state-player-status-ready": "lobby-state-player-status-not-ready");
+ Lobby!.ReadyButton.ToggleMode = true;
+ Lobby!.ReadyButton.Disabled = false;
+ Lobby!.ReadyButton.Pressed = _gameTicker.AreWeReady;
+ Lobby!.ObserveButton.Disabled = true;
}
if (_gameTicker.ServerInfoBlob != null)
{
- _lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob);
+ Lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob);
}
}
{
if (ev.SoundtrackFilename == null)
{
- _lobby!.LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text"));
+ Lobby!.LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text"));
}
else if (
ev.SoundtrackFilename != null
("songTitle", title),
("songArtist", artist));
- _lobby!.LobbySong.SetMarkup(markup);
+ Lobby!.LobbySong.SetMarkup(markup);
}
}
{
if (_gameTicker.LobbyBackground != null)
{
- _lobby!.Background.Texture = _resourceCache.GetResource<TextureResource>(_gameTicker.LobbyBackground );
+ Lobby!.Background.Texture = _resourceCache.GetResource<TextureResource>(_gameTicker.LobbyBackground );
}
else
{
- _lobby!.Background.Texture = null;
+ Lobby!.Background.Texture = null;
}
}
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.Players.PlayTimeTracking;
using Content.Client.Station;
+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.Preferences;
using Content.Shared.Preferences.Loadouts;
-using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Roles;
+using Content.Shared.Traits;
+using Robust.Client.Player;
+using Robust.Client.ResourceManagement;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
+using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.Manager;
+using Robust.Shared.Utility;
namespace Content.Client.Lobby;
public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState>, IOnStateExited<LobbyState>
{
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
- [Dependency] private readonly IStateManager _stateManager = default!;
+ [Dependency] private readonly IConfigurationManager _configurationManager = default!;
+ [Dependency] private readonly IFileDialogManager _dialogManager = default!;
+ [Dependency] private readonly ILogManager _logManager = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IResourceCache _resourceCache = default!;
+ [Dependency] private readonly IStateManager _stateManager = default!;
+ [Dependency] private readonly JobRequirementsManager _requirements = default!;
+ [Dependency] private readonly MarkingManager _markings = 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.
- */
+ private CharacterSetupGui? _characterSetup;
+ private HumanoidProfileEditor? _profileEditor;
/// <summary>
- /// Preview dummy for role gear.
+ /// This is the characher preview panel in the chat. This should only update if their character updates.
/// </summary>
- private EntityUid? _previewDummy;
+ private LobbyCharacterPreviewPanel? PreviewPanel => GetLobbyPreview();
/// <summary>
- /// If we currently have a job prototype selected.
+ /// This is the modified profile currently being edited.
/// </summary>
- private JobPrototype? _dummyJob;
-
- // TODO: Load the species directly and don't update entity ever.
- public event Action<EntityUid>? PreviewDummyUpdated;
+ private HumanoidCharacterProfile? EditedProfile => _profileEditor?.Profile;
- private HumanoidCharacterProfile? _profile;
+ private int? EditedSlot => _profileEditor?.CharacterSlot;
public override void Initialize()
{
base.Initialize();
+ _prototypeManager.PrototypesReloaded += OnProtoReload;
_preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
- }
+ _requirements.Updated += OnRequirementsUpdated;
- private void PreferencesDataLoaded()
- {
- UpdateProfile();
+ _configurationManager.OnValueChanged(CCVars.FlavorText, args =>
+ {
+ _profileEditor?.RefreshFlavorText();
+ });
+
+ _configurationManager.OnValueChanged(CCVars.GameRoleTimers, args =>
+ {
+ _profileEditor?.RefreshAntags();
+ _profileEditor?.RefreshJobs();
+ _profileEditor?.RefreshLoadouts();
+ });
}
- public void OnStateEntered(LobbyState state)
+ private LobbyCharacterPreviewPanel? GetLobbyPreview()
{
+ if (_stateManager.CurrentState is LobbyState lobby)
+ {
+ return lobby.Lobby?.CharacterPreview;
+ }
+
+ return null;
}
- public void OnStateExited(LobbyState state)
+ private void OnRequirementsUpdated()
{
- EntityManager.DeleteEntity(_previewDummy);
- _previewDummy = null;
+ if (_profileEditor != null)
+ {
+ _profileEditor.RefreshAntags();
+ _profileEditor.RefreshJobs();
+ }
}
- public void SetPreviewPanel(LobbyCharacterPreviewPanel? panel)
+ private void OnProtoReload(PrototypesReloadedEventArgs obj)
{
- _previewPanel = panel;
- ReloadProfile();
+ if (_profileEditor != null)
+ {
+ if (obj.WasModified<AntagPrototype>())
+ {
+ _profileEditor.RefreshAntags();
+ }
+
+ if (obj.WasModified<JobPrototype>() ||
+ obj.WasModified<DepartmentPrototype>())
+ {
+ _profileEditor.RefreshJobs();
+ }
+
+ if (obj.WasModified<LoadoutPrototype>() ||
+ obj.WasModified<LoadoutGroupPrototype>() ||
+ obj.WasModified<RoleLoadoutPrototype>())
+ {
+ _profileEditor.RefreshLoadouts();
+ }
+
+ if (obj.WasModified<SpeciesPrototype>())
+ {
+ _profileEditor.RefreshSpecies();
+ }
+
+ if (obj.WasModified<TraitPrototype>())
+ {
+ _profileEditor.RefreshTraits();
+ }
+ }
}
- public void SetClothes(bool value)
+ private void PreferencesDataLoaded()
{
- if (_showClothes == value)
+ PreviewPanel?.SetLoaded(true);
+
+ if (_stateManager.CurrentState is not LobbyState)
return;
- _showClothes = value;
- ReloadCharacterUI();
+ ReloadCharacterSetup();
}
- public void SetDummyJob(JobPrototype? job)
+ public void OnStateEntered(LobbyState state)
{
- _dummyJob = job;
- ReloadCharacterUI();
+ PreviewPanel?.SetLoaded(_preferencesManager.ServerDataLoaded);
+ ReloadCharacterSetup();
}
- /// <summary>
- /// Updates the character only with the specified profile change.
- /// </summary>
- public void ReloadProfile()
+ public void OnStateExited(LobbyState state)
{
- // Test moment
- if (_profile == null || _stateManager.CurrentState is not LobbyState)
- return;
+ PreviewPanel?.SetLoaded(false);
+ _profileEditor?.Dispose();
+ _characterSetup?.Dispose();
- // 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);
+ _characterSetup = null;
+ _profileEditor = null;
}
/// <summary>
- /// Updates the currently selected character's preview.
+ /// Reloads every single character setup control.
/// </summary>
- public void ReloadCharacterUI()
+ public void ReloadCharacterSetup()
{
- // 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);
+ RefreshLobbyPreview();
+ var (characterGui, profileEditor) = EnsureGui();
+ characterGui.ReloadCharacterPickers();
+ profileEditor.SetProfile(
+ (HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter,
+ _preferencesManager.Preferences?.SelectedCharacterIndex);
}
/// <summary>
- /// Updates character profile to the default.
+ /// Refreshes the character preview in the lobby chat.
/// </summary>
- public void UpdateProfile()
+ private void RefreshLobbyPreview()
{
- if (!_preferencesManager.ServerDataLoaded)
- {
- _profile = null;
+ if (PreviewPanel == null)
return;
- }
- if (_preferencesManager.Preferences?.SelectedCharacter is HumanoidCharacterProfile selectedCharacter)
- {
- _profile = selectedCharacter;
- _previewPanel?.SetLoaded(true);
- }
- else
+ // Get selected character, load it, then set it
+ var character = _preferencesManager.Preferences?.SelectedCharacter;
+
+ if (character is not HumanoidCharacterProfile humanoid)
{
- _previewPanel?.SetSummaryText(string.Empty);
- _previewPanel?.SetLoaded(false);
+ PreviewPanel.SetSprite(EntityUid.Invalid);
+ PreviewPanel.SetSummaryText(string.Empty);
+ return;
}
- ReloadCharacterUI();
+ var dummy = LoadProfileEntity(humanoid, null, true);
+ PreviewPanel.SetSprite(dummy);
+ PreviewPanel.SetSummaryText(humanoid.Summary);
}
- public void UpdateProfile(HumanoidCharacterProfile? profile)
+ private void SaveProfile()
{
- if (_profile?.Equals(profile) == true)
+ DebugTools.Assert(EditedProfile != null);
+
+ if (EditedProfile == null || EditedSlot == null)
return;
- if (_stateManager.CurrentState is not LobbyState)
+ var selected = _preferencesManager.Preferences?.SelectedCharacterIndex;
+
+ if (selected == null)
return;
- _profile = profile;
+ _preferencesManager.UpdateCharacter(EditedProfile, EditedSlot.Value);
+ ReloadCharacterSetup();
}
- private EntityUid EnsurePreviewDummy(HumanoidCharacterProfile profile)
+ private (CharacterSetupGui, HumanoidProfileEditor) EnsureGui()
{
- if (_previewDummy != null)
- return _previewDummy.Value;
+ if (_characterSetup != null && _profileEditor != null)
+ {
+ _characterSetup.Visible = true;
+ _profileEditor.Visible = true;
+ return (_characterSetup, _profileEditor);
+ }
+
+ _profileEditor = new HumanoidProfileEditor(
+ _preferencesManager,
+ _configurationManager,
+ EntityManager,
+ _dialogManager,
+ _logManager,
+ _playerManager,
+ _prototypeManager,
+ _requirements,
+ _markings);
+
+ _characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor);
+
+ _characterSetup.CloseButton.OnPressed += _ =>
+ {
+ // Reset sliders etc.
+ _profileEditor.SetProfile(null, null);
+ _profileEditor.Visible = false;
+
+ if (_stateManager.CurrentState is LobbyState lobbyGui)
+ {
+ lobbyGui.SwitchState(LobbyGui.LobbyGuiState.Default);
+ }
+ };
+
+ _profileEditor.Save += SaveProfile;
+
+ _characterSetup.SelectCharacter += args =>
+ {
+ _preferencesManager.SelectCharacter(args);
+ ReloadCharacterSetup();
+ };
+
+ _characterSetup.DeleteCharacter += args =>
+ {
+ _preferencesManager.DeleteCharacter(args);
- _previewDummy = EntityManager.SpawnEntity(_prototypeManager.Index<SpeciesPrototype>(profile.Species).DollPrototype, MapCoordinates.Nullspace);
- PreviewDummyUpdated?.Invoke(_previewDummy.Value);
- return _previewDummy.Value;
+ // Reload everything
+ if (EditedSlot == args)
+ {
+ ReloadCharacterSetup();
+ }
+ else
+ {
+ // Only need to reload character pickers
+ _characterSetup?.ReloadCharacterPickers();
+ }
+ };
+
+ if (_stateManager.CurrentState is LobbyState lobby)
+ {
+ lobby.Lobby?.CharacterSetupState.AddChild(_characterSetup);
+ }
+
+ return (_characterSetup, _profileEditor);
}
+ #region Helpers
+
/// <summary>
/// Applies the highest priority job's clothes to the dummy.
/// </summary>
- public void GiveDummyJobClothesLoadout(EntityUid dummy, HumanoidCharacterProfile profile)
+ public void GiveDummyJobClothesLoadout(EntityUid dummy, JobPrototype? jobProto, HumanoidCharacterProfile profile)
{
- var job = _dummyJob ?? GetPreferredJob(profile);
+ var job = jobProto ?? GetPreferredJob(profile);
GiveDummyJobClothes(dummy, profile, job);
if (_prototypeManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID)))
{
- var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), EntityManager, _prototypeManager);
+ var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), profile.Species, EntityManager, _prototypeManager);
GiveDummyLoadout(dummy, loadout);
}
}
}
}
- public EntityUid? GetPreviewDummy()
+ /// <summary>
+ /// Loads the profile onto a dummy entity.
+ /// </summary>
+ public EntityUid LoadProfileEntity(HumanoidCharacterProfile? humanoid, JobPrototype? job, bool jobClothes)
{
- return _previewDummy;
+ EntityUid dummyEnt;
+
+ if (humanoid is not null)
+ {
+ var dummy = _prototypeManager.Index<SpeciesPrototype>(humanoid.Species).DollPrototype;
+ dummyEnt = EntityManager.SpawnEntity(dummy, MapCoordinates.Nullspace);
+ }
+ else
+ {
+ dummyEnt = EntityManager.SpawnEntity(_prototypeManager.Index<SpeciesPrototype>(SharedHumanoidAppearanceSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace);
+ }
+
+ _humanoid.LoadProfile(dummyEnt, humanoid);
+
+ if (humanoid != null && jobClothes)
+ {
+ job ??= GetPreferredJob(humanoid);
+ GiveDummyJobClothes(dummyEnt, humanoid, job);
+
+ if (_prototypeManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID)))
+ {
+ var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), humanoid.Species, EntityManager, _prototypeManager);
+ GiveDummyLoadout(dummyEnt, loadout);
+ }
+ }
+
+ return dummyEnt;
}
+
+ #endregion
}
--- /dev/null
+<ContainerButton xmlns="https://spacestation14.io"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:style="clr-namespace:Content.Client.Stylesheets">
+ <BoxContainer Orientation="Horizontal"
+ HorizontalExpand="True"
+ SeparationOverride="0"
+ Name="InternalHBox">
+ <SpriteView Scale="2 2"
+ OverrideDirection="South"
+ Name="View"/>
+ <Label Name="DescriptionLabel"
+ ClipText="True"
+ HorizontalExpand="True"/>
+ <Button Name="DeleteButton"
+ Text="{Loc 'character-setup-gui-character-picker-button-delete-button'}"/>
+ <Button Name="ConfirmDeleteButton"
+ Text="{Loc 'character-setup-gui-character-picker-button-confirm-delete-button'}"
+ Visible="False"
+ ModulateSelfOverride="{x:Static style:StyleNano.ButtonColorCautionDefault}"/>
+
+ </BoxContainer>
+</ContainerButton>
--- /dev/null
+using System.Linq;
+using Content.Client.Humanoid;
+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.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Lobby.UI;
+
+/// <summary>
+/// Holds character data on the side of the setup GUI.
+/// </summary>
+[GenerateTypedNameReferences]
+public sealed partial class CharacterPickerButton : ContainerButton
+{
+ private IEntityManager _entManager;
+
+ private EntityUid _previewDummy;
+
+ /// <summary>
+ /// Invoked if we should delete the attached character
+ /// </summary>
+ public event Action? OnDeletePressed;
+
+ public CharacterPickerButton(
+ IEntityManager entityManager,
+ IPrototypeManager prototypeManager,
+ ButtonGroup group,
+ ICharacterProfile profile,
+ bool isSelected)
+ {
+ RobustXamlLoader.Load(this);
+ _entManager = entityManager;
+ AddStyleClass(StyleClassButton);
+ ToggleMode = true;
+ Group = group;
+ var description = profile.Name;
+
+ if (profile is not HumanoidCharacterProfile humanoid)
+ {
+ _previewDummy = entityManager.SpawnEntity(prototypeManager.Index<SpeciesPrototype>(SharedHumanoidAppearanceSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace);
+ }
+ else
+ {
+ _previewDummy = UserInterfaceManager.GetUIController<LobbyUIController>()
+ .LoadProfileEntity(humanoid, null, true);
+
+ var highPriorityJob = humanoid.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key;
+ if (highPriorityJob != null)
+ {
+ var jobName = prototypeManager.Index<JobPrototype>(highPriorityJob).LocalizedName;
+ description = $"{description}\n{jobName}";
+ }
+ }
+
+ Pressed = isSelected;
+ DeleteButton.Visible = !isSelected;
+
+ View.SetEntity(_previewDummy);
+ DescriptionLabel.Text = description;
+
+ ConfirmDeleteButton.OnPressed += _ =>
+ {
+ Parent?.RemoveChild(this);
+ Parent?.RemoveChild(ConfirmDeleteButton);
+ OnDeletePressed?.Invoke();
+ };
+
+ DeleteButton.OnPressed += _ =>
+ {
+ DeleteButton.Visible = false;
+ ConfirmDeleteButton.Visible = true;
+ };
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+
+ _entManager.DeleteEntity(_previewDummy);
+ _previewDummy = default;
+ }
+}
<Button Name="RulesButton"
Text="{Loc 'character-setup-gui-character-setup-rules-button'}"
StyleClasses="ButtonBig"/>
- <Button Name="SaveButton"
- Access="Public"
- Text="{Loc 'character-setup-gui-character-setup-save-button'}"
- StyleClasses="ButtonBig"/>
<Button Name="CloseButton"
Access="Public"
Text="{Loc 'character-setup-gui-character-setup-close-button'}"
--- /dev/null
+using Content.Client.Info;
+using Content.Client.Info.PlaytimeStats;
+using Content.Client.Resources;
+using Content.Shared.Preferences;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Lobby.UI
+{
+ /// <summary>
+ /// Holds the entire character setup GUI, from character picks to individual character editing.
+ /// </summary>
+ [GenerateTypedNameReferences]
+ public sealed partial class CharacterSetupGui : Control
+ {
+ private readonly IClientPreferencesManager _preferencesManager;
+ private readonly IEntityManager _entManager;
+ private readonly IPrototypeManager _protomanager;
+
+ private readonly Button _createNewCharacterButton;
+
+ public event Action<int>? SelectCharacter;
+ public event Action<int>? DeleteCharacter;
+
+ public CharacterSetupGui(
+ IEntityManager entManager,
+ IPrototypeManager protoManager,
+ IResourceCache resourceCache,
+ IClientPreferencesManager preferencesManager,
+ HumanoidProfileEditor profileEditor)
+ {
+ RobustXamlLoader.Load(this);
+ _preferencesManager = preferencesManager;
+ _entManager = entManager;
+ _protomanager = protoManager;
+
+ var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
+ var back = new StyleBoxTexture
+ {
+ Texture = panelTex,
+ Modulate = new Color(37, 37, 42)
+ };
+ back.SetPatchMargin(StyleBox.Margin.All, 10);
+
+ BackgroundPanel.PanelOverride = back;
+
+ _createNewCharacterButton = new Button
+ {
+ Text = Loc.GetString("character-setup-gui-create-new-character-button"),
+ };
+
+ _createNewCharacterButton.OnPressed += args =>
+ {
+ preferencesManager.CreateCharacter(HumanoidCharacterProfile.Random());
+ ReloadCharacterPickers();
+ args.Event.Handle();
+ };
+
+ CharEditor.AddChild(profileEditor);
+ RulesButton.OnPressed += _ => new RulesAndInfoWindow().Open();
+
+ StatsButton.OnPressed += _ => new PlaytimeStatsWindow().OpenCentered();
+ }
+
+ /// <summary>
+ /// Disposes and reloads all character picker buttons from the preferences data.
+ /// </summary>
+ public void ReloadCharacterPickers()
+ {
+ _createNewCharacterButton.Orphan();
+ Characters.DisposeAllChildren();
+
+ var numberOfFullSlots = 0;
+ var characterButtonsGroup = new ButtonGroup();
+
+ if (!_preferencesManager.ServerDataLoaded)
+ {
+ return;
+ }
+
+ _createNewCharacterButton.ToolTip =
+ Loc.GetString("character-setup-gui-create-new-character-button-tooltip",
+ ("maxCharacters", _preferencesManager.Settings!.MaxCharacterSlots));
+
+ var selectedSlot = _preferencesManager.Preferences?.SelectedCharacterIndex;
+
+ foreach (var (slot, character) in _preferencesManager.Preferences!.Characters)
+ {
+ numberOfFullSlots++;
+ var characterPickerButton = new CharacterPickerButton(_entManager,
+ _protomanager,
+ characterButtonsGroup,
+ character,
+ slot == selectedSlot);
+
+ Characters.AddChild(characterPickerButton);
+
+ characterPickerButton.OnPressed += args =>
+ {
+ SelectCharacter?.Invoke(slot);
+ };
+
+ characterPickerButton.OnDeletePressed += () =>
+ {
+ DeleteCharacter?.Invoke(slot);
+ };
+ }
+
+ _createNewCharacterButton.Disabled = numberOfFullSlots >= _preferencesManager.Settings.MaxCharacterSlots;
+ Characters.AddChild(_createNewCharacterButton);
+ }
+ }
+}
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-namespace Content.Client.Preferences.UI;
+namespace Content.Client.Lobby.UI;
[GenerateTypedNameReferences]
public sealed partial class HighlightedContainer : PanelContainer
<BoxContainer xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:prefUi="clr-namespace:Content.Client.Preferences.UI"
xmlns:humanoid="clr-namespace:Content.Client.Humanoid"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
+ xmlns:ui="clr-namespace:Content.Client.Lobby.UI"
HorizontalExpand="True">
<!-- Left side -->
<BoxContainer Orientation="Vertical" Margin="10 10 10 10" HorizontalExpand="True">
<BoxContainer Orientation="Horizontal" SeparationOverride="10">
<!-- Name box-->
<BoxContainer Orientation="Vertical">
- <prefUi:HighlightedContainer>
+ <ui:HighlightedContainer>
<BoxContainer Orientation="Vertical">
- <prefUi:HighlightedContainer>
+ <ui:HighlightedContainer>
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-name-label'}" />
- <LineEdit Name="CNameEdit" MinSize="270 0" VerticalAlignment="Center" Margin="5 0 0 0" />
- <Button Name="CNameRandomize" Text="{Loc 'humanoid-profile-editor-name-random-button'}" />
+ <LineEdit Name="NameEdit" MinSize="270 0" VerticalAlignment="Center" Margin="5 0 0 0" />
+ <Button Name="NameRandomize" Text="{Loc 'humanoid-profile-editor-name-random-button'}" />
</BoxContainer>
- <Button Name="CRandomizeEverything" HorizontalAlignment="Center"
+ <Button Name="RandomizeEverythingButton" HorizontalAlignment="Center"
HorizontalExpand="False" MaxWidth="256"
Text="{Loc 'humanoid-profile-editor-randomize-everything-button'}" />
- <RichTextLabel Name="CWarningLabel" HorizontalExpand="False"
+ <RichTextLabel Name="WarningLabel" HorizontalExpand="False"
VerticalExpand="True" MaxWidth="425"
HorizontalAlignment="Left" />
</BoxContainer>
- </prefUi:HighlightedContainer>
+ </ui:HighlightedContainer>
</BoxContainer>
- </prefUi:HighlightedContainer>
+ </ui:HighlightedContainer>
</BoxContainer>
<!-- Import/Export -->
<BoxContainer Orientation="Vertical">
- <prefUi:HighlightedContainer>
- <BoxContainer Orientation="Horizontal">
- <Button Text="{Loc 'humanoid-profile-editor-import-button'}" Disabled="True"
- ToolTip="{Loc 'generic-not-yet-implemented'}" />
- <Button Text="{Loc 'humanoid-profile-editor-export-button'}" Disabled="True"
- ToolTip="{Loc 'generic-not-yet-implemented'}" />
+ <ui:HighlightedContainer>
+ <BoxContainer Orientation="Horizontal" SeparationOverride="5">
+ <Button Name="ImportButton" Text="{Loc 'humanoid-profile-editor-import-button'}"/>
+ <Button Name="ExportButton" Text="{Loc 'humanoid-profile-editor-export-button'}"/>
+ <Button Name="SaveButton" Text="{Loc 'humanoid-profile-editor-save-button'}" />
+ <Button Name="ResetButton" Disabled="True" Text="{Loc 'humanoid-profile-editor-reset-button'}"/>
</BoxContainer>
- </prefUi:HighlightedContainer>
- <!-- Save -->
- <prefUi:HighlightedContainer>
- <Button Name="CSaveButton" Text="{Loc 'humanoid-profile-editor-save-button'}" HorizontalAlignment="Center" />
- </prefUi:HighlightedContainer>
+ </ui:HighlightedContainer>
</BoxContainer>
</BoxContainer>
<Control MinHeight="10" />
<!-- tabContainer -->
- <TabContainer Name="CTabContainer" VerticalExpand="True">
+ <TabContainer Name="TabContainer" VerticalExpand="True">
<BoxContainer Orientation="Vertical">
<ScrollContainer VerticalExpand="True">
<!-- appearanceList -->
<TextureButton Name="SpeciesInfoButton" Scale="0.3 0.3"
VerticalAlignment="Center"
ToolTip="{Loc 'humanoid-profile-editor-guidebook-button-tooltip'}"/>
- <OptionButton Name="CSpeciesButton" HorizontalAlignment="Right" />
+ <OptionButton Name="SpeciesButton" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Age -->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-age-label'}" />
<Control HorizontalExpand="True"/>
- <LineEdit Name="CAgeEdit" MinSize="40 0" HorizontalAlignment="Right" />
+ <LineEdit Name="AgeEdit" MinSize="40 0" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Sex -->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-sex-label'}" />
<Control HorizontalExpand="True"/>
- <OptionButton Name="CSexButton" HorizontalAlignment="Right" />
+ <OptionButton Name="SexButton" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Pronouns -->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-pronouns-label'}" />
<Control HorizontalExpand="True"/>
- <OptionButton Name="CPronounsButton" HorizontalAlignment="Right" />
+ <OptionButton Name="PronounsButton" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Show clothing -->
<BoxContainer HorizontalExpand="True">
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-spawn-priority-label'}" />
<Control HorizontalExpand="True"/>
- <OptionButton Name="CSpawnPriorityButton" HorizontalAlignment="Right" />
+ <OptionButton Name="SpawnPriorityButton" HorizontalAlignment="Right" />
</BoxContainer>
</BoxContainer>
<!-- Skin -->
<BoxContainer Margin="10" HorizontalExpand="True" Orientation="Vertical">
<Label Text="{Loc 'humanoid-profile-editor-skin-color-label'}" />
- <Slider HorizontalExpand="True" Name="CSkin" MinValue="0" MaxValue="100" Value="20" />
- <BoxContainer Name="CRgbSkinColorContainer" Visible="False" Orientation="Vertical" HorizontalExpand="True"></BoxContainer>
+ <Slider HorizontalExpand="True" Name="Skin" MinValue="0" MaxValue="100" Value="20" />
+ <BoxContainer Name="RgbSkinColorContainer" Visible="False" Orientation="Vertical" HorizontalExpand="True"></BoxContainer>
</BoxContainer>
<!-- Hair -->
<BoxContainer Margin="10" Orientation="Horizontal">
- <humanoid:SingleMarkingPicker Name="CHairStylePicker" Category="Hair" />
- <humanoid:SingleMarkingPicker Name="CFacialHairPicker" Category="FacialHair" />
+ <humanoid:SingleMarkingPicker Name="HairStylePicker" Category="Hair" />
+ <humanoid:SingleMarkingPicker Name="FacialHairPicker" Category="FacialHair" />
</BoxContainer>
<!-- Eyes -->
<BoxContainer Margin="10" Orientation="Vertical">
<Label Text="{Loc 'humanoid-profile-editor-eyes-label'}" />
- <humanoid:EyeColorPicker Name="CEyeColorPicker" />
+ <humanoid:EyeColorPicker Name="EyeColorPicker" />
</BoxContainer>
</BoxContainer>
</ScrollContainer>
</BoxContainer>
<BoxContainer Orientation="Vertical">
<!-- Jobs -->
- <OptionButton Name="CPreferenceUnavailableButton" />
+ <OptionButton Name="PreferenceUnavailableButton" />
<ScrollContainer VerticalExpand="True">
- <BoxContainer Name="CJobList" Orientation="Vertical" />
+ <BoxContainer Name="JobList" Orientation="Vertical" />
</ScrollContainer>
</BoxContainer>
<BoxContainer Orientation="Vertical" Margin="10">
<!-- Antags -->
<ScrollContainer VerticalExpand="True">
- <BoxContainer Name="CAntagList" Orientation="Vertical" />
+ <BoxContainer Name="AntagList" Orientation="Vertical" />
</ScrollContainer>
</BoxContainer>
<BoxContainer Orientation="Vertical" Margin="10">
<!-- Traits -->
<ScrollContainer VerticalExpand="True">
- <BoxContainer Name="CTraitsList" Orientation="Vertical" />
+ <BoxContainer Name="TraitsList" Orientation="Vertical" />
</ScrollContainer>
</BoxContainer>
- <BoxContainer Name="CMarkingsTab" Orientation="Vertical" Margin="10">
+ <BoxContainer Name="MarkingsTab" Orientation="Vertical" Margin="10">
<!-- Markings -->
<ScrollContainer VerticalExpand="True">
- <humanoid:MarkingPicker Name="CMarkings" IgnoreCategories="Hair,FacialHair" />
+ <humanoid:MarkingPicker Name="Markings" IgnoreCategories="Hair,FacialHair" />
</ScrollContainer>
</BoxContainer>
</TabContainer>
</BoxContainer>
<!-- Right side -->
<BoxContainer Orientation="Vertical" VerticalExpand="True" VerticalAlignment="Center">
- <SpriteView Name="CSpriteView" Scale="8 8" SizeFlagsStretchRatio="1" />
+ <SpriteView Name="SpriteView" Scale="8 8" SizeFlagsStretchRatio="1" />
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 5">
- <Button Name="CSpriteRotateLeft" Text="◀" StyleClasses="OpenRight" />
+ <Button Name="SpriteRotateLeft" Text="◀" StyleClasses="OpenRight" />
<cc:VSeparator Margin="2 0 3 0" />
- <Button Name="CSpriteRotateRight" Text="▶" StyleClasses="OpenLeft" />
+ <Button Name="SpriteRotateRight" Text="▶" StyleClasses="OpenLeft" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
+using System.IO;
using System.Linq;
using System.Numerics;
using Content.Client.Guidebook;
using Content.Client.Humanoid;
-using Content.Client.Lobby;
+using Content.Client.Lobby.UI.Loadouts;
+using Content.Client.Lobby.UI.Roles;
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.Humanoid.Prototypes;
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.Graphics;
+using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
using Direction = Robust.Shared.Maths.Direction;
-namespace Content.Client.Preferences.UI
+namespace Content.Client.Lobby.UI
{
[GenerateTypedNameReferences]
public sealed partial class HumanoidProfileEditor : BoxContainer
{
private readonly IClientPreferencesManager _preferencesManager;
+ private readonly IConfigurationManager _cfgManager;
+ private readonly IEntityManager _entManager;
+ private readonly IFileDialogManager _dialogManager;
+ private readonly IPlayerManager _playerManager;
private readonly IPrototypeManager _prototypeManager;
private readonly MarkingManager _markingManager;
private readonly JobRequirementsManager _requirements;
+ private readonly LobbyUIController _controller;
- private LineEdit _ageEdit => CAgeEdit;
- private LineEdit _nameEdit => CNameEdit;
+ private FlavorText.FlavorText? _flavorText;
private TextEdit? _flavorTextEdit;
- private Button _nameRandomButton => CNameRandomize;
- private Button _randomizeEverythingButton => CRandomizeEverything;
- private RichTextLabel _warningLabel => CWarningLabel;
- private Button _saveButton => CSaveButton;
- private OptionButton _sexButton => CSexButton;
- private OptionButton _genderButton => CPronounsButton;
- private Slider _skinColor => CSkin;
- private OptionButton _spawnPriorityButton => CSpawnPriorityButton;
- private SingleMarkingPicker _hairPicker => CHairStylePicker;
- private SingleMarkingPicker _facialHairPicker => CFacialHairPicker;
- private EyeColorPicker _eyesPicker => CEyeColorPicker;
-
- private TabContainer _tabContainer => CTabContainer;
- private BoxContainer _jobList => CJobList;
- private BoxContainer _antagList => CAntagList;
- private BoxContainer _traitsList => CTraitsList;
- private readonly List<JobPrioritySelector> _jobPriorities;
- private OptionButton _preferenceUnavailableButton => CPreferenceUnavailableButton;
+
+ // One at a time.
+ private LoadoutWindow? _loadoutWindow;
+
+ private bool _exporting;
+
+ /// <summary>
+ /// If we're attempting to save.
+ /// </summary>
+ public event Action? Save;
+
+ /// <summary>
+ /// Entity used for the profile editor preview
+ /// </summary>
+ public EntityUid PreviewDummy;
+
+ /// <summary>
+ /// Temporary override of their selected job, used to preview roles.
+ /// </summary>
+ public JobPrototype? JobOverride;
+
+ /// <summary>
+ /// The character slot for the current profile.
+ /// </summary>
+ public int? CharacterSlot;
+
+ /// <summary>
+ /// The work in progress profile being edited.
+ /// </summary>
+ public HumanoidCharacterProfile? Profile;
+
+ private List<SpeciesPrototype> _species = new();
+
+ private List<(string, RequirementsSelector)> _jobPriorities = new();
+
private readonly Dictionary<string, BoxContainer> _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<SpeciesPrototype> _speciesList;
- private readonly List<AntagPreferenceSelector> _antagPreferences = new();
- private readonly List<TraitPreferenceSelector> _traitPreferences;
-
- private SpriteView _previewSpriteView => CSpriteView;
- private Button _previewRotateLeftButton => CSpriteRotateLeft;
- private Button _previewRotateRightButton => CSpriteRotateRight;
+
private Direction _previewRotation = Direction.North;
- private BoxContainer _rgbSkinColorContainer => CRgbSkinColorContainer;
private ColorSelectorSliders _rgbSkinColorSelector;
private bool _isDirty;
- public int CharacterSlot;
- public HumanoidCharacterProfile? Profile;
-
- public event Action<HumanoidCharacterProfile, int>? OnProfileChanged;
[ValidatePrototypeId<GuideEntryPrototype>]
private const string DefaultSpeciesGuidebook = "Species";
- public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager, IConfigurationManager configurationManager)
+ private ISawmill _sawmill;
+
+ public HumanoidProfileEditor(
+ IClientPreferencesManager preferencesManager,
+ IConfigurationManager configurationManager,
+ IEntityManager entManager,
+ IFileDialogManager dialogManager,
+ ILogManager logManager,
+ IPlayerManager playerManager,
+ IPrototypeManager prototypeManager,
+ JobRequirementsManager requirements,
+ MarkingManager markings)
{
RobustXamlLoader.Load(this);
+ _sawmill = logManager.GetSawmill("profile.editor");
+ _cfgManager = configurationManager;
+ _entManager = entManager;
+ _dialogManager = dialogManager;
+ _playerManager = playerManager;
_prototypeManager = prototypeManager;
+ _markingManager = markings;
_preferencesManager = preferencesManager;
- _markingManager = IoCManager.Resolve<MarkingManager>();
- var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
- controller.PreviewDummyUpdated += OnDummyUpdate;
+ _requirements = requirements;
+ _controller = UserInterfaceManager.GetUIController<LobbyUIController>();
+
+ ImportButton.OnPressed += args =>
+ {
+ ImportProfile();
+ };
+
+ ExportButton.OnPressed += args =>
+ {
+ ExportProfile();
+ };
- _previewSpriteView.SetEntity(controller.GetPreviewDummy());
+ ResetButton.OnPressed += args =>
+ {
+ SetProfile((HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter, _preferencesManager.Preferences?.SelectedCharacterIndex);
+ };
+
+ SaveButton.OnPressed += args =>
+ {
+ Save?.Invoke();
+ };
#region Left
#region Name
- _nameEdit.OnTextChanged += args => { SetName(args.Text); };
- _nameRandomButton.OnPressed += args => RandomizeName();
- _randomizeEverythingButton.OnPressed += args => { RandomizeEverything(); };
- _warningLabel.SetMarkup($"[color=red]{Loc.GetString("humanoid-profile-editor-naming-rules-warning")}[/color]");
+ NameEdit.OnTextChanged += args => { SetName(args.Text); };
+ NameRandomize.OnPressed += args => RandomizeName();
+ RandomizeEverythingButton.OnPressed += args => { RandomizeEverything(); };
+ WarningLabel.SetMarkup($"[color=red]{Loc.GetString("humanoid-profile-editor-naming-rules-warning")}[/color]");
#endregion Name
#region Appearance
- _tabContainer.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-appearance-tab"));
+ TabContainer.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-appearance-tab"));
#region Sex
- _sexButton.OnItemSelected += args =>
+ SexButton.OnItemSelected += args =>
{
- _sexButton.SelectId(args.Id);
+ SexButton.SelectId(args.Id);
SetSex((Sex) args.Id);
};
#region Age
- _ageEdit.OnTextChanged += args =>
+ AgeEdit.OnTextChanged += args =>
{
if (!int.TryParse(args.Text, out var newAge))
return;
+
SetAge(newAge);
};
#region Gender
- _genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-male-text"), (int) Gender.Male);
- _genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-female-text"), (int) Gender.Female);
- _genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-epicene-text"), (int) Gender.Epicene);
- _genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-neuter-text"), (int) Gender.Neuter);
+ PronounsButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-male-text"), (int) Gender.Male);
+ PronounsButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-female-text"), (int) Gender.Female);
+ PronounsButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-epicene-text"), (int) Gender.Epicene);
+ PronounsButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-neuter-text"), (int) Gender.Neuter);
- _genderButton.OnItemSelected += args =>
+ PronounsButton.OnItemSelected += args =>
{
- _genderButton.SelectId(args.Id);
+ PronounsButton.SelectId(args.Id);
SetGender((Gender) args.Id);
};
#endregion Gender
- #region Species
-
- _speciesList = prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(o => o.RoundStart).ToList();
- for (var i = 0; i < _speciesList.Count; i++)
- {
- var name = Loc.GetString(_speciesList[i].Name);
- CSpeciesButton.AddItem(name, i);
- }
+ RefreshSpecies();
- CSpeciesButton.OnItemSelected += args =>
+ SpeciesButton.OnItemSelected += args =>
{
- CSpeciesButton.SelectId(args.Id);
- SetSpecies(_speciesList[args.Id].ID);
+ SpeciesButton.SelectId(args.Id);
+ SetSpecies(_species[args.Id].ID);
UpdateHairPickers();
OnSkinColorOnValueChanged();
};
- #endregion Species
-
#region Skin
-
- _skinColor.OnValueChanged += _ =>
+ Skin.OnValueChanged += _ =>
{
OnSkinColorOnValueChanged();
};
- _rgbSkinColorContainer.AddChild(_rgbSkinColorSelector = new ColorSelectorSliders());
+ RgbSkinColorContainer.AddChild(_rgbSkinColorSelector = new ColorSelectorSliders());
_rgbSkinColorSelector.OnColorChanged += _ =>
{
OnSkinColorOnValueChanged();
#region Hair
- _hairPicker.OnMarkingSelect += newStyle =>
+ HairStylePicker.OnMarkingSelect += newStyle =>
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithHairStyleName(newStyle.id));
- SetDirty();
+ ReloadPreview();
};
- _hairPicker.OnColorChanged += newColor =>
+ HairStylePicker.OnColorChanged += newColor =>
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
UpdateCMarkingsHair();
- SetDirty();
+ ReloadPreview();
};
- _facialHairPicker.OnMarkingSelect += newStyle =>
+ FacialHairPicker.OnMarkingSelect += newStyle =>
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithFacialHairStyleName(newStyle.id));
- SetDirty();
+ ReloadPreview();
};
- _facialHairPicker.OnColorChanged += newColor =>
+ FacialHairPicker.OnColorChanged += newColor =>
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
UpdateCMarkingsFacialHair();
- SetDirty();
+ ReloadPreview();
};
- _hairPicker.OnSlotRemove += _ =>
+ HairStylePicker.OnSlotRemove += _ =>
{
if (Profile is null)
return;
);
UpdateHairPickers();
UpdateCMarkingsHair();
- SetDirty();
+ ReloadPreview();
};
- _facialHairPicker.OnSlotRemove += _ =>
+ FacialHairPicker.OnSlotRemove += _ =>
{
if (Profile is null)
return;
);
UpdateHairPickers();
UpdateCMarkingsFacialHair();
- SetDirty();
+ ReloadPreview();
};
- _hairPicker.OnSlotAdd += delegate()
+ HairStylePicker.OnSlotAdd += delegate()
{
if (Profile is null)
return;
UpdateHairPickers();
UpdateCMarkingsHair();
- SetDirty();
+ ReloadPreview();
};
- _facialHairPicker.OnSlotAdd += delegate()
+ FacialHairPicker.OnSlotAdd += delegate()
{
if (Profile is null)
return;
UpdateHairPickers();
UpdateCMarkingsFacialHair();
- SetDirty();
+ ReloadPreview();
};
#endregion Hair
foreach (var value in Enum.GetValues<SpawnPriorityPreference>())
{
- _spawnPriorityButton.AddItem(Loc.GetString($"humanoid-profile-editor-preference-spawn-priority-{value.ToString().ToLower()}"), (int) value);
+ SpawnPriorityButton.AddItem(Loc.GetString($"humanoid-profile-editor-preference-spawn-priority-{value.ToString().ToLower()}"), (int) value);
}
- _spawnPriorityButton.OnItemSelected += args =>
+ SpawnPriorityButton.OnItemSelected += args =>
{
- _spawnPriorityButton.SelectId(args.Id);
+ SpawnPriorityButton.SelectId(args.Id);
SetSpawnPriority((SpawnPriorityPreference) args.Id);
};
#region Eyes
- _eyesPicker.OnEyeColorPicked += newColor =>
+ EyeColorPicker.OnEyeColorPicked += newColor =>
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithEyeColor(newColor));
- CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor;
- SetDirty();
+ Markings.CurrentEyeColor = Profile.Appearance.EyeColor;
+ ReloadProfilePreview();
};
#endregion Eyes
#region Jobs
- _tabContainer.SetTabTitle(1, Loc.GetString("humanoid-profile-editor-jobs-tab"));
+ TabContainer.SetTabTitle(1, Loc.GetString("humanoid-profile-editor-jobs-tab"));
- _preferenceUnavailableButton.AddItem(
+ PreferenceUnavailableButton.AddItem(
Loc.GetString("humanoid-profile-editor-preference-unavailable-stay-in-lobby-button"),
(int) PreferenceUnavailableMode.StayInLobby);
- _preferenceUnavailableButton.AddItem(
+ PreferenceUnavailableButton.AddItem(
Loc.GetString("humanoid-profile-editor-preference-unavailable-spawn-as-overflow-button",
("overflowJob", Loc.GetString(SharedGameTicker.FallbackOverflowJobName))),
(int) PreferenceUnavailableMode.SpawnAsOverflow);
- _preferenceUnavailableButton.OnItemSelected += args =>
+ PreferenceUnavailableButton.OnItemSelected += args =>
{
- _preferenceUnavailableButton.SelectId(args.Id);
-
+ PreferenceUnavailableButton.SelectId(args.Id);
Profile = Profile?.WithPreferenceUnavailable((PreferenceUnavailableMode) args.Id);
- SetDirty();
};
- _jobPriorities = new List<JobPrioritySelector>();
_jobCategories = new Dictionary<string, BoxContainer>();
- _requirements = IoCManager.Resolve<JobRequirementsManager>();
- // TODO: Move this to the LobbyUIController instead of being spaghetti everywhere.
- _requirements.Updated += UpdateAntagRequirements;
- _requirements.Updated += UpdateRoleRequirements;
- UpdateAntagRequirements();
- UpdateRoleRequirements();
+
+ RefreshAntags();
+ RefreshJobs();
#endregion Jobs
- _tabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab"));
+ TabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab"));
+
+ RefreshTraits();
- #region Traits
+ #region Markings
- var traits = prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => Loc.GetString(t.Name)).ToList();
- _traitPreferences = new List<TraitPreferenceSelector>();
- _tabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
+ TabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-markings-tab"));
+
+ Markings.OnMarkingAdded += OnMarkingChange;
+ Markings.OnMarkingRemoved += OnMarkingChange;
+ Markings.OnMarkingColorChange += OnMarkingChange;
+ Markings.OnMarkingRankChange += OnMarkingChange;
+
+ #endregion Markings
+
+ RefreshFlavorText();
+
+ #region Dummy
+
+ SpriteRotateLeft.OnPressed += _ =>
+ {
+ _previewRotation = _previewRotation.TurnCw();
+ SetPreviewRotation(_previewRotation);
+ };
+ SpriteRotateRight.OnPressed += _ =>
+ {
+ _previewRotation = _previewRotation.TurnCcw();
+ SetPreviewRotation(_previewRotation);
+ };
+
+ #endregion Dummy
+
+ #endregion Left
+
+ ShowClothes.OnToggled += args =>
+ {
+ ReloadPreview();
+ };
+
+ SpeciesInfoButton.OnPressed += OnSpeciesInfoButtonPressed;
+
+ UpdateSpeciesGuidebookIcon();
+ ReloadPreview();
+ IsDirty = false;
+ }
+
+ /// <summary>
+ /// Refreshes the flavor text editor status.
+ /// </summary>
+ public void RefreshFlavorText()
+ {
+ if (_cfgManager.GetCVar(CCVars.FlavorText))
+ {
+ if (_flavorText != null)
+ return;
+
+ _flavorText = new FlavorText.FlavorText();
+ TabContainer.AddChild(_flavorText);
+ TabContainer.SetTabTitle(TabContainer.ChildCount - 1, Loc.GetString("humanoid-profile-editor-flavortext-tab"));
+ _flavorTextEdit = _flavorText.CFlavorTextInput;
+
+ _flavorText.OnFlavorTextChanged += OnFlavorTextChange;
+ }
+ else
+ {
+ if (_flavorText == null)
+ return;
+
+ TabContainer.RemoveChild(_flavorText);
+ _flavorText.OnFlavorTextChanged -= OnFlavorTextChange;
+ _flavorText.Dispose();
+ _flavorTextEdit?.Dispose();
+ _flavorTextEdit = null;
+ _flavorText = null;
+ }
+ }
+
+ /// <summary>
+ /// Refreshes traits selector
+ /// </summary>
+ public void RefreshTraits()
+ {
+ TraitsList.DisposeAllChildren();
+
+ var traits = _prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => Loc.GetString(t.Name)).ToList();
+ TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
if (traits.Count > 0)
{
foreach (var trait in traits)
{
var selector = new TraitPreferenceSelector(trait);
- _traitsList.AddChild(selector);
- _traitPreferences.Add(selector);
+
+ if (Profile?.TraitPreferences.Contains(trait.ID) == true)
+ {
+ selector.Preference = true;
+ }
+ else
+ {
+ selector.Preference = false;
+ }
selector.PreferenceChanged += preference =>
{
Profile = Profile?.WithTraitPreference(trait.ID, preference);
SetDirty();
};
+
+ TraitsList.AddChild(selector);
}
}
else
{
- _traitsList.AddChild(new Label
+ TraitsList.AddChild(new Label
{
+ // TODO: Localise
Text = "No traits available :(",
FontColorOverride = Color.Gray,
});
}
+ }
- #endregion
+ /// <summary>
+ /// Refreshes the species selector.
+ /// </summary>
+ public void RefreshSpecies()
+ {
+ SpeciesButton.Clear();
+ _species.Clear();
- #region Save
+ _species.AddRange(_prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(o => o.RoundStart));
+ var speciesIds = _species.Select(o => o.ID).ToList();
- _saveButton.OnPressed += _ => { Save(); };
+ for (var i = 0; i < _species.Count; i++)
+ {
+ var name = Loc.GetString(_species[i].Name);
+ SpeciesButton.AddItem(name, i);
- #endregion Save
+ if (Profile?.Species.Equals(_species[i].ID) == true)
+ {
+ SpeciesButton.SelectId(i);
+ }
+ }
- #region Markings
- _tabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-markings-tab"));
+ // If our species isn't available then reset it to default.
+ if (Profile != null)
+ {
+ if (!speciesIds.Contains(Profile.Species))
+ {
+ SetSpecies(SharedHumanoidAppearanceSystem.DefaultSpecies);
+ }
+ }
+ }
- CMarkings.OnMarkingAdded += OnMarkingChange;
- CMarkings.OnMarkingRemoved += OnMarkingChange;
- CMarkings.OnMarkingColorChange += OnMarkingChange;
- CMarkings.OnMarkingRankChange += OnMarkingChange;
+ public void RefreshAntags()
+ {
+ AntagList.DisposeAllChildren();
+ var items = new[]
+ {
+ ("humanoid-profile-editor-antag-preference-yes-button", 0),
+ ("humanoid-profile-editor-antag-preference-no-button", 1)
+ };
- #endregion Markings
+ foreach (var antag in _prototypeManager.EnumeratePrototypes<AntagPrototype>().OrderBy(a => Loc.GetString(a.Name)))
+ {
+ if (!antag.SetPreference)
+ continue;
- #region FlavorText
+ var antagContainer = new BoxContainer()
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ };
- if (configurationManager.GetCVar(CCVars.FlavorText))
- {
- var flavorText = new FlavorText.FlavorText();
- _tabContainer.AddChild(flavorText);
- _tabContainer.SetTabTitle(_tabContainer.ChildCount - 1, Loc.GetString("humanoid-profile-editor-flavortext-tab"));
- _flavorTextEdit = flavorText.CFlavorTextInput;
+ var selector = new RequirementsSelector()
+ {
+ Margin = new Thickness(3f, 3f, 3f, 0f),
+ };
- flavorText.OnFlavorTextChanged += OnFlavorTextChange;
- }
+ var title = Loc.GetString(antag.Name);
+ var description = Loc.GetString(antag.Objective);
+ selector.Setup(items, title, 250, description);
+ selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
- #endregion FlavorText
+ if (!_requirements.CheckRoleTime(antag.Requirements, out var reason))
+ {
+ selector.LockRequirements(reason);
+ Profile = Profile?.WithAntagPreference(antag.ID, false);
+ SetDirty();
+ }
+ else
+ {
+ selector.UnlockRequirements();
+ }
- #region Dummy
+ selector.OnSelected += preference =>
+ {
+ Profile = Profile?.WithAntagPreference(antag.ID, preference == 0);
+ SetDirty();
+ };
- _previewRotateLeftButton.OnPressed += _ =>
- {
- _previewRotation = _previewRotation.TurnCw();
- SetPreviewRotation(_previewRotation);
- };
- _previewRotateRightButton.OnPressed += _ =>
- {
- _previewRotation = _previewRotation.TurnCcw();
- SetPreviewRotation(_previewRotation);
- };
+ antagContainer.AddChild(selector);
- #endregion Dummy
+ antagContainer.AddChild(new Button()
+ {
+ Disabled = true,
+ Text = Loc.GetString("loadout-window"),
+ HorizontalAlignment = HAlignment.Right,
+ Margin = new Thickness(3f, 0f, 0f, 0f),
+ });
- #endregion Left
+ AntagList.AddChild(antagContainer);
+ }
+ }
- if (preferencesManager.ServerDataLoaded)
+ private void SetDirty()
+ {
+ // If it equals default then reset the button.
+ if (Profile == null || _preferencesManager.Preferences?.SelectedCharacter.MemberwiseEquals(Profile) == true)
{
- LoadServerData();
+ IsDirty = false;
+ return;
}
- ShowClothes.OnToggled += args =>
- {
- var lobby = UserInterfaceManager.GetUIController<LobbyUIController>();
- lobby.SetClothes(args.Pressed);
- SetDirty();
- };
+ // TODO: Check if profile matches default.
+ IsDirty = true;
+ }
- preferencesManager.OnServerDataLoaded += LoadServerData;
+ /// <summary>
+ /// Refresh all loadouts.
+ /// </summary>
+ public void RefreshLoadouts()
+ {
+ _loadoutWindow?.Dispose();
+ }
- SpeciesInfoButton.OnPressed += OnSpeciesInfoButtonPressed;
+ /// <summary>
+ /// Reloads the entire dummy entity for preview.
+ /// </summary>
+ /// <remarks>
+ /// This is expensive so not recommended to run if you have a slider.
+ /// </remarks>
+ private void ReloadPreview()
+ {
+ _entManager.DeleteEntity(PreviewDummy);
+ PreviewDummy = EntityUid.Invalid;
- UpdateSpeciesGuidebookIcon();
+ if (Profile == null || !_prototypeManager.HasIndex<SpeciesPrototype>(Profile.Species))
+ return;
+
+ PreviewDummy = _controller.LoadProfileEntity(Profile, JobOverride, ShowClothes.Pressed);
+ SpriteView.SetEntity(PreviewDummy);
+ }
+
+ /// <summary>
+ /// Resets the profile to the defaults.
+ /// </summary>
+ public void ResetToDefault()
+ {
+ SetProfile(
+ (HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter,
+ _preferencesManager.Preferences?.SelectedCharacterIndex);
+ }
+ /// <summary>
+ /// Sets the editor to the specified profile with the specified slot.
+ /// </summary>
+ public void SetProfile(HumanoidCharacterProfile? profile, int? slot)
+ {
+ Profile = profile?.Clone();
+ CharacterSlot = slot;
IsDirty = false;
- controller.UpdateProfile();
+ JobOverride = null;
+
+ UpdateNameEdit();
+ UpdateFlavorTextEdit();
+ UpdateSexControls();
+ UpdateGenderControls();
+ UpdateSkinColor();
+ UpdateSpawnPriorityControls();
+ UpdateAgeEdit();
+ UpdateEyePickers();
+ UpdateSaveButton();
+ UpdateMarkings();
+ UpdateHairPickers();
+ UpdateCMarkingsHair();
+ UpdateCMarkingsFacialHair();
+
+ RefreshAntags();
+ RefreshJobs();
+ RefreshLoadouts();
+ RefreshSpecies();
+ RefreshTraits();
+ RefreshFlavorText();
+ ReloadPreview();
+
+ if (Profile != null)
+ {
+ PreferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable);
+ }
}
- private void SetDirty()
+
+ /// <summary>
+ /// A slim reload that only updates the entity itself and not any of the job entities, etc.
+ /// </summary>
+ private void ReloadProfilePreview()
{
- var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
- controller.UpdateProfile(Profile);
- controller.ReloadCharacterUI();
- IsDirty = true;
+ if (Profile == null || !_entManager.EntityExists(PreviewDummy))
+ return;
+
+ _entManager.System<HumanoidAppearanceSystem>().LoadProfile(PreviewDummy, Profile);
}
private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
}
}
- private void OnDummyUpdate(EntityUid value)
- {
- _previewSpriteView.SetEntity(value);
- }
-
- private void UpdateAntagRequirements()
+ /// <summary>
+ /// Refreshes all job selectors.
+ /// </summary>
+ public void RefreshJobs()
{
- _antagList.DisposeAllChildren();
- _antagPreferences.Clear();
- var btnGroup = new ButtonGroup();
-
- foreach (var antag in _prototypeManager.EnumeratePrototypes<AntagPrototype>().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()
- {
- _jobList.DisposeAllChildren();
- _jobPriorities.Clear();
+ JobList.DisposeAllChildren();
_jobCategories.Clear();
+ _jobPriorities.Clear();
var firstCategory = true;
var departments = _prototypeManager.EnumeratePrototypes<DepartmentPrototype>().ToArray();
Array.Sort(departments, DepartmentUIComparer.Instance);
+ var items = new[]
+ {
+ ("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never),
+ ("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low),
+ ("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium),
+ ("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High),
+ };
+
foreach (var department in departments)
{
var departmentName = Loc.GetString($"department-{department.ID}");
});
_jobCategories[department.ID] = category;
- _jobList.AddChild(category);
+ JobList.AddChild(category);
}
var jobs = department.Roles.Select(jobId => _prototypeManager.Index<JobPrototype>(jobId))
.Where(job => job.SetPreference)
.ToArray();
+
Array.Sort(jobs, JobUIComparer.Instance);
- var jobLoadoutGroup = new ButtonGroup();
foreach (var job in jobs)
{
- RoleLoadout? loadout = null;
+ var jobContainer = new BoxContainer()
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ };
- // 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)
+ var selector = new RequirementsSelector()
{
Margin = new Thickness(3f, 3f, 3f, 0f),
};
+ var icon = new TextureRect
+ {
+ TextureScale = new Vector2(2, 2),
+ VerticalAlignment = VAlignment.Center
+ };
+ var jobIcon = _prototypeManager.Index<StatusIconPrototype>(job.Icon);
+ icon.Texture = jobIcon.Icon.Frame0();
+ selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon);
+
if (!_requirements.IsAllowed(job, out var reason))
{
selector.LockRequirements(reason);
}
-
- category.AddChild(selector);
- _jobPriorities.Add(selector);
-
- selector.LoadoutUpdated += args =>
+ else
{
- Profile = Profile?.WithLoadout(args);
- SetDirty();
- };
+ selector.UnlockRequirements();
+ }
- selector.PriorityChanged += priority =>
+ selector.OnSelected += selectedPrio =>
{
- Profile = Profile?.WithJobPriority(job.ID, priority);
+ var selectedJobPrio = (JobPriority) selectedPrio;
+ Profile = Profile?.WithJobPriority(job.ID, selectedJobPrio);
- foreach (var jobSelector in _jobPriorities)
+ foreach (var (jobId, other) in _jobPriorities)
{
// Sync other selectors with the same job in case of multiple department jobs
- if (jobSelector.Proto == selector.Proto)
+ if (jobId == job.ID)
{
- jobSelector.Priority = priority;
+ other.Select(selectedPrio);
}
- else if (priority == JobPriority.High && jobSelector.Priority == JobPriority.High)
+ else if (selectedJobPrio == JobPriority.High && (JobPriority) other.Selected == JobPriority.High)
{
// Lower any other high priorities to medium.
- jobSelector.Priority = JobPriority.Medium;
- Profile = Profile?.WithJobPriority(jobSelector.Proto.ID, JobPriority.Medium);
+ other.Select((int) JobPriority.Medium);
+ Profile = Profile?.WithJobPriority(jobId, JobPriority.Medium);
}
}
+ // TODO: Only reload on high change (either to or from).
+ ReloadPreview();
+
+ UpdateJobPriorities();
SetDirty();
};
+ var loadoutWindowBtn = new Button()
+ {
+ Text = Loc.GetString("loadout-window"),
+ HorizontalAlignment = HAlignment.Right,
+ VerticalAlignment = VAlignment.Center,
+ Margin = new Thickness(3f, 3f, 0f, 0f),
+ };
+
+ var collection = IoCManager.Instance!;
+ var protoManager = collection.Resolve<IPrototypeManager>();
+
+ // If no loadout found then disabled button
+ if (!protoManager.TryIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID), out var roleLoadoutProto))
+ {
+ loadoutWindowBtn.Disabled = true;
+ }
+ // else
+ else
+ {
+ loadoutWindowBtn.OnPressed += args =>
+ {
+ RoleLoadout? loadout = null;
+
+ // Clone so we don't modify the underlying loadout.
+ Profile?.Loadouts.TryGetValue(LoadoutSystem.GetJobPrototype(job.ID), out loadout);
+ loadout = loadout?.Clone();
+
+ if (loadout == null)
+ {
+ loadout = new RoleLoadout(roleLoadoutProto.ID);
+ loadout.SetDefault(_prototypeManager);
+ }
+
+ OpenLoadout(job, loadout, roleLoadoutProto);
+ };
+ }
+
+ _jobPriorities.Add((job.ID, selector));
+ jobContainer.AddChild(selector);
+ jobContainer.AddChild(loadoutWindowBtn);
+ category.AddChild(jobContainer);
}
}
- if (Profile is not null)
+ UpdateJobPriorities();
+ }
+
+ private void OpenLoadout(JobPrototype? jobProto, RoleLoadout roleLoadout, RoleLoadoutPrototype roleLoadoutProto)
+ {
+ _loadoutWindow?.Dispose();
+ _loadoutWindow = null;
+ var collection = IoCManager.Instance;
+
+ if (collection == null || _playerManager.LocalSession == null || Profile == null)
+ return;
+
+ JobOverride = jobProto;
+ var session = _playerManager.LocalSession;
+
+ _loadoutWindow = new LoadoutWindow(Profile, roleLoadout, roleLoadoutProto, _playerManager.LocalSession, collection)
{
- UpdateJobPriorities();
- }
+ Title = jobProto?.ID + "-loadout",
+ };
+
+ // Refresh the buttons etc.
+ _loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
+ _loadoutWindow.OpenCenteredLeft();
+
+ _loadoutWindow.OnLoadoutPressed += (loadoutGroup, loadoutProto) =>
+ {
+ roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager);
+ _loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
+ Profile = Profile?.WithLoadout(roleLoadout);
+ SetDirty();
+ ReloadPreview();
+ };
+
+ _loadoutWindow.OnLoadoutUnpressed += (loadoutGroup, loadoutProto) =>
+ {
+ roleLoadout.RemoveLoadout(loadoutGroup, loadoutProto, _prototypeManager);
+ _loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
+ Profile = Profile?.WithLoadout(roleLoadout);
+ SetDirty();
+ ReloadPreview();
+ };
+
+ JobOverride = jobProto;
+ ReloadPreview();
+
+ _loadoutWindow.OnClose += () =>
+ {
+ JobOverride = null;
+ SetDirty();
+ ReloadPreview();
+ };
}
private void OnFlavorTextChange(string content)
return;
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList()));
- IsDirty = true;
- var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
- controller.UpdateProfile(Profile);
- controller.ReloadProfile();
+ SetDirty();
+ ReloadProfilePreview();
}
private void OnSkinColorOnValueChanged()
{
case HumanoidSkinColor.HumanToned:
{
- if (!_skinColor.Visible)
+ if (!Skin.Visible)
{
- _skinColor.Visible = true;
- _rgbSkinColorContainer.Visible = false;
+ Skin.Visible = true;
+ RgbSkinColorContainer.Visible = false;
}
- var color = SkinColor.HumanSkinTone((int) _skinColor.Value);
+ var color = SkinColor.HumanSkinTone((int) Skin.Value);
- CMarkings.CurrentSkinColor = color;
+ Markings.CurrentSkinColor = color;
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));//
break;
}
case HumanoidSkinColor.Hues:
{
- if (!_rgbSkinColorContainer.Visible)
+ if (!RgbSkinColorContainer.Visible)
{
- _skinColor.Visible = false;
- _rgbSkinColorContainer.Visible = true;
+ Skin.Visible = false;
+ RgbSkinColorContainer.Visible = true;
}
- CMarkings.CurrentSkinColor = _rgbSkinColorSelector.Color;
+ Markings.CurrentSkinColor = _rgbSkinColorSelector.Color;
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(_rgbSkinColorSelector.Color));
break;
}
case HumanoidSkinColor.TintedHues:
{
- if (!_rgbSkinColorContainer.Visible)
+ if (!RgbSkinColorContainer.Visible)
{
- _skinColor.Visible = false;
- _rgbSkinColorContainer.Visible = true;
+ Skin.Visible = false;
+ RgbSkinColorContainer.Visible = true;
}
var color = SkinColor.TintedHues(_rgbSkinColorSelector.Color);
- CMarkings.CurrentSkinColor = color;
+ Markings.CurrentSkinColor = color;
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
break;
}
case HumanoidSkinColor.VoxFeathers:
{
- if (!_rgbSkinColorContainer.Visible)
+ if (!RgbSkinColorContainer.Visible)
{
- _skinColor.Visible = false;
- _rgbSkinColorContainer.Visible = true;
+ Skin.Visible = false;
+ RgbSkinColorContainer.Visible = true;
}
var color = SkinColor.ClosestVoxColor(_rgbSkinColorSelector.Color);
- CMarkings.CurrentSkinColor = color;
+ Markings.CurrentSkinColor = color;
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
break;
}
}
- IsDirty = true;
- var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
- controller.UpdateProfile(Profile);
- controller.ReloadProfile();
+ SetDirty();
+ ReloadProfilePreview();
}
protected override void Dispose(bool disposing)
if (!disposing)
return;
- var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
- controller.PreviewDummyUpdated -= OnDummyUpdate;
- _requirements.Updated -= UpdateAntagRequirements;
- _requirements.Updated -= UpdateRoleRequirements;
- _preferencesManager.OnServerDataLoaded -= LoadServerData;
- }
-
- public void LoadServerData()
- {
- Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter;
- CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex;
-
- UpdateAntagRequirements();
- UpdateControls();
- ShowClothes.Pressed = true;
+ _loadoutWindow?.Dispose();
+ _loadoutWindow = null;
+ _entManager.DeleteEntity(PreviewDummy);
+ PreviewDummy = EntityUid.Invalid;
}
private void SetAge(int newAge)
{
Profile = Profile?.WithAge(newAge);
+ ReloadPreview();
SetDirty();
}
Profile = Profile?.WithGender(Gender.Epicene);
break;
}
+
UpdateGenderControls();
- CMarkings.SetSex(newSex);
+ Markings.SetSex(newSex);
+ ReloadPreview();
SetDirty();
}
private void SetGender(Gender newGender)
{
Profile = Profile?.WithGender(newGender);
+ ReloadPreview();
SetDirty();
}
{
Profile = Profile?.WithSpecies(newSpecies);
OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
- CMarkings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
+ Markings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
+ // In case there's job restrictions for the species
+ RefreshJobs();
+ // In case there's species restrictions for loadouts
+ RefreshLoadouts();
UpdateSexControls(); // update sex for new species
UpdateSpeciesGuidebookIcon();
SetDirty();
- UpdatePreview();
+ ReloadPreview();
}
private void SetName(string newName)
SetDirty();
}
- public void Save()
- {
- IsDirty = false;
-
- if (Profile == null)
- return;
-
- _preferencesManager.UpdateCharacter(Profile, CharacterSlot);
- OnProfileChanged?.Invoke(Profile, CharacterSlot);
- // Reset profile to default.
- UserInterfaceManager.GetUIController<LobbyUIController>().UpdateProfile();
- }
-
private bool IsDirty
{
get => _isDirty;
set
{
+ if (_isDirty == value)
+ return;
+
_isDirty = value;
UpdateSaveButton();
}
private void UpdateNameEdit()
{
- _nameEdit.Text = Profile?.Name ?? "";
+ NameEdit.Text = Profile?.Name ?? "";
}
private void UpdateFlavorTextEdit()
{
- if(_flavorTextEdit != null)
+ if (_flavorTextEdit != null)
{
_flavorTextEdit.TextRope = new Rope.Leaf(Profile?.FlavorText ?? "");
}
private void UpdateAgeEdit()
{
- _ageEdit.Text = Profile?.Age.ToString() ?? "";
+ AgeEdit.Text = Profile?.Age.ToString() ?? "";
+ }
+
+ /// <summary>
+ /// Updates selected job priorities to the profile's.
+ /// </summary>
+ private void UpdateJobPriorities()
+ {
+ foreach (var (jobId, prioritySelector) in _jobPriorities)
+ {
+ var priority = Profile?.JobPriorities.GetValueOrDefault(jobId, JobPriority.Never) ?? JobPriority.Never;
+ prioritySelector.Select((int) priority);
+ }
}
private void UpdateSexControls()
if (Profile == null)
return;
- _sexButton.Clear();
+ SexButton.Clear();
var sexes = new List<Sex>();
{
sexes.Add(sex);
}
- } else
+ }
+ else
{
sexes.Add(Sex.Unsexed);
}
// add button for each sex
foreach (var sex in sexes)
{
- _sexButton.AddItem(Loc.GetString($"humanoid-profile-editor-sex-{sex.ToString().ToLower()}-text"), (int) sex);
+ SexButton.AddItem(Loc.GetString($"humanoid-profile-editor-sex-{sex.ToString().ToLower()}-text"), (int) sex);
}
if (sexes.Contains(Profile.Sex))
- _sexButton.SelectId((int) Profile.Sex);
+ SexButton.SelectId((int) Profile.Sex);
else
- _sexButton.SelectId((int) sexes[0]);
+ SexButton.SelectId((int) sexes[0]);
}
private void UpdateSkinColor()
{
case HumanoidSkinColor.HumanToned:
{
- if (!_skinColor.Visible)
+ if (!Skin.Visible)
{
- _skinColor.Visible = true;
- _rgbSkinColorContainer.Visible = false;
+ Skin.Visible = true;
+ RgbSkinColorContainer.Visible = false;
}
- _skinColor.Value = SkinColor.HumanSkinToneFromColor(Profile.Appearance.SkinColor);
+ Skin.Value = SkinColor.HumanSkinToneFromColor(Profile.Appearance.SkinColor);
break;
}
case HumanoidSkinColor.Hues:
{
- if (!_rgbSkinColorContainer.Visible)
+ if (!RgbSkinColorContainer.Visible)
{
- _skinColor.Visible = false;
- _rgbSkinColorContainer.Visible = true;
+ Skin.Visible = false;
+ RgbSkinColorContainer.Visible = true;
}
// set the RGB values to the direct values otherwise
}
case HumanoidSkinColor.TintedHues:
{
- if (!_rgbSkinColorContainer.Visible)
+ if (!RgbSkinColorContainer.Visible)
{
- _skinColor.Visible = false;
- _rgbSkinColorContainer.Visible = true;
+ Skin.Visible = false;
+ RgbSkinColorContainer.Visible = true;
}
// set the RGB values to the direct values otherwise
}
case HumanoidSkinColor.VoxFeathers:
{
- if (!_rgbSkinColorContainer.Visible)
+ if (!RgbSkinColorContainer.Visible)
{
- _skinColor.Visible = false;
- _rgbSkinColorContainer.Visible = true;
+ Skin.Visible = false;
+ RgbSkinColorContainer.Visible = true;
}
_rgbSkinColorSelector.Color = SkinColor.ClosestVoxColor(Profile.Appearance.SkinColor);
return;
}
- CMarkings.SetData(Profile.Appearance.Markings, Profile.Species,
+ Markings.SetData(Profile.Appearance.Markings, Profile.Species,
Profile.Sex, Profile.Appearance.SkinColor, Profile.Appearance.EyeColor
);
}
- private void UpdateSpecies()
- {
- if (Profile == null)
- {
- return;
- }
-
- CSpeciesButton.Select(_speciesList.FindIndex(x => x.ID == Profile.Species));
- }
-
private void UpdateGenderControls()
{
if (Profile == null)
return;
}
- _genderButton.SelectId((int) Profile.Gender);
+ PronounsButton.SelectId((int) Profile.Gender);
}
private void UpdateSpawnPriorityControls()
return;
}
- _spawnPriorityButton.SelectId((int) Profile.SpawnPriority);
+ SpawnPriorityButton.SelectId((int) Profile.SpawnPriority);
}
private void UpdateHairPickers()
_ => new() { new(Profile.Appearance.FacialHairStyleId, new List<Color>() { Profile.Appearance.FacialHairColor }) },
};
- _hairPicker.UpdateData(
+ HairStylePicker.UpdateData(
hairMarking,
Profile.Species,
1);
- _facialHairPicker.UpdateData(
+ FacialHairPicker.UpdateData(
facialHairMarking,
Profile.Species,
1);
}
if (hairColor != null)
{
- CMarkings.HairMarking = new (Profile.Appearance.HairStyleId, new List<Color>() { hairColor.Value });
+ Markings.HairMarking = new (Profile.Appearance.HairStyleId, new List<Color>() { hairColor.Value });
}
else
{
- CMarkings.HairMarking = null;
+ Markings.HairMarking = null;
}
}
// facial hair color
Color? facialHairColor = null;
if ( Profile.Appearance.FacialHairStyleId != HairStyles.DefaultFacialHairStyle &&
- _markingManager.Markings.TryGetValue(Profile.Appearance.FacialHairStyleId, out var facialHairProto)
- )
+ _markingManager.Markings.TryGetValue(Profile.Appearance.FacialHairStyleId, out var facialHairProto))
{
if (_markingManager.CanBeApplied(Profile.Species, Profile.Sex, facialHairProto, _prototypeManager))
{
}
if (facialHairColor != null)
{
- CMarkings.FacialHairMarking = new (Profile.Appearance.FacialHairStyleId, new List<Color>() { facialHairColor.Value });
+ Markings.FacialHairMarking = new (Profile.Appearance.FacialHairStyleId, new List<Color>() { facialHairColor.Value });
}
else
{
- CMarkings.FacialHairMarking = null;
+ Markings.FacialHairMarking = null;
}
}
return;
}
- CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor;
- _eyesPicker.SetData(Profile.Appearance.EyeColor);
+ Markings.CurrentEyeColor = Profile.Appearance.EyeColor;
+ EyeColorPicker.SetData(Profile.Appearance.EyeColor);
}
private void UpdateSaveButton()
{
- _saveButton.Disabled = Profile is null || !IsDirty;
+ SaveButton.Disabled = Profile is null || !IsDirty;
+ ResetButton.Disabled = Profile is null || !IsDirty;
}
- private void UpdatePreview()
+ private void SetPreviewRotation(Direction direction)
{
- if (Profile is null)
- return;
-
- UserInterfaceManager.GetUIController<LobbyUIController>().ReloadProfile();
- SetPreviewRotation(_previewRotation);
+ SpriteView.OverrideDirection = (Direction) ((int) direction % 4 * 2);
}
- private void SetPreviewRotation(Direction direction)
+ private void RandomizeEverything()
{
- _previewSpriteView.OverrideDirection = (Direction) ((int) direction % 4 * 2);
+ Profile = HumanoidCharacterProfile.Random();
+ SetProfile(Profile, CharacterSlot);
+ SetDirty();
}
- public void UpdateControls()
+ private void RandomizeName()
{
- if (Profile is null) return;
+ if (Profile == null) return;
+ var name = HumanoidCharacterProfile.GetName(Profile.Species, Profile.Gender);
+ SetName(name);
UpdateNameEdit();
- UpdateFlavorTextEdit();
- UpdateSexControls();
- UpdateGenderControls();
- UpdateSkinColor();
- UpdateSpecies();
- UpdateSpawnPriorityControls();
- UpdateAgeEdit();
- UpdateEyePickers();
- UpdateSaveButton();
- UpdateLoadouts();
- UpdateRoleRequirements();
- UpdateJobPriorities();
- UpdateAntagPreferences();
- UpdateTraitPreferences();
- UpdateMarkings();
- UpdateHairPickers();
- UpdateCMarkingsHair();
- UpdateCMarkingsFacialHair();
-
- _preferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable);
}
- private void UpdateJobPriorities()
+ private async void ImportProfile()
{
- foreach (var prioritySelector in _jobPriorities)
- {
- var jobId = prioritySelector.Proto.ID;
+ if (_exporting || CharacterSlot == null || Profile == null)
+ return;
- var priority = Profile?.JobPriorities.GetValueOrDefault(jobId, JobPriority.Never) ?? JobPriority.Never;
+ StartExport();
+ await using var file = await _dialogManager.OpenFile(new FileDialogFilters(new FileDialogFilters.Group("yml")));
- prioritySelector.Priority = priority;
+ if (file == null)
+ {
+ EndExport();
+ return;
}
- }
- private void UpdateLoadouts()
- {
- foreach (var prioritySelector in _jobPriorities)
+ try
{
- prioritySelector.CloseLoadout();
- }
- }
+ var profile = _entManager.System<HumanoidAppearanceSystem>().FromStream(file, _playerManager.LocalSession!);
+ var oldProfile = Profile;
+ SetProfile(profile, CharacterSlot);
- private void UpdateAntagPreferences()
- {
- foreach (var preferenceSelector in _antagPreferences)
+ IsDirty = !profile.MemberwiseEquals(oldProfile);
+ }
+ catch (Exception exc)
{
- var antagId = preferenceSelector.Proto.ID;
- var preference = Profile?.AntagPreferences.Contains(antagId) ?? false;
- preferenceSelector.Preference = preference;
+ _sawmill.Error($"Error when importing profile\n{exc.StackTrace}");
}
- }
-
- private void UpdateTraitPreferences()
- {
- foreach (var preferenceSelector in _traitPreferences)
+ finally
{
- var traitId = preferenceSelector.Trait.ID;
- var preference = Profile?.TraitPreferences.Contains(traitId) ?? false;
-
- preferenceSelector.Preference = preference;
+ EndExport();
}
}
- private sealed class TraitPreferenceSelector : Control
+ private async void ExportProfile()
{
- public TraitPrototype Trait { get; }
- private readonly CheckBox _checkBox;
+ if (Profile == null || _exporting)
+ return;
- public bool Preference
+ StartExport();
+ var file = await _dialogManager.SaveFile(new FileDialogFilters(new FileDialogFilters.Group("yml")));
+
+ if (file == null)
{
- get => _checkBox.Pressed;
- set => _checkBox.Pressed = value;
+ EndExport();
+ return;
}
- public event Action<bool>? PreferenceChanged;
-
- public TraitPreferenceSelector(TraitPrototype trait)
+ try
{
- Trait = trait;
-
- _checkBox = new CheckBox {Text = Loc.GetString(trait.Name)};
- _checkBox.OnToggled += OnCheckBoxToggled;
-
- if (trait.Description is { } desc)
- {
- _checkBox.ToolTip = Loc.GetString(desc);
- }
-
- AddChild(new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- Children = { _checkBox },
- });
+ var dataNode = _entManager.System<HumanoidAppearanceSystem>().ToDataNode(Profile);
+ await using var writer = new StreamWriter(file.Value.fileStream);
+ dataNode.Write(writer);
}
-
- private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
+ catch (Exception exc)
{
- PreferenceChanged?.Invoke(Preference);
+ _sawmill.Error($"Error when exporting profile\n{exc.StackTrace}");
}
+ finally
+ {
+ EndExport();
+ await file.Value.fileStream.DisposeAsync();
+ }
+ }
+
+ private void StartExport()
+ {
+ _exporting = true;
+ ImportButton.Disabled = true;
+ ExportButton.Disabled = true;
+ }
+
+ private void EndExport()
+ {
+ _exporting = false;
+ ImportButton.Disabled = false;
+ ExportButton.Disabled = false;
}
}
}
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
-namespace Content.Client.Preferences.UI;
+namespace Content.Client.Lobby.UI.Loadouts;
[GenerateTypedNameReferences]
public sealed partial class LoadoutContainer : BoxContainer
var spriteTooltip = new Tooltip();
spriteTooltip.SetMessage(FormattedMessage.FromUnformatted(_entManager.GetComponent<MetaDataComponent>(_entity.Value).EntityDescription));
- Sprite.TooltipSupplier = _ => spriteTooltip;
+ TooltipSupplier = _ => spriteTooltip;
}
}
}
using System.Linq;
using Content.Shared.Clothing;
+using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
-namespace Content.Client.Preferences.UI;
+namespace Content.Client.Lobby.UI.Loadouts;
[GenerateTypedNameReferences]
public sealed partial class LoadoutGroupContainer : BoxContainer
public event Action<ProtoId<LoadoutPrototype>>? OnLoadoutPressed;
public event Action<ProtoId<LoadoutPrototype>>? OnLoadoutUnpressed;
- public LoadoutGroupContainer(RoleLoadout loadout, LoadoutGroupPrototype groupProto, ICommonSession session, IDependencyCollection collection)
+ public LoadoutGroupContainer(HumanoidCharacterProfile profile, RoleLoadout loadout, LoadoutGroupPrototype groupProto, ICommonSession session, IDependencyCollection collection)
{
RobustXamlLoader.Load(this);
_groupProto = groupProto;
- RefreshLoadouts(loadout, session, collection);
+ RefreshLoadouts(profile, loadout, session, collection);
}
/// <summary>
/// Updates button availabilities and buttons.
/// </summary>
- public void RefreshLoadouts(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
+ public void RefreshLoadouts(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
{
var protoMan = collection.Resolve<IPrototypeManager>();
var loadoutSystem = collection.Resolve<IEntityManager>().System<LoadoutSystem>();
var matchingLoadout = selected.FirstOrDefault(e => e.Prototype == loadoutProto);
var pressed = matchingLoadout != null;
- var enabled = loadout.IsValid(session, loadoutProto, collection, out var reason);
+ var enabled = loadout.IsValid(profile, session, loadoutProto, collection, out var reason);
var loadoutContainer = new LoadoutContainer(loadoutProto, !enabled, reason);
loadoutContainer.Select.Pressed = pressed;
loadoutContainer.Text = loadoutSystem.GetName(loadProto);
-using Content.Client.Lobby;
using Content.Client.UserInterface.Controls;
+using Content.Shared.Preferences;
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;
+namespace Content.Client.Lobby.UI.Loadouts;
[GenerateTypedNameReferences]
public sealed partial class LoadoutWindow : FancyWindow
private List<LoadoutGroupContainer> _groups = new();
- public LoadoutWindow(RoleLoadout loadout, RoleLoadoutPrototype proto, ICommonSession session, IDependencyCollection collection)
+ public HumanoidCharacterProfile Profile;
+
+ public LoadoutWindow(HumanoidCharacterProfile profile, RoleLoadout loadout, RoleLoadoutPrototype proto, ICommonSession session, IDependencyCollection collection)
{
RobustXamlLoader.Load(this);
+ Profile = profile;
var protoManager = collection.Resolve<IPrototypeManager>();
foreach (var group in proto.Groups)
if (!protoManager.TryIndex(group, out var groupProto))
continue;
- var container = new LoadoutGroupContainer(loadout, protoManager.Index(group), session, collection);
+ var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection);
LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
_groups.Add(container);
}
}
- public override void Close()
- {
- base.Close();
- var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
- controller.SetDummyJob(null);
- }
-
public void RefreshLoadouts(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
{
foreach (var group in _groups)
{
- group.RefreshLoadouts(loadout, session, collection);
+ group.RefreshLoadouts(Profile, loadout, session, collection);
}
}
}
[GenerateTypedNameReferences]
public sealed partial class LobbyCharacterPreviewPanel : Control
{
+ [Dependency] private readonly IEntityManager _entManager = default!;
+
public Button CharacterSetupButton => CharacterSetup;
+ private EntityUid? _previewDummy;
+
public LobbyCharacterPreviewPanel()
{
RobustXamlLoader.Load(this);
- UserInterfaceManager.GetUIController<LobbyUIController>().SetPreviewPanel(this);
+ IoCManager.InjectDependencies(this);
}
public void SetLoaded(bool value)
public void SetSummaryText(string value)
{
- Summary.Text = string.Empty;
+ Summary.Text = value;
}
public void SetSprite(EntityUid uid)
{
+ if (_previewDummy != null)
+ {
+ _entManager.DeleteEntity(_previewDummy);
+ }
+
+ _previewDummy = uid;
+
ViewBox.DisposeAllChildren();
var spriteView = new SpriteView
{
spriteView.SetEntity(uid);
ViewBox.AddChild(spriteView);
}
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ _entManager.DeleteEntity(_previewDummy);
+ _previewDummy = null;
+ }
}
namespace Content.Client.Lobby.UI
{
[GenerateTypedNameReferences]
- internal sealed partial class LobbyGui : UIScreen
+ public sealed partial class LobbyGui : UIScreen
{
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
- [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
public LobbyGui()
{
LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text"));
LeaveButton.OnPressed += _ => _consoleHost.ExecuteCommand("disconnect");
- OptionsButton.OnPressed += _ => _userInterfaceManager.GetUIController<OptionsUIController>().ToggleWindow();
+ OptionsButton.OnPressed += _ => UserInterfaceManager.GetUIController<OptionsUIController>().ToggleWindow();
}
public void SwitchState(LobbyGuiState state)
case LobbyGuiState.CharacterSetup:
CharacterSetupState.Visible = true;
- var actualWidth = (float) _userInterfaceManager.RootControl.PixelWidth;
+ var actualWidth = (float) UserInterfaceManager.RootControl.PixelWidth;
var setupWidth = (float) LeftSide.PixelWidth;
if (1 - (setupWidth / actualWidth) > 0.30)
RightSide.Visible = false;
}
+ UserInterfaceManager.GetUIController<LobbyUIController>().ReloadCharacterSetup();
+
break;
}
}
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
-namespace Content.Client.Lobby.UI
+namespace Content.Client.Lobby.UI;
+
+[GenerateTypedNameReferences]
+[UsedImplicitly]
+public sealed partial class ObserveWarningWindow : DefaultWindow
{
- [GenerateTypedNameReferences]
- [UsedImplicitly]
- internal sealed partial class ObserveWarningWindow : DefaultWindow
+ public ObserveWarningWindow()
{
- public ObserveWarningWindow()
- {
- Title = Loc.GetString("observe-warning-window-title");
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
+ Title = Loc.GetString("observe-warning-window-title");
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
- ObserveButton.OnPressed += _ => { this.Close(); };
- NevermindButton.OnPressed += _ => { this.Close(); };
- }
+ ObserveButton.OnPressed += _ => { this.Close(); };
+ NevermindButton.OnPressed += _ => { this.Close(); };
}
}
--- /dev/null
+<BoxContainer xmlns="https://spacestation14.io"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ Orientation="Horizontal">
+ <Label Name="TitleLabel"
+ Margin="5 0"
+ MouseFilter="Stop"/>
+ <BoxContainer Name="OptionsContainer"
+ SetWidth="400"/>
+</BoxContainer>
--- /dev/null
+using System.Numerics;
+using Content.Client.Stylesheets;
+using Content.Client.UserInterface.Controls;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Lobby.UI.Roles;
+
+/// <summary>
+/// A generic locking selector.
+/// </summary>
+[GenerateTypedNameReferences]
+public sealed partial class RequirementsSelector : BoxContainer
+{
+ private readonly RadioOptions<int> _options;
+ private readonly StripeBack _lockStripe;
+
+ public event Action<int>? OnSelected;
+
+ public int Selected => _options.SelectedId;
+
+ public RequirementsSelector()
+ {
+ RobustXamlLoader.Load(this);
+ _options = new RadioOptions<int>(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);
+ OnSelected?.Invoke(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
+ }
+ };
+ }
+
+ /// <summary>
+ /// Actually adds the controls.
+ /// </summary>
+ public 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);
+ }
+
+ TitleLabel.Text = title;
+ TitleLabel.MinSize = new Vector2(titleSize, 0f);
+ TitleLabel.ToolTip = description;
+
+ if (icon != null)
+ {
+ AddChild(icon);
+ icon.SetPositionFirst();
+ }
+
+ OptionsContainer.AddChild(_options);
+ OptionsContainer.AddChild(_lockStripe);
+ }
+
+ public void LockRequirements(FormattedMessage requirements)
+ {
+ var tooltip = new Tooltip();
+ tooltip.SetMessage(requirements);
+ _lockStripe.TooltipSupplier = _ => tooltip;
+ _lockStripe.Visible = true;
+ _options.Visible = false;
+ }
+
+ 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,
+ };
+ }
+
+ public void Select(int id)
+ {
+ _options.Select(id);
+ }
+}
--- /dev/null
+<Control xmlns="https://spacestation14.io"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+ <BoxContainer Name="Container"
+ Orientation="Horizontal">
+ <CheckBox Name="Checkbox"/>
+ </BoxContainer>
+</Control>
--- /dev/null
+using Content.Shared.Traits;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Lobby.UI.Roles;
+
+[GenerateTypedNameReferences]
+public sealed partial class TraitPreferenceSelector : Control
+{
+ public bool Preference
+ {
+ get => Checkbox.Pressed;
+ set => Checkbox.Pressed = value;
+ }
+
+ public event Action<bool>? PreferenceChanged;
+
+ public TraitPreferenceSelector(TraitPrototype trait)
+ {
+ RobustXamlLoader.Load(this);
+ Checkbox.Text = Loc.GetString(trait.Name);
+ Checkbox.OnToggled += OnCheckBoxToggled;
+
+ if (trait.Description is { } desc)
+ {
+ Checkbox.ToolTip = Loc.GetString(desc);
+ }
+ }
+
+ private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
+ {
+ PreferenceChanged?.Invoke(Preference);
+ }
+}
+++ /dev/null
-using Content.Client.Players.PlayTimeTracking;
-using Content.Shared.Roles;
-using Robust.Client.UserInterface.Controls;
-
-namespace Content.Client.Preferences.UI;
-
-public sealed class AntagPreferenceSelector : RequirementsSelector<AntagPrototype>
-{
- // 0 is yes and 1 is no
- public bool Preference
- {
- get => Options.SelectedValue == 0;
- set => Options.Select((value && !Disabled) ? 0 : 1);
- }
-
- public event Action<bool>? 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<JobRequirementsManager>();
- if (proto.Requirements != null && !requirements.CheckRoleTime(proto.Requirements, out var reason))
- {
- LockRequirements(reason);
- }
- }
-}
+++ /dev/null
-using System.Linq;
-using System.Numerics;
-using Content.Client.Humanoid;
-using Content.Client.Info;
-using Content.Client.Info.PlaytimeStats;
-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.Graphics;
-using Robust.Client.ResourceManagement;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Configuration;
-using Robust.Shared.Map;
-using Robust.Shared.Prototypes;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
-using Direction = Robust.Shared.Maths.Direction;
-
-namespace Content.Client.Preferences.UI
-{
- [GenerateTypedNameReferences]
- public sealed partial class CharacterSetupGui : Control
- {
- private readonly IClientPreferencesManager _preferencesManager;
- private readonly IEntityManager _entityManager;
- private readonly IPrototypeManager _prototypeManager;
- private readonly Button _createNewCharacterButton;
- private readonly HumanoidProfileEditor _humanoidProfileEditor;
-
- public CharacterSetupGui(
- IEntityManager entityManager,
- IResourceCache resourceCache,
- IClientPreferencesManager preferencesManager,
- IPrototypeManager prototypeManager,
- IConfigurationManager configurationManager)
- {
- RobustXamlLoader.Load(this);
- _entityManager = entityManager;
- _prototypeManager = prototypeManager;
- _preferencesManager = preferencesManager;
-
- var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
- var back = new StyleBoxTexture
- {
- Texture = panelTex,
- Modulate = new Color(37, 37, 42)
- };
- back.SetPatchMargin(StyleBox.Margin.All, 10);
-
- BackgroundPanel.PanelOverride = back;
-
- _createNewCharacterButton = new Button
- {
- Text = Loc.GetString("character-setup-gui-create-new-character-button"),
- };
- _createNewCharacterButton.OnPressed += args =>
- {
- preferencesManager.CreateCharacter(HumanoidCharacterProfile.Random());
- UpdateUI();
- args.Event.Handle();
- };
-
- _humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, configurationManager);
- _humanoidProfileEditor.OnProfileChanged += ProfileChanged;
- CharEditor.AddChild(_humanoidProfileEditor);
-
- UpdateUI();
-
- RulesButton.OnPressed += _ => new RulesAndInfoWindow().Open();
-
- StatsButton.OnPressed += _ => new PlaytimeStatsWindow().OpenCentered();
- preferencesManager.OnServerDataLoaded += UpdateUI;
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
-
- _preferencesManager.OnServerDataLoaded -= UpdateUI;
- }
-
- public void Save() => _humanoidProfileEditor.Save();
-
- private void ProfileChanged(ICharacterProfile profile, int profileSlot)
- {
- _humanoidProfileEditor.UpdateControls();
- UpdateUI();
- }
-
- public void UpdateControls()
- {
- // Reset sliders etc. upon going going back to GUI.
- _humanoidProfileEditor.LoadServerData();
- }
-
- private void UpdateUI()
- {
- var numberOfFullSlots = 0;
- var characterButtonsGroup = new ButtonGroup();
- Characters.RemoveAllChildren();
-
- if (!_preferencesManager.ServerDataLoaded)
- {
- return;
- }
-
- _createNewCharacterButton.ToolTip =
- Loc.GetString("character-setup-gui-create-new-character-button-tooltip",
- ("maxCharacters", _preferencesManager.Settings!.MaxCharacterSlots));
-
- foreach (var (slot, character) in _preferencesManager.Preferences!.Characters)
- {
- numberOfFullSlots++;
- var characterPickerButton = new CharacterPickerButton(_entityManager,
- _preferencesManager,
- _prototypeManager,
- characterButtonsGroup,
- character);
- Characters.AddChild(characterPickerButton);
-
- var characterIndexCopy = slot;
- characterPickerButton.OnPressed += args =>
- {
- _humanoidProfileEditor.Profile = (HumanoidCharacterProfile)character;
- _humanoidProfileEditor.CharacterSlot = characterIndexCopy;
- _humanoidProfileEditor.UpdateControls();
- _preferencesManager.SelectCharacter(character);
- var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
- controller.UpdateProfile(_humanoidProfileEditor.Profile);
- controller.ReloadCharacterUI();
- UpdateUI();
- args.Event.Handle();
- };
- }
-
- _createNewCharacterButton.Disabled =
- numberOfFullSlots >= _preferencesManager.Settings.MaxCharacterSlots;
- Characters.AddChild(_createNewCharacterButton);
- // TODO: Move this shit to the Lobby UI controller
- }
-
- /// <summary>
- /// Shows individual characters on the side of the character GUI.
- /// </summary>
- private sealed class CharacterPickerButton : ContainerButton
- {
- private EntityUid _previewDummy;
-
- public CharacterPickerButton(
- IEntityManager entityManager,
- IClientPreferencesManager preferencesManager,
- IPrototypeManager prototypeManager,
- ButtonGroup group,
- ICharacterProfile profile)
- {
- AddStyleClass(StyleClassButton);
- ToggleMode = true;
- Group = group;
-
- var humanoid = profile as HumanoidCharacterProfile;
- if (humanoid is not null)
- {
- var dummy = prototypeManager.Index<SpeciesPrototype>(humanoid.Species).DollPrototype;
- _previewDummy = entityManager.SpawnEntity(dummy, MapCoordinates.Nullspace);
- }
- else
- {
- _previewDummy = entityManager.SpawnEntity(prototypeManager.Index<SpeciesPrototype>(SharedHumanoidAppearanceSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace);
- }
-
- EntitySystem.Get<HumanoidAppearanceSystem>().LoadProfile(_previewDummy, (HumanoidCharacterProfile)profile);
-
- if (humanoid != null)
- {
- var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
- var job = controller.GetPreferredJob(humanoid);
- controller.GiveDummyJobClothes(_previewDummy, humanoid, job);
-
- if (prototypeManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID)))
- {
- var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), entityManager, prototypeManager);
- controller.GiveDummyLoadout(_previewDummy, loadout);
- }
- }
-
- var isSelectedCharacter = profile == preferencesManager.Preferences?.SelectedCharacter;
-
- if (isSelectedCharacter)
- Pressed = true;
-
- var view = new SpriteView
- {
- Scale = new Vector2(2, 2),
- OverrideDirection = Direction.South
- };
- view.SetEntity(_previewDummy);
-
- var description = profile.Name;
-
- var highPriorityJob = humanoid?.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key;
- if (highPriorityJob != null)
- {
- var jobName = IoCManager.Resolve<IPrototypeManager>().Index<JobPrototype>(highPriorityJob).LocalizedName;
- description = $"{description}\n{jobName}";
- }
-
- var descriptionLabel = new Label
- {
- Text = description,
- ClipText = true,
- HorizontalExpand = true
- };
- var deleteButton = new Button
- {
- Text = Loc.GetString("character-setup-gui-character-picker-button-delete-button"),
- Visible = !isSelectedCharacter,
- };
- var confirmDeleteButton = new Button
- {
- Text = Loc.GetString("character-setup-gui-character-picker-button-confirm-delete-button"),
- Visible = false,
- };
- confirmDeleteButton.ModulateSelfOverride = StyleNano.ButtonColorCautionDefault;
- confirmDeleteButton.OnPressed += _ =>
- {
- Parent?.RemoveChild(this);
- Parent?.RemoveChild(confirmDeleteButton);
- preferencesManager.DeleteCharacter(profile);
- };
- deleteButton.OnPressed += _ =>
- {
-
- deleteButton.Visible = false;
- confirmDeleteButton.Visible = true;
-
- };
-
- var internalHBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- HorizontalExpand = true,
- SeparationOverride = 0,
- Children =
- {
- view,
- descriptionLabel,
- deleteButton,
- confirmDeleteButton
- }
- };
-
- AddChild(internalHBox);
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
-
- IoCManager.Resolve<IEntityManager>().DeleteEntity(_previewDummy);
- _previewDummy = default;
- }
- }
- }
-}
+++ /dev/null
-using Content.Shared.Preferences;
-using Robust.Shared.Prototypes;
-
-namespace Content.Client.Preferences.UI
-{
- public sealed partial class HumanoidProfileEditor
- {
- private void RandomizeEverything()
- {
- Profile = HumanoidCharacterProfile.Random();
- UpdateControls();
- IsDirty = true;
- }
-
- private void RandomizeName()
- {
- if (Profile == null) return;
- var name = HumanoidCharacterProfile.GetName(Profile.Species, Profile.Gender);
- SetName(name);
- UpdateNameEdit();
- }
- }
-}
+++ /dev/null
-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<JobPrototype>
-{
- public JobPriority Priority
- {
- get => (JobPriority) Options.SelectedValue;
- set => Options.SelectByValue((int) value);
- }
-
- public event Action<JobPriority>? 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<StatusIconPrototype>(proto.Icon);
- icon.Texture = jobIcon.Icon.Frame0();
-
- Setup(loadout, items, proto.LocalizedName, 200, proto.LocalizedDescription, icon);
- }
-}
+++ /dev/null
-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<T> : BoxContainer where T : IPrototype
-{
- private ButtonGroup _loadoutGroup;
-
- public T Proto { get; }
- public bool Disabled => _lockStripe.Visible;
-
- protected readonly RadioOptions<int> Options;
- private readonly StripeBack _lockStripe;
- private LoadoutWindow? _loadoutWindow;
-
- private RoleLoadout? _loadout;
-
- /// <summary>
- /// Raised if a loadout has been updated.
- /// </summary>
- public event Action<RoleLoadout>? LoadoutUpdated;
-
- protected RequirementsSelector(T proto, ButtonGroup loadoutGroup)
- {
- _loadoutGroup = loadoutGroup;
- Proto = proto;
-
- Options = new RadioOptions<int>(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
- }
-
- /// <summary>
- /// Actually adds the controls, must be called in the inheriting class' constructor.
- /// </summary>
- 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<IPrototypeManager>();
-
- // If no loadout found then disabled button
- if (!protoManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(Proto.ID)))
- {
- loadoutWindowBtn.Disabled = true;
- }
- // else
- else
- {
- var session = collection.Resolve<IPlayerManager>().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<LobbyUIController>();
- 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<LobbyUIController>();
- 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<LobbyUIController>();
- 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,
- };
- }
-}
using Content.Client.Lobby;
-using Content.Client.Preferences;
using Content.Server.Preferences.Managers;
using Content.Shared.Preferences;
using Robust.Client.State;
private static HumanoidCharacterProfile CharlieCharlieson()
{
- return new(
- "Charlie Charlieson",
- "The biggest boy around.",
- "Human",
- 21,
- Sex.Male,
- Gender.Epicene,
- new HumanoidCharacterAppearance(
+ return new()
+ {
+ Name = "Charlie Charlieson",
+ FlavorText = "The biggest boy around.",
+ Species = "Human",
+ Age = 21,
+ Appearance = new(
"Afro",
Color.Aqua,
"Shaved",
Color.Aquamarine,
Color.Azure,
Color.Beige,
- new ()
- ),
- SpawnPriorityPreference.None,
- new Dictionary<string, JobPriority>
- {
- {SharedGameTicker.FallbackOverflowJob, JobPriority.High}
- },
- PreferenceUnavailableMode.StayInLobby,
- new List<string> (),
- new List<string>(),
- new Dictionary<string, RoleLoadout>()
- );
+ new ())
+ };
}
private static ServerDbSqlite GetDb(RobustIntegrationTest.ServerIntegrationInstance server)
using Content.Shared.Humanoid.Markings;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
-using Content.Shared.Preferences.Loadouts.Effects;
using Microsoft.EntityFrameworkCore;
using Robust.Shared.Enums;
using Robust.Shared.Network;
spawnPriority,
jobs,
(PreferenceUnavailableMode) profile.PreferenceUnavailable,
- antags.ToList(),
- traits.ToList(),
+ antags.ToHashSet(),
+ traits.ToHashSet(),
loadouts
);
}
[DataDefinition]
[Serializable, NetSerializable]
-public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance
+public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance, IEquatable<HumanoidCharacterAppearance>
{
+ [DataField("hair")]
+ public string HairStyleId { get; set; } = HairStyles.DefaultHairStyle;
+
+ [DataField]
+ public Color HairColor { get; set; } = Color.Black;
+
+ [DataField("facialHair")]
+ public string FacialHairStyleId { get; set; } = HairStyles.DefaultFacialHairStyle;
+
+ [DataField]
+ public Color FacialHairColor { get; set; } = Color.Black;
+
+ [DataField]
+ public Color EyeColor { get; set; } = Color.Black;
+
+ [DataField]
+ public Color SkinColor { get; set; } = Humanoid.SkinColor.ValidHumanSkinTone;
+
+ [DataField]
+ public List<Marking> Markings { get; set; } = new();
+
public HumanoidCharacterAppearance(string hairStyleId,
Color hairColor,
string facialHairStyleId,
Markings = markings;
}
- [DataField("hair")]
- public string HairStyleId { get; private set; }
-
- [DataField("hairColor")]
- public Color HairColor { get; private set; }
-
- [DataField("facialHair")]
- public string FacialHairStyleId { get; private set; }
-
- [DataField("facialHairColor")]
- public Color FacialHairColor { get; private set; }
-
- [DataField("eyeColor")]
- public Color EyeColor { get; private set; }
-
- [DataField("skinColor")]
- public Color SkinColor { get; private set; }
+ public HumanoidCharacterAppearance(HumanoidCharacterAppearance other) :
+ this(other.HairStyleId, other.HairColor, other.FacialHairStyleId, other.FacialHairColor, other.EyeColor, other.SkinColor, new(other.Markings))
+ {
- [DataField("markings")]
- public List<Marking> Markings { get; private set; }
+ }
public HumanoidCharacterAppearance WithHairStyleName(string newName)
{
return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, newMarkings);
}
- public HumanoidCharacterAppearance() : this(
- HairStyles.DefaultHairStyle,
- Color.Black,
- HairStyles.DefaultFacialHairStyle,
- Color.Black,
- Color.Black,
- Humanoid.SkinColor.ValidHumanSkinTone,
- new ()
- )
- {
- }
-
public static HumanoidCharacterAppearance DefaultWithSpecies(string species)
{
var speciesPrototype = IoCManager.Resolve<IPrototypeManager>().Index<SpeciesPrototype>(species);
if (!Markings.SequenceEqual(other.Markings)) return false;
return true;
}
+
+ public bool Equals(HumanoidCharacterAppearance? other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return HairStyleId == other.HairStyleId &&
+ HairColor.Equals(other.HairColor) &&
+ FacialHairStyleId == other.FacialHairStyleId &&
+ FacialHairColor.Equals(other.FacialHairColor) &&
+ EyeColor.Equals(other.EyeColor) &&
+ SkinColor.Equals(other.SkinColor) &&
+ Markings.SequenceEqual(other.Markings);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return ReferenceEquals(this, obj) || obj is HumanoidCharacterAppearance other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings);
+ }
+
+ public HumanoidCharacterAppearance Clone()
+ {
+ return new(this);
+ }
}
--- /dev/null
+using Content.Shared.Preferences;
+
+namespace Content.Shared.Humanoid;
+
+/// <summary>
+/// Holds all of the data for importing / exporting character profiles.
+/// </summary>
+[DataDefinition]
+public sealed partial class HumanoidProfileExport
+{
+ [DataField]
+ public string ForkId;
+
+ [DataField]
+ public int Version = 1;
+
+ [DataField(required: true)]
+ public HumanoidCharacterProfile Profile = default!;
+}
+using System.IO;
using System.Linq;
+using Content.Shared.CCVar;
using Content.Shared.Decals;
using Content.Shared.Examine;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.IdentityManagement;
using Content.Shared.Preferences;
+using Robust.Shared;
+using Robust.Shared.Configuration;
using Robust.Shared.GameObjects.Components.Localization;
using Robust.Shared.Network;
+using Robust.Shared.Player;
using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.Manager;
+using Robust.Shared.Serialization.Markdown;
+using Robust.Shared.Utility;
+using YamlDotNet.RepresentationModel;
namespace Content.Shared.Humanoid;
/// </summary>
public abstract class SharedHumanoidAppearanceSystem : EntitySystem
{
+ [Dependency] private readonly IConfigurationManager _cfgManager = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly ISerializationManager _serManager = default!;
[Dependency] private readonly MarkingManager _markingManager = default!;
[ValidatePrototypeId<SpeciesPrototype>]
SubscribeLocalEvent<HumanoidAppearanceComponent, ExaminedEvent>(OnExamined);
}
+ public DataNode ToDataNode(HumanoidCharacterProfile profile)
+ {
+ var export = new HumanoidProfileExport()
+ {
+ ForkId = _cfgManager.GetCVar(CVars.BuildForkId),
+ Profile = profile,
+ };
+
+ var dataNode = _serManager.WriteValue(export, alwaysWrite: true, notNullableOverride: true);
+ return dataNode;
+ }
+
+ public HumanoidCharacterProfile FromStream(Stream stream, ICommonSession session)
+ {
+ using var reader = new StreamReader(stream, EncodingHelpers.UTF8);
+ var yamlStream = new YamlStream();
+ yamlStream.Load(reader);
+
+ var root = yamlStream.Documents[0].RootNode;
+ var export = _serManager.Read<HumanoidProfileExport>(root.ToDataNode(), notNullableOverride: true);
+
+ /*
+ * Add custom handling here for forks / version numbers if you care.
+ */
+
+ var profile = export.Profile;
+ var collection = IoCManager.Instance;
+ profile.EnsureValid(session, collection!);
+ return profile;
+ }
+
private void OnInit(EntityUid uid, HumanoidAppearanceComponent humanoid, ComponentInit args)
{
if (string.IsNullOrEmpty(humanoid.Species) || _netManager.IsClient && !IsClientSide(uid))
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences.Loadouts;
-using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Roles;
using Content.Shared.Traits;
using Robust.Shared.Collections;
public const int MaxNameLength = 32;
public const int MaxDescLength = 512;
- private readonly Dictionary<string, JobPriority> _jobPriorities;
- private readonly List<string> _antagPreferences;
- private readonly List<string> _traitPreferences;
+ /// <summary>
+ /// Job preferences for initial spawn.
+ /// </summary>
+ [DataField]
+ private Dictionary<string, JobPriority> _jobPriorities = new()
+ {
+ {
+ SharedGameTicker.FallbackOverflowJob, JobPriority.High
+ }
+ };
+
+ /// <summary>
+ /// Antags we have opted in to.
+ /// </summary>
+ [DataField]
+ private HashSet<string> _antagPreferences = new();
+
+ /// <summary>
+ /// Enabled traits.
+ /// </summary>
+ [DataField]
+ private HashSet<string> _traitPreferences = new();
+ /// <summary>
+ /// <see cref="_loadouts"/>
+ /// </summary>
public IReadOnlyDictionary<string, RoleLoadout> Loadouts => _loadouts;
- private Dictionary<string, RoleLoadout> _loadouts;
+ [DataField]
+ private Dictionary<string, RoleLoadout> _loadouts = new();
+
+ [DataField]
+ public string Name { get; set; } = "John Doe";
+
+ /// <summary>
+ /// Detailed text that can appear for the character if <see cref="CCVars.FlavorText"/> is enabled.
+ /// </summary>
+ [DataField]
+ public string FlavorText { get; set; } = string.Empty;
+
+ /// <summary>
+ /// Associated <see cref="SpeciesPrototype"/> for this profile.
+ /// </summary>
+ [DataField]
+ public string Species { get; set; } = SharedHumanoidAppearanceSystem.DefaultSpecies;
+
+ [DataField]
+ public int Age { get; set; } = 18;
+
+ [DataField]
+ public Sex Sex { get; private set; } = Sex.Male;
+
+ [DataField]
+ public Gender Gender { get; private set; } = Gender.Male;
+
+ /// <summary>
+ /// <see cref="Appearance"/>
+ /// </summary>
+ public ICharacterAppearance CharacterAppearance => Appearance;
- // What in the lord is happening here.
- private HumanoidCharacterProfile(
+ /// <summary>
+ /// Stores markings, eye colors, etc for the profile.
+ /// </summary>
+ [DataField]
+ public HumanoidCharacterAppearance Appearance { get; set; } = new();
+
+ /// <summary>
+ /// When spawning into a round what's the preferred spot to spawn.
+ /// </summary>
+ [DataField]
+ public SpawnPriorityPreference SpawnPriority { get; private set; } = SpawnPriorityPreference.None;
+
+ /// <summary>
+ /// <see cref="_jobPriorities"/>
+ /// </summary>
+ public IReadOnlyDictionary<string, JobPriority> JobPriorities => _jobPriorities;
+
+ /// <summary>
+ /// <see cref="_antagPreferences"/>
+ /// </summary>
+ public IReadOnlySet<string> AntagPreferences => _antagPreferences;
+
+ /// <summary>
+ /// <see cref="_traitPreferences"/>
+ /// </summary>
+ public IReadOnlySet<string> TraitPreferences => _traitPreferences;
+
+ /// <summary>
+ /// If we're unable to get one of our preferred jobs do we spawn as a fallback job or do we stay in lobby.
+ /// </summary>
+ [DataField]
+ public PreferenceUnavailableMode PreferenceUnavailable { get; private set; } =
+ PreferenceUnavailableMode.SpawnAsOverflow;
+
+ public HumanoidCharacterProfile(
string name,
string flavortext,
string species,
SpawnPriorityPreference spawnPriority,
Dictionary<string, JobPriority> jobPriorities,
PreferenceUnavailableMode preferenceUnavailable,
- List<string> antagPreferences,
- List<string> traitPreferences,
+ HashSet<string> antagPreferences,
+ HashSet<string> traitPreferences,
Dictionary<string, RoleLoadout> loadouts)
{
Name = name;
_loadouts = loadouts;
}
- /// <summary>Copy constructor but with overridable references (to prevent useless copies)</summary>
- private HumanoidCharacterProfile(
- HumanoidCharacterProfile other,
- Dictionary<string, JobPriority> jobPriorities,
- List<string> antagPreferences,
- List<string> traitPreferences,
- Dictionary<string, RoleLoadout> loadouts)
- : this(other.Name, other.FlavorText, other.Species, other.Age, other.Sex, other.Gender, other.Appearance, other.SpawnPriority,
- jobPriorities, other.PreferenceUnavailable, antagPreferences, traitPreferences, loadouts)
- {
- }
-
/// <summary>Copy constructor</summary>
- private HumanoidCharacterProfile(HumanoidCharacterProfile other)
- : this(other, new Dictionary<string, JobPriority>(other.JobPriorities), new List<string>(other.AntagPreferences), new List<string>(other.TraitPreferences), new Dictionary<string, RoleLoadout>(other.Loadouts))
- {
- }
-
- public HumanoidCharacterProfile(
- string name,
- string flavortext,
- string species,
- int age,
- Sex sex,
- Gender gender,
- HumanoidCharacterAppearance appearance,
- SpawnPriorityPreference spawnPriority,
- IReadOnlyDictionary<string, JobPriority> jobPriorities,
- PreferenceUnavailableMode preferenceUnavailable,
- IReadOnlyList<string> antagPreferences,
- IReadOnlyList<string> traitPreferences,
- Dictionary<string, RoleLoadout> loadouts)
- : this(name, flavortext, species, age, sex, gender, appearance, spawnPriority, new Dictionary<string, JobPriority>(jobPriorities),
- preferenceUnavailable, new List<string>(antagPreferences), new List<string>(traitPreferences), new Dictionary<string, RoleLoadout>(loadouts))
+ public HumanoidCharacterProfile(HumanoidCharacterProfile other)
+ : this(other.Name,
+ other.FlavorText,
+ other.Species,
+ other.Age,
+ other.Sex,
+ other.Gender,
+ other.Appearance.Clone(),
+ other.SpawnPriority,
+ new Dictionary<string, JobPriority>(other.JobPriorities),
+ other.PreferenceUnavailable,
+ new HashSet<string>(other.AntagPreferences),
+ new HashSet<string>(other.TraitPreferences),
+ new Dictionary<string, RoleLoadout>(other.Loadouts))
{
}
/// Defaults to <see cref="SharedHumanoidAppearanceSystem.DefaultSpecies"/> for the species.
/// </summary>
/// <returns></returns>
- public HumanoidCharacterProfile() : this(
- "John Doe",
- "",
- SharedHumanoidAppearanceSystem.DefaultSpecies,
- 18,
- Sex.Male,
- Gender.Male,
- new HumanoidCharacterAppearance(),
- SpawnPriorityPreference.None,
- new Dictionary<string, JobPriority>
- {
- {SharedGameTicker.FallbackOverflowJob, JobPriority.High}
- },
- PreferenceUnavailableMode.SpawnAsOverflow,
- new List<string>(),
- new List<string>(),
- new Dictionary<string, RoleLoadout>())
+ public HumanoidCharacterProfile()
{
}
/// <returns>Humanoid character profile with default settings.</returns>
public static HumanoidCharacterProfile DefaultWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies)
{
- return new(
- "John Doe",
- "",
- species,
- 18,
- Sex.Male,
- Gender.Male,
- HumanoidCharacterAppearance.DefaultWithSpecies(species),
- SpawnPriorityPreference.None,
- new Dictionary<string, JobPriority>
- {
- {SharedGameTicker.FallbackOverflowJob, JobPriority.High}
- },
- PreferenceUnavailableMode.SpawnAsOverflow,
- new List<string>(),
- new List<string>(),
- new Dictionary<string, RoleLoadout>());
+ return new()
+ {
+ Species = species,
+ };
}
// TODO: This should eventually not be a visual change only.
var name = GetName(species, gender);
- return new HumanoidCharacterProfile(name, "", species, age, sex, gender, HumanoidCharacterAppearance.Random(species, sex), SpawnPriorityPreference.None,
- new Dictionary<string, JobPriority>
- {
- {SharedGameTicker.FallbackOverflowJob, JobPriority.High},
- }, PreferenceUnavailableMode.StayInLobby, new List<string>(), new List<string>(), new Dictionary<string, RoleLoadout>());
+ return new HumanoidCharacterProfile()
+ {
+ Name = name,
+ Sex = sex,
+ Age = age,
+ Gender = gender,
+ Species = species,
+ Appearance = HumanoidCharacterAppearance.Random(species, sex),
+ };
}
- public string Name { get; private set; }
- public string FlavorText { get; private set; }
- public string Species { get; private set; }
-
- [DataField("age")]
- public int Age { get; private set; }
-
- [DataField("sex")]
- public Sex Sex { get; private set; }
-
- [DataField("gender")]
- public Gender Gender { get; private set; }
-
- public ICharacterAppearance CharacterAppearance => Appearance;
-
- [DataField("appearance")]
- public HumanoidCharacterAppearance Appearance { get; private set; }
- public SpawnPriorityPreference SpawnPriority { get; private set; }
- public IReadOnlyDictionary<string, JobPriority> JobPriorities => _jobPriorities;
- public IReadOnlyList<string> AntagPreferences => _antagPreferences;
- public IReadOnlyList<string> TraitPreferences => _traitPreferences;
- public PreferenceUnavailableMode PreferenceUnavailable { get; private set; }
-
public HumanoidCharacterProfile WithName(string name)
{
return new(this) { Name = name };
public HumanoidCharacterProfile WithJobPriorities(IEnumerable<KeyValuePair<string, JobPriority>> jobPriorities)
{
- return new(this, new Dictionary<string, JobPriority>(jobPriorities), _antagPreferences, _traitPreferences, _loadouts);
+ return new(this)
+ {
+ _jobPriorities = new Dictionary<string, JobPriority>(jobPriorities),
+ };
}
public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority)
{
dictionary[jobId] = priority;
}
- return new(this, dictionary, _antagPreferences, _traitPreferences, _loadouts);
+
+ return new(this)
+ {
+ _jobPriorities = dictionary,
+ };
}
public HumanoidCharacterProfile WithPreferenceUnavailable(PreferenceUnavailableMode mode)
public HumanoidCharacterProfile WithAntagPreferences(IEnumerable<string> antagPreferences)
{
- return new(this, _jobPriorities, new List<string>(antagPreferences), _traitPreferences, _loadouts);
+ return new(this)
+ {
+ _antagPreferences = new HashSet<string>(antagPreferences),
+ };
}
public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref)
{
- var list = new List<string>(_antagPreferences);
+ var list = new HashSet<string>(_antagPreferences);
if (pref)
{
- if (!list.Contains(antagId))
- {
- list.Add(antagId);
- }
+ list.Add(antagId);
}
else
{
- if (list.Contains(antagId))
- {
- list.Remove(antagId);
- }
+ list.Remove(antagId);
}
- return new(this, _jobPriorities, list, _traitPreferences, _loadouts);
+ return new(this)
+ {
+ _antagPreferences = list,
+ };
}
public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref)
{
- var list = new List<string>(_traitPreferences);
+ var list = new HashSet<string>(_traitPreferences);
- // TODO: Maybe just refactor this to HashSet? Same with _antagPreferences
if (pref)
{
- if (!list.Contains(traitId))
- {
- list.Add(traitId);
- }
+ list.Add(traitId);
}
else
{
- if (list.Contains(traitId))
- {
- list.Remove(traitId);
- }
+ list.Remove(traitId);
}
- return new(this, _jobPriorities, _antagPreferences, list, _loadouts);
+
+ return new(this)
+ {
+ _traitPreferences = list,
+ };
}
public string Summary =>
PreferenceUnavailable = prefsUnavailableMode;
_antagPreferences.Clear();
- _antagPreferences.AddRange(antags);
+ _antagPreferences.UnionWith(antags);
_traitPreferences.Clear();
- _traitPreferences.AddRange(traits);
+ _traitPreferences.UnionWith(traits);
// Checks prototypes exist for all loadouts and dump / set to default if not.
var toRemove = new ValueList<string>();
continue;
}
- loadouts.EnsureValid(session, collection);
+ loadouts.EnsureValid(this, session, collection);
}
foreach (var value in toRemove)
public override bool Equals(object? obj)
{
- return obj is HumanoidCharacterProfile other && MemberwiseEquals(other);
+ return ReferenceEquals(this, obj) || obj is HumanoidCharacterProfile other && Equals(other);
}
public override int GetHashCode()
{
- return HashCode.Combine(
- HashCode.Combine(
- Name,
- Species,
- Age,
- Sex,
- Gender,
- Appearance
- ),
- SpawnPriority,
- PreferenceUnavailable,
- _jobPriorities,
- _antagPreferences,
- _traitPreferences,
- _loadouts
- );
+ var hashCode = new HashCode();
+ hashCode.Add(_jobPriorities);
+ hashCode.Add(_antagPreferences);
+ hashCode.Add(_traitPreferences);
+ hashCode.Add(_loadouts);
+ hashCode.Add(Name);
+ hashCode.Add(FlavorText);
+ hashCode.Add(Species);
+ hashCode.Add(Age);
+ hashCode.Add((int)Sex);
+ hashCode.Add((int)Gender);
+ hashCode.Add(Appearance);
+ hashCode.Add((int)SpawnPriority);
+ hashCode.Add((int)PreferenceUnavailable);
+ return hashCode.ToHashCode();
}
public void SetLoadout(RoleLoadout loadout)
}
copied[loadout.Role] = loadout.Clone();
- return new(this, _jobPriorities, _antagPreferences, _traitPreferences, copied);
+ var profile = Clone();
+ profile._loadouts = copied;
+ return profile;
}
- public RoleLoadout GetLoadoutOrDefault(string id, IEntityManager entManager, IPrototypeManager protoManager)
+ public RoleLoadout GetLoadoutOrDefault(string id, ProtoId<SpeciesPrototype>? species, IEntityManager entManager, IPrototypeManager protoManager)
{
if (!_loadouts.TryGetValue(id, out var loadout))
{
loadout.SetDefault(protoManager);
return loadout;
}
+
+ public HumanoidCharacterProfile Clone()
+ {
+ return new HumanoidCharacterProfile(this);
+ }
}
}
[DataField(required: true)]
public ProtoId<LoadoutEffectGroupPrototype> Proto;
- public override bool Validate(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
+ public override bool Validate(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
{
var effectsProto = collection.Resolve<IPrototypeManager>().Index(Proto);
foreach (var effect in effectsProto.Effects)
{
- if (!effect.Validate(loadout, session, collection, out reason))
+ if (!effect.Validate(profile, loadout, session, collection, out reason))
return false;
}
[DataField(required: true)]
public JobRequirement Requirement = default!;
- public override bool Validate(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
+ public override bool Validate(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
{
var manager = collection.Resolve<ISharedPlaytimeManager>();
var playtimes = manager.GetPlayTimes(session);
/// Tries to validate the effect.
/// </summary>
public abstract bool Validate(
+ HumanoidCharacterProfile profile,
RoleLoadout loadout,
ICommonSession session,
IDependencyCollection collection,
public int Cost = 1;
public override bool Validate(
+ HumanoidCharacterProfile profile,
RoleLoadout loadout,
ICommonSession session,
IDependencyCollection collection,
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.Humanoid.Prototypes;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
namespace Content.Shared.Preferences.Loadouts.Effects;
-public sealed class SpeciesLoadoutEffect
+public sealed partial class SpeciesLoadoutEffect : LoadoutEffect
{
-
+ [DataField(required: true)]
+ public List<ProtoId<SpeciesPrototype>> Species = new();
+
+ public override bool Validate(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection,
+ [NotNullWhen(false)] out FormattedMessage? reason)
+ {
+ if (Species.Contains(profile.Species))
+ {
+ reason = null;
+ return true;
+ }
+
+ reason = FormattedMessage.FromUnformatted(Loc.GetString("loadout-group-species-restriction"));
+ return false;
+ }
}
/// <summary>
/// Specifies the selected prototype and custom data for a loadout.
/// </summary>
-[Serializable, NetSerializable]
-public sealed class Loadout
+[Serializable, NetSerializable, DataDefinition]
+public sealed partial class Loadout
{
+ [DataField]
public ProtoId<LoadoutPrototype> Prototype;
}
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Random;
using Robust.Shared.Collections;
using Robust.Shared.Player;
/// <summary>
/// Contains all of the selected data for a role's loadout.
/// </summary>
-[Serializable, NetSerializable]
-public sealed class RoleLoadout
+[Serializable, NetSerializable, DataDefinition]
+public sealed partial class RoleLoadout : IEquatable<RoleLoadout>
{
- public readonly ProtoId<RoleLoadoutPrototype> Role;
+ [DataField]
+ public ProtoId<RoleLoadoutPrototype> Role;
+ [DataField]
public Dictionary<ProtoId<LoadoutGroupPrototype>, List<Loadout>> SelectedLoadouts = new();
/*
/// <summary>
/// Ensures all prototypes exist and effects can be applied.
/// </summary>
- public void EnsureValid(ICommonSession session, IDependencyCollection collection)
+ public void EnsureValid(HumanoidCharacterProfile profile, ICommonSession session, IDependencyCollection collection)
{
var groupRemove = new ValueList<string>();
var protoManager = collection.Resolve<IPrototypeManager>();
}
// Validate the loadout can be applied (e.g. points).
- if (!IsValid(session, loadout.Prototype, collection, out _))
+ if (!IsValid(profile, session, loadout.Prototype, collection, out _))
{
loadouts.RemoveAt(i);
continue;
/// <summary>
/// Returns whether a loadout is valid or not.
/// </summary>
- public bool IsValid(ICommonSession session, ProtoId<LoadoutPrototype> loadout, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
+ public bool IsValid(HumanoidCharacterProfile profile, ICommonSession session, ProtoId<LoadoutPrototype> loadout, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
{
reason = null;
return false;
}
- if (!protoManager.TryIndex(Role, out var roleProto))
+ if (!protoManager.HasIndex(Role))
{
reason = FormattedMessage.FromUnformatted("loadouts-prototype-missing");
return false;
foreach (var effect in loadoutProto.Effects)
{
- valid = valid && effect.Validate(this, session, collection, out reason);
+ valid = valid && effect.Validate(profile, this, session, collection, out reason);
}
return valid;
return false;
}
+
+ public bool Equals(RoleLoadout? other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return Role.Equals(other.Role) && SelectedLoadouts.SequenceEqual(other.SelectedLoadouts) && Points == other.Points;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return ReferenceEquals(this, obj) || obj is RoleLoadout other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Role, SelectedLoadouts, Points);
+ }
}
character-setup-gui-character-setup-label = Character setup
character-setup-gui-character-setup-stats-button = Stats
character-setup-gui-character-setup-rules-button = Rules
-character-setup-gui-character-setup-save-button = Save
character-setup-gui-character-setup-close-button = Close
character-setup-gui-create-new-character-button = Create new slot...
character-setup-gui-create-new-character-button-tooltip = A maximum of {$maxCharacters} characters are allowed.
humanoid-profile-editor-import-button = Import
humanoid-profile-editor-export-button = Export
humanoid-profile-editor-save-button = Save
+humanoid-profile-editor-reset-button = Reset
humanoid-profile-editor-spawn-priority-label = Spawn priority:
humanoid-profile-editor-eyes-label = Eye color:
humanoid-profile-editor-jobs-tab = Jobs
- type: species
id: Vox
name: species-name-vox
- roundStart: false # sad...
+ roundStart: false # sad
prototype: MobVox
sprites: MobVoxSprites
markingLimits: MobVoxMarkingLimits