/// <summary> /// Raises a number of events in order to get all verbs of the given type(s) defined in local systems. This /// does not request verbs from the server. /// </summary> public SortedSet <Verb> GetLocalVerbs(EntityUid target, EntityUid user, List <Type> types, bool force = false) { SortedSet <Verb> verbs = new(); // accessibility checks bool canAccess = false; if (force || target == user) { canAccess = true; } else if (EntityManager.EntityExists(target) && _interactionSystem.InRangeUnobstructed(user, target)) { if (ContainerSystem.IsInSameOrParentContainer(user, target)) { canAccess = true; } else { // the item might be in a backpack that the user has open canAccess = _interactionSystem.CanAccessViaStorage(user, target); } } // A large number of verbs need to check action blockers. Instead of repeatedly having each system individually // call ActionBlocker checks, just cache it for the verb request. var canInteract = force || _actionBlockerSystem.CanInteract(user, target); EntityUid? @using = null; if (TryComp(user, out SharedHandsComponent? hands) && (force || _actionBlockerSystem.CanUseHeldEntity(user))) { @using = hands.ActiveHandEntity; // Check whether the "Held" entity is a virtual pull entity. If yes, set that as the entity being "Used". // This allows you to do things like buckle a dragged person onto a surgery table, without click-dragging // their sprite. if (TryComp(@using, out HandVirtualItemComponent? pull)) { @using = pull.BlockingEntity; } } if (types.Contains(typeof(InteractionVerb))) { var verbEvent = new GetVerbsEvent <InteractionVerb>(user, target, @using, hands, canInteract, canAccess); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } if (types.Contains(typeof(UtilityVerb)) && @using != null && @using != target) { var verbEvent = new GetVerbsEvent <UtilityVerb>(user, target, @using, hands, canInteract, canAccess); RaiseLocalEvent(@using.Value, verbEvent, true); // directed at used, not at target verbs.UnionWith(verbEvent.Verbs); } if (types.Contains(typeof(InnateVerb))) { var verbEvent = new GetVerbsEvent <InnateVerb>(user, target, @using, hands, canInteract, canAccess); RaiseLocalEvent(user, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } if (types.Contains(typeof(AlternativeVerb))) { var verbEvent = new GetVerbsEvent <AlternativeVerb>(user, target, @using, hands, canInteract, canAccess); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } if (types.Contains(typeof(ActivationVerb))) { var verbEvent = new GetVerbsEvent <ActivationVerb>(user, target, @using, hands, canInteract, canAccess); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } if (types.Contains(typeof(ExamineVerb))) { var verbEvent = new GetVerbsEvent <ExamineVerb>(user, target, @using, hands, canInteract, canAccess); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } // generic verbs if (types.Contains(typeof(Verb))) { var verbEvent = new GetVerbsEvent <Verb>(user, target, @using, hands, canInteract, canAccess); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } return(verbs); }
/// <summary> /// Resolves user interactions with objects. /// </summary> /// <remarks> /// Checks Whether combat mode is enabled and whether the user can actually interact with the given entity. /// </remarks> /// <param name="altInteract">Whether to use default or alternative interactions (usually as a result of /// alt+clicking). If combat mode is enabled, the alternative action is to perform the default non-combat /// interaction. Having an item in the active hand also disables alternative interactions.</param> public void UserInteraction( EntityUid user, EntityCoordinates coordinates, EntityUid?target, bool altInteract = false, bool checkCanInteract = true, bool checkAccess = true, bool checkCanUse = true) { if (target != null && Deleted(target.Value)) { return; } // TODO COMBAT Consider using alt-interact for advanced combat? maybe alt-interact disarms? if (!altInteract && TryComp(user, out SharedCombatModeComponent? combatMode) && combatMode.IsInCombatMode) { DoAttack(user, coordinates, false, target); return; } if (!ValidateInteractAndFace(user, coordinates)) { return; } if (altInteract && target != null) { // Perform alternative interactions, using context menu verbs. // These perform their own range, can-interact, and accessibility checks. AltInteract(user, target.Value); return; } if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target)) { return; } // Check if interacted entity is in the same container, the direct child, or direct parent of the user. // Also checks if the item is accessible via some storage UI (e.g., open backpack) if (checkAccess && target != null && !ContainerSystem.IsInSameOrParentContainer(user, target.Value) && !CanAccessViaStorage(user, target.Value)) { return; } // Does the user have hands? Hand?hand; if (!TryComp(user, out SharedHandsComponent? hands) || hands.ActiveHand == null) { return; } var inRangeUnobstructed = target == null ? !checkAccess || InRangeUnobstructed(user, coordinates) : !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities // empty-hand interactions if (hands.ActiveHandEntity is not EntityUid held) { if (inRangeUnobstructed && target != null) { InteractHand(user, target.Value); } return; } // Can the user use the held entity? if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user)) { return; } if (target == held) { UseInHandInteraction(user, target.Value, checkCanUse: false, checkCanInteract: false); return; } if (inRangeUnobstructed && target != null) { InteractUsing( user, held, target.Value, coordinates, checkCanInteract: false, checkCanUse: false); return; } InteractUsingRanged( user, held, target, coordinates, inRangeUnobstructed); }