diff --git a/Content.Server/Chemistry/Components/InjectorComponent.cs b/Content.Server/Chemistry/Components/InjectorComponent.cs index b922aabd5c..aab8a633d5 100644 --- a/Content.Server/Chemistry/Components/InjectorComponent.cs +++ b/Content.Server/Chemistry/Components/InjectorComponent.cs @@ -1,18 +1,25 @@ using System; using System.Threading.Tasks; +using Content.Server.Administration.Logs; using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.Chemistry.Components.SolutionManager; using Content.Server.Chemistry.EntitySystems; +using Content.Server.CombatMode; +using Content.Server.DoAfter; +using Content.Shared.ActionBlocker; using Content.Shared.Body.Components; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; +using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; +using Content.Shared.MobState.Components; using Content.Shared.Popups; using Robust.Shared.GameObjects; using Robust.Shared.Localization; +using Robust.Shared.Player; using Robust.Shared.Players; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -41,16 +48,25 @@ namespace Content.Server.Chemistry.Components /// Amount to inject or draw on each usage. If the injector is inject only, it will /// attempt to inject it's entire contents upon use. /// - [ViewVariables] + [ViewVariables(VVAccess.ReadWrite)] [DataField("transferAmount")] private FixedPoint2 _transferAmount = FixedPoint2.New(5); /// - /// Initial storage volume of the injector + /// Injection delay (seconds) when the target is a mob. /// - [ViewVariables] - [DataField("initialMaxVolume")] - private FixedPoint2 _initialMaxVolume = FixedPoint2.New(15); + /// + /// The base delay has a minimum of 1 second, but this will still be modified if the target is incapacitated or + /// in combat mode. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("delay")] + public float Delay = 5; + + /// + /// Is this component currently being used in a DoAfter? + /// + public bool InUse = false; private InjectorToggleMode _toggleState; @@ -111,9 +127,15 @@ namespace Content.Server.Chemistry.Components /// async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { + if (InUse) + return false; + if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return false; + if (!EntitySystem.Get().CanInteract(eventArgs.User.Uid)) + return false; + var solutionsSys = EntitySystem.Get(); //Make sure we have the attacking entity if (eventArgs.Target == null || !Owner.HasComponent()) @@ -123,6 +145,13 @@ namespace Content.Server.Chemistry.Components var targetEntity = eventArgs.Target; + // Is the target a mob? If yes, use a do-after to give them time to respond. + if (Owner.EntityManager.HasComponent(targetEntity.Uid) || + Owner.EntityManager.HasComponent(targetEntity.Uid)) + { + if (!await TryInjectDoAfter(eventArgs.User.Uid, eventArgs.Target.Uid)) + return true; + } // Handle injecting/drawing for solutions if (ToggleState == InjectorToggleMode.Inject) @@ -163,6 +192,77 @@ namespace Content.Server.Chemistry.Components return true; } + /// + /// Send informative pop-up messages and wait for a do-after to complete. + /// + public async Task TryInjectDoAfter(EntityUid user, EntityUid target) + { + InUse = true; + var popupSys = EntitySystem.Get(); + + // Create a pop-up for the user + popupSys.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, Filter.Entities(user)); + + // Get entity for logging. Log with EntityUids when? + var userEntity = Owner.EntityManager.GetEntity(user); + var logSys = EntitySystem.Get(); + + var actualDelay = MathF.Max(Delay, 1f); + if (user != target) + { + // Create a pop-up for the target + var userName = Owner.EntityManager.GetComponent(user).EntityName; + popupSys.PopupEntity(Loc.GetString("injector-component-injecting-target", + ("user", userName)), user, Filter.Entities(target)); + + // Check if the target is incapacitated or in combat mode and modify time accordingly. + if (Owner.EntityManager.TryGetComponent(target, out var mobState) && + mobState.IsIncapacitated()) + { + actualDelay /= 2; + } + else if (Owner.EntityManager.TryGetComponent(target, out var combat) && + combat.IsInCombatMode) + { + // Slightly increase the delay when the target is in combat mode. Helps prevents cheese injections in + // combat with fast syringes & lag. + actualDelay += 1; + } + + // Add an admin log, using the "force feed" log type. It's not quite feeding, but the effect is the same. + var targetEntity = Owner.EntityManager.GetEntity(target); + if (ToggleState == InjectorToggleMode.Inject) + { + logSys.Add(LogType.ForceFeed, + $"{userEntity} is attempting to inject a solution into {targetEntity}"); + // TODO solution pretty string. + } + } + else + { + // Self-injections take half as long. + actualDelay /= 2; + + if (ToggleState == InjectorToggleMode.Inject) + logSys.Add(LogType.Ingestion, + $"{userEntity} is attempting to inject themselves with a solution."); + //TODO solution pretty string. + } + + var status = await EntitySystem.Get().WaitDoAfter( + new DoAfterEventArgs(user, actualDelay, target: target) + { + BreakOnUserMove = true, + BreakOnDamage = true, + BreakOnStun = true, + BreakOnTargetMove = true, + MovementThreshold = 1.0f + }); + InUse = false; + + return status == DoAfterStatus.Finished; + } + /// /// Called when use key is pressed when held in active hand /// diff --git a/Content.Shared.Database/LogType.cs b/Content.Shared.Database/LogType.cs index e6a5fe9bed..154c0ea3d6 100644 --- a/Content.Shared.Database/LogType.cs +++ b/Content.Shared.Database/LogType.cs @@ -1,4 +1,4 @@ -namespace Content.Shared.Database; +namespace Content.Shared.Database; // DO NOT CHANGE THE NUMERIC VALUES OF THESE public enum LogType @@ -40,7 +40,8 @@ public enum LogType Pickup = 36, Drop = 37, BulletHit = 38, - ForceFeed = 40, + ForceFeed = 40, // involuntary + Ingestion = 53, // voluntary MeleeHit = 41, HitScanHit = 42, Suicide = 43, diff --git a/Resources/Locale/en-US/chemistry/components/injector-component.ftl b/Resources/Locale/en-US/chemistry/components/injector-component.ftl index 84e58824a6..4249c14e87 100644 --- a/Resources/Locale/en-US/chemistry/components/injector-component.ftl +++ b/Resources/Locale/en-US/chemistry/components/injector-component.ftl @@ -17,3 +17,8 @@ injector-component-transfer-success-message = You transfer {$amount}u into {$tar injector-component-draw-success-message = You draw {$amount}u from {$target}. injector-component-target-already-full-message = {$target} is already full! injector-component-target-is-empty-message = {$target} is empty! + +## mob-inject doafter messages + +injector-component-injecting-user = You start inserting the needle. +injector-component-injecting-target = {$user} is trying to stick a needle into you!