]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Chameleon clothes + EMP behaviour (#30924)
authormhamster <81412348+mhamsterr@users.noreply.github.com>
Fri, 30 May 2025 00:06:03 +0000 (07:06 +0700)
committerGitHub <noreply@github.com>
Fri, 30 May 2025 00:06:03 +0000 (20:06 -0400)
* resolving conflicts??

* Controlled clothes changing

+ time stuff
+ EmpChangeIntensity

* Single clothes change

+ EmpContinious
+ moved random pick logic into GetRandomValidPrototype

* Changes from reviews

Co-Authored-By: Nemanja <98561806+emogarbage404@users.noreply.github.com>
* Update ChameleonClothingComponent.cs

* repairing irreparable damage

i failed, did i?

* damaging repaired irreparable

uh???

* 2025 FUN ALLOWED!!!!

* Minor changes from reviews

Co-Authored-By: beck-thompson <107373427+beck-thompson@users.noreply.github.com>
* Fix merge conflicts

* Fix that last bug

* cleanup

* Remove VV attr.

* AutoPausedField on emp time change

---------

Co-authored-by: Nemanja <98561806+emogarbage404@users.noreply.github.com>
Co-authored-by: beck-thompson <107373427+beck-thompson@users.noreply.github.com>
Co-authored-by: beck-thompson <beck314159@hotmail.com>
Co-authored-by: EmoGarbage404 <retron404@gmail.com>
Content.Client/Clothing/Systems/ChameleonClothingSystem.cs
Content.Client/Clothing/UI/ChameleonBoundUserInterface.cs
Content.Client/Clothing/UI/ChameleonMenu.xaml.cs
Content.Server/Clothing/Systems/ChameleonClothingSystem.cs
Content.Shared/Clothing/Components/ChameleonClothingComponent.cs
Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs

index bde6a4b99ab068239f2d56391467b19db17ace97..74fcf48849a52c3c06f1ba5b3437b97ced3314b5 100644 (file)
@@ -13,16 +13,6 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
 {
     [Dependency] private readonly IPrototypeManager _proto = default!;
 
-    private static readonly SlotFlags[] IgnoredSlots =
-    {
-        SlotFlags.All,
-        SlotFlags.PREVENTEQUIP,
-        SlotFlags.NONE
-    };
-    private static readonly SlotFlags[] Slots = Enum.GetValues<SlotFlags>().Except(IgnoredSlots).ToArray();
-
-    private readonly Dictionary<SlotFlags, List<string>> _data = new();
-
     public override void Initialize()
     {
         base.Initialize();
@@ -61,49 +51,4 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
             borderColor.AccentVColor = otherBorderColor.AccentVColor;
         }
     }
-
-    /// <summary>
-    ///     Get a list of valid chameleon targets for these slots.
-    /// </summary>
-    public IEnumerable<string> GetValidTargets(SlotFlags slot)
-    {
-        var set = new HashSet<string>();
-        foreach (var availableSlot in _data.Keys)
-        {
-            if (slot.HasFlag(availableSlot))
-            {
-                set.UnionWith(_data[availableSlot]);
-            }
-        }
-        return set;
-    }
-
-    private void PrepareAllVariants()
-    {
-        _data.Clear();
-        var prototypes = _proto.EnumeratePrototypes<EntityPrototype>();
-
-        foreach (var proto in prototypes)
-        {
-            // check if this is valid clothing
-            if (!IsValidTarget(proto))
-                continue;
-            if (!proto.TryGetComponent(out ClothingComponent? item, Factory))
-                continue;
-
-            // sort item by their slot flags
-            // one item can be placed in several buckets
-            foreach (var slot in Slots)
-            {
-                if (!item.Slots.HasFlag(slot))
-                    continue;
-
-                if (!_data.ContainsKey(slot))
-                {
-                    _data.Add(slot, new List<string>());
-                }
-                _data[slot].Add(proto.ID);
-            }
-        }
-    }
 }
