SuicideKind ISuicideAct.Suicide(EntityUid victim, IChatManager chat) { // check that victim even have head if (_entMan.TryGetComponent <SharedBodyComponent?>(victim, out var body) && body.HasPartOfType(BodyPartType.Head)) { var othersMessage = Loc.GetString("toilet-component-suicide-head-message-others", ("victim", Name: _entMan.GetComponent <MetaDataComponent>(victim).EntityName), ("owner", Name: _entMan.GetComponent <MetaDataComponent>(Owner).EntityName)); victim.PopupMessageOtherClients(othersMessage); var selfMessage = Loc.GetString("toilet-component-suicide-head-message", ("owner", Name: _entMan.GetComponent <MetaDataComponent>(Owner).EntityName)); victim.PopupMessage(selfMessage); return(SuicideKind.Asphyxiation); } else { var othersMessage = Loc.GetString("toilet-component-suicide-message-others", ("victim", Name: _entMan.GetComponent <MetaDataComponent>(victim).EntityName), ("owner", Name: _entMan.GetComponent <MetaDataComponent>(Owner).EntityName)); victim.PopupMessageOtherClients(othersMessage); var selfMessage = Loc.GetString("toilet-component-suicide-message", ("owner", Name: _entMan.GetComponent <MetaDataComponent>(Owner).EntityName)); victim.PopupMessage(selfMessage); return(SuicideKind.Blunt); } }
private void TurnOn(StunbatonComponent comp, EntityUid user) { if (comp.Activated) { return; } if (!EntityManager.TryGetComponent <SpriteComponent?>(comp.Owner, out var sprite) || !EntityManager.TryGetComponent <SharedItemComponent?>(comp.Owner, out var item)) { return; } var playerFilter = Filter.Pvs(comp.Owner); if (!_cellSystem.TryGetBatteryFromSlot(comp.Owner, out var battery)) { SoundSystem.Play(playerFilter, comp.TurnOnFailSound.GetSound(), comp.Owner, AudioHelpers.WithVariation(0.25f)); user.PopupMessage(Loc.GetString("comp-stunbaton-activated-missing-cell")); return; } if (battery.CurrentCharge < comp.EnergyPerUse) { SoundSystem.Play(playerFilter, comp.TurnOnFailSound.GetSound(), comp.Owner, AudioHelpers.WithVariation(0.25f)); user.PopupMessage(Loc.GetString("comp-stunbaton-activated-dead-cell")); return; } SoundSystem.Play(playerFilter, comp.SparksSound.GetSound(), comp.Owner, AudioHelpers.WithVariation(0.25f)); item.EquippedPrefix = "on"; sprite.LayerSetState(0, "stunbaton_on"); comp.Activated = true; }
/// <returns>The actual amount transferred.</returns> private static FixedPoint2 DoTransfer(EntityUid user, EntityUid sourceEntity, Solution source, EntityUid targetEntity, Solution target, FixedPoint2 amount) { if (source.DrainAvailable == 0) { sourceEntity.PopupMessage(user, Loc.GetString("comp-solution-transfer-is-empty", ("target", sourceEntity))); return(FixedPoint2.Zero); } if (target.AvailableVolume == 0) { targetEntity.PopupMessage(user, Loc.GetString("comp-solution-transfer-is-full", ("target", targetEntity))); return(FixedPoint2.Zero); } var actualAmount = FixedPoint2.Min(amount, FixedPoint2.Min(source.DrainAvailable, target.AvailableVolume)); var solution = EntitySystem.Get <SolutionContainerSystem>().Drain(sourceEntity, source, actualAmount); EntitySystem.Get <SolutionContainerSystem>().Refill(targetEntity, target, solution); return(actualAmount); }
private void TurnOn(StunbatonComponent comp, EntityUid user) { if (comp.Activated) { return; } var playerFilter = Filter.Pvs(comp.Owner, entityManager: EntityManager); if (!TryComp <BatteryComponent>(comp.Owner, out var battery) || battery.CurrentCharge < comp.EnergyPerUse) { SoundSystem.Play(comp.TurnOnFailSound.GetSound(), playerFilter, comp.Owner, AudioHelpers.WithVariation(0.25f)); user.PopupMessage(Loc.GetString("stunbaton-component-low-charge")); return; } if (EntityManager.TryGetComponent <AppearanceComponent>(comp.Owner, out var appearance) && EntityManager.TryGetComponent <ItemComponent>(comp.Owner, out var item)) { _item.SetHeldPrefix(comp.Owner, "on", item); appearance.SetData(ToggleVisuals.Toggled, true); } SoundSystem.Play(comp.SparksSound.GetSound(), playerFilter, comp.Owner, AudioHelpers.WithVariation(0.25f)); comp.Activated = true; }
// ECS this out!, Handleable SuicideEvent? SuicideKind ISuicideAct.Suicide(EntityUid victim, IChatManager chat) { var othersMessage = Loc.GetString("comp-kitchen-spike-suicide-other", ("victim", victim)); victim.PopupMessageOtherClients(othersMessage); var selfMessage = Loc.GetString("comp-kitchen-spike-suicide-self"); victim.PopupMessage(selfMessage); return(SuicideKind.Piercing); }
private void OnOpenTaggerUIAttempt(EntityUid uid, DisposalTaggerComponent router, ActivatableUIOpenAttemptEvent args) { if (!TryComp<HandsComponent>(args.User, out var hands)) { uid.PopupMessage(args.User, Loc.GetString("disposal-tagger-window-activate-no-hands")); return; } var activeHandEntity = hands.ActiveHandEntity; if (activeHandEntity != null) { args.Cancel(); } }
SuicideKind ISuicideAct.Suicide(EntityUid victim, IChatManager chat) { var headCount = 0; if (_entities.TryGetComponent <SharedBodyComponent?>(victim, out var body)) { var headSlots = body.GetSlotsOfType(BodyPartType.Head); foreach (var slot in headSlots) { var part = slot.Part; if (part == null || !body.TryDropPart(slot, out var dropped)) { continue; } foreach (var droppedPart in dropped.Values) { if (droppedPart.PartType != BodyPartType.Head) { continue; } _storage.Insert(droppedPart.Owner); headCount++; } } } var othersMessage = headCount > 1 ? Loc.GetString("microwave-component-suicide-multi-head-others-message", ("victim", victim)) : Loc.GetString("microwave-component-suicide-others-message", ("victim", victim)); victim.PopupMessageOtherClients(othersMessage); var selfMessage = headCount > 1 ? Loc.GetString("microwave-component-suicide-multi-head-message") : Loc.GetString("microwave-component-suicide-message"); victim.PopupMessage(selfMessage); _currentCookTimerTime = 10; ClickSound(); UIDirty = true; Wzhzhzh(); return(SuicideKind.Heat); }
/// <summary> /// If not handled, does the default suicide, which is biting your own tongue /// </summary> private static void DefaultSuicideHandler(EntityUid victim, SuicideEvent suicideEvent) { if (suicideEvent.Handled) { return; } var othersMessage = Loc.GetString("suicide-command-default-text-others", ("name", victim)); victim.PopupMessageOtherClients(othersMessage); var selfMessage = Loc.GetString("suicide-command-default-text-self"); victim.PopupMessage(selfMessage); suicideEvent.SetHandled(SuicideKind.Bloodloss); }
/// <summary> /// Pops up a message for every player around <see cref="source"/> to see, /// except for <see cref="source"/> itself. /// </summary> /// <param name="source">The entity on which to popup the message.</param> /// <param name="message">The message to show.</param> public static void PopupMessageOtherClients(this EntityUid source, string message) { var viewers = Filter.Empty() .AddPlayersByPvs(source) .Recipients; foreach (var viewer in viewers) { if (viewer.AttachedEntity is not { Valid: true } viewerEntity || source == viewerEntity || viewer.AttachedEntity == null) { continue; } source.PopupMessage(viewerEntity, message); } }
public void AddPilot(EntityUid entity, ShuttleConsoleComponent component) { if (!_blocker.CanInteract(entity) || !EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent) || component.SubscribedPilots.Contains(pilotComponent)) { return; } component.SubscribedPilots.Add(pilotComponent); if (EntityManager.TryGetComponent(entity, out ServerAlertsComponent? alertsComponent)) { alertsComponent.ShowAlert(AlertType.PilotingShuttle); } entity.PopupMessage(Loc.GetString("shuttle-pilot-start")); pilotComponent.Console = component; pilotComponent.Position = EntityManager.GetComponent <TransformComponent>(entity).Coordinates; pilotComponent.Dirty(); }
public bool CanWield(EntityUid uid, WieldableComponent component, EntityUid user, bool quiet = false) { // Do they have enough hands free? if (!EntityManager.TryGetComponent <HandsComponent>(user, out var hands)) { if (!quiet) { user.PopupMessage(Loc.GetString("wieldable-component-no-hands")); } return(false); } if (hands.CountFreeHands() < component.FreeHandsRequired) { // TODO FLUENT need function to change 'hands' to 'hand' when there's only 1 required if (!quiet) { user.PopupMessage(Loc.GetString("wieldable-component-not-enough-free-hands", ("number", component.FreeHandsRequired), ("item", uid))); } return(false); } // Is it.. actually in one of their hands? if (!_handsSystem.IsHolding(user, uid, out _, hands)) { if (!quiet) { user.PopupMessage(Loc.GetString("wieldable-component-not-in-hands", ("item", uid))); } return(false); } // Seems legit. return(true); }
public void AddPilot(EntityUid entity, ShuttleConsoleComponent component) { if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent) || component.SubscribedPilots.Contains(pilotComponent)) { return; } if (TryComp <SharedEyeComponent>(entity, out var eye)) { eye.Zoom = component.Zoom; } component.SubscribedPilots.Add(pilotComponent); _alertsSystem.ShowAlert(entity, AlertType.PilotingShuttle); entity.PopupMessage(Loc.GetString("shuttle-pilot-start")); pilotComponent.Console = component; pilotComponent.Position = EntityManager.GetComponent <TransformComponent>(entity).Coordinates; pilotComponent.Dirty(); }
/// <summary> /// Checks that an entity and a set of map coordinates 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="origin">The entity to use.</param> /// <param name="other">The map coordinates 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 <see cref="origin"/> or <see cref="other"/> 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 origin entity for /// it to see. /// </param> /// <returns> /// True if the two points are within a given range without being obstructed. /// </returns> public bool InRangeUnobstructed( EntityUid origin, MapCoordinates other, float range = InteractionRange, CollisionGroup collisionMask = CollisionGroup.Impassable, Ignored?predicate = null, bool ignoreInsideBlocker = false, bool popup = false) { var originPosition = Transform(origin).MapPosition; predicate ??= e => e == origin; var inRange = InRangeUnobstructed(originPosition, other, range, collisionMask, predicate, ignoreInsideBlocker); if (!inRange && popup) { var message = Loc.GetString("shared-interaction-system-in-range-unobstructed-cannot-reach"); origin.PopupMessage(message); } return(inRange); }
/// <summary> /// Pops up a message at the given entity's location for it alone to see. /// </summary> /// <param name="viewer">The entity that will see the message.</param> /// <param name="message">The message to be seen.</param> public static void PopupMessage(this EntityUid viewer, string message) { viewer.PopupMessage(viewer, message); }
/// <summary> /// Pops up a message at the given entity's location for everyone, /// including itself, to see. /// </summary> /// <param name="source">The entity above which to show the message.</param> /// <param name="message">The message to be seen.</param> /// <param name="playerManager"> /// The instance of player manager to use, will be resolved automatically /// if null. /// </param> /// <param name="range"> /// The range in which to search for players, defaulting to one screen. /// </param> public static void PopupMessageEveryone(this EntityUid source, string message, IPlayerManager?playerManager = null, int range = 15) { source.PopupMessage(message); source.PopupMessageOtherClients(message); }
/// <summary> /// Tries to fire a round of ammo out of the weapon. /// </summary> private void TryFire(EntityUid user, EntityCoordinates targetCoords, ServerRangedWeaponComponent gun) { if (!TryComp(gun.Owner, out ServerRangedBarrelComponent? barrel)) { return; } if (!TryComp(user, out HandsComponent? hands) || hands.ActiveHand?.HeldEntity != gun.Owner) { return; } if (!TryComp(user, out CombatModeComponent? combat) || !combat.IsInCombatMode || !_blocker.CanInteract(user, gun.Owner)) { return; } var fireAttempt = new GunFireAttemptEvent(user, gun); EntityManager.EventBus.RaiseLocalEvent(gun.Owner, fireAttempt); if (fireAttempt.Cancelled) { return; } var curTime = _gameTiming.CurTime; var span = curTime - gun.LastFireTime; if (span.TotalSeconds < 1 / barrel.FireRate) { return; } // TODO: Clumsy should be eventbus I think? gun.LastFireTime = curTime; var coordinates = Transform(gun.Owner).Coordinates; if (gun.ClumsyCheck && EntityManager.TryGetComponent <ClumsyComponent>(user, out var clumsyComponent) && ClumsyComponent.TryRollClumsy(user, gun.ClumsyExplodeChance)) { //Wound them _damageable.TryChangeDamage(user, clumsyComponent.ClumsyDamage); _stun.TryParalyze(user, TimeSpan.FromSeconds(3f), true); // Apply salt to the wound ("Honk!") SoundSystem.Play( Filter.Pvs(gun.Owner), gun.ClumsyWeaponHandlingSound.GetSound(), coordinates, AudioParams.Default.WithMaxDistance(5)); SoundSystem.Play( Filter.Pvs(gun.Owner), gun.ClumsyWeaponShotSound.GetSound(), coordinates, AudioParams.Default.WithMaxDistance(5)); user.PopupMessage(Loc.GetString("server-ranged-weapon-component-try-fire-clumsy")); EntityManager.DeleteEntity(gun.Owner); return; } // Firing confirmed if (gun.CanHotspot) { _atmos.HotspotExpose(coordinates, 700, 50); } EntityManager.EventBus.RaiseLocalEvent(gun.Owner, new GunShotEvent()); Fire(user, barrel, targetCoords); }
// Handles logic for our different types of valid target. // Checks for conditions that would prevent a doAfter from starting. private void HandleDoAfter(EntityUid user, EntityUid used, EntityUid target, AbsorbentComponent component, FixedPoint2 currentVolume, FixedPoint2 availableVolume) { // Below variables will be set within this function depending on what kind of target was clicked. // They will be passed to the OnTransferComplete if the doAfter succeeds. EntityUid donor; EntityUid acceptor; string donorSolutionName; string acceptorSolutionName; FixedPoint2 transferAmount; var delay = 1.0f; //default do_after delay in seconds. string msg; SoundSpecifier sfx; // For our purposes, if our target has a PuddleComponent, treat it as a puddle above all else. if (TryComp <PuddleComponent>(target, out var puddle)) { // These return conditions will abort BEFORE the do_after is called: if (!_solutionSystem.TryGetSolution(target, puddle.SolutionName, out var puddleSolution) || // puddle Solution is null (puddleSolution.TotalVolume <= 0)) // puddle is completely empty { return; } else if (availableVolume < 0) // mop is completely full { msg = "mopping-system-tool-full"; user.PopupMessage(user, Loc.GetString(msg, ("used", used))); // play message now because we are aborting. return; } // adding to puddles else if (puddleSolution.TotalVolume < component.MopLowerLimit && // if the puddle is too small for the tool to effectively absorb any more solution from it currentVolume > 0) // tool needs a solution to dilute the puddle with. { // Dilutes the puddle with some solution from the tool transferAmount = FixedPoint2.Max(component.ResidueAmount, currentVolume); TryTransfer(used, target, "absorbed", puddle.SolutionName, transferAmount); // Complete the transfer right away, with no doAfter. sfx = component.TransferSound; SoundSystem.Play(sfx.GetSound(), Filter.Pvs(user), used); // Give instant feedback for diluting puddle, so that it's clear that the player is adding to the puddle (as opposed to other behaviours, which have a doAfter). msg = "mopping-system-puddle-diluted"; user.PopupMessage(user, Loc.GetString(msg)); // play message now because we are aborting. return; // Do not begin a doAfter. } else { // Taking from puddles: // Determine transferAmount: transferAmount = FixedPoint2.Min(component.PickupAmount, puddleSolution.TotalVolume, availableVolume); // TODO: consider onelining this with the above, using additional args on Min()? if ((puddleSolution.TotalVolume - transferAmount) < component.MopLowerLimit) // If the transferAmount would bring the puddle below the MopLowerLimit { transferAmount = puddleSolution.TotalVolume - component.MopLowerLimit; // Then the transferAmount should bring the puddle down to the MopLowerLimit exactly } donor = target; // the puddle Uid donorSolutionName = puddle.SolutionName; acceptor = used; // the mop/tool Uid acceptorSolutionName = "absorbed"; // by definition on AbsorbentComponent // Set delay/popup/sound if nondefault. Popup and sound will only play on a successful doAfter. delay = (component.PickupAmount.Float() / 10.0f) * component.MopSpeed; // Delay should scale with PickupAmount, which represents the maximum we can pick up per click. msg = "mopping-system-puddle-success"; sfx = component.PickupSound; DoMopInteraction(user, used, target, donor, acceptor, component, donorSolutionName, acceptorSolutionName, transferAmount, delay, msg, sfx); } } else if ((TryComp <RefillableSolutionComponent>(target, out var refillable)) && // We can put solution from the tool into the target (currentVolume > 0)) // And the tool is wet { // These return conditions will abort BEFORE the do_after is called: if (!_solutionSystem.TryGetRefillableSolution(target, out var refillableSolution)) // refillable Solution is null { return; } else if (refillableSolution.AvailableVolume <= 0) // target container is full (liquid destination) { msg = "mopping-system-target-container-full"; user.PopupMessage(user, Loc.GetString(msg, ("target", target))); // play message now because we are aborting. return; } else if (refillableSolution.MaxVolume <= FixedPoint2.New(20)) // target container is too small (e.g. syringe) { msg = "mopping-system-target-container-too-small"; user.PopupMessage(user, Loc.GetString(msg, ("target", target))); // play message now because we are aborting. return; } else { // Determine transferAmount if (_tagSystem.HasTag(used, "Mop") && // if the tool used is a literal mop (and not a sponge, rag, etc.) !_tagSystem.HasTag(target, "Wringer")) // and if the target does not have a wringer for properly drying the mop { delay = 5.0f; // Should take much longer if you don't have a wringer if ((currentVolume / (currentVolume + availableVolume)) > 0.25) // mop is more than one-quarter full { transferAmount = FixedPoint2.Min(refillableSolution.AvailableVolume, currentVolume * 0.6); // squeeze up to 60% of the solution from the mop. msg = "mopping-system-hand-squeeze-little-wet"; if ((currentVolume / (currentVolume + availableVolume)) > 0.5) // if the mop is more than half full { msg = "mopping-system-hand-squeeze-still-wet"; // overwrites the above } } else // mop is less than one-quarter full { transferAmount = FixedPoint2.Min(refillableSolution.AvailableVolume, currentVolume); // squeeze remainder of solution from the mop. msg = "mopping-system-hand-squeeze-dry"; } } else { transferAmount = FixedPoint2.Min(refillableSolution.AvailableVolume, currentVolume); //Transfer all liquid from the tool to the container, but only if it will fit. msg = "mopping-system-refillable-success"; delay = 1.0f; } donor = used; // the mop/tool Uid donorSolutionName = "absorbed"; // by definition on AbsorbentComponent acceptor = target; // the refillable container's Uid acceptorSolutionName = refillable.Solution; // Set delay/popup/sound if nondefault. Popup and sound will only play on a successful doAfter. sfx = component.TransferSound; DoMopInteraction(user, used, target, donor, acceptor, component, donorSolutionName, acceptorSolutionName, transferAmount, delay, msg, sfx); } }
/// <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); } } }
/// <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); } }
public bool TryDoInject(EntityUid?target, EntityUid user) { if (!EligibleEntity(target, _entMan)) { return(false); } string?msgFormat = null; if (target == user) { msgFormat = "hypospray-component-inject-self-message"; } else if (EligibleEntity(user, _entMan) && ClumsyComponent.TryRollClumsy(user, ClumsyFailChance)) { msgFormat = "hypospray-component-inject-self-clumsy-message"; target = user; } var solutionsSys = EntitySystem.Get <SolutionContainerSystem>(); solutionsSys.TryGetSolution(Owner, SolutionName, out var hypoSpraySolution); if (hypoSpraySolution == null || hypoSpraySolution.CurrentVolume == 0) { user.PopupMessageCursor(Loc.GetString("hypospray-component-empty-message")); return(true); } if (!solutionsSys.TryGetInjectableSolution(target.Value, out var targetSolution)) { user.PopupMessage(user, Loc.GetString("hypospray-cant-inject", ("target", Identity.Entity(target.Value, _entMan)))); return(false); } user.PopupMessage(Loc.GetString(msgFormat ?? "hypospray-component-inject-other-message", ("other", target))); if (target != user) { target.Value.PopupMessage(Loc.GetString("hypospray-component-feel-prick-message")); var meleeSys = EntitySystem.Get <MeleeWeaponSystem>(); var angle = Angle.FromWorldVec(_entMan.GetComponent <TransformComponent>(target.Value).WorldPosition - _entMan.GetComponent <TransformComponent>(user).WorldPosition); meleeSys.SendLunge(angle, user); } SoundSystem.Play(_injectSound.GetSound(), Filter.Pvs(user), user); // Get transfer amount. May be smaller than _transferAmount if not enough room var realTransferAmount = FixedPoint2.Min(TransferAmount, targetSolution.AvailableVolume); if (realTransferAmount <= 0) { user.PopupMessage(user, Loc.GetString("hypospray-component-transfer-already-full-message", ("owner", target))); return(true); } // Move units from attackSolution to targetSolution var removedSolution = EntitySystem.Get <SolutionContainerSystem>() .SplitSolution(Owner, hypoSpraySolution, realTransferAmount); if (!targetSolution.CanAddSolution(removedSolution)) { return(true); } removedSolution.DoEntityReaction(target.Value, ReactionMethod.Injection); EntitySystem.Get <SolutionContainerSystem>().TryAddSolution(target.Value, targetSolution, removedSolution);