base.Initialize();
SubscribeLocalEvent<BotanySwabComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<BotanySwabComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<BotanySwabComponent, DoAfterEvent>(OnDoAfter);
}
/// <summary>
/// </summary>
private void OnAfterInteract(EntityUid uid, BotanySwabComponent swab, AfterInteractEvent args)
{
- if (args.Target == null || !args.CanReach)
+ if (args.Target == null || !args.CanReach || !HasComp<PlantHolderComponent>(args.Target))
return;
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, target: args.Target, used: uid)
/// <summary>
/// Save seed data or cross-pollenate.
/// </summary>
- private void OnDoAfter(DoAfterEvent args)
+ private void OnDoAfter(EntityUid uid, BotanySwabComponent component, DoAfterEvent args)
{
if (args.Cancelled || args.Handled || !TryComp<PlantHolderComponent>(args.Args.Target, out var plant) || !TryComp<BotanySwabComponent>(args.Args.Used, out var swab))
return;
private void OnInjectDoAfter(EntityUid uid, InjectorComponent component, DoAfterEvent args)
{
- if (args.Handled || args.Cancelled || args.Args.Target == null)
+ if (args.Cancelled)
+ {
+ component.IsInjecting = false;
+ return;
+ }
+
+ if (args.Handled || args.Args.Target == null)
return;
UseInjector(args.Args.Target.Value, args.Args.User, uid, component);
+ component.IsInjecting = false;
args.Handled = true;
}
if (!_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution))
return;
+ //If it found it's injecting
+ if (component.IsInjecting)
+ return;
+
var actualDelay = MathF.Max(component.Delay, 1f);
// Injections take 1 second longer per additional 5u
actualDelay += (float) component.TransferAmount / component.Delay - 1;
- if (user != target)
+ var isTarget = user != target;
+
+ if (isTarget)
{
// Create a pop-up for the target
var userName = Identity.Entity(user, EntityManager);
_adminLogger.Add(LogType.Ingestion, $"{EntityManager.ToPrettyString(user):user} is attempting to inject themselves with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}.");
}
+ component.IsInjecting = true;
+
_doAfter.DoAfter(new DoAfterEventArgs(user, actualDelay, target:target, used:injector)
{
+ RaiseOnTarget = isTarget,
+ RaiseOnUser = !isTarget,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
// Private Events
SubscribeLocalEvent<DiseaseDiagnoserComponent, DiseaseMachineFinishedEvent>(OnDiagnoserFinished);
SubscribeLocalEvent<DiseaseVaccineCreatorComponent, DiseaseMachineFinishedEvent>(OnVaccinatorFinished);
- SubscribeLocalEvent<DoAfterEvent>(OnSwabDoAfter);
+ SubscribeLocalEvent<DiseaseSwabComponent, DoAfterEvent>(OnSwabDoAfter);
}
private Queue<EntityUid> AddQueue = new();
/// </summary>
private void OnAfterInteract(EntityUid uid, DiseaseSwabComponent swab, AfterInteractEvent args)
{
- if (args.Target == null || !args.CanReach)
+ if (args.Target == null || !args.CanReach || !HasComp<DiseaseCarrierComponent>(args.Target))
return;
if (swab.Used)
return;
}
+ var isTarget = args.User != args.Target;
+
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, target: args.Target, used: uid)
{
+ RaiseOnTarget = isTarget,
+ RaiseOnUser = !isTarget,
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
/// Copies a disease prototype to the swab
/// after the doafter completes.
/// </summary>
- private void OnSwabDoAfter(DoAfterEvent args)
+ private void OnSwabDoAfter(EntityUid uid, DiseaseSwabComponent component, DoAfterEvent args)
{
- if (args.Handled || args.Cancelled || !TryComp<DiseaseCarrierComponent>(args.Args.Target, out var carrier) || !TryComp<DiseaseSwabComponent>(args.Args.Target, out var swab))
+ if (args.Handled || args.Cancelled || !TryComp<DiseaseCarrierComponent>(args.Args.Target, out var carrier) || !TryComp<DiseaseSwabComponent>(args.Args.Used, out var swab))
return;
swab.Used = true;
return;
swab.Disease = _random.Pick(carrier.Diseases);
-
}
[DataField("delay")]
public float Delay = 3f;
+ /// <summary>
+ /// Cancel token to prevent rapid healing
+ /// </summary>
+ [DataField("cancelToken")]
+ public CancellationTokenSource? CancelToken;
+
/// <summary>
/// Delay multiplier when healing yourself.
/// </summary>
+using System.Threading;
using Content.Server.Administration.Logs;
using Content.Server.Body.Systems;
using Content.Server.DoAfter;
private void OnDoAfter(EntityUid uid, DamageableComponent component, DoAfterEvent<HealingData> args)
{
+ if (args.Cancelled)
+ {
+ args.AdditionalData.HealingComponent.CancelToken = null;
+ return;
+ }
+
if (args.Handled || args.Cancelled || _mobStateSystem.IsDead(uid) || args.Args.Used == null)
return;
if (args.AdditionalData.HealingComponent.HealingEndSound != null)
_audio.PlayPvs(args.AdditionalData.HealingComponent.HealingEndSound, uid, AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
+ args.AdditionalData.HealingComponent.CancelToken = null;
args.Handled = true;
}
private bool TryHeal(EntityUid uid, EntityUid user, EntityUid target, HealingComponent component)
{
- if (_mobStateSystem.IsDead(target) || !TryComp<DamageableComponent>(target, out var targetDamage))
+ if (_mobStateSystem.IsDead(target) || !TryComp<DamageableComponent>(target, out var targetDamage) || component.CancelToken != null)
return false;
if (targetDamage.TotalDamage == 0)
if (component.HealingBeginSound != null)
_audio.PlayPvs(component.HealingBeginSound, uid, AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
- var delay = user != target
+ var isNotSelf = user != target;
+
+ var delay = isNotSelf
? component.Delay
: component.Delay * GetScaledHealingPenalty(user, component);
+ component.CancelToken = new CancellationTokenSource();
+
var healingData = new HealingData(component, stack);
- var doAfterEventArgs = new DoAfterEventArgs(user, delay, target: target, used: uid)
+ var doAfterEventArgs = new DoAfterEventArgs(user, delay, cancelToken: component.CancelToken.Token,target: target, used: uid)
{
+ //Raise the event on the target if it's not self, otherwise raise it on self.
+ RaiseOnTarget = isNotSelf,
+ RaiseOnUser = !isNotSelf,
BreakOnUserMove = true,
BreakOnTargetMove = true,
// Didn't break on damage as they may be trying to prevent it and
[DataField("burstSound")]
public SoundSpecifier BurstSound = new SoundPathSpecifier("/Audio/Effects/flash_bang.ogg");
+ /// <summary>
+ /// Is this drink being forced on someone else?
+ /// </summary>
+ /// <returns></returns>
+ [DataField("forceDrink")]
+ public bool ForceDrink;
+
/// <summary>
/// How long it takes to drink this yourself.
/// </summary>
[DataField("eatMessage")]
public string EatMessage = "food-nom";
+ /// <summary>
+ /// Is this entity being forcefed?
+ /// Prevents the entity from being forced to eat multiple times if not self
+ /// </summary>
+ [DataField("forceFeed")]
+ public bool ForceFeed;
+
/// <summary>
/// How long it takes to eat the food personally.
/// </summary>
private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item)
{
- if (!EntityManager.HasComponent<BodyComponent>(target))
+ if (!EntityManager.HasComponent<BodyComponent>(target) || drink.ForceDrink)
return false;
if (!drink.Opened)
if (!_interactionSystem.InRangeUnobstructed(user, item, popup: true))
return true;
- var forceDrink = user != target;
+ drink.ForceDrink = user != target;
- if (forceDrink)
+ if (drink.ForceDrink)
{
var userName = Identity.Entity(user, EntityManager);
var drinkData = new DrinkData(drinkSolution, flavors);
- var doAfterEventArgs = new DoAfterEventArgs(user, forceDrink ? drink.ForceFeedDelay : drink.Delay,
+ var doAfterEventArgs = new DoAfterEventArgs(user, drink.ForceDrink ? drink.ForceFeedDelay : drink.Delay,
target: target, used: item)
{
BreakOnUserMove = moveBreak,
/// </summary>
private void OnDoAfter(EntityUid uid, DrinkComponent component, DoAfterEvent<DrinkData> args)
{
+ //Special cancel if they're force feeding someone.
+ //Allows self to drink multiple times but prevents force feeding drinks to others rapidly.
+ if (args.Cancelled && component.ForceDrink)
+ {
+ component.ForceDrink = false;
+ return;
+ }
+
if (args.Handled || args.Cancelled || component.Deleted)
return;
var transferAmount = FixedPoint2.Min(component.TransferAmount, args.AdditionalData.DrinkSolution.Volume);
var drained = _solutionContainerSystem.Drain(uid, args.AdditionalData.DrinkSolution, transferAmount);
- var forceDrink = args.Args.Target.Value != args.Args.User;
+ //var forceDrink = args.Args.Target.Value != args.Args.User;
if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(args.Args.Target.Value, out var stomachs, body))
{
- _popupSystem.PopupEntity(forceDrink ? Loc.GetString("drink-component-try-use-drink-cannot-drink-other") : Loc.GetString("drink-component-try-use-drink-had-enough"), args.Args.Target.Value, args.Args.User);
+ _popupSystem.PopupEntity(component.ForceDrink ? Loc.GetString("drink-component-try-use-drink-cannot-drink-other") : Loc.GetString("drink-component-try-use-drink-had-enough"), args.Args.Target.Value, args.Args.User);
if (HasComp<RefillableSolutionComponent>(args.Args.Target.Value))
{
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough"), args.Args.Target.Value, args.Args.Target.Value);
- if (forceDrink)
+ if (component.ForceDrink)
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough-other"), args.Args.Target.Value, args.Args.User);
_spillableSystem.SpillAt(args.Args.Target.Value, drained, "PuddleSmear");
var flavors = args.AdditionalData.FlavorMessage;
- if (forceDrink)
+ if (component.ForceDrink)
{
var targetName = Identity.Entity(args.Args.Target.Value, EntityManager);
var userName = Identity.Entity(args.Args.User, EntityManager);
//TODO: Grab the stomach UIDs somehow without using Owner
_stomachSystem.TryTransferSolution(firstStomach.Value.Comp.Owner, drained, firstStomach.Value.Comp);
+ component.ForceDrink = false;
args.Handled = true;
}
if (food == user || EntityManager.TryGetComponent<MobStateComponent>(food, out var mobState) && _mobStateSystem.IsAlive(food, mobState)) // Suppresses eating alive mobs
return false;
- // Target can't be fed
- if (!EntityManager.HasComponent<BodyComponent>(target))
+ // Target can't be fed or they're already forcefeeding
+ if (!EntityManager.HasComponent<BodyComponent>(target) || foodComp.ForceFeed)
return false;
if (!_solutionContainerSystem.TryGetSolution(food, foodComp.SolutionName, out var foodSolution))
if (!_interactionSystem.InRangeUnobstructed(user, food, popup: true))
return true;
- var forceFeed = user != target;
+ foodComp.ForceFeed = user != target;
- if (forceFeed)
+ if (foodComp.ForceFeed)
{
var userName = Identity.Entity(user, EntityManager);
_popupSystem.PopupEntity(Loc.GetString("food-system-force-feed", ("user", userName)),
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is eating {ToPrettyString(food):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}");
}
- var moveBreak = user != target;
-
var foodData = new FoodData(foodSolution, flavors, utensils);
- var doAfterEventArgs = new DoAfterEventArgs(user, forceFeed ? foodComp.ForceFeedDelay : foodComp.Delay, target: target, used: food)
+ var doAfterEventArgs = new DoAfterEventArgs(user, foodComp.ForceFeed ? foodComp.ForceFeedDelay : foodComp.Delay, target: target, used: food)
{
- BreakOnUserMove = moveBreak,
+ RaiseOnTarget = foodComp.ForceFeed,
+ RaiseOnUser = !foodComp.ForceFeed,
+ BreakOnUserMove = foodComp.ForceFeed,
BreakOnDamage = true,
BreakOnStun = true,
- BreakOnTargetMove = moveBreak,
+ BreakOnTargetMove = foodComp.ForceFeed,
MovementThreshold = 0.01f,
DistanceThreshold = 1.0f,
NeedHand = true
private void OnDoAfter(EntityUid uid, FoodComponent component, DoAfterEvent<FoodData> args)
{
+ //Prevents the target from being force fed food but allows the user to chow down
+ if (args.Cancelled && component.ForceFeed)
+ {
+ component.ForceFeed = false;
+ return;
+ }
+
if (args.Cancelled || args.Handled || component.Deleted || args.Args.Target == null)
return;
//TODO: Get the stomach UID somehow without nabbing owner
var firstStomach = stomachs.FirstOrNull(stomach => _stomachSystem.CanTransferSolution(stomach.Comp.Owner, split));
- var forceFeed = args.Args.Target.Value != args.Args.User;
-
// No stomach so just popup a message that they can't eat.
if (firstStomach == null)
{
_solutionContainerSystem.TryAddSolution(uid, args.AdditionalData.FoodSolution, split);
- _popupSystem.PopupEntity(forceFeed ? Loc.GetString("food-system-you-cannot-eat-any-more-other") : Loc.GetString("food-system-you-cannot-eat-any-more"), args.Args.Target.Value, args.Args.User);
+ _popupSystem.PopupEntity(component.ForceFeed ? Loc.GetString("food-system-you-cannot-eat-any-more-other") : Loc.GetString("food-system-you-cannot-eat-any-more"), args.Args.Target.Value, args.Args.User);
args.Handled = true;
return;
}
var flavors = args.AdditionalData.FlavorMessage;
- if (forceFeed)
+ if (component.ForceFeed)
{
var targetName = Identity.Entity(args.Args.Target.Value, EntityManager);
var userName = Identity.Entity(args.Args.User, EntityManager);
[DataField("delay")]
public float Delay = 1f;
+
+ [DataField("cancelToken")]
+ public CancellationTokenSource? CancelToken;
}
}
{
SubscribeLocalEvent<TilePryingComponent, AfterInteractEvent>(OnTilePryingAfterInteract);
SubscribeLocalEvent<TilePryingComponent, TilePryingCompleteEvent>(OnTilePryComplete);
+ SubscribeLocalEvent<TilePryingComponent, TilePryingCancelledEvent>(OnTilePryCancelled);
+ }
+
+ private void OnTilePryingAfterInteract(EntityUid uid, TilePryingComponent component, AfterInteractEvent args)
+ {
+ if (args.Handled || !args.CanReach || (args.Target != null && !HasComp<PuddleComponent>(args.Target))) return;
+
+ if (TryPryTile(uid, args.User, component, args.ClickLocation))
+ args.Handled = true;
}
private void OnTilePryComplete(EntityUid uid, TilePryingComponent component, TilePryingCompleteEvent args)
{
+ component.CancelToken = null;
var gridUid = args.Coordinates.GetGridUid(EntityManager);
if (!_mapManager.TryGetGrid(gridUid, out var grid))
{
_tile.PryTile(tile);
}
- private void OnTilePryingAfterInteract(EntityUid uid, TilePryingComponent component, AfterInteractEvent args)
+ private void OnTilePryCancelled(EntityUid uid, TilePryingComponent component, TilePryingCancelledEvent args)
{
- if (args.Handled || !args.CanReach || (args.Target != null && !HasComp<PuddleComponent>(args.Target))) return;
-
- if (TryPryTile(uid, args.User, component, args.ClickLocation))
- args.Handled = true;
+ component.CancelToken = null;
}
private bool TryPryTile(EntityUid toolEntity, EntityUid user, TilePryingComponent component, EntityCoordinates clickLocation)
{
- if (!TryComp<ToolComponent?>(toolEntity, out var tool) && component.ToolComponentNeeded)
+ if (!TryComp<ToolComponent?>(toolEntity, out var tool) && component.ToolComponentNeeded || component.CancelToken != null)
return false;
if (!_mapManager.TryGetGrid(clickLocation.GetGridUid(EntityManager), out var mapGrid))
if (!tileDef.CanCrowbar)
return false;
- var toolEvData = new ToolEventData(new TilePryingCompleteEvent(clickLocation), targetEntity:toolEntity);
+ component.CancelToken = new CancellationTokenSource();
+
+ var toolEvData = new ToolEventData(new TilePryingCompleteEvent(clickLocation), cancelledEv:new TilePryingCancelledEvent() ,targetEntity:toolEntity);
- if (!UseTool(toolEntity, user, null, component.Delay, new[] { component.QualityNeeded }, toolEvData, toolComponent: tool))
+ if (!UseTool(toolEntity, user, null, component.Delay, new[] { component.QualityNeeded }, toolEvData, toolComponent: tool, cancelToken: component.CancelToken))
return false;
return true;
[NetworkedComponent, ComponentProtoName("Injector")]
public abstract class SharedInjectorComponent : Component
{
+ /// <summary>
+ /// Checks to see if the entity being injected
+ /// </summary>
+ [DataField("isInjecting")]
+ public bool IsInjecting;
+
/// <summary>
/// Component data used for net updates. Used by client for item status ui
/// </summary>
using System.Linq;
+using System.Threading;
using Content.Shared.Audio;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
private void OnDoAfter(EntityUid uid, ToolComponent component, DoAfterEvent<ToolEventData> args)
{
- if (args.Handled || args.Cancelled || args.AdditionalData.Ev == null)
+ if (args.Handled || args.AdditionalData.Ev == null)
return;
- if (ToolFinishUse(uid, args.Args.User, args.AdditionalData.Fuel))
+ if (args.Cancelled)
{
- if (args.AdditionalData.TargetEntity != null)
- RaiseLocalEvent(args.AdditionalData.TargetEntity.Value, args.AdditionalData.Ev);
- else
- RaiseLocalEvent(args.AdditionalData.Ev);
+ if (args.AdditionalData.CancelledEv != null)
+ {
+ if (args.AdditionalData.TargetEntity != null)
+ RaiseLocalEvent(args.AdditionalData.TargetEntity.Value, args.AdditionalData.CancelledEv);
+ else
+ RaiseLocalEvent(args.AdditionalData.CancelledEv);
- args.Handled = true;
+ args.Handled = true;
+ }
+
+ return;
}
- else if (args.AdditionalData.CancelledEv != null)
+
+ if (ToolFinishUse(uid, args.Args.User, args.AdditionalData.Fuel))
{
if (args.AdditionalData.TargetEntity != null)
- RaiseLocalEvent(args.AdditionalData.TargetEntity.Value, args.AdditionalData.CancelledEv);
+ RaiseLocalEvent(args.AdditionalData.TargetEntity.Value, args.AdditionalData.Ev);
else
- RaiseLocalEvent(args.AdditionalData.CancelledEv);
+ RaiseLocalEvent(args.AdditionalData.Ev);
args.Handled = true;
}
}
- public bool UseTool(EntityUid tool, EntityUid user, EntityUid? target, float doAfterDelay, IEnumerable<string> toolQualitiesNeeded, ToolEventData toolEventData, float fuel = 0f, ToolComponent? toolComponent = null, Func<bool>? doAfterCheck = null)
+ public bool UseTool(EntityUid tool, EntityUid user, EntityUid? target, float doAfterDelay, IEnumerable<string> toolQualitiesNeeded, ToolEventData toolEventData, float fuel = 0f, ToolComponent? toolComponent = null, Func<bool>? doAfterCheck = null, CancellationTokenSource? cancelToken = null)
{
// No logging here, after all that'd mean the caller would need to check if the component is there or not.
if (!Resolve(tool, ref toolComponent, false))
if (doAfterDelay > 0f)
{
- var doAfterArgs = new DoAfterEventArgs(user, doAfterDelay / toolComponent.SpeedModifier, target:target, used:tool)
+ var doAfterArgs = new DoAfterEventArgs(user, doAfterDelay / toolComponent.SpeedModifier, cancelToken:cancelToken?.Token ?? default, target:target, used:tool)
{
ExtraCheck = doAfterCheck,
BreakOnDamage = true,