index bd86ffbec0b6a143afb6294a64b906e5d4486e97..876f300e50c380a7b3ce960a1dd4d7340586d8eb 100644 (file)
@@ -42,7 +42,7 @@ public sealed class ChameleonBoundUserInterface : BoundUserInterface
         var targets = _chameleon.GetValidTargets(st.Slot);
         if (st.RequiredTag != null)
         {
-            var newTargets = new List<string>();
+            var newTargets = new List<EntProtoId>();
             foreach (var target in targets)
             {
                 if (string.IsNullOrEmpty(target) || !_proto.TryIndex(target, out EntityPrototype? proto))
index bd45be4510f6ac2b994f388faad0580994d10ae2..c6dce107767e1450b92759b77cc297251f183585 100644 (file)
@@ -19,8 +19,8 @@ public sealed partial class ChameleonMenu : DefaultWindow
     private readonly SpriteSystem _sprite;
     public event Action<string>? OnIdSelected;
 
-    private IEnumerable<string> _possibleIds = Enumerable.Empty<string>();
-    private string? _selectedId;
+    private IEnumerable<EntProtoId> _possibleIds = [];
+    private EntProtoId? _selectedId;
     private string _searchFilter = "";
 
     public ChameleonMenu()
@@ -32,7 +32,7 @@ public sealed partial class ChameleonMenu : DefaultWindow
         Search.OnTextChanged += OnSearchEntered;
     }
 
-    public void UpdateState(IEnumerable<string> possibleIds, string? selectedId)
+    public void UpdateState(IEnumerable<EntProtoId> possibleIds, string? selectedId)
     {
         _possibleIds = possibleIds;
         _selectedId = selectedId;
@@ -57,7 +57,7 @@ public sealed partial class ChameleonMenu : DefaultWindow
             if (!_prototypeManager.TryIndex(id, out EntityPrototype? proto))
                 continue;
 
-            var lowId = id.ToLowerInvariant();
+            var lowId = id.Id.ToLowerInvariant();
             var lowName = proto.Name.ToLowerInvariant();
             if (!lowId.Contains(searchFilterLow) && !lowName.Contains(_searchFilter))
                 continue;
index f3c7e316cae520ec337e32588383130cbe2ada19..75850e6d7483a1ec48bbbd6f1568d687720c6fbb 100644 (file)
@@ -1,9 +1,14 @@
+using System.Linq;
+using Content.Server.Emp;
 using Content.Server.IdentityManagement;
 using Content.Shared.Clothing.Components;
 using Content.Shared.Clothing.EntitySystems;
+using Content.Shared.Emp;
 using Content.Shared.IdentityManagement.Components;
+using Content.Shared.Inventory;
 using Content.Shared.Prototypes;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
 
 namespace Content.Server.Clothing.Systems;
 
@@ -11,12 +16,16 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
 {
     [Dependency] private readonly IPrototypeManager _proto = default!;
     [Dependency] private readonly IdentitySystem _identity = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly IEntityManager _entityManager = default!;
 
     public override void Initialize()
     {
         base.Initialize();
         SubscribeLocalEvent<ChameleonClothingComponent, MapInitEvent>(OnMapInit);
         SubscribeLocalEvent<ChameleonClothingComponent, ChameleonPrototypeSelectedMessage>(OnSelected);
+
+        SubscribeLocalEvent<ChameleonClothingComponent, EmpPulseEvent>(OnEmpPulse);
     }
 
     private void OnMapInit(EntityUid uid, ChameleonClothingComponent component, MapInitEvent args)
@@ -29,6 +38,21 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
         SetSelectedPrototype(uid, args.SelectedId, component: component);
     }
 
+    private void OnEmpPulse(EntityUid uid, ChameleonClothingComponent component, ref EmpPulseEvent args)
+    {
+        if (!component.AffectedByEmp)
+            return;
+
+        if (component.EmpContinuous)
+            component.NextEmpChange = _timing.CurTime + TimeSpan.FromSeconds(1f / component.EmpChangeIntensity);
+
+        var pick = GetRandomValidPrototype(component.Slot, component.RequireTag);
+        SetSelectedPrototype(uid, pick, component: component);
+
+        args.Affected = true;
+        args.Disabled = true;
+    }
+
     private void UpdateUi(EntityUid uid, ChameleonClothingComponent? component = null)
     {
         if (!Resolve(uid, ref component))
@@ -65,6 +89,35 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
         Dirty(uid, component);
     }
 
+    /// <summary>
+    ///     Get a random prototype for a given slot.
+    /// </summary>
+    public string GetRandomValidPrototype(SlotFlags slot, string? tag = null)
+    {
+        return _random.Pick(GetValidTargets(slot, tag).ToList());
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+        // Randomize EMP-affected clothing
+        var query = EntityQueryEnumerator<EmpDisabledComponent, ChameleonClothingComponent>();
+        while (query.MoveNext(out var uid, out _, out var chameleon))
+        {
+            if (!chameleon.EmpContinuous)
+                continue;
+
+            if (_timing.CurTime < chameleon.NextEmpChange)
+                continue;
+
+            // randomly pick cloth element from available and apply it
+            var pick = GetRandomValidPrototype(chameleon.Slot, chameleon.RequireTag);
+            SetSelectedPrototype(uid, pick, component: chameleon);
+
+            chameleon.NextEmpChange += TimeSpan.FromSeconds(1f / chameleon.EmpChangeIntensity);
+        }
+    }
+
     private void UpdateIdentityBlocker(EntityUid uid, ChameleonClothingComponent component, EntityPrototype proto)
     {
         if (proto.HasComponent<IdentityBlockerComponent>(Factory))
index 8fa2f19fa2fff91deaf365d5cf137f08c9fa223c..5caddda18be5689b0c121a0587a614647a0235e7 100644 (file)
@@ -3,27 +3,26 @@ using Content.Shared.Inventory;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
 namespace Content.Shared.Clothing.Components;
 
 /// <summary>
 ///     Allow players to change clothing sprite to any other clothing prototype.
 /// </summary>
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), AutoGenerateComponentPause]
 [Access(typeof(SharedChameleonClothingSystem))]
 public sealed partial class ChameleonClothingComponent : Component
 {
     /// <summary>
     ///     Filter possible chameleon options by their slot flag.
     /// </summary>
-    [ViewVariables(VVAccess.ReadOnly)]
     [DataField(required: true)]
     public SlotFlags Slot;
 
     /// <summary>
     ///     EntityPrototype id that chameleon item is trying to mimic.
     /// </summary>
-    [ViewVariables(VVAccess.ReadOnly)]
     [DataField(required: true), AutoNetworkedField]
     public EntProtoId? Default;
 
@@ -38,6 +37,34 @@ public sealed partial class ChameleonClothingComponent : Component
     /// </summary>
     [DataField]
     public string? RequireTag;
+
+    /// <summary>
+    ///     Will component owner be affected by EMP pulses?
+    /// </summary>
+    [DataField]
+    public bool AffectedByEmp = true;
+
+    /// <summary>
+    ///     Intensity of clothes change on EMP.
+    ///     Can be interpreted as "How many times clothes will change every second?".
+    ///     Useless without <see cref="AffectedByEmp"/> set to true.
+    /// </summary>
+    [DataField]
+    public int EmpChangeIntensity = 7;
+
+    /// <summary>
+    ///     Should the EMP-change happen continuously, or only once?
+    ///     (False = once, True = continuously)
+    ///     Useless without <see cref="AffectedByEmp"/>
+    /// </summary>
+    [DataField]
+    public bool EmpContinuous = true;
+
+    /// <summary>
+    ///     When should next EMP-caused appearance change happen?
+    /// </summary>
+    [AutoPausedField, DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    public TimeSpan NextEmpChange = TimeSpan.Zero;
 }
 
 [Serializable, NetSerializable]
index 6c5fbdfb0b01b184ec41d8bbc9960a45e6a1dbbf..233a71acee7875acc045b55450edd2f38c66f8a6 100644 (file)
@@ -1,3 +1,4 @@
+using System.Linq;
 using Content.Shared.Access.Components;
 using Content.Shared.Clothing.Components;
 using Content.Shared.Contraband;
@@ -7,6 +8,7 @@ using Content.Shared.Item;
 using Content.Shared.Tag;
 using Content.Shared.Verbs;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 
 namespace Content.Shared.Clothing.EntitySystems;
@@ -20,6 +22,19 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
     [Dependency] private readonly SharedItemSystem _itemSystem = default!;
     [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
     [Dependency] private readonly TagSystem _tag = default!;
+    [Dependency] protected readonly IGameTiming _timing = default!;
+
+    private static readonly SlotFlags[] IgnoredSlots =
+    {
+        SlotFlags.All,
+        SlotFlags.PREVENTEQUIP,
+        SlotFlags.NONE
+    };
+    private static readonly SlotFlags[] Slots = Enum.GetValues<SlotFlags>().Except(IgnoredSlots).ToArray();
+
+    private readonly Dictionary<SlotFlags, List<EntProtoId>> _data = new();
+
+    public readonly Dictionary<SlotFlags, List<string>> ValidVariants = new();
     [Dependency] protected readonly SharedUserInterfaceSystem UI = default!;
 
     private static readonly ProtoId<TagPrototype> WhitelistChameleonTag = "WhitelistChameleon";
@@ -30,6 +45,14 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
         SubscribeLocalEvent<ChameleonClothingComponent, GotEquippedEvent>(OnGotEquipped);
         SubscribeLocalEvent<ChameleonClothingComponent, GotUnequippedEvent>(OnGotUnequipped);
         SubscribeLocalEvent<ChameleonClothingComponent, GetVerbsEvent<InteractionVerb>>(OnVerb);
+
+        SubscribeLocalEvent<ChameleonClothingComponent, PrototypesReloadedEventArgs>(OnPrototypeReload);
+        PrepareAllVariants();
+    }
+
+    private void OnPrototypeReload(EntityUid uid, ChameleonClothingComponent component, PrototypesReloadedEventArgs args)
+    {
+        PrepareAllVariants();
     }
 
     private void OnGotEquipped(EntityUid uid, ChameleonClothingComponent component, GotEquippedEvent args)
@@ -139,4 +162,55 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
 
         return true;
     }
+
+    /// <summary>
+    ///     Get a list of valid chameleon targets for these slots.
+    /// </summary>
+    public IEnumerable<EntProtoId> GetValidTargets(SlotFlags slot, string? tag = null)
+    {
+        var validTargets = new List<EntProtoId>();
+        if (tag != null)
+        {
+            foreach (var proto in _data[slot])
+            {
+                if (IsValidTarget(_proto.Index(proto), slot, tag))
+                    validTargets.Add(proto);
+            }
+        }
+        else
+        {
+            validTargets = _data[slot];
+        }
+
+        return validTargets;
+    }
+
+    protected void PrepareAllVariants()
+    {
+        _data.Clear();
+        var prototypes = _proto.EnumeratePrototypes<EntityPrototype>();
+
+        foreach (var proto in prototypes)
+        {
+            // check if this is valid clothing
+            if (!IsValidTarget(proto))
+                continue;
+            if (!proto.TryGetComponent(out ClothingComponent? item, Factory))
+                continue;
+
+            // sort item by their slot flags
+            // one item can be placed in several buckets
+            foreach (var slot in Slots)
+            {
+                if (!item.Slots.HasFlag(slot))
+                    continue;
+
+                if (!_data.ContainsKey(slot))
+                {
+                    _data.Add(slot, new List<EntProtoId>());
+                }
+                _data[slot].Add(proto.ID);
+            }
+        }
+    }
 }