private async void HandleToolInteraction(AfterInteractMessage msg) { if (msg.Handled) { return; } // You can only construct/deconstruct things within reach if (!msg.CanReach) { return; } var targetEnt = msg.Attacked; var handEnt = msg.ItemInHand; // A tool has to interact with an entity. if (targetEnt is null || handEnt is null) { return; } if (!handEnt.InRangeUnobstructed(targetEnt, ignoreInsideBlocker: true)) { return; } // Cannot deconstruct an entity with no prototype. var targetPrototype = targetEnt.MetaData.EntityPrototype; if (targetPrototype is null) { return; } // the target entity is in the process of being constructed/deconstructed if (msg.Attacked.TryGetComponent <ConstructionComponent>(out var constructComp)) { var result = await TryConstructEntity(constructComp, handEnt, msg.User); // TryConstructEntity may delete the existing entity msg.Handled = result; } else // try to start the deconstruction process { // A tool was not used on the entity. if (!handEnt.TryGetComponent <IToolComponent>(out var toolComp)) { return; } // no known recipe for entity if (!_craftRecipes.TryGetValue(targetPrototype.ID, out var prototype)) { return; } // there is a recipe, but it can't be deconstructed. var lastStep = prototype.Stages[^ 1].Backward;
/// <summary> /// Checks that the user and target of a /// <see cref="AfterInteractMessage"/> are within a certain distance /// without any entity that matches the collision mask obstructing them. /// If the <paramref name="range"/> is zero or negative, /// this method will only check if nothing obstructs the entity and component. /// </summary> /// <param name="args">The event args to use.</param> /// <param name="range"> /// Maximum distance between the two entity and set of map coordinates. /// </param> /// <param name="collisionMask">The mask to check for collisions.</param> /// <param name="predicate"> /// A predicate to check whether to ignore an entity or not. /// If it returns true, it will be ignored. /// </param> /// <param name="ignoreInsideBlocker"> /// If true and both the user and target are inside /// the obstruction, ignores the obstruction and considers the interaction /// unobstructed. /// Therefore, setting this to true makes this check more permissive, /// such as allowing an interaction to occur inside something impassable /// (like a wall). The default, false, makes the check more restrictive. /// </param> /// <param name="popup"> /// Whether or not to popup a feedback message on the user entity for /// it to see. /// </param> /// <returns> /// True if the two points are within a given range without being obstructed. /// </returns> public bool InRangeUnobstructed( AfterInteractMessage args, float range = InteractionRange, CollisionGroup collisionMask = CollisionGroup.Impassable, Ignored?predicate = null, bool ignoreInsideBlocker = false, bool popup = false) { var user = args.User; var target = args.Attacked; predicate ??= e => e == user; MapCoordinates otherPosition; if (target == null) { otherPosition = args.ClickLocation.ToMap(EntityManager); } else { otherPosition = target.Transform.MapPosition; predicate += e => e == target; } return(InRangeUnobstructed(user, otherPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup)); }
public static bool InRangeUnobstructed( this AfterInteractMessage args, float range = InteractionRange, CollisionGroup collisionMask = CollisionGroup.Impassable, Ignored?predicate = null, bool ignoreInsideBlocker = false, bool popup = false) { return(SharedInteractionSystem.InRangeUnobstructed(args, range, collisionMask, predicate, ignoreInsideBlocker, popup)); }
private async void HandleAfterInteract(EntityUid uid, SpawnAfterInteractComponent component, AfterInteractMessage args) { if (string.IsNullOrEmpty(component.Prototype)) { return; } if (!_mapManager.TryGetGrid(args.ClickLocation.GetGridId(EntityManager), out var grid)) { return; } if (!grid.TryGetTileRef(args.ClickLocation, out var tileRef)) { return; } bool IsTileClear() { return(tileRef.Tile.IsEmpty == false && args.User.InRangeUnobstructed(args.ClickLocation, popup: true)); } if (!IsTileClear()) { return; } if (component.DoAfterTime > 0 && TryGet <DoAfterSystem>(out var doAfterSystem)) { var doAfterArgs = new DoAfterEventArgs(args.User, component.DoAfterTime) { BreakOnUserMove = true, BreakOnStun = true, PostCheck = IsTileClear, }; var result = await doAfterSystem.DoAfter(doAfterArgs); if (result != DoAfterStatus.Finished) { return; } } if (component.Deleted || component.Owner.Deleted) { return; } StackComponent?stack = null; if (component.RemoveOnInteract && component.Owner.TryGetComponent(out stack) && !stack.Use(1)) { return; } EntityManager.SpawnEntity(component.Prototype, args.ClickLocation.SnapToGrid(grid, SnapGridOffset.Center)); if (component.RemoveOnInteract && stack == null && !component.Owner.Deleted) { component.Owner.Delete(); } return; }
private void HandleAfterInteract(EntityUid uid, LightReplacerComponent component, AfterInteractMessage eventArgs) { // standard interaction checks if (!ActionBlockerSystem.CanUse(eventArgs.User)) { return; } if (!eventArgs.CanReach) { return; } // behaviour will depends on target type if (eventArgs.Attacked != null) { // replace broken light in fixture? if (eventArgs.Attacked.TryGetComponent(out PoweredLightComponent? fixture)) { component.TryReplaceBulb(fixture, eventArgs.User); } // add new bulb to light replacer container? else if (eventArgs.Attacked.TryGetComponent(out LightBulbComponent? bulb)) { component.TryInsertBulb(bulb, eventArgs.User, true); } } }