diff --git a/Content.Shared/Actions/ActionEvents.cs b/Content.Shared/Actions/ActionEvents.cs index a861ec1255..c99151ecef 100644 --- a/Content.Shared/Actions/ActionEvents.cs +++ b/Content.Shared/Actions/ActionEvents.cs @@ -20,6 +20,11 @@ public sealed class GetItemActionsEvent : EntityEventArgs { public SortedSet Actions = new(); + /// + /// User equipping the item. + /// + public EntityUid User; + /// /// Slot flags for the inventory slot that this item got equipped to. Null if not in a slot (i.e., if equipped to hands). /// @@ -30,8 +35,9 @@ public sealed class GetItemActionsEvent : EntityEventArgs /// public bool InHands => SlotFlags == null; - public GetItemActionsEvent(SlotFlags? slotFlags = null) + public GetItemActionsEvent(EntityUid user, SlotFlags? slotFlags = null) { + User = user; SlotFlags = slotFlags; } } diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 8dce478eb2..9fc1b83748 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -410,7 +410,7 @@ public abstract class SharedActionsSystem : EntitySystem #region EquipHandlers private void OnDidEquip(EntityUid uid, ActionsComponent component, DidEquipEvent args) { - var ev = new GetItemActionsEvent(args.SlotFlags); + var ev = new GetItemActionsEvent(args.Equipee, args.SlotFlags); RaiseLocalEvent(args.Equipment, ev); if (ev.Actions.Count == 0) @@ -421,7 +421,7 @@ public abstract class SharedActionsSystem : EntitySystem private void OnHandEquipped(EntityUid uid, ActionsComponent component, DidEquipHandEvent args) { - var ev = new GetItemActionsEvent(); + var ev = new GetItemActionsEvent(args.User); RaiseLocalEvent(args.Equipped, ev); if (ev.Actions.Count == 0) diff --git a/Content.Shared/Clothing/Components/StealthClothingComponent.cs b/Content.Shared/Clothing/Components/StealthClothingComponent.cs new file mode 100644 index 0000000000..2571d89b1f --- /dev/null +++ b/Content.Shared/Clothing/Components/StealthClothingComponent.cs @@ -0,0 +1,43 @@ +using Content.Shared.Actions; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Clothing.EntitySystems; +using Robust.Shared.GameStates; + +namespace Content.Shared.Clothing.Components; + +/// +/// Adds StealthComponent to the user when enabled, either by an action or the system's SetEnabled method. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(StealthClothingSystem))] +public sealed partial class StealthClothingComponent : Component +{ + /// + /// Whether stealth effect is enabled. + /// + [DataField("enabled"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public bool Enabled; + + /// + /// Number added to MinVisibility when stealthed, to make the user not fully invisible. + /// + [DataField("visibility"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public float Visibility; + + /// + /// The action for enabling and disabling stealth. + /// + [DataField("toggleAction")] + public InstantAction ToggleAction = new() + { + Event = new ToggleStealthEvent() + }; +} + +/// +/// When stealth is enabled, disables it. +/// When it is disabled, raises before enabling. +/// Put any checks in a handler for that event to cancel it. +/// +public sealed partial class ToggleStealthEvent : InstantActionEvent +{ +} diff --git a/Content.Shared/Clothing/EntitySystems/StealthClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/StealthClothingSystem.cs new file mode 100644 index 0000000000..dc8de4385b --- /dev/null +++ b/Content.Shared/Clothing/EntitySystems/StealthClothingSystem.cs @@ -0,0 +1,137 @@ +using Content.Shared.Actions; +using Content.Shared.Clothing.Components; +using Content.Shared.Inventory.Events; +using Content.Shared.Stealth; +using Content.Shared.Stealth.Components; +using Robust.Shared.GameStates; + +namespace Content.Shared.Clothing.EntitySystems; + +/// +/// Handles the toggle action and disables stealth when clothing is unequipped. +/// +public sealed class StealthClothingSystem : EntitySystem +{ + [Dependency] private readonly SharedStealthSystem _stealth = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetItemActions); + SubscribeLocalEvent(OnToggleStealth); + SubscribeLocalEvent(OnHandleState); + SubscribeLocalEvent(OnUnequipped); + } + + /// + /// Sets the clothing's stealth effect for the user. + /// + /// True if it was changed, false otherwise + public bool SetEnabled(EntityUid uid, EntityUid user, bool enabled, StealthClothingComponent? comp = null) + { + if (!Resolve(uid, ref comp) || comp.Enabled == enabled) + return false; + + // TODO remove this when clothing unequip on delete is less sus + // prevent debug assert when ending round and its disabled + if (MetaData(user).EntityLifeStage >= EntityLifeStage.Terminating) + return false; + + comp.Enabled = enabled; + Dirty(uid, comp); + + var stealth = EnsureComp(user); + // slightly visible, but doesn't change when moving so it's ok + var visibility = enabled ? stealth.MinVisibility + comp.Visibility : stealth.MaxVisibility; + _stealth.SetVisibility(user, visibility, stealth); + _stealth.SetEnabled(user, enabled, stealth); + return true; + } + + /// + /// Raise then add the toggle action if it was not cancelled. + /// + private void OnGetItemActions(EntityUid uid, StealthClothingComponent comp, GetItemActionsEvent args) + { + var ev = new AddStealthActionEvent(args.User); + RaiseLocalEvent(uid, ev); + if (ev.Cancelled) + return; + + args.Actions.Add(comp.ToggleAction); + } + + /// + /// Raises if enabling. + /// + private void OnToggleStealth(EntityUid uid, StealthClothingComponent comp, ToggleStealthEvent args) + { + args.Handled = true; + var user = args.Performer; + if (comp.Enabled) + { + SetEnabled(uid, user, false, comp); + return; + } + + var ev = new AttemptStealthEvent(user); + RaiseLocalEvent(uid, ev); + if (ev.Cancelled) + return; + + SetEnabled(uid, user, true, comp); + } + + /// + /// Calls when server sends new state. + /// + private void OnHandleState(EntityUid uid, StealthClothingComponent comp, ref AfterAutoHandleStateEvent args) + { + // SetEnabled checks if it is the same, so change it to before state was received from the server + var enabled = comp.Enabled; + comp.Enabled = !enabled; + var user = Transform(uid).ParentUid; + SetEnabled(uid, user, enabled, comp); + } + + /// + /// Force unstealths the user, doesnt remove StealthComponent since other things might use it + /// + private void OnUnequipped(EntityUid uid, StealthClothingComponent comp, GotUnequippedEvent args) + { + SetEnabled(uid, args.Equipee, false, comp); + } +} + +/// +/// Raised on the stealth clothing when attempting to add an action. +/// +public class AddStealthActionEvent : CancellableEntityEventArgs +{ + /// + /// User that equipped the stealth clothing. + /// + public EntityUid User; + + public AddStealthActionEvent(EntityUid user) + { + User = user; + } +} + +/// +/// Raised on the stealth clothing when the user is attemping to enable it. +/// +public class AttemptStealthEvent : CancellableEntityEventArgs +{ + /// + /// User that is attempting to enable the stealth clothing. + /// + public EntityUid User; + + public AttemptStealthEvent(EntityUid user) + { + User = user; + } +} diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml index 652bc60d3c..0c7017fe0b 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml @@ -101,6 +101,20 @@ sprite: Clothing/OuterClothing/Suits/spaceninja.rsi - type: Clothing sprite: Clothing/OuterClothing/Suits/spaceninja.rsi + - type: StealthClothing + visibility: 0.3 + toggleAction: + # have to plan (un)cloaking ahead of time + useDelay: 5 + name: action-name-toggle-phase-cloak + description: action-desc-toggle-phase-cloak + priority: -9 + event: !type:ToggleStealthEvent + - type: PressureProtection + highPressureMultiplier: 0.6 + lowPressureMultiplier: 1000 + - type: TemperatureProtection + coefficient: 0.01 - type: Armor modifiers: coefficients: