Skip to content

Commit

Permalink
Generic Clothing Equip Functions (#1407)
Browse files Browse the repository at this point in the history
# 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.

<!--
# Changelog

:cl:
- add: Added a universal modular system for making clothing items DO
things when equipped and unequipped. This will be used for "Psionic
Artifacts" in conjunction with the Psionic Refactor V3.
-->
  • Loading branch information
VMSolidus authored Jan 2, 2025
1 parent 0c0ac31 commit 731e14f
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 32 deletions.
4 changes: 2 additions & 2 deletions Content.Client/Clothing/ClientClothingSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ private bool TryGetDefaultVisuals(EntityUid uid, ClothingComponent clothing, str

RSI? rsi = null;

if (clothing.RsiPath != null)
rsi = _cache.GetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / clothing.RsiPath).RSI;
if (clothing.Sprite != null)
rsi = _cache.GetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / clothing.Sprite).RSI;
else if (TryComp(uid, out SpriteComponent? sprite))
rsi = sprite.BaseRSI;

Expand Down
28 changes: 28 additions & 0 deletions Content.Server/Clothing/ClothingSystem.cs
Original file line number Diff line number Diff line change
@@ -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<ClothingComponent, DidEquipEvent>(OnClothingEquipped);
SubscribeLocalEvent<ClothingComponent, DidUnequipEvent>(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);
}
}
51 changes: 50 additions & 1 deletion Content.Server/Traits/TraitSystem.Functions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,36 @@ public sealed partial class TraitAddPsionics : TraitFunction
[DataField, AlwaysPushInheritance]
public List<ProtoId<PsionicPowerPrototype>> PsionicPowers { get; private set; } = new();

[DataField, AlwaysPushInheritance]
public bool PlayFeedback;

public override void OnPlayerSpawn(EntityUid uid,
IComponentFactory factory,
IEntityManager entityManager,
ISerializationManager serializationManager)
{
var prototype = IoCManager.Resolve<IPrototypeManager>();
var psionic = entityManager.System<PsionicAbilitiesSystem>();

foreach (var powerProto in PsionicPowers)
if (prototype.TryIndex(powerProto, out var psionicPower))
psionic.InitializePsionicPower(uid, psionicPower, PlayFeedback);
}
}

/// <summary>
/// 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.
/// </summary>
[UsedImplicitly]
public sealed partial class TraitRemovePsionics : TraitFunction
{
[DataField, AlwaysPushInheritance]
public List<ProtoId<PsionicPowerPrototype>> PsionicPowers { get; private set; } = new();

[DataField, AlwaysPushInheritance]
public bool Forced = true;

public override void OnPlayerSpawn(EntityUid uid,
IComponentFactory factory,
IEntityManager entityManager,
Expand All @@ -147,7 +177,7 @@ public override void OnPlayerSpawn(EntityUid uid,

foreach (var powerProto in PsionicPowers)
if (prototype.TryIndex(powerProto, out var psionicPower))
psionic.InitializePsionicPower(uid, psionicPower, false);
psionic.RemovePsionicPower(uid, psionicPower, Forced);
}
}

Expand Down Expand Up @@ -316,6 +346,25 @@ public override void OnPlayerSpawn(EntityUid uid,
}
}

[UsedImplicitly]
public sealed partial class TraitRemoveArmor : TraitFunction
{
[DataField, AlwaysPushInheritance]
public List<string> DamageModifierSets { get; private set; } = new();

public override void OnPlayerSpawn(EntityUid uid,
IComponentFactory factory,
IEntityManager entityManager,
ISerializationManager serializationManager)
{
if (!entityManager.TryGetComponent<DamageableComponent>(uid, out var damageableComponent))
return;

foreach (var modifierSet in DamageModifierSets)
damageableComponent.DamageModifierSets.Remove(modifierSet);
}
}

[UsedImplicitly]
public sealed partial class TraitAddSolutionContainer : TraitFunction
{
Expand Down
58 changes: 30 additions & 28 deletions Content.Shared/Clothing/Components/ClothingComponent.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -15,67 +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<string, List<PrototypeLayerData>> 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;

/// <summary>
/// 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.
/// </summary>
[Access(typeof(ClothingSystem))]
[ViewVariables(VVAccess.ReadWrite)]
[DataField("equippedState")]
[DataField]
public string? EquippedState;

[ViewVariables(VVAccess.ReadWrite)]
[DataField("sprite")]
public string? RsiPath;
[DataField]
public string? Sprite;

[ViewVariables(VVAccess.ReadWrite)]
[DataField("maleMask")]
[DataField]
public ClothingMask MaleMask = ClothingMask.UniformFull;

[ViewVariables(VVAccess.ReadWrite)]
[DataField("femaleMask")]
[DataField]
public ClothingMask FemaleMask = ClothingMask.UniformFull;

[ViewVariables(VVAccess.ReadWrite)]
[DataField("unisexMask")]
[DataField]
public ClothingMask UnisexMask = ClothingMask.UniformFull;

/// <summary>
/// Name of the inventory slot the clothing is in.
/// Name of the inventory slot the clothing is in.
/// </summary>
public string? InSlot;

[DataField, ViewVariables(VVAccess.ReadWrite)]
[DataField]
public TimeSpan EquipDelay = TimeSpan.Zero;

[DataField, ViewVariables(VVAccess.ReadWrite)]
[DataField]
public TimeSpan UnequipDelay = TimeSpan.Zero;

/// <summary>
/// These functions are called when an entity equips an item with this component.
/// </summary>
[DataField(serverOnly: true)]
public TraitFunction[] OnEquipFunctions { get; private set; } = Array.Empty<TraitFunction>();

/// <summary>
/// These functions are called when an entity un-equips an item with this component.
/// </summary>
[DataField(serverOnly: true)]
public TraitFunction[] OnUnequipFunctions { get; private set; } = Array.Empty<TraitFunction>();
}

[Serializable, NetSerializable]
Expand Down Expand Up @@ -121,4 +124,3 @@ public ClothingUnequipDoAfterEvent(string slot)

public override DoAfterEvent Clone() => this;
}

2 changes: 1 addition & 1 deletion Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public void CopyVisuals(EntityUid uid, ClothingComponent otherClothing, Clothing

clothing.ClothingVisuals = otherClothing.ClothingVisuals;
clothing.EquippedPrefix = otherClothing.EquippedPrefix;
clothing.RsiPath = otherClothing.RsiPath;
clothing.Sprite = otherClothing.Sprite;
clothing.FemaleMask = otherClothing.FemaleMask;

_itemSys.VisualsChanged(uid);
Expand Down

0 comments on commit 731e14f

Please sign in to comment.