/// <summary> /// Allows a user to pick up entities by clicking them, or pick up all entities in a certain radius /// around a click. /// </summary> /// <returns></returns> private async void AfterInteract(EntityUid uid, ServerStorageComponent storageComp, AfterInteractEvent eventArgs) { if (!eventArgs.CanReach) { return; } if (storageComp.CancelToken != null) { storageComp.CancelToken.Cancel(); storageComp.CancelToken = null; return; } // Pick up all entities in a radius around the clicked location. // The last half of the if is because carpets exist and this is terrible if (storageComp.AreaInsert && (eventArgs.Target == null || !HasComp <ItemComponent>(eventArgs.Target.Value))) { var validStorables = new List <EntityUid>(); foreach (var entity in _entityLookupSystem.GetEntitiesInRange(eventArgs.ClickLocation, storageComp.AreaInsertRadius, LookupFlags.None)) { if (entity == eventArgs.User || !HasComp <ItemComponent>(entity) || !_interactionSystem.InRangeUnobstructed(eventArgs.User, entity)) { continue; } validStorables.Add(entity); } //If there's only one then let's be generous if (validStorables.Count > 1) { storageComp.CancelToken = new CancellationTokenSource(); var doAfterArgs = new DoAfterEventArgs(eventArgs.User, 0.2f * validStorables.Count, storageComp.CancelToken.Token, uid) { BreakOnStun = true, BreakOnDamage = true, BreakOnUserMove = true, NeedHand = true, }; await _doAfterSystem.WaitDoAfter(doAfterArgs); } // TODO: Make it use the event DoAfter var successfullyInserted = new List <EntityUid>(); var successfullyInsertedPositions = new List <EntityCoordinates>(); foreach (var entity in validStorables) { // Check again, situation may have changed for some entities, but we'll still pick up any that are valid if (_containerSystem.IsEntityInContainer(entity) || entity == eventArgs.User || !HasComp <ItemComponent>(entity)) { continue; } if (TryComp <TransformComponent>(uid, out var transformOwner) && TryComp <TransformComponent>(entity, out var transformEnt)) { var position = EntityCoordinates.FromMap(transformOwner.Parent?.Owner ?? uid, transformEnt.MapPosition); if (PlayerInsertEntityInWorld(uid, eventArgs.User, entity, storageComp)) { successfullyInserted.Add(entity); successfullyInsertedPositions.Add(position); } } } // If we picked up atleast one thing, play a sound and do a cool animation! if (successfullyInserted.Count > 0) { if (storageComp.StorageInsertSound is not null) { SoundSystem.Play(storageComp.StorageInsertSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid, AudioParams.Default); } RaiseNetworkEvent(new AnimateInsertingEntitiesEvent(uid, successfullyInserted, successfullyInsertedPositions)); } return; } // Pick up the clicked entity else if (storageComp.QuickInsert) { if (eventArgs.Target is not { Valid : true } target) { return; } if (_containerSystem.IsEntityInContainer(target) || target == eventArgs.User || !HasComp <ItemComponent>(target)) { return; } if (TryComp <TransformComponent>(uid, out var transformOwner) && TryComp <TransformComponent>(target, out var transformEnt)) { var parent = transformOwner.ParentUid; var position = EntityCoordinates.FromMap( parent.IsValid() ? parent : uid, transformEnt.MapPosition); if (PlayerInsertEntityInWorld(uid, eventArgs.User, target, storageComp)) { RaiseNetworkEvent(new AnimateInsertingEntitiesEvent(uid, new List <EntityUid> { target }, new List <EntityCoordinates> { position })); } } } return; }
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}"); } } } }