diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index f05e445588..26a22fa8b8 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -259,12 +259,6 @@ namespace Content.Client.Actions if (action.ClientExclusive) { - if (instantAction.Event != null) - { - instantAction.Event.Performer = user; - instantAction.Event.Action = actionId; - } - PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime); } else diff --git a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs index d67c5cbcd6..1dffeb8d2d 100644 --- a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs +++ b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs @@ -219,8 +219,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged /// The action the event belongs to. /// - public EntityUid Action; + public Entity Action; + + /// + /// Should we toggle the action entity? + /// + public bool Toggle; } diff --git a/Content.Shared/Actions/ActionGrantComponent.cs b/Content.Shared/Actions/ActionGrantComponent.cs new file mode 100644 index 0000000000..94c3a0bbd1 --- /dev/null +++ b/Content.Shared/Actions/ActionGrantComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Actions; + +/// +/// Grants actions on MapInit and removes them on shutdown +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(ActionGrantSystem))] +public sealed partial class ActionGrantComponent : Component +{ + [DataField(required: true), AutoNetworkedField, AlwaysPushInheritance] + public List Actions = new(); + + [DataField, AutoNetworkedField] + public List ActionEntities = new(); +} diff --git a/Content.Shared/Actions/ActionGrantSystem.cs b/Content.Shared/Actions/ActionGrantSystem.cs new file mode 100644 index 0000000000..f73ecf8a46 --- /dev/null +++ b/Content.Shared/Actions/ActionGrantSystem.cs @@ -0,0 +1,48 @@ +namespace Content.Shared.Actions; + +/// +/// +/// +public sealed class ActionGrantSystem : EntitySystem +{ + [Dependency] private readonly SharedActionsSystem _actions = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnItemGet); + } + + private void OnItemGet(Entity ent, ref GetItemActionsEvent args) + { + if (!TryComp(ent.Owner, out ActionGrantComponent? grant)) + return; + + foreach (var action in grant.ActionEntities) + { + args.AddAction(action); + } + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + foreach (var action in ent.Comp.Actions) + { + EntityUid? actionEnt = null; + _actions.AddAction(ent.Owner, ref actionEnt, action); + + if (actionEnt != null) + ent.Comp.ActionEntities.Add(actionEnt.Value); + } + } + + private void OnShutdown(Entity ent, ref ComponentShutdown args) + { + foreach (var actionEnt in ent.Comp.ActionEntities) + { + _actions.RemoveAction(ent.Owner, actionEnt); + } + } +} diff --git a/Content.Shared/Actions/Events/ActionComponentChangeEvent.cs b/Content.Shared/Actions/Events/ActionComponentChangeEvent.cs new file mode 100644 index 0000000000..c9c4db145d --- /dev/null +++ b/Content.Shared/Actions/Events/ActionComponentChangeEvent.cs @@ -0,0 +1,27 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Actions.Events; + +/// +/// Adds / removes the component upon action. +/// +[Virtual] +public partial class ActionComponentChangeEvent : InstantActionEvent +{ + [DataField(required: true)] + public ComponentRegistry Components = new(); +} + +/// +/// Similar to except raises an event to attempt to relay it. +/// +public sealed partial class RelayedActionComponentChangeEvent : ActionComponentChangeEvent +{ + +} + +[ByRefEvent] +public record struct AttemptRelayActionComponentChangeEvent +{ + public EntityUid? Target; +} diff --git a/Content.Shared/Actions/ItemActionGrantComponent.cs b/Content.Shared/Actions/ItemActionGrantComponent.cs new file mode 100644 index 0000000000..d1769b51a2 --- /dev/null +++ b/Content.Shared/Actions/ItemActionGrantComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Actions; + +/// +/// Works in tandem with by granting those actions to the equipper entity. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(ActionGrantSystem))] +public sealed partial class ItemActionGrantComponent : Component +{ + [DataField(required: true), AutoNetworkedField, AlwaysPushInheritance] + public List Actions = new(); +} diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index fb9415096f..2756345428 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -11,7 +11,6 @@ using Content.Shared.Mind; using Content.Shared.Rejuvenate; using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; -using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Timing; @@ -45,6 +44,8 @@ public abstract class SharedActionsSystem : EntitySystem SubscribeLocalEvent(OnActionShutdown); SubscribeLocalEvent(OnActionShutdown); + SubscribeLocalEvent(OnActionCompChange); + SubscribeLocalEvent(OnRelayActionCompChange); SubscribeLocalEvent(OnDidEquip); SubscribeLocalEvent(OnHandEquipped); SubscribeLocalEvent(OnDidUnequip); @@ -490,12 +491,6 @@ public abstract class SharedActionsSystem : EntitySystem break; } - if (performEvent != null) - { - performEvent.Performer = user; - performEvent.Action = actionEnt; - } - // All checks passed. Perform the action! PerformAction(user, component, actionEnt, action, performEvent, curTime); } @@ -641,6 +636,8 @@ public abstract class SharedActionsSystem : EntitySystem // This here is required because of client-side prediction (RaisePredictiveEvent results in event re-use). actionEvent.Handled = false; var target = performer; + actionEvent.Performer = performer; + actionEvent.Action = (actionId, action); if (!action.RaiseOnUser && action.Container != null && !HasComp(action.Container)) target = action.Container.Value; @@ -653,10 +650,14 @@ public abstract class SharedActionsSystem : EntitySystem return; // no interaction occurred. // play sound, reduce charges, start cooldown, and mark as dirty (if required). + if (actionEvent?.Toggle == true) + { + action.Toggled = !action.Toggled; + } - _audio.PlayPredicted(action.Sound, performer,predicted ? performer : null); + _audio.PlayPredicted(action.Sound, performer, predicted ? performer : null); - var dirty = toggledBefore == action.Toggled; + var dirty = toggledBefore != action.Toggled; if (action.Charges != null) { @@ -673,10 +674,11 @@ public abstract class SharedActionsSystem : EntitySystem action.Cooldown = (curTime, curTime + action.UseDelay.Value); } - Dirty(actionId, action); - - if (dirty && component != null) - Dirty(performer, component); + if (dirty) + { + Dirty(actionId, action); + UpdateAction(actionId, action); + } var ev = new ActionPerformedEvent(performer); RaiseLocalEvent(actionId, ref ev); @@ -975,6 +977,47 @@ public abstract class SharedActionsSystem : EntitySystem #endregion + private void OnRelayActionCompChange(Entity ent, ref RelayedActionComponentChangeEvent args) + { + if (args.Handled) + return; + + var ev = new AttemptRelayActionComponentChangeEvent(); + RaiseLocalEvent(ent.Owner, ref ev); + var target = ev.Target ?? ent.Owner; + + args.Handled = true; + args.Toggle = true; + + if (!args.Action.Comp.Toggled) + { + EntityManager.AddComponents(target, args.Components); + } + else + { + EntityManager.RemoveComponents(target, args.Components); + } + } + + private void OnActionCompChange(Entity ent, ref ActionComponentChangeEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + args.Toggle = true; + var target = ent.Owner; + + if (!args.Action.Comp.Toggled) + { + EntityManager.AddComponents(target, args.Components); + } + else + { + EntityManager.RemoveComponents(target, args.Components); + } + } + #region EquipHandlers private void OnDidEquip(EntityUid uid, ActionsComponent component, DidEquipEvent args) {