protected override void OnCanDragDropOn(EntityUid uid, SharedClimbableComponent component, CanDragDropOnEvent args) { base.OnCanDragDropOn(uid, component, args); if (!args.CanDrop) { return; } var user = args.User; var target = args.Target; var dragged = args.Dragged; bool Ignored(EntityUid entity) => entity == target || entity == user || entity == dragged; args.CanDrop = _interactionSystem.InRangeUnobstructed(user, target, component.Range, predicate: Ignored) && _interactionSystem.InRangeUnobstructed(user, dragged, component.Range, predicate: Ignored); args.Handled = true; }
/// <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; }
// TODO ECS BUCKLE/STRAP These 'Strap' verbs are an incestuous mess of buckle component and strap component // functions. Whenever these are fully ECSed, maybe do it in a way that allows for these verbs to be handled in // a sensible manner in a single system? private void AddStrapVerbs(EntityUid uid, StrapComponent component, GetVerbsEvent <InteractionVerb> args) { if (args.Hands == null || !args.CanAccess || !args.CanInteract || !component.Enabled) { return; } // Note that for whatever bloody reason, buckle component has its own interaction range. Additionally, this // range can be set per-component, so we have to check a modified InRangeUnobstructed for every verb. // Add unstrap verbs for every strapped entity. foreach (var entity in component.BuckledEntities) { var buckledComp = EntityManager.GetComponent <BuckleComponent>(entity); if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target, range: buckledComp.Range)) { continue; } InteractionVerb verb = new() { Act = () => buckledComp.TryUnbuckle(args.User), Category = VerbCategory.Unbuckle }; if (entity == args.User) { verb.Text = Loc.GetString("verb-self-target-pronoun"); } else { verb.Text = EntityManager.GetComponent <MetaDataComponent>(entity).EntityName; } // In the event that you have more than once entity with the same name strapped to the same object, // these two verbs will be identical according to Verb.CompareTo, and only one with actually be added to // the verb list. However this should rarely ever be a problem. If it ever is, it could be fixed by // appending an integer to verb.Text to distinguish the verbs. args.Verbs.Add(verb); } // Add a verb to buckle the user. if (EntityManager.TryGetComponent <BuckleComponent?>(args.User, out var buckle) && buckle.BuckledTo != component && args.User != component.Owner && component.HasSpace(buckle) && _interactionSystem.InRangeUnobstructed(args.User, args.Target, range: buckle.Range)) { InteractionVerb verb = new() { Act = () => buckle.TryBuckle(args.User, args.Target), Category = VerbCategory.Buckle, Text = Loc.GetString("verb-self-target-pronoun") }; args.Verbs.Add(verb); } // If the user is currently holding/pulling an entity that can be buckled, add a verb for that. if (args.Using is { Valid : true } @using&& EntityManager.TryGetComponent <BuckleComponent?>(@using, out var usingBuckle) && component.HasSpace(usingBuckle) && _interactionSystem.InRangeUnobstructed(@using, args.Target, range : usingBuckle.Range)) { // Check that the entity is unobstructed from the target (ignoring the user). bool Ignored(EntityUid entity) => entity == args.User || entity == args.Target || entity == @using; if (!_interactionSystem.InRangeUnobstructed(@using, args.Target, usingBuckle.Range, predicate: Ignored)) { return; } InteractionVerb verb = new() { Act = () => usingBuckle.TryBuckle(args.User, args.Target), Category = VerbCategory.Buckle, Text = EntityManager.GetComponent <MetaDataComponent>(@using).EntityName, // just a held object, the user is probably just trying to sit down. // If the used entity is a person being pulled, prioritize this verb. Conversely, if it is Priority = EntityManager.HasComponent <ActorComponent>(@using) ? 1 : -1 }; args.Verbs.Add(verb); } }