/// <summary> /// Checks if the user can vault the dragged entity onto the the target /// </summary> /// <param name="user">The user that wants to vault the entity</param> /// <param name="dragged">The entity that is being vaulted</param> /// <param name="target">The object that is being vaulted onto</param> /// <param name="reason">The reason why it cant be dropped</param> /// <returns></returns> private bool CanVault(EntityUid user, EntityUid dragged, EntityUid target, out string reason) { if (!EntitySystem.Get <ActionBlockerSystem>().CanInteract(user)) { reason = Loc.GetString("comp-climbable-cant-interact"); return(false); } if (!_entities.HasComponent <ClimbingComponent>(dragged)) { reason = Loc.GetString("comp-climbable-cant-climb"); return(false); } bool Ignored(EntityUid entity) => entity == target || entity == user || entity == dragged; if (!user.InRangeUnobstructed(target, Range, predicate: Ignored) || !user.InRangeUnobstructed(dragged, Range, predicate: Ignored)) { reason = Loc.GetString("comp-climbable-cant-reach"); return(false); } reason = string.Empty; return(true); }
/// <summary> /// Checks if the user can vault the target /// </summary> /// <param name="user">The entity that wants to vault</param> /// <param name="target">The object that is being vaulted</param> /// <param name="reason">The reason why it cant be dropped</param> /// <returns></returns> private bool CanVault(EntityUid user, EntityUid target, out string reason) { if (!EntitySystem.Get <ActionBlockerSystem>().CanInteract(user)) { reason = Loc.GetString("comp-climbable-cant-interact"); return(false); } if (!_entities.HasComponent <ClimbingComponent>(user) || !_entities.TryGetComponent(user, out SharedBodyComponent? body)) { reason = Loc.GetString("comp-climbable-cant-climb"); return(false); } if (!body.HasPartOfType(BodyPartType.Leg) || !body.HasPartOfType(BodyPartType.Foot)) { reason = Loc.GetString("comp-climbable-cant-climb"); return(false); } if (!user.InRangeUnobstructed(target, Range)) { reason = Loc.GetString("comp-climbable-cant-reach"); return(false); } reason = string.Empty; return(true); }
public override Outcome Execute(float frameTime) { if (!_target.TryGetContainer(out var container)) { return(Outcome.Success); } if (!_owner.InRangeUnobstructed(container, popup: true)) { return(Outcome.Failed); } if (!IoCManager.Resolve <IEntityManager>().TryGetComponent(container.Owner, out EntityStorageComponent? storageComponent) || storageComponent.IsWeldedShut) { return(Outcome.Failed); } if (!storageComponent.Open) { var activateArgs = new ActivateEventArgs(_owner, _target); storageComponent.Activate(activateArgs); } var blackboard = UtilityAiHelpers.GetBlackboard(_owner); blackboard?.GetState <LastOpenedStorageState>().SetValue(container.Owner); return(Outcome.Success); }
public async void TryPryTile(EntityUid user, EntityCoordinates clickLocation) { if (!_entMan.TryGetComponent <ToolComponent?>(Owner, out var tool) && _toolComponentNeeded) { return; } if (!_mapManager.TryGetGrid(clickLocation.GetGridId(_entMan), out var mapGrid)) { return; } var tile = mapGrid.GetTileRef(clickLocation); var coordinates = mapGrid.GridTileToLocal(tile.GridIndices); if (!user.InRangeUnobstructed(coordinates, popup: false)) { return; } var tileDef = (ContentTileDefinition)_tileDefinitionManager[tile.Tile.TypeId]; if (!tileDef.CanCrowbar) { return; } if (_toolComponentNeeded && !await EntitySystem.Get <ToolSystem>().UseTool(Owner, user, null, 0f, 0f, _qualityNeeded, toolComponent: tool)) { return; } coordinates.PryTile(_entMan, _mapManager); }
public override Outcome Execute(float frameTime) { var targetTransform = _entMan.GetComponent <TransformComponent>(_useTarget); if (targetTransform.GridID != _entMan.GetComponent <TransformComponent>(_owner).GridID) { return(Outcome.Failed); } if (!_owner.InRangeUnobstructed(_useTarget, popup: true)) { return(Outcome.Failed); } if (_entMan.TryGetComponent(_owner, out CombatModeComponent? combatModeComponent)) { combatModeComponent.IsInCombatMode = false; } // Click on da thing var interactionSystem = EntitySystem.Get <InteractionSystem>(); interactionSystem.AiUseInteraction(_owner, targetTransform.Coordinates, _useTarget); return(Outcome.Success); }
private bool IsInDetailsRange(EntityUid examiner, EntityUid entity) { // check if the mob is in ciritcal or dead if (EntityManager.TryGetComponent(examiner, out MobStateComponent mobState) && mobState.IsIncapacitated()) { return(false); } if (entity.TryGetContainerMan(out var man) && man.Owner == examiner) { return(true); } return(examiner.InRangeUnobstructed(entity, ExamineDetailsRange, ignoreInsideBlocker: true) && examiner.IsInSameOrNoContainer(entity)); }
public override Outcome Execute(float frameTime) { var entMan = IoCManager.Resolve <IEntityManager>(); if (entMan.Deleted(_target) || !entMan.HasComponent <SharedItemComponent>(_target) || _target.IsInContainer() || !_owner.InRangeUnobstructed(_target, popup: true)) { return(Outcome.Failed); } if (!entMan.TryGetComponent(_owner, out HandsComponent? handsComponent)) { return(Outcome.Failed); } var emptyHands = false; foreach (var hand in handsComponent.ActivePriorityEnumerable()) { if (handsComponent.GetItem(hand) == null) { if (handsComponent.ActiveHand != hand) { handsComponent.ActiveHand = hand; } emptyHands = true; break; } } if (!emptyHands) { return(Outcome.Failed); } var interactionSystem = EntitySystem.Get <InteractionSystem>(); interactionSystem.InteractHand(_owner, _target); return(Outcome.Success); }
/// <summary> /// Whether the table exists, and the player can interact with it. /// </summary> /// <param name="playerEntity">The player entity to check.</param> /// <param name="table">The table entity to check.</param> protected bool CanSeeTable(EntityUid playerEntity, EntityUid?table) { if (table == null) { return(false); } if (EntityManager.GetComponent <TransformComponent>(table.Value).Parent?.Owner is not { } parent) { return(false); } if (!EntityManager.HasComponent <MapComponent>(parent) && !EntityManager.HasComponent <IMapGridComponent>(parent)) { return(false); } return(playerEntity.InRangeUnobstructed(table.Value) && _actionBlockerSystem.CanInteract(playerEntity)); }
private bool TryUseUtensil(EntityUid user, EntityUid target, UtensilComponent component) { if (!EntityManager.TryGetComponent(target, out FoodComponent food)) { return(false); } //Prevents food usage with a wrong utensil if ((food.Utensil & component.Types) == 0) { _popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", food.Owner), ("utensil", component.Owner)), user, Filter.Entities(user)); return(false); } if (!user.InRangeUnobstructed(target, popup: true)) { return(false); } return(_foodSystem.TryUseFood(target, user)); }
public void DoAttack(EntityUid user, EntityCoordinates coordinates, bool wideAttack, EntityUid targetUid = default) { if (!ValidateInteractAndFace(user, coordinates)) { return; } if (!_actionBlockerSystem.CanAttack(user)) { return; } if (!wideAttack) { // Check if interacted entity is in the same container, the direct child, or direct parent of the user. if (targetUid != default && !user.IsInSameOrParentContainer(targetUid) && !CanAccessViaStorage(user, targetUid)) { Logger.WarningS("system.interaction", $"User entity named {EntityManager.GetComponent<MetaDataComponent>(user).EntityName} clicked on object {EntityManager.GetComponent<MetaDataComponent>(targetUid).EntityName} that isn't the parent, child, or in the same container"); return; } // TODO: Replace with body attack range when we get something like arm length or telekinesis or something. if (!user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true)) { return; } } // Verify user has a hand, and find what object they are currently holding in their active hand if (EntityManager.TryGetComponent <HandsComponent?>(user, out var hands)) { if (hands.GetActiveHand?.Owner is { Valid : true } item) { if (wideAttack) { var ev = new WideAttackEvent(item, user, coordinates); RaiseLocalEvent(item, ev, false); if (ev.Handled) { _adminLogSystem.Add(LogType.AttackArmedWide, LogImpact.Medium, $"{user} wide attacked with {item} at {coordinates}"); return; } } else { var ev = new ClickAttackEvent(item, user, coordinates, targetUid); RaiseLocalEvent(item, ev, false); if (ev.Handled) { if (targetUid != default) { _adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium, $"{user} attacked {targetUid} with {item} at {coordinates}"); } else { _adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium, $"{user} attacked with {item} at {coordinates}"); } return; } } } else if (!wideAttack && targetUid != default && _entityManager.HasComponent <ItemComponent>(targetUid)) { // We pick up items if our hand is empty, even if we're in combat mode. InteractHand(user, targetUid); return; } }
/// <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 async void UserInteraction(EntityUid user, EntityCoordinates coordinates, EntityUid target, bool altInteract = false) { // TODO COMBAT Consider using alt-interact for advanced combat? maybe alt-interact disarms? if (!altInteract && EntityManager.TryGetComponent(user, out CombatModeComponent? combatMode) && combatMode.IsInCombatMode) { DoAttack(user, coordinates, false, target); return; } if (!ValidateInteractAndFace(user, coordinates)) { return; } if (!_actionBlockerSystem.CanInteract(user)) { return; } // Check if interacted entity is in the same container, the direct child, or direct parent of the user. // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI) if (target != default && !user.IsInSameOrParentContainer(target) && !CanAccessViaStorage(user, target)) { Logger.WarningS("system.interaction", $"User entity named {EntityManager.GetComponent<MetaDataComponent>(user).EntityName} clicked on object {EntityManager.GetComponent<MetaDataComponent>(target).EntityName} that isn't the parent, child, or in the same container"); return; } // Verify user has a hand, and find what object they are currently holding in their active hand if (!EntityManager.TryGetComponent <HandsComponent?>(user, out var hands)) { return; } var item = hands.GetActiveHand?.Owner; // TODO: Replace with body interaction range when we get something like arm length or telekinesis or something. var inRangeUnobstructed = user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true); if (target == default || !inRangeUnobstructed) { if (item == null) { return; } if (!await InteractUsingRanged(user, item.Value, target, coordinates, inRangeUnobstructed) && !inRangeUnobstructed) { var message = Loc.GetString("interaction-system-user-interaction-cannot-reach"); user.PopupMessage(message); } return; } else { // We are close to the nearby object. if (altInteract) { // Perform alternative interactions, using context menu verbs. AltInteract(user, target); } else if (item != null && item != target) { // We are performing a standard interaction with an item, and the target isn't the same as the item // currently in our hand. We will use the item in our hand on the nearby object via InteractUsing await InteractUsing(user, item.Value, target, coordinates); } else if (item == null) { // Since our hand is empty we will use InteractHand/Activate InteractHand(user, target); } } }
public override void DoAttack(EntityUid user, EntityCoordinates coordinates, bool wideAttack, EntityUid?target = null) { // TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction. if (!ValidateInteractAndFace(user, coordinates)) { return; } if (!_actionBlockerSystem.CanAttack(user)) { return; } if (!wideAttack) { // Check if interacted entity is in the same container, the direct child, or direct parent of the user. if (target != null && !Deleted(target.Value) && !user.IsInSameOrParentContainer(target.Value) && !CanAccessViaStorage(user, target.Value)) { Logger.WarningS("system.interaction", $"User entity {ToPrettyString(user):user} clicked on object {ToPrettyString(target.Value):target} that isn't the parent, child, or in the same container"); return; } // TODO: Replace with body attack range when we get something like arm length or telekinesis or something. if (!user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true)) { return; } } // Verify user has a hand, and find what object they are currently holding in their active hand if (TryComp(user, out HandsComponent? hands)) { var item = hands.GetActiveHand?.Owner; if (item != null && !Deleted(item.Value)) { if (wideAttack) { var ev = new WideAttackEvent(item.Value, user, coordinates); RaiseLocalEvent(item.Value, ev, false); if (ev.Handled) { _adminLogSystem.Add(LogType.AttackArmedWide, LogImpact.Medium, $"{ToPrettyString(user):user} wide attacked with {ToPrettyString(item.Value):used} at {coordinates}"); return; } } else { var ev = new ClickAttackEvent(item.Value, user, coordinates, target); RaiseLocalEvent(item.Value, ev, false); if (ev.Handled) { if (target != null) { _adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium, $"{ToPrettyString(user):user} attacked {ToPrettyString(target.Value):target} with {ToPrettyString(item.Value):used} at {coordinates}"); } else { _adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium, $"{ToPrettyString(user):user} attacked with {ToPrettyString(item.Value):used} at {coordinates}"); } return; } } } else if (!wideAttack && target != null && HasComp <SharedItemComponent>(target.Value)) { // We pick up items if our hand is empty, even if we're in combat mode. InteractHand(user, target.Value); return; } } // TODO: Make this saner? // Attempt to do unarmed combat. We don't check for handled just because at this point it doesn't matter. if (wideAttack) { var ev = new WideAttackEvent(user, user, coordinates); RaiseLocalEvent(user, ev, false); if (ev.Handled) { _adminLogSystem.Add(LogType.AttackUnarmedWide, $"{ToPrettyString(user):user} wide attacked at {coordinates}"); } } else { var ev = new ClickAttackEvent(user, user, coordinates, target); RaiseLocalEvent(user, ev, false); if (ev.Handled) { if (target != null) { _adminLogSystem.Add(LogType.AttackUnarmedClick, LogImpact.Medium, $"{ToPrettyString(user):user} attacked {ToPrettyString(target.Value):target} at {coordinates}"); } else { _adminLogSystem.Add(LogType.AttackUnarmedClick, LogImpact.Medium, $"{ToPrettyString(user):user} attacked at {coordinates}"); } } } }
/// <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 async void UserInteraction(EntityUid user, EntityCoordinates coordinates, EntityUid?target, bool altInteract = false) { 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 (!_actionBlockerSystem.CanInteract(user)) { return; } // Check if interacted entity is in the same container, the direct child, or direct parent of the user. // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI) if (target != null && !user.IsInSameOrParentContainer(target.Value) && !CanAccessViaStorage(user, target.Value)) { return; } // Verify user has a hand, and find what object they are currently holding in their active hand if (!TryComp(user, out SharedHandsComponent? hands)) { return; } // TODO: Replace with body interaction range when we get something like arm length or telekinesis or something. var inRangeUnobstructed = user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true); if (target == null || !inRangeUnobstructed) { if (!hands.TryGetActiveHeldEntity(out var heldEntity)) { return; } if (!await InteractUsingRanged(user, heldEntity.Value, target, coordinates, inRangeUnobstructed) && !inRangeUnobstructed) { var message = Loc.GetString("interaction-system-user-interaction-cannot-reach"); user.PopupMessage(message); } return; } // We are close to the nearby object. if (altInteract) { // Perform alternative interactions, using context menu verbs. AltInteract(user, target.Value); } else if (!hands.TryGetActiveHeldEntity(out var heldEntity)) { // Since our hand is empty we will use InteractHand/Activate InteractHand(user, target.Value); } else if (heldEntity != target) { await InteractUsing(user, heldEntity.Value, target.Value, coordinates); } }
/// <summary> /// Tries to eat some food /// </summary> /// <param name="uid">Food entity.</param> /// <param name="user">Feeding initiator.</param> /// <returns>True if an interaction occurred (i.e., food was consumed, or a pop-up message was created)</returns> public bool TryUseFood(EntityUid uid, EntityUid user, FoodComponent?food = null) { if (!Resolve(uid, ref food)) { return(false); } // if currently being used to force-feed, cancel that action. if (food.CancelToken != null) { food.CancelToken.Cancel(); food.CancelToken = null; return(true); } if (uid == user || //Suppresses self-eating EntityManager.TryGetComponent <MobStateComponent>(uid, out var mobState) && mobState.IsAlive()) // Suppresses eating alive mobs { return(false); } if (!_solutionContainerSystem.TryGetSolution(uid, food.SolutionName, out var solution)) { return(false); } if (food.UsesRemaining <= 0) { _popupSystem.PopupEntity(Loc.GetString("food-system-try-use-food-is-empty", ("entity", uid)), user, Filter.Entities(user)); DeleteAndSpawnTrash(food, user); return(true); } if (!EntityManager.TryGetComponent(user, out SharedBodyComponent ? body) || !_bodySystem.TryGetComponentsOnMechanisms <StomachComponent>(user, out var stomachs, body)) { return(false); } if (IsMouthBlocked(user, user)) { return(true); } var usedUtensils = new List <UtensilComponent>(); if (!TryGetRequiredUtensils(user, food, out var utensils)) { return(true); } if (!user.InRangeUnobstructed(uid, popup: true)) { return(true); } var transferAmount = food.TransferAmount != null?FixedPoint2.Min((FixedPoint2)food.TransferAmount, solution.CurrentVolume) : solution.CurrentVolume; var split = _solutionContainerSystem.SplitSolution(uid, solution, transferAmount); var firstStomach = stomachs.FirstOrNull( stomach => _stomachSystem.CanTransferSolution((stomach.Comp).Owner, split)); if (firstStomach == null) { _solutionContainerSystem.TryAddSolution(uid, solution, split); _popupSystem.PopupEntity(Loc.GetString("food-system-you-cannot-eat-any-more"), user, Filter.Entities(user)); return(true); } // TODO: Account for partial transfer. split.DoEntityReaction(user, ReactionMethod.Ingestion); _stomachSystem.TryTransferSolution((firstStomach.Value.Comp).Owner, split, firstStomach.Value.Comp); SoundSystem.Play(Filter.Pvs(user), food.UseSound.GetSound(), user, AudioParams.Default.WithVolume(-1f)); _popupSystem.PopupEntity(Loc.GetString(food.EatMessage, ("food", food.Owner)), user, Filter.Entities(user)); // Try to break all used utensils foreach (var utensil in usedUtensils) { _utensilSystem.TryBreak((utensil).Owner, user); } if (food.UsesRemaining > 0) { return(true); } if (string.IsNullOrEmpty(food.TrashPrototype)) { EntityManager.QueueDeleteEntity((food).Owner); } else { DeleteAndSpawnTrash(food, user); } return(true); }