using Content.Shared.Actions; using Content.Shared.Actions.Components; using Content.Shared.Actions.Events; using Content.Shared.Interaction; using Robust.Shared.Serialization; namespace Content.Shared._RMC14.Actions; public sealed class RMCActionsSystem : EntitySystem { [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!; private EntityQuery _actionSharedCooldownQuery; public override void Initialize() { _actionSharedCooldownQuery = GetEntityQuery(); SubscribeLocalEvent(OnSharedCooldownPerformed); SubscribeLocalEvent(OnCooldownUse); SubscribeLocalEvent(OnInRangeUnobstructedUseAttempt); SubscribeLocalEvent(OnReducedUseDelayEvent); SubscribeAllEvent(OnMissedTargetAction); } private void OnSharedCooldownPerformed(Entity ent, ref ActionPerformedEvent args) { if (ent.Comp.OnPerform) ActivateSharedCooldown((ent, ent), args.Performer); } public void ActivateSharedCooldown(Entity action, EntityUid performer) { if (!Resolve(action, ref action.Comp, false)) return; if (action.Comp.Cooldown == TimeSpan.Zero) return; foreach (var (actionId, _) in _actions.GetActions(performer)) { if (!_actionSharedCooldownQuery.TryComp(actionId, out var shared)) continue; // Same ID or primary ID found in subset of other action's ids if ((shared.Id != null && shared.Id == action.Comp.Id) || (action.Comp.Id != null && shared.Ids.Contains(action.Comp.Id.Value))) _actions.SetIfBiggerCooldown(actionId, action.Comp.Cooldown); } } /// /// Enable all events that have a shared cooldown with the provided action /// public void EnableSharedCooldownEvents(Entity action, EntityUid performer) { SetStatusOfSharedCooldownEvents(action, performer, true); } /// /// Disable all events that have a shared cooldown with the provided action /// public void DisableSharedCooldownEvents(Entity action, EntityUid performer) { SetStatusOfSharedCooldownEvents(action, performer, false); } /// /// Sets the enabled status of all events that have a shared cooldown with the provided action /// private void SetStatusOfSharedCooldownEvents(Entity action, EntityUid performer, bool newStatus) { if (!Resolve(action, ref action.Comp, false)) return; if (action.Comp.Cooldown == TimeSpan.Zero) return; foreach (var (actionId, comp) in _actions.GetActions(performer)) { if (!_actionSharedCooldownQuery.TryComp(actionId, out var shared)) continue; // Same ID or primary ID found in subset of other action's ids if (!(shared.Id != null && shared.Id == action.Comp.Id || action.Comp.Id != null && (shared.Ids.Contains(action.Comp.Id.Value) || shared.ActiveIds.Contains(action.Comp.Id.Value)))) { continue; } _actions.SetEnabled((actionId, comp), newStatus); } } private void OnReducedUseDelayEvent(EntityUid uid, ActionComponent component, ActionReducedUseDelayEvent args) { if (!TryComp(uid, out ActionReducedUseDelayComponent? comp)) return; if (args.Amount < 0 || args.Amount > 1) return; comp.UseDelayReduction = args.Amount; if (TryComp(uid, out ActionSharedCooldownComponent? shared)) { comp.UseDelayBase ??= shared.Cooldown; RefreshSharedUseDelay((uid, comp), shared); return; } // Should be fine to only set this once as the base use delay should remain constant comp.UseDelayBase ??= component.UseDelay; RefreshUseDelay((uid, comp)); } private void RefreshUseDelay(Entity ent) { if (ent.Comp.UseDelayBase is not { } delayBase) return; var reduction = ent.Comp.UseDelayReduction.Double(); var delayNew = delayBase.Multiply(1 - reduction); _actions.SetUseDelay(ent.Owner, delayNew); } private void RefreshSharedUseDelay(Entity ent, ActionSharedCooldownComponent shared) { if (ent.Comp.UseDelayBase is not { } delayBase) return; var reduction = ent.Comp.UseDelayReduction.Double(); var delayNew = delayBase.Multiply(1 - reduction); shared.Cooldown = delayNew; } private void OnCooldownUse(Entity ent, ref RMCActionUseEvent args) { _actions.SetIfBiggerCooldown(ent.Owner, ent.Comp.Cooldown); } private void OnMissedTargetAction(RMCMissedTargetActionEvent args) { var action = GetEntity(args.Action); if (!TryComp(action, out RMCCooldownOnMissComponent? cooldown)) return; _actions.SetIfBiggerCooldown(action, cooldown.MissCooldown); } private void OnInRangeUnobstructedUseAttempt(Entity ent, ref RMCActionUseAttemptEvent args) { if (args.Cancelled) return; if (args.Target is not { } target) return; if (!_interaction.InRangeUnobstructed(ent.Owner, target, ent.Comp.Range)) args.Cancelled = true; } public bool CanUseActionPopup(EntityUid user, EntityUid action, EntityUid? target = null) { var ev = new RMCActionUseAttemptEvent(user, target); RaiseLocalEvent(action, ref ev); return !ev.Cancelled; } private void ActionUsed(EntityUid user, EntityUid action) { var ev = new RMCActionUseEvent(user); RaiseLocalEvent(action, ref ev); } public bool TryUseAction(EntityUid user, EntityUid action, EntityUid target) { if (!CanUseActionPopup(user, action, target)) return false; ActionUsed(user, action); return true; } public bool TryUseAction(InstantActionEvent action) { if (!CanUseActionPopup(action.Performer, action.Action)) return false; ActionUsed(action.Performer, action.Action); return true; } public bool TryUseAction(EntityTargetActionEvent action) { if (!CanUseActionPopup(action.Performer, action.Action, action.Target)) return false; ActionUsed(action.Performer, action.Action); return true; } public bool TryUseAction(WorldTargetActionEvent action) { if (!CanUseActionPopup(action.Performer, action.Action)) return false; ActionUsed(action.Performer, action.Action); return true; } public IEnumerable> GetActionsWithEvent(EntityUid user) where T : BaseActionEvent { foreach (var action in _actions.GetActions(user)) { if (_actions.GetEvent(action) is T) yield return action; } } } [Serializable, NetSerializable] public sealed class RMCMissedTargetActionEvent : EntityEventArgs { public readonly NetEntity Action; public RMCMissedTargetActionEvent(NetEntity actionId) { Action = actionId; } }