From ead47c541dfc3de3a2de888d24e3c73f0e38043b Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 5 Aug 2023 14:25:47 +1000 Subject: [PATCH] Fix humanoid appearances for placement manager (#18291) --- Content.Client/Body/Systems/BodySystem.cs | 8 +- .../Humanoid/HumanoidAppearanceSystem.cs | 2 +- .../Station/StationSpawningSystem.cs | 8 + Content.Server/Body/Systems/BodySystem.cs | 16 -- .../Clothing/Components/LoadoutComponent.cs | 16 -- Content.Server/Clothing/LoadoutSystem.cs | 34 ---- ...s => HumanoidAppearanceSystem.Modifier.cs} | 0 .../Systems/HumanoidAppearanceSystem.cs | 179 +----------------- .../Station/Systems/StationSpawningSystem.cs | 40 +--- .../Body/Systems/SharedBodySystem.Body.cs | 24 ++- .../Clothing/Components/LoadoutComponent.cs | 16 ++ Content.Shared/Clothing/LoadoutSystem.cs | 35 ++++ .../SharedHumanoidAppearanceSystem.cs | 179 ++++++++++++++++++ .../Station/SharedStationSpawningSystem.cs | 46 +++++ .../Prototypes/Entities/Mobs/NPCs/animals.yml | 3 +- .../Prototypes/Entities/Mobs/Player/human.yml | 2 - 16 files changed, 313 insertions(+), 295 deletions(-) create mode 100644 Content.Client/Station/StationSpawningSystem.cs delete mode 100644 Content.Server/Clothing/Components/LoadoutComponent.cs delete mode 100644 Content.Server/Clothing/LoadoutSystem.cs rename Content.Server/Humanoid/Systems/{HumanoidSystem.Modifier.cs => HumanoidAppearanceSystem.Modifier.cs} (100%) create mode 100644 Content.Shared/Clothing/Components/LoadoutComponent.cs create mode 100644 Content.Shared/Clothing/LoadoutSystem.cs create mode 100644 Content.Shared/Station/SharedStationSpawningSystem.cs diff --git a/Content.Client/Body/Systems/BodySystem.cs b/Content.Client/Body/Systems/BodySystem.cs index 172728a955..bab785525b 100644 --- a/Content.Client/Body/Systems/BodySystem.cs +++ b/Content.Client/Body/Systems/BodySystem.cs @@ -1,13 +1,7 @@ -using Content.Shared.Body.Components; -using Content.Shared.Body.Prototypes; -using Content.Shared.Body.Systems; +using Content.Shared.Body.Systems; namespace Content.Client.Body.Systems; public sealed class BodySystem : SharedBodySystem { - protected override void InitBody(BodyComponent body, BodyPrototype prototype) - { - return; - } } diff --git a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs index 141aa34ab7..78e20594db 100644 --- a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs +++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs @@ -126,7 +126,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem /// This should not be used if the entity is owned by the server. The server will otherwise /// override this with the appearance data it sends over. /// - public void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null) + public override void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null) { if (!Resolve(uid, ref humanoid)) { diff --git a/Content.Client/Station/StationSpawningSystem.cs b/Content.Client/Station/StationSpawningSystem.cs new file mode 100644 index 0000000000..65da518d22 --- /dev/null +++ b/Content.Client/Station/StationSpawningSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Station; + +namespace Content.Client.Station; + +public sealed class StationSpawningSystem : SharedStationSpawningSystem +{ + +} diff --git a/Content.Server/Body/Systems/BodySystem.cs b/Content.Server/Body/Systems/BodySystem.cs index 43294976fe..50d265f2e1 100644 --- a/Content.Server/Body/Systems/BodySystem.cs +++ b/Content.Server/Body/Systems/BodySystem.cs @@ -181,22 +181,6 @@ public sealed class BodySystem : SharedBodySystem return true; } - protected override void InitBody(BodyComponent body, BodyPrototype prototype) - { - var root = prototype.Slots[prototype.Root]; - Containers.EnsureContainer(body.Owner, BodyContainerId); - if (root.Part == null) - return; - var bodyId = Spawn(root.Part, body.Owner.ToCoordinates()); - var partComponent = Comp(bodyId); - var slot = new BodyPartSlot(root.Part, body.Owner, partComponent.PartType); - body.Root = slot; - partComponent.Body = bodyId; - - AttachPart(bodyId, slot, partComponent); - InitPart(partComponent, prototype, prototype.Root); - } - public override HashSet GibBody(EntityUid? bodyId, bool gibOrgans = false, BodyComponent? body = null, bool deleteItems = false) { if (bodyId == null || !Resolve(bodyId.Value, ref body, false)) diff --git a/Content.Server/Clothing/Components/LoadoutComponent.cs b/Content.Server/Clothing/Components/LoadoutComponent.cs deleted file mode 100644 index 6068d0cf4d..0000000000 --- a/Content.Server/Clothing/Components/LoadoutComponent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Content.Shared.Roles; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Server.Clothing.Components -{ - [RegisterComponent] - public sealed class LoadoutComponent : Component - { - /// - /// A list of starting gears, of which one will be given. - /// All elements are weighted the same in the list. - /// - [DataField("prototypes", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List? Prototypes; - } -} diff --git a/Content.Server/Clothing/LoadoutSystem.cs b/Content.Server/Clothing/LoadoutSystem.cs deleted file mode 100644 index b2cb7d2cb0..0000000000 --- a/Content.Server/Clothing/LoadoutSystem.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Content.Server.Clothing.Components; -using Content.Server.Station.Systems; -using Content.Shared.Roles; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; - -namespace Content.Server.Clothing -{ - /// - /// Assigns a loadout to an entity based on the startingGear prototype - /// - public sealed class LoadoutSystem : EntitySystem - { - [Dependency] private readonly StationSpawningSystem _station = default!; - [Dependency] private readonly IPrototypeManager _protoMan = default!; - [Dependency] private readonly IRobustRandom _random = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnStartup); - } - - private void OnStartup(EntityUid uid, LoadoutComponent component, ComponentStartup args) - { - if (component.Prototypes == null) - return; - - var proto = _protoMan.Index(_random.Pick(component.Prototypes)); - _station.EquipStartingGear(uid, proto, null); - } - } -} diff --git a/Content.Server/Humanoid/Systems/HumanoidSystem.Modifier.cs b/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.Modifier.cs similarity index 100% rename from Content.Server/Humanoid/Systems/HumanoidSystem.Modifier.cs rename to Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.Modifier.cs diff --git a/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.cs b/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.cs index 5cf3999d8e..36ab038562 100644 --- a/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.cs +++ b/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.cs @@ -1,4 +1,3 @@ -using System.Linq; using Content.Shared.Examine; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; @@ -19,130 +18,21 @@ public sealed partial class HumanoidAppearanceSystem : SharedHumanoidAppearanceS public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnMarkingsSet); SubscribeLocalEvent(OnBaseLayersSet); SubscribeLocalEvent>(OnVerbsRequest); SubscribeLocalEvent(OnExamined); } - private void OnInit(EntityUid uid, HumanoidAppearanceComponent humanoid, ComponentInit args) - { - if (string.IsNullOrEmpty(humanoid.Species)) - { - return; - } - - if (string.IsNullOrEmpty(humanoid.Initial) - || !_prototypeManager.TryIndex(humanoid.Initial, out HumanoidProfilePrototype? startingSet)) - { - LoadProfile(uid, HumanoidCharacterProfile.DefaultWithSpecies(humanoid.Species), humanoid); - return; - } - - // Do this first, because profiles currently do not support custom base layers - foreach (var (layer, info) in startingSet.CustomBaseLayers) - { - humanoid.CustomBaseLayers.Add(layer, info); - } - - LoadProfile(uid, startingSet.Profile, humanoid); - - } - private void OnExamined(EntityUid uid, HumanoidAppearanceComponent component, ExaminedEvent args) { - var identity = Identity.Entity(component.Owner, EntityManager); + var identity = Identity.Entity(uid, EntityManager); var species = GetSpeciesRepresentation(component.Species).ToLower(); var age = GetAgeRepresentation(component.Species, component.Age); args.PushText(Loc.GetString("humanoid-appearance-component-examine", ("user", identity), ("age", age), ("species", species))); } - /// - /// Loads a humanoid character profile directly onto this humanoid mob. - /// - /// The mob's entity UID. - /// The character profile to load. - /// Humanoid component of the entity - public void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null) - { - if (!Resolve(uid, ref humanoid)) - { - return; - } - - SetSpecies(uid, profile.Species, false, humanoid); - SetSex(uid, profile.Sex, false, humanoid); - humanoid.EyeColor = profile.Appearance.EyeColor; - - SetSkinColor(uid, profile.Appearance.SkinColor, false); - - humanoid.MarkingSet.Clear(); - - // Add markings that doesn't need coloring. We store them until we add all other markings that doesn't need it. - var markingFColored = new Dictionary(); - foreach (var marking in profile.Appearance.Markings) - { - if (_markingManager.TryGetMarking(marking, out var prototype)) - { - if (!prototype.ForcedColoring) - { - AddMarking(uid, marking.MarkingId, marking.MarkingColors, false); - } - else - { - markingFColored.Add(marking, prototype); - } - } - } - - // Hair/facial hair - this may eventually be deprecated. - // We need to ensure hair before applying it or coloring can try depend on markings that can be invalid - var hairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.Hair, out var hairAlpha, _prototypeManager) - ? profile.Appearance.SkinColor.WithAlpha(hairAlpha) : profile.Appearance.HairColor; - var facialHairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.FacialHair, out var facialHairAlpha, _prototypeManager) - ? profile.Appearance.SkinColor.WithAlpha(facialHairAlpha) : profile.Appearance.FacialHairColor; - - if (_markingManager.Markings.TryGetValue(profile.Appearance.HairStyleId, out var hairPrototype) && - _markingManager.CanBeApplied(profile.Species, hairPrototype, _prototypeManager)) - { - AddMarking(uid, profile.Appearance.HairStyleId, hairColor, false); - } - - if (_markingManager.Markings.TryGetValue(profile.Appearance.FacialHairStyleId, out var facialHairPrototype) && - _markingManager.CanBeApplied(profile.Species, facialHairPrototype, _prototypeManager)) - { - AddMarking(uid, profile.Appearance.FacialHairStyleId, facialHairColor, false); - } - - humanoid.MarkingSet.EnsureSpecies(profile.Species, profile.Appearance.SkinColor, _markingManager, _prototypeManager); - - // Finally adding marking with forced colors - foreach (var (marking, prototype) in markingFColored) - { - var markingColors = MarkingColoring.GetMarkingLayerColors( - prototype, - profile.Appearance.SkinColor, - profile.Appearance.EyeColor, - humanoid.MarkingSet - ); - AddMarking(uid, marking.MarkingId, markingColors, false); - } - - EnsureDefaultMarkings(uid, humanoid); - - humanoid.Gender = profile.Gender; - if (TryComp(uid, out var grammar)) - { - grammar.Gender = profile.Gender; - } - - humanoid.Age = profile.Age; - - Dirty(humanoid); - } - // this was done enough times that it only made sense to do it here /// @@ -177,64 +67,6 @@ public sealed partial class HumanoidAppearanceSystem : SharedHumanoidAppearanceS Dirty(targetHumanoid); } - /// - /// Adds a marking to this humanoid. - /// - /// Humanoid mob's UID - /// Marking ID to use - /// Color to apply to all marking layers of this marking - /// Whether to immediately sync this marking or not - /// If this marking was forced (ignores marking points) - /// Humanoid component of the entity - public void AddMarking(EntityUid uid, string marking, Color? color = null, bool sync = true, bool forced = false, HumanoidAppearanceComponent? humanoid = null) - { - if (!Resolve(uid, ref humanoid) - || !_markingManager.Markings.TryGetValue(marking, out var prototype)) - { - return; - } - - var markingObject = prototype.AsMarking(); - markingObject.Forced = forced; - if (color != null) - { - for (var i = 0; i < prototype.Sprites.Count; i++) - { - markingObject.SetColor(i, color.Value); - } - } - - humanoid.MarkingSet.AddBack(prototype.MarkingCategory, markingObject); - - if (sync) - Dirty(humanoid); - } - - /// - /// - /// - /// Humanoid mob's UID - /// Marking ID to use - /// Colors to apply against this marking's set of sprites. - /// Whether to immediately sync this marking or not - /// If this marking was forced (ignores marking points) - /// Humanoid component of the entity - public void AddMarking(EntityUid uid, string marking, IReadOnlyList colors, bool sync = true, bool forced = false, HumanoidAppearanceComponent? humanoid = null) - { - if (!Resolve(uid, ref humanoid) - || !_markingManager.Markings.TryGetValue(marking, out var prototype)) - { - return; - } - - var markingObject = new Marking(marking, colors); - markingObject.Forced = forced; - humanoid.MarkingSet.AddBack(prototype.MarkingCategory, markingObject); - - if (sync) - Dirty(humanoid); - } - /// /// Removes a marking from a humanoid by ID. /// @@ -370,13 +202,4 @@ public sealed partial class HumanoidAppearanceSystem : SharedHumanoidAppearanceS return Loc.GetString("identity-age-old"); } - - private void EnsureDefaultMarkings(EntityUid uid, HumanoidAppearanceComponent? humanoid) - { - if (!Resolve(uid, ref humanoid)) - { - return; - } - humanoid.MarkingSet.EnsureDefault(humanoid.SkinColor, humanoid.EyeColor, _markingManager); - } } diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index 7908ffb6b1..8885b5b786 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -20,6 +20,7 @@ using Content.Shared.Random; using Content.Shared.Random.Helpers; using Content.Shared.Roles; using Content.Shared.StatusIcon; +using Content.Shared.Station; using JetBrains.Annotations; using Robust.Shared.Configuration; using Robust.Shared.Map; @@ -34,15 +35,13 @@ namespace Content.Server.Station.Systems; /// Also provides helpers for spawning in the player's mob. /// [PublicAPI] -public sealed class StationSpawningSystem : EntitySystem +public sealed class StationSpawningSystem : SharedStationSpawningSystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!; - [Dependency] private readonly HandsSystem _handsSystem = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; [Dependency] private readonly IdCardSystem _cardSystem = default!; - [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly PdaSystem _pdaSystem = default!; [Dependency] private readonly SharedAccessSystem _accessSystem = default!; [Dependency] private readonly IdentitySystem _identity = default!; @@ -169,39 +168,6 @@ public sealed class StationSpawningSystem : EntitySystem } } - /// - /// Equips starting gear onto the given entity. - /// - /// Entity to load out. - /// Starting gear to use. - /// Character profile to use, if any. - public void EquipStartingGear(EntityUid entity, StartingGearPrototype startingGear, HumanoidCharacterProfile? profile) - { - if (_inventorySystem.TryGetSlots(entity, out var slotDefinitions)) - { - foreach (var slot in slotDefinitions) - { - var equipmentStr = startingGear.GetGear(slot.Name, profile); - if (!string.IsNullOrEmpty(equipmentStr)) - { - var equipmentEntity = EntityManager.SpawnEntity(equipmentStr, EntityManager.GetComponent(entity).Coordinates); - _inventorySystem.TryEquip(entity, equipmentEntity, slot.Name, true); - } - } - } - - if (!TryComp(entity, out HandsComponent? handsComponent)) - return; - - var inhand = startingGear.Inhand; - var coords = EntityManager.GetComponent(entity).Coordinates; - foreach (var (hand, prototype) in inhand) - { - var inhandEntity = EntityManager.SpawnEntity(prototype, coords); - _handsSystem.TryPickup(entity, inhandEntity, hand, checkActionBlocker: false, handsComp: handsComponent); - } - } - /// /// Equips an ID card and PDA onto the given entity. /// @@ -211,7 +177,7 @@ public sealed class StationSpawningSystem : EntitySystem /// The station this player is being spawned on. public void EquipIdCard(EntityUid entity, string characterName, JobPrototype jobPrototype, EntityUid? station) { - if (!_inventorySystem.TryGetSlotEntity(entity, "id", out var idUid)) + if (!InventorySystem.TryGetSlotEntity(entity, "id", out var idUid)) return; if (!EntityManager.TryGetComponent(idUid, out PdaComponent? pdaComponent) || !TryComp(pdaComponent.ContainedId, out var card)) diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Body.cs b/Content.Shared/Body/Systems/SharedBodySystem.Body.cs index 45cb9bfa6d..151f287cdb 100644 --- a/Content.Shared/Body/Systems/SharedBodySystem.Body.cs +++ b/Content.Shared/Body/Systems/SharedBodySystem.Body.cs @@ -8,11 +8,14 @@ using Content.Shared.Coordinates; using Content.Shared.DragDrop; using Robust.Shared.Containers; using Robust.Shared.GameStates; +using Robust.Shared.Network; namespace Content.Shared.Body.Systems; public partial class SharedBodySystem { + [Dependency] private readonly INetManager _netManager = default!; + public void InitializeBody() { SubscribeLocalEvent(OnBodyInit); @@ -34,7 +37,10 @@ public partial class SharedBodySystem return; var prototype = Prototypes.Index(body.Prototype); - InitBody(body, prototype); + + if (!_netManager.IsClient || bodyId.IsClientSide()) + InitBody(body, prototype); + Dirty(body); // Client doesn't actually spawn the body, need to sync it } @@ -72,7 +78,21 @@ public partial class SharedBodySystem return true; } - protected abstract void InitBody(BodyComponent body, BodyPrototype prototype); + protected void InitBody(BodyComponent body, BodyPrototype prototype) + { + var root = prototype.Slots[prototype.Root]; + Containers.EnsureContainer(body.Owner, BodyContainerId); + if (root.Part == null) + return; + var bodyId = Spawn(root.Part, body.Owner.ToCoordinates()); + var partComponent = Comp(bodyId); + var slot = new BodyPartSlot(root.Part, body.Owner, partComponent.PartType); + body.Root = slot; + partComponent.Body = bodyId; + + AttachPart(bodyId, slot, partComponent); + InitPart(partComponent, prototype, prototype.Root); + } protected void InitPart(BodyPartComponent parent, BodyPrototype prototype, string slotId, HashSet? initialized = null) { diff --git a/Content.Shared/Clothing/Components/LoadoutComponent.cs b/Content.Shared/Clothing/Components/LoadoutComponent.cs new file mode 100644 index 0000000000..a6d426a4b7 --- /dev/null +++ b/Content.Shared/Clothing/Components/LoadoutComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared.Roles; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Shared.Clothing.Components; + +[RegisterComponent, NetworkedComponent] +public sealed class LoadoutComponent : Component +{ + /// + /// A list of starting gears, of which one will be given. + /// All elements are weighted the same in the list. + /// + [DataField("prototypes", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer)), AutoNetworkedField] + public List? Prototypes; +} diff --git a/Content.Shared/Clothing/LoadoutSystem.cs b/Content.Shared/Clothing/LoadoutSystem.cs new file mode 100644 index 0000000000..bb93e9e38c --- /dev/null +++ b/Content.Shared/Clothing/LoadoutSystem.cs @@ -0,0 +1,35 @@ +using Content.Shared.Clothing.Components; +using Content.Shared.Roles; +using Content.Shared.Station; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Shared.Clothing; + +/// +/// Assigns a loadout to an entity based on the startingGear prototype +/// +public sealed class LoadoutSystem : EntitySystem +{ + // Shared so we can predict it for placement manager. + + [Dependency] private readonly SharedStationSpawningSystem _station = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + } + + private void OnMapInit(EntityUid uid, LoadoutComponent component, MapInitEvent args) + { + if (component.Prototypes == null) + return; + + var proto = _protoMan.Index(_random.Pick(component.Prototypes)); + _station.EquipStartingGear(uid, proto, null); + } +} diff --git a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs index 31bcbb56dd..e462077d46 100644 --- a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs +++ b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs @@ -4,6 +4,9 @@ using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Utility; using System.Linq; +using Content.Shared.Preferences; +using Robust.Shared.GameObjects.Components.Localization; +using Robust.Shared.Network; namespace Content.Shared.Humanoid; @@ -18,6 +21,7 @@ namespace Content.Shared.Humanoid; /// public abstract class SharedHumanoidAppearanceSystem : EntitySystem { + [Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly MarkingManager _markingManager = default!; @@ -26,9 +30,33 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnGetState); } + private void OnInit(EntityUid uid, HumanoidAppearanceComponent humanoid, ComponentInit args) + { + if (string.IsNullOrEmpty(humanoid.Species) || _netManager.IsClient && !uid.IsClientSide()) + { + return; + } + + if (string.IsNullOrEmpty(humanoid.Initial) + || !_prototypeManager.TryIndex(humanoid.Initial, out HumanoidProfilePrototype? startingSet)) + { + LoadProfile(uid, HumanoidCharacterProfile.DefaultWithSpecies(humanoid.Species), humanoid); + return; + } + + // Do this first, because profiles currently do not support custom base layers + foreach (var (layer, info) in startingSet.CustomBaseLayers) + { + humanoid.CustomBaseLayers.Add(layer, info); + } + + LoadProfile(uid, startingSet.Profile, humanoid); + } + private void OnGetState(EntityUid uid, HumanoidAppearanceComponent component, ref ComponentGetState args) { args.State = new HumanoidAppearanceState(component.MarkingSet, @@ -233,4 +261,155 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem Dirty(humanoid); } } + + /// + /// Loads a humanoid character profile directly onto this humanoid mob. + /// + /// The mob's entity UID. + /// The character profile to load. + /// Humanoid component of the entity + public virtual void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null) + { + if (!Resolve(uid, ref humanoid)) + { + return; + } + + SetSpecies(uid, profile.Species, false, humanoid); + SetSex(uid, profile.Sex, false, humanoid); + humanoid.EyeColor = profile.Appearance.EyeColor; + + SetSkinColor(uid, profile.Appearance.SkinColor, false); + + humanoid.MarkingSet.Clear(); + + // Add markings that doesn't need coloring. We store them until we add all other markings that doesn't need it. + var markingFColored = new Dictionary(); + foreach (var marking in profile.Appearance.Markings) + { + if (_markingManager.TryGetMarking(marking, out var prototype)) + { + if (!prototype.ForcedColoring) + { + AddMarking(uid, marking.MarkingId, marking.MarkingColors, false); + } + else + { + markingFColored.Add(marking, prototype); + } + } + } + + // Hair/facial hair - this may eventually be deprecated. + // We need to ensure hair before applying it or coloring can try depend on markings that can be invalid + var hairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.Hair, out var hairAlpha, _prototypeManager) + ? profile.Appearance.SkinColor.WithAlpha(hairAlpha) : profile.Appearance.HairColor; + var facialHairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.FacialHair, out var facialHairAlpha, _prototypeManager) + ? profile.Appearance.SkinColor.WithAlpha(facialHairAlpha) : profile.Appearance.FacialHairColor; + + if (_markingManager.Markings.TryGetValue(profile.Appearance.HairStyleId, out var hairPrototype) && + _markingManager.CanBeApplied(profile.Species, hairPrototype, _prototypeManager)) + { + AddMarking(uid, profile.Appearance.HairStyleId, hairColor, false); + } + + if (_markingManager.Markings.TryGetValue(profile.Appearance.FacialHairStyleId, out var facialHairPrototype) && + _markingManager.CanBeApplied(profile.Species, facialHairPrototype, _prototypeManager)) + { + AddMarking(uid, profile.Appearance.FacialHairStyleId, facialHairColor, false); + } + + humanoid.MarkingSet.EnsureSpecies(profile.Species, profile.Appearance.SkinColor, _markingManager, _prototypeManager); + + // Finally adding marking with forced colors + foreach (var (marking, prototype) in markingFColored) + { + var markingColors = MarkingColoring.GetMarkingLayerColors( + prototype, + profile.Appearance.SkinColor, + profile.Appearance.EyeColor, + humanoid.MarkingSet + ); + AddMarking(uid, marking.MarkingId, markingColors, false); + } + + EnsureDefaultMarkings(uid, humanoid); + + humanoid.Gender = profile.Gender; + if (TryComp(uid, out var grammar)) + { + grammar.Gender = profile.Gender; + } + + humanoid.Age = profile.Age; + + Dirty(humanoid); + } + + /// + /// Adds a marking to this humanoid. + /// + /// Humanoid mob's UID + /// Marking ID to use + /// Color to apply to all marking layers of this marking + /// Whether to immediately sync this marking or not + /// If this marking was forced (ignores marking points) + /// Humanoid component of the entity + public void AddMarking(EntityUid uid, string marking, Color? color = null, bool sync = true, bool forced = false, HumanoidAppearanceComponent? humanoid = null) + { + if (!Resolve(uid, ref humanoid) + || !_markingManager.Markings.TryGetValue(marking, out var prototype)) + { + return; + } + + var markingObject = prototype.AsMarking(); + markingObject.Forced = forced; + if (color != null) + { + for (var i = 0; i < prototype.Sprites.Count; i++) + { + markingObject.SetColor(i, color.Value); + } + } + + humanoid.MarkingSet.AddBack(prototype.MarkingCategory, markingObject); + + if (sync) + Dirty(humanoid); + } + + private void EnsureDefaultMarkings(EntityUid uid, HumanoidAppearanceComponent? humanoid) + { + if (!Resolve(uid, ref humanoid)) + { + return; + } + humanoid.MarkingSet.EnsureDefault(humanoid.SkinColor, humanoid.EyeColor, _markingManager); + } + + /// + /// + /// + /// Humanoid mob's UID + /// Marking ID to use + /// Colors to apply against this marking's set of sprites. + /// Whether to immediately sync this marking or not + /// If this marking was forced (ignores marking points) + /// Humanoid component of the entity + public void AddMarking(EntityUid uid, string marking, IReadOnlyList colors, bool sync = true, bool forced = false, HumanoidAppearanceComponent? humanoid = null) + { + if (!Resolve(uid, ref humanoid) + || !_markingManager.Markings.TryGetValue(marking, out var prototype)) + { + return; + } + + var markingObject = new Marking(marking, colors); + markingObject.Forced = forced; + humanoid.MarkingSet.AddBack(prototype.MarkingCategory, markingObject); + + if (sync) + Dirty(humanoid); + } } diff --git a/Content.Shared/Station/SharedStationSpawningSystem.cs b/Content.Shared/Station/SharedStationSpawningSystem.cs new file mode 100644 index 0000000000..cf575fb4f2 --- /dev/null +++ b/Content.Shared/Station/SharedStationSpawningSystem.cs @@ -0,0 +1,46 @@ +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Inventory; +using Content.Shared.Preferences; +using Content.Shared.Roles; + +namespace Content.Shared.Station; + +public abstract class SharedStationSpawningSystem : EntitySystem +{ + [Dependency] protected readonly InventorySystem InventorySystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + + /// + /// Equips starting gear onto the given entity. + /// + /// Entity to load out. + /// Starting gear to use. + /// Character profile to use, if any. + public void EquipStartingGear(EntityUid entity, StartingGearPrototype startingGear, HumanoidCharacterProfile? profile) + { + if (InventorySystem.TryGetSlots(entity, out var slotDefinitions)) + { + foreach (var slot in slotDefinitions) + { + var equipmentStr = startingGear.GetGear(slot.Name, profile); + if (!string.IsNullOrEmpty(equipmentStr)) + { + var equipmentEntity = EntityManager.SpawnEntity(equipmentStr, EntityManager.GetComponent(entity).Coordinates); + InventorySystem.TryEquip(entity, equipmentEntity, slot.Name, true); + } + } + } + + if (!TryComp(entity, out HandsComponent? handsComponent)) + return; + + var inhand = startingGear.Inhand; + var coords = EntityManager.GetComponent(entity).Coordinates; + foreach (var (hand, prototype) in inhand) + { + var inhandEntity = EntityManager.SpawnEntity(prototype, coords); + _handsSystem.TryPickup(entity, inhandEntity, hand, checkActionBlocker: false, handsComp: handsComponent); + } + } +} diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 01133abae0..0c564c2772 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1353,7 +1353,7 @@ damage: 50 behaviors: - !type:ExplodeBehavior - + - type: entity name: monkey id: MobMonkeySyndicateAgent @@ -1442,7 +1442,6 @@ - type: GhostTakeoverAvailable - type: IdExaminable - type: Loadout - prototype: SyndicateOperativeGearMonkey prototypes: [SyndicateOperativeGearMonkey] # I have included a snake_hiss.ogg sound file so if you want to use that be my guest diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml index eb5ae34eb2..1196642554 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -11,7 +11,6 @@ name: Syndicate Agent components: - type: Loadout - prototype: SyndicateOperativeGearExtremelyBasic prototypes: [SyndicateOperativeGearExtremelyBasic] - type: RandomMetadata nameSegments: [names_death_commando] @@ -46,7 +45,6 @@ randomizeName: false - type: NukeOperative - type: Loadout - prototype: SyndicateOperativeGearFull prototypes: [SyndicateOperativeGearFull] - type: RandomMetadata nameSegments: