diff --git a/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs b/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs index b95f778520..853a048a92 100644 --- a/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs +++ b/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs @@ -108,24 +108,44 @@ namespace Content.Client.GameObjects private void _setSlot(Slots slot, IEntity entity) { - if (_sprite != null && entity.TryGetComponent(out ClothingComponent clothing)) + SetSlotVisuals(slot, entity); + + InterfaceController?.AddToSlot(slot, entity); + } + + internal void SetSlotVisuals(Slots slot, IEntity entity) + { + if (_sprite == null) + { + return; + } + + if (entity != null && entity.TryGetComponent(out ClothingComponent clothing)) { var flag = SlotMasks[slot]; var data = clothing.GetEquippedStateInfo(flag); - if (data == null) - { - _sprite.LayerSetVisible(slot, false); - } - else + if (data != null) { var (rsi, state) = data.Value; _sprite.LayerSetVisible(slot, true); _sprite.LayerSetRSI(slot, rsi); _sprite.LayerSetState(slot, state); + return; } } - InterfaceController?.AddToSlot(slot, entity); + _sprite.LayerSetVisible(slot, false); + } + + internal void ClearAllSlotVisuals() + { + foreach (var slot in InventoryInstance.SlotMasks) + { + if (slot != Slots.NONE) + { + _sprite.LayerSetVisible(slot, false); + } + } } private void _clearSlot(Slots slot) diff --git a/Content.Client/UserInterface/CharacterSetupGui.cs b/Content.Client/UserInterface/CharacterSetupGui.cs index e9f92893e9..e3e210dfb2 100644 --- a/Content.Client/UserInterface/CharacterSetupGui.cs +++ b/Content.Client/UserInterface/CharacterSetupGui.cs @@ -209,6 +209,10 @@ namespace Content.Client.UserInterface _previewDummy = entityManager.SpawnEntityAt("HumanMob_Dummy", new MapCoordinates(Vector2.Zero, MapId.Nullspace)); _previewDummy.GetComponent().UpdateFromProfile(profile); + if (profile is HumanoidCharacterProfile humanoid) + { + LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy, humanoid); + } var isSelectedCharacter = profile == preferencesManager.Preferences.SelectedCharacter; @@ -239,7 +243,7 @@ namespace Content.Client.UserInterface { Text = "Delete", Visible = !isSelectedCharacter, - SizeFlagsHorizontal = SizeFlags.ShrinkEnd + SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Expand }; deleteButton.OnPressed += args => { diff --git a/Content.Client/UserInterface/LobbyCharacterPreviewPanel.cs b/Content.Client/UserInterface/LobbyCharacterPreviewPanel.cs index 4197724019..33548ad904 100644 --- a/Content.Client/UserInterface/LobbyCharacterPreviewPanel.cs +++ b/Content.Client/UserInterface/LobbyCharacterPreviewPanel.cs @@ -1,13 +1,20 @@ -using Content.Client.GameObjects.Components.Mobs; +using System.Linq; +using Content.Client.GameObjects; +using Content.Client.GameObjects.Components.Mobs; using Content.Client.Interfaces; +using Content.Shared; +using Content.Shared.GameObjects.Components.Inventory; +using Content.Shared.Jobs; 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.IoC; using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Maths; +using Robust.Shared.Prototypes; namespace Content.Client.UserInterface { @@ -94,6 +101,33 @@ namespace Content.Client.UserInterface _summaryLabel.Text = selectedCharacter.Summary; var component = _previewDummy.GetComponent(); component.UpdateFromProfile(selectedCharacter); + + GiveDummyJobClothes(_previewDummy, selectedCharacter); + } + } + + public static void GiveDummyJobClothes(IEntity dummy, HumanoidCharacterProfile profile) + { + var protoMan = IoCManager.Resolve(); + var entityMan = IoCManager.Resolve(); + + var inventory = dummy.GetComponent(); + + var highPriorityJob = profile.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key; + + var job = protoMan.Index(highPriorityJob ?? SharedGameTicker.OverflowJob); + var gear = protoMan.Index(job.StartingGear); + + inventory.ClearAllSlotVisuals(); + + foreach (var (slot, itemType) in gear.Equipment) + { + var item = entityMan + .SpawnEntityAt(itemType, new MapCoordinates(Vector2.Zero, MapId.Nullspace)); + + inventory.SetSlotVisuals(slot, item); + + item.Delete(); } } } diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index d899b73156..02e99f20bb 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -265,14 +265,8 @@ namespace Content.Server.GameTicking { var gear = _prototypeManager.Index(job.StartingGear).Equipment; - foreach (var (slotStr, equipmentStr) in gear) + foreach (var (slot, equipmentStr) in gear) { - if (!Enum.TryParse(slotStr.ToUpper(), out EquipmentSlotDefines.Slots slot)) - { - Logger.Error("{0} is an invalid equipment slot.", slotStr); - continue; - } - var equipmentEntity = _entityManager.SpawnEntity(equipmentStr, entity.Transform.GridPosition); inventory.Equip(slot, equipmentEntity.GetComponent()); } diff --git a/Content.Shared/GameObjects/Components/Inventory/EquipmentSlotDefinitions.cs b/Content.Shared/GameObjects/Components/Inventory/EquipmentSlotDefinitions.cs index 562fc88429..c17d196c33 100644 --- a/Content.Shared/GameObjects/Components/Inventory/EquipmentSlotDefinitions.cs +++ b/Content.Shared/GameObjects/Components/Inventory/EquipmentSlotDefinitions.cs @@ -6,13 +6,28 @@ namespace Content.Shared.GameObjects.Components.Inventory { public static class EquipmentSlotDefines { + public static IReadOnlyCollection AllSlots { get; } + + static EquipmentSlotDefines() + { + var output = new Slots[(int)Slots.LAST - (int)Slots.HEAD]; + + // The index stuff is to jump over NONE. + for (var i = 0; i < output.Length; i++) + { + output[i] = (Slots)(i+1); + } + + AllSlots = output; + } + /// /// Uniquely identifies a single slot in an inventory. /// [Serializable, NetSerializable] public enum Slots { - NONE, + NONE = 0, HEAD, EYES, EARS, @@ -29,7 +44,12 @@ namespace Content.Shared.GameObjects.Components.Inventory POCKET3, POCKET4, EXOSUITSLOT1, - EXOSUITSLOT2 + EXOSUITSLOT2, + + /// + /// Not a real slot. + /// + LAST } /// diff --git a/Content.Shared/Jobs/StartingGearPrototype.cs b/Content.Shared/Jobs/StartingGearPrototype.cs index e538eabec3..5fc9b9a877 100644 --- a/Content.Shared/Jobs/StartingGearPrototype.cs +++ b/Content.Shared/Jobs/StartingGearPrototype.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Linq; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; using YamlDotNet.RepresentationModel; +using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines; namespace Content.Shared.Jobs { @@ -11,20 +13,30 @@ namespace Content.Shared.Jobs public class StartingGearPrototype : IPrototype, IIndexedPrototype { private string _id; - private Dictionary _equipment; + private Dictionary _equipment; - [ViewVariables] - public string ID => _id; + [ViewVariables] public string ID => _id; - [ViewVariables] - public Dictionary Equipment => _equipment; + [ViewVariables] public IReadOnlyDictionary Equipment => _equipment; public void LoadFrom(YamlMappingNode mapping) { var serializer = YamlObjectSerializer.NewReader(mapping); serializer.DataField(ref _id, "id", string.Empty); - serializer.DataField(ref _equipment, "equipment", new Dictionary()); + + var equipment = serializer.ReadDataField>("equipment"); + + _equipment = equipment.ToDictionary(slotStr => + { + var (key, _) = slotStr; + if (!Enum.TryParse(key, true, out Slots slot)) + { + throw new Exception($"{key} is an invalid equipment slot."); + } + + return slot; + }, type => type.Value); } } } diff --git a/Content.Tests/Shared/EquipmentSlotDefinesTest.cs b/Content.Tests/Shared/EquipmentSlotDefinesTest.cs new file mode 100644 index 0000000000..59431b568e --- /dev/null +++ b/Content.Tests/Shared/EquipmentSlotDefinesTest.cs @@ -0,0 +1,58 @@ +using System; +using System.Linq; +using Content.Shared.GameObjects.Components.Inventory; +using NUnit.Framework; +using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines; + +namespace Content.Tests.Shared +{ + [TestFixture] + [Parallelizable(ParallelScope.All)] + [TestOf(typeof(EquipmentSlotDefines))] + public class EquipmentSlotDefinesTest + { + /// + /// Test that all slots are contained in + /// + [Test] + public void TestAllSlotsContainsAll() + { + foreach (var slotObj in Enum.GetValues(typeof(Slots))) + { + var slot = (Slots) slotObj; + + if (slot == Slots.NONE || slot == Slots.LAST) + { + // Not real slots, skip these. + continue; + } + + Assert.That(AllSlots.Contains(slot)); + } + } + + /// + /// Test that every slot has an entry in . + /// + [Test] + public void TestSlotNamesContainsAll() + { + foreach (var slot in AllSlots) + { + Assert.That(SlotNames, Contains.Key(slot)); + } + } + + /// + /// Test that every slot has an entry in . + /// + [Test] + public void TestSlotMasksContainsAll() + { + foreach (var slot in AllSlots) + { + Assert.That(SlotMasks, Contains.Key(slot)); + } + } + } +}