/// <summary> /// Get all of the entities in an area for displaying on the context menu. /// </summary> public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true)] out List <EntityUid>?result) { result = null; if (_stateManager.CurrentState is not GameScreenBase gameScreenBase) { return(false); } var player = _playerManager.LocalPlayer?.ControlledEntity; if (player == null) { return(false); } // If FOV drawing is disabled, we will modify the visibility option to ignore visiblity checks. var visibility = _eyeManager.CurrentEye.DrawFov ? Visibility : Visibility | MenuVisibility.NoFov; // Do we have to do FoV checks? if ((visibility & MenuVisibility.NoFov) == 0) { var entitiesUnderMouse = gameScreenBase.GetEntitiesUnderPosition(targetPos); bool Predicate(EntityUid e) => e == player || entitiesUnderMouse.Contains(e); if (!_examineSystem.CanExamine(player.Value, targetPos, Predicate)) { return(false); } } // Get entities var entities = _entityLookup.GetEntitiesInRange(targetPos.MapId, targetPos.Position, EntityMenuLookupSize) .ToList(); if (entities.Count == 0) { return(false); } if (visibility == MenuVisibility.All) { result = entities; return(true); } // remove any entities in containers if ((visibility & MenuVisibility.InContainer) == 0) { foreach (var entity in entities.ToList()) { if (!ContainerSystem.IsInSameOrTransparentContainer(player.Value, entity)) { entities.Remove(entity); } } } // remove any invisible entities if ((visibility & MenuVisibility.Invisible) == 0) { foreach (var entity in entities.ToList()) { if (!EntityManager.TryGetComponent(entity, out ISpriteComponent? spriteComponent) || !spriteComponent.Visible) { entities.Remove(entity); continue; } if (_tagSystem.HasTag(entity, "HideContextMenu")) { entities.Remove(entity); } } } // Remove any entities that do not have LOS if ((visibility & MenuVisibility.NoFov) == 0) { var playerPos = EntityManager.GetComponent <TransformComponent>(player.Value).MapPosition; foreach (var entity in entities.ToList()) { if (!ExamineSystemShared.InRangeUnOccluded( playerPos, EntityManager.GetComponent <TransformComponent>(entity).MapPosition, ExamineSystemShared.ExamineRange, null)) { entities.Remove(entity); } } } if (entities.Count == 0) { return(false); } result = entities; return(true); }
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, target)) { 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) && !ContainerSystem.IsInSameOrParentContainer(user, 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. var unobstructed = (target == null) ? InRangeUnobstructed(user, coordinates) : InRangeUnobstructed(user, target.Value); if (!unobstructed) { 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.GetActiveHandItem?.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}"); } } } }
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; } // Check general interaction blocking. if (!_actionBlockerSystem.CanInteract(user, target)) { return; } // Check combat-specific action blocking. if (!_actionBlockerSystem.CanAttack(user, target)) { 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) && !ContainerSystem.IsInSameOrParentContainer(user, 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. var unobstructed = (target == null) ? InRangeUnobstructed(user, coordinates) : InRangeUnobstructed(user, target.Value); if (!unobstructed) { return; } } else if (ContainerSystem.IsEntityInContainer(user)) { // No wide attacking while in containers (holos, lockers, etc). // Can't think of a valid case where you would want this. 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.ActiveHandEntity; if (!Deleted(item)) { var meleeVee = new MeleeAttackAttemptEvent(); RaiseLocalEvent(item.Value, ref meleeVee, true); if (meleeVee.Cancelled) { return; } if (wideAttack) { var ev = new WideAttackEvent(item.Value, user, coordinates); RaiseLocalEvent(item.Value, ev, false); if (ev.Handled) { _adminLogger.Add(LogType.AttackArmedWide, LogImpact.Low, $"{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) { _adminLogger.Add(LogType.AttackArmedClick, LogImpact.Low, $"{ToPrettyString(user):user} attacked {ToPrettyString(target.Value):target} with {ToPrettyString(item.Value):used} at {coordinates}"); } else { _adminLogger.Add(LogType.AttackArmedClick, LogImpact.Low, $"{ToPrettyString(user):user} attacked with {ToPrettyString(item.Value):used} at {coordinates}"); } return; } } } else if (!wideAttack && target != null && HasComp <ItemComponent>(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. var used = user; if (_inventory.TryGetSlotEntity(user, "gloves", out var gloves) && HasComp <MeleeWeaponComponent>(gloves)) { used = (EntityUid)gloves; } if (wideAttack) { var ev = new WideAttackEvent(used, user, coordinates); RaiseLocalEvent(used, ev, false); if (ev.Handled) { _adminLogger.Add(LogType.AttackUnarmedWide, LogImpact.Low, $"{ToPrettyString(user):user} wide attacked at {coordinates}"); } } else { var ev = new ClickAttackEvent(used, user, coordinates, target); RaiseLocalEvent(used, ev, false); if (ev.Handled) { if (target != null) { _adminLogger.Add(LogType.AttackUnarmedClick, LogImpact.Low, $"{ToPrettyString(user):user} attacked {ToPrettyString(target.Value):target} at {coordinates}"); } else { _adminLogger.Add(LogType.AttackUnarmedClick, LogImpact.Low, $"{ToPrettyString(user):user} attacked at {coordinates}"); } } } }