Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chameleon clothes + EMP behaviour #30924

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
55 changes: 0 additions & 55 deletions Content.Client/Clothing/Systems/ChameleonClothingSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,6 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IComponentFactory _factory = 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();
Expand Down Expand Up @@ -62,49 +52,4 @@ protected override void UpdateSprite(EntityUid uid, EntityPrototype proto)
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);
}
}
}
}
64 changes: 64 additions & 0 deletions Content.Server/Clothing/Systems/ChameleonClothingSystem.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
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.Utility;
using Robust.Shared.Random;


namespace Content.Server.Clothing.Systems;

Expand All @@ -12,12 +19,16 @@
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IComponentFactory _factory = 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)
Expand All @@ -30,6 +41,30 @@
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);
SetSelectedPrototype(uid, pick, component: component);

args.Affected = true;
args.Disabled = true;
}

private void TryOpenUi(EntityUid uid, EntityUid user, ChameleonClothingComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
if (!TryComp(user, out ActorComponent? actor))

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / Test Packaging

The type or namespace name 'ActorComponent' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / Test Packaging

The type 'ActorComponent?' cannot be used as type parameter 'T' in the generic type or method 'EntitySystem.TryComp<T>(EntityUid, out T?)'. The nullable type 'ActorComponent?' does not satisfy the constraint of 'Robust.Shared.GameObjects.IComponent'. Nullable types can not satisfy any interface constraints.

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / Test Packaging

The type or namespace name 'ActorComponent' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / Test Packaging

The type 'ActorComponent?' cannot be used as type parameter 'T' in the generic type or method 'EntitySystem.TryComp<T>(EntityUid, out T?)'. The nullable type 'ActorComponent?' does not satisfy the constraint of 'Robust.Shared.GameObjects.IComponent'. Nullable types can not satisfy any interface constraints.

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The type or namespace name 'ActorComponent' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The type 'ActorComponent?' cannot be used as type parameter 'T' in the generic type or method 'EntitySystem.TryComp<T>(EntityUid, out T?)'. The nullable type 'ActorComponent?' does not satisfy the constraint of 'Robust.Shared.GameObjects.IComponent'. Nullable types can not satisfy any interface constraints.

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The type or namespace name 'ActorComponent' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The type 'ActorComponent?' cannot be used as type parameter 'T' in the generic type or method 'EntitySystem.TryComp<T>(EntityUid, out T?)'. The nullable type 'ActorComponent?' does not satisfy the constraint of 'Robust.Shared.GameObjects.IComponent'. Nullable types can not satisfy any interface constraints.

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / YAML Linter

The type or namespace name 'ActorComponent' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / YAML Linter

The type 'ActorComponent?' cannot be used as type parameter 'T' in the generic type or method 'EntitySystem.TryComp<T>(EntityUid, out T?)'. The nullable type 'ActorComponent?' does not satisfy the constraint of 'Robust.Shared.GameObjects.IComponent'. Nullable types can not satisfy any interface constraints.

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / YAML Linter

The type or namespace name 'ActorComponent' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / YAML Linter

The type 'ActorComponent?' cannot be used as type parameter 'T' in the generic type or method 'EntitySystem.TryComp<T>(EntityUid, out T?)'. The nullable type 'ActorComponent?' does not satisfy the constraint of 'Robust.Shared.GameObjects.IComponent'. Nullable types can not satisfy any interface constraints.

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The type or namespace name 'ActorComponent' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The type 'ActorComponent?' cannot be used as type parameter 'T' in the generic type or method 'EntitySystem.TryComp<T>(EntityUid, out T?)'. The nullable type 'ActorComponent?' does not satisfy the constraint of 'Robust.Shared.GameObjects.IComponent'. Nullable types can not satisfy any interface constraints.

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The type or namespace name 'ActorComponent' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 63 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The type 'ActorComponent?' cannot be used as type parameter 'T' in the generic type or method 'EntitySystem.TryComp<T>(EntityUid, out T?)'. The nullable type 'ActorComponent?' does not satisfy the constraint of 'Robust.Shared.GameObjects.IComponent'. Nullable types can not satisfy any interface constraints.
return;
_uiSystem.TryToggleUi(uid, ChameleonUiKey.Key, actor.PlayerSession);

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / Test Packaging

The name '_uiSystem' does not exist in the current context

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / Test Packaging

'ActorComponent?' does not contain a definition for 'PlayerSession' and no accessible extension method 'PlayerSession' accepting a first argument of type 'ActorComponent?' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / Test Packaging

The name '_uiSystem' does not exist in the current context

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / Test Packaging

'ActorComponent?' does not contain a definition for 'PlayerSession' and no accessible extension method 'PlayerSession' accepting a first argument of type 'ActorComponent?' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The name '_uiSystem' does not exist in the current context

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

'ActorComponent?' does not contain a definition for 'PlayerSession' and no accessible extension method 'PlayerSession' accepting a first argument of type 'ActorComponent?' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The name '_uiSystem' does not exist in the current context

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

'ActorComponent?' does not contain a definition for 'PlayerSession' and no accessible extension method 'PlayerSession' accepting a first argument of type 'ActorComponent?' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / YAML Linter

The name '_uiSystem' does not exist in the current context

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / YAML Linter

'ActorComponent?' does not contain a definition for 'PlayerSession' and no accessible extension method 'PlayerSession' accepting a first argument of type 'ActorComponent?' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / YAML Linter

The name '_uiSystem' does not exist in the current context

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / YAML Linter

'ActorComponent?' does not contain a definition for 'PlayerSession' and no accessible extension method 'PlayerSession' accepting a first argument of type 'ActorComponent?' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The name '_uiSystem' does not exist in the current context

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

'ActorComponent?' does not contain a definition for 'PlayerSession' and no accessible extension method 'PlayerSession' accepting a first argument of type 'ActorComponent?' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

The name '_uiSystem' does not exist in the current context

Check failure on line 65 in Content.Server/Clothing/Systems/ChameleonClothingSystem.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

'ActorComponent?' does not contain a definition for 'PlayerSession' and no accessible extension method 'PlayerSession' accepting a first argument of type 'ActorComponent?' could be found (are you missing a using directive or an assembly reference?)
}

private void UpdateUi(EntityUid uid, ChameleonClothingComponent? component = null)
{
if (!Resolve(uid, ref component))
Expand Down Expand Up @@ -66,6 +101,35 @@
Dirty(uid, component);
}

/// <summary>
/// Get a random prototype for a given slot.
/// </summary>
public string GetRandomValidPrototype(SlotFlags slot)
{
return _random.Pick(GetValidTargets(slot).ToList());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't quite work! I was super confused for a while but there is an issue with PDAs. When a PDA gets zapped, it picks a random item from that slot. However, a LOT of items in the ID card slot slots IDs, not PDAs so there is a very good chance it doesn't update every time.

You'll have to do something with the RequireTag I think (See #30514 for more info)

}

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 var emp, 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);
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))
Expand Down
30 changes: 29 additions & 1 deletion Content.Shared/Clothing/Components/ChameleonClothingComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ 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
{
Expand Down Expand Up @@ -38,6 +38,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>
[ViewVariables, 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>
[ViewVariables, DataField]
public bool EmpContinuous = true;

/// <summary>
/// When should next EMP-caused appearance change happen?
/// </summary>
[AutoPausedField, DataField]
public TimeSpan NextEmpChange = TimeSpan.Zero;
}

[Serializable, NetSerializable]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using Content.Shared.Access.Components;
using Content.Shared.Clothing.Components;
using Content.Shared.Contraband;
Expand All @@ -7,6 +8,8 @@
using Content.Shared.Tag;
using Content.Shared.Verbs;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Timing;
using Robust.Shared.Utility;

namespace Content.Shared.Clothing.EntitySystems;
Expand All @@ -21,14 +24,33 @@ 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();

public readonly Dictionary<SlotFlags, List<string>> ValidVariants = new();
[Dependency] protected readonly SharedUserInterfaceSystem UI = default!;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChameleonClothingComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<ChameleonClothingComponent, GotUnequippedEvent>(OnGotUnequipped);

SubscribeLocalEvent<ChameleonClothingComponent, PrototypesReloadedEventArgs>(OnPrototypeReload);
PrepareAllVariants();
}

private void OnPrototypeReload(EntityUid uid, ChameleonClothingComponent component, PrototypesReloadedEventArgs args)
{
SubscribeLocalEvent<ChameleonClothingComponent, GetVerbsEvent<InteractionVerb>>(OnVerb);
PrepareAllVariants();
}

private void OnGotEquipped(EntityUid uid, ChameleonClothingComponent component, GotEquippedEvent args)
Expand Down Expand Up @@ -138,4 +160,49 @@ public bool IsValidTarget(EntityPrototype proto, SlotFlags chameleonSlot = SlotF

return true;
}

/// <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 ValidVariants.Keys)
{
if (slot.HasFlag(availableSlot))
{
set.UnionWith(ValidVariants[availableSlot]);
}
}
return set;
}

public void PrepareAllVariants()
{
ValidVariants.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 (!ValidVariants.ContainsKey(slot))
{
ValidVariants.Add(slot, new List<string>());
}
ValidVariants[slot].Add(proto.ID);
}
}
}
}
Loading