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!;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user