diff --git a/Content.Client/ClientPreferencesManager.cs b/Content.Client/ClientPreferencesManager.cs
index 3b338ba70c..35b1032fdd 100644
--- a/Content.Client/ClientPreferencesManager.cs
+++ b/Content.Client/ClientPreferencesManager.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using Content.Client.Interfaces;
using Content.Shared.Preferences;
using Robust.Shared.Interfaces.Network;
@@ -6,8 +7,9 @@ using Robust.Shared.IoC;
namespace Content.Client
{
///
- /// Receives and from the server during the initial connection.
- /// Stores preferences on the server through and .
+ /// Receives and from the server during the initial
+ /// connection.
+ /// Stores preferences on the server through and .
///
public class ClientPreferencesManager : SharedPreferencesManager, IClientPreferencesManager
{
@@ -24,14 +26,14 @@ namespace Content.Client
HandlePreferencesAndSettings);
}
- private void HandlePreferencesAndSettings(MsgPreferencesAndSettings message)
+ public void SelectCharacter(ICharacterProfile profile)
{
- Preferences = message.Preferences;
- Settings = message.Settings;
+ SelectCharacter(Preferences.IndexOfCharacter(profile));
}
public void SelectCharacter(int slot)
{
+ Preferences = new PlayerPreferences(Preferences.Characters, slot);
var msg = _netManager.CreateNetMessage();
msg.SelectedCharacterIndex = slot;
_netManager.ClientSendMessage(msg);
@@ -39,10 +41,34 @@ namespace Content.Client
public void UpdateCharacter(ICharacterProfile profile, int slot)
{
+ var characters = Preferences.Characters.ToArray();
+ characters[slot] = profile;
+ Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex);
var msg = _netManager.CreateNetMessage();
msg.Profile = profile;
msg.Slot = slot;
_netManager.ClientSendMessage(msg);
}
+
+ public void CreateCharacter(ICharacterProfile profile)
+ {
+ UpdateCharacter(profile, Preferences.FirstEmptySlot);
+ }
+
+ public void DeleteCharacter(ICharacterProfile profile)
+ {
+ DeleteCharacter(Preferences.IndexOfCharacter(profile));
+ }
+
+ public void DeleteCharacter(int slot)
+ {
+ UpdateCharacter(null, slot);
+ }
+
+ private void HandlePreferencesAndSettings(MsgPreferencesAndSettings message)
+ {
+ Preferences = message.Preferences;
+ Settings = message.Settings;
+ }
}
}
diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs
index 577b7861d4..478c76e952 100644
--- a/Content.Client/EntryPoint.cs
+++ b/Content.Client/EntryPoint.cs
@@ -22,7 +22,9 @@ using Robust.Client.Interfaces.UserInterface;
using Robust.Client.Player;
using Robust.Shared.ContentPack;
using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
+using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -215,6 +217,7 @@ namespace Content.Client
IoCManager.Resolve().Initialize();
IoCManager.Resolve().Initialize();
IoCManager.Resolve().Initialize();
+ IoCManager.Resolve().CreateNewMapEntity(MapId.Nullspace);
IoCManager.Resolve().Initialize();
}
diff --git a/Content.Client/GameObjects/Components/MagicMirrorBoundUserInterface.cs b/Content.Client/GameObjects/Components/MagicMirrorBoundUserInterface.cs
index 4ce94dd235..93eb3d6184 100644
--- a/Content.Client/GameObjects/Components/MagicMirrorBoundUserInterface.cs
+++ b/Content.Client/GameObjects/Components/MagicMirrorBoundUserInterface.cs
@@ -77,7 +77,7 @@ namespace Content.Client.GameObjects.Components
var styles = HairStyles.FacialHairStylesMap.ToList();
styles.Sort(HairStyles.FacialHairStyleComparer);
- foreach (var (styleName, styleState) in styles)
+ foreach (var (styleName, styleState) in HairStyles.FacialHairStylesMap)
{
Items.AddItem(styleName, humanFacialHairRSI[styleState].Frame0);
}
@@ -141,6 +141,7 @@ namespace Content.Client.GameObjects.Components
Items = new ItemList
{
SizeFlagsVertical = SizeFlags.FillExpand,
+ CustomMinimumSize = (300, 250)
};
vBox.AddChild(Items);
Items.OnItemSelected += ItemSelected;
diff --git a/Content.Client/GameObjects/Components/Mobs/HairComponent.cs b/Content.Client/GameObjects/Components/Mobs/HairComponent.cs
deleted file mode 100644
index 3940f40c44..0000000000
--- a/Content.Client/GameObjects/Components/Mobs/HairComponent.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-using Content.Shared.GameObjects.Components.Mobs;
-using Content.Shared.Preferences.Appearance;
-using Robust.Client.GameObjects;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Maths;
-
-namespace Content.Client.GameObjects.Components.Mobs
-{
- [RegisterComponent]
- public sealed class HairComponent : SharedHairComponent
- {
- protected override void Startup()
- {
- base.Startup();
-
- UpdateHairStyle();
- }
-
- public override string FacialHairStyleName
- {
- get => base.FacialHairStyleName;
- set
- {
- base.FacialHairStyleName = value;
- UpdateHairStyle();
- }
- }
-
- public override string HairStyleName
- {
- get => base.HairStyleName;
- set
- {
- base.HairStyleName = value;
- UpdateHairStyle();
- }
- }
-
- public override Color HairColor
- {
- get => base.HairColor;
- set
- {
- base.HairColor = value;
- UpdateHairStyle();
- }
- }
-
- public override Color FacialHairColor
- {
- get => base.FacialHairColor;
- set
- {
- base.FacialHairColor = value;
- UpdateHairStyle();
- }
- }
-
- private void UpdateHairStyle()
- {
- var sprite = Owner.GetComponent();
-
- sprite.LayerSetColor(HumanoidVisualLayers.Hair, HairColor);
- sprite.LayerSetColor(HumanoidVisualLayers.FacialHair, FacialHairColor);
-
- sprite.LayerSetState(HumanoidVisualLayers.Hair,
- HairStyles.HairStylesMap[HairStyleName ?? HairStyles.DefaultHairStyle]);
- sprite.LayerSetState(HumanoidVisualLayers.FacialHair,
- HairStyles.FacialHairStylesMap[FacialHairStyleName ?? HairStyles.DefaultFacialHairStyle]);
- }
- }
-}
diff --git a/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs b/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs
new file mode 100644
index 0000000000..c7c4cd182e
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs
@@ -0,0 +1,62 @@
+using Content.Shared.GameObjects.Components.Mobs;
+using Content.Shared.Preferences;
+using Content.Shared.Preferences.Appearance;
+using Robust.Client.GameObjects;
+using Robust.Shared.GameObjects;
+
+namespace Content.Client.GameObjects.Components.Mobs
+{
+ [RegisterComponent]
+ public sealed class HumanoidAppearanceComponent : SharedHumanoidAppearanceComponent
+ {
+ public override HumanoidCharacterAppearance Appearance
+ {
+ get => base.Appearance;
+ set
+ {
+ base.Appearance = value;
+ UpdateLooks();
+ }
+ }
+
+ public override Sex Sex
+ {
+ get => base.Sex;
+ set
+ {
+ base.Sex = value;
+ UpdateLooks();
+ }
+ }
+
+ protected override void Startup()
+ {
+ base.Startup();
+
+ UpdateLooks();
+ }
+
+ private void UpdateLooks()
+ {
+ if (Appearance is null) return;
+ var sprite = Owner.GetComponent();
+
+ sprite.LayerSetColor(HumanoidVisualLayers.Hair, Appearance.HairColor);
+ sprite.LayerSetColor(HumanoidVisualLayers.FacialHair, Appearance.FacialHairColor);
+
+ sprite.LayerSetState(HumanoidVisualLayers.Body, Sex == Sex.Male ? "male" : "female");
+
+ var hairStyle = Appearance.HairStyleName;
+ if (string.IsNullOrWhiteSpace(hairStyle) || !HairStyles.HairStylesMap.ContainsKey(hairStyle))
+ hairStyle = HairStyles.DefaultHairStyle;
+ sprite.LayerSetState(HumanoidVisualLayers.Hair,
+ HairStyles.HairStylesMap[hairStyle]);
+
+ var facialHairStyle = Appearance.FacialHairStyleName;
+ if (string.IsNullOrWhiteSpace(facialHairStyle) || !HairStyles.FacialHairStylesMap.ContainsKey(facialHairStyle))
+ facialHairStyle = HairStyles.DefaultFacialHairStyle;
+ sprite.LayerSetState(HumanoidVisualLayers.FacialHair,
+ HairStyles.FacialHairStylesMap[facialHairStyle]);
+ }
+ }
+}
diff --git a/Content.Client/GameTicking/ClientGameTicker.cs b/Content.Client/GameTicking/ClientGameTicker.cs
index 2e60f19f53..8dcfd10a3a 100644
--- a/Content.Client/GameTicking/ClientGameTicker.cs
+++ b/Content.Client/GameTicking/ClientGameTicker.cs
@@ -13,9 +13,9 @@ using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.Interfaces.UserInterface;
using Robust.Client.Player;
-using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
+using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
@@ -38,16 +38,19 @@ namespace Content.Client.GameTicking
[Dependency] private IResourceCache _resourceCache;
[Dependency] private IPlayerManager _playerManager;
[Dependency] private IGameHud _gameHud;
+ [Dependency] private IEntityManager _entityManager;
+ [Dependency] private IClientPreferencesManager _preferencesManager;
#pragma warning restore 649
[ViewVariables] private bool _areWeReady;
- [ViewVariables] private bool _initialized;
- [ViewVariables] private TickerState _tickerState;
+ [ViewVariables] private CharacterSetupGui _characterSetup;
[ViewVariables] private ChatBox _gameChat;
- [ViewVariables] private LobbyGui _lobby;
[ViewVariables] private bool _gameStarted;
- [ViewVariables] private DateTime _startTime;
+ [ViewVariables] private bool _initialized;
+ [ViewVariables] private LobbyGui _lobby;
[ViewVariables] private string _serverInfoBlob;
+ [ViewVariables] private DateTime _startTime;
+ [ViewVariables] private TickerState _tickerState;
public void Initialize()
{
@@ -189,7 +192,15 @@ namespace Content.Client.GameTicking
_tickerState = TickerState.InLobby;
- _lobby = new LobbyGui(_localization, _resourceCache);
+ _characterSetup = new CharacterSetupGui(_entityManager, _localization, _resourceCache, _preferencesManager);
+ LayoutContainer.SetAnchorPreset(_characterSetup, LayoutContainer.LayoutPreset.Wide);
+ _characterSetup.CloseButton.OnPressed += args =>
+ {
+ _lobby.CharacterPreview.UpdateUI();
+ _userInterfaceManager.StateRoot.AddChild(_lobby);
+ _userInterfaceManager.StateRoot.RemoveChild(_characterSetup);
+ };
+ _lobby = new LobbyGui(_entityManager, _localization, _resourceCache, _preferencesManager);
_userInterfaceManager.StateRoot.AddChild(_lobby);
LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide);
@@ -204,6 +215,12 @@ namespace Content.Client.GameTicking
_updateLobbyUi();
+ _lobby.CharacterPreview.CharacterSetupButton.OnPressed += args =>
+ {
+ _userInterfaceManager.StateRoot.RemoveChild(_lobby);
+ _userInterfaceManager.StateRoot.AddChild(_characterSetup);
+ };
+
_lobby.ObserveButton.OnPressed += args => _console.ProcessCommand("observe");
_lobby.ReadyButton.OnPressed += args =>
{
diff --git a/Content.Client/Interfaces/IClientPreferencesManager.cs b/Content.Client/Interfaces/IClientPreferencesManager.cs
index c983c4f933..d64f019e7e 100644
--- a/Content.Client/Interfaces/IClientPreferencesManager.cs
+++ b/Content.Client/Interfaces/IClientPreferencesManager.cs
@@ -4,10 +4,14 @@ namespace Content.Client.Interfaces
{
public interface IClientPreferencesManager
{
- void Initialize();
GameSettings Settings { get; }
PlayerPreferences Preferences { get; }
+ void Initialize();
+ void SelectCharacter(ICharacterProfile profile);
void SelectCharacter(int slot);
void UpdateCharacter(ICharacterProfile profile, int slot);
+ void CreateCharacter(ICharacterProfile profile);
+ void DeleteCharacter(ICharacterProfile profile);
+ void DeleteCharacter(int slot);
}
}
diff --git a/Content.Client/UserInterface/CharacterSetupGui.cs b/Content.Client/UserInterface/CharacterSetupGui.cs
new file mode 100644
index 0000000000..b647c5b26f
--- /dev/null
+++ b/Content.Client/UserInterface/CharacterSetupGui.cs
@@ -0,0 +1,268 @@
+using Content.Client.GameObjects.Components.Mobs;
+using Content.Client.Interfaces;
+using Content.Client.Utility;
+using Content.Shared.Preferences;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics.Drawing;
+using Robust.Client.Interfaces.ResourceManagement;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Localization;
+using Robust.Shared.Map;
+using Robust.Shared.Maths;
+
+namespace Content.Client.UserInterface
+{
+ public class CharacterSetupGui : Control
+ {
+ private readonly VBoxContainer _charactersVBox;
+ private readonly Button _createNewCharacterButton;
+ private readonly IEntityManager _entityManager;
+ private readonly HumanoidProfileEditor _humanoidProfileEditor;
+ private readonly IClientPreferencesManager _preferencesManager;
+ public readonly Button CloseButton;
+
+ public CharacterSetupGui(IEntityManager entityManager,
+ ILocalizationManager localization,
+ IResourceCache resourceCache,
+ IClientPreferencesManager preferencesManager)
+ {
+ _entityManager = entityManager;
+ _preferencesManager = preferencesManager;
+ var margin = new MarginContainer
+ {
+ MarginBottomOverride = 20,
+ MarginLeftOverride = 20,
+ MarginRightOverride = 20,
+ MarginTopOverride = 20
+ };
+
+ AddChild(margin);
+
+ var panelTex = resourceCache.GetTexture("/Nano/button.svg.96dpi.png");
+ var back = new StyleBoxTexture
+ {
+ Texture = panelTex,
+ Modulate = new Color(37, 37, 42)
+ };
+ back.SetPatchMargin(StyleBox.Margin.All, 10);
+
+ var panel = new PanelContainer
+ {
+ PanelOverride = back
+ };
+
+ margin.AddChild(panel);
+
+ var vBox = new VBoxContainer {SeparationOverride = 0};
+
+ margin.AddChild(vBox);
+
+ CloseButton = new Button
+ {
+ SizeFlagsHorizontal = SizeFlags.Expand | SizeFlags.ShrinkEnd,
+ Text = localization.GetString("Save and close"),
+ StyleClasses = {NanoStyle.StyleClassButtonBig}
+ };
+
+ var topHBox = new HBoxContainer
+ {
+ CustomMinimumSize = (0, 40),
+ Children =
+ {
+ new MarginContainer
+ {
+ MarginLeftOverride = 8,
+ Children =
+ {
+ new Label
+ {
+ Text = localization.GetString("Character Setup"),
+ StyleClasses = {NanoStyle.StyleClassLabelHeadingBigger},
+ VAlign = Label.VAlignMode.Center,
+ SizeFlagsHorizontal = SizeFlags.Expand | SizeFlags.ShrinkCenter
+ }
+ }
+ },
+ CloseButton
+ }
+ };
+
+ vBox.AddChild(topHBox);
+
+ vBox.AddChild(new PanelContainer
+ {
+ PanelOverride = new StyleBoxFlat
+ {
+ BackgroundColor = NanoStyle.NanoGold,
+ ContentMarginTopOverride = 2
+ }
+ });
+
+ var hBox = new HBoxContainer
+ {
+ SizeFlagsVertical = SizeFlags.FillExpand,
+ SeparationOverride = 0
+ };
+ vBox.AddChild(hBox);
+
+ _charactersVBox = new VBoxContainer();
+
+ hBox.AddChild(new MarginContainer
+ {
+ CustomMinimumSize = (420, 0),
+ SizeFlagsHorizontal = SizeFlags.Fill,
+ MarginTopOverride = 5,
+ MarginLeftOverride = 5,
+ Children =
+ {
+ new ScrollContainer
+ {
+ SizeFlagsVertical = SizeFlags.FillExpand,
+ Children =
+ {
+ _charactersVBox
+ }
+ }
+ }
+ });
+
+ _createNewCharacterButton = new Button
+ {
+ Text = "Create new slot...",
+ ToolTip = $"A maximum of {preferencesManager.Settings.MaxCharacterSlots} characters are allowed."
+ };
+ _createNewCharacterButton.OnPressed += args =>
+ {
+ preferencesManager.CreateCharacter(HumanoidCharacterProfile.Default());
+ UpdateUI();
+ };
+
+ hBox.AddChild(new PanelContainer
+ {
+ PanelOverride = new StyleBoxFlat {BackgroundColor = NanoStyle.NanoGold},
+ CustomMinimumSize = (2, 0)
+ });
+ _humanoidProfileEditor = new HumanoidProfileEditor(localization, preferencesManager);
+ _humanoidProfileEditor.OnProfileChanged += newProfile => { UpdateUI(); };
+ hBox.AddChild(_humanoidProfileEditor);
+
+ UpdateUI();
+ }
+
+ private void UpdateUI()
+ {
+ var numberOfFullSlots = 0;
+ var characterButtonsGroup = new ButtonGroup();
+ _charactersVBox.RemoveAllChildren();
+ var characterIndex = 0;
+ foreach (var character in _preferencesManager.Preferences.Characters)
+ {
+ if (character is null)
+ {
+ characterIndex++;
+ continue;
+ }
+
+ numberOfFullSlots++;
+ var characterPickerButton = new CharacterPickerButton(_entityManager,
+ _preferencesManager,
+ characterButtonsGroup,
+ character);
+ _charactersVBox.AddChild(characterPickerButton);
+
+ var characterIndexCopy = characterIndex;
+ characterPickerButton.ActualButton.OnPressed += args =>
+ {
+ _humanoidProfileEditor.Profile = (HumanoidCharacterProfile) character;
+ _humanoidProfileEditor.CharacterSlot = characterIndexCopy;
+ _humanoidProfileEditor.UpdateControls();
+ _preferencesManager.SelectCharacter(character);
+ };
+ characterIndex++;
+ }
+
+ _createNewCharacterButton.Disabled =
+ numberOfFullSlots >= _preferencesManager.Settings.MaxCharacterSlots;
+ _charactersVBox.AddChild(_createNewCharacterButton);
+ }
+
+ private class CharacterPickerButton : Control
+ {
+ public readonly Button ActualButton;
+ private IEntity _previewDummy;
+
+ public CharacterPickerButton(
+ IEntityManager entityManager,
+ IClientPreferencesManager preferencesManager,
+ ButtonGroup group,
+ ICharacterProfile profile)
+ {
+ _previewDummy = entityManager.SpawnEntityAt("HumanMob_Dummy",
+ new MapCoordinates(Vector2.Zero, MapId.Nullspace));
+ _previewDummy.GetComponent().UpdateFromProfile(profile);
+
+ var isSelectedCharacter = profile == preferencesManager.Preferences.SelectedCharacter;
+
+ ActualButton = new Button
+ {
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ SizeFlagsVertical = SizeFlags.FillExpand,
+ ToggleMode = true,
+ Group = group
+ };
+ if (isSelectedCharacter)
+ ActualButton.Pressed = true;
+ AddChild(ActualButton);
+
+ var view = new SpriteView
+ {
+ Sprite = _previewDummy.GetComponent(),
+ Scale = (2, 2),
+ MouseFilter = MouseFilterMode.Ignore,
+ OverrideDirection = Direction.South
+ };
+
+ var descriptionLabel = new Label
+ {
+ Text = $"{profile.Name}\nAssistant" //TODO implement job selection
+ };
+ var deleteButton = new Button
+ {
+ Text = "Delete",
+ Visible = !isSelectedCharacter,
+ SizeFlagsHorizontal = SizeFlags.ShrinkEnd
+ };
+ deleteButton.OnPressed += args =>
+ {
+ Parent.RemoveChild(this);
+ preferencesManager.DeleteCharacter(profile);
+ };
+
+ var internalHBox = new HBoxContainer
+ {
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ MouseFilter = MouseFilterMode.Ignore,
+ SeparationOverride = 0,
+ Children =
+ {
+ view,
+ descriptionLabel,
+ deleteButton
+ }
+ };
+
+ AddChild(internalHBox);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing) return;
+ _previewDummy.Delete();
+ _previewDummy = null;
+ }
+ }
+ }
+}
diff --git a/Content.Client/UserInterface/HumanoidProfileEditor.cs b/Content.Client/UserInterface/HumanoidProfileEditor.cs
new file mode 100644
index 0000000000..88e477dbd0
--- /dev/null
+++ b/Content.Client/UserInterface/HumanoidProfileEditor.cs
@@ -0,0 +1,347 @@
+using System;
+using Content.Client.GameObjects.Components;
+using Content.Client.Interfaces;
+using Content.Shared.Preferences;
+using Robust.Client.Graphics.Drawing;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Localization;
+using Robust.Shared.Maths;
+
+namespace Content.Client.UserInterface
+{
+ public class HumanoidProfileEditor : Control
+ {
+ private static readonly StyleBoxFlat HighlightedStyle = new StyleBoxFlat
+ {
+ BackgroundColor = new Color(47, 47, 53),
+ ContentMarginTopOverride = 10,
+ ContentMarginBottomOverride = 10,
+ ContentMarginLeftOverride = 10,
+ ContentMarginRightOverride = 10
+ };
+
+ private readonly LineEdit _ageEdit;
+
+ private readonly LineEdit _nameEdit;
+ private readonly IClientPreferencesManager _preferencesManager;
+ private readonly Button _saveButton;
+ private readonly Button _sexFemaleButton;
+ private readonly Button _sexMaleButton;
+ private readonly HairStylePicker _hairPicker;
+ private readonly FacialHairStylePicker _facialHairPicker;
+
+ private bool _isDirty;
+ public int CharacterSlot;
+ public HumanoidCharacterProfile Profile;
+ public event Action OnProfileChanged;
+
+ public HumanoidProfileEditor(ILocalizationManager localization,
+ IClientPreferencesManager preferencesManager)
+ {
+ Profile = (HumanoidCharacterProfile) preferencesManager.Preferences.SelectedCharacter;
+ CharacterSlot = preferencesManager.Preferences.SelectedCharacterIndex;
+ _preferencesManager = preferencesManager;
+
+ var margin = new MarginContainer
+ {
+ MarginTopOverride = 10,
+ MarginBottomOverride = 10,
+ MarginLeftOverride = 10,
+ MarginRightOverride = 10
+ };
+ AddChild(margin);
+
+ var vBox = new VBoxContainer();
+ margin.AddChild(vBox);
+
+ var middleContainer = new HBoxContainer
+ {
+ SeparationOverride = 10
+ };
+ vBox.AddChild(middleContainer);
+
+ var leftColumn = new VBoxContainer();
+ middleContainer.AddChild(leftColumn);
+
+ #region Randomize
+
+ {
+ var panel = HighlightedContainer();
+ var randomizeEverythingButton = new Button
+ {
+ Text = localization.GetString("Randomize everything"),
+ Disabled = true,
+ ToolTip = "Not yet implemented!"
+ };
+ panel.AddChild(randomizeEverythingButton);
+ leftColumn.AddChild(panel);
+ }
+
+ #endregion Randomize
+
+ var middleColumn = new VBoxContainer();
+ leftColumn.AddChild(middleColumn);
+
+ #region Name
+
+ {
+ var panel = HighlightedContainer();
+ var hBox = new HBoxContainer
+ {
+ SizeFlagsVertical = SizeFlags.FillExpand
+ };
+ var nameLabel = new Label {Text = localization.GetString("Name:")};
+ _nameEdit = new LineEdit
+ {
+ CustomMinimumSize = (270, 0),
+ SizeFlagsVertical = SizeFlags.ShrinkCenter
+ };
+ _nameEdit.OnTextChanged += args =>
+ {
+ Profile = Profile?.WithName(args.Text);
+ IsDirty = true;
+ };
+ var nameRandomButton = new Button
+ {
+ Text = localization.GetString("Randomize"),
+ Disabled = true,
+ ToolTip = "Not implemented yet!"
+ };
+ hBox.AddChild(nameLabel);
+ hBox.AddChild(_nameEdit);
+ hBox.AddChild(nameRandomButton);
+ panel.AddChild(hBox);
+ middleColumn.AddChild(panel);
+ }
+
+ #endregion Name
+
+ var sexAndAgeRow = new HBoxContainer
+ {
+ SeparationOverride = 10
+ };
+ middleColumn.AddChild(sexAndAgeRow);
+
+ #region Sex
+
+ {
+ var panel = HighlightedContainer();
+ var hBox = new HBoxContainer();
+ var sexLabel = new Label {Text = localization.GetString("Sex:")};
+
+ var sexButtonGroup = new ButtonGroup();
+
+ _sexMaleButton = new Button
+ {
+ Text = localization.GetString("Male"),
+ Group = sexButtonGroup
+ };
+ _sexMaleButton.OnPressed += args =>
+ {
+ Profile = Profile?.WithSex(Sex.Male);
+ IsDirty = true;
+ };
+ _sexFemaleButton = new Button
+ {
+ Text = localization.GetString("Female"),
+ Group = sexButtonGroup
+ };
+ _sexFemaleButton.OnPressed += args =>
+ {
+ Profile = Profile?.WithSex(Sex.Female);
+ IsDirty = true;
+ };
+ hBox.AddChild(sexLabel);
+ hBox.AddChild(_sexMaleButton);
+ hBox.AddChild(_sexFemaleButton);
+ panel.AddChild(hBox);
+ sexAndAgeRow.AddChild(panel);
+ }
+
+ #endregion Sex
+
+ #region Age
+
+ {
+ var panel = HighlightedContainer();
+ var hBox = new HBoxContainer();
+ var ageLabel = new Label {Text = localization.GetString("Age:")};
+ _ageEdit = new LineEdit {CustomMinimumSize = (40, 0)};
+ _ageEdit.OnTextChanged += args =>
+ {
+ if (!int.TryParse(args.Text, out var newAge))
+ return;
+ Profile = Profile?.WithAge(newAge);
+ IsDirty = true;
+ };
+ hBox.AddChild(ageLabel);
+ hBox.AddChild(_ageEdit);
+ panel.AddChild(hBox);
+ sexAndAgeRow.AddChild(panel);
+ }
+
+ #endregion Age
+
+ var rightColumn = new VBoxContainer();
+ middleContainer.AddChild(rightColumn);
+
+ #region Import/Export
+
+ {
+ var panelContainer = HighlightedContainer();
+ var hBox = new HBoxContainer();
+ var importButton = new Button
+ {
+ Text = localization.GetString("Import"),
+ Disabled = true,
+ ToolTip = "Not yet implemented!"
+ };
+ var exportButton = new Button
+ {
+ Text = localization.GetString("Export"),
+ Disabled = true,
+ ToolTip = "Not yet implemented!"
+ };
+ hBox.AddChild(importButton);
+ hBox.AddChild(exportButton);
+ panelContainer.AddChild(hBox);
+ rightColumn.AddChild(panelContainer);
+ }
+
+ #endregion Import/Export
+
+ #region Save
+
+ {
+ var panel = HighlightedContainer();
+ _saveButton = new Button
+ {
+ Text = localization.GetString("Save"),
+ SizeFlagsHorizontal = SizeFlags.ShrinkCenter
+ };
+ _saveButton.OnPressed += args =>
+ {
+ IsDirty = false;
+ _preferencesManager.UpdateCharacter(Profile, CharacterSlot);
+ OnProfileChanged?.Invoke(Profile);
+ };
+ panel.AddChild(_saveButton);
+ rightColumn.AddChild(panel);
+ }
+
+ #endregion Save
+
+ #region Hair
+
+ {
+ var panel = HighlightedContainer();
+ panel.SizeFlagsHorizontal = SizeFlags.None;
+ var hairHBox = new HBoxContainer();
+
+ _hairPicker = new HairStylePicker();
+ _hairPicker.Populate();
+
+ _hairPicker.OnHairStylePicked += newStyle =>
+ {
+ if (Profile is null)
+ return;
+ Profile = Profile.WithCharacterAppearance(
+ Profile.Appearance.WithHairStyleName(newStyle));
+ IsDirty = true;
+ };
+
+ _hairPicker.OnHairColorPicked += newColor =>
+ {
+ if (Profile is null)
+ return;
+ Profile = Profile.WithCharacterAppearance(
+ Profile.Appearance.WithHairColor(newColor));
+ IsDirty = true;
+ };
+
+ _facialHairPicker = new FacialHairStylePicker();
+ _facialHairPicker.Populate();
+
+ _facialHairPicker.OnHairStylePicked += newStyle =>
+ {
+ if (Profile is null)
+ return;
+ Profile = Profile.WithCharacterAppearance(
+ Profile.Appearance.WithFacialHairStyleName(newStyle));
+ IsDirty = true;
+ };
+
+ _facialHairPicker.OnHairColorPicked += newColor =>
+ {
+ if (Profile is null)
+ return;
+ Profile = Profile.WithCharacterAppearance(
+ Profile.Appearance.WithFacialHairColor(newColor));
+ IsDirty = true;
+ };
+
+ hairHBox.AddChild(_hairPicker);
+ hairHBox.AddChild(_facialHairPicker);
+
+ panel.AddChild(hairHBox);
+ vBox.AddChild(panel);
+ }
+
+ #endregion Hair
+
+ UpdateControls();
+ }
+
+ private bool IsDirty
+ {
+ get => _isDirty;
+ set
+ {
+ _isDirty = value;
+ UpdateSaveButton();
+ }
+ }
+
+ private static Control HighlightedContainer()
+ {
+ return new PanelContainer
+ {
+ PanelOverride = HighlightedStyle
+ };
+ }
+
+ private void UpdateSexControls()
+ {
+ if (Profile.Sex == Sex.Male)
+ _sexMaleButton.Pressed = true;
+ else
+ _sexFemaleButton.Pressed = true;
+ }
+
+ private void UpdateHairPickers()
+ {
+ _hairPicker.SetInitialData(
+ Profile.Appearance.HairColor,
+ Profile.Appearance.HairStyleName);
+ _facialHairPicker.SetInitialData(
+ Profile.Appearance.FacialHairColor,
+ Profile.Appearance.FacialHairStyleName);
+ }
+
+ private void UpdateSaveButton()
+ {
+ _saveButton.Disabled = !(Profile is null) || !IsDirty;
+ }
+
+ public void UpdateControls()
+ {
+ if (Profile is null) return;
+ _nameEdit.Text = Profile?.Name;
+ UpdateSexControls();
+ _ageEdit.Text = Profile?.Age.ToString();
+ UpdateHairPickers();
+ UpdateSaveButton();
+ }
+ }
+}
diff --git a/Content.Client/UserInterface/LobbyCharacterPreviewPanel.cs b/Content.Client/UserInterface/LobbyCharacterPreviewPanel.cs
new file mode 100644
index 0000000000..42935e3aee
--- /dev/null
+++ b/Content.Client/UserInterface/LobbyCharacterPreviewPanel.cs
@@ -0,0 +1,101 @@
+using Content.Client.GameObjects.Components.Mobs;
+using Content.Client.Interfaces;
+using Content.Shared.Preferences;
+using Robust.Client.Interfaces.GameObjects.Components;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Localization;
+using Robust.Shared.Map;
+using Robust.Shared.Maths;
+
+namespace Content.Client.UserInterface
+{
+ public class LobbyCharacterPreviewPanel : Control
+ {
+ private readonly IClientPreferencesManager _preferencesManager;
+ private IEntity _previewDummy;
+ private readonly Label _summaryLabel;
+
+ public LobbyCharacterPreviewPanel(IEntityManager entityManager,
+ ILocalizationManager localization,
+ IClientPreferencesManager preferencesManager)
+ {
+ _preferencesManager = preferencesManager;
+ _previewDummy = entityManager.SpawnEntityAt("HumanMob_Dummy",
+ new MapCoordinates(Vector2.Zero, MapId.Nullspace));
+
+ var header = new NanoHeading
+ {
+ Text = localization.GetString("Character setup")
+ };
+
+ CharacterSetupButton = new Button
+ {
+ Text = localization.GetString("Customize"),
+ SizeFlagsHorizontal = SizeFlags.None
+ };
+
+ _summaryLabel = new Label();
+
+ var viewSouth = MakeSpriteView(_previewDummy, Direction.South);
+ var viewNorth = MakeSpriteView(_previewDummy, Direction.North);
+ var viewWest = MakeSpriteView(_previewDummy, Direction.West);
+ var viewEast = MakeSpriteView(_previewDummy, Direction.East);
+
+ var vBox = new VBoxContainer();
+
+ vBox.AddChild(header);
+ vBox.AddChild(CharacterSetupButton);
+
+ vBox.AddChild(_summaryLabel);
+
+ var hBox = new HBoxContainer();
+ hBox.AddChild(viewSouth);
+ hBox.AddChild(viewNorth);
+ hBox.AddChild(viewWest);
+ hBox.AddChild(viewEast);
+
+ vBox.AddChild(hBox);
+
+ AddChild(vBox);
+
+ UpdateUI();
+ }
+
+ public Button CharacterSetupButton { get; }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing) return;
+ _previewDummy.Delete();
+ _previewDummy = null;
+ }
+
+ private static SpriteView MakeSpriteView(IEntity entity, Direction direction)
+ {
+ return new SpriteView
+ {
+ Sprite = entity.GetComponent(),
+ OverrideDirection = direction,
+ Scale = (2, 2)
+ };
+ }
+
+ public void UpdateUI()
+ {
+ if (!(_preferencesManager.Preferences.SelectedCharacter is HumanoidCharacterProfile selectedCharacter))
+ {
+ _summaryLabel.Text = string.Empty;
+ }
+ else
+ {
+ _summaryLabel.Text = selectedCharacter.Summary;
+ _previewDummy
+ .GetComponent()
+ .Appearance = (HumanoidCharacterAppearance) selectedCharacter.CharacterAppearance;
+ }
+ }
+ }
+}
diff --git a/Content.Client/UserInterface/LobbyGui.cs b/Content.Client/UserInterface/LobbyGui.cs
index 72e39a8301..5acd10f365 100644
--- a/Content.Client/UserInterface/LobbyGui.cs
+++ b/Content.Client/UserInterface/LobbyGui.cs
@@ -1,9 +1,11 @@
using Content.Client.Chat;
+using Content.Client.Interfaces;
using Content.Client.Utility;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
@@ -19,8 +21,12 @@ namespace Content.Client.UserInterface
public ChatBox Chat { get; }
public ItemList OnlinePlayerItemList { get; }
public ServerInfo ServerInfo { get; }
+ public LobbyCharacterPreviewPanel CharacterPreview { get; }
- public LobbyGui(ILocalizationManager localization, IResourceCache resourceCache)
+ public LobbyGui(IEntityManager entityManager,
+ ILocalizationManager localization,
+ IResourceCache resourceCache,
+ IClientPreferencesManager preferencesManager)
{
var margin = new MarginContainer
{
@@ -107,17 +113,20 @@ namespace Content.Client.UserInterface
};
vBox.AddChild(hBox);
+ CharacterPreview = new LobbyCharacterPreviewPanel(
+ entityManager,
+ localization,
+ preferencesManager)
+ {
+ SizeFlagsHorizontal = SizeFlags.None
+ };
hBox.AddChild(new VBoxContainer
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
SeparationOverride = 0,
Children =
{
- new Placeholder(resourceCache)
- {
- SizeFlagsVertical = SizeFlags.FillExpand,
- PlaceholderText = localization.GetString("Character UI\nPlaceholder")
- },
+ CharacterPreview,
new StripeBack
{
diff --git a/Content.Server.Database/Content.Server.Database.csproj b/Content.Server.Database/Content.Server.Database.csproj
index 469168a8e8..8123f496ed 100644
--- a/Content.Server.Database/Content.Server.Database.csproj
+++ b/Content.Server.Database/Content.Server.Database.csproj
@@ -1,5 +1,5 @@
-
+
$(TargetFramework)
@@ -11,18 +11,14 @@
true
enable
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
-
-
-
-
+
diff --git a/Content.Server.Database/Migrations/20200111103836_InitialCreate.Designer.cs b/Content.Server.Database/Migrations/20200111103836_InitialCreate.Designer.cs
deleted file mode 100644
index 3d5f2d5b1d..0000000000
--- a/Content.Server.Database/Migrations/20200111103836_InitialCreate.Designer.cs
+++ /dev/null
@@ -1,109 +0,0 @@
-//
-using System;
-using Content.Server.Database;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-
-namespace Content.Server.Database.Migrations
-{
- [DbContext(typeof(PreferencesDbContext))]
- [Migration("20200111103836_InitialCreate")]
- partial class InitialCreate
- {
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "3.1.0");
-
- modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b =>
- {
- b.Property("HumanoidProfileId")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER");
-
- b.Property("Age")
- .HasColumnType("INTEGER");
-
- b.Property("CharacterName")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("EyeColor")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("FacialHairColor")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("FacialHairName")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("HairColor")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("HairName")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("PrefsId")
- .HasColumnType("INTEGER");
-
- b.Property("Sex")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("SkinColor")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("Slot")
- .HasColumnType("INTEGER");
-
- b.Property("SlotName")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.HasKey("HumanoidProfileId");
-
- b.HasIndex("PrefsId");
-
- b.ToTable("HumanoidProfile");
- });
-
- modelBuilder.Entity("Content.Server.Database.Prefs", b =>
- {
- b.Property("PrefsId")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER");
-
- b.Property("SelectedCharacterSlot")
- .HasColumnType("INTEGER");
-
- b.Property("Username")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.HasKey("PrefsId");
-
- b.HasIndex("Username")
- .IsUnique();
-
- b.ToTable("Preferences");
- });
-
- modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b =>
- {
- b.HasOne("Content.Server.Database.Prefs", null)
- .WithMany("HumanoidProfiles")
- .HasForeignKey("PrefsId");
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/Content.Server.Database/Migrations/20200111103836_InitialCreate.cs b/Content.Server.Database/Migrations/20200111103836_InitialCreate.cs
deleted file mode 100644
index 1324d3ca88..0000000000
--- a/Content.Server.Database/Migrations/20200111103836_InitialCreate.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-using Microsoft.EntityFrameworkCore.Migrations;
-
-namespace Content.Server.Database.Migrations
-{
- public partial class InitialCreate : Migration
- {
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- "Preferences",
- table => new
- {
- PrefsId = table.Column()
- .Annotation("Sqlite:Autoincrement", true),
- Username = table.Column(),
- SelectedCharacterSlot = table.Column()
- },
- constraints: table => { table.PrimaryKey("PK_Preferences", x => x.PrefsId); });
-
- migrationBuilder.CreateTable(
- "HumanoidProfile",
- table => new
- {
- HumanoidProfileId = table.Column()
- .Annotation("Sqlite:Autoincrement", true),
- Slot = table.Column(),
- SlotName = table.Column(),
- CharacterName = table.Column(),
- Age = table.Column(),
- Sex = table.Column(),
- HairName = table.Column(),
- HairColor = table.Column(),
- FacialHairName = table.Column(),
- FacialHairColor = table.Column(),
- EyeColor = table.Column(),
- SkinColor = table.Column(),
- PrefsId = table.Column(nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_HumanoidProfile", x => x.HumanoidProfileId);
- table.ForeignKey(
- "FK_HumanoidProfile_Preferences_PrefsId",
- x => x.PrefsId,
- "Preferences",
- "PrefsId",
- onDelete: ReferentialAction.Restrict);
- });
-
- migrationBuilder.CreateIndex(
- "IX_HumanoidProfile_PrefsId",
- "HumanoidProfile",
- "PrefsId");
-
- migrationBuilder.CreateIndex(
- "IX_Preferences_Username",
- "Preferences",
- "Username",
- unique: true);
- }
-
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(
- "HumanoidProfile");
-
- migrationBuilder.DropTable(
- "Preferences");
- }
- }
-}
diff --git a/Content.Server.Database/Migrations/PreferencesDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/PreferencesDbContextModelSnapshot.cs
deleted file mode 100644
index 1cdebf3e2e..0000000000
--- a/Content.Server.Database/Migrations/PreferencesDbContextModelSnapshot.cs
+++ /dev/null
@@ -1,105 +0,0 @@
-//
-
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-
-namespace Content.Server.Database.Migrations
-{
- [DbContext(typeof(PreferencesDbContext))]
- internal class PreferencesDbContextModelSnapshot : ModelSnapshot
- {
- protected override void BuildModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "3.1.0");
-
- modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b =>
- {
- b.Property("HumanoidProfileId")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER");
-
- b.Property("Age")
- .HasColumnType("INTEGER");
-
- b.Property("CharacterName")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("EyeColor")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("FacialHairColor")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("FacialHairName")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("HairColor")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("HairName")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("PrefsId")
- .HasColumnType("INTEGER");
-
- b.Property("Sex")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("SkinColor")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("Slot")
- .HasColumnType("INTEGER");
-
- b.Property("SlotName")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.HasKey("HumanoidProfileId");
-
- b.HasIndex("PrefsId");
-
- b.ToTable("HumanoidProfile");
- });
-
- modelBuilder.Entity("Content.Server.Database.Prefs", b =>
- {
- b.Property("PrefsId")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER");
-
- b.Property("SelectedCharacterSlot")
- .HasColumnType("INTEGER");
-
- b.Property("Username")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.HasKey("PrefsId");
-
- b.HasIndex("Username")
- .IsUnique();
-
- b.ToTable("Preferences");
- });
-
- modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b =>
- {
- b.HasOne("Content.Server.Database.Prefs", null)
- .WithMany("HumanoidProfiles")
- .HasForeignKey("PrefsId");
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs
index 54b4fce67a..8c1dc07789 100644
--- a/Content.Server.Database/Model.cs
+++ b/Content.Server.Database/Model.cs
@@ -47,5 +47,8 @@ namespace Content.Server.Database
public string FacialHairColor { get; set; } = null!;
public string EyeColor { get; set; } = null!;
public string SkinColor { get; set; } = null!;
+
+ public int PrefsId { get; set; }
+ public Prefs Prefs { get; set; } = null!;
}
}
diff --git a/Content.Server.Database/PrefsDb.cs b/Content.Server.Database/PrefsDb.cs
index 506686dccc..622b85d07f 100644
--- a/Content.Server.Database/PrefsDb.cs
+++ b/Content.Server.Database/PrefsDb.cs
@@ -18,7 +18,10 @@ namespace Content.Server.Database
public Prefs GetPlayerPreferences(string username)
{
- return _prefsCtx.Preferences.SingleOrDefault(p => p.Username == username);
+ return _prefsCtx
+ .Preferences
+ .Include(p => p.HumanoidProfiles)
+ .SingleOrDefault(p => p.Username == username);
}
public void SaveSelectedCharacterIndex(string username, int slot)
@@ -45,6 +48,7 @@ namespace Content.Server.Database
.SingleOrDefault(h => h.Slot == newProfile.Slot);
if (!(oldProfile is null)) prefs.HumanoidProfiles.Remove(oldProfile);
prefs.HumanoidProfiles.Add(newProfile);
+ _prefsCtx.SaveChanges();
}
public void DeleteCharacterSlot(string username, int slot)
diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj
index 948ae862a2..83dada1c9e 100644
--- a/Content.Server/Content.Server.csproj
+++ b/Content.Server/Content.Server.csproj
@@ -16,7 +16,7 @@
-
+
false
diff --git a/Content.Server/GameObjects/Components/MagicMirrorComponent.cs b/Content.Server/GameObjects/Components/MagicMirrorComponent.cs
index 2f827cd91f..c245ef6aa3 100644
--- a/Content.Server/GameObjects/Components/MagicMirrorComponent.cs
+++ b/Content.Server/GameObjects/Components/MagicMirrorComponent.cs
@@ -27,7 +27,7 @@ namespace Content.Server.GameObjects.Components
private static void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
- if (!obj.Session.AttachedEntity.TryGetComponent(out HairComponent hair))
+ if (!obj.Session.AttachedEntity.TryGetComponent(out HumanoidAppearanceComponent looks))
{
return;
}
@@ -42,11 +42,11 @@ namespace Content.Server.GameObjects.Components
if (msg.IsFacialHair)
{
- hair.FacialHairStyleName = msg.HairName;
+ looks.Appearance = looks.Appearance.WithFacialHairStyleName(msg.HairName);
}
else
{
- hair.HairStyleName = msg.HairName;
+ looks.Appearance = looks.Appearance.WithHairStyleName(msg.HairName);
}
break;
@@ -57,11 +57,11 @@ namespace Content.Server.GameObjects.Components
if (msg.IsFacialHair)
{
- hair.FacialHairColor = color;
+ looks.Appearance = looks.Appearance.WithFacialHairColor(color);
}
else
{
- hair.HairColor = color;
+ looks.Appearance = looks.Appearance.WithHairColor(color);
}
break;
@@ -75,7 +75,7 @@ namespace Content.Server.GameObjects.Components
return;
}
- if (!eventArgs.User.TryGetComponent(out HairComponent hair))
+ if (!eventArgs.User.TryGetComponent(out HumanoidAppearanceComponent looks))
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("You can't have any hair!"));
return;
@@ -83,8 +83,8 @@ namespace Content.Server.GameObjects.Components
_userInterface.Open(actor.playerSession);
- var msg = new MagicMirrorInitialDataMessage(hair.HairColor, hair.FacialHairColor, hair.HairStyleName,
- hair.FacialHairStyleName);
+ var msg = new MagicMirrorInitialDataMessage(looks.Appearance.HairColor, looks.Appearance.FacialHairColor, looks.Appearance.HairStyleName,
+ looks.Appearance.FacialHairStyleName);
_userInterface.SendMessage(msg, actor.playerSession);
}
diff --git a/Content.Server/GameObjects/Components/Mobs/HairComponent.cs b/Content.Server/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs
similarity index 66%
rename from Content.Server/GameObjects/Components/Mobs/HairComponent.cs
rename to Content.Server/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs
index fc1ec345e1..0eb9196a32 100644
--- a/Content.Server/GameObjects/Components/Mobs/HairComponent.cs
+++ b/Content.Server/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs
@@ -4,7 +4,7 @@ using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Mobs
{
[RegisterComponent]
- public sealed class HairComponent : SharedHairComponent
+ public sealed class HumanoidAppearanceComponent : SharedHumanoidAppearanceComponent
{
}
diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs
index 033b884bd0..a4749c4da6 100644
--- a/Content.Server/GameTicking/GameTicker.cs
+++ b/Content.Server/GameTicking/GameTicker.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects;
using Content.Server.GameObjects.Components.Markers;
+using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameTicking.GamePresets;
using Content.Server.Interfaces;
using Content.Server.Interfaces.Chat;
@@ -13,6 +14,7 @@ using Content.Server.Players;
using Content.Shared;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.Jobs;
+using Content.Shared.Preferences;
using Robust.Server.Interfaces.Maps;
using Robust.Server.Interfaces.Player;
using Robust.Server.Player;
@@ -41,16 +43,37 @@ namespace Content.Server.GameTicking
{
public class GameTicker : SharedGameTicker, IGameTicker
{
+ private const string PlayerPrototypeName = "HumanMob_Content";
+ private const string ObserverPrototypeName = "MobObserver";
+ private const string MapFile = "Maps/stationstation.yml";
+
+ // Seconds.
+ private const float LobbyDuration = 20;
+
+ [ViewVariables] private readonly List _gameRules = new List();
+
+ // Value is whether they're ready.
+ [ViewVariables]
+ private readonly Dictionary _playersInLobby = new Dictionary();
+
+ [ViewVariables] private bool _initialized;
+
+ [ViewVariables] private Type _presetType;
+
+ [ViewVariables] private bool _roundStartCountdownHasNotStartedYetDueToNoPlayers;
+ private DateTime _roundStartTimeUtc;
+ [ViewVariables] private GameRunLevel _runLevel;
+ [ViewVariables(VVAccess.ReadWrite)] private GridCoordinates _spawnPoint;
+
+ [ViewVariables] private bool LobbyEnabled => _configurationManager.GetCVar("game.lobbyenabled");
+
[ViewVariables]
public GameRunLevel RunLevel
{
get => _runLevel;
private set
{
- if (_runLevel == value)
- {
- return;
- }
+ if (_runLevel == value) return;
var old = _runLevel;
_runLevel = value;
@@ -61,46 +84,6 @@ namespace Content.Server.GameTicking
public event Action OnRunLevelChanged;
- private const string PlayerPrototypeName = "HumanMob_Content";
- private const string ObserverPrototypeName = "MobObserver";
- private const string MapFile = "Maps/stationstation.yml";
-
- // Seconds.
- private const float LobbyDuration = 20;
-
- [ViewVariables] private bool _initialized;
- [ViewVariables(VVAccess.ReadWrite)] private GridCoordinates _spawnPoint;
- [ViewVariables] private GameRunLevel _runLevel;
-
- [ViewVariables] private bool LobbyEnabled => _configurationManager.GetCVar("game.lobbyenabled");
-
- // Value is whether they're ready.
- [ViewVariables]
- private readonly Dictionary _playersInLobby = new Dictionary();
-
- [ViewVariables] private bool _roundStartCountdownHasNotStartedYetDueToNoPlayers;
- private DateTime _roundStartTimeUtc;
-
- [ViewVariables] private readonly List _gameRules = new List();
-
- [ViewVariables] private Type _presetType;
-
-#pragma warning disable 649
- [Dependency] private IEntityManager _entityManager;
- [Dependency] private IMapManager _mapManager;
- [Dependency] private IMapLoader _mapLoader;
- [Dependency] private IGameTiming _gameTiming;
- [Dependency] private IConfigurationManager _configurationManager;
- [Dependency] private IPlayerManager _playerManager;
- [Dependency] private IChatManager _chatManager;
- [Dependency] private IServerNetManager _netManager;
- [Dependency] private IDynamicTypeFactory _dynamicTypeFactory;
- [Dependency] private IPrototypeManager _prototypeManager;
- [Dependency] private readonly ILocalizationManager _localization;
- [Dependency] private readonly IRobustRandom _robustRandom;
- [Dependency] private readonly IServerPreferencesManager _prefsManager;
-#pragma warning restore 649
-
public void Initialize()
{
DebugTools.Assert(!_initialized);
@@ -122,9 +105,7 @@ namespace Content.Server.GameTicking
{
if (RunLevel != GameRunLevel.PreRoundLobby || _roundStartTimeUtc > DateTime.UtcNow ||
_roundStartCountdownHasNotStartedYetDueToNoPlayers)
- {
return;
- }
StartRound();
}
@@ -144,13 +125,9 @@ namespace Content.Server.GameTicking
else
{
if (_playerManager.PlayerCount == 0)
- {
_roundStartCountdownHasNotStartedYetDueToNoPlayers = true;
- }
else
- {
_roundStartTimeUtc = DateTime.UtcNow + TimeSpan.FromSeconds(LobbyDuration);
- }
_sendStatusToAll();
}
@@ -168,10 +145,7 @@ namespace Content.Server.GameTicking
foreach (var (playerSession, ready) in _playersInLobby.ToList())
{
- if (LobbyEnabled && !ready)
- {
- continue;
- }
+ if (LobbyEnabled && !ready) continue;
_spawnPlayer(playerSession);
}
@@ -192,41 +166,28 @@ namespace Content.Server.GameTicking
targetPlayer.ContentData().WipeMind();
if (LobbyEnabled)
- {
_playerJoinLobby(targetPlayer);
- }
else
- {
_spawnPlayer(targetPlayer);
- }
}
public void MakeObserve(IPlayerSession player)
{
- if (!_playersInLobby.ContainsKey(player))
- {
- return;
- }
+ if (!_playersInLobby.ContainsKey(player)) return;
_spawnObserver(player);
}
public void MakeJoinGame(IPlayerSession player)
{
- if (!_playersInLobby.ContainsKey(player))
- {
- return;
- }
+ if (!_playersInLobby.ContainsKey(player)) return;
_spawnPlayer(player);
}
public void ToggleReady(IPlayerSession player, bool ready)
{
- if (!_playersInLobby.ContainsKey(player))
- {
- return;
- }
+ if (!_playersInLobby.ContainsKey(player)) return;
_playersInLobby[player] = ready;
_netManager.ServerSendMessage(_getStatusMsg(player), player.ConnectedClient);
@@ -244,10 +205,7 @@ namespace Content.Server.GameTicking
public void RemoveGameRule(GameRule rule)
{
- if (_gameRules.Contains(rule))
- {
- return;
- }
+ if (_gameRules.Contains(rule)) return;
rule.Removed();
@@ -258,10 +216,8 @@ namespace Content.Server.GameTicking
public void SetStartPreset(Type type)
{
- if (!typeof(GamePreset).IsAssignableFrom(type))
- {
- throw new ArgumentException("type must inherit GamePreset");
- }
+ if (!typeof(GamePreset).IsAssignableFrom(type)) throw new ArgumentException("type must inherit GamePreset");
+
_presetType = type;
UpdateInfoText();
}
@@ -280,6 +236,7 @@ namespace Content.Server.GameTicking
Logger.Error("{0} is an invalid equipment slot.", slotStr);
continue;
}
+
var equipmentEntity = _entityManager.SpawnEntity(equipmentStr, entity.Transform.GridPosition);
inventory.Equip(slot, equipmentEntity.GetComponent());
}
@@ -288,6 +245,14 @@ namespace Content.Server.GameTicking
return entity;
}
+ private void ApplyCharacterProfile(IEntity entity, ICharacterProfile profile)
+ {
+ if (profile is null)
+ return;
+ entity.GetComponent().UpdateFromProfile(profile);
+ entity.Name = profile.Name;
+ }
+
private IEntity _spawnObserverMob()
{
return _entityManager.SpawnEntityAt(ObserverPrototypeName, _getLateJoinSpawnPoint());
@@ -301,16 +266,10 @@ namespace Content.Server.GameTicking
foreach (var entity in _entityManager.GetEntities(new TypeEntityQuery(typeof(SpawnPointComponent))))
{
var point = entity.GetComponent();
- if (point.SpawnType == SpawnPointType.LateJoin)
- {
- possiblePoints.Add(entity.Transform.GridPosition);
- }
+ if (point.SpawnType == SpawnPointType.LateJoin) possiblePoints.Add(entity.Transform.GridPosition);
}
- if (possiblePoints.Count != 0)
- {
- location = _robustRandom.Pick(possiblePoints);
- }
+ if (possiblePoints.Count != 0) location = _robustRandom.Pick(possiblePoints);
return location;
}
@@ -323,44 +282,29 @@ namespace Content.Server.GameTicking
{
// Delete all entities.
foreach (var entity in _entityManager.GetEntities().ToList())
- {
// TODO: Maybe something less naive here?
// FIXME: Actually, definitely.
entity.Delete();
- }
// Delete all maps outside of nullspace.
foreach (var mapId in _mapManager.GetAllMapIds().ToList())
- {
// TODO: Maybe something less naive here?
if (mapId != MapId.Nullspace)
- {
_mapManager.DeleteMap(mapId);
- }
- }
// Delete the minds of everybody.
// TODO: Maybe move this into a separate manager?
- foreach (var unCastData in _playerManager.GetAllPlayerData())
- {
- unCastData.ContentData().WipeMind();
- }
+ foreach (var unCastData in _playerManager.GetAllPlayerData()) unCastData.ContentData().WipeMind();
// Clear up any game rules.
- foreach (var rule in _gameRules)
- {
- rule.Removed();
- }
+ foreach (var rule in _gameRules) rule.Removed();
_gameRules.Clear();
// Move everybody currently in the server to lobby.
foreach (var player in _playerManager.GetAllPlayers())
{
- if (_playersInLobby.ContainsKey(player))
- {
- continue;
- }
+ if (_playersInLobby.ContainsKey(player)) continue;
_playerJoinLobby(player);
}
@@ -388,9 +332,7 @@ namespace Content.Server.GameTicking
{
// Always make sure the client has player data. Mind gets assigned on spawn.
if (session.Data.ContentDataUncast == null)
- {
session.Data.ContentDataUncast = new PlayerData(session.SessionId);
- }
// timer time must be > tick length
Timer.Spawn(0, args.Session.JoinGame);
@@ -437,10 +379,7 @@ namespace Content.Server.GameTicking
case SessionStatus.Disconnected:
{
- if (_playersInLobby.ContainsKey(session))
- {
- _playersInLobby.Remove(session);
- }
+ if (_playersInLobby.ContainsKey(session)) _playersInLobby.Remove(session);
_chatManager.DispatchServerAnnouncement($"Player {args.Session.SessionId} left server!");
break;
@@ -460,6 +399,10 @@ namespace Content.Server.GameTicking
var mob = _spawnPlayerMob(job);
data.Mind.TransferTo(mob);
+ var character = _prefsManager
+ .GetPreferences(session.SessionId.Username)
+ .SelectedCharacter;
+ ApplyCharacterProfile(mob, character);
}
private void _spawnObserver(IPlayerSession session)
@@ -485,11 +428,9 @@ namespace Content.Server.GameTicking
private void _playerJoinGame(IPlayerSession session)
{
- _chatManager.DispatchServerMessage(session, $"Welcome to Space Station 14! If this is your first time checking out the game, be sure to check out the tutorial in the top left!");
- if (_playersInLobby.ContainsKey(session))
- {
- _playersInLobby.Remove(session);
- }
+ _chatManager.DispatchServerMessage(session,
+ "Welcome to Space Station 14! If this is your first time checking out the game, be sure to check out the tutorial in the top left!");
+ if (_playersInLobby.ContainsKey(session)) _playersInLobby.Remove(session);
_netManager.ServerSendMessage(_netManager.CreateNetMessage(), session.ConnectedClient);
}
@@ -514,9 +455,7 @@ namespace Content.Server.GameTicking
private void _sendStatusToAll()
{
foreach (var player in _playersInLobby.Keys)
- {
_netManager.ServerSendMessage(_getStatusMsg(player), player.ConnectedClient);
- }
}
private string GetInfoText()
@@ -538,6 +477,22 @@ The current game mode is [color=white]{0}[/color]", gameMode);
{
return _dynamicTypeFactory.CreateInstance(_presetType ?? typeof(PresetSandbox));
}
+
+#pragma warning disable 649
+ [Dependency] private IEntityManager _entityManager;
+ [Dependency] private IMapManager _mapManager;
+ [Dependency] private IMapLoader _mapLoader;
+ [Dependency] private IGameTiming _gameTiming;
+ [Dependency] private IConfigurationManager _configurationManager;
+ [Dependency] private IPlayerManager _playerManager;
+ [Dependency] private IChatManager _chatManager;
+ [Dependency] private IServerNetManager _netManager;
+ [Dependency] private IDynamicTypeFactory _dynamicTypeFactory;
+ [Dependency] private IPrototypeManager _prototypeManager;
+ [Dependency] private readonly ILocalizationManager _localization;
+ [Dependency] private readonly IRobustRandom _robustRandom;
+ [Dependency] private readonly IServerPreferencesManager _prefsManager;
+#pragma warning restore 649
}
public enum GameRunLevel
@@ -549,13 +504,13 @@ The current game mode is [color=white]{0}[/color]", gameMode);
public class GameRunLevelChangedEventArgs : EventArgs
{
- public GameRunLevel OldRunLevel { get; }
- public GameRunLevel NewRunLevel { get; }
-
public GameRunLevelChangedEventArgs(GameRunLevel oldRunLevel, GameRunLevel newRunLevel)
{
OldRunLevel = oldRunLevel;
NewRunLevel = newRunLevel;
}
+
+ public GameRunLevel OldRunLevel { get; }
+ public GameRunLevel NewRunLevel { get; }
}
}
diff --git a/Content.Server/Preferences/PreferencesDatabase.cs b/Content.Server/Preferences/PreferencesDatabase.cs
index e806fa27f9..06452e27b2 100644
--- a/Content.Server/Preferences/PreferencesDatabase.cs
+++ b/Content.Server/Preferences/PreferencesDatabase.cs
@@ -29,27 +29,28 @@ namespace Content.Server.Preferences
var profiles = new ICharacterProfile[_maxCharacterSlots];
foreach (var profile in prefs.HumanoidProfiles)
- profiles[profile.Slot] = new HumanoidCharacterProfile
- {
- Name = profile.CharacterName,
- Age = profile.Age,
- Sex = profile.Sex == "Male" ? Male : Female,
- CharacterAppearance = new HumanoidCharacterAppearance
- {
- HairStyleName = profile.HairName,
- HairColor = Color.FromHex(profile.HairColor),
- FacialHairStyleName = profile.FacialHairName,
- FacialHairColor = Color.FromHex(profile.FacialHairColor),
- EyeColor = Color.FromHex(profile.EyeColor),
- SkinColor = Color.FromHex(profile.SkinColor)
- }
- };
+ {
+ profiles[profile.Slot] = new HumanoidCharacterProfile(
+ profile.CharacterName,
+ profile.Age,
+ profile.Sex == "Male" ? Male : Female,
+ new HumanoidCharacterAppearance
+ (
+ profile.HairName,
+ Color.FromHex(profile.HairColor),
+ profile.FacialHairName,
+ Color.FromHex(profile.FacialHairColor),
+ Color.FromHex(profile.EyeColor),
+ Color.FromHex(profile.SkinColor)
+ )
+ );
+ }
return new PlayerPreferences
- {
- SelectedCharacterIndex = prefs.SelectedCharacterSlot,
- Characters = profiles.ToList()
- };
+ (
+ profiles,
+ prefs.SelectedCharacterSlot
+ );
}
public void SaveSelectedCharacterIndex(string username, int index)
@@ -71,10 +72,10 @@ namespace Content.Server.Preferences
if (!(profile is HumanoidCharacterProfile humanoid))
// TODO: Handle other ICharacterProfile implementations properly
throw new NotImplementedException();
-
var appearance = (HumanoidCharacterAppearance) humanoid.CharacterAppearance;
_prefsDb.SaveCharacterSlot(username, new HumanoidProfile
{
+ SlotName = humanoid.Name,
CharacterName = humanoid.Name,
Age = humanoid.Age,
Sex = humanoid.Sex.ToString(),
diff --git a/Content.Shared/GameObjects/Components/Mobs/SharedHairComponent.cs b/Content.Shared/GameObjects/Components/Mobs/SharedHairComponent.cs
index 2769ddb41f..e69de29bb2 100644
--- a/Content.Shared/GameObjects/Components/Mobs/SharedHairComponent.cs
+++ b/Content.Shared/GameObjects/Components/Mobs/SharedHairComponent.cs
@@ -1,109 +0,0 @@
-using System;
-using Content.Shared.Preferences.Appearance;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Maths;
-using Robust.Shared.Serialization;
-using Robust.Shared.ViewVariables;
-
-namespace Content.Shared.GameObjects.Components.Mobs
-{
- public abstract class SharedHairComponent : Component
- {
- private static readonly Color DefaultHairColor = Color.FromHex("#232323");
-
- private string _facialHairStyleName = HairStyles.DefaultFacialHairStyle;
- private string _hairStyleName = HairStyles.DefaultHairStyle;
- private Color _hairColor = DefaultHairColor;
- private Color _facialHairColor = DefaultHairColor;
-
- public sealed override string Name => "Hair";
- public sealed override uint? NetID => ContentNetIDs.HAIR;
- public sealed override Type StateType => typeof(HairComponentState);
-
- [ViewVariables(VVAccess.ReadWrite)]
- public virtual string HairStyleName
- {
- get => _hairStyleName;
- set
- {
- _hairStyleName = value;
- Dirty();
- }
- }
-
- [ViewVariables(VVAccess.ReadWrite)]
- public virtual string FacialHairStyleName
- {
- get => _facialHairStyleName;
- set
- {
- _facialHairStyleName = value;
- Dirty();
- }
- }
-
- [ViewVariables(VVAccess.ReadWrite)]
- public virtual Color HairColor
- {
- get => _hairColor;
- set
- {
- _hairColor = value;
- Dirty();
- }
- }
-
- [ViewVariables(VVAccess.ReadWrite)]
- public virtual Color FacialHairColor
- {
- get => _facialHairColor;
- set
- {
- _facialHairColor = value;
- Dirty();
- }
- }
-
- public override ComponentState GetComponentState()
- {
- return new HairComponentState(HairStyleName, FacialHairStyleName, HairColor, FacialHairColor);
- }
-
- public override void HandleComponentState(ComponentState curState, ComponentState nextState)
- {
- var cast = (HairComponentState) curState;
-
- HairStyleName = cast.HairStyleName;
- FacialHairStyleName = cast.FacialHairStyleName;
- HairColor = cast.HairColor;
- FacialHairColor = cast.FacialHairColor;
- }
-
- public override void ExposeData(ObjectSerializer serializer)
- {
- base.ExposeData(serializer);
-
- serializer.DataField(ref _hairColor, "hairColor", DefaultHairColor);
- serializer.DataField(ref _facialHairColor, "facialHairColor", DefaultHairColor);
- serializer.DataField(ref _hairStyleName, "hairStyle", HairStyles.DefaultHairStyle);
- serializer.DataField(ref _facialHairStyleName, "facialHairStyle", HairStyles.DefaultFacialHairStyle);
- }
-
- [Serializable, NetSerializable]
- private sealed class HairComponentState : ComponentState
- {
- public string HairStyleName { get; }
- public string FacialHairStyleName { get; }
- public Color HairColor { get; }
- public Color FacialHairColor { get; }
-
- public HairComponentState(string hairStyleName, string facialHairStyleName, Color hairColor, Color facialHairColor) : base(ContentNetIDs.HAIR)
- {
- HairStyleName = hairStyleName;
- FacialHairStyleName = facialHairStyleName;
- HairColor = hairColor;
- FacialHairColor = facialHairColor;
- }
- }
- }
-}
diff --git a/Content.Shared/GameObjects/Components/Mobs/SharedHumanoidAppearanceComponent.cs b/Content.Shared/GameObjects/Components/Mobs/SharedHumanoidAppearanceComponent.cs
new file mode 100644
index 0000000000..cc3871790e
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/Mobs/SharedHumanoidAppearanceComponent.cs
@@ -0,0 +1,73 @@
+using System;
+using Content.Shared.Preferences;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization;
+using Robust.Shared.ViewVariables;
+
+namespace Content.Shared.GameObjects.Components.Mobs
+{
+ public abstract class SharedHumanoidAppearanceComponent : Component
+ {
+ private HumanoidCharacterAppearance _appearance;
+ private Sex _sex;
+
+ public sealed override string Name => "HumanoidAppearance";
+ public sealed override uint? NetID => ContentNetIDs.HUMANOID_APPEARANCE;
+ public sealed override Type StateType => typeof(HumanoidAppearanceComponentState);
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ public virtual HumanoidCharacterAppearance Appearance
+ {
+ get => _appearance;
+ set
+ {
+ _appearance = value;
+ Dirty();
+ }
+ }
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ public virtual Sex Sex
+ {
+ get => _sex;
+ set
+ {
+ _sex = value;
+ Dirty();
+ }
+ }
+
+ public override ComponentState GetComponentState()
+ {
+ return new HumanoidAppearanceComponentState(Appearance, Sex);
+ }
+
+ public override void HandleComponentState(ComponentState curState, ComponentState nextState)
+ {
+ var cast = (HumanoidAppearanceComponentState) curState;
+ Appearance = cast.Appearance;
+ Sex = cast.Sex;
+ }
+
+ public void UpdateFromProfile(ICharacterProfile profile)
+ {
+ var humanoid = (HumanoidCharacterProfile) profile;
+ Appearance = (HumanoidCharacterAppearance) humanoid.CharacterAppearance;
+ Sex = humanoid.Sex;
+ }
+
+ [Serializable]
+ [NetSerializable]
+ private sealed class HumanoidAppearanceComponentState : ComponentState
+ {
+ public HumanoidAppearanceComponentState(HumanoidCharacterAppearance appearance, Sex sex) : base(ContentNetIDs.HUMANOID_APPEARANCE)
+ {
+ Appearance = appearance;
+ Sex = sex;
+ }
+
+ public HumanoidCharacterAppearance Appearance { get; }
+ public Sex Sex { get; }
+ }
+ }
+}
diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs
index 599c7a3125..c6482cdf62 100644
--- a/Content.Shared/GameObjects/ContentNetIDs.cs
+++ b/Content.Shared/GameObjects/ContentNetIDs.cs
@@ -34,7 +34,7 @@
public const uint ITEMCOOLDOWN = 1029;
public const uint CARGO_ORDER_DATABASE = 1030;
public const uint GALACTIC_MARKET = 1031;
- public const uint HAIR = 1032;
+ public const uint HUMANOID_APPEARANCE = 1032;
public const uint INSTRUMENTS = 1033;
public const uint WELDER = 1034;
public const uint STACK = 1035;
diff --git a/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs b/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs
index f7defbecc2..31ede8b7c1 100644
--- a/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs
+++ b/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs
@@ -1,11 +1,9 @@
-using System;
-using Robust.Shared.Serialization;
-
namespace Content.Shared.Preferences.Appearance
{
public enum HumanoidVisualLayers
{
Hair,
FacialHair,
+ Body
}
}
diff --git a/Content.Shared/Preferences/HumanoidCharacterAppearance.cs b/Content.Shared/Preferences/HumanoidCharacterAppearance.cs
index cf7a7b69df..a2139a91c5 100644
--- a/Content.Shared/Preferences/HumanoidCharacterAppearance.cs
+++ b/Content.Shared/Preferences/HumanoidCharacterAppearance.cs
@@ -7,24 +7,69 @@ namespace Content.Shared.Preferences
[Serializable, NetSerializable]
public class HumanoidCharacterAppearance : ICharacterAppearance
{
- public string HairStyleName { get; set; }
- public Color HairColor { get; set; }
- public string FacialHairStyleName { get; set; }
- public Color FacialHairColor { get; set; }
- public Color EyeColor { get; set; }
- public Color SkinColor { get; set; }
+ public HumanoidCharacterAppearance(string hairStyleName,
+ Color hairColor,
+ string facialHairStyleName,
+ Color facialHairColor,
+ Color eyeColor,
+ Color skinColor)
+ {
+ HairStyleName = hairStyleName;
+ HairColor = hairColor;
+ FacialHairStyleName = facialHairStyleName;
+ FacialHairColor = facialHairColor;
+ EyeColor = eyeColor;
+ SkinColor = skinColor;
+ }
+
+ public string HairStyleName { get; }
+ public Color HairColor { get; }
+ public string FacialHairStyleName { get; }
+ public Color FacialHairColor { get; }
+ public Color EyeColor { get; }
+ public Color SkinColor { get; }
+
+ public HumanoidCharacterAppearance WithHairStyleName(string newName)
+ {
+ return new HumanoidCharacterAppearance(newName, HairColor, FacialHairStyleName, FacialHairColor, EyeColor, SkinColor);
+ }
+
+ public HumanoidCharacterAppearance WithHairColor(Color newColor)
+ {
+ return new HumanoidCharacterAppearance(HairStyleName, newColor, FacialHairStyleName, FacialHairColor, EyeColor, SkinColor);
+ }
+
+ public HumanoidCharacterAppearance WithFacialHairStyleName(string newName)
+ {
+ return new HumanoidCharacterAppearance(HairStyleName, HairColor, newName, FacialHairColor, EyeColor, SkinColor);
+ }
+
+ public HumanoidCharacterAppearance WithFacialHairColor(Color newColor)
+ {
+ return new HumanoidCharacterAppearance(HairStyleName, HairColor, FacialHairStyleName, newColor, EyeColor, SkinColor);
+ }
+
+ public HumanoidCharacterAppearance WithEyeColor(Color newColor)
+ {
+ return new HumanoidCharacterAppearance(HairStyleName, HairColor, FacialHairStyleName, FacialHairColor, newColor, SkinColor);
+ }
+
+ public HumanoidCharacterAppearance WithSkinColor(Color newColor)
+ {
+ return new HumanoidCharacterAppearance(HairStyleName, HairColor, FacialHairStyleName, FacialHairColor, EyeColor, newColor);
+ }
public static HumanoidCharacterAppearance Default()
{
return new HumanoidCharacterAppearance
- {
- HairStyleName = "Bald",
- HairColor = Color.Black,
- FacialHairStyleName = "Shaved",
- FacialHairColor = Color.Black,
- EyeColor = Color.Black,
- SkinColor = Color.Black
- };
+ (
+ "Bald",
+ Color.Black,
+ "Shaved",
+ Color.Black,
+ Color.Black,
+ Color.Black
+ );
}
public bool MemberwiseEquals(ICharacterAppearance maybeOther)
diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs
index 8dd87dfe91..dc97c2c914 100644
--- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs
+++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs
@@ -6,21 +6,49 @@ namespace Content.Shared.Preferences
[Serializable, NetSerializable]
public class HumanoidCharacterProfile : ICharacterProfile
{
- public static HumanoidCharacterProfile Default()
+ public HumanoidCharacterProfile(string name,
+ int age,
+ Sex sex,
+ HumanoidCharacterAppearance appearance)
{
- return new HumanoidCharacterProfile
- {
- Name = "John Doe",
- Age = 18,
- Sex = Sex.Male,
- CharacterAppearance = HumanoidCharacterAppearance.Default()
- };
+ Name = name;
+ Age = age;
+ Sex = sex;
+ Appearance = appearance;
}
- public string Name { get; set; }
- public int Age { get; set; }
- public Sex Sex { get; set; }
- public ICharacterAppearance CharacterAppearance { get; set; }
+ public static HumanoidCharacterProfile Default()
+ {
+ return new HumanoidCharacterProfile("John Doe", 18, Sex.Male, HumanoidCharacterAppearance.Default());
+ }
+
+ public string Name { get; }
+ public int Age { get; }
+ public Sex Sex { get; }
+ public ICharacterAppearance CharacterAppearance => Appearance;
+ public HumanoidCharacterAppearance Appearance { get; }
+
+ public HumanoidCharacterProfile WithName(string name)
+ {
+ return new HumanoidCharacterProfile(name, Age, Sex, Appearance);
+ }
+
+ public HumanoidCharacterProfile WithAge(int age)
+ {
+ return new HumanoidCharacterProfile(Name, age, Sex, Appearance);
+ }
+
+ public HumanoidCharacterProfile WithSex(Sex sex)
+ {
+ return new HumanoidCharacterProfile(Name, Age, sex, Appearance);
+ }
+
+ public HumanoidCharacterProfile WithCharacterAppearance(HumanoidCharacterAppearance appearance)
+ {
+ return new HumanoidCharacterProfile(Name, Age, Sex, appearance);
+ }
+
+ public string Summary => $"{Name}, {Age} years old {Sex.ToString().ToLower()} human.\nOccupation: to be implemented.";
public bool MemberwiseEquals(ICharacterProfile maybeOther)
{
@@ -28,9 +56,7 @@ namespace Content.Shared.Preferences
if (Name != other.Name) return false;
if (Age != other.Age) return false;
if (Sex != other.Sex) return false;
- if (CharacterAppearance is null)
- return other.CharacterAppearance is null;
- return CharacterAppearance.MemberwiseEquals(other.CharacterAppearance);
+ return Appearance.MemberwiseEquals(other.Appearance);
}
}
}
diff --git a/Content.Shared/Preferences/ICharacterProfile.cs b/Content.Shared/Preferences/ICharacterProfile.cs
index abd73ec7a0..ab0b7c49b6 100644
--- a/Content.Shared/Preferences/ICharacterProfile.cs
+++ b/Content.Shared/Preferences/ICharacterProfile.cs
@@ -2,6 +2,8 @@ namespace Content.Shared.Preferences
{
public interface ICharacterProfile
{
+ string Name { get; }
+ ICharacterAppearance CharacterAppearance { get; }
bool MemberwiseEquals(ICharacterProfile other);
}
}
diff --git a/Content.Shared/Preferences/PlayerPreferences.cs b/Content.Shared/Preferences/PlayerPreferences.cs
index 6472da8c6d..25a0adb811 100644
--- a/Content.Shared/Preferences/PlayerPreferences.cs
+++ b/Content.Shared/Preferences/PlayerPreferences.cs
@@ -1,54 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization;
namespace Content.Shared.Preferences
{
///
- /// Contains all player characters and the index of the currently selected character.
- /// Serialized both over the network and to disk.
+ /// Contains all player characters and the index of the currently selected character.
+ /// Serialized both over the network and to disk.
///
- [Serializable, NetSerializable]
- public class PlayerPreferences
+ [Serializable]
+ [NetSerializable]
+ public sealed class PlayerPreferences
{
+ private List _characters;
+
+ public PlayerPreferences(IEnumerable characters, int selectedCharacterIndex)
+ {
+ _characters = characters.ToList();
+ SelectedCharacterIndex = selectedCharacterIndex;
+ }
+
+ ///
+ /// All player characters.
+ ///
+ public IEnumerable Characters => _characters.AsEnumerable();
+
+ ///
+ /// Index of the currently selected character.
+ ///
+ public int SelectedCharacterIndex { get; }
+
+ ///
+ /// The currently selected character.
+ ///
+ public ICharacterProfile SelectedCharacter => Characters.ElementAtOrDefault(SelectedCharacterIndex);
+
+ public int FirstEmptySlot => IndexOfCharacter(null);
+
public static PlayerPreferences Default()
{
- return new PlayerPreferences
- {
- Characters = new List
+ return new PlayerPreferences(new List
{
HumanoidCharacterProfile.Default()
},
- SelectedCharacterIndex = 0
- };
+ 0);
}
- private List _characters;
- private int _selectedCharacterIndex;
-
- ///
- /// All player characters.
- ///
- public List Characters
+ public int IndexOfCharacter(ICharacterProfile profile)
{
- get => _characters;
- set => _characters = value;
+ return _characters.FindIndex(x => x == profile);
}
-
- ///
- /// Index of the currently selected character.
- ///
- public int SelectedCharacterIndex
- {
- get => _selectedCharacterIndex;
- set => _selectedCharacterIndex = value;
- }
-
- ///
- /// Retrieves the currently selected character.
- ///
- public ICharacterProfile SelectedCharacter => Characters.ElementAtOrDefault(SelectedCharacterIndex);
}
}
diff --git a/Content.Tests/Server/Preferences/PreferencesDatabaseTests.cs b/Content.Tests/Server/Preferences/PreferencesDatabaseTests.cs
index 66b82005bd..13bdb16c54 100644
--- a/Content.Tests/Server/Preferences/PreferencesDatabaseTests.cs
+++ b/Content.Tests/Server/Preferences/PreferencesDatabaseTests.cs
@@ -1,4 +1,5 @@
using System.IO;
+using System.Linq;
using Content.Server.Preferences;
using Content.Shared.Preferences;
using NUnit.Framework;
@@ -12,23 +13,21 @@ namespace Content.Tests.Server.Preferences
{
private const int MaxCharacterSlots = 10;
- private static ICharacterProfile CharlieCharlieson()
+ private static HumanoidCharacterProfile CharlieCharlieson()
{
- return new HumanoidCharacterProfile
- {
- Name = "Charlie Charlieson",
- Age = 21,
- Sex = Sex.Male,
- CharacterAppearance = new HumanoidCharacterAppearance()
- {
- HairStyleName = "Afro",
- HairColor = Color.Aqua,
- FacialHairStyleName = "Shaved",
- FacialHairColor = Color.Aquamarine,
- EyeColor = Color.Azure,
- SkinColor = Color.Beige
- }
- };
+ return new HumanoidCharacterProfile(
+ "Charlie Charlieson",
+ 21,
+ Sex.Male,
+ new HumanoidCharacterAppearance(
+ "Afro",
+ Color.Aqua,
+ "Shaved",
+ Color.Aquamarine,
+ Color.Azure,
+ Color.Beige
+ )
+ );
}
private static PreferencesDatabase GetDb()
@@ -52,7 +51,7 @@ namespace Content.Tests.Server.Preferences
var prefs = db.GetPlayerPreferences(username);
Assert.NotNull(prefs);
Assert.Zero(prefs.SelectedCharacterIndex);
- Assert.That(prefs.Characters.TrueForAll(character => character is null));
+ Assert.That(prefs.Characters.ToList().TrueForAll(character => character is null));
}
[Test]
@@ -65,7 +64,7 @@ namespace Content.Tests.Server.Preferences
db.SaveSelectedCharacterIndex(username, slot);
db.SaveCharacterSlot(username, originalProfile, slot);
var prefs = db.GetPlayerPreferences(username);
- Assert.That(prefs.Characters[slot].MemberwiseEquals(originalProfile));
+ Assert.That(prefs.Characters.ElementAt(slot).MemberwiseEquals(originalProfile));
}
[Test]
@@ -78,7 +77,7 @@ namespace Content.Tests.Server.Preferences
db.SaveCharacterSlot(username, CharlieCharlieson(), slot);
db.SaveCharacterSlot(username, null, slot);
var prefs = db.GetPlayerPreferences(username);
- Assert.That(prefs.Characters.TrueForAll(character => character is null));
+ Assert.That(prefs.Characters.ToList().TrueForAll(character => character is null));
}
[Test]
diff --git a/Resources/Prototypes/Entities/mobs/human.yml b/Resources/Prototypes/Entities/mobs/human.yml
index 70b8dbbcd7..78a55e8c66 100644
--- a/Resources/Prototypes/Entities/mobs/human.yml
+++ b/Resources/Prototypes/Entities/mobs/human.yml
@@ -22,7 +22,8 @@
drawdepth: Mobs
layers:
- - sprite: Mob/human.rsi
+ - map: ["enum.HumanoidVisualLayers.Body"]
+ sprite: Mob/human.rsi
state: male
- map: ["enum.Slots.INNERCLOTHING"]
- map: ["enum.Slots.IDCARD"]
@@ -82,4 +83,71 @@
- type: Examiner
- type: CharacterInfo
- type: FootstepSound
- - type: Hair
+ - type: HumanoidAppearance
+
+- type: entity
+ save: false
+ name: Urist McHands
+ id: HumanMob_Dummy
+ description: A dummy human meant to be used in character setup
+ components:
+ - type: Hands
+ hands:
+ - left
+ - right
+ # Organs
+ - type: Stomach
+
+ - type: Inventory
+ - type: Sprite
+ netsync: false
+ drawdepth: Mobs
+
+ layers:
+ - map: ["enum.HumanoidVisualLayers.Body"]
+ sprite: Mob/human.rsi
+ state: male
+ - map: ["enum.Slots.INNERCLOTHING"]
+ - map: ["enum.Slots.IDCARD"]
+ - map: ["enum.Slots.GLOVES"]
+ - map: ["enum.Slots.SHOES"]
+ - map: ["enum.Slots.EARS"]
+ - map: ["enum.Slots.OUTERCLOTHING"]
+ - map: ["enum.Slots.EYES"]
+ - map: ["enum.Slots.BELT"]
+ - map: ["enum.Slots.BACKPACK"]
+ - map: ["enum.HumanoidVisualLayers.FacialHair"]
+ state: shaved
+ sprite: Mob/human_facial_hair.rsi
+ - map: ["enum.HumanoidVisualLayers.Hair"]
+ state: bald
+ sprite: Mob/human_hair.rsi
+ - map: ["enum.Slots.MASK"]
+ - map: ["enum.Slots.HEAD"]
+ - map: ["hand-left"]
+ - map: ["hand-right"]
+
+ - type: Icon
+ sprite: Mob/human.rsi
+ state: male
+
+ - type: Physics
+ mass: 85
+
+ - type: Collidable
+ shapes:
+ - !type:PhysShapeAabb
+ bounds: "-0.35,-0.35,0.35,0.35"
+ mask: 30
+ layer: 4
+
+ - type: Species
+ Template: Human
+ HeatResistance: 323
+ - type: Damageable
+
+ - type: Appearance
+ visuals:
+ - type: SpeciesVisualizer2D
+
+ - type: HumanoidAppearance
diff --git a/Resources/Textures/Mob/human.rsi/fat_husk.png b/Resources/Textures/Mob/human.rsi/fat_husk.png
new file mode 100644
index 0000000000..24eec54b40
Binary files /dev/null and b/Resources/Textures/Mob/human.rsi/fat_husk.png differ
diff --git a/Resources/Textures/Mob/human.rsi/female.png b/Resources/Textures/Mob/human.rsi/female.png
new file mode 100644
index 0000000000..d6465cccf7
Binary files /dev/null and b/Resources/Textures/Mob/human.rsi/female.png differ
diff --git a/Resources/Textures/Mob/human.rsi/female_fat.png b/Resources/Textures/Mob/human.rsi/female_fat.png
new file mode 100644
index 0000000000..19b3235c02
Binary files /dev/null and b/Resources/Textures/Mob/human.rsi/female_fat.png differ
diff --git a/Resources/Textures/Mob/human.rsi/female_slim.png b/Resources/Textures/Mob/human.rsi/female_slim.png
new file mode 100644
index 0000000000..14450f6e23
Binary files /dev/null and b/Resources/Textures/Mob/human.rsi/female_slim.png differ
diff --git a/Resources/Textures/Mob/human.rsi/husk.png b/Resources/Textures/Mob/human.rsi/husk.png
new file mode 100644
index 0000000000..02249f2eac
Binary files /dev/null and b/Resources/Textures/Mob/human.rsi/husk.png differ
diff --git a/Resources/Textures/Mob/human.rsi/male.png b/Resources/Textures/Mob/human.rsi/male.png
index b501089e4a..5d021234c8 100644
Binary files a/Resources/Textures/Mob/human.rsi/male.png and b/Resources/Textures/Mob/human.rsi/male.png differ
diff --git a/Resources/Textures/Mob/human.rsi/male_fat.png b/Resources/Textures/Mob/human.rsi/male_fat.png
new file mode 100644
index 0000000000..507369263f
Binary files /dev/null and b/Resources/Textures/Mob/human.rsi/male_fat.png differ
diff --git a/Resources/Textures/Mob/human.rsi/male_slim.png b/Resources/Textures/Mob/human.rsi/male_slim.png
new file mode 100644
index 0000000000..db0d08b483
Binary files /dev/null and b/Resources/Textures/Mob/human.rsi/male_slim.png differ
diff --git a/Resources/Textures/Mob/human.rsi/meta.json b/Resources/Textures/Mob/human.rsi/meta.json
index 604233c32b..48bf02436c 100644
--- a/Resources/Textures/Mob/human.rsi/meta.json
+++ b/Resources/Textures/Mob/human.rsi/meta.json
@@ -1,29 +1,153 @@
{
- "version": 1,
- "size": {
- "x": 32,
- "y": 32
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "fat_husk",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
},
- "license": "CC-BY-SA-3.0",
- "copyright": "Taken from https://github.com/discordia-space/CEV-Eris at commit 9a3a3a180344460263e8df7ea2565128e07b86b5",
- "states": [
- {
- "name": "male",
- "directions": 4,
- "delays": [
- [
- 1.0
- ],
- [
- 1.0
- ],
- [
- 1.0
- ],
- [
- 1.0
- ]
- ]
- }
- ]
+ {
+ "name": "female",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "female_fat",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "female_slim",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "husk",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "male",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "male_fat",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "male_slim",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ }
+ ]
}