Fix food & do-after bugs (#5716)

This commit is contained in:
Leon Friedrich
2021-12-07 19:19:26 +13:00
committed by GitHub
parent 7fa117bf7e
commit bd8acc5b6b
7 changed files with 117 additions and 36 deletions

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.Body.Components; using Content.Server.Body.Components;
@@ -64,9 +65,10 @@ namespace Content.Server.Chemistry.Components
public float Delay = 5; public float Delay = 5;
/// <summary> /// <summary>
/// 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".
/// </summary> /// </summary>
public bool InUse = false; public CancellationTokenSource? CancelToken;
private InjectorToggleMode _toggleState; private InjectorToggleMode _toggleState;
@@ -127,8 +129,11 @@ namespace Content.Server.Chemistry.Components
/// <param name="eventArgs"></param> /// <param name="eventArgs"></param>
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{ {
if (InUse) if (CancelToken != null)
return false; {
CancelToken.Cancel();
return true;
}
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
return false; return false;
@@ -197,7 +202,6 @@ namespace Content.Server.Chemistry.Components
/// </summary> /// </summary>
public async Task<bool> TryInjectDoAfter(EntityUid user, EntityUid target) public async Task<bool> TryInjectDoAfter(EntityUid user, EntityUid target)
{ {
InUse = true;
var popupSys = EntitySystem.Get<SharedPopupSystem>(); var popupSys = EntitySystem.Get<SharedPopupSystem>();
// Create a pop-up for the user // Create a pop-up for the user
@@ -249,8 +253,9 @@ namespace Content.Server.Chemistry.Components
//TODO solution pretty string. //TODO solution pretty string.
} }
CancelToken = new();
var status = await EntitySystem.Get<DoAfterSystem>().WaitDoAfter( var status = await EntitySystem.Get<DoAfterSystem>().WaitDoAfter(
new DoAfterEventArgs(user, actualDelay, target: target) new DoAfterEventArgs(user, actualDelay, CancelToken.Token, target)
{ {
BreakOnUserMove = true, BreakOnUserMove = true,
BreakOnDamage = true, BreakOnDamage = true,
@@ -258,7 +263,7 @@ namespace Content.Server.Chemistry.Components
BreakOnTargetMove = true, BreakOnTargetMove = true,
MovementThreshold = 1.0f MovementThreshold = 1.0f
}); });
InUse = false; CancelToken = null;
return status == DoAfterStatus.Finished; return status == DoAfterStatus.Finished;
} }

View File

@@ -1,6 +1,8 @@
using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Components;
using Content.Shared.Hands;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using System;
namespace Content.Server.Chemistry.EntitySystems namespace Content.Server.Chemistry.EntitySystems
{ {
@@ -12,6 +14,16 @@ namespace Content.Server.Chemistry.EntitySystems
base.Initialize(); base.Initialize();
SubscribeLocalEvent<InjectorComponent, SolutionChangedEvent>(OnSolutionChange); SubscribeLocalEvent<InjectorComponent, SolutionChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<InjectorComponent, HandDeselectedEvent>(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) private void OnSolutionChange(EntityUid uid, InjectorComponent component, SolutionChangedEvent args)

View File

@@ -12,8 +12,8 @@ namespace Content.Server.DoAfter
public sealed class DoAfterSystem : EntitySystem public sealed class DoAfterSystem : EntitySystem
{ {
// We cache these lists as to not allocate them every update tick... // We cache these lists as to not allocate them every update tick...
private readonly List<DoAfter> _cancelled = new(); private readonly Queue<DoAfter> _cancelled = new();
private readonly List<DoAfter> _finished = new(); private readonly Queue<DoAfter> _finished = new();
public override void Initialize() public override void Initialize()
{ {
@@ -52,17 +52,17 @@ namespace Content.Server.DoAfter
case DoAfterStatus.Running: case DoAfterStatus.Running:
break; break;
case DoAfterStatus.Cancelled: case DoAfterStatus.Cancelled:
_cancelled.Add(doAfter); _cancelled.Enqueue(doAfter);
break; break;
case DoAfterStatus.Finished: case DoAfterStatus.Finished:
_finished.Add(doAfter); _finished.Enqueue(doAfter);
break; break;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
} }
foreach (var doAfter in _cancelled) while (_cancelled.TryDequeue(out var doAfter))
{ {
comp.Cancelled(doAfter); comp.Cancelled(doAfter);
@@ -76,7 +76,7 @@ namespace Content.Server.DoAfter
RaiseLocalEvent(doAfter.EventArgs.BroadcastCancelledEvent); RaiseLocalEvent(doAfter.EventArgs.BroadcastCancelledEvent);
} }
foreach (var doAfter in _finished) while (_finished.TryDequeue(out var doAfter))
{ {
comp.Finished(doAfter); comp.Finished(doAfter);
@@ -89,10 +89,6 @@ namespace Content.Server.DoAfter
if(doAfter.EventArgs.BroadcastFinishedEvent != null) if(doAfter.EventArgs.BroadcastFinishedEvent != null)
RaiseLocalEvent(doAfter.EventArgs.BroadcastFinishedEvent); 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();
} }
} }

View File

@@ -1,4 +1,3 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Sound; using Content.Shared.Sound;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -7,6 +6,7 @@ using Robust.Shared.ViewVariables;
using Content.Server.Nutrition.EntitySystems; using Content.Server.Nutrition.EntitySystems;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Robust.Shared.Analyzers; using Robust.Shared.Analyzers;
using System.Threading;
namespace Content.Server.Nutrition.Components namespace Content.Server.Nutrition.Components
{ {
@@ -50,8 +50,9 @@ namespace Content.Server.Nutrition.Components
public float ForceFeedDelay = 3; public float ForceFeedDelay = 3;
/// <summary> /// <summary>
/// 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".
/// </summary> /// </summary>
public bool InUse = false; public CancellationTokenSource? CancelToken;
} }
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Threading;
using Content.Server.Chemistry.EntitySystems; using Content.Server.Chemistry.EntitySystems;
using Content.Server.Nutrition.EntitySystems; using Content.Server.Nutrition.EntitySystems;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
@@ -55,9 +56,10 @@ namespace Content.Server.Nutrition.Components
public float ForceFeedDelay = 3; public float ForceFeedDelay = 3;
/// <summary> /// <summary>
/// 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".
/// </summary> /// </summary>
public bool InUse = false; public CancellationTokenSource? CancelToken;
[ViewVariables] [ViewVariables]
public int UsesRemaining public int UsesRemaining

View File

@@ -16,6 +16,7 @@ using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Hands;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers; using Content.Shared.Interaction.Helpers;
using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.Components;
@@ -54,12 +55,26 @@ namespace Content.Server.Nutrition.EntitySystems
SubscribeLocalEvent<DrinkComponent, ComponentInit>(OnDrinkInit); SubscribeLocalEvent<DrinkComponent, ComponentInit>(OnDrinkInit);
SubscribeLocalEvent<DrinkComponent, LandEvent>(HandleLand); SubscribeLocalEvent<DrinkComponent, LandEvent>(HandleLand);
SubscribeLocalEvent<DrinkComponent, UseInHandEvent>(OnUse); SubscribeLocalEvent<DrinkComponent, UseInHandEvent>(OnUse);
SubscribeLocalEvent<DrinkComponent, HandDeselectedEvent>(OnDrinkDeselected);
SubscribeLocalEvent<DrinkComponent, AfterInteractEvent>(AfterInteract); SubscribeLocalEvent<DrinkComponent, AfterInteractEvent>(AfterInteract);
SubscribeLocalEvent<DrinkComponent, ExaminedEvent>(OnExamined); SubscribeLocalEvent<DrinkComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<SharedBodyComponent, ForceDrinkEvent>(OnForceDrink); SubscribeLocalEvent<SharedBodyComponent, ForceDrinkEvent>(OnForceDrink);
SubscribeLocalEvent<ForceDrinkCancelledEvent>(OnForceDrinkCancelled); SubscribeLocalEvent<ForceDrinkCancelledEvent>(OnForceDrinkCancelled);
} }
/// <summary>
/// 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.
/// </summary>
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) public bool IsEmpty(EntityUid uid, DrinkComponent? component = null)
{ {
if(!Resolve(uid, ref component)) if(!Resolve(uid, ref component))
@@ -240,6 +255,14 @@ namespace Content.Server.Nutrition.EntitySystems
if (!Resolve(uid, ref drink)) if (!Resolve(uid, ref drink))
return false; 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) if (!drink.Opened)
{ {
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-not-open", _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-not-open",
@@ -312,8 +335,12 @@ namespace Content.Server.Nutrition.EntitySystems
return false; return false;
// cannot stack do-afters // cannot stack do-afters
if (drink.InUse) if (drink.CancelToken != null)
return false; {
drink.CancelToken.Cancel();
drink.CancelToken = null;
return true;
}
if (!EntityManager.HasComponent<SharedBodyComponent>(targetUid)) if (!EntityManager.HasComponent<SharedBodyComponent>(targetUid))
return false; return false;
@@ -342,7 +369,8 @@ namespace Content.Server.Nutrition.EntitySystems
_popupSystem.PopupEntity(Loc.GetString("drink-component-force-feed", ("user", userName)), _popupSystem.PopupEntity(Loc.GetString("drink-component-force-feed", ("user", userName)),
userUid, Filter.Entities(targetUid)); 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, BreakOnUserMove = true,
BreakOnDamage = true, BreakOnDamage = true,
@@ -350,7 +378,7 @@ namespace Content.Server.Nutrition.EntitySystems
BreakOnTargetMove = true, BreakOnTargetMove = true,
MovementThreshold = 1.0f, MovementThreshold = 1.0f,
TargetFinishedEvent = new ForceDrinkEvent(userUid, drink, drinkSolution), TargetFinishedEvent = new ForceDrinkEvent(userUid, drink, drinkSolution),
BroadcastCancelledEvent = new ForceDrinkCancelledEvent(drink) BroadcastCancelledEvent = new ForceDrinkCancelledEvent(drink),
}); });
// logging // logging
@@ -359,7 +387,6 @@ namespace Content.Server.Nutrition.EntitySystems
var drinkable = EntityManager.GetEntity(uid); var drinkable = EntityManager.GetEntity(uid);
_logSystem.Add(LogType.ForceFeed, LogImpact.Medium, $"{user} is forcing {target} to drink {drinkable}"); _logSystem.Add(LogType.ForceFeed, LogImpact.Medium, $"{user} is forcing {target} to drink {drinkable}");
drink.InUse = true;
return true; return true;
} }
@@ -368,7 +395,10 @@ namespace Content.Server.Nutrition.EntitySystems
/// </summary> /// </summary>
private void OnForceDrink(EntityUid uid, SharedBodyComponent body, ForceDrinkEvent args) 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 transferAmount = FixedPoint2.Min(args.Drink.TransferAmount, args.DrinkSolution.DrainAvailable);
var drained = _solutionContainerSystem.Drain(args.Drink.OwnerUid, args.DrinkSolution, transferAmount); var drained = _solutionContainerSystem.Drain(args.Drink.OwnerUid, args.DrinkSolution, transferAmount);
@@ -414,7 +444,7 @@ namespace Content.Server.Nutrition.EntitySystems
private void OnForceDrinkCancelled(ForceDrinkCancelledEvent args) private void OnForceDrinkCancelled(ForceDrinkCancelledEvent args)
{ {
args.Drink.InUse = false; args.Drink.CancelToken = null;
} }
} }
} }

View File

@@ -9,7 +9,6 @@ using Content.Server.Popups;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Administration.Logs; using Content.Shared.Administration.Logs;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
@@ -23,10 +22,10 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Player; using Robust.Shared.Player;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Content.Server.Inventory.Components; using Content.Server.Inventory.Components;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Hands;
namespace Content.Server.Nutrition.EntitySystems namespace Content.Server.Nutrition.EntitySystems
{ {
@@ -50,12 +49,26 @@ namespace Content.Server.Nutrition.EntitySystems
SubscribeLocalEvent<FoodComponent, UseInHandEvent>(OnUseFoodInHand); SubscribeLocalEvent<FoodComponent, UseInHandEvent>(OnUseFoodInHand);
SubscribeLocalEvent<FoodComponent, AfterInteractEvent>(OnFeedFood); SubscribeLocalEvent<FoodComponent, AfterInteractEvent>(OnFeedFood);
SubscribeLocalEvent<FoodComponent, HandDeselectedEvent>(OnFoodDeselected);
SubscribeLocalEvent<FoodComponent, GetInteractionVerbsEvent>(AddEatVerb); SubscribeLocalEvent<FoodComponent, GetInteractionVerbsEvent>(AddEatVerb);
SubscribeLocalEvent<SharedBodyComponent, ForceFeedEvent>(OnForceFeed); SubscribeLocalEvent<SharedBodyComponent, ForceFeedEvent>(OnForceFeed);
SubscribeLocalEvent<ForceFeedCancelledEvent>(OnForceFeedCancelled); SubscribeLocalEvent<ForceFeedCancelledEvent>(OnForceFeedCancelled);
SubscribeLocalEvent<InventoryComponent, IngestionAttemptEvent>(OnInventoryIngestAttempt); SubscribeLocalEvent<InventoryComponent, IngestionAttemptEvent>(OnInventoryIngestAttempt);
} }
/// <summary>
/// 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.
/// </summary>
private void OnFoodDeselected(EntityUid uid, FoodComponent component, HandDeselectedEvent args)
{
if (component.CancelToken != null)
{
component.CancelToken.Cancel();
component.CancelToken = null;
}
}
/// <summary> /// <summary>
/// Eat item /// Eat item
/// </summary> /// </summary>
@@ -120,6 +133,14 @@ namespace Content.Server.Nutrition.EntitySystems
if (!Resolve(uid, ref component)) if (!Resolve(uid, ref component))
return false; 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 if (uid == userUid || //Suppresses self-eating
EntityManager.TryGetComponent<MobStateComponent>(uid, out var mobState) && mobState.IsAlive()) // Suppresses eating alive mobs EntityManager.TryGetComponent<MobStateComponent>(uid, out var mobState) && mobState.IsAlive()) // Suppresses eating alive mobs
return false; return false;
@@ -214,6 +235,9 @@ namespace Content.Server.Nutrition.EntitySystems
private void AddEatVerb(EntityUid uid, FoodComponent component, GetInteractionVerbsEvent ev) private void AddEatVerb(EntityUid uid, FoodComponent component, GetInteractionVerbsEvent ev)
{ {
if (component.CancelToken != null)
return;
if (uid == ev.UserUid || if (uid == ev.UserUid ||
!ev.CanInteract || !ev.CanInteract ||
!ev.CanAccess || !ev.CanAccess ||
@@ -244,6 +268,14 @@ namespace Content.Server.Nutrition.EntitySystems
if (!Resolve(uid, ref food)) if (!Resolve(uid, ref food))
return false; 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<SharedBodyComponent>(targetUid)) if (!EntityManager.HasComponent<SharedBodyComponent>(targetUid))
return false; return false;
@@ -270,7 +302,8 @@ namespace Content.Server.Nutrition.EntitySystems
_popupSystem.PopupEntity(Loc.GetString("food-system-force-feed", ("user", userName)), _popupSystem.PopupEntity(Loc.GetString("food-system-force-feed", ("user", userName)),
userUid, Filter.Entities(targetUid)); 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, BreakOnUserMove = true,
BreakOnDamage = true, BreakOnDamage = true,
@@ -287,13 +320,15 @@ namespace Content.Server.Nutrition.EntitySystems
var edible = EntityManager.GetEntity(uid); var edible = EntityManager.GetEntity(uid);
_logSystem.Add(LogType.ForceFeed, LogImpact.Medium, $"{user} is forcing {target} to eat {edible}"); _logSystem.Add(LogType.ForceFeed, LogImpact.Medium, $"{user} is forcing {target} to eat {edible}");
food.InUse = true;
return true; return true;
} }
private void OnForceFeed(EntityUid uid, SharedBodyComponent body, ForceFeedEvent args) 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<StomachComponent>(uid, out var stomachs, body)) if (!_bodySystem.TryGetComponentsOnMechanisms<StomachComponent>(uid, out var stomachs, body))
return; return;
@@ -433,7 +468,7 @@ namespace Content.Server.Nutrition.EntitySystems
private void OnForceFeedCancelled(ForceFeedCancelledEvent args) private void OnForceFeedCancelled(ForceFeedCancelledEvent args)
{ {
args.Food.InUse = false; args.Food.CancelToken = null;
} }
/// <summary> /// <summary>