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!