Fix food & do-after bugs (#5716)
This commit is contained in:
@@ -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;
|
||||
|
||||
/// <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>
|
||||
public bool InUse = false;
|
||||
public CancellationTokenSource? CancelToken;
|
||||
|
||||
private InjectorToggleMode _toggleState;
|
||||
|
||||
@@ -127,8 +129,11 @@ namespace Content.Server.Chemistry.Components
|
||||
/// <param name="eventArgs"></param>
|
||||
async Task<bool> 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
|
||||
/// </summary>
|
||||
public async Task<bool> TryInjectDoAfter(EntityUid user, EntityUid target)
|
||||
{
|
||||
InUse = true;
|
||||
var popupSys = EntitySystem.Get<SharedPopupSystem>();
|
||||
|
||||
// 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<DoAfterSystem>().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;
|
||||
}
|
||||
|
||||
@@ -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<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)
|
||||
|
||||
@@ -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<DoAfter> _cancelled = new();
|
||||
private readonly List<DoAfter> _finished = new();
|
||||
private readonly Queue<DoAfter> _cancelled = new();
|
||||
private readonly Queue<DoAfter> _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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <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>
|
||||
public bool InUse = false;
|
||||
public CancellationTokenSource? CancelToken;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <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>
|
||||
public bool InUse = false;
|
||||
public CancellationTokenSource? CancelToken;
|
||||
|
||||
[ViewVariables]
|
||||
public int UsesRemaining
|
||||
|
||||
@@ -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<DrinkComponent, ComponentInit>(OnDrinkInit);
|
||||
SubscribeLocalEvent<DrinkComponent, LandEvent>(HandleLand);
|
||||
SubscribeLocalEvent<DrinkComponent, UseInHandEvent>(OnUse);
|
||||
SubscribeLocalEvent<DrinkComponent, HandDeselectedEvent>(OnDrinkDeselected);
|
||||
SubscribeLocalEvent<DrinkComponent, AfterInteractEvent>(AfterInteract);
|
||||
SubscribeLocalEvent<DrinkComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<SharedBodyComponent, ForceDrinkEvent>(OnForceDrink);
|
||||
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)
|
||||
{
|
||||
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<SharedBodyComponent>(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
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<FoodComponent, UseInHandEvent>(OnUseFoodInHand);
|
||||
SubscribeLocalEvent<FoodComponent, AfterInteractEvent>(OnFeedFood);
|
||||
SubscribeLocalEvent<FoodComponent, HandDeselectedEvent>(OnFoodDeselected);
|
||||
SubscribeLocalEvent<FoodComponent, GetInteractionVerbsEvent>(AddEatVerb);
|
||||
SubscribeLocalEvent<SharedBodyComponent, ForceFeedEvent>(OnForceFeed);
|
||||
SubscribeLocalEvent<ForceFeedCancelledEvent>(OnForceFeedCancelled);
|
||||
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>
|
||||
/// Eat item
|
||||
/// </summary>
|
||||
@@ -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<MobStateComponent>(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<SharedBodyComponent>(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<StomachComponent>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user