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 + ] + ] + } + ] }