Chameleon clothes + EMP behaviour (#30924)
* 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>
This commit is contained in:
@@ -13,16 +13,6 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
[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()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -61,49 +51,4 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
|
|||||||
borderColor.AccentVColor = otherBorderColor.AccentVColor;
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public sealed class ChameleonBoundUserInterface : BoundUserInterface
|
|||||||
var targets = _chameleon.GetValidTargets(st.Slot);
|
var targets = _chameleon.GetValidTargets(st.Slot);
|
||||||
if (st.RequiredTag != null)
|
if (st.RequiredTag != null)
|
||||||
{
|
{
|
||||||
var newTargets = new List<string>();
|
var newTargets = new List<EntProtoId>();
|
||||||
foreach (var target in targets)
|
foreach (var target in targets)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(target) || !_proto.TryIndex(target, out EntityPrototype? proto))
|
if (string.IsNullOrEmpty(target) || !_proto.TryIndex(target, out EntityPrototype? proto))
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ public sealed partial class ChameleonMenu : DefaultWindow
|
|||||||
private readonly SpriteSystem _sprite;
|
private readonly SpriteSystem _sprite;
|
||||||
public event Action<string>? OnIdSelected;
|
public event Action<string>? OnIdSelected;
|
||||||
|
|
||||||
private IEnumerable<string> _possibleIds = Enumerable.Empty<string>();
|
private IEnumerable<EntProtoId> _possibleIds = [];
|
||||||
private string? _selectedId;
|
private EntProtoId? _selectedId;
|
||||||
private string _searchFilter = "";
|
private string _searchFilter = "";
|
||||||
|
|
||||||
public ChameleonMenu()
|
public ChameleonMenu()
|
||||||
@@ -32,7 +32,7 @@ public sealed partial class ChameleonMenu : DefaultWindow
|
|||||||
Search.OnTextChanged += OnSearchEntered;
|
Search.OnTextChanged += OnSearchEntered;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateState(IEnumerable<string> possibleIds, string? selectedId)
|
public void UpdateState(IEnumerable<EntProtoId> possibleIds, string? selectedId)
|
||||||
{
|
{
|
||||||
_possibleIds = possibleIds;
|
_possibleIds = possibleIds;
|
||||||
_selectedId = selectedId;
|
_selectedId = selectedId;
|
||||||
@@ -57,7 +57,7 @@ public sealed partial class ChameleonMenu : DefaultWindow
|
|||||||
if (!_prototypeManager.TryIndex(id, out EntityPrototype? proto))
|
if (!_prototypeManager.TryIndex(id, out EntityPrototype? proto))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var lowId = id.ToLowerInvariant();
|
var lowId = id.Id.ToLowerInvariant();
|
||||||
var lowName = proto.Name.ToLowerInvariant();
|
var lowName = proto.Name.ToLowerInvariant();
|
||||||
if (!lowId.Contains(searchFilterLow) && !lowName.Contains(_searchFilter))
|
if (!lowId.Contains(searchFilterLow) && !lowName.Contains(_searchFilter))
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Server.Emp;
|
||||||
using Content.Server.IdentityManagement;
|
using Content.Server.IdentityManagement;
|
||||||
using Content.Shared.Clothing.Components;
|
using Content.Shared.Clothing.Components;
|
||||||
using Content.Shared.Clothing.EntitySystems;
|
using Content.Shared.Clothing.EntitySystems;
|
||||||
|
using Content.Shared.Emp;
|
||||||
using Content.Shared.IdentityManagement.Components;
|
using Content.Shared.IdentityManagement.Components;
|
||||||
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Prototypes;
|
using Content.Shared.Prototypes;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
namespace Content.Server.Clothing.Systems;
|
namespace Content.Server.Clothing.Systems;
|
||||||
|
|
||||||
@@ -11,12 +16,16 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
[Dependency] private readonly IdentitySystem _identity = default!;
|
[Dependency] private readonly IdentitySystem _identity = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
SubscribeLocalEvent<ChameleonClothingComponent, MapInitEvent>(OnMapInit);
|
SubscribeLocalEvent<ChameleonClothingComponent, MapInitEvent>(OnMapInit);
|
||||||
SubscribeLocalEvent<ChameleonClothingComponent, ChameleonPrototypeSelectedMessage>(OnSelected);
|
SubscribeLocalEvent<ChameleonClothingComponent, ChameleonPrototypeSelectedMessage>(OnSelected);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<ChameleonClothingComponent, EmpPulseEvent>(OnEmpPulse);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMapInit(EntityUid uid, ChameleonClothingComponent component, MapInitEvent args)
|
private void OnMapInit(EntityUid uid, ChameleonClothingComponent component, MapInitEvent args)
|
||||||
@@ -29,6 +38,21 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
|
|||||||
SetSelectedPrototype(uid, args.SelectedId, component: component);
|
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)
|
private void UpdateUi(EntityUid uid, ChameleonClothingComponent? component = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component))
|
if (!Resolve(uid, ref component))
|
||||||
@@ -65,6 +89,35 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
|
|||||||
Dirty(uid, component);
|
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)
|
private void UpdateIdentityBlocker(EntityUid uid, ChameleonClothingComponent component, EntityPrototype proto)
|
||||||
{
|
{
|
||||||
if (proto.HasComponent<IdentityBlockerComponent>(Factory))
|
if (proto.HasComponent<IdentityBlockerComponent>(Factory))
|
||||||
|
|||||||
@@ -3,27 +3,26 @@ using Content.Shared.Inventory;
|
|||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
namespace Content.Shared.Clothing.Components;
|
namespace Content.Shared.Clothing.Components;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allow players to change clothing sprite to any other clothing prototype.
|
/// Allow players to change clothing sprite to any other clothing prototype.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), AutoGenerateComponentPause]
|
||||||
[Access(typeof(SharedChameleonClothingSystem))]
|
[Access(typeof(SharedChameleonClothingSystem))]
|
||||||
public sealed partial class ChameleonClothingComponent : Component
|
public sealed partial class ChameleonClothingComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Filter possible chameleon options by their slot flag.
|
/// Filter possible chameleon options by their slot flag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadOnly)]
|
|
||||||
[DataField(required: true)]
|
[DataField(required: true)]
|
||||||
public SlotFlags Slot;
|
public SlotFlags Slot;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// EntityPrototype id that chameleon item is trying to mimic.
|
/// EntityPrototype id that chameleon item is trying to mimic.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadOnly)]
|
|
||||||
[DataField(required: true), AutoNetworkedField]
|
[DataField(required: true), AutoNetworkedField]
|
||||||
public EntProtoId? Default;
|
public EntProtoId? Default;
|
||||||
|
|
||||||
@@ -38,6 +37,34 @@ public sealed partial class ChameleonClothingComponent : Component
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public string? RequireTag;
|
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]
|
[Serializable, NetSerializable]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Linq;
|
||||||
using Content.Shared.Access.Components;
|
using Content.Shared.Access.Components;
|
||||||
using Content.Shared.Clothing.Components;
|
using Content.Shared.Clothing.Components;
|
||||||
using Content.Shared.Contraband;
|
using Content.Shared.Contraband;
|
||||||
@@ -7,6 +8,7 @@ using Content.Shared.Item;
|
|||||||
using Content.Shared.Tag;
|
using Content.Shared.Tag;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Shared.Clothing.EntitySystems;
|
namespace Content.Shared.Clothing.EntitySystems;
|
||||||
@@ -20,6 +22,19 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
|
|||||||
[Dependency] private readonly SharedItemSystem _itemSystem = default!;
|
[Dependency] private readonly SharedItemSystem _itemSystem = default!;
|
||||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
[Dependency] private readonly TagSystem _tag = 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!;
|
[Dependency] protected readonly SharedUserInterfaceSystem UI = default!;
|
||||||
|
|
||||||
private static readonly ProtoId<TagPrototype> WhitelistChameleonTag = "WhitelistChameleon";
|
private static readonly ProtoId<TagPrototype> WhitelistChameleonTag = "WhitelistChameleon";
|
||||||
@@ -30,6 +45,14 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
|
|||||||
SubscribeLocalEvent<ChameleonClothingComponent, GotEquippedEvent>(OnGotEquipped);
|
SubscribeLocalEvent<ChameleonClothingComponent, GotEquippedEvent>(OnGotEquipped);
|
||||||
SubscribeLocalEvent<ChameleonClothingComponent, GotUnequippedEvent>(OnGotUnequipped);
|
SubscribeLocalEvent<ChameleonClothingComponent, GotUnequippedEvent>(OnGotUnequipped);
|
||||||
SubscribeLocalEvent<ChameleonClothingComponent, GetVerbsEvent<InteractionVerb>>(OnVerb);
|
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)
|
private void OnGotEquipped(EntityUid uid, ChameleonClothingComponent component, GotEquippedEvent args)
|
||||||
@@ -139,4 +162,55 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
|
|||||||
|
|
||||||
return true;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user