From ec1803b44fc3755788e4e57224284a75f8eb3ecb Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 2 Jan 2025 23:49:42 +0300 Subject: [PATCH] Generic Clothing Equip Functions (#1407) # Description This PR was originally going to be called "Psionic Refactor V3 Part 2, Items Of Power", but I began getting shakes and started vomiting when I saw the list of Components that make items do things when equipped, and I had a conniption about how much code there is constantly being repeated. So instead of this PR adding Items Of Power, I added a universal modular generic system for making clothing items do things when equipped and unequipped. Which hooks into the library of TraitFunctions that we've previously created. I also added a few "Inverse" versions of trait functions that can be used by these new clothing functions. (cherry picked from commit 731e14f100d8161c0f8f7eb1bc5accc2abaa24da) --- .../Clothing/ClientClothingSystem.cs | 4 +- Content.Server/Clothing/ClothingSystem.cs | 28 +++++++++ .../Traits/TraitSystem.Functions.cs | 51 +++++++++++++++- .../Clothing/Components/ClothingComponent.cs | 58 ++++++++++++------- .../Clothing/EntitySystems/ClothingSystem.cs | 3 +- 5 files changed, 118 insertions(+), 26 deletions(-) diff --git a/Content.Client/Clothing/ClientClothingSystem.cs b/Content.Client/Clothing/ClientClothingSystem.cs index b3d6848915..11caa82860 100644 --- a/Content.Client/Clothing/ClientClothingSystem.cs +++ b/Content.Client/Clothing/ClientClothingSystem.cs @@ -133,8 +133,8 @@ public sealed class ClientClothingSystem : ClothingSystem RSI? rsi = null; - if (clothing.RsiPath != null) - rsi = _cache.GetResource(SpriteSpecifierSerializer.TextureRoot / clothing.RsiPath).RSI; + if (clothing.Sprite != null) + rsi = _cache.GetResource(SpriteSpecifierSerializer.TextureRoot / clothing.Sprite).RSI; else if (TryComp(uid, out SpriteComponent? sprite)) rsi = sprite.BaseRSI; diff --git a/Content.Server/Clothing/ClothingSystem.cs b/Content.Server/Clothing/ClothingSystem.cs index 17ad016198..407186f87c 100644 --- a/Content.Server/Clothing/ClothingSystem.cs +++ b/Content.Server/Clothing/ClothingSystem.cs @@ -1,7 +1,35 @@ +using Content.Shared.Clothing.Components; using Content.Shared.Clothing.EntitySystems; +using Content.Shared.Inventory.Events; +using Robust.Shared.Serialization.Manager; namespace Content.Server.Clothing; public sealed class ServerClothingSystem : ClothingSystem { + [Dependency] private readonly IComponentFactory _componentFactory = default!; + [Dependency] private readonly ISerializationManager _serializationManager = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnClothingEquipped); + SubscribeLocalEvent(OnClothingUnequipped); + } + + private void OnClothingEquipped(EntityUid uid, ClothingComponent clothingComponent, DidEquipEvent args) + { + // Yes, this is using the trait system functions. I'm not going to write an entire third function library to do this. + // They're generic for a reason. + foreach (var function in clothingComponent.OnEquipFunctions) + function.OnPlayerSpawn(uid, _componentFactory, EntityManager, _serializationManager); + } + + private void OnClothingUnequipped(EntityUid uid, ClothingComponent clothingComponent, DidUnequipEvent args) + { + // Yes, this is using the trait system functions. I'm not going to write an entire third function library to do this. + // They're generic for a reason. + foreach (var function in clothingComponent.OnUnequipFunctions) + function.OnPlayerSpawn(uid, _componentFactory, EntityManager, _serializationManager); + } } diff --git a/Content.Server/Traits/TraitSystem.Functions.cs b/Content.Server/Traits/TraitSystem.Functions.cs index 7ec8b8c54f..e6644067c7 100644 --- a/Content.Server/Traits/TraitSystem.Functions.cs +++ b/Content.Server/Traits/TraitSystem.Functions.cs @@ -137,6 +137,9 @@ public sealed partial class TraitAddPsionics : TraitFunction [DataField, AlwaysPushInheritance] public List> PsionicPowers { get; private set; } = new(); + [DataField, AlwaysPushInheritance] + public bool PlayFeedback; + public override void OnPlayerSpawn(EntityUid uid, IComponentFactory factory, IEntityManager entityManager, @@ -147,7 +150,34 @@ public sealed partial class TraitAddPsionics : TraitFunction foreach (var powerProto in PsionicPowers) if (prototype.TryIndex(powerProto, out var psionicPower)) - psionic.InitializePsionicPower(uid, psionicPower, false); + psionic.InitializePsionicPower(uid, psionicPower, PlayFeedback); + } +} + +/// +/// This isn't actually used for any traits, surprise, other systems can use these functions! +/// This is used by Items of Power to remove a psionic power when unequipped. +/// +[UsedImplicitly] +public sealed partial class TraitRemovePsionics : TraitFunction +{ + [DataField, AlwaysPushInheritance] + public List> PsionicPowers { get; private set; } = new(); + + [DataField, AlwaysPushInheritance] + public bool Forced = true; + + public override void OnPlayerSpawn(EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager) + { + var prototype = IoCManager.Resolve(); + var psionic = entityManager.System(); + + foreach (var powerProto in PsionicPowers) + if (prototype.TryIndex(powerProto, out var psionicPower)) + psionic.RemovePsionicPower(uid, psionicPower, Forced); } } @@ -316,6 +346,25 @@ public sealed partial class TraitAddArmor : TraitFunction } } +[UsedImplicitly] +public sealed partial class TraitRemoveArmor : TraitFunction +{ + [DataField, AlwaysPushInheritance] + public List DamageModifierSets { get; private set; } = new(); + + public override void OnPlayerSpawn(EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager) + { + if (!entityManager.TryGetComponent(uid, out var damageableComponent)) + return; + + foreach (var modifierSet in DamageModifierSets) + damageableComponent.DamageModifierSets.Remove(modifierSet); + } +} + [UsedImplicitly] public sealed partial class TraitAddSolutionContainer : TraitFunction { diff --git a/Content.Shared/Clothing/Components/ClothingComponent.cs b/Content.Shared/Clothing/Components/ClothingComponent.cs index 1e2307b92b..fc63beaaed 100644 --- a/Content.Shared/Clothing/Components/ClothingComponent.cs +++ b/Content.Shared/Clothing/Components/ClothingComponent.cs @@ -1,6 +1,7 @@ using Content.Shared.Clothing.EntitySystems; using Content.Shared.DoAfter; using Content.Shared.Inventory; +using Content.Shared.Traits; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Serialization; @@ -15,55 +16,69 @@ namespace Content.Shared.Clothing.Components; [Access(typeof(ClothingSystem), typeof(InventorySystem))] public sealed partial class ClothingComponent : Component { - [DataField("clothingVisuals")] + [DataField] [Access(typeof(ClothingSystem), typeof(InventorySystem), Other = AccessPermissions.ReadExecute)] // TODO remove execute permissions. public Dictionary> ClothingVisuals = new(); - [ViewVariables(VVAccess.ReadWrite)] - [DataField("quickEquip")] + [DataField] public bool QuickEquip = true; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("slots", required: true)] + [DataField(required: true)] [Access(typeof(ClothingSystem), typeof(InventorySystem), Other = AccessPermissions.ReadExecute)] public SlotFlags Slots = SlotFlags.NONE; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("equipSound")] + [DataField] public SoundSpecifier? EquipSound; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("unequipSound")] + [DataField] public SoundSpecifier? UnequipSound; [Access(typeof(ClothingSystem))] - [ViewVariables(VVAccess.ReadWrite)] - [DataField("equippedPrefix")] + [DataField] public string? EquippedPrefix; /// - /// Allows the equipped state to be directly overwritten. - /// useful when prototyping INNERCLOTHING items into OUTERCLOTHING items without duplicating/modifying RSIs etc. + /// Allows the equipped state to be directly overwritten. + /// useful when prototyping INNERCLOTHING items into OUTERCLOTHING items without duplicating/modifying RSIs etc. /// [Access(typeof(ClothingSystem))] - [ViewVariables(VVAccess.ReadWrite)] - [DataField("equippedState")] + [DataField] public string? EquippedState; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("sprite")] - public string? RsiPath; + [DataField] + public string? Sprite; + + [DataField] + public ClothingMask MaleMask = ClothingMask.UniformFull; + + [DataField] + public ClothingMask FemaleMask = ClothingMask.UniformFull; + + [DataField] + public ClothingMask UnisexMask = ClothingMask.UniformFull; /// - /// Name of the inventory slot the clothing is in. + /// Name of the inventory slot the clothing is in. /// public string? InSlot; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public TimeSpan EquipDelay = TimeSpan.Zero; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public TimeSpan UnequipDelay = TimeSpan.Zero; + + /// + /// These functions are called when an entity equips an item with this component. + /// + [DataField(serverOnly: true)] + public TraitFunction[] OnEquipFunctions { get; private set; } = Array.Empty(); + + /// + /// These functions are called when an entity un-equips an item with this component. + /// + [DataField(serverOnly: true)] + public TraitFunction[] OnUnequipFunctions { get; private set; } = Array.Empty(); } [Serializable, NetSerializable] @@ -109,4 +124,3 @@ public sealed partial class ClothingUnequipDoAfterEvent : DoAfterEvent public override DoAfterEvent Clone() => this; } - diff --git a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs index 7d457ffb05..708eb9f1f6 100644 --- a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs @@ -247,7 +247,8 @@ public abstract class ClothingSystem : EntitySystem clothing.ClothingVisuals = otherClothing.ClothingVisuals; clothing.EquippedPrefix = otherClothing.EquippedPrefix; - clothing.RsiPath = otherClothing.RsiPath; + clothing.Sprite = otherClothing.Sprite; + clothing.FemaleMask = otherClothing.FemaleMask; _itemSys.VisualsChanged(uid); Dirty(uid, clothing);