diff --git a/Content.Server/Chemistry/Components/InjectorComponent.cs b/Content.Server/Chemistry/Components/InjectorComponent.cs
index aab8a633d5..446f6e935a 100644
--- a/Content.Server/Chemistry/Components/InjectorComponent.cs
+++ b/Content.Server/Chemistry/Components/InjectorComponent.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading;
using System.Threading.Tasks;
using Content.Server.Administration.Logs;
using Content.Server.Body.Components;
@@ -64,9 +65,10 @@ namespace Content.Server.Chemistry.Components
public float Delay = 5;
///
- /// Is this component currently being used in a DoAfter?
+ /// Token for interrupting a do-after action (e.g., injection another player). If not null, implies
+ /// component is currently "in use".
///
- public bool InUse = false;
+ public CancellationTokenSource? CancelToken;
private InjectorToggleMode _toggleState;
@@ -127,8 +129,11 @@ namespace Content.Server.Chemistry.Components
///
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
- if (InUse)
- return false;
+ if (CancelToken != null)
+ {
+ CancelToken.Cancel();
+ return true;
+ }
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
return false;
@@ -197,7 +202,6 @@ namespace Content.Server.Chemistry.Components
///
public async Task TryInjectDoAfter(EntityUid user, EntityUid target)
{
- InUse = true;
var popupSys = EntitySystem.Get();
// Create a pop-up for the user
@@ -249,8 +253,9 @@ namespace Content.Server.Chemistry.Components
//TODO solution pretty string.
}
+ CancelToken = new();
var status = await EntitySystem.Get().WaitDoAfter(
- new DoAfterEventArgs(user, actualDelay, target: target)
+ new DoAfterEventArgs(user, actualDelay, CancelToken.Token, target)
{
BreakOnUserMove = true,
BreakOnDamage = true,
@@ -258,7 +263,7 @@ namespace Content.Server.Chemistry.Components
BreakOnTargetMove = true,
MovementThreshold = 1.0f
});
- InUse = false;
+ CancelToken = null;
return status == DoAfterStatus.Finished;
}
diff --git a/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs b/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs
index e4affc4678..19bc66986e 100644
--- a/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs
+++ b/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs
@@ -1,6 +1,8 @@
-using Content.Server.Chemistry.Components;
+using Content.Server.Chemistry.Components;
+using Content.Shared.Hands;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
+using System;
namespace Content.Server.Chemistry.EntitySystems
{
@@ -12,6 +14,16 @@ namespace Content.Server.Chemistry.EntitySystems
base.Initialize();
SubscribeLocalEvent(OnSolutionChange);
+ SubscribeLocalEvent(OnInjectorDeselected);
+ }
+
+ private void OnInjectorDeselected(EntityUid uid, InjectorComponent component, HandDeselectedEvent args)
+ {
+ if (component.CancelToken != null)
+ {
+ component.CancelToken.Cancel();
+ component.CancelToken = null;
+ }
}
private void OnSolutionChange(EntityUid uid, InjectorComponent component, SolutionChangedEvent args)
diff --git a/Content.Server/DoAfter/DoAfterSystem.cs b/Content.Server/DoAfter/DoAfterSystem.cs
index e64afc9456..ea301b9743 100644
--- a/Content.Server/DoAfter/DoAfterSystem.cs
+++ b/Content.Server/DoAfter/DoAfterSystem.cs
@@ -12,8 +12,8 @@ namespace Content.Server.DoAfter
public sealed class DoAfterSystem : EntitySystem
{
// We cache these lists as to not allocate them every update tick...
- private readonly List _cancelled = new();
- private readonly List _finished = new();
+ private readonly Queue _cancelled = new();
+ private readonly Queue _finished = new();
public override void Initialize()
{
@@ -52,17 +52,17 @@ namespace Content.Server.DoAfter
case DoAfterStatus.Running:
break;
case DoAfterStatus.Cancelled:
- _cancelled.Add(doAfter);
+ _cancelled.Enqueue(doAfter);
break;
case DoAfterStatus.Finished:
- _finished.Add(doAfter);
+ _finished.Enqueue(doAfter);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
- foreach (var doAfter in _cancelled)
+ while (_cancelled.TryDequeue(out var doAfter))
{
comp.Cancelled(doAfter);
@@ -76,7 +76,7 @@ namespace Content.Server.DoAfter
RaiseLocalEvent(doAfter.EventArgs.BroadcastCancelledEvent);
}
- foreach (var doAfter in _finished)
+ while (_finished.TryDequeue(out var doAfter))
{
comp.Finished(doAfter);
@@ -89,10 +89,6 @@ namespace Content.Server.DoAfter
if(doAfter.EventArgs.BroadcastFinishedEvent != null)
RaiseLocalEvent(doAfter.EventArgs.BroadcastFinishedEvent);
}
-
- // Clean the shared lists at the end, ensuring they'll be clean for the next time we need them.
- _cancelled.Clear();
- _finished.Clear();
}
}
diff --git a/Content.Server/Nutrition/Components/DrinkComponent.cs b/Content.Server/Nutrition/Components/DrinkComponent.cs
index 2dfc6c495d..b6d893ae02 100644
--- a/Content.Server/Nutrition/Components/DrinkComponent.cs
+++ b/Content.Server/Nutrition/Components/DrinkComponent.cs
@@ -1,4 +1,3 @@
-using Content.Shared.Chemistry.Reagent;
using Content.Shared.Sound;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
@@ -7,6 +6,7 @@ using Robust.Shared.ViewVariables;
using Content.Server.Nutrition.EntitySystems;
using Content.Shared.FixedPoint;
using Robust.Shared.Analyzers;
+using System.Threading;
namespace Content.Server.Nutrition.Components
{
@@ -50,8 +50,9 @@ namespace Content.Server.Nutrition.Components
public float ForceFeedDelay = 3;
///
- /// If true, this drink has some DoAfter active (someone is being force fed).
+ /// Token for interrupting a do-after action (e.g., force feeding). If not null, implies component is
+ /// currently "in use".
///
- public bool InUse = false;
+ public CancellationTokenSource? CancelToken;
}
}
diff --git a/Content.Server/Nutrition/Components/FoodComponent.cs b/Content.Server/Nutrition/Components/FoodComponent.cs
index b567e5cdd0..8d3e1fac19 100644
--- a/Content.Server/Nutrition/Components/FoodComponent.cs
+++ b/Content.Server/Nutrition/Components/FoodComponent.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Nutrition.EntitySystems;
using Content.Shared.FixedPoint;
@@ -55,9 +56,10 @@ namespace Content.Server.Nutrition.Components
public float ForceFeedDelay = 3;
///
- /// If true, this food has some DoAfter active (someone is being force fed).
+ /// Token for interrupting a do-after action (e.g., force feeding). If not null, implies component is
+ /// currently "in use".
///
- public bool InUse = false;
+ public CancellationTokenSource? CancelToken;
[ViewVariables]
public int UsesRemaining
diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs
index b327c8c1a2..c2258c565b 100644
--- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs
+++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs
@@ -16,6 +16,7 @@ using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.FixedPoint;
+using Content.Shared.Hands;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Nutrition.Components;
@@ -54,12 +55,26 @@ namespace Content.Server.Nutrition.EntitySystems
SubscribeLocalEvent(OnDrinkInit);
SubscribeLocalEvent(HandleLand);
SubscribeLocalEvent(OnUse);
+ SubscribeLocalEvent(OnDrinkDeselected);
SubscribeLocalEvent(AfterInteract);
SubscribeLocalEvent(OnExamined);
SubscribeLocalEvent(OnForceDrink);
SubscribeLocalEvent(OnForceDrinkCancelled);
}
+ ///
+ /// If the user is currently forcing someone do drink, this cancels the attempt if they swap hands or
+ /// otherwise loose the item. Prevents force-feeding dual-wielding.
+ ///
+ private void OnDrinkDeselected(EntityUid uid, DrinkComponent component, HandDeselectedEvent args)
+ {
+ if (component.CancelToken != null)
+ {
+ component.CancelToken.Cancel();
+ component.CancelToken = null;
+ }
+ }
+
public bool IsEmpty(EntityUid uid, DrinkComponent? component = null)
{
if(!Resolve(uid, ref component))
@@ -240,6 +255,14 @@ namespace Content.Server.Nutrition.EntitySystems
if (!Resolve(uid, ref drink))
return false;
+ // if currently being used to force-feed, cancel that action.
+ if (drink.CancelToken != null)
+ {
+ drink.CancelToken.Cancel();
+ drink.CancelToken = null;
+ return true;
+ }
+
if (!drink.Opened)
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-not-open",
@@ -312,8 +335,12 @@ namespace Content.Server.Nutrition.EntitySystems
return false;
// cannot stack do-afters
- if (drink.InUse)
- return false;
+ if (drink.CancelToken != null)
+ {
+ drink.CancelToken.Cancel();
+ drink.CancelToken = null;
+ return true;
+ }
if (!EntityManager.HasComponent(targetUid))
return false;
@@ -342,7 +369,8 @@ namespace Content.Server.Nutrition.EntitySystems
_popupSystem.PopupEntity(Loc.GetString("drink-component-force-feed", ("user", userName)),
userUid, Filter.Entities(targetUid));
- _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, drink.ForceFeedDelay, target: targetUid)
+ drink.CancelToken = new();
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, drink.ForceFeedDelay, drink.CancelToken.Token, targetUid)
{
BreakOnUserMove = true,
BreakOnDamage = true,
@@ -350,7 +378,7 @@ namespace Content.Server.Nutrition.EntitySystems
BreakOnTargetMove = true,
MovementThreshold = 1.0f,
TargetFinishedEvent = new ForceDrinkEvent(userUid, drink, drinkSolution),
- BroadcastCancelledEvent = new ForceDrinkCancelledEvent(drink)
+ BroadcastCancelledEvent = new ForceDrinkCancelledEvent(drink),
});
// logging
@@ -359,7 +387,6 @@ namespace Content.Server.Nutrition.EntitySystems
var drinkable = EntityManager.GetEntity(uid);
_logSystem.Add(LogType.ForceFeed, LogImpact.Medium, $"{user} is forcing {target} to drink {drinkable}");
- drink.InUse = true;
return true;
}
@@ -368,7 +395,10 @@ namespace Content.Server.Nutrition.EntitySystems
///
private void OnForceDrink(EntityUid uid, SharedBodyComponent body, ForceDrinkEvent args)
{
- args.Drink.InUse = false;
+ if (args.Drink.Deleted)
+ return;
+
+ args.Drink.CancelToken = null;
var transferAmount = FixedPoint2.Min(args.Drink.TransferAmount, args.DrinkSolution.DrainAvailable);
var drained = _solutionContainerSystem.Drain(args.Drink.OwnerUid, args.DrinkSolution, transferAmount);
@@ -414,7 +444,7 @@ namespace Content.Server.Nutrition.EntitySystems
private void OnForceDrinkCancelled(ForceDrinkCancelledEvent args)
{
- args.Drink.InUse = false;
+ args.Drink.CancelToken = null;
}
}
}
diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs
index 9876afd2e1..1f872ca1dc 100644
--- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs
+++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs
@@ -9,7 +9,6 @@ using Content.Server.Popups;
using Content.Shared.ActionBlocker;
using Content.Shared.Administration.Logs;
using Content.Shared.Body.Components;
-using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
@@ -23,10 +22,10 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Player;
using System.Collections.Generic;
-using System.Linq;
using Robust.Shared.Utility;
using Content.Server.Inventory.Components;
using Content.Shared.Inventory;
+using Content.Shared.Hands;
namespace Content.Server.Nutrition.EntitySystems
{
@@ -50,12 +49,26 @@ namespace Content.Server.Nutrition.EntitySystems
SubscribeLocalEvent(OnUseFoodInHand);
SubscribeLocalEvent(OnFeedFood);
+ SubscribeLocalEvent(OnFoodDeselected);
SubscribeLocalEvent(AddEatVerb);
SubscribeLocalEvent(OnForceFeed);
SubscribeLocalEvent(OnForceFeedCancelled);
SubscribeLocalEvent(OnInventoryIngestAttempt);
}
+ ///
+ /// If the user is currently force feeding someone, this cancels the attempt if they swap hands or otherwise
+ /// loose the item. Prevents force-feeding dual-wielding.
+ ///
+ private void OnFoodDeselected(EntityUid uid, FoodComponent component, HandDeselectedEvent args)
+ {
+ if (component.CancelToken != null)
+ {
+ component.CancelToken.Cancel();
+ component.CancelToken = null;
+ }
+ }
+
///
/// Eat item
///
@@ -120,6 +133,14 @@ namespace Content.Server.Nutrition.EntitySystems
if (!Resolve(uid, ref component))
return false;
+ // if currently being used to force-feed, cancel that action.
+ if (component.CancelToken != null)
+ {
+ component.CancelToken.Cancel();
+ component.CancelToken = null;
+ return true;
+ }
+
if (uid == userUid || //Suppresses self-eating
EntityManager.TryGetComponent(uid, out var mobState) && mobState.IsAlive()) // Suppresses eating alive mobs
return false;
@@ -214,6 +235,9 @@ namespace Content.Server.Nutrition.EntitySystems
private void AddEatVerb(EntityUid uid, FoodComponent component, GetInteractionVerbsEvent ev)
{
+ if (component.CancelToken != null)
+ return;
+
if (uid == ev.UserUid ||
!ev.CanInteract ||
!ev.CanAccess ||
@@ -244,6 +268,14 @@ namespace Content.Server.Nutrition.EntitySystems
if (!Resolve(uid, ref food))
return false;
+ // if currently being used to force-feed, cancel that action.
+ if (food.CancelToken != null)
+ {
+ food.CancelToken.Cancel();
+ food.CancelToken = null;
+ return true;
+ }
+
if (!EntityManager.HasComponent(targetUid))
return false;
@@ -270,7 +302,8 @@ namespace Content.Server.Nutrition.EntitySystems
_popupSystem.PopupEntity(Loc.GetString("food-system-force-feed", ("user", userName)),
userUid, Filter.Entities(targetUid));
- _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, food.ForceFeedDelay, target: targetUid)
+ food.CancelToken = new();
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, food.ForceFeedDelay, food.CancelToken.Token, targetUid)
{
BreakOnUserMove = true,
BreakOnDamage = true,
@@ -287,13 +320,15 @@ namespace Content.Server.Nutrition.EntitySystems
var edible = EntityManager.GetEntity(uid);
_logSystem.Add(LogType.ForceFeed, LogImpact.Medium, $"{user} is forcing {target} to eat {edible}");
- food.InUse = true;
return true;
}
private void OnForceFeed(EntityUid uid, SharedBodyComponent body, ForceFeedEvent args)
{
- args.Food.InUse = false;
+ if (args.Food.Deleted)
+ return;
+
+ args.Food.CancelToken = null;
if (!_bodySystem.TryGetComponentsOnMechanisms(uid, out var stomachs, body))
return;
@@ -433,7 +468,7 @@ namespace Content.Server.Nutrition.EntitySystems
private void OnForceFeedCancelled(ForceFeedCancelledEvent args)
{
- args.Food.InUse = false;
+ args.Food.CancelToken = null;
}
///