using System.Linq; using Content.Shared.Actions; using Content.Shared.Actions.Components; using Content.Shared.Charges.Components; using Content.Shared.Charges.Systems; using Content.Shared.Interaction; using Robust.Shared.Random; using Robust.Shared.Timing; namespace Content.Server.Actions; /// /// This System handled interactions for the . /// public sealed class ActionOnInteractSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly ActionContainerSystem _actionContainer = default!; [Dependency] private readonly SharedChargesSystem _charges = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnAfterInteract); SubscribeLocalEvent(OnMapInit); } private void OnMapInit(EntityUid uid, ActionOnInteractComponent component, MapInitEvent args) { if (component.Actions == null) return; var comp = EnsureComp(uid); foreach (var id in component.Actions) { _actionContainer.AddAction(uid, id, comp); } } private void OnActivate(EntityUid uid, ActionOnInteractComponent component, ActivateInWorldEvent args) { if (args.Handled || !args.Complex) return; if (component.ActionEntities is not {} actionEnts) { if (!TryComp(uid, out var actionsContainerComponent)) return; actionEnts = actionsContainerComponent.Container.ContainedEntities.ToList(); } var options = GetValidActions(actionEnts); if (options.Count == 0) return; if (!TryUseCharge((uid, component))) return; // not predicted as this is in server due to random // TODO: use predicted random and move to shared? var (actId, action, comp) = _random.Pick(options); _actions.PerformAction(args.User, (actId, action), predicted: false); args.Handled = true; } private void OnAfterInteract(EntityUid uid, ActionOnInteractComponent component, AfterInteractEvent args) { if (args.Handled) return; if (component.ActionEntities is not {} actionEnts) { if (!TryComp(uid, out var actionsContainerComponent)) return; actionEnts = actionsContainerComponent.Container.ContainedEntities.ToList(); } // First, try entity target actions if (args.Target is {} target) { var entOptions = GetValidActions(actionEnts, args.CanReach); for (var i = entOptions.Count - 1; i >= 0; i--) { var action = entOptions[i]; if (!_actions.ValidateEntityTarget(args.User, target, (action, action.Comp2))) entOptions.RemoveAt(i); } if (entOptions.Count > 0) { if (!TryUseCharge((uid, component))) return; var (actionId, action, _) = _random.Pick(entOptions); _actions.SetEventTarget(actionId, target); _actions.PerformAction(args.User, (actionId, action), predicted: false); args.Handled = true; return; } } // else: try world target actions var options = GetValidActions(component.ActionEntities, args.CanReach); for (var i = options.Count - 1; i >= 0; i--) { var action = options[i]; if (!_actions.ValidateWorldTarget(args.User, args.ClickLocation, (action, action.Comp2))) options.RemoveAt(i); } if (options.Count == 0) return; if (!TryUseCharge((uid, component))) return; var (actId, comp, world) = _random.Pick(options); if (world.Event is {} worldEv) { worldEv.Target = args.ClickLocation; worldEv.Entity = HasComp(actId) ? args.Target : null; } _actions.PerformAction(args.User, (actId, comp), world.Event, predicted: false); args.Handled = true; } private List> GetValidActions(List? actions, bool canReach = true) where T: Component { var valid = new List>(); if (actions == null) return valid; foreach (var id in actions) { if (_actions.GetAction(id) is not {} action || !TryComp(id, out var comp) || !_actions.ValidAction(action, canReach)) { continue; } valid.Add((id, action, comp)); } return valid; } private bool TryUseCharge(Entity ent) { if (!ent.Comp.RequiresCharge) return true; Entity charges = ent.Owner; if (_charges.IsEmpty(charges)) return false; _charges.TryUseCharge(charges); return true; } }