+++ /dev/null
-using Content.Shared.DoAfter;
-
-namespace Content.Client.DoAfter
-{
- [RegisterComponent, Access(typeof(DoAfterSystem))]
- public sealed class DoAfterComponent : SharedDoAfterComponent
- {
- public readonly Dictionary<byte, ClientDoAfter> DoAfters = new();
-
- public readonly Dictionary<byte, ClientDoAfter> CancelledDoAfters = new();
- }
-}
-using Content.Client.Resources;
+using Content.Shared.DoAfter;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
var index = 0;
var worldMatrix = Matrix3.CreateTranslation(worldPosition);
- foreach (var (_, doAfter) in comp.DoAfters)
+ foreach (var doAfter in comp.DoAfters.Values)
{
- var elapsed = doAfter.Accumulator;
+ var elapsed = doAfter.Elapsed;
var displayRatio = MathF.Min(1.0f,
- elapsed / doAfter.Delay);
+ (float)elapsed.TotalSeconds / doAfter.Delay);
Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
// if we're cancelled then flick red / off.
if (cancelled)
{
- var flash = Math.Floor(doAfter.CancelledAccumulator / flashTime) % 2 == 0;
+ var flash = Math.Floor((float)doAfter.CancelledElapsed.TotalSeconds / flashTime) % 2 == 0;
color = new Color(1f, 0f, 0f, flash ? 1f : 0f);
}
else
using Content.Shared.DoAfter;
-using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
-using Robust.Shared.Timing;
using Robust.Shared.Utility;
-namespace Content.Client.DoAfter
+namespace Content.Client.DoAfter;
+
+/// <summary>
+/// Handles events that need to happen after a certain amount of time where the event could be cancelled by factors
+/// such as moving.
+/// </summary>
+public sealed class DoAfterSystem : SharedDoAfterSystem
{
+ [Dependency] private readonly IOverlayManager _overlay = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
/// <summary>
- /// Handles events that need to happen after a certain amount of time where the event could be cancelled by factors
- /// such as moving.
+ /// We'll use an excess time so stuff like finishing effects can show.
/// </summary>
- [UsedImplicitly]
- public sealed class DoAfterSystem : EntitySystem
- {
- [Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly IOverlayManager _overlay = default!;
- [Dependency] private readonly IPlayerManager _player = default!;
- [Dependency] private readonly IPrototypeManager _prototype = default!;
+ public const float ExcessTime = 0.5f;
- /// <summary>
- /// We'll use an excess time so stuff like finishing effects can show.
- /// </summary>
- public const float ExcessTime = 0.5f;
+ public override void Initialize()
+ {
+ base.Initialize();
+ UpdatesOutsidePrediction = true;
+ SubscribeNetworkEvent<CancelledDoAfterMessage>(OnCancelledDoAfter);
+ SubscribeLocalEvent<DoAfterComponent, ComponentHandleState>(OnDoAfterHandleState);
+ _overlay.AddOverlay(new DoAfterOverlay(EntityManager, _prototype));
+ }
- public override void Initialize()
- {
- base.Initialize();
- UpdatesOutsidePrediction = true;
- SubscribeNetworkEvent<CancelledDoAfterMessage>(OnCancelledDoAfter);
- SubscribeLocalEvent<DoAfterComponent, ComponentHandleState>(OnDoAfterHandleState);
- _overlay.AddOverlay(
- new DoAfterOverlay(
- EntityManager,
- _prototype));
- }
+ public override void Shutdown()
+ {
+ base.Shutdown();
+ _overlay.RemoveOverlay<DoAfterOverlay>();
+ }
- public override void Shutdown()
- {
- base.Shutdown();
- _overlay.RemoveOverlay<DoAfterOverlay>();
- }
+ private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent component, ref ComponentHandleState args)
+ {
+ if (args.Current is not DoAfterComponentState state)
+ return;
- private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent component, ref ComponentHandleState args)
+ foreach (var (_, doAfter) in state.DoAfters)
{
- if (args.Current is not DoAfterComponentState state)
- return;
+ if (component.DoAfters.ContainsKey(doAfter.ID))
+ continue;
- var toRemove = new RemQueue<ClientDoAfter>();
-
- foreach (var (id, doAfter) in component.DoAfters)
- {
- var found = false;
-
- foreach (var clientdoAfter in state.DoAfters)
- {
- if (clientdoAfter.ID == id)
- {
- found = true;
- break;
- }
- }
-
- if (!found)
- {
- toRemove.Add(doAfter);
- }
- }
-
- foreach (var doAfter in toRemove)
- {
- Remove(component, doAfter);
- }
-
- foreach (var doAfter in state.DoAfters)
- {
- if (component.DoAfters.ContainsKey(doAfter.ID))
- continue;
-
- component.DoAfters.Add(doAfter.ID, doAfter);
- }
+ component.DoAfters.Add(doAfter.ID, doAfter);
}
+ }
- private void OnCancelledDoAfter(CancelledDoAfterMessage ev)
- {
- if (!TryComp<DoAfterComponent>(ev.Uid, out var doAfter))
- return;
+ private void OnCancelledDoAfter(CancelledDoAfterMessage ev)
+ {
+ if (!TryComp<DoAfterComponent>(ev.Uid, out var doAfter))
+ return;
- Cancel(doAfter, ev.ID);
- }
+ Cancel(doAfter, ev.ID);
+ }
- /// <summary>
- /// Remove a DoAfter without showing a cancellation graphic.
- /// </summary>
- public void Remove(DoAfterComponent component, ClientDoAfter clientDoAfter)
- {
- component.DoAfters.Remove(clientDoAfter.ID);
+ /// <summary>
+ /// Remove a DoAfter without showing a cancellation graphic.
+ /// </summary>
+ public void Remove(DoAfterComponent component, Shared.DoAfter.DoAfter doAfter, bool found = false)
+ {
+ component.DoAfters.Remove(doAfter.ID);
+ component.CancelledDoAfters.Remove(doAfter.ID);
+ }
- var found = false;
+ /// <summary>
+ /// Mark a DoAfter as cancelled and show a cancellation graphic.
+ /// </summary>
+ /// Actual removal is handled by DoAfterEntitySystem.
+ public void Cancel(DoAfterComponent component, byte id)
+ {
+ if (component.CancelledDoAfters.ContainsKey(id))
+ return;
- component.CancelledDoAfters.Remove(clientDoAfter.ID);
+ if (!component.DoAfters.ContainsKey(id))
+ return;
- if (!found)
- component.DoAfters.Remove(clientDoAfter.ID);
- }
-
- /// <summary>
- /// Mark a DoAfter as cancelled and show a cancellation graphic.
- /// </summary>
- /// Actual removal is handled by DoAfterEntitySystem.
- public void Cancel(DoAfterComponent component, byte id)
- {
- if (component.CancelledDoAfters.ContainsKey(id))
- return;
+ var doAfterMessage = component.DoAfters[id];
+ doAfterMessage.Cancelled = true;
+ doAfterMessage.CancelledTime = GameTiming.CurTime;
+ component.CancelledDoAfters.Add(id, doAfterMessage);
+ }
- if (!component.DoAfters.ContainsKey(id))
- return;
+ // TODO separate DoAfter & ActiveDoAfter components for the entity query.
+ public override void Update(float frameTime)
+ {
+ if (!GameTiming.IsFirstTimePredicted)
+ return;
- var doAfterMessage = component.DoAfters[id];
- doAfterMessage.Cancelled = true;
- component.CancelledDoAfters.Add(id, doAfterMessage);
- }
+ var playerEntity = _player.LocalPlayer?.ControlledEntity;
- // TODO separate DoAfter & ActiveDoAfter components for the entity query.
- public override void Update(float frameTime)
+ foreach (var (comp, xform) in EntityQuery<DoAfterComponent, TransformComponent>())
{
- if (!_gameTiming.IsFirstTimePredicted)
- return;
+ var doAfters = comp.DoAfters;
- var playerEntity = _player.LocalPlayer?.ControlledEntity;
+ if (doAfters.Count == 0)
+ continue;
- foreach (var (comp, xform) in EntityQuery<DoAfterComponent, TransformComponent>())
- {
- var doAfters = comp.DoAfters;
+ var userGrid = xform.Coordinates;
+ var toRemove = new RemQueue<Shared.DoAfter.DoAfter>();
- if (doAfters.Count == 0)
+ // Check cancellations / finishes
+ foreach (var (id, doAfter) in doAfters)
+ {
+ // If we've passed the final time (after the excess to show completion graphic) then remove.
+ if ((float)doAfter.Elapsed.TotalSeconds + (float)doAfter.CancelledElapsed.TotalSeconds >
+ doAfter.Delay + ExcessTime)
{
+ toRemove.Add(doAfter);
continue;
}
- var userGrid = xform.Coordinates;
- var toRemove = new RemQueue<ClientDoAfter>();
-
- // Check cancellations / finishes
- foreach (var (id, doAfter) in doAfters)
+ if (doAfter.Cancelled)
{
- // If we've passed the final time (after the excess to show completion graphic) then remove.
- if ((doAfter.Accumulator + doAfter.CancelledAccumulator) > doAfter.Delay + ExcessTime)
- {
- toRemove.Add(doAfter);
- continue;
- }
-
- if (doAfter.Cancelled)
- {
- doAfter.CancelledAccumulator += frameTime;
- continue;
- }
-
- doAfter.Accumulator += frameTime;
+ doAfter.CancelledElapsed = GameTiming.CurTime - doAfter.CancelledTime;
+ continue;
+ }
- // Well we finished so don't try to predict cancels.
- if (doAfter.Accumulator > doAfter.Delay)
- {
- continue;
- }
+ doAfter.Elapsed = GameTiming.CurTime - doAfter.StartTime;
- // Predictions
- if (comp.Owner != playerEntity)
- continue;
+ // Well we finished so don't try to predict cancels.
+ if ((float)doAfter.Elapsed.TotalSeconds > doAfter.Delay)
+ continue;
- // TODO: Add these back in when I work out some system for changing the accumulation rate
- // based on ping. Right now these would show as cancelled near completion if we moved at the end
- // despite succeeding.
+ // Predictions
+ if (comp.Owner != playerEntity)
continue;
- if (doAfter.BreakOnUserMove)
- {
- if (!userGrid.InRange(EntityManager, doAfter.UserGrid, doAfter.MovementThreshold))
- {
- Cancel(comp, id);
- continue;
- }
- }
+ // TODO: Add these back in when I work out some system for changing the accumulation rate
+ // based on ping. Right now these would show as cancelled near completion if we moved at the end
+ // despite succeeding.
+ continue;
- if (doAfter.BreakOnTargetMove)
+ if (doAfter.EventArgs.BreakOnUserMove)
+ {
+ if (!userGrid.InRange(EntityManager, doAfter.UserGrid, doAfter.EventArgs.MovementThreshold))
{
- if (!EntityManager.Deleted(doAfter.Target) &&
- !Transform(doAfter.Target.Value).Coordinates.InRange(EntityManager, doAfter.TargetGrid,
- doAfter.MovementThreshold))
- {
- Cancel(comp, id);
- continue;
- }
+ Cancel(comp, id);
+ continue;
}
}
- foreach (var doAfter in toRemove)
- {
- Remove(comp, doAfter);
- }
-
- // Remove cancelled DoAfters after ExcessTime has elapsed
- var toRemoveCancelled = new List<ClientDoAfter>();
-
- foreach (var (_, doAfter) in comp.CancelledDoAfters)
+ if (doAfter.EventArgs.BreakOnTargetMove)
{
- if (doAfter.CancelledAccumulator > ExcessTime)
+ if (!Deleted(doAfter.EventArgs.Target) &&
+ !Transform(doAfter.EventArgs.Target.Value).Coordinates.InRange(EntityManager,
+ doAfter.TargetGrid,
+ doAfter.EventArgs.MovementThreshold))
{
- toRemoveCancelled.Add(doAfter);
+ Cancel(comp, id);
+ continue;
}
}
+ }
- foreach (var doAfter in toRemoveCancelled)
- {
- Remove(comp, doAfter);
- }
+ foreach (var doAfter in toRemove)
+ {
+ Remove(comp, doAfter);
+ }
+
+ // Remove cancelled DoAfters after ExcessTime has elapsed
+ var toRemoveCancelled = new RemQueue<Shared.DoAfter.DoAfter>();
+
+ foreach (var (_, doAfter) in comp.CancelledDoAfters)
+ {
+ var cancelledElapsed = (float)doAfter.CancelledElapsed.TotalSeconds;
+
+ if (cancelledElapsed > ExcessTime)
+ toRemoveCancelled.Add(doAfter);
+ }
+
+ foreach (var doAfter in toRemoveCancelled)
+ {
+ Remove(comp, doAfter);
}
}
}
SubscribeLocalEvent<CryoPodComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<CryoPodComponent, GetVerbsEvent<AlternativeVerb>>(AddAlternativeVerbs);
SubscribeLocalEvent<CryoPodComponent, GotEmaggedEvent>(OnEmagged);
- SubscribeLocalEvent<CryoPodComponent, DoInsertCryoPodEvent>(DoInsertCryoPod);
- SubscribeLocalEvent<CryoPodComponent, DoInsertCancelledCryoPodEvent>(DoInsertCancelCryoPod);
SubscribeLocalEvent<CryoPodComponent, CryoPodPryFinished>(OnCryoPodPryFinished);
SubscribeLocalEvent<CryoPodComponent, CryoPodPryInterrupted>(OnCryoPodPryInterrupted);
using System.Threading;
using System.Threading.Tasks;
using Content.Server.DoAfter;
+using Content.Shared.DoAfter;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
- type: DoAfter
";
+ public sealed class TestDoAfterSystem : EntitySystem
+ {
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<DoAfterEvent<TestDoAfterData>>(OnTestDoAfterFinishEvent);
+ }
+
+ private void OnTestDoAfterFinishEvent(DoAfterEvent<TestDoAfterData> ev)
+ {
+ ev.AdditionalData.Cancelled = ev.Cancelled;
+ }
+ }
+
+ private sealed class TestDoAfterData
+ {
+ public bool Cancelled;
+ };
+
[Test]
public async Task TestFinished()
{
- Task<DoAfterStatus> task = null;
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes});
var server = pairTracker.Pair.Server;
await server.WaitIdleAsync();
- var mapManager = server.ResolveDependency<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem<DoAfterSystem>();
+ var data = new TestDoAfterData();
// That it finishes successfully
await server.WaitPost(() =>
var tickTime = 1.0f / IoCManager.Resolve<IGameTiming>().TickRate;
var mob = entityManager.SpawnEntity("Dummy", MapCoordinates.Nullspace);
var cancelToken = new CancellationTokenSource();
- var args = new DoAfterEventArgs(mob, tickTime / 2, cancelToken.Token);
- task = doAfterSystem.WaitDoAfter(args);
+ var args = new DoAfterEventArgs(mob, tickTime / 2, cancelToken.Token) { Broadcast = true };
+ doAfterSystem.DoAfter(args, data);
});
await server.WaitRunTicks(1);
- Assert.That(task.Status, Is.EqualTo(TaskStatus.RanToCompletion));
-#pragma warning disable RA0004
- Assert.That(task.Result == DoAfterStatus.Finished);
-#pragma warning restore RA0004
+ Assert.That(data.Cancelled, Is.False);
await pairTracker.CleanReturnAsync();
}
[Test]
public async Task TestCancelled()
{
- Task<DoAfterStatus> task = null;
-
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes});
var server = pairTracker.Pair.Server;
var entityManager = server.ResolveDependency<IEntityManager>();
- var mapManager = server.ResolveDependency<IMapManager>();
var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem<DoAfterSystem>();
+ var data = new TestDoAfterData();
await server.WaitPost(() =>
{
var mob = entityManager.SpawnEntity("Dummy", MapCoordinates.Nullspace);
var cancelToken = new CancellationTokenSource();
- var args = new DoAfterEventArgs(mob, tickTime * 2, cancelToken.Token);
- task = doAfterSystem.WaitDoAfter(args);
+ var args = new DoAfterEventArgs(mob, tickTime * 2, cancelToken.Token) { Broadcast = true };
+ doAfterSystem.DoAfter(args, data);
cancelToken.Cancel();
});
await server.WaitRunTicks(3);
- Assert.That(task.Status, Is.EqualTo(TaskStatus.RanToCompletion));
-#pragma warning disable RA0004
- Assert.That(task.Result, Is.EqualTo(DoAfterStatus.Cancelled), $"Result was {task.Result}");
-#pragma warning restore RA0004
+ Assert.That(data.Cancelled, Is.False);
await pairTracker.CleanReturnAsync();
}
using Content.Server.UserInterface;
using Content.Shared.AirlockPainter;
using Content.Shared.AirlockPainter.Prototypes;
+using Content.Shared.DoAfter;
using Content.Shared.Database;
using Content.Shared.Doors.Components;
using Content.Shared.Interaction;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
using Robust.Shared.Player;
namespace Content.Server.AirlockPainter
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize()
SubscribeLocalEvent<AirlockPainterComponent, AfterInteractEvent>(AfterInteractOn);
SubscribeLocalEvent<AirlockPainterComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<AirlockPainterComponent, AirlockPainterSpritePickedMessage>(OnSpritePicked);
- SubscribeLocalEvent<AirlockPainterDoAfterComplete>(OnDoAfterComplete);
- SubscribeLocalEvent<AirlockPainterDoAfterCancelled>(OnDoAfterCancelled);
+ SubscribeLocalEvent<AirlockPainterComponent, DoAfterEvent<AirlockPainterData>>(OnDoAfter);
}
- private void OnDoAfterComplete(AirlockPainterDoAfterComplete ev)
+ private void OnDoAfter(EntityUid uid, AirlockPainterComponent component, DoAfterEvent<AirlockPainterData> args)
{
- ev.Component.IsSpraying = false;
- if (TryComp<AppearanceComponent>(ev.Target, out var appearance) &&
- TryComp(ev.Target, out PaintableAirlockComponent? _))
+ if (args.Handled || args.Cancelled)
{
- SoundSystem.Play(ev.Component.SpraySound.GetSound(), Filter.Pvs(ev.UsedTool, entityManager:EntityManager), ev.UsedTool);
- _appearance.SetData(ev.Target, DoorVisuals.BaseRSI, ev.Sprite, appearance);
+ component.IsSpraying = false;
+ return;
+ }
- // Log success
- _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(ev.User):user} painted {ToPrettyString(ev.Target):target}");
+ if (args.Args.Target != null)
+ {
+ _audio.Play(component.SpraySound, Filter.Pvs(uid, entityManager:EntityManager), uid, true);
+ _appearance.SetData(args.Args.Target.Value, DoorVisuals.BaseRSI, args.AdditionalData.Sprite);
+ _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Args.User):user} painted {ToPrettyString(args.Args.Target.Value):target}");
+ component.IsSpraying = false;
}
- }
- private void OnDoAfterCancelled(AirlockPainterDoAfterCancelled ev)
- {
- ev.Component.IsSpraying = false;
+ args.Handled = true;
}
private void OnActivate(EntityUid uid, AirlockPainterComponent component, ActivateInWorldEvent args)
return;
}
component.IsSpraying = true;
- var doAfterEventArgs = new DoAfterEventArgs(args.User, component.SprayTime, default, target)
+
+ var airlockPainterData = new AirlockPainterData(sprite);
+ var doAfterEventArgs = new DoAfterEventArgs(args.User, component.SprayTime, target:target, used:uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
NeedHand = true,
- BroadcastFinishedEvent = new AirlockPainterDoAfterComplete(uid, target, sprite, component, args.User),
- BroadcastCancelledEvent = new AirlockPainterDoAfterCancelled(component),
};
- _doAfterSystem.DoAfter(doAfterEventArgs);
+ _doAfterSystem.DoAfter(doAfterEventArgs, airlockPainterData);
// Log attempt
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} is painting {ToPrettyString(uid):target} to '{style}' at {Transform(uid).Coordinates:targetlocation}");
}
- private sealed class AirlockPainterDoAfterComplete : EntityEventArgs
- {
- public readonly EntityUid User;
- public readonly EntityUid UsedTool;
- public readonly EntityUid Target;
- public readonly string Sprite;
- public readonly AirlockPainterComponent Component;
-
- public AirlockPainterDoAfterComplete(EntityUid usedTool, EntityUid target, string sprite,
- AirlockPainterComponent component, EntityUid user)
- {
- User = user;
- UsedTool = usedTool;
- Target = target;
- Sprite = sprite;
- Component = component;
- }
- }
-
- private sealed class AirlockPainterDoAfterCancelled : EntityEventArgs
- {
- public readonly AirlockPainterComponent Component;
-
- public AirlockPainterDoAfterCancelled(AirlockPainterComponent component)
- {
- Component = component;
- }
- }
-
private void OnSpritePicked(EntityUid uid, AirlockPainterComponent component, AirlockPainterSpritePickedMessage args)
{
component.Index = args.Index;
_userInterfaceSystem.TrySetUiState(uid, AirlockPainterUiKey.Key,
new AirlockPainterBoundUserInterfaceState(component.Index));
}
+
+ private record struct AirlockPainterData(string Sprite)
+ {
+ public string Sprite = Sprite;
+ }
}
}
if (!entManager.TryGetComponent(ensnare, out EnsnaringComponent? ensnaringComponent))
return;
- entManager.EntitySysManager.GetEntitySystem<EnsnareableSystem>().TryFree(player, ensnaringComponent);
+ entManager.EntitySysManager.GetEntitySystem<EnsnareableSystem>().TryFree(player, ensnare, ensnaringComponent);
}
}
}
using Content.Server.DoAfter;
using Content.Server.Nutrition.Components;
using Content.Server.Popups;
+using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Verbs;
-using Robust.Shared.Player;
namespace Content.Server.Animals.Systems
{
base.Initialize();
SubscribeLocalEvent<UdderComponent, GetVerbsEvent<AlternativeVerb>>(AddMilkVerb);
- SubscribeLocalEvent<UdderComponent, MilkingFinishedEvent>(OnMilkingFinished);
- SubscribeLocalEvent<UdderComponent, MilkingFailEvent>(OnMilkingFailed);
+ SubscribeLocalEvent<UdderComponent, DoAfterEvent>(OnDoAfter);
}
-
public override void Update(float frameTime)
{
foreach (var udder in EntityManager.EntityQuery<UdderComponent>(false))
udder.BeingMilked = true;
- var doargs = new DoAfterEventArgs(userUid, 5, default, uid)
+ var doargs = new DoAfterEventArgs(userUid, 5, target:uid, used:containerUid)
{
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
- MovementThreshold = 1.0f,
- TargetFinishedEvent = new MilkingFinishedEvent(userUid, containerUid),
- TargetCancelledEvent = new MilkingFailEvent()
+ MovementThreshold = 1.0f
};
_doAfterSystem.DoAfter(doargs);
}
- private void OnMilkingFinished(EntityUid uid, UdderComponent udder, MilkingFinishedEvent ev)
+ private void OnDoAfter(EntityUid uid, UdderComponent component, DoAfterEvent args)
{
- udder.BeingMilked = false;
+ if (args.Cancelled)
+ {
+ component.BeingMilked = false;
+ return;
+ }
- if (!_solutionContainerSystem.TryGetSolution(uid, udder.TargetSolutionName, out var solution))
+ if (args.Handled || args.Args.Used == null)
return;
- if (!_solutionContainerSystem.TryGetRefillableSolution(ev.ContainerUid, out var targetSolution))
+ component.BeingMilked = false;
+
+ if (!_solutionContainerSystem.TryGetSolution(uid, component.TargetSolutionName, out var solution))
+ return;
+
+ if (!_solutionContainerSystem.TryGetRefillableSolution(args.Args.Used.Value, out var targetSolution))
return;
var quantity = solution.Volume;
if(quantity == 0)
{
- _popupSystem.PopupEntity(Loc.GetString("udder-system-dry"), uid, ev.UserUid);
+ _popupSystem.PopupEntity(Loc.GetString("udder-system-dry"), uid, args.Args.User);
return;
}
quantity = targetSolution.AvailableVolume;
var split = _solutionContainerSystem.SplitSolution(uid, solution, quantity);
- _solutionContainerSystem.TryAddSolution(ev.ContainerUid, targetSolution, split);
+ _solutionContainerSystem.TryAddSolution(args.Args.Used.Value, targetSolution, split);
- _popupSystem.PopupEntity(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(ev.ContainerUid, EntityManager))), uid,
- ev.UserUid, PopupType.Medium);
- }
+ _popupSystem.PopupEntity(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), uid,
+ args.Args.User, PopupType.Medium);
- private void OnMilkingFailed(EntityUid uid, UdderComponent component, MilkingFailEvent ev)
- {
- component.BeingMilked = false;
+ args.Handled = true;
}
private void AddMilkVerb(EntityUid uid, UdderComponent component, GetVerbsEvent<AlternativeVerb> args)
};
args.Verbs.Add(verb);
}
-
- private sealed class MilkingFinishedEvent : EntityEventArgs
- {
- public EntityUid UserUid;
- public EntityUid ContainerUid;
-
- public MilkingFinishedEvent(EntityUid userUid, EntityUid containerUid)
- {
- UserUid = userUid;
- ContainerUid = containerUid;
- }
- }
-
- private sealed class MilkingFailEvent : EntityEventArgs
- { }
}
}
using Content.Server.Anomaly.Components;
-using Content.Server.DoAfter;
using Content.Shared.Anomaly;
using Content.Shared.Anomaly.Components;
+using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Robust.Server.GameObjects;
using Robust.Shared.Utility;
{
SubscribeLocalEvent<AnomalyScannerComponent, BoundUIOpenedEvent>(OnScannerUiOpened);
SubscribeLocalEvent<AnomalyScannerComponent, AfterInteractEvent>(OnScannerAfterInteract);
- SubscribeLocalEvent<AnomalyScannerComponent, AnomalyScanFinishedEvent>(OnScannerDoAfterFinished);
- SubscribeLocalEvent<AnomalyScannerComponent, AnomalyScanCancelledEvent>(OnScannerDoAfterCancelled);
+ SubscribeLocalEvent<AnomalyScannerComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<AnomalyShutdownEvent>(OnScannerAnomalyShutdown);
SubscribeLocalEvent<AnomalySeverityChangedEvent>(OnScannerAnomalySeverityChanged);
private void OnScannerAfterInteract(EntityUid uid, AnomalyScannerComponent component, AfterInteractEvent args)
{
- if (component.TokenSource != null)
- return;
-
if (args.Target is not { } target)
return;
if (!HasComp<AnomalyComponent>(target))
return;
- component.TokenSource = new();
- _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ScanDoAfterDuration, component.TokenSource.Token, target, uid)
+ _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ScanDoAfterDuration, target:target, used:uid)
{
- DistanceThreshold = 2f,
- UsedFinishedEvent = new AnomalyScanFinishedEvent(target, args.User),
- UsedCancelledEvent = new AnomalyScanCancelledEvent()
+ DistanceThreshold = 2f
});
}
- private void OnScannerDoAfterFinished(EntityUid uid, AnomalyScannerComponent component, AnomalyScanFinishedEvent args)
+ private void OnDoAfter(EntityUid uid, AnomalyScannerComponent component, DoAfterEvent args)
{
- component.TokenSource = null;
+ if (args.Cancelled || args.Handled || args.Args.Target == null)
+ return;
Audio.PlayPvs(component.CompleteSound, uid);
Popup.PopupEntity(Loc.GetString("anomaly-scanner-component-scan-complete"), uid);
- UpdateScannerWithNewAnomaly(uid, args.Anomaly, component);
+ UpdateScannerWithNewAnomaly(uid, args.Args.Target.Value, component);
- if (TryComp<ActorComponent>(args.User, out var actor))
- _ui.TryOpen(uid, AnomalyScannerUiKey.Key, actor.PlayerSession);
- }
+ if (TryComp<ActorComponent>(args.Args.User, out var actor)) _ui.TryOpen(uid, AnomalyScannerUiKey.Key, actor.PlayerSession);
- private void OnScannerDoAfterCancelled(EntityUid uid, AnomalyScannerComponent component, AnomalyScanCancelledEvent args)
- {
- component.TokenSource = null;
+ args.Handled = true;
}
public void UpdateScannerUi(EntityUid uid, AnomalyScannerComponent? component = null)
using Content.Server.Radio.EntitySystems;
using Content.Shared.Anomaly;
using Content.Shared.Anomaly.Components;
+using Content.Shared.DoAfter;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.Physics.Events;
[DataField("scanDoAfterDuration")]
public float ScanDoAfterDuration = 5;
- public CancellationTokenSource? TokenSource;
-
/// <summary>
/// The sound plays when the scan finished
/// </summary>
[DataField("completeSound")]
public SoundSpecifier? CompleteSound = new SoundPathSpecifier("/Audio/Items/beep.ogg");
}
-
-public sealed class AnomalyScanFinishedEvent : EntityEventArgs
-{
- public EntityUid Anomaly;
-
- public EntityUid User;
-
- public AnomalyScanFinishedEvent(EntityUid anomaly, EntityUid user)
- {
- Anomaly = anomaly;
- User = user;
- }
-}
-
-public sealed class AnomalyScanCancelledEvent : EntityEventArgs
-{
-}
[ViewVariables(VVAccess.ReadWrite)]
[DataField("delay")]
public float Delay = 3;
-
- public CancellationTokenSource? CancelToken = null;
}
}
using Content.Shared.Verbs;
using Content.Server.Popups;
using Content.Server.DoAfter;
-using System.Threading;
+using Content.Shared.DoAfter;
namespace Content.Server.Body.Systems;
SubscribeLocalEvent<InternalsComponent, ComponentStartup>(OnInternalsStartup);
SubscribeLocalEvent<InternalsComponent, ComponentShutdown>(OnInternalsShutdown);
SubscribeLocalEvent<InternalsComponent, GetVerbsEvent<InteractionVerb>>(OnGetInteractionVerbs);
- SubscribeLocalEvent<InternalsComponent, ToggleOtherInternalsCompleteEvent>(OnToggleOtherInternalsComplete);
- SubscribeLocalEvent<InternalsComponent, ToggleOtherInternalsCancelledEvent>(OnToggleOtherInternalCanceled);
+ SubscribeLocalEvent<InternalsComponent, DoAfterEvent<InternalsData>>(OnDoAfter);
}
private void OnGetInteractionVerbs(EntityUid uid, InternalsComponent component, GetVerbsEvent<InteractionVerb> args)
public void ToggleInternals(EntityUid uid, EntityUid user, bool force, InternalsComponent? internals = null)
{
if (!Resolve(uid, ref internals, false))
- {
return;
- }
// Toggle off if they're on
if (AreInternalsWorking(internals))
return;
}
- var tank = FindBestGasTank(internals);
+ var tank = FindBestGasTank(uid ,internals);
if (tank == null)
{
return;
}
+ var isUser = uid == user;
+
+ var internalsData = new InternalsData();
+
if (!force)
{
// Is the target not you? If yes, use a do-after to give them time to respond.
//If no, do a short delay. There's no reason it should be beyond 1 second.
- var delay = uid != user ? internals.Delay : 1.0f;
+ var delay = !isUser ? internals.Delay : 1.0f;
- internals.CancelToken?.Cancel();
- internals.CancelToken = new CancellationTokenSource();
- _doAfter.DoAfter(new DoAfterEventArgs(user, delay, internals.CancelToken.Token, uid)
+ _doAfter.DoAfter(new DoAfterEventArgs(user, delay, target:uid)
{
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
MovementThreshold = 0.1f,
- TargetFinishedEvent = new ToggleOtherInternalsCompleteEvent(user, tank),
- TargetCancelledEvent = new ToggleOtherInternalsCancelledEvent(),
- });
+ RaiseOnUser = isUser,
+ RaiseOnTarget = !isUser
+ }, internalsData);
return;
}
_gasTank.ConnectToInternals(tank);
}
- private void OnToggleOtherInternalsComplete(EntityUid uid, InternalsComponent component, ToggleOtherInternalsCompleteEvent ev)
+ private void OnDoAfter(EntityUid uid, InternalsComponent component, DoAfterEvent<InternalsData> args)
{
- component.CancelToken = null;
- ToggleInternals(uid, ev.User, true, component);
- }
+ if (args.Cancelled || args.Handled)
+ return;
- private static void OnToggleOtherInternalCanceled(EntityUid uid, InternalsComponent component, ToggleOtherInternalsCancelledEvent ev)
- {
- component.CancelToken = null;
+ ToggleInternals(uid, args.Args.User, true, component);
+
+ args.Handled = true;
}
private void OnInternalsStartup(EntityUid uid, InternalsComponent component, ComponentStartup args)
{
- _alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
+ _alerts.ShowAlert(uid, AlertType.Internals, GetSeverity(component));
}
private void OnInternalsShutdown(EntityUid uid, InternalsComponent component, ComponentShutdown args)
var gasTank = Comp<GasTankComponent>(component.GasTankEntity!.Value);
args.Gas = _gasTank.RemoveAirVolume(gasTank, Atmospherics.BreathVolume);
// TODO: Should listen to gas tank updates instead I guess?
- _alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
+ _alerts.ShowAlert(uid, AlertType.Internals, GetSeverity(component));
}
}
public void DisconnectBreathTool(InternalsComponent component)
public void DisconnectTank(InternalsComponent? component)
{
- if (component == null) return;
+ if (component == null)
+ return;
if (TryComp(component.GasTankEntity, out GasTankComponent? tank))
- {
_gasTank.DisconnectFromInternals(tank);
- }
component.GasTankEntity = null;
_alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
return false;
if (TryComp(component.GasTankEntity, out GasTankComponent? tank))
- {
_gasTank.DisconnectFromInternals(tank);
- }
component.GasTankEntity = tankEntity;
_alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
private short GetSeverity(InternalsComponent component)
{
- if (component.BreathToolEntity == null || !AreInternalsWorking(component)) return 2;
+ if (component.BreathToolEntity == null || !AreInternalsWorking(component))
+ return 2;
// If pressure in the tank is below low pressure threshhold, flash warning on internals UI
if (TryComp<GasTankComponent>(component.GasTankEntity, out var gasTank) && gasTank.IsLowPressure)
return 1;
}
- public GasTankComponent? FindBestGasTank(InternalsComponent component)
+ public GasTankComponent? FindBestGasTank(EntityUid internalsOwner, InternalsComponent component)
{
// Prioritise
// 1. back equipped tanks
InventoryComponent? inventory = null;
ContainerManagerComponent? containerManager = null;
- if (_inventory.TryGetSlotEntity(component.Owner, "back", out var backEntity, inventory, containerManager) &&
+ if (_inventory.TryGetSlotEntity(internalsOwner, "back", out var backEntity, inventory, containerManager) &&
TryComp<GasTankComponent>(backEntity, out var backGasTank) &&
_gasTank.CanConnectToInternals(backGasTank))
{
return backGasTank;
}
- if (_inventory.TryGetSlotEntity(component.Owner, "suitstorage", out var entity, inventory, containerManager) &&
+ if (_inventory.TryGetSlotEntity(internalsOwner, "suitstorage", out var entity, inventory, containerManager) &&
TryComp<GasTankComponent>(entity, out var gasTank) &&
_gasTank.CanConnectToInternals(gasTank))
{
var tanks = new List<GasTankComponent>();
- foreach (var hand in _hands.EnumerateHands(component.Owner))
+ foreach (var hand in _hands.EnumerateHands(internalsOwner))
{
if (TryComp(hand.HeldEntity, out gasTank) && _gasTank.CanConnectToInternals(gasTank))
- {
tanks.Add(gasTank);
- }
}
if (tanks.Count > 0)
return tanks[0];
}
- if (Resolve(component.Owner, ref inventory, false))
+ if (Resolve(internalsOwner, ref inventory, false))
{
- var enumerator = new InventorySystem.ContainerSlotEnumerator(component.Owner, inventory.TemplateId, _protoManager, _inventory, SlotFlags.POCKET | SlotFlags.BELT);
+ var enumerator = new InventorySystem.ContainerSlotEnumerator(internalsOwner, inventory.TemplateId, _protoManager, _inventory, SlotFlags.POCKET | SlotFlags.BELT);
while (enumerator.MoveNext(out var container))
{
if (TryComp(container.ContainedEntity, out gasTank) && _gasTank.CanConnectToInternals(gasTank))
- {
tanks.Add(gasTank);
- }
}
if (tanks.Count > 0)
return null;
}
- private readonly record struct ToggleOtherInternalsCompleteEvent(EntityUid User, GasTankComponent Tank)
+
+ private record struct InternalsData
{
- public readonly EntityUid User = User;
- public readonly GasTankComponent Tank = Tank;
- }
- private readonly record struct ToggleOtherInternalsCancelledEvent;
+ }
}
[DataField("swabDelay")]
public float SwabDelay = 2f;
- /// <summary>
- /// Token for interrupting swabbing do after.
- /// </summary>
- public CancellationTokenSource? CancelToken;
-
/// <summary>
/// SeedData from the first plant that got swabbed.
/// </summary>
-using System.Threading;
using Content.Server.Botany.Components;
using Content.Server.DoAfter;
-using Content.Server.Hands.Components;
-using Content.Server.Nutrition.EntitySystems;
using Content.Server.Popups;
+using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Interaction;
-using Content.Shared.Inventory;
-using Content.Shared.Tools.Components;
-using Robust.Shared.Audio;
-using Robust.Shared.Player;
-using Robust.Shared.Random;
-using Robust.Shared.Utility;
-namespace Content.Server.Botany.Systems
+namespace Content.Server.Botany.Systems;
+
+public sealed class BotanySwabSystem : EntitySystem
{
- public sealed class BotanySwabSystem : EntitySystem
- {
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
- [Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly MutationSystem _mutationSystem = default!;
+ [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly PopupSystem _popupSystem = default!;
+ [Dependency] private readonly MutationSystem _mutationSystem = default!;
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<BotanySwabComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<BotanySwabComponent, ExaminedEvent>(OnExamined);
- // Private Events
- SubscribeLocalEvent<TargetSwabSuccessfulEvent>(OnTargetSwabSuccessful);
- SubscribeLocalEvent<SwabCancelledEvent>(OnSwabCancelled);
- }
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<BotanySwabComponent, ExaminedEvent>(OnExamined);
+ SubscribeLocalEvent<BotanySwabComponent, AfterInteractEvent>(OnAfterInteract);
+ SubscribeLocalEvent<DoAfterEvent>(OnDoAfter);
+ }
- /// <summary>
- /// Handles swabbing a plant.
- /// </summary>
- private void OnAfterInteract(EntityUid uid, BotanySwabComponent swab, AfterInteractEvent args)
+ /// <summary>
+ /// This handles swab examination text
+ /// so you can tell if they are used or not.
+ /// </summary>
+ private void OnExamined(EntityUid uid, BotanySwabComponent swab, ExaminedEvent args)
+ {
+ if (args.IsInDetailsRange)
{
- if (swab.CancelToken != null)
- {
- swab.CancelToken.Cancel();
- swab.CancelToken = null;
- return;
- }
-
- if (args.Target == null || !args.CanReach)
- return;
-
- if (!TryComp<PlantHolderComponent>(args.Target, out var plant))
- return;
-
- swab.CancelToken = new CancellationTokenSource();
- _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, swab.CancelToken.Token, target: args.Target)
- {
- BroadcastFinishedEvent = new TargetSwabSuccessfulEvent(args.User, args.Target, swab, plant),
- BroadcastCancelledEvent = new SwabCancelledEvent(swab),
- BreakOnTargetMove = true,
- BreakOnUserMove = true,
- BreakOnStun = true,
- NeedHand = true
- });
+ if (swab.SeedData != null)
+ args.PushMarkup(Loc.GetString("swab-used"));
+ else
+ args.PushMarkup(Loc.GetString("swab-unused"));
}
+ }
- /// <summary>
- /// This handles swab examination text
- /// so you can tell if they are used or not.
- /// </summary>
- private void OnExamined(EntityUid uid, BotanySwabComponent swab, ExaminedEvent args)
- {
- if (args.IsInDetailsRange)
- {
- if (swab.SeedData != null)
- args.PushMarkup(Loc.GetString("swab-used"));
- else
- args.PushMarkup(Loc.GetString("swab-unused"));
- }
- }
+ /// <summary>
+ /// Handles swabbing a plant.
+ /// </summary>
+ private void OnAfterInteract(EntityUid uid, BotanySwabComponent swab, AfterInteractEvent args)
+ {
+ if (args.Target == null || !args.CanReach)
+ return;
- /// <summary>
- /// Save seed data or cross-pollenate.
- /// </summary>
- private void OnTargetSwabSuccessful(TargetSwabSuccessfulEvent args)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, target: args.Target, used: uid)
{
- if (args.Target == null)
- return;
-
- if (args.Swab.SeedData == null)
- {
- // Pick up pollen
- args.Swab.SeedData = args.Plant.Seed;
- _popupSystem.PopupEntity(Loc.GetString("botany-swab-from"), args.Target.Value, args.User);
- }
- else
- {
- var old = args.Plant.Seed; // Save old plant pollen
- if (old == null)
- return;
- args.Plant.Seed = _mutationSystem.Cross(args.Swab.SeedData, old); // Cross-pollenate
- args.Swab.SeedData = old; // Transfer old plant pollen to swab
- _popupSystem.PopupEntity(Loc.GetString("botany-swab-to"), args.Target.Value, args.User);
- }
+ Broadcast = true,
+ BreakOnTargetMove = true,
+ BreakOnUserMove = true,
+ BreakOnStun = true,
+ NeedHand = true
+ });
+ }
- if (args.Swab.CancelToken != null)
- {
- args.Swab.CancelToken.Cancel();
- args.Swab.CancelToken = null;
- }
- }
+ /// <summary>
+ /// Save seed data or cross-pollenate.
+ /// </summary>
+ private void OnDoAfter(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 static void OnSwabCancelled(SwabCancelledEvent args)
+ if (swab.SeedData == null)
{
- args.Swab.CancelToken = null;
+ // Pick up pollen
+ swab.SeedData = plant.Seed;
+ _popupSystem.PopupEntity(Loc.GetString("botany-swab-from"), args.Args.Target.Value, args.Args.User);
}
-
- private sealed class SwabCancelledEvent : EntityEventArgs
+ else
{
- public readonly BotanySwabComponent Swab;
- public SwabCancelledEvent(BotanySwabComponent swab)
- {
- Swab = swab;
- }
+ var old = plant.Seed;
+ if (old == null)
+ return;
+ plant.Seed = _mutationSystem.Cross(swab.SeedData, old); // Cross-pollenate
+ swab.SeedData = old; // Transfer old plant pollen to swab
+ _popupSystem.PopupEntity(Loc.GetString("botany-swab-to"), args.Args.Target.Value, args.Args.User);
}
- private sealed class TargetSwabSuccessfulEvent : EntityEventArgs
- {
- public EntityUid User { get; }
- public EntityUid? Target { get; }
- public BotanySwabComponent Swab { get; }
-
- public PlantHolderComponent Plant { get; }
-
- public TargetSwabSuccessfulEvent(EntityUid user, EntityUid? target, BotanySwabComponent swab, PlantHolderComponent plant)
- {
- User = user;
- Target = target;
- Swab = swab;
- Plant = plant;
- }
- }
+ args.Handled = true;
}
}
-using System.Threading;
using Content.Shared.Chemistry.Components;
using Content.Shared.FixedPoint;
[DataField("delay")]
public float Delay = 5;
- /// <summary>
- /// Token for interrupting a do-after action (e.g., injection another player). If not null, implies
- /// component is currently "in use".
- /// </summary>
- public CancellationTokenSource? CancelToken;
-
[DataField("toggleState")] private InjectorToggleMode _toggleState;
/// <summary>
using Content.Server.Body.Components;
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.Components.SolutionManager;
-using Content.Server.CombatMode;
-using Content.Server.DoAfter;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
-using Content.Shared.Hands;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Robust.Shared.GameStates;
-using Robust.Shared.Player;
-using System.Threading;
+using Content.Shared.DoAfter;
using Content.Shared.Mobs.Components;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
{
SubscribeLocalEvent<InjectorComponent, GetVerbsEvent<AlternativeVerb>>(AddSetTransferVerbs);
SubscribeLocalEvent<InjectorComponent, SolutionChangedEvent>(OnSolutionChange);
- SubscribeLocalEvent<InjectorComponent, HandDeselectedEvent>(OnInjectorDeselected);
+ SubscribeLocalEvent<InjectorComponent, DoAfterEvent>(OnInjectDoAfter);
SubscribeLocalEvent<InjectorComponent, ComponentStartup>(OnInjectorStartup);
SubscribeLocalEvent<InjectorComponent, UseInHandEvent>(OnInjectorUse);
SubscribeLocalEvent<InjectorComponent, AfterInteractEvent>(OnInjectorAfterInteract);
SubscribeLocalEvent<InjectorComponent, ComponentGetState>(OnInjectorGetState);
-
- SubscribeLocalEvent<InjectionCompleteEvent>(OnInjectionComplete);
- SubscribeLocalEvent<InjectionCancelledEvent>(OnInjectionCancelled);
}
private void AddSetTransferVerbs(EntityUid uid, InjectorComponent component, GetVerbsEvent<AlternativeVerb> args)
verb.Act = () =>
{
component.TransferAmount = FixedPoint2.New(amount);
- args.User.PopupMessage(Loc.GetString("comp-solution-transfer-set-amount", ("amount", amount)));
+ _popup.PopupEntity(Loc.GetString("comp-solution-transfer-set-amount", ("amount", amount)), args.User, args.User);
};
// we want to sort by size, not alphabetically by the verb text.
}
}
- private static void OnInjectionCancelled(InjectionCancelledEvent ev)
- {
- ev.Component.CancelToken = null;
- }
-
- private void OnInjectionComplete(InjectionCompleteEvent ev)
- {
- ev.Component.CancelToken = null;
- UseInjector(ev.Target, ev.User, ev.Component);
- }
-
- private void UseInjector(EntityUid target, EntityUid user, InjectorComponent component)
+ private void UseInjector(EntityUid target, EntityUid user, EntityUid injector, InjectorComponent component)
{
// Handle injecting/drawing for solutions
if (component.ToggleState == SharedInjectorComponent.InjectorToggleMode.Inject)
{
if (_solutions.TryGetInjectableSolution(target, out var injectableSolution))
{
- TryInject(component, target, injectableSolution, user, false);
+ TryInject(component, injector, target, injectableSolution, user, false);
}
else if (_solutions.TryGetRefillableSolution(target, out var refillableSolution))
{
- TryInject(component, target, refillableSolution, user, true);
+ TryInject(component, injector, target, refillableSolution, user, true);
}
else if (TryComp<BloodstreamComponent>(target, out var bloodstream))
{
- TryInjectIntoBloodstream(component, bloodstream, user);
+ TryInjectIntoBloodstream(component, injector, target, bloodstream, user);
}
else
{
_popup.PopupEntity(Loc.GetString("injector-component-cannot-transfer-message",
- ("target", Identity.Entity(target, EntityManager))), component.Owner, user);
+ ("target", Identity.Entity(target, EntityManager))), injector, user);
}
}
else if (component.ToggleState == SharedInjectorComponent.InjectorToggleMode.Draw)
// Draw from a bloodstream, if the target has that
if (TryComp<BloodstreamComponent>(target, out var stream))
{
- TryDraw(component, target, stream.BloodSolution, user, stream);
+ TryDraw(component, injector, target, stream.BloodSolution, user, stream);
return;
}
// Draw from an object (food, beaker, etc)
if (_solutions.TryGetDrawableSolution(target, out var drawableSolution))
{
- TryDraw(component, target, drawableSolution, user);
+ TryDraw(component, injector, target, drawableSolution, user);
}
else
{
_popup.PopupEntity(Loc.GetString("injector-component-cannot-draw-message",
- ("target", Identity.Entity(target, EntityManager))), component.Owner, user);
+ ("target", Identity.Entity(target, EntityManager))), injector, user);
}
}
}
- private static void OnInjectorDeselected(EntityUid uid, InjectorComponent component, HandDeselectedEvent args)
- {
- component.CancelToken?.Cancel();
- component.CancelToken = null;
- }
-
private void OnSolutionChange(EntityUid uid, InjectorComponent component, SolutionChangedEvent args)
{
Dirty(component);
args.State = new SharedInjectorComponent.InjectorComponentState(currentVolume, maxVolume, component.ToggleState);
}
- private void OnInjectorAfterInteract(EntityUid uid, InjectorComponent component, AfterInteractEvent args)
+ private void OnInjectDoAfter(EntityUid uid, InjectorComponent component, DoAfterEvent args)
{
- if (args.Handled || !args.CanReach)
+ if (args.Handled || args.Cancelled || args.Args.Target == null)
return;
- if (component.CancelToken != null)
- {
- args.Handled = true;
+ UseInjector(args.Args.Target.Value, args.Args.User, uid, component);
+
+ args.Handled = true;
+ }
+
+ private void OnInjectorAfterInteract(EntityUid uid, InjectorComponent component, AfterInteractEvent args)
+ {
+ if (args.Handled || !args.CanReach)
return;
- }
//Make sure we have the attacking entity
- if (args.Target is not { Valid: true } target ||
- !HasComp<SolutionContainerManagerComponent>(uid))
- {
+ if (args.Target is not { Valid: true } target || !HasComp<SolutionContainerManagerComponent>(uid))
return;
- }
// Is the target a mob? If yes, use a do-after to give them time to respond.
- if (HasComp<MobStateComponent>(target) ||
- HasComp<BloodstreamComponent>(target))
+ if (HasComp<MobStateComponent>(target) || HasComp<BloodstreamComponent>(target))
{
// Are use using an injector capible of targeting a mob?
if (component.IgnoreMobs)
return;
- InjectDoAfter(component, args.User, target);
+ InjectDoAfter(component, args.User, target, uid);
args.Handled = true;
return;
}
- UseInjector(target, args.User, component);
+ UseInjector(target, args.User, uid, component);
args.Handled = true;
}
if (args.Handled)
return;
- Toggle(component, args.User);
+ Toggle(component, args.User, uid);
args.Handled = true;
}
/// <summary>
/// Toggle between draw/inject state if applicable
/// </summary>
- private void Toggle(InjectorComponent component, EntityUid user)
+ private void Toggle(InjectorComponent component, EntityUid user, EntityUid injector)
{
if (component.InjectOnly)
{
throw new ArgumentOutOfRangeException();
}
- _popup.PopupEntity(Loc.GetString(msg), component.Owner, user);
+ _popup.PopupEntity(Loc.GetString(msg), injector, user);
}
/// <summary>
/// Send informative pop-up messages and wait for a do-after to complete.
/// </summary>
- private void InjectDoAfter(InjectorComponent component, EntityUid user, EntityUid target)
+ private void InjectDoAfter(InjectorComponent component, EntityUid user, EntityUid target, EntityUid injector)
{
// Create a pop-up for the user
_popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, user);
- if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution))
+ if (!_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution))
return;
var actualDelay = MathF.Max(component.Delay, 1f);
actualDelay /= 2;
if (component.ToggleState == SharedInjectorComponent.InjectorToggleMode.Inject)
- _adminLogger.Add(LogType.Ingestion,
- $"{EntityManager.ToPrettyString(user):user} is attempting to inject themselves with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}.");
+ _adminLogger.Add(LogType.Ingestion, $"{EntityManager.ToPrettyString(user):user} is attempting to inject themselves with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}.");
}
- component.CancelToken = new CancellationTokenSource();
-
- _doAfter.DoAfter(new DoAfterEventArgs(user, actualDelay, component.CancelToken.Token, target)
+ _doAfter.DoAfter(new DoAfterEventArgs(user, actualDelay, target:target, used:injector)
{
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
- MovementThreshold = 0.1f,
- BroadcastFinishedEvent = new InjectionCompleteEvent()
- {
- Component = component,
- User = user,
- Target = target,
- },
- BroadcastCancelledEvent = new InjectionCancelledEvent()
- {
- Component = component,
- }
+ MovementThreshold = 0.1f
});
}
- private void TryInjectIntoBloodstream(InjectorComponent component, BloodstreamComponent targetBloodstream, EntityUid user)
+ private void TryInjectIntoBloodstream(InjectorComponent component, EntityUid injector, EntityUid target, BloodstreamComponent targetBloodstream, EntityUid user)
{
// Get transfer amount. May be smaller than _transferAmount if not enough room
var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetBloodstream.ChemicalSolution.AvailableVolume);
if (realTransferAmount <= 0)
{
- _popup.PopupEntity(Loc.GetString("injector-component-cannot-inject-message", ("target", Identity.Entity(targetBloodstream.Owner, EntityManager))),
- component.Owner, user);
+ _popup.PopupEntity(Loc.GetString("injector-component-cannot-inject-message", ("target", Identity.Entity(target, EntityManager))), injector, user);
return;
}
// Move units from attackSolution to targetSolution
var removedSolution = _solutions.SplitSolution(user, targetBloodstream.ChemicalSolution, realTransferAmount);
- _blood.TryAddToChemicals((targetBloodstream).Owner, removedSolution, targetBloodstream);
+ _blood.TryAddToChemicals(target, removedSolution, targetBloodstream);
- removedSolution.DoEntityReaction(targetBloodstream.Owner, ReactionMethod.Injection);
+ _reactiveSystem.DoEntityReaction(target, removedSolution, ReactionMethod.Injection);
_popup.PopupEntity(Loc.GetString("injector-component-inject-success-message",
("amount", removedSolution.Volume),
- ("target", Identity.Entity(targetBloodstream.Owner, EntityManager))), component.Owner, user);
+ ("target", Identity.Entity(target, EntityManager))), injector, user);
Dirty(component);
- AfterInject(component);
+ AfterInject(component, injector);
}
- private void TryInject(InjectorComponent component, EntityUid targetEntity, Solution targetSolution, EntityUid user, bool asRefill)
+ private void TryInject(InjectorComponent component, EntityUid injector, EntityUid targetEntity, Solution targetSolution, EntityUid user, bool asRefill)
{
- if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)
+ if (!_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution)
|| solution.Volume == 0)
{
return;
if (realTransferAmount <= 0)
{
_popup.PopupEntity(Loc.GetString("injector-component-target-already-full-message", ("target", Identity.Entity(targetEntity, EntityManager))),
- component.Owner, user);
+ injector, user);
return;
}
// Move units from attackSolution to targetSolution
- var removedSolution = _solutions.SplitSolution(component.Owner, solution, realTransferAmount);
+ var removedSolution = _solutions.SplitSolution(injector, solution, realTransferAmount);
- removedSolution.DoEntityReaction(targetEntity, ReactionMethod.Injection);
+ _reactiveSystem.DoEntityReaction(targetEntity, removedSolution, ReactionMethod.Injection);
if (!asRefill)
{
_popup.PopupEntity(Loc.GetString("injector-component-transfer-success-message",
("amount", removedSolution.Volume),
- ("target", Identity.Entity(targetEntity, EntityManager))), component.Owner, user);
+ ("target", Identity.Entity(targetEntity, EntityManager))), injector, user);
Dirty(component);
- AfterInject(component);
+ AfterInject(component, injector);
}
- private void AfterInject(InjectorComponent component)
+ private void AfterInject(InjectorComponent component, EntityUid injector)
{
// Automatically set syringe to draw after completely draining it.
- if (_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)
+ if (_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution)
&& solution.Volume == 0)
{
component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Draw;
}
}
- private void AfterDraw(InjectorComponent component)
+ private void AfterDraw(InjectorComponent component, EntityUid injector)
{
// Automatically set syringe to inject after completely filling it.
- if (_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)
+ if (_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution)
&& solution.AvailableVolume == 0)
{
component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Inject;
}
}
- private void TryDraw(InjectorComponent component, EntityUid targetEntity, Solution targetSolution, EntityUid user, BloodstreamComponent? stream = null)
+ private void TryDraw(InjectorComponent component, EntityUid injector, EntityUid targetEntity, Solution targetSolution, EntityUid user, BloodstreamComponent? stream = null)
{
- if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)
+ if (!_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution)
|| solution.AvailableVolume == 0)
{
return;
if (realTransferAmount <= 0)
{
_popup.PopupEntity(Loc.GetString("injector-component-target-is-empty-message", ("target", Identity.Entity(targetEntity, EntityManager))),
- component.Owner, user);
+ injector, user);
return;
}
// We have some snowflaked behavior for streams.
if (stream != null)
{
- DrawFromBlood(user, targetEntity, component, solution, stream, realTransferAmount);
+ DrawFromBlood(user, injector, targetEntity, component, solution, stream, realTransferAmount);
return;
}
// Move units from attackSolution to targetSolution
var removedSolution = _solutions.Draw(targetEntity, targetSolution, realTransferAmount);
- if (!_solutions.TryAddSolution(component.Owner, solution, removedSolution))
+ if (!_solutions.TryAddSolution(injector, solution, removedSolution))
{
return;
}
_popup.PopupEntity(Loc.GetString("injector-component-draw-success-message",
("amount", removedSolution.Volume),
- ("target", Identity.Entity(targetEntity, EntityManager))), component.Owner, user);
+ ("target", Identity.Entity(targetEntity, EntityManager))), injector, user);
Dirty(component);
- AfterDraw(component);
+ AfterDraw(component, injector);
}
- private void DrawFromBlood(EntityUid user, EntityUid target, InjectorComponent component, Solution injectorSolution, BloodstreamComponent stream, FixedPoint2 transferAmount)
+ private void DrawFromBlood(EntityUid user, EntityUid injector, EntityUid target, InjectorComponent component, Solution injectorSolution, BloodstreamComponent stream, FixedPoint2 transferAmount)
{
var drawAmount = (float) transferAmount;
var bloodAmount = drawAmount;
var bloodTemp = stream.BloodSolution.SplitSolution(bloodAmount);
var chemTemp = stream.ChemicalSolution.SplitSolution(chemAmount);
- _solutions.TryAddSolution(component.Owner, injectorSolution, bloodTemp);
- _solutions.TryAddSolution(component.Owner, injectorSolution, chemTemp);
+ _solutions.TryAddSolution(injector, injectorSolution, bloodTemp);
+ _solutions.TryAddSolution(injector, injectorSolution, chemTemp);
_popup.PopupEntity(Loc.GetString("injector-component-draw-success-message",
("amount", transferAmount),
- ("target", Identity.Entity(target, EntityManager))), component.Owner, user);
+ ("target", Identity.Entity(target, EntityManager))), injector, user);
Dirty(component);
- AfterDraw(component);
- }
- private sealed class InjectionCompleteEvent : EntityEventArgs
- {
- public InjectorComponent Component { get; init; } = default!;
- public EntityUid User { get; init; }
- public EntityUid Target { get; init; }
- }
-
- private sealed class InjectionCancelledEvent : EntityEventArgs
- {
- public InjectorComponent Component { get; init; } = default!;
+ AfterDraw(component, injector);
}
}
using Content.Shared.Climbing;
using Content.Shared.Climbing.Events;
using Content.Shared.Damage;
+using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.GameTicking;
using Content.Shared.IdentityManagement;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using JetBrains.Annotations;
-using Robust.Server.GameObjects;
-using Robust.Shared.Configuration;
using Robust.Shared.GameStates;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
[UsedImplicitly]
public sealed class ClimbSystem : SharedClimbSystem
{
- [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly InteractionSystem _interactionSystem = default!;
[Dependency] private readonly StunSystem _stunSystem = default!;
- [Dependency] private readonly AudioSystem _audioSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly BonkSystem _bonkSystem = default!;
SubscribeLocalEvent<ClimbableComponent, GetVerbsEvent<AlternativeVerb>>(AddClimbableVerb);
SubscribeLocalEvent<ClimbableComponent, DragDropTargetEvent>(OnClimbableDragDrop);
- SubscribeLocalEvent<ClimbingComponent, ClimbFinishedEvent>(OnClimbFinished);
+ SubscribeLocalEvent<ClimbingComponent, DoAfterEvent<ClimbExtraEvent>>(OnDoAfter);
SubscribeLocalEvent<ClimbingComponent, EndCollideEvent>(OnClimbEndCollide);
SubscribeLocalEvent<ClimbingComponent, BuckleChangeEvent>(OnBuckleChange);
SubscribeLocalEvent<ClimbingComponent, ComponentGetState>(OnClimbingGetState);
if (_bonkSystem.TryBonk(entityToMove, climbable))
return;
- _doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.ClimbDelay, default, climbable, entityToMove)
+ var ev = new ClimbExtraEvent();
+
+ var args = new DoAfterEventArgs(user, component.ClimbDelay, target: climbable, used: entityToMove)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
- UsedFinishedEvent = new ClimbFinishedEvent(user, climbable, entityToMove)
- });
+ RaiseOnUser = user == entityToMove,
+ RaiseOnTarget = user != entityToMove
+ };
+
+ _doAfterSystem.DoAfter(args, ev);
}
- private void OnClimbFinished(EntityUid uid, ClimbingComponent climbing, ClimbFinishedEvent args)
+ private void OnDoAfter(EntityUid uid, ClimbingComponent component, DoAfterEvent<ClimbExtraEvent> args)
{
- Climb(uid, args.User, args.Instigator, args.Climbable, climbing: climbing);
+ if (args.Handled || args.Cancelled || args.Args.Target == null || args.Args.Used == null)
+ return;
+
+ Climb(uid, args.Args.User, args.Args.Used.Value, args.Args.Target.Value, climbing: component);
+
+ args.Handled = true;
}
private void Climb(EntityUid uid, EntityUid user, EntityUid instigator, EntityUid climbable, bool silent = false, ClimbingComponent? climbing = null,
{
_fixtureRemoveQueue.Clear();
}
-}
-internal sealed class ClimbFinishedEvent : EntityEventArgs
-{
- public ClimbFinishedEvent(EntityUid user, EntityUid climbable, EntityUid instigator)
+ private sealed class ClimbExtraEvent : EntityEventArgs
{
- User = user;
- Climbable = climbable;
- Instigator = instigator;
+ //Honestly this is only here because otherwise this activates on every single doafter on a human
}
-
- public EntityUid User { get; }
- public EntityUid Climbable { get; }
- public EntityUid Instigator { get; }
}
/// <summary>
-using System.Threading;
using Content.Server.Administration.Logs;
using Content.Server.Coordinates.Helpers;
using Content.Server.Popups;
using Content.Server.Pulling;
-using Content.Server.Tools;
using Content.Shared.Construction.Components;
using Content.Shared.Construction.EntitySystems;
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.Pulling.Components;
+using Content.Shared.Tools;
using Content.Shared.Tools.Components;
using Robust.Shared.Map;
-using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
-using Robust.Shared.Player;
namespace Content.Server.Construction
{
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly PopupSystem _popup = default!;
- [Dependency] private readonly ToolSystem _tool = default!;
+ [Dependency] private readonly SharedToolSystem _tool = default!;
[Dependency] private readonly PullingSystem _pulling = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AnchorableComponent, TryAnchorCompletedEvent>(OnAnchorComplete);
- SubscribeLocalEvent<AnchorableComponent, TryAnchorCancelledEvent>(OnAnchorCancelled);
SubscribeLocalEvent<AnchorableComponent, TryUnanchorCompletedEvent>(OnUnanchorComplete);
- SubscribeLocalEvent<AnchorableComponent, TryUnanchorCancelledEvent>(OnUnanchorCancelled);
SubscribeLocalEvent<AnchorableComponent, ExaminedEvent>(OnAnchoredExamine);
}
args.PushMarkup(Loc.GetString(messageId, ("target", uid)));
}
- private void OnUnanchorCancelled(EntityUid uid, AnchorableComponent component, TryUnanchorCancelledEvent args)
- {
- component.CancelToken = null;
- }
-
private void OnUnanchorComplete(EntityUid uid, AnchorableComponent component, TryUnanchorCompletedEvent args)
{
- component.CancelToken = null;
var xform = Transform(uid);
RaiseLocalEvent(uid, new BeforeUnanchoredEvent(args.User, args.Using));
);
}
- private void OnAnchorCancelled(EntityUid uid, AnchorableComponent component, TryAnchorCancelledEvent args)
- {
- component.CancelToken = null;
- }
-
private void OnAnchorComplete(EntityUid uid, AnchorableComponent component, TryAnchorCompletedEvent args)
{
- component.CancelToken = null;
var xform = Transform(uid);
if (TryComp<PhysicsComponent>(uid, out var anchorBody) &&
!TileFree(xform.Coordinates, anchorBody))
/// <returns>true if it is valid, false otherwise</returns>
private bool Valid(EntityUid uid, EntityUid userUid, EntityUid usingUid, bool anchoring, AnchorableComponent? anchorable = null, ToolComponent? usingTool = null)
{
- if (!Resolve(uid, ref anchorable) ||
- anchorable.CancelToken != null)
+ if (!Resolve(uid, ref anchorable))
return false;
if (!Resolve(usingUid, ref usingTool))
return;
}
- anchorable.CancelToken = new CancellationTokenSource();
-
- _tool.UseTool(usingUid, userUid, uid, 0f, anchorable.Delay, usingTool.Qualities,
- new TryAnchorCompletedEvent(userUid, usingUid), new TryAnchorCancelledEvent(userUid, usingUid), uid, cancelToken: anchorable.CancelToken.Token);
+ var toolEvData = new ToolEventData(new TryAnchorCompletedEvent(userUid, usingUid), targetEntity:uid);
+ _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, toolEvData);
}
/// <summary>
TransformComponent? transform = null,
ToolComponent? usingTool = null)
{
- if (!Resolve(uid, ref anchorable, ref transform) ||
- anchorable.CancelToken != null)
+ if (!Resolve(uid, ref anchorable, ref transform))
return;
- if (!Resolve(usingUid, ref usingTool)) return;
-
- if (!Valid(uid, userUid, usingUid, false)) return;
+ if (!Resolve(usingUid, ref usingTool))
+ return;
- anchorable.CancelToken = new CancellationTokenSource();
+ if (!Valid(uid, userUid, usingUid, false))
+ return;
- _tool.UseTool(usingUid, userUid, uid, 0f, anchorable.Delay, usingTool.Qualities,
- new TryUnanchorCompletedEvent(userUid, usingUid), new TryUnanchorCancelledEvent(userUid, usingUid), uid, cancelToken: anchorable.CancelToken.Token);
+ var toolEvData = new ToolEventData(new TryUnanchorCompletedEvent(userUid, usingUid), targetEntity:uid);
+ _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, toolEvData);
}
/// <summary>
}
}
- private sealed class TryUnanchorCancelledEvent : AnchorEvent
- {
- public TryUnanchorCancelledEvent(EntityUid userUid, EntityUid usingUid) : base(userUid, usingUid)
- {
- }
- }
private sealed class TryAnchorCompletedEvent : AnchorEvent
{
{
}
}
-
- private sealed class TryAnchorCancelledEvent : AnchorEvent
- {
- public TryAnchorCancelledEvent(EntityUid userUid, EntityUid usingUid) : base(userUid, usingUid)
- {
- }
- }
}
}
public SoundSpecifier ExchangeSound = new SoundPathSpecifier("/Audio/Items/rped.ogg");
public IPlayingAudioStream? AudioStream;
-
- public CancellationTokenSource? Token;
}
using Content.Shared.Construction.Steps;
using Content.Shared.Coordinates;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Inventory;
using Content.Server.Administration.Logs;
using Content.Server.Construction.Components;
-using Content.Server.DoAfter;
using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems;
using Content.Shared.Construction;
-using Content.Shared.Construction.EntitySystems;
using Content.Shared.Construction.Steps;
-using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Interaction;
+using Content.Shared.Tools.Components;
using Robust.Shared.Containers;
#if EXCEPTION_TOLERANCE
+// ReSharper disable once RedundantUsingDirective
using Robust.Shared.Exceptions;
#endif
SubscribeLocalEvent<ConstructionDoAfterCancelled>(OnDoAfterCancelled);
SubscribeLocalEvent<ConstructionComponent, ConstructionDoAfterComplete>(EnqueueEvent);
SubscribeLocalEvent<ConstructionComponent, ConstructionDoAfterCancelled>(EnqueueEvent);
+ SubscribeLocalEvent<ConstructionComponent, DoAfterEvent<ConstructionData>>(OnDoAfter);
#endregion
// If we still haven't completed this step's DoAfter...
if (doAfterState == DoAfterState.None && insertStep.DoAfter > 0)
{
- _doAfterSystem.DoAfter(
- new DoAfterEventArgs(interactUsing.User, step.DoAfter, default, interactUsing.Target)
- {
- BreakOnDamage = false,
- BreakOnStun = true,
- BreakOnTargetMove = true,
- BreakOnUserMove = true,
- NeedHand = true,
-
- // These events will be broadcast and handled by this very same system, that will
- // raise them directed to the target. These events wrap the original event.
- BroadcastFinishedEvent = new ConstructionDoAfterComplete(uid, ev),
- BroadcastCancelledEvent = new ConstructionDoAfterCancelled(uid, ev)
- });
+ // These events will be broadcast and handled by this very same system, that will
+ // raise them directed to the target. These events wrap the original event.
+ var constructionData = new ConstructionData(new ConstructionDoAfterComplete(uid, ev), new ConstructionDoAfterCancelled(uid, ev));
+ var doAfterEventArgs = new DoAfterEventArgs(interactUsing.User, step.DoAfter, target: interactUsing.Target)
+ {
+ BreakOnDamage = false,
+ BreakOnStun = true,
+ BreakOnTargetMove = true,
+ BreakOnUserMove = true,
+ NeedHand = true
+ };
+
+ _doAfterSystem.DoAfter(doAfterEventArgs, constructionData);
// To properly signal that we're waiting for a DoAfter, we have to set the flag on the component
// and then also return the DoAfter HandleResult.
if (doAfterState != DoAfterState.None)
return doAfterState == DoAfterState.Completed ? HandleResult.True : HandleResult.False;
- if (!_toolSystem.UseTool(interactUsing.Used, interactUsing.User,
- uid, toolInsertStep.Fuel, toolInsertStep.DoAfter, toolInsertStep.Tool,
- new ConstructionDoAfterComplete(uid, ev), new ConstructionDoAfterCancelled(uid, ev)))
+ var toolEvData = new ToolEventData(new ConstructionDoAfterComplete(uid, ev), toolInsertStep.Fuel, new ConstructionDoAfterCancelled(uid, ev));
+
+ if(!_toolSystem.UseTool(interactUsing.Used, interactUsing.User, uid, toolInsertStep.DoAfter, new [] {toolInsertStep.Tool}, toolEvData))
return HandleResult.False;
// In the case we're not waiting for a doAfter, then this step is complete!
_constructionUpdateQueue.Add(uid);
}
+ private void OnDoAfter(EntityUid uid, ConstructionComponent component, DoAfterEvent<ConstructionData> args)
+ {
+ if (!Exists(args.Args.Target) || args.Handled)
+ return;
+
+ if (args.Cancelled)
+ {
+ RaiseLocalEvent(args.Args.Target.Value, args.AdditionalData.CancelEvent);
+ args.Handled = true;
+ }
+
+ RaiseLocalEvent(args.Args.Target.Value, args.AdditionalData.CompleteEvent);
+ args.Handled = true;
+ }
+
private void OnDoAfterComplete(ConstructionDoAfterComplete ev)
{
// Make extra sure the target entity exists...
#region Event Definitions
+ private sealed class ConstructionData
+ {
+ public readonly object CompleteEvent;
+ public readonly object CancelEvent;
+
+ public ConstructionData(object completeEvent, object cancelEvent)
+ {
+ CompleteEvent = completeEvent;
+ CancelEvent = cancelEvent;
+ }
+ }
+
/// <summary>
/// This event signals that a construction interaction's DoAfter has completed successfully.
/// This wraps the original event and also keeps some custom data that event handlers might need.
using Content.Server.Construction.Components;
using Content.Server.DoAfter;
using Content.Server.Stack;
-using Content.Server.Tools;
using Content.Shared.Construction;
+using Content.Shared.Tools;
using JetBrains.Annotations;
using Robust.Server.Containers;
using Robust.Shared.Prototypes;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
- [Dependency] private readonly ToolSystem _toolSystem = default!;
+ [Dependency] private readonly SharedToolSystem _toolSystem = default!;
private const string SawmillName = "Construction";
private ISawmill _sawmill = default!;
using System.Linq;
-using System.Threading;
using Content.Server.Construction.Components;
using Content.Server.DoAfter;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Server.Wires;
+using Content.Shared.DoAfter;
using Content.Shared.Construction.Components;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Robust.Shared.Containers;
-using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Server.Construction;
public override void Initialize()
{
SubscribeLocalEvent<PartExchangerComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<PartExchangerComponent, RpedExchangeFinishedEvent>(OnFinished);
- SubscribeLocalEvent<PartExchangerComponent, RpedExchangeCancelledEvent>(OnCancelled);
+ SubscribeLocalEvent<PartExchangerComponent, DoAfterEvent>(OnDoAfter);
}
- private void OnFinished(EntityUid uid, PartExchangerComponent component, RpedExchangeFinishedEvent args)
+ private void OnDoAfter(EntityUid uid, PartExchangerComponent component, DoAfterEvent args)
{
- component.Token = null;
+ if (args.Cancelled || args.Handled || args.Args.Target == null)
+ {
+ component.AudioStream?.Stop();
+ return;
+ }
+
component.AudioStream?.Stop();
- if (!TryComp<MachineComponent>(args.Target, out var machine))
+ if (!TryComp<MachineComponent>(args.Args.Target.Value, out var machine))
return;
if (!TryComp<ServerStorageComponent>(uid, out var storage) || storage.Storage == null)
if (TryComp<MachinePartComponent>(ent, out var part))
{
machineParts.Add(part);
- _container.RemoveEntity(machine.Owner, ent);
+ _container.RemoveEntity(args.Args.Target.Value, ent);
}
}
_storage.Insert(uid, unused.Owner, null, false);
}
_construction.RefreshParts(machine);
- }
- private void OnCancelled(EntityUid uid, PartExchangerComponent component, RpedExchangeCancelledEvent args)
- {
- component.Token = null;
- component.AudioStream?.Stop();
+ args.Handled = true;
}
private void OnAfterInteract(EntityUid uid, PartExchangerComponent component, AfterInteractEvent args)
{
- if (component.Token != null)
- return;
-
if (component.DoDistanceCheck && !args.CanReach)
return;
component.AudioStream = _audio.PlayPvs(component.ExchangeSound, uid);
- component.Token = new CancellationTokenSource();
- _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ExchangeDuration, component.Token.Token, args.Target, args.Used)
+ _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ExchangeDuration, target:args.Target, used:args.Used)
{
BreakOnDamage = true,
BreakOnStun = true,
- BreakOnUserMove = true,
- UsedFinishedEvent = new RpedExchangeFinishedEvent(args.Target.Value),
- UsedCancelledEvent = new RpedExchangeCancelledEvent()
+ BreakOnUserMove = true
});
}
}
-
-public sealed class RpedExchangeFinishedEvent : EntityEventArgs
-{
- public readonly EntityUid Target;
-
- public RpedExchangeFinishedEvent(EntityUid target)
- {
- Target = target;
- }
-}
-
-public readonly struct RpedExchangeCancelledEvent
-{
-}
using Content.Server.Construction.Components;
-using Content.Server.Tools;
using Content.Server.Stack;
using Content.Shared.Interaction;
using Content.Shared.Stacks;
+using Content.Shared.Tools;
using Content.Shared.Tools.Components;
namespace Content.Server.Construction
{
public sealed class RefiningSystem : EntitySystem
{
- [Dependency] private readonly ToolSystem _toolSystem = default!;
+ [Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
public override void Initialize()
{
component.BeingWelded = true;
- if (!await _toolSystem.UseTool(args.Used, args.User, uid, component.RefineFuel, component.RefineTime, component.QualityNeeded))
+ var toolEvData = new ToolEventData(null);
+
+ if (!_toolSystem.UseTool(args.Used, args.User, uid, component.RefineTime, component.QualityNeeded, toolEvData, component.RefineFuel))
{
// failed to veld - abort refine
component.BeingWelded = false;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Content.Server.Recycling.Components;
+using Content.Shared.DoAfter;
using Robust.Shared.Map;
namespace Content.Server.Cuffs.Components
using Content.Server.DoAfter;
using Content.Shared.Cuffs.Components;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Popups;
using Content.Shared.Stunnable;
using Robust.Shared.Audio;
-using System.Threading;
using Content.Shared.Disease;
namespace Content.Server.Disease.Components
/// </summary>
public bool Used = false;
/// <summary>
- /// Token for interrupting swabbing do after.
- /// </summary>
- public CancellationTokenSource? CancelToken;
- /// <summary>
/// The disease prototype currently on the swab
/// </summary>
[ViewVariables]
/// If this vaccine has been used
/// </summary>
public bool Used = false;
- /// <summary>
- /// Token for interrupting injection do after.
- /// </summary>
- public CancellationTokenSource? CancelToken;
/// <summary>
/// The disease prototype currently on the vaccine
-using System.Threading;
using Content.Server.Disease.Components;
using Content.Shared.Disease;
using Content.Shared.Interaction;
using Robust.Shared.Utility;
using Content.Shared.Tools.Components;
using Content.Server.Station.Systems;
+using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Robust.Server.GameObjects;
// Private Events
SubscribeLocalEvent<DiseaseDiagnoserComponent, DiseaseMachineFinishedEvent>(OnDiagnoserFinished);
SubscribeLocalEvent<DiseaseVaccineCreatorComponent, DiseaseMachineFinishedEvent>(OnVaccinatorFinished);
- SubscribeLocalEvent<TargetSwabSuccessfulEvent>(OnTargetSwabSuccessful);
- SubscribeLocalEvent<SwabCancelledEvent>(OnSwabCancelled);
+ SubscribeLocalEvent<DoAfterEvent>(OnSwabDoAfter);
}
private Queue<EntityUid> AddQueue = new();
/// </summary>
private void OnAfterInteract(EntityUid uid, DiseaseSwabComponent swab, AfterInteractEvent args)
{
- if (swab.CancelToken != null)
- {
- swab.CancelToken.Cancel();
- swab.CancelToken = null;
- return;
- }
if (args.Target == null || !args.CanReach)
return;
- if (!TryComp<DiseaseCarrierComponent>(args.Target, out var carrier))
- return;
-
if (swab.Used)
{
_popupSystem.PopupEntity(Loc.GetString("swab-already-used"), args.User, args.User);
return;
}
- swab.CancelToken = new CancellationTokenSource();
- _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, swab.CancelToken.Token, target: args.Target)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, target: args.Target, used: uid)
{
- BroadcastFinishedEvent = new TargetSwabSuccessfulEvent(args.User, args.Target, swab, carrier),
- BroadcastCancelledEvent = new SwabCancelledEvent(swab),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
{
UpdateAppearance(uid, args.Powered, false);
}
- ///
- /// Private events
- ///
/// <summary>
/// Copies a disease prototype to the swab
/// after the doafter completes.
/// </summary>
- private void OnTargetSwabSuccessful(TargetSwabSuccessfulEvent args)
+ private void OnSwabDoAfter(DoAfterEvent args)
{
- if (args.Target == null)
+ if (args.Handled || args.Cancelled || !TryComp<DiseaseCarrierComponent>(args.Args.Target, out var carrier) || !TryComp<DiseaseSwabComponent>(args.Args.Target, out var swab))
return;
- args.Swab.Used = true;
- _popupSystem.PopupEntity(Loc.GetString("swab-swabbed", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.Target.Value, args.User);
+ swab.Used = true;
+ _popupSystem.PopupEntity(Loc.GetString("swab-swabbed", ("target", Identity.Entity(args.Args.Target.Value, EntityManager))), args.Args.Target.Value, args.Args.User);
- if (args.Swab.Disease != null || args.Carrier.Diseases.Count == 0)
+ if (swab.Disease != null || carrier.Diseases.Count == 0)
return;
- args.Swab.Disease = _random.Pick(args.Carrier.Diseases);
- }
+ swab.Disease = _random.Pick(carrier.Diseases);
- /// <summary>
- /// Cancels the swab doafter if needed.
- /// </summary>
- private static void OnSwabCancelled(SwabCancelledEvent args)
- {
- args.Swab.CancelToken = null;
}
+
/// <summary>
/// Prints a diagnostic report with its findings.
/// Also cancels the animation.
vaxxComp.Disease = args.Machine.Disease;
}
- /// <summary>
- /// Cancels the mouth-swabbing doafter
- /// </summary>
- private sealed class SwabCancelledEvent : EntityEventArgs
- {
- public readonly DiseaseSwabComponent Swab;
- public SwabCancelledEvent(DiseaseSwabComponent swab)
- {
- Swab = swab;
- }
- }
-
- /// <summary>
- /// Fires if the doafter for swabbing someone's mouth succeeds
- /// </summary>
- private sealed class TargetSwabSuccessfulEvent : EntityEventArgs
- {
- public EntityUid User { get; }
- public EntityUid? Target { get; }
- public DiseaseSwabComponent Swab { get; }
-
- public DiseaseCarrierComponent Carrier { get; }
-
- public TargetSwabSuccessfulEvent(EntityUid user, EntityUid? target, DiseaseSwabComponent swab, DiseaseCarrierComponent carrier)
- {
- User = user;
- Target = target;
- Swab = swab;
- Carrier = carrier;
- }
- }
-
/// <summary>
/// Fires when a disease machine is done
/// with its production delay and ready to
-using System.Threading;
using Content.Server.Body.Systems;
using Content.Server.Chat.Systems;
using Content.Server.Disease.Components;
using Content.Shared.Disease;
using Content.Shared.Disease.Components;
using Content.Shared.Disease.Events;
+using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Rejuvenate;
using Robust.Shared.Audio;
using Robust.Server.GameObjects;
-using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager;
// Handling stuff from other systems
SubscribeLocalEvent<DiseaseCarrierComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
// Private events stuff
- SubscribeLocalEvent<TargetVaxxSuccessfulEvent>(OnTargetVaxxSuccessful);
- SubscribeLocalEvent<VaxxCancelledEvent>(OnVaxxCancelled);
+ SubscribeLocalEvent<DiseaseVaccineComponent, DoAfterEvent>(OnDoAfter);
}
private Queue<EntityUid> AddQueue = new();
/// </summary>
private void OnAfterInteract(EntityUid uid, DiseaseVaccineComponent vaxx, AfterInteractEvent args)
{
- if (vaxx.CancelToken != null)
- {
- vaxx.CancelToken.Cancel();
- vaxx.CancelToken = null;
- return;
- }
- if (args.Target == null)
- return;
-
- if (!args.CanReach)
- return;
-
- if (vaxx.CancelToken != null)
- return;
-
- if (!TryComp<DiseaseCarrierComponent>(args.Target, out var carrier))
+ if (args.Target == null || !args.CanReach)
return;
if (vaxx.Used)
return;
}
- vaxx.CancelToken = new CancellationTokenSource();
- _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, vaxx.InjectDelay, vaxx.CancelToken.Token, target: args.Target)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, vaxx.InjectDelay, target: args.Target, used:uid)
{
- BroadcastFinishedEvent = new TargetVaxxSuccessfulEvent(args.User, args.Target, vaxx, carrier),
- BroadcastCancelledEvent = new VaxxCancelledEvent(vaxx),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
}
}
-
///
/// Helper functions
///
carrier.PastDiseases.Add(disease);
}
- ///
- /// Private Events Stuff
- ///
-
- /// <summary>
- /// Injects the vaccine into the target
- /// if the doafter is completed
- /// </summary>
- private void OnTargetVaxxSuccessful(TargetVaxxSuccessfulEvent args)
+ private void OnDoAfter(EntityUid uid, DiseaseVaccineComponent component, DoAfterEvent args)
{
- if (args.Vaxx.Disease == null)
+ if (args.Handled || args.Cancelled || !TryComp<DiseaseCarrierComponent>(args.Args.Target, out var carrier) || component.Disease == null)
return;
- Vaccinate(args.Carrier, args.Vaxx.Disease);
- EntityManager.DeleteEntity(args.Vaxx.Owner);
- }
- /// <summary>
- /// Cancels the vaccine doafter
- /// </summary>
- private static void OnVaxxCancelled(VaxxCancelledEvent args)
- {
- args.Vaxx.CancelToken = null;
- }
- /// These two are standard doafter stuff you can ignore
- private sealed class VaxxCancelledEvent : EntityEventArgs
- {
- public readonly DiseaseVaccineComponent Vaxx;
- public VaxxCancelledEvent(DiseaseVaccineComponent vaxx)
- {
- Vaxx = vaxx;
- }
- }
- private sealed class TargetVaxxSuccessfulEvent : EntityEventArgs
- {
- public EntityUid User { get; }
- public EntityUid? Target { get; }
- public DiseaseVaccineComponent Vaxx { get; }
- public DiseaseCarrierComponent Carrier { get; }
- public TargetVaxxSuccessfulEvent(EntityUid user, EntityUid? target, DiseaseVaccineComponent vaxx, DiseaseCarrierComponent carrier)
- {
- User = user;
- Target = target;
- Vaxx = vaxx;
- Carrier = carrier;
- }
+ Vaccinate(carrier, component.Disease);
+ EntityManager.DeleteEntity(uid);
+ args.Handled = true;
}
}
- /// <summary>
- /// This event is fired by chems
- /// and other brute-force rather than
- /// specific cures. It will roll the dice to attempt
- /// to cure each disease on the target
- /// </summary>
+ /// <summary>
+ /// This event is fired by chems
+ /// and other brute-force rather than
+ /// specific cures. It will roll the dice to attempt
+ /// to cure each disease on the target
+ /// </summary>
public sealed class CureDiseaseAttemptEvent : EntityEventArgs
{
public float CureChance { get; }
using Content.Shared.Destructible;
using Content.Shared.Disposal;
using Content.Shared.Disposal.Components;
+using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Physics.Components;
-using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Map.Components;
SubscribeLocalEvent<DisposalUnitComponent, GetVerbsEvent<Verb>>(AddClimbInsideVerb);
// Units
- SubscribeLocalEvent<DoInsertDisposalUnitEvent>(DoInsertDisposalUnit);
+ SubscribeLocalEvent<DisposalUnitComponent, DoAfterEvent>(OnDoAfter);
//UI
SubscribeLocalEvent<DisposalUnitComponent, SharedDisposalUnitComponent.UiButtonPressedMessage>(OnUiButtonPressed);
{
_handsSystem.TryDropIntoContainer(args.User, args.Using.Value, component.Container, checkActionBlocker: false, args.Hands);
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} inserted {ToPrettyString(args.Using.Value)} into {ToPrettyString(uid)}");
- AfterInsert(uid, component, args.Using.Value);
+ AfterInsert(uid, component, args.Using.Value, args.User);
}
};
args.Verbs.Add(insertVerb);
}
- private void DoInsertDisposalUnit(DoInsertDisposalUnitEvent ev)
+ private void OnDoAfter(EntityUid uid, DisposalUnitComponent component, DoAfterEvent args)
{
- var toInsert = ev.ToInsert;
-
- if (!TryComp(ev.Unit, out DisposalUnitComponent? unit))
- {
+ if (args.Handled || args.Cancelled || args.Args.Target == null || args.Args.Used == null)
return;
- }
- if (!unit.Container.Insert(toInsert))
- return;
- if (ev.User != null)
- _adminLogger.Add(LogType.Action, LogImpact.Medium,
- $"{ToPrettyString(ev.User.Value):player} inserted {ToPrettyString(toInsert)} into {ToPrettyString(ev.Unit)}");
- AfterInsert(ev.Unit, unit, toInsert);
+ AfterInsert(uid, component, args.Args.Target.Value, args.Args.User);
+
+ args.Handled = true;
}
public void DoInsertDisposalUnit(EntityUid uid, EntityUid toInsert, EntityUid user, DisposalUnitComponent? disposal = null)
return;
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(user):player} inserted {ToPrettyString(toInsert)} into {ToPrettyString(uid)}");
- AfterInsert(uid, disposal, toInsert);
+ AfterInsert(uid, disposal, toInsert, user);
}
public override void Update(float frameTime)
}
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} inserted {ToPrettyString(args.Used)} into {ToPrettyString(uid)}");
- AfterInsert(uid, component, args.Used);
+ AfterInsert(uid, component, args.Used, args.User);
args.Handled = true;
}
TryComp(ejectedId, out PhysicsComponent? body))
{
// TODO: We need to use a specific collision method (which sloth hasn't coded yet) for actual bounds overlaps.
+ // TODO: Come do this sloth :^)
// Check for itemcomp as we won't just block the disposal unit "sleeping" for something it can't collide with anyway.
if (!HasComp<ItemComponent>(ejectedId)
&& _lookup.GetWorldAABB(ejectedId).Intersects(disposalsBounds!.Value))
return false;
var delay = userId == toInsertId ? unit.EntryDelay : unit.DraggedEntryDelay;
- var ev = new DoInsertDisposalUnitEvent(userId, toInsertId, unitId);
if (delay <= 0 || userId == null)
{
- DoInsertDisposalUnit(ev);
+ AfterInsert(unitId, unit, toInsertId, userId);
return true;
}
// Can't check if our target AND disposals moves currently so we'll just check target.
// if you really want to check if disposals moves then add a predicate.
- var doAfterArgs = new DoAfterEventArgs(userId.Value, delay, default, toInsertId)
+ var doAfterArgs = new DoAfterEventArgs(userId.Value, delay, target:toInsertId, used:unitId)
{
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = false,
- BroadcastFinishedEvent = ev
};
_doAfterSystem.DoAfter(doAfterArgs);
}, component.AutomaticEngageToken.Token);
}
- public void AfterInsert(EntityUid uid, DisposalUnitComponent component, EntityUid inserted)
+ public void AfterInsert(EntityUid uid, DisposalUnitComponent component, EntityUid inserted, EntityUid? user = null)
{
+ if (!component.Container.Insert(inserted))
+ return;
+
+ if (user != inserted && user != null)
+ _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(user.Value):player} inserted {ToPrettyString(inserted)} into {ToPrettyString(uid)}");
+
TryQueueEngage(uid, component);
if (TryComp(inserted, out ActorComponent? actor))
+++ /dev/null
-using System.Threading.Tasks;
-using Content.Server.Hands.Components;
-using Content.Shared.Stunnable;
-using Robust.Shared.Map;
-using Robust.Shared.Timing;
-
-namespace Content.Server.DoAfter
-{
- public sealed class DoAfter
- {
- public Task<DoAfterStatus> AsTask { get; }
-
- private TaskCompletionSource<DoAfterStatus> Tcs { get; }
-
- public readonly DoAfterEventArgs EventArgs;
-
- public TimeSpan StartTime { get; }
-
- public float Elapsed { get; set; }
-
- public EntityCoordinates UserGrid { get; }
-
- public EntityCoordinates TargetGrid { get; }
-
-#pragma warning disable RA0004
- public DoAfterStatus Status => AsTask.IsCompletedSuccessfully ? AsTask.Result : DoAfterStatus.Running;
-#pragma warning restore RA0004
-
- // NeedHand
- private readonly string? _activeHand;
- private readonly EntityUid? _activeItem;
-
- public DoAfter(DoAfterEventArgs eventArgs, IEntityManager entityManager)
- {
- EventArgs = eventArgs;
- StartTime = IoCManager.Resolve<IGameTiming>().CurTime;
-
- if (eventArgs.BreakOnUserMove)
- {
- UserGrid = entityManager.GetComponent<TransformComponent>(eventArgs.User).Coordinates;
- }
-
- if (eventArgs.Target != null && eventArgs.BreakOnTargetMove)
- {
- // Target should never be null if the bool is set.
- TargetGrid = entityManager.GetComponent<TransformComponent>(eventArgs.Target!.Value).Coordinates;
- }
-
- // For this we need to stay on the same hand slot and need the same item in that hand slot
- // (or if there is no item there we need to keep it free).
- if (eventArgs.NeedHand && entityManager.TryGetComponent(eventArgs.User, out HandsComponent? handsComponent))
- {
- _activeHand = handsComponent.ActiveHand?.Name;
- _activeItem = handsComponent.ActiveHandEntity;
- }
-
- Tcs = new TaskCompletionSource<DoAfterStatus>();
- AsTask = Tcs.Task;
- }
-
- public void Cancel()
- {
- if (Status == DoAfterStatus.Running)
- Tcs.SetResult(DoAfterStatus.Cancelled);
- }
-
- public void Run(float frameTime, IEntityManager entityManager)
- {
- switch (Status)
- {
- case DoAfterStatus.Running:
- break;
- case DoAfterStatus.Cancelled:
- case DoAfterStatus.Finished:
- return;
- default:
- throw new ArgumentOutOfRangeException();
- }
-
- Elapsed += frameTime;
-
- if (IsFinished())
- {
- // Do the final checks here
- if (!TryPostCheck())
- {
- Tcs.SetResult(DoAfterStatus.Cancelled);
- }
- else
- {
- Tcs.SetResult(DoAfterStatus.Finished);
- }
-
- return;
- }
-
- if (IsCancelled(entityManager))
- {
- Tcs.SetResult(DoAfterStatus.Cancelled);
- }
- }
-
- private bool IsCancelled(IEntityManager entityManager)
- {
- if (!entityManager.EntityExists(EventArgs.User) || EventArgs.Target is {} target && !entityManager.EntityExists(target))
- {
- return true;
- }
-
- //https://github.com/tgstation/tgstation/blob/1aa293ea337283a0191140a878eeba319221e5df/code/__HELPERS/mobs.dm
- if (EventArgs.CancelToken.IsCancellationRequested)
- {
- return true;
- }
-
- // TODO :Handle inertia in space.
- if (EventArgs.BreakOnUserMove && !entityManager.GetComponent<TransformComponent>(EventArgs.User).Coordinates.InRange(
- entityManager, UserGrid, EventArgs.MovementThreshold))
- {
- return true;
- }
-
- if (EventArgs.Target != null &&
- EventArgs.BreakOnTargetMove &&
- !entityManager.GetComponent<TransformComponent>(EventArgs.Target!.Value).Coordinates.InRange(entityManager, TargetGrid, EventArgs.MovementThreshold))
- {
- return true;
- }
-
- if (EventArgs.ExtraCheck != null && !EventArgs.ExtraCheck.Invoke())
- {
- return true;
- }
-
- if (EventArgs.BreakOnStun &&
- entityManager.HasComponent<StunnedComponent>(EventArgs.User))
- {
- return true;
- }
-
- if (EventArgs.NeedHand)
- {
- if (!entityManager.TryGetComponent(EventArgs.User, out HandsComponent? handsComponent))
- {
- // If we had a hand but no longer have it that's still a paddlin'
- if (_activeHand != null)
- {
- return true;
- }
- }
- else
- {
- var currentActiveHand = handsComponent.ActiveHand?.Name;
- if (_activeHand != currentActiveHand)
- {
- return true;
- }
-
- var currentItem = handsComponent.ActiveHandEntity;
- if (_activeItem != currentItem)
- {
- return true;
- }
- }
- }
-
- if (EventArgs.DistanceThreshold != null)
- {
- var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
- TransformComponent? userXform = null;
-
- // Check user distance to target AND used entities.
- if (EventArgs.Target != null && !EventArgs.User.Equals(EventArgs.Target))
- {
- //recalculate Target location in case Target has also moved
- var targetCoordinates = xformQuery.GetComponent(EventArgs.Target.Value).Coordinates;
- userXform ??= xformQuery.GetComponent(EventArgs.User);
- if (!userXform.Coordinates.InRange(entityManager, targetCoordinates, EventArgs.DistanceThreshold.Value))
- return true;
- }
-
- if (EventArgs.Used != null)
- {
- var targetCoordinates = xformQuery.GetComponent(EventArgs.Used.Value).Coordinates;
- userXform ??= xformQuery.GetComponent(EventArgs.User);
- if (!userXform.Coordinates.InRange(entityManager, targetCoordinates, EventArgs.DistanceThreshold.Value))
- return true;
- }
- }
-
- return false;
- }
-
- private bool TryPostCheck()
- {
- return EventArgs.PostCheck?.Invoke() != false;
- }
-
- private bool IsFinished()
- {
- if (Elapsed <= EventArgs.Delay)
- {
- return false;
- }
-
- return true;
- }
- }
-}
+++ /dev/null
-using Content.Shared.DoAfter;
-
-namespace Content.Server.DoAfter
-{
- [RegisterComponent, Access(typeof(DoAfterSystem))]
- public sealed class DoAfterComponent : SharedDoAfterComponent
- {
- public readonly Dictionary<DoAfter, byte> DoAfters = new();
-
- // So the client knows which one to update (and so we don't send all of the do_afters every time 1 updates)
- // we'll just send them the index. Doesn't matter if it wraps around.
- public byte RunningIndex;
- }
-
- /// <summary>
- /// Added to entities that are currently performing any doafters.
- /// </summary>
- [RegisterComponent]
- public sealed class ActiveDoAfterComponent : Component {}
-}
+++ /dev/null
-using System.Threading;
-using Content.Shared.FixedPoint;
-using Robust.Shared.Utility;
-
-namespace Content.Server.DoAfter
-{
- public sealed class DoAfterEventArgs
- {
- /// <summary>
- /// The entity invoking do_after
- /// </summary>
- public EntityUid User { get; }
-
- /// <summary>
- /// How long does the do_after require to complete
- /// </summary>
- public float Delay { get; }
-
- /// <summary>
- /// Applicable target (if relevant)
- /// </summary>
- public EntityUid? Target { get; }
-
- /// <summary>
- /// Entity used by the User on the Target.
- /// </summary>
- public EntityUid? Used { get; set; }
-
- /// <summary>
- /// Manually cancel the do_after so it no longer runs
- /// </summary>
- public CancellationToken CancelToken { get; }
-
- // Break the chains
- /// <summary>
- /// Whether we need to keep our active hand as is (i.e. can't change hand or change item).
- /// This also covers requiring the hand to be free (if applicable).
- /// </summary>
- public bool NeedHand { get; set; }
-
- /// <summary>
- /// If do_after stops when the user moves
- /// </summary>
- public bool BreakOnUserMove { get; set; }
-
- /// <summary>
- /// If do_after stops when the target moves (if there is a target)
- /// </summary>
- public bool BreakOnTargetMove { get; set; }
-
- /// <summary>
- /// Threshold for user and target movement
- /// </summary>
- public float MovementThreshold { get; set; }
-
- public bool BreakOnDamage { get; set; }
-
- /// <summary>
- /// Threshold for user damage
- /// </summary>
- public FixedPoint2 DamageThreshold { get; set; }
- public bool BreakOnStun { get; set; }
-
- /// <summary>
- /// Threshold for distance user from the used OR target entities.
- /// </summary>
- public float? DistanceThreshold { get; set; }
-
- /// <summary>
- /// Requires a function call once at the end (like InRangeUnobstructed).
- /// </summary>
- /// <remarks>
- /// Anything that needs a pre-check should do it itself so no DoAfterState is ever sent to the client.
- /// </remarks>
- public Func<bool>? PostCheck { get; set; } = null;
-
- /// <summary>
- /// Additional conditions that need to be met. Return false to cancel.
- /// </summary>
- public Func<bool>? ExtraCheck { get; set; }
-
- /// <summary>
- /// Event to be raised directed to the <see cref="User"/> entity when the DoAfter is cancelled.
- /// </summary>
- public object? UserCancelledEvent { get; set; }
-
- /// <summary>
- /// Event to be raised directed to the <see cref="User"/> entity when the DoAfter is finished successfully.
- /// </summary>
- public object? UserFinishedEvent { get; set; }
-
- /// <summary>
- /// Event to be raised directed to the <see cref="Used"/> entity when the DoAfter is cancelled.
- /// </summary>
- public object? UsedCancelledEvent { get; set; }
-
- /// <summary>
- /// Event to be raised directed to the <see cref="Used"/> entity when the DoAfter is finished successfully.
- /// </summary>
- public object? UsedFinishedEvent { get; set; }
-
- /// <summary>
- /// Event to be raised directed to the <see cref="Target"/> entity when the DoAfter is cancelled.
- /// </summary>
- public object? TargetCancelledEvent { get; set; }
-
- /// <summary>
- /// Event to be raised directed to the <see cref="Target"/> entity when the DoAfter is finished successfully.
- /// </summary>
- public object? TargetFinishedEvent { get; set; }
-
- /// <summary>
- /// Event to be broadcast when the DoAfter is cancelled.
- /// </summary>
- public object? BroadcastCancelledEvent { get; set; }
-
- /// <summary>
- /// Event to be broadcast when the DoAfter is finished successfully.
- /// </summary>
- public object? BroadcastFinishedEvent { get; set; }
-
- public DoAfterEventArgs(
- EntityUid user,
- float delay,
- CancellationToken cancelToken = default,
- EntityUid? target = null,
- EntityUid? used = null)
- {
- User = user;
- Delay = delay;
- CancelToken = cancelToken;
- Target = target;
- Used = used;
- MovementThreshold = 0.1f;
- DamageThreshold = 1.0;
-
- if (Target == null)
- {
- DebugTools.Assert(!BreakOnTargetMove);
- BreakOnTargetMove = false;
- }
- }
- }
-}
-using System.Linq;
-using System.Threading.Tasks;
-using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.Mobs;
using JetBrains.Annotations;
-using Robust.Shared.GameStates;
-namespace Content.Server.DoAfter
-{
- [UsedImplicitly]
- public sealed class DoAfterSystem : EntitySystem
- {
- // We cache these lists as to not allocate them every update tick...
- private readonly Queue<DoAfter> _cancelled = new();
- private readonly Queue<DoAfter> _finished = new();
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<DoAfterComponent, DamageChangedEvent>(OnDamage);
- SubscribeLocalEvent<DoAfterComponent, MobStateChangedEvent>(OnStateChanged);
- SubscribeLocalEvent<DoAfterComponent, ComponentGetState>(OnDoAfterGetState);
- }
-
- public void Add(DoAfterComponent component, DoAfter doAfter)
- {
- component.DoAfters.Add(doAfter, component.RunningIndex);
- EnsureComp<ActiveDoAfterComponent>(component.Owner);
- component.RunningIndex++;
- Dirty(component);
- }
-
- public void Cancelled(DoAfterComponent component, DoAfter doAfter)
- {
- if (!component.DoAfters.TryGetValue(doAfter, out var index))
- return;
-
- component.DoAfters.Remove(doAfter);
-
- if (component.DoAfters.Count == 0)
- {
- RemComp<ActiveDoAfterComponent>(component.Owner);
- }
-
- RaiseNetworkEvent(new CancelledDoAfterMessage(component.Owner, index));
- }
-
- /// <summary>
- /// Call when the particular DoAfter is finished.
- /// Client should be tracking this independently.
- /// </summary>
- public void Finished(DoAfterComponent component, DoAfter doAfter)
- {
- if (!component.DoAfters.ContainsKey(doAfter))
- return;
-
- component.DoAfters.Remove(doAfter);
-
- if (component.DoAfters.Count == 0)
- {
- RemComp<ActiveDoAfterComponent>(component.Owner);
- }
- }
-
- private void OnDoAfterGetState(EntityUid uid, DoAfterComponent component, ref ComponentGetState args)
- {
- var toAdd = new List<ClientDoAfter>(component.DoAfters.Count);
-
- foreach (var (doAfter, _) in component.DoAfters)
- {
- // THE ALMIGHTY PYRAMID
- var clientDoAfter = new ClientDoAfter(
- component.DoAfters[doAfter],
- doAfter.UserGrid,
- doAfter.TargetGrid,
- doAfter.StartTime,
- doAfter.EventArgs.Delay,
- doAfter.EventArgs.BreakOnUserMove,
- doAfter.EventArgs.BreakOnTargetMove,
- doAfter.EventArgs.MovementThreshold,
- doAfter.EventArgs.DamageThreshold,
- doAfter.EventArgs.Target);
-
- toAdd.Add(clientDoAfter);
- }
-
- args.State = new DoAfterComponentState(toAdd);
- }
-
- private void OnStateChanged(EntityUid uid, DoAfterComponent component, MobStateChangedEvent args)
- {
- if (args.NewMobState == MobState.Alive)
- return;
-
- foreach (var (doAfter, _) in component.DoAfters)
- {
- doAfter.Cancel();
- }
- }
-
- /// <summary>
- /// Cancels DoAfter if it breaks on damage and it meets the threshold
- /// </summary>
- /// <param name="_">
- /// The EntityUID of the user
- /// </param>
- /// <param name="component"></param>
- /// <param name="args"></param>
- public void OnDamage(EntityUid _, DoAfterComponent component, DamageChangedEvent args)
- {
- if (!args.InterruptsDoAfters || !args.DamageIncreased || args.DamageDelta == null)
- return;
-
- foreach (var (doAfter, _) in component.DoAfters)
- {
- if (doAfter.EventArgs.BreakOnDamage && args.DamageDelta?.Total.Float() > doAfter.EventArgs.DamageThreshold)
- {
- doAfter.Cancel();
- }
- }
- }
+namespace Content.Server.DoAfter;
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- foreach (var (_, comp) in EntityManager.EntityQuery<ActiveDoAfterComponent, DoAfterComponent>())
- {
- foreach (var (doAfter, _) in comp.DoAfters.ToArray())
- {
- doAfter.Run(frameTime, EntityManager);
-
- switch (doAfter.Status)
- {
- case DoAfterStatus.Running:
- break;
- case DoAfterStatus.Cancelled:
- _cancelled.Enqueue(doAfter);
- break;
- case DoAfterStatus.Finished:
- _finished.Enqueue(doAfter);
- break;
- default:
- throw new ArgumentOutOfRangeException();
- }
- }
-
- while (_cancelled.TryDequeue(out var doAfter))
- {
- Cancelled(comp, doAfter);
-
- if(EntityManager.EntityExists(doAfter.EventArgs.User) && doAfter.EventArgs.UserCancelledEvent != null)
- RaiseLocalEvent(doAfter.EventArgs.User, doAfter.EventArgs.UserCancelledEvent, false);
-
- if (doAfter.EventArgs.Used is {} used && EntityManager.EntityExists(used) && doAfter.EventArgs.UsedCancelledEvent != null)
- RaiseLocalEvent(used, doAfter.EventArgs.UsedCancelledEvent);
-
- if(doAfter.EventArgs.Target is {} target && EntityManager.EntityExists(target) && doAfter.EventArgs.TargetCancelledEvent != null)
- RaiseLocalEvent(target, doAfter.EventArgs.TargetCancelledEvent, false);
-
- if(doAfter.EventArgs.BroadcastCancelledEvent != null)
- RaiseLocalEvent(doAfter.EventArgs.BroadcastCancelledEvent);
- }
-
- while (_finished.TryDequeue(out var doAfter))
- {
- Finished(comp, doAfter);
-
- if(EntityManager.EntityExists(doAfter.EventArgs.User) && doAfter.EventArgs.UserFinishedEvent != null)
- RaiseLocalEvent(doAfter.EventArgs.User, doAfter.EventArgs.UserFinishedEvent, false);
-
- if(doAfter.EventArgs.Used is {} used && EntityManager.EntityExists(used) && doAfter.EventArgs.UsedFinishedEvent != null)
- RaiseLocalEvent(used, doAfter.EventArgs.UsedFinishedEvent);
-
- if(doAfter.EventArgs.Target is {} target && EntityManager.EntityExists(target) && doAfter.EventArgs.TargetFinishedEvent != null)
- RaiseLocalEvent(target, doAfter.EventArgs.TargetFinishedEvent, false);
-
- if(doAfter.EventArgs.BroadcastFinishedEvent != null)
- RaiseLocalEvent(doAfter.EventArgs.BroadcastFinishedEvent);
- }
- }
- }
-
- /// <summary>
- /// Tasks that are delayed until the specified time has passed
- /// These can be potentially cancelled by the user moving or when other things happen.
- /// </summary>
- /// <param name="eventArgs"></param>
- /// <returns></returns>
- public async Task<DoAfterStatus> WaitDoAfter(DoAfterEventArgs eventArgs)
- {
- var doAfter = CreateDoAfter(eventArgs);
-
- await doAfter.AsTask;
-
- return doAfter.Status;
- }
-
- /// <summary>
- /// Creates a DoAfter without waiting for it to finish. You can use events with this.
- /// These can be potentially cancelled by the user moving or when other things happen.
- /// </summary>
- /// <param name="eventArgs"></param>
- public void DoAfter(DoAfterEventArgs eventArgs)
- {
- CreateDoAfter(eventArgs);
- }
-
- private DoAfter CreateDoAfter(DoAfterEventArgs eventArgs)
- {
- // Setup
- var doAfter = new DoAfter(eventArgs, EntityManager);
- // Caller's gonna be responsible for this I guess
- var doAfterComponent = Comp<DoAfterComponent>(eventArgs.User);
- Add(doAfterComponent, doAfter);
- return doAfter;
- }
- }
+[UsedImplicitly]
+public sealed class DoAfterSystem : SharedDoAfterSystem
+{
- public enum DoAfterStatus : byte
- {
- Running,
- Cancelled,
- Finished,
- }
}
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Construction;
-using Content.Server.Doors.Components;
-using Content.Server.Tools;
using Content.Server.Tools.Systems;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Verbs;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
-using Robust.Shared.Player;
using System.Linq;
using Content.Server.Power.EntitySystems;
+using Content.Shared.Tools;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
[Dependency] private readonly AirlockSystem _airlock = default!;
[Dependency] private readonly AirtightSystem _airtightSystem = default!;
[Dependency] private readonly ConstructionSystem _constructionSystem = default!;
- [Dependency] private readonly ToolSystem _toolSystem = default!;
+ [Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
public override void Initialize()
RaiseLocalEvent(target, modEv, false);
door.BeingPried = true;
- _toolSystem.UseTool(tool, user, target, 0f, modEv.PryTimeModifier * door.PryTime, door.PryingQuality,
- new PryFinishedEvent(), new PryCancelledEvent(), target);
-
+ var toolEvData = new ToolEventData(new PryFinishedEvent(), cancelledEv: new PryCancelledEvent(),targetEntity: target);
+ _toolSystem.UseTool(tool, user, target, modEv.PryTimeModifier * door.PryTime, new[] { door.PryingQuality }, toolEvData);
return true; // we might not actually succeeded, but a do-after has started
}
Params = AudioParams.Default.WithVolume(3f),
};
- public CancellationTokenSource? CancelToken;
-
[ViewVariables(VVAccess.ReadWrite), DataField("devourWhitelist")]
public EntityWhitelist? DevourWhitelist = new()
{
using Content.Server.Chat.Systems;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
-using Content.Server.Humanoid;
using Content.Server.NPC;
using Content.Shared.Damage;
using Content.Shared.Dragon;
using Robust.Shared.Map;
using Robust.Shared.Random;
using Content.Server.NPC.Systems;
+using Content.Shared.DoAfter;
using Content.Shared.Humanoid;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
SubscribeLocalEvent<DragonComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<DragonComponent, ComponentShutdown>(OnShutdown);
- SubscribeLocalEvent<DragonComponent, DragonDevourComplete>(OnDragonDevourComplete);
SubscribeLocalEvent<DragonComponent, DragonDevourActionEvent>(OnDevourAction);
SubscribeLocalEvent<DragonComponent, DragonSpawnRiftActionEvent>(OnDragonRift);
SubscribeLocalEvent<DragonComponent, RefreshMovementSpeedModifiersEvent>(OnDragonMove);
- SubscribeLocalEvent<DragonComponent, DragonStructureDevourComplete>(OnDragonStructureDevourComplete);
- SubscribeLocalEvent<DragonComponent, DragonDevourCancelledEvent>(OnDragonDevourCancelled);
+ SubscribeLocalEvent<DragonComponent, DoAfterEvent>(OnDoAfter);
+
SubscribeLocalEvent<DragonComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<DragonRiftComponent, ComponentShutdown>(OnRiftShutdown);
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRiftRoundEnd);
}
+ private void OnDoAfter(EntityUid uid, DragonComponent component, DoAfterEvent args)
+ {
+ if (args.Handled || args.Cancelled)
+ return;
+
+ var ichorInjection = new Solution(component.DevourChem, component.DevourHealRate);
+
+ //Humanoid devours allow dragon to get eggs, corpses included
+ if (HasComp<HumanoidAppearanceComponent>(args.Args.Target))
+ {
+ ichorInjection.ScaleSolution(0.5f);
+ component.DragonStomach.Insert(args.Args.Target.Value);
+ _bloodstreamSystem.TryAddToChemicals(uid, ichorInjection);
+ }
+
+ //TODO: Figure out a better way of removing structures via devour that still entails standing still and waiting for a DoAfter. Somehow.
+ //If it's not human, it must be a structure
+ else if (args.Args.Target != null)
+ EntityManager.QueueDeleteEntity(args.Args.Target.Value);
+
+ if (component.SoundDevour != null)
+ _audioSystem.PlayPvs(component.SoundDevour, uid, component.SoundDevour.Params);
+ }
+
public override void Update(float frameTime)
{
base.Update(frameTime);
}
}
- private void OnDragonDevourCancelled(EntityUid uid, DragonComponent component, DragonDevourCancelledEvent args)
- {
- component.CancelToken = null;
- }
-
- private void OnDragonDevourComplete(EntityUid uid, DragonComponent component, DragonDevourComplete args)
- {
- component.CancelToken = null;
- var ichorInjection = new Solution(component.DevourChem, component.DevourHealRate);
-
- //Humanoid devours allow dragon to get eggs, corpses included
- if (!EntityManager.HasComponent<HumanoidAppearanceComponent>(args.Target))
- {
- ichorInjection.ScaleSolution(0.5f);
- }
-
- _bloodstreamSystem.TryAddToChemicals(uid, ichorInjection);
- component.DragonStomach.Insert(args.Target);
-
- if (component.SoundDevour != null)
- _audioSystem.PlayPvs(component.SoundDevour, uid, component.SoundDevour.Params);
- }
-
- private void OnDragonStructureDevourComplete(EntityUid uid, DragonComponent component, DragonStructureDevourComplete args)
- {
- component.CancelToken = null;
- //TODO: Figure out a better way of removing structures via devour that still entails standing still and waiting for a DoAfter. Somehow.
- EntityManager.QueueDeleteEntity(args.Target);
-
- if (component.SoundDevour != null)
- _audioSystem.PlayPvs(component.SoundDevour, uid, component.SoundDevour.Params);
- }
-
private void Roar(DragonComponent component)
{
if (component.SoundRoar != null)
/// </summary>
private void OnDevourAction(EntityUid uid, DragonComponent component, DragonDevourActionEvent args)
{
- if (component.CancelToken != null ||
- args.Handled ||
- component.DevourWhitelist?.IsValid(args.Target, EntityManager) != true)
- {
+ if (args.Handled || component.DevourWhitelist?.IsValid(args.Target, EntityManager) != true)
return;
- }
args.Handled = true;
var target = args.Target;
{
case MobState.Critical:
case MobState.Dead:
- component.CancelToken = new CancellationTokenSource();
- _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.DevourTime, component.CancelToken.Token, target)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.DevourTime, target:target)
{
- UserFinishedEvent = new DragonDevourComplete(uid, target),
- UserCancelledEvent = new DragonDevourCancelledEvent(),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
if (component.SoundStructureDevour != null)
_audioSystem.PlayPvs(component.SoundStructureDevour, uid, component.SoundStructureDevour.Params);
- component.CancelToken = new CancellationTokenSource();
-
- _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.StructureDevourTime, component.CancelToken.Token, target)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.StructureDevourTime, target:target)
{
- UserFinishedEvent = new DragonStructureDevourComplete(uid, target),
- UserCancelledEvent = new DragonDevourCancelledEvent(),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
});
}
-
- private sealed class DragonDevourComplete : EntityEventArgs
- {
- public EntityUid User { get; }
- public EntityUid Target { get; }
-
- public DragonDevourComplete(EntityUid user, EntityUid target)
- {
- User = user;
- Target = target;
- }
- }
-
- private sealed class DragonStructureDevourComplete : EntityEventArgs
- {
- public EntityUid User { get; }
- public EntityUid Target { get; }
-
- public DragonStructureDevourComplete(EntityUid user, EntityUid target)
- {
- User = user;
- Target = target;
- }
- }
-
- private sealed class DragonDevourCancelledEvent : EntityEventArgs {}
}
}
using Content.Server.DoAfter;
using Content.Server.Engineering.Components;
+using Content.Shared.DoAfter;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Content.Server.DoAfter;
using Content.Server.Engineering.Components;
using Content.Server.Stack;
+using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Maps;
using Content.Shared.Stacks;
using System.Threading;
using Content.Server.DoAfter;
using Content.Shared.Alert;
+using Content.Shared.DoAfter;
using Content.Shared.Ensnaring.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.StepTrigger.Systems;
return;
if (ensnared.IsEnsnared)
- ForceFree(component);
+ ForceFree(uid, component);
}
private void AttemptStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggerAttemptEvent args)
private void OnStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggeredEvent args)
{
- TryEnsnare(args.Tripper, component);
+ TryEnsnare(args.Tripper, uid, component);
}
private void OnThrowHit(EntityUid uid, EnsnaringComponent component, ThrowDoHitEvent args)
if (!component.CanThrowTrigger)
return;
- TryEnsnare(args.Target, component);
+ TryEnsnare(args.Target, uid, component);
}
/// <summary>
/// Used where you want to try to ensnare an entity with the <see cref="EnsnareableComponent"/>
/// </summary>
/// <param name="target">The entity that will be ensnared</param>
+ /// <paramref name="ensnare"> The entity that is used to ensnare</param>
/// <param name="component">The ensnaring component</param>
- public void TryEnsnare(EntityUid target, EnsnaringComponent component)
+ public void TryEnsnare(EntityUid target, EntityUid ensnare, EnsnaringComponent component)
{
//Don't do anything if they don't have the ensnareable component.
if (!TryComp<EnsnareableComponent>(target, out var ensnareable))
return;
component.Ensnared = target;
- ensnareable.Container.Insert(component.Owner);
+ ensnareable.Container.Insert(ensnare);
ensnareable.IsEnsnared = true;
Dirty(ensnareable);
- UpdateAlert(ensnareable);
+ UpdateAlert(ensnare, ensnareable);
var ev = new EnsnareEvent(component.WalkSpeed, component.SprintSpeed);
RaiseLocalEvent(target, ev);
}
/// Used where you want to try to free an entity with the <see cref="EnsnareableComponent"/>
/// </summary>
/// <param name="target">The entity that will be free</param>
+ /// <param name="ensnare">The entity used to ensnare</param>
/// <param name="component">The ensnaring component</param>
- public void TryFree(EntityUid target, EnsnaringComponent component, EntityUid? user = null)
+ public void TryFree(EntityUid target, EntityUid ensnare, EnsnaringComponent component, EntityUid? user = null)
{
//Don't do anything if they don't have the ensnareable component.
if (!HasComp<EnsnareableComponent>(target))
return;
- if (component.CancelToken != null)
- return;
-
- component.CancelToken = new CancellationTokenSource();
-
var isOwner = !(user != null && target != user);
var freeTime = isOwner ? component.BreakoutTime : component.FreeTime;
bool breakOnMove;
else
breakOnMove = true;
- var doAfterEventArgs = new DoAfterEventArgs(target, freeTime, component.CancelToken.Token, target)
+ var doAfterEventArgs = new DoAfterEventArgs(target, freeTime, target: target, used:ensnare)
{
BreakOnUserMove = breakOnMove,
BreakOnTargetMove = breakOnMove,
BreakOnDamage = false,
BreakOnStun = true,
- NeedHand = true,
- TargetFinishedEvent = new FreeEnsnareDoAfterComplete(component.Owner),
- TargetCancelledEvent = new FreeEnsnareDoAfterCancel(component.Owner),
+ NeedHand = true
};
_doAfter.DoAfter(doAfterEventArgs);
if (isOwner)
- _popup.PopupEntity(Loc.GetString("ensnare-component-try-free", ("ensnare", component.Owner)), target, target);
+ _popup.PopupEntity(Loc.GetString("ensnare-component-try-free", ("ensnare", ensnare)), target, target);
if (!isOwner && user != null)
- {
- _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-other", ("ensnare", component.Owner), ("user", Identity.Entity(target, EntityManager))), user.Value, user.Value);
- }
+ _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-other", ("ensnare", ensnare), ("user", Identity.Entity(target, EntityManager))), user.Value, user.Value);
}
/// <summary>
/// Used to force free someone for things like if the <see cref="EnsnaringComponent"/> is removed
/// </summary>
- public void ForceFree(EnsnaringComponent component)
+ public void ForceFree(EntityUid ensnare, EnsnaringComponent component)
{
if (!TryComp<EnsnareableComponent>(component.Ensnared, out var ensnareable))
return;
- ensnareable.Container.ForceRemove(component.Owner);
+ ensnareable.Container.ForceRemove(ensnare);
ensnareable.IsEnsnared = false;
Dirty(ensnareable);
component.Ensnared = null;
- UpdateAlert(ensnareable);
+ UpdateAlert(ensnare, ensnareable);
var ev = new EnsnareRemoveEvent();
- RaiseLocalEvent(component.Owner, ev);
+ RaiseLocalEvent(ensnare, ev);
}
- public void UpdateAlert(EnsnareableComponent component)
+ public void UpdateAlert(EntityUid ensnare, EnsnareableComponent component)
{
if (!component.IsEnsnared)
- {
- _alerts.ClearAlert(component.Owner, AlertType.Ensnared);
- }
+ _alerts.ClearAlert(ensnare, AlertType.Ensnared);
else
- {
- _alerts.ShowAlert(component.Owner, AlertType.Ensnared);
- }
+ _alerts.ShowAlert(ensnare, AlertType.Ensnared);
}
}
using Content.Server.Popups;
+using Content.Shared.DoAfter;
using Content.Shared.Ensnaring;
using Content.Shared.Ensnaring.Components;
using Content.Shared.Popups;
InitializeEnsnaring();
SubscribeLocalEvent<EnsnareableComponent, ComponentInit>(OnEnsnareableInit);
- SubscribeLocalEvent<EnsnareableComponent, FreeEnsnareDoAfterComplete>(OnFreeComplete);
- SubscribeLocalEvent<EnsnareableComponent, FreeEnsnareDoAfterCancel>(OnFreeFail);
+ SubscribeLocalEvent<EnsnareableComponent, DoAfterEvent>(OnDoAfter);
}
private void OnEnsnareableInit(EntityUid uid, EnsnareableComponent component, ComponentInit args)
{
- component.Container = _container.EnsureContainer<Container>(component.Owner, "ensnare");
+ component.Container = _container.EnsureContainer<Container>(uid, "ensnare");
}
- private void OnFreeComplete(EntityUid uid, EnsnareableComponent component, FreeEnsnareDoAfterComplete args)
+ private void OnDoAfter(EntityUid uid, EnsnareableComponent component, DoAfterEvent args)
{
- if (!TryComp<EnsnaringComponent>(args.EnsnaringEntity, out var ensnaring))
+ if (args.Handled || !TryComp<EnsnaringComponent>(args.Args.Used, out var ensnaring))
return;
- component.Container.Remove(args.EnsnaringEntity);
+ if (args.Cancelled)
+ {
+ _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-fail", ("ensnare", args.Args.Used)), uid, uid, PopupType.Large);
+ return;
+ }
+
+ component.Container.Remove(args.Args.Used.Value);
component.IsEnsnared = false;
Dirty(component);
ensnaring.Ensnared = null;
- _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-complete", ("ensnare", args.EnsnaringEntity)),
- uid, uid, PopupType.Large);
+ _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-complete", ("ensnare", args.Args.Used)), uid, uid, PopupType.Large);
- UpdateAlert(component);
+ UpdateAlert(args.Args.Used.Value, component);
var ev = new EnsnareRemoveEvent();
RaiseLocalEvent(uid, ev);
- ensnaring.CancelToken = null;
- }
-
- private void OnFreeFail(EntityUid uid, EnsnareableComponent component, FreeEnsnareDoAfterCancel args)
- {
- if (!TryComp<EnsnaringComponent>(args.EnsnaringEntity, out var ensnaring))
- return;
-
- ensnaring.CancelToken = null;
-
- _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-fail", ("ensnare", args.EnsnaringEntity)),
- uid, uid, PopupType.Large);
+ args.Handled = true;
}
}
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Server.Tools;
+using Content.Shared.Tools.Components;
namespace Content.Server.Eye.Blinding.EyeProtection
{
-using System.Threading;
-
namespace Content.Server.Fluids.Components;
[RegisterComponent]
[DataField("spillDelay")]
public float? SpillDelay;
-
- public CancellationTokenSource? CancelToken;
}
using Content.Server.Fluids.Components;
using Content.Server.Popups;
using Content.Shared.Chemistry.Components;
+using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids;
using Content.Shared.Interaction;
base.Initialize();
SubscribeLocalEvent<AbsorbentComponent, ComponentInit>(OnAbsorbentInit);
SubscribeLocalEvent<AbsorbentComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<AbsorbentComponent, SolutionChangedEvent>(OnAbsorbentSolutionChange);
- SubscribeLocalEvent<TransferCancelledEvent>(OnTransferCancelled);
- SubscribeLocalEvent<TransferCompleteEvent>(OnTransferComplete);
+ SubscribeLocalEvent<AbsorbentComponent, DoAfterEvent<AbsorbantData>>(OnDoAfter);
}
private void OnAbsorbentInit(EntityUid uid, AbsorbentComponent component, ComponentInit args)
if (!component.InteractingEntities.Add(target))
return;
- var doAfterArgs = new DoAfterEventArgs(user, delay, target: target)
+ var aborbantData = new AbsorbantData(targetSolution, msg, sfx, transferAmount);
+
+ var doAfterArgs = new DoAfterEventArgs(user, delay, target: target, used:used)
{
BreakOnUserMove = true,
BreakOnStun = true,
BreakOnDamage = true,
- MovementThreshold = 0.2f,
- BroadcastCancelledEvent = new TransferCancelledEvent(target, component),
- BroadcastFinishedEvent = new TransferCompleteEvent(used, target, component, targetSolution, msg, sfx, transferAmount)
+ MovementThreshold = 0.2f
};
- _doAfterSystem.DoAfter(doAfterArgs);
+ _doAfterSystem.DoAfter(doAfterArgs, aborbantData);
}
- private void OnTransferComplete(TransferCompleteEvent ev)
+ private void OnDoAfter(EntityUid uid, AbsorbentComponent component, DoAfterEvent<AbsorbantData> args)
{
- _audio.PlayPvs(ev.Sound, ev.Tool);
- _popups.PopupEntity(ev.Message, ev.Tool);
- _solutionSystem.TryTransferSolution(ev.Target, ev.Tool, ev.TargetSolution, AbsorbentComponent.SolutionName, ev.TransferAmount);
- ev.Component.InteractingEntities.Remove(ev.Target);
- }
+ if (args.Handled || args.Cancelled || args.Args.Target == null)
+ return;
- private void OnTransferCancelled(TransferCancelledEvent ev)
- {
- ev.Component.InteractingEntities.Remove(ev.Target);
- }
-}
+ _audio.PlayPvs(args.AdditionalData.Sound, uid);
+ _popups.PopupEntity(Loc.GetString(args.AdditionalData.Message, ("target", args.Args.Target.Value), ("used", uid)), uid);
+ _solutionSystem.TryTransferSolution(args.Args.Target.Value, uid, args.AdditionalData.TargetSolution,
+ AbsorbentComponent.SolutionName, args.AdditionalData.TransferAmount);
+ component.InteractingEntities.Remove(args.Args.Target.Value);
-public sealed class TransferCompleteEvent : EntityEventArgs
-{
- public readonly EntityUid Tool;
- public readonly EntityUid Target;
- public readonly AbsorbentComponent Component;
- public readonly string TargetSolution;
- public readonly string Message;
- public readonly SoundSpecifier Sound;
- public readonly FixedPoint2 TransferAmount;
-
- public TransferCompleteEvent(EntityUid tool, EntityUid target, AbsorbentComponent component, string targetSolution, string message, SoundSpecifier sound, FixedPoint2 transferAmount)
- {
- Tool = tool;
- Target = target;
- Component = component;
- TargetSolution = targetSolution;
- Message = Loc.GetString(message, ("target", target), ("used", tool));
- Sound = sound;
- TransferAmount = transferAmount;
+ args.Handled = true;
}
-}
-
-public sealed class TransferCancelledEvent : EntityEventArgs
-{
- public readonly EntityUid Target;
- public readonly AbsorbentComponent Component;
- public TransferCancelledEvent(EntityUid target, AbsorbentComponent component)
+ private record struct AbsorbantData(string TargetSolution, string Message, SoundSpecifier Sound, FixedPoint2 TransferAmount)
{
- Target = target;
- Component = component;
+ public readonly string TargetSolution = TargetSolution;
+ public readonly string Message = Message;
+ public readonly SoundSpecifier Sound = Sound;
+ public readonly FixedPoint2 TransferAmount = TransferAmount;
}
}
using Robust.Shared.Prototypes;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
-using System.Threading;
+using Content.Shared.DoAfter;
namespace Content.Server.Fluids.EntitySystems;
SubscribeLocalEvent<SpillableComponent, GetVerbsEvent<Verb>>(AddSpillVerb);
SubscribeLocalEvent<SpillableComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<SpillableComponent, SolutionSpikeOverflowEvent>(OnSpikeOverflow);
- SubscribeLocalEvent<SpillableComponent, SpillFinishedEvent>(OnSpillFinished);
- SubscribeLocalEvent<SpillableComponent, SpillCancelledEvent>(OnSpillCancelled);
+ SubscribeLocalEvent<SpillableComponent, DoAfterEvent>(OnDoAfter);
}
private void OnSpikeOverflow(EntityUid uid, SpillableComponent component, SolutionSpikeOverflowEvent args)
{
verb.Act = () =>
{
- if (component.CancelToken == null)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, component.SpillDelay.Value, target:uid)
{
- component.CancelToken = new CancellationTokenSource();
- _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, component.SpillDelay.Value, component.CancelToken.Token, component.Owner)
- {
- BreakOnTargetMove = true,
- BreakOnUserMove = true,
- BreakOnDamage = true,
- BreakOnStun = true,
- NeedHand = true,
- TargetFinishedEvent = new SpillFinishedEvent(args.User, component.Owner, solution),
- TargetCancelledEvent = new SpillCancelledEvent(component.Owner)
- });
- }
+ BreakOnTargetMove = true,
+ BreakOnUserMove = true,
+ BreakOnDamage = true,
+ BreakOnStun = true,
+ NeedHand = true
+ });
};
}
verb.Impact = LogImpact.Medium; // dangerous reagent reaction are logged separately.
return puddleComponent;
}
- private void OnSpillFinished(EntityUid uid, SpillableComponent component, SpillFinishedEvent ev)
+ private void OnDoAfter(EntityUid uid, SpillableComponent component, DoAfterEvent args)
{
- component.CancelToken = null;
+ if (args.Handled || args.Cancelled || args.Args.Target == null)
+ return;
//solution gone by other means before doafter completes
- if (ev.Solution == null || ev.Solution.Volume == 0)
+ if (!_solutionContainerSystem.TryGetDrainableSolution(uid, out var solution) || solution.Volume == 0)
return;
- var puddleSolution = _solutionContainerSystem.SplitSolution(uid,
- ev.Solution, ev.Solution.Volume);
-
- SpillAt(puddleSolution, Transform(component.Owner).Coordinates, "PuddleSmear");
- }
-
- private void OnSpillCancelled(EntityUid uid, SpillableComponent component, SpillCancelledEvent ev)
- {
- component.CancelToken = null;
- }
-
- internal sealed class SpillFinishedEvent : EntityEventArgs
- {
- public SpillFinishedEvent(EntityUid user, EntityUid spillable, Solution solution)
- {
- User = user;
- Spillable = spillable;
- Solution = solution;
- }
-
- public EntityUid User { get; }
- public EntityUid Spillable { get; }
- public Solution Solution { get; }
- }
+ var puddleSolution = _solutionContainerSystem.SplitSolution(uid, solution, solution.Volume);
- private sealed class SpillCancelledEvent : EntityEventArgs
- {
- public EntityUid Spillable;
+ SpillAt(puddleSolution, Transform(uid).Coordinates, "PuddleSmear");
- public SpillCancelledEvent(EntityUid spillable)
- {
- Spillable = spillable;
- }
+ args.Handled = true;
}
}
-using System.Threading;
-
namespace Content.Server.Forensics
{
/// <summary>
[RegisterComponent]
public sealed class ForensicPadComponent : Component
{
- public CancellationTokenSource? CancelToken;
-
[DataField("scanDelay")]
public float ScanDelay = 3.0f;
-using System.Threading;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Inventory;
using Content.Server.DoAfter;
using Content.Server.Popups;
+using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
-using Robust.Shared.Player;
+using Robust.Shared.Serialization;
namespace Content.Server.Forensics
{
{
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
-
[Dependency] private readonly PopupSystem _popupSystem = default!;
public override void Initialize()
base.Initialize();
SubscribeLocalEvent<ForensicPadComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<ForensicPadComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<TargetPadSuccessfulEvent>(OnTargetPadSuccessful);
- SubscribeLocalEvent<PadCancelledEvent>(OnPadCancelled);
+ SubscribeLocalEvent<ForensicPadComponent, DoAfterEvent<ForensicPadData>>(OnDoAfter);
}
private void OnExamined(EntityUid uid, ForensicPadComponent component, ExaminedEvent args)
private void OnAfterInteract(EntityUid uid, ForensicPadComponent component, AfterInteractEvent args)
{
- if (component.CancelToken != null || !args.CanReach || args.Target == null)
+ if (!args.CanReach || args.Target == null)
return;
if (HasComp<ForensicScannerComponent>(args.Target))
_popupSystem.PopupEntity(Loc.GetString("forensic-pad-start-scan-user", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.Target.Value, args.User);
_popupSystem.PopupEntity(Loc.GetString("forensic-pad-start-scan-target", ("user", Identity.Entity(args.User, EntityManager))), args.Target.Value, args.Target.Value);
}
- StartScan(args.User, args.Target.Value, component, fingerprint.Fingerprint);
+ StartScan(uid, args.User, args.Target.Value, component, fingerprint.Fingerprint);
return;
}
if (TryComp<FiberComponent>(args.Target, out var fiber))
- StartScan(args.User, args.Target.Value, component, string.IsNullOrEmpty(fiber.FiberColor) ? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial)) : Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial)));
+ StartScan(uid, args.User, args.Target.Value, component, string.IsNullOrEmpty(fiber.FiberColor) ? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial)) : Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial)));
}
- private void StartScan(EntityUid user, EntityUid target, ForensicPadComponent pad, string sample)
+ private void StartScan(EntityUid used, EntityUid user, EntityUid target, ForensicPadComponent pad, string sample)
{
- pad.CancelToken = new CancellationTokenSource();
- _doAfterSystem.DoAfter(new DoAfterEventArgs(user, pad.ScanDelay, pad.CancelToken.Token, target: target)
+ var padData = new ForensicPadData(sample);
+
+ var doAfterEventArgs = new DoAfterEventArgs(user, pad.ScanDelay, target: target, used: used)
{
- BroadcastFinishedEvent = new TargetPadSuccessfulEvent(user, target, pad.Owner, sample),
- BroadcastCancelledEvent = new PadCancelledEvent(pad.Owner),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
NeedHand = true
- });
- }
-
- /// <summary>
- /// When the forensic pad is successfully used, take their fingerprint sample and flag the pad as used.
- /// </summary>
- private void OnTargetPadSuccessful(TargetPadSuccessfulEvent ev)
- {
- if (!EntityManager.TryGetComponent(ev.Pad, out ForensicPadComponent? component))
- return;
+ };
- if (HasComp<FingerprintComponent>(ev.Target))
- MetaData(component.Owner).EntityName = Loc.GetString("forensic-pad-fingerprint-name", ("entity", ev.Target));
- else
- MetaData(component.Owner).EntityName = Loc.GetString("forensic-pad-gloves-name", ("entity", ev.Target));
-
- component.CancelToken = null;
- component.Sample = ev.Sample;
- component.Used = true;
- }
- private void OnPadCancelled(PadCancelledEvent ev)
- {
- if (!EntityManager.TryGetComponent(ev.Pad, out ForensicPadComponent? component))
- return;
- component.CancelToken = null;
+ _doAfterSystem.DoAfter(doAfterEventArgs, padData);
}
- private sealed class PadCancelledEvent : EntityEventArgs
+ private void OnDoAfter(EntityUid uid, ForensicPadComponent component, DoAfterEvent<ForensicPadData> args)
{
- public EntityUid Pad;
+ if (args.Handled
+ || args.Cancelled
+ || !EntityManager.TryGetComponent(args.Args.Used, out ForensicPadComponent? padComponent))
+ {
+ return;
+ }
- public PadCancelledEvent(EntityUid pad)
+ if (args.Args.Target != null)
{
- Pad = pad;
+ if (HasComp<FingerprintComponent>(args.Args.Target))
+ MetaData(uid).EntityName = Loc.GetString("forensic-pad-fingerprint-name", ("entity", args.Args.Target));
+ else
+ MetaData(uid).EntityName = Loc.GetString("forensic-pad-gloves-name", ("entity", args.Args.Target));
}
+
+ padComponent.Sample = args.AdditionalData.Sample;
+ padComponent.Used = true;
+
+ args.Handled = true;
}
- private sealed class TargetPadSuccessfulEvent : EntityEventArgs
+ private sealed class ForensicPadData
{
- public EntityUid User;
- public EntityUid Target;
- public EntityUid Pad;
- public string Sample = string.Empty;
+ public string Sample;
- public TargetPadSuccessfulEvent(EntityUid user, EntityUid target, EntityUid pad, string sample)
+ public ForensicPadData(string sample)
{
- User = user;
- Target = target;
- Pad = pad;
Sample = sample;
}
}
using System.Linq;
using System.Text; // todo: remove this stinky LINQy
-using System.Threading;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
-using Robust.Shared.Player;
using Robust.Shared.Timing;
using Content.Server.DoAfter;
using Content.Server.Paper;
using Content.Server.Popups;
using Content.Server.UserInterface;
+using Content.Shared.DoAfter;
using Content.Shared.Forensics;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
SubscribeLocalEvent<ForensicScannerComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
SubscribeLocalEvent<ForensicScannerComponent, ForensicScannerPrintMessage>(OnPrint);
SubscribeLocalEvent<ForensicScannerComponent, ForensicScannerClearMessage>(OnClear);
- SubscribeLocalEvent<TargetScanSuccessfulEvent>(OnTargetScanSuccessful);
- SubscribeLocalEvent<ScanCancelledEvent>(OnScanCancelled);
+ SubscribeLocalEvent<ForensicScannerComponent, DoAfterEvent>(OnDoAfter);
}
private void UpdateUserInterface(EntityUid uid, ForensicScannerComponent component)
component.PrintReadyAt);
if (!_uiSystem.TrySetUiState(uid, ForensicScannerUiKey.Key, state))
- {
_sawmill.Warning($"{ToPrettyString(uid)} was unable to set UI state.");
- }
}
- private void OnScanCancelled(ScanCancelledEvent ev)
+ private void OnDoAfter(EntityUid uid, ForensicScannerComponent component, DoAfterEvent args)
{
- if (!EntityManager.TryGetComponent(ev.Scanner, out ForensicScannerComponent? scanner))
+ if (args.Handled || args.Cancelled)
return;
- scanner.CancelToken = null;
- }
-
- private void OnTargetScanSuccessful(TargetScanSuccessfulEvent ev)
- {
- if (!EntityManager.TryGetComponent(ev.Scanner, out ForensicScannerComponent? scanner))
+ if (!EntityManager.TryGetComponent(uid, out ForensicScannerComponent? scanner))
return;
- scanner.CancelToken = null;
-
- if (!TryComp<ForensicsComponent>(ev.Target, out var forensics))
+ if (args.Args.Target != null)
{
- scanner.Fingerprints = new();
- scanner.Fibers = new();
- }
- else
- {
- scanner.Fingerprints = forensics.Fingerprints.ToList();
- scanner.Fibers = forensics.Fibers.ToList();
- }
+ if (!TryComp<ForensicsComponent>(args.Args.Target, out var forensics))
+ {
+ scanner.Fingerprints = new();
+ scanner.Fibers = new();
+ }
+
+ else
+ {
+ scanner.Fingerprints = forensics.Fingerprints.ToList();
+ scanner.Fibers = forensics.Fibers.ToList();
+ }
- scanner.LastScannedName = MetaData(ev.Target).EntityName;
+ scanner.LastScannedName = MetaData(args.Args.Target.Value).EntityName;
+ }
- OpenUserInterface(ev.User, scanner);
+ OpenUserInterface(args.Args.User, scanner);
}
/// <remarks>
/// </remarks>
private void StartScan(EntityUid uid, ForensicScannerComponent component, EntityUid user, EntityUid target)
{
- component.CancelToken = new CancellationTokenSource();
- _doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.ScanDelay, component.CancelToken.Token, target: target)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.ScanDelay, target: target, used: uid)
{
- BroadcastFinishedEvent = new TargetScanSuccessfulEvent(user, target, component.Owner),
- BroadcastCancelledEvent = new ScanCancelledEvent(component.Owner),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
UpdateUserInterface(uid, component);
}
-
- private sealed class ScanCancelledEvent : EntityEventArgs
- {
- public EntityUid Scanner;
-
- public ScanCancelledEvent(EntityUid scanner)
- {
- Scanner = scanner;
- }
- }
-
- private sealed class TargetScanSuccessfulEvent : EntityEventArgs
- {
- public EntityUid User;
- public EntityUid Target;
- public EntityUid Scanner;
- public TargetScanSuccessfulEvent(EntityUid user, EntityUid target, EntityUid scanner)
- {
- User = user;
- Target = target;
- Scanner = scanner;
- }
- }
}
}
public int MaxGatheringEntities = 1;
[ViewVariables]
- public readonly Dictionary<EntityUid, CancellationTokenSource> GatheringEntities = new();
+ public readonly List<EntityUid> GatheringEntities = new();
}
}
using Content.Server.DoAfter;
using Content.Server.Gatherable.Components;
using Content.Shared.Damage;
+using Content.Shared.DoAfter;
using Content.Shared.Destructible;
using Content.Shared.EntityList;
using Content.Shared.Interaction;
base.Initialize();
SubscribeLocalEvent<GatherableComponent, InteractUsingEvent>(OnInteractUsing);
- SubscribeLocalEvent<GatheringDoafterCancel>(OnDoafterCancel);
- SubscribeLocalEvent<GatherableComponent, GatheringDoafterSuccess>(OnDoafterSuccess);
+ SubscribeLocalEvent<GatherableComponent, DoAfterEvent>(OnDoAfter);
}
private void OnInteractUsing(EntityUid uid, GatherableComponent component, InteractUsingEvent args)
{
- if (!TryComp<GatheringToolComponent>(args.Used, out var tool) ||
- component.ToolWhitelist?.IsValid(args.Used) == false ||
- tool.GatheringEntities.TryGetValue(uid, out var cancelToken))
+ if (!TryComp<GatheringToolComponent>(args.Used, out var tool) || component.ToolWhitelist?.IsValid(args.Used) == false)
return;
// Can't gather too many entities at once.
var damageTime = (damageRequired / tool.Damage.Total).Float();
damageTime = Math.Max(1f, damageTime);
- cancelToken = new CancellationTokenSource();
- tool.GatheringEntities[uid] = cancelToken;
-
- var doAfter = new DoAfterEventArgs(args.User, damageTime, cancelToken.Token, uid)
+ var doAfter = new DoAfterEventArgs(args.User, damageTime, target: uid, used: args.Used)
{
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
MovementThreshold = 0.25f,
- BroadcastCancelledEvent = new GatheringDoafterCancel { Tool = args.Used, Resource = uid },
- TargetFinishedEvent = new GatheringDoafterSuccess { Tool = args.Used, Resource = uid, Player = args.User }
};
_doAfterSystem.DoAfter(doAfter);
}
- private void OnDoafterSuccess(EntityUid uid, GatherableComponent component, GatheringDoafterSuccess ev)
+ private void OnDoAfter(EntityUid uid, GatherableComponent component, DoAfterEvent args)
{
- if (!TryComp(ev.Tool, out GatheringToolComponent? tool))
+ if(!TryComp<GatheringToolComponent>(args.Args.Used, out var tool) || args.Args.Target == null)
+ return;
+
+ if (args.Handled || args.Cancelled)
+ {
+ tool.GatheringEntities.Remove(args.Args.Target.Value);
return;
+ }
// Complete the gathering process
- _destructible.DestroyEntity(uid);
- _audio.PlayPvs(tool.GatheringSound, ev.Resource);
- tool.GatheringEntities.Remove(ev.Resource);
+ _destructible.DestroyEntity(args.Args.Target.Value);
+ _audio.PlayPvs(tool.GatheringSound, args.Args.Target.Value);
+ tool.GatheringEntities.Remove(args.Args.Target.Value);
// Spawn the loot!
if (component.MappedLoot == null)
return;
- var playerPos = Transform(ev.Player).MapPosition;
+ var playerPos = Transform(args.Args.User).MapPosition;
foreach (var (tag, table) in component.MappedLoot)
{
var spawnPos = playerPos.Offset(_random.NextVector2(0.3f));
Spawn(spawnLoot[0], spawnPos);
}
- }
-
- private void OnDoafterCancel(GatheringDoafterCancel ev)
- {
- if (!TryComp<GatheringToolComponent>(ev.Tool, out var tool))
- return;
-
- tool.GatheringEntities.Remove(ev.Resource);
- }
-
- private sealed class GatheringDoafterCancel : EntityEventArgs
- {
- public EntityUid Tool;
- public EntityUid Resource;
- }
-
- private sealed class GatheringDoafterSuccess : EntityEventArgs
- {
- public EntityUid Tool;
- public EntityUid Resource;
- public EntityUid Player;
+ args.Handled = true;
}
}
using Content.Shared.Actions;
using Content.Shared.Audio;
using Content.Shared.Damage;
+using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Mobs;
using Content.Shared.Popups;
using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Utility;
[Dependency] private readonly DamageableSystem _damageSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
SubscribeLocalEvent<GuardianCreatorComponent, UseInHandEvent>(OnCreatorUse);
SubscribeLocalEvent<GuardianCreatorComponent, AfterInteractEvent>(OnCreatorInteract);
SubscribeLocalEvent<GuardianCreatorComponent, ExaminedEvent>(OnCreatorExamine);
- SubscribeLocalEvent<GuardianCreatorInjectedEvent>(OnCreatorInject);
- SubscribeLocalEvent<GuardianCreatorInjectCancelledEvent>(OnCreatorCancelled);
+ SubscribeLocalEvent<GuardianCreatorComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<GuardianComponent, MoveEvent>(OnGuardianMove);
SubscribeLocalEvent<GuardianComponent, DamageChangedEvent>(OnGuardianDamaged);
return;
if (component.HostedGuardian != null)
- ToggleGuardian(component);
+ ToggleGuardian(uid, component);
args.Handled = true;
}
{
var host = component.Host;
- if (!TryComp<GuardianHostComponent>(host, out var hostComponent)) return;
-
- if (LifeStage(host) >= EntityLifeStage.MapInitialized)
+ if (!TryComp<GuardianHostComponent>(host, out var hostComponent) || LifeStage(host) >= EntityLifeStage.MapInitialized)
return;
- RetractGuardian(hostComponent, component);
+ RetractGuardian(host, hostComponent, uid, component);
}
private void OnGuardianPlayer(EntityUid uid, GuardianComponent component, PlayerAttachedEvent args)
{
var host = component.Host;
- if (!HasComp<GuardianHostComponent>(host)) return;
+ if (!HasComp<GuardianHostComponent>(host))
+ return;
_popupSystem.PopupEntity(Loc.GetString("guardian-available"), host, host);
}
private void OnHostShutdown(EntityUid uid, GuardianHostComponent component, ComponentShutdown args)
{
- if (component.HostedGuardian == null) return;
+ if (component.HostedGuardian == null)
+ return;
+
EntityManager.QueueDeleteEntity(component.HostedGuardian.Value);
_actionSystem.RemoveAction(uid, component.Action);
}
args.Cancel();
}
- public void ToggleGuardian(GuardianHostComponent hostComponent)
+ public void ToggleGuardian(EntityUid user, GuardianHostComponent hostComponent)
{
- if (hostComponent.HostedGuardian == null ||
- !TryComp(hostComponent.HostedGuardian, out GuardianComponent? guardianComponent)) return;
+ if (hostComponent.HostedGuardian == null || !TryComp<GuardianComponent>(hostComponent.HostedGuardian, out var guardianComponent))
+ return;
if (guardianComponent.GuardianLoose)
- {
- RetractGuardian(hostComponent, guardianComponent);
- }
+ RetractGuardian(user, hostComponent, hostComponent.HostedGuardian.Value, guardianComponent);
else
- {
- ReleaseGuardian(hostComponent, guardianComponent);
- }
+ ReleaseGuardian(user, hostComponent, hostComponent.HostedGuardian.Value, guardianComponent);
}
/// <summary>
/// </summary>
private void OnCreatorUse(EntityUid uid, GuardianCreatorComponent component, UseInHandEvent args)
{
- if (args.Handled) return;
- args.Handled = true;
- UseCreator(args.User, args.User, component);
+ if (args.Handled)
+ return;
+
+ //args.Handled = true;
+ UseCreator(args.User, args.User, uid, component);
}
private void OnCreatorInteract(EntityUid uid, GuardianCreatorComponent component, AfterInteractEvent args)
{
- if (args.Handled || args.Target == null || !args.CanReach) return;
- args.Handled = true;
- UseCreator(args.User, args.Target.Value, component);
- }
+ if (args.Handled || args.Target == null || !args.CanReach)
+ return;
- private void OnCreatorCancelled(GuardianCreatorInjectCancelledEvent ev)
- {
- ev.Component.Injecting = false;
+ //args.Handled = true;
+ UseCreator(args.User, args.Target.Value, uid, component);
}
-
- private void UseCreator(EntityUid user, EntityUid target, GuardianCreatorComponent component)
+ private void UseCreator(EntityUid user, EntityUid target, EntityUid injector, GuardianCreatorComponent component)
{
if (component.Used)
{
return;
}
- if (component.Injecting) return;
+ if (component.Injecting)
+ return;
component.Injecting = true;
- _doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.InjectionDelay, target: target)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.InjectionDelay, target: target, used: injector)
{
- BroadcastFinishedEvent = new GuardianCreatorInjectedEvent(user, target, component),
- BroadcastCancelledEvent = new GuardianCreatorInjectCancelledEvent(target, component),
BreakOnTargetMove = true,
- BreakOnUserMove = true,
+ BreakOnUserMove = true
});
}
- private void OnCreatorInject(GuardianCreatorInjectedEvent ev)
+ private void OnDoAfter(EntityUid uid, GuardianCreatorComponent component, DoAfterEvent args)
{
- var comp = ev.Component;
+ if (args.Handled || args.Args.Target == null)
+ return;
- if (comp.Deleted ||
- comp.Used ||
- !_handsSystem.IsHolding(ev.User, comp.Owner, out _) ||
- HasComp<GuardianHostComponent>(ev.Target))
+ if (args.Cancelled || component.Deleted || component.Used || !_handsSystem.IsHolding(args.Args.User, uid, out _) || HasComp<GuardianHostComponent>(args.Args.Target))
{
- comp.Injecting = false;
+ component.Injecting = false;
return;
}
- var hostXform = EntityManager.GetComponent<TransformComponent>(ev.Target);
- var host = EntityManager.EnsureComponent<GuardianHostComponent>(ev.Target);
+ var hostXform = Transform(args.Args.Target.Value);
+ var host = EnsureComp<GuardianHostComponent>(args.Args.Target.Value);
// Use map position so it's not inadvertantly parented to the host + if it's in a container it spawns outside I guess.
- var guardian = EntityManager.SpawnEntity(comp.GuardianProto, hostXform.MapPosition);
+ var guardian = Spawn(component.GuardianProto, hostXform.MapPosition);
host.GuardianContainer.Insert(guardian);
host.HostedGuardian = guardian;
- if (TryComp(guardian, out GuardianComponent? guardianComponent))
+ if (TryComp<GuardianComponent>(guardian, out var guardianComp))
{
- guardianComponent.Host = ev.Target;
-
- SoundSystem.Play("/Audio/Effects/guardian_inject.ogg", Filter.Pvs(ev.Target), ev.Target);
-
- _popupSystem.PopupEntity(Loc.GetString("guardian-created"), ev.Target, ev.Target);
+ guardianComp.Host = args.Args.Target.Value;
+ _audio.Play("/Audio/Effects/guardian_inject.ogg", Filter.Pvs(args.Args.Target.Value), args.Args.Target.Value, true);
+ _popupSystem.PopupEntity(Loc.GetString("guardian-created"), args.Args.Target.Value, args.Args.Target.Value);
// Exhaust the activator
- comp.Used = true;
+ component.Used = true;
}
else
{
Logger.ErrorS("guardian", $"Tried to spawn a guardian that doesn't have {nameof(GuardianComponent)}");
EntityManager.QueueDeleteEntity(guardian);
}
+
+ args.Handled = true;
}
/// <summary>
/// </summary>
private void OnHostStateChange(EntityUid uid, GuardianHostComponent component, MobStateChangedEvent args)
{
- if (component.HostedGuardian == null) return;
+ if (component.HostedGuardian == null)
+ return;
if (args.NewMobState == MobState.Critical)
{
_popupSystem.PopupEntity(Loc.GetString("guardian-critical-warn"), component.HostedGuardian.Value, component.HostedGuardian.Value);
- SoundSystem.Play("/Audio/Effects/guardian_warn.ogg", Filter.Pvs(component.HostedGuardian.Value), component.HostedGuardian.Value);
+ _audio.Play("/Audio/Effects/guardian_warn.ogg", Filter.Pvs(component.HostedGuardian.Value), component.HostedGuardian.Value, true);
}
else if (args.NewMobState == MobState.Dead)
{
- SoundSystem.Play("/Audio/Voice/Human/malescream_guardian.ogg", Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.20f));
+ //TODO: Replace WithVariation with datafield
+ _audio.Play("/Audio/Voice/Human/malescream_guardian.ogg", Filter.Pvs(uid), uid, true, AudioHelpers.WithVariation(0.20f));
EntityManager.RemoveComponent<GuardianHostComponent>(uid);
}
}
/// </summary>
private void OnGuardianDamaged(EntityUid uid, GuardianComponent component, DamageChangedEvent args)
{
- if (args.DamageDelta == null) return;
+ if (args.DamageDelta == null)
+ return;
_damageSystem.TryChangeDamage(component.Host, args.DamageDelta * component.DamageShare, origin: args.Origin);
_popupSystem.PopupEntity(Loc.GetString("guardian-entity-taking-damage"), component.Host, component.Host);
private void OnCreatorExamine(EntityUid uid, GuardianCreatorComponent component, ExaminedEvent args)
{
if (component.Used)
- {
args.PushMarkup(Loc.GetString("guardian-activator-empty-examine"));
- }
}
/// <summary>
{
if (component.HostedGuardian == null ||
!TryComp(component.HostedGuardian, out GuardianComponent? guardianComponent) ||
- !guardianComponent.GuardianLoose) return;
+ !guardianComponent.GuardianLoose)
+ {
+ return;
+ }
CheckGuardianMove(uid, component.HostedGuardian.Value, component);
}
/// </summary>
private void OnGuardianMove(EntityUid uid, GuardianComponent component, ref MoveEvent args)
{
- if (!component.GuardianLoose) return;
+ if (!component.GuardianLoose)
+ return;
CheckGuardianMove(component.Host, uid, guardianComponent: component);
}
return;
}
- if (!guardianComponent.GuardianLoose) return;
+ if (!guardianComponent.GuardianLoose)
+ return;
if (!guardianXform.Coordinates.InRange(EntityManager, hostXform.Coordinates, guardianComponent.DistanceAllowed))
- {
- RetractGuardian(hostComponent, guardianComponent);
- }
+ RetractGuardian(hostUid, hostComponent, guardianUid, guardianComponent);
}
- private bool CanRelease(GuardianHostComponent host, GuardianComponent guardian)
+ private bool CanRelease(EntityUid guardian)
{
- return HasComp<ActorComponent>(guardian.Owner);
+ return HasComp<ActorComponent>(guardian);
}
- private void ReleaseGuardian(GuardianHostComponent hostComponent, GuardianComponent guardianComponent)
+ private void ReleaseGuardian(EntityUid host, GuardianHostComponent hostComponent, EntityUid guardian, GuardianComponent guardianComponent)
{
if (guardianComponent.GuardianLoose)
{
- DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
+ DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardian));
return;
}
- if (!CanRelease(hostComponent, guardianComponent))
+ if (!CanRelease(guardian))
{
- _popupSystem.PopupEntity(Loc.GetString("guardian-no-soul"), hostComponent.Owner, hostComponent.Owner);
+ _popupSystem.PopupEntity(Loc.GetString("guardian-no-soul"), host, host);
return;
}
- DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
- hostComponent.GuardianContainer.Remove(guardianComponent.Owner);
- DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
+ DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardian));
+ hostComponent.GuardianContainer.Remove(guardian);
+ DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardian));
guardianComponent.GuardianLoose = true;
}
- private void RetractGuardian(GuardianHostComponent hostComponent, GuardianComponent guardianComponent)
+ private void RetractGuardian(EntityUid host,GuardianHostComponent hostComponent, EntityUid guardian, GuardianComponent guardianComponent)
{
if (!guardianComponent.GuardianLoose)
{
- DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
+ DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardian));
return;
}
- hostComponent.GuardianContainer.Insert(guardianComponent.Owner);
- DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
- _popupSystem.PopupEntity(Loc.GetString("guardian-entity-recall"), hostComponent.Owner);
+ hostComponent.GuardianContainer.Insert(guardian);
+ DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardian));
+ _popupSystem.PopupEntity(Loc.GetString("guardian-entity-recall"), host);
guardianComponent.GuardianLoose = false;
}
-
- private sealed class GuardianCreatorInjectedEvent : EntityEventArgs
- {
- public EntityUid User { get; }
- public EntityUid Target { get; }
- public GuardianCreatorComponent Component { get; }
-
- public GuardianCreatorInjectedEvent(EntityUid user, EntityUid target, GuardianCreatorComponent component)
- {
- User = user;
- Target = target;
- Component = component;
- }
- }
-
- private sealed class GuardianCreatorInjectCancelledEvent : EntityEventArgs
- {
- public EntityUid Target { get; }
- public GuardianCreatorComponent Component { get; }
-
- public GuardianCreatorInjectCancelledEvent(EntityUid target, GuardianCreatorComponent component)
- {
- Target = target;
- Component = component;
- }
- }
}
}
using Content.Server.DoAfter;
using Content.Server.Guardian;
using Content.Server.Popups;
+using Content.Shared.DoAfter;
using Content.Shared.Hands;
using Content.Shared.IdentityManagement;
using Content.Shared.Implants;
using Content.Shared.Popups;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
-using Robust.Shared.Player;
namespace Content.Server.Implants;
SubscribeLocalEvent<ImplanterComponent, AfterInteractEvent>(OnImplanterAfterInteract);
SubscribeLocalEvent<ImplanterComponent, ComponentGetState>(OnImplanterGetState);
- SubscribeLocalEvent<ImplanterComponent, ImplanterImplantCompleteEvent>(OnImplantAttemptSuccess);
- SubscribeLocalEvent<ImplanterComponent, ImplanterDrawCompleteEvent>(OnDrawAttemptSuccess);
- SubscribeLocalEvent<ImplanterComponent, ImplanterCancelledEvent>(OnImplantAttemptFail);
+ SubscribeLocalEvent<ImplanterComponent, DoAfterEvent<ImplantEvent>>(OnImplant);
+ SubscribeLocalEvent<ImplanterComponent, DoAfterEvent<DrawEvent>>(OnDraw);
}
private void OnImplanterAfterInteract(EntityUid uid, ImplanterComponent component, AfterInteractEvent args)
if (args.Target == null || !args.CanReach || args.Handled)
return;
- if (component.CancelToken != null)
- {
- args.Handled = true;
- return;
- }
-
//Simplemobs and regular mobs should be injectable, but only regular mobs have mind.
//So just don't implant/draw anything that isn't living or is a guardian
//TODO: Rework a bit when surgery is in to work with implant cases
/// <param name="implanter">The implanter being used</param>
public void TryImplant(ImplanterComponent component, EntityUid user, EntityUid target, EntityUid implanter)
{
+ if (component.CancelToken != null)
+ return;
+
_popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, user);
var userName = Identity.Entity(user, EntityManager);
component.CancelToken?.Cancel();
component.CancelToken = new CancellationTokenSource();
- _doAfter.DoAfter(new DoAfterEventArgs(user, component.ImplantTime, component.CancelToken.Token, target, implanter)
+ var implantEvent = new ImplantEvent();
+
+ _doAfter.DoAfter(new DoAfterEventArgs(user, component.ImplantTime, component.CancelToken.Token,target:target, used:implanter)
{
BreakOnUserMove = true,
BreakOnTargetMove = true,
BreakOnDamage = true,
BreakOnStun = true,
- UsedFinishedEvent = new ImplanterImplantCompleteEvent(implanter, target),
- UserCancelledEvent = new ImplanterCancelledEvent()
- });
+ NeedHand = true
+ }, implantEvent);
}
/// <summary>
component.CancelToken?.Cancel();
component.CancelToken = new CancellationTokenSource();
- _doAfter.DoAfter(new DoAfterEventArgs(user, component.DrawTime, component.CancelToken.Token, target ,implanter)
+ var drawEvent = new DrawEvent();
+
+ _doAfter.DoAfter(new DoAfterEventArgs(user, component.DrawTime, target:target,used:implanter)
{
BreakOnUserMove = true,
BreakOnTargetMove = true,
BreakOnDamage = true,
BreakOnStun = true,
- UsedFinishedEvent = new ImplanterDrawCompleteEvent(implanter, user, target),
- UsedCancelledEvent = new ImplanterCancelledEvent()
- });
+ NeedHand = true
+ }, drawEvent);
}
private void OnImplanterGetState(EntityUid uid, ImplanterComponent component, ref ComponentGetState args)
args.State = new ImplanterComponentState(component.CurrentMode, component.ImplantOnly);
}
- private void OnImplantAttemptSuccess(EntityUid uid, ImplanterComponent component, ImplanterImplantCompleteEvent args)
+ private void OnImplant(EntityUid uid, ImplanterComponent component, DoAfterEvent<ImplantEvent> args)
{
- component.CancelToken?.Cancel();
- component.CancelToken = null;
- Implant(args.Implanter, args.Target, component);
- }
+ if (args.Cancelled)
+ {
+ component.CancelToken = null;
+ return;
+ }
- private void OnDrawAttemptSuccess(EntityUid uid, ImplanterComponent component, ImplanterDrawCompleteEvent args)
- {
- component.CancelToken?.Cancel();
- component.CancelToken = null;
- Draw(args.Implanter, args.User, args.Target, component);
- }
+ if (args.Handled || args.Args.Target == null || args.Args.Used == null)
+ return;
- private void OnImplantAttemptFail(EntityUid uid, ImplanterComponent component, ImplanterCancelledEvent args)
- {
- component.CancelToken?.Cancel();
+ Implant(args.Args.Used.Value, args.Args.Target.Value, component);
+
+ args.Handled = true;
component.CancelToken = null;
}
- private sealed class ImplanterImplantCompleteEvent : EntityEventArgs
+ private void OnDraw(EntityUid uid, ImplanterComponent component, DoAfterEvent<DrawEvent> args)
{
- public EntityUid Implanter;
- public EntityUid Target;
-
- public ImplanterImplantCompleteEvent(EntityUid implanter, EntityUid target)
+ if (args.Cancelled)
{
- Implanter = implanter;
- Target = target;
+ component.CancelToken = null;
+ return;
}
+
+ if (args.Handled || args.Args.Used == null || args.Args.Target == null)
+ return;
+
+ Draw(args.Args.Used.Value, args.Args.User, args.Args.Target.Value, component);
+
+ args.Handled = true;
+ component.CancelToken = null;
}
- private sealed class ImplanterCancelledEvent : EntityEventArgs
+ private sealed class ImplantEvent : EntityEventArgs
{
}
- private sealed class ImplanterDrawCompleteEvent : EntityEventArgs
+ private sealed class DrawEvent : EntityEventArgs
{
- public EntityUid Implanter;
- public EntityUid User;
- public EntityUid Target;
- public ImplanterDrawCompleteEvent(EntityUid implanter, EntityUid user, EntityUid target)
- {
- Implanter = implanter;
- User = user;
- Target = target;
- }
}
-
}
using Content.Server.Administration.Logs;
using Content.Server.DoAfter;
using Content.Server.Kitchen.Components;
-using Content.Server.Nutrition.Components;
using Content.Server.Popups;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Nutrition.Components;
-using Robust.Shared.Audio;
using Robust.Shared.Player;
using Content.Shared.Storage;
using Robust.Shared.Random;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;
-using Robust.Server.GameObjects;
namespace Content.Server.Kitchen.EntitySystems
{
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
SubscribeLocalEvent<KitchenSpikeComponent, DragDropTargetEvent>(OnDragDrop);
//DoAfter
- SubscribeLocalEvent<KitchenSpikeComponent, SpikingFinishedEvent>(OnSpikingFinished);
- SubscribeLocalEvent<KitchenSpikeComponent, SpikingFailEvent>(OnSpikingFail);
+ SubscribeLocalEvent<KitchenSpikeComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<KitchenSpikeComponent, SuicideEvent>(OnSuicide);
private void OnSuicide(EntityUid uid, KitchenSpikeComponent component, SuicideEvent args)
{
- if (args.Handled) return;
+ if (args.Handled)
+ return;
args.SetHandled(SuicideKind.Piercing);
var victim = args.Victim;
var othersMessage = Loc.GetString("comp-kitchen-spike-suicide-other", ("victim", victim));
- victim.PopupMessageOtherClients(othersMessage);
+ _popupSystem.PopupEntity(othersMessage, victim);
var selfMessage = Loc.GetString("comp-kitchen-spike-suicide-self");
- victim.PopupMessage(selfMessage);
+ _popupSystem.PopupEntity(selfMessage, victim, victim);
}
- private void OnSpikingFail(EntityUid uid, KitchenSpikeComponent component, SpikingFailEvent args)
+ private void OnDoAfter(EntityUid uid, KitchenSpikeComponent component, DoAfterEvent args)
{
- component.InUse = false;
+ if (args.Args.Target == null)
+ return;
- if (EntityManager.TryGetComponent<ButcherableComponent>(args.VictimUid, out var butcherable))
+ if (TryComp<ButcherableComponent>(args.Args.Target.Value, out var butcherable))
butcherable.BeingButchered = false;
- }
- private void OnSpikingFinished(EntityUid uid, KitchenSpikeComponent component, SpikingFinishedEvent args)
- {
- component.InUse = false;
+ if (args.Handled || args.Cancelled)
+ return;
- if (EntityManager.TryGetComponent<ButcherableComponent>(args.VictimUid, out var butcherable))
- butcherable.BeingButchered = false;
+ if (Spikeable(uid, args.Args.User, args.Args.Target.Value, component, butcherable))
+ Spike(uid, args.Args.User, args.Args.Target.Value, component);
- if (Spikeable(uid, args.UserUid, args.VictimUid, component, butcherable))
- {
- Spike(uid, args.UserUid, args.VictimUid, component);
- }
+ args.Handled = true;
}
private void OnDragDrop(EntityUid uid, KitchenSpikeComponent component, ref DragDropTargetEvent args)
if (Spikeable(uid, args.User, args.Dragged, component))
TrySpike(uid, args.User, args.Dragged, component);
-
}
+
private void OnInteractHand(EntityUid uid, KitchenSpikeComponent component, InteractHandEvent args)
{
if (args.Handled)
// TODO: Need to be able to leave them on the spike to do DoT, see ss13.
EntityManager.QueueDeleteEntity(victimUid);
- SoundSystem.Play(component.SpikeSound.GetSound(), Filter.Pvs(uid), uid);
+ _audio.Play(component.SpikeSound, Filter.Pvs(uid), uid, true);
}
private bool TryGetPiece(EntityUid uid, EntityUid user, EntityUid used,
Loc.GetString("comp-kitchen-spike-meat-name", ("name", Name(ent)), ("victim", component.Victim));
if (component.PrototypesToSpawn.Count != 0)
- {
_popupSystem.PopupEntity(component.MeatSource1p, uid, user, PopupType.MediumCaution);
- }
else
{
UpdateAppearance(uid, null, component);
butcherable.BeingButchered = true;
component.InUse = true;
- var doAfterArgs = new DoAfterEventArgs(userUid, component.SpikeDelay + butcherable.ButcherDelay, default, uid)
+ var doAfterArgs = new DoAfterEventArgs(userUid, component.SpikeDelay + butcherable.ButcherDelay, target:victimUid, used:uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
- NeedHand = true,
- TargetFinishedEvent = new SpikingFinishedEvent(userUid, victimUid),
- TargetCancelledEvent = new SpikingFailEvent(victimUid)
+ NeedHand = true
};
_doAfter.DoAfter(doAfterArgs);
return true;
}
-
- private sealed class SpikingFinishedEvent : EntityEventArgs
- {
- public EntityUid VictimUid;
- public EntityUid UserUid;
-
- public SpikingFinishedEvent(EntityUid userUid, EntityUid victimUid)
- {
- UserUid = userUid;
- VictimUid = victimUid;
- }
- }
-
- private sealed class SpikingFailEvent : EntityEventArgs
- {
- public EntityUid VictimUid;
-
- public SpikingFailEvent(EntityUid victimUid)
- {
- VictimUid = victimUid;
- }
- }
}
}
-using Content.Server.Body.Systems;
+using Content.Server.Body.Systems;
using Content.Server.DoAfter;
using Content.Server.Kitchen.Components;
using Content.Shared.Body.Components;
using Content.Shared.Storage;
using Content.Shared.Verbs;
using Content.Shared.Destructible;
+using Content.Shared.DoAfter;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Robust.Server.Containers;
-using Robust.Shared.Player;
using Robust.Shared.Random;
namespace Content.Server.Kitchen.EntitySystems;
base.Initialize();
SubscribeLocalEvent<SharpComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<SharpButcherDoafterComplete>(OnDoafterComplete);
- SubscribeLocalEvent<SharpButcherDoafterCancelled>(OnDoafterCancelled);
+ SubscribeLocalEvent<SharpComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<ButcherableComponent, GetVerbsEvent<InteractionVerb>>(OnGetInteractionVerbs);
}
return;
var doAfter =
- new DoAfterEventArgs(user, sharp.ButcherDelayModifier * butcher.ButcherDelay, default, target)
+ new DoAfterEventArgs(user, sharp.ButcherDelayModifier * butcher.ButcherDelay, target: target, used: knife)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
- NeedHand = true,
- BroadcastFinishedEvent = new SharpButcherDoafterComplete { User = user, Entity = target, Sharp = knife },
- BroadcastCancelledEvent = new SharpButcherDoafterCancelled { Entity = target, Sharp = knife }
+ NeedHand = true
};
_doAfterSystem.DoAfter(doAfter);
}
- private void OnDoafterComplete(SharpButcherDoafterComplete ev)
+ private void OnDoAfter(EntityUid uid, SharpComponent component, DoAfterEvent args)
{
- if (!TryComp<ButcherableComponent>(ev.Entity, out var butcher))
+ if (args.Handled || args.Cancelled || !TryComp<ButcherableComponent>(args.Args.Target, out var butcher))
return;
- if (!TryComp<SharpComponent>(ev.Sharp, out var sharp))
- return;
-
- sharp.Butchering.Remove(ev.Entity);
+ component.Butchering.Remove(args.Args.Target.Value);
- if (_containerSystem.IsEntityInContainer(ev.Entity))
+ if (_containerSystem.IsEntityInContainer(args.Args.Target.Value))
+ {
+ args.Handled = true;
return;
+ }
var spawnEntities = EntitySpawnCollection.GetSpawns(butcher.SpawnedEntities, _robustRandom);
- var coords = Transform(ev.Entity).MapPosition;
- EntityUid popupEnt = default;
+ var coords = Transform(args.Args.Target.Value).MapPosition;
+ EntityUid popupEnt = default!;
foreach (var proto in spawnEntities)
{
// distribute the spawned items randomly in a small radius around the origin
popupEnt = Spawn(proto, coords.Offset(_robustRandom.NextVector2(0.25f)));
}
- var hasBody = TryComp<BodyComponent>(ev.Entity, out var body);
+ var hasBody = TryComp<BodyComponent>(args.Args.Target.Value, out var body);
// only show a big popup when butchering living things.
var popupType = PopupType.Small;
if (hasBody)
popupType = PopupType.LargeCaution;
- _popupSystem.PopupEntity(Loc.GetString("butcherable-knife-butchered-success", ("target", ev.Entity), ("knife", ev.Sharp)),
- popupEnt, ev.User, popupType);
+ _popupSystem.PopupEntity(Loc.GetString("butcherable-knife-butchered-success", ("target", args.Args.Target.Value), ("knife", uid)),
+ popupEnt, args.Args.User, popupType);
if (hasBody)
- _bodySystem.GibBody(body!.Owner, body: body);
+ _bodySystem.GibBody(args.Args.Target.Value, body: body);
- _destructibleSystem.DestroyEntity(ev.Entity);
- }
-
- private void OnDoafterCancelled(SharpButcherDoafterCancelled ev)
- {
- if (!TryComp<SharpComponent>(ev.Sharp, out var sharp))
- return;
+ _destructibleSystem.DestroyEntity(args.Args.Target.Value);
- sharp.Butchering.Remove(ev.Entity);
+ args.Handled = true;
}
private void OnGetInteractionVerbs(EntityUid uid, ButcherableComponent component, GetVerbsEvent<InteractionVerb> args)
args.Verbs.Add(verb);
}
}
-
-public sealed class SharpButcherDoafterComplete : EntityEventArgs
-{
- public EntityUid Entity;
- public EntityUid Sharp;
- public EntityUid User;
-}
-
-public sealed class SharpButcherDoafterCancelled : EntityEventArgs
-{
- public EntityUid Entity;
- public EntityUid Sharp;
-}
[DataField("togglePort", customTypeSerializer: typeof(PrototypeIdSerializer<ReceiverPortPrototype>))]
public string TogglePort = "Toggle";
- public CancellationTokenSource? CancelToken;
-
/// <summary>
/// How long it takes to eject a bulb from this
/// </summary>
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Timing;
-using System.Threading;
+using Content.Shared.DoAfter;
namespace Content.Server.Light.EntitySystems
{
[Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2);
SubscribeLocalEvent<PoweredLightComponent, PowerChangedEvent>(OnPowerChanged);
- SubscribeLocalEvent<PoweredLightComponent, EjectBulbCompleteEvent>(OnEjectBulbComplete);
- SubscribeLocalEvent<PoweredLightComponent, EjectBulbCancelledEvent>(OnEjectBulbCancelled);
+ SubscribeLocalEvent<PoweredLightComponent, DoAfterEvent>(OnDoAfter);
}
private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args)
if (args.Handled)
return;
- if (light.CancelToken != null)
- return;
-
// check if light has bulb to eject
var bulbUid = GetBulb(uid, light);
if (bulbUid == null)
var damage = _damageableSystem.TryChangeDamage(userUid, light.Damage, origin: userUid);
if (damage != null)
- _adminLogger.Add(LogType.Damaged,
- $"{ToPrettyString(args.User):user} burned their hand on {ToPrettyString(args.Target):target} and received {damage.Total:damage} damage");
+ _adminLogger.Add(LogType.Damaged, $"{ToPrettyString(args.User):user} burned their hand on {ToPrettyString(args.Target):target} and received {damage.Total:damage} damage");
- SoundSystem.Play(light.BurnHandSound.GetSound(), Filter.Pvs(uid), uid);
+ _audio.Play(light.BurnHandSound, Filter.Pvs(uid), uid, true);
args.Handled = true;
return;
}
// removing a working bulb, so require a delay
- light.CancelToken = new CancellationTokenSource();
- _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, light.EjectBulbDelay, light.CancelToken.Token, uid)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, light.EjectBulbDelay, target:uid)
{
BreakOnUserMove = true,
BreakOnDamage = true,
- BreakOnStun = true,
- TargetFinishedEvent = new EjectBulbCompleteEvent()
- {
- Component = light,
- User = userUid,
- Target = uid,
- },
- TargetCancelledEvent = new EjectBulbCancelledEvent()
- {
- Component = light,
- }
+ BreakOnStun = true
});
args.Handled = true;
if (time > light.LastThunk + ThunkDelay)
{
light.LastThunk = time;
- SoundSystem.Play(light.TurnOnSound.GetSound(), Filter.Pvs(uid), uid, AudioParams.Default.WithVolume(-10f));
+ _audio.Play(light.TurnOnSound, Filter.Pvs(uid), uid, true, AudioParams.Default.WithVolume(-10f));
}
}
else
light.IsBlinking = isNowBlinking;
- if (!EntityManager.TryGetComponent(light.Owner, out AppearanceComponent? appearance))
+ if (!EntityManager.TryGetComponent(uid, out AppearanceComponent? appearance))
return;
_appearance.SetData(uid, PoweredLightVisuals.Blinking, isNowBlinking, appearance);
UpdateLight(uid, light);
}
- private void OnEjectBulbComplete(EntityUid uid, PoweredLightComponent component, EjectBulbCompleteEvent args)
- {
- args.Component.CancelToken = null;
- EjectBulb(args.Target, args.User, args.Component);
- }
-
- private static void OnEjectBulbCancelled(EntityUid uid, PoweredLightComponent component, EjectBulbCancelledEvent args)
+ private void OnDoAfter(EntityUid uid, PoweredLightComponent component, DoAfterEvent args)
{
- args.Component.CancelToken = null;
- }
+ if (args.Handled || args.Cancelled || args.Args.Target == null)
+ return;
- private sealed class EjectBulbCompleteEvent : EntityEventArgs
- {
- public PoweredLightComponent Component { get; init; } = default!;
- public EntityUid User { get; init; }
- public EntityUid Target { get; init; }
- }
+ EjectBulb(args.Args.Target.Value, args.Args.User, component);
- private sealed class EjectBulbCancelledEvent : EntityEventArgs
- {
- public PoweredLightComponent Component { get; init; } = default!;
+ args.Handled = true;
}
}
}
-using System.Threading;
-using Content.Shared.Actions.ActionTypes;
+using Content.Shared.Actions.ActionTypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
namespace Content.Server.Magic;
[DataField("learnTime")]
public float LearnTime = .75f;
-
- public CancellationTokenSource? CancelToken;
}
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Body.Components;
+using Content.Shared.DoAfter;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Content.Shared.Interaction.Events;
SubscribeLocalEvent<SpellbookComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<SpellbookComponent, UseInHandEvent>(OnUse);
- SubscribeLocalEvent<SpellbookComponent, LearnDoAfterComplete>(OnLearnComplete);
- SubscribeLocalEvent<SpellbookComponent, LearnDoAfterCancel>(OnLearnCancel);
+ SubscribeLocalEvent<SpellbookComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<InstantSpawnSpellEvent>(OnInstantSpawn);
SubscribeLocalEvent<TeleportSpellEvent>(OnTeleportSpell);
SubscribeLocalEvent<ChangeComponentsSpellEvent>(OnChangeComponentsSpell);
}
+ private void OnDoAfter(EntityUid uid, SpellbookComponent component, DoAfterEvent args)
+ {
+ if (args.Handled || args.Cancelled)
+ return;
+
+ _actionsSystem.AddActions(args.Args.User, component.Spells, uid);
+ args.Handled = true;
+ }
+
private void OnInit(EntityUid uid, SpellbookComponent component, ComponentInit args)
{
//Negative charges means the spell can be used without it running out.
private void AttemptLearn(EntityUid uid, SpellbookComponent component, UseInHandEvent args)
{
- if (component.CancelToken != null) return;
-
- component.CancelToken = new CancellationTokenSource();
-
- var doAfterEventArgs = new DoAfterEventArgs(args.User, component.LearnTime, component.CancelToken.Token, uid)
+ var doAfterEventArgs = new DoAfterEventArgs(args.User, component.LearnTime, target:uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
- NeedHand = true, //What, are you going to read with your eyes only??
- TargetFinishedEvent = new LearnDoAfterComplete(args.User),
- TargetCancelledEvent = new LearnDoAfterCancel(),
+ NeedHand = true //What, are you going to read with your eyes only??
};
_doAfter.DoAfter(doAfterEventArgs);
}
- private void OnLearnComplete(EntityUid uid, SpellbookComponent component, LearnDoAfterComplete ev)
- {
- component.CancelToken = null;
- _actionsSystem.AddActions(ev.User, component.Spells, uid);
- }
-
- private void OnLearnCancel(EntityUid uid, SpellbookComponent component, LearnDoAfterCancel args)
- {
- component.CancelToken = null;
- }
-
#region Spells
/// <summary>
}
#endregion
-
- #region DoAfterClasses
-
- private sealed class LearnDoAfterComplete : EntityEventArgs
- {
- public readonly EntityUid User;
-
- public LearnDoAfterComplete(EntityUid uid)
- {
- User = uid;
- }
- }
-
- private sealed class LearnDoAfterCancel : EntityEventArgs { }
-
- #endregion
}
-using System.Threading;
-using Content.Server.Atmos;
+using Content.Server.Atmos;
using Content.Shared.Mech.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
[DataField("batteryRemovalDelay")]
public float BatteryRemovalDelay = 2;
- public CancellationTokenSource? EntryTokenSource;
-
/// <summary>
/// Whether or not the mech is airtight.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public Container ItemContainer = default!;
- public CancellationTokenSource? Token;
-}
-
-/// <summary>
-/// Event raised on the user when the grab is complete.
-/// </summary>
-public sealed class MechGrabberGrabFinishedEvent : EntityEventArgs
-{
- /// <summary>
- /// The thing that was grabbed.
- /// </summary>
- public EntityUid Grabbed;
-
- public MechGrabberGrabFinishedEvent(EntityUid grabbed)
- {
- Grabbed = grabbed;
- }
-}
-
-/// <summary>
-/// Event raised on the user when the grab fails
-/// </summary>
-public sealed class MechGrabberGrabCancelledEvent : EntityEventArgs
-{
-
}
using Content.Server.Mech.Components;
using Content.Server.Mech.Equipment.Components;
using Content.Server.Mech.Systems;
+using Content.Shared.DoAfter;
+using Content.Shared.Construction.Components;
using Content.Shared.Interaction;
using Content.Shared.Mech;
using Content.Shared.Mech.Equipment.Components;
SubscribeLocalEvent<MechGrabberComponent, AttemptRemoveMechEquipmentEvent>(OnAttemptRemove);
SubscribeLocalEvent<MechGrabberComponent, InteractNoHandEvent>(OnInteract);
- SubscribeLocalEvent<MechGrabberComponent, MechGrabberGrabFinishedEvent>(OnGrabFinished);
- SubscribeLocalEvent<MechGrabberComponent, MechGrabberGrabCancelledEvent>(OnGrabCancelled);
+ SubscribeLocalEvent<MechGrabberComponent, DoAfterEvent>(OnMechGrab);
}
private void OnGrabberMessage(EntityUid uid, MechGrabberComponent component, MechEquipmentUiMessageRelayEvent args)
if (mech.Energy + component.GrabEnergyDelta < 0)
return;
- if (component.Token != null)
- return;
-
if (!_interaction.InRangeUnobstructed(args.User, target))
return;
args.Handled = true;
- component.Token = new();
component.AudioStream = _audio.PlayPvs(component.GrabSound, uid);
- _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.GrabDelay, component.Token.Token, target, uid)
+ _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.GrabDelay, target:target, used:uid)
{
BreakOnTargetMove = true,
- BreakOnUserMove = true,
- UsedFinishedEvent = new MechGrabberGrabFinishedEvent(target),
- UsedCancelledEvent = new MechGrabberGrabCancelledEvent()
+ BreakOnUserMove = true
});
}
- private void OnGrabFinished(EntityUid uid, MechGrabberComponent component, MechGrabberGrabFinishedEvent args)
+ private void OnMechGrab(EntityUid uid, MechGrabberComponent component, DoAfterEvent args)
{
- component.Token = null;
+ if (args.Cancelled)
+ {
+ component.AudioStream?.Stop();
+ return;
+ }
+
+ if (args.Handled || args.Args.Target == null)
+ return;
if (!TryComp<MechEquipmentComponent>(uid, out var equipmentComponent) || equipmentComponent.EquipmentOwner == null)
return;
if (!_mech.TryChangeEnergy(equipmentComponent.EquipmentOwner.Value, component.GrabEnergyDelta))
return;
- component.ItemContainer.Insert(args.Grabbed);
+ component.ItemContainer.Insert(args.Args.Target.Value);
_mech.UpdateUserInterface(equipmentComponent.EquipmentOwner.Value);
- }
- private void OnGrabCancelled(EntityUid uid, MechGrabberComponent component, MechGrabberGrabCancelledEvent args)
- {
- component.AudioStream?.Stop();
- component.Token = null;
+ args.Handled = true;
}
}
using Content.Server.DoAfter;
using Content.Server.Mech.Components;
using Content.Server.Popups;
+using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Mech.Equipment.Components;
-using Robust.Shared.Player;
namespace Content.Server.Mech.Systems;
public override void Initialize()
{
SubscribeLocalEvent<MechEquipmentComponent, AfterInteractEvent>(OnUsed);
- SubscribeLocalEvent<MechEquipmentComponent, MechEquipmentInstallFinished>(OnFinished);
- SubscribeLocalEvent<MechEquipmentComponent, MechEquipmentInstallCancelled>(OnCancelled);
+ SubscribeLocalEvent<MechEquipmentComponent, DoAfterEvent<InsertEquipmentEvent>>(OnInsertEquipment);
}
private void OnUsed(EntityUid uid, MechEquipmentComponent component, AfterInteractEvent args)
{
- if (component.TokenSource != null)
- return;
-
if (args.Handled || !args.CanReach || args.Target == null)
return;
_popup.PopupEntity(Loc.GetString("mech-equipment-begin-install", ("item", uid)), mech);
- component.TokenSource = new();
- _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.InstallDuration, component.TokenSource.Token, mech, uid)
+ var insertEquipment = new InsertEquipmentEvent();
+ var doAfterEventArgs = new DoAfterEventArgs(args.User, component.InstallDuration, target: mech, used: uid)
{
BreakOnStun = true,
BreakOnTargetMove = true,
- BreakOnUserMove = true,
- UsedFinishedEvent = new MechEquipmentInstallFinished(mech),
- UsedCancelledEvent = new MechEquipmentInstallCancelled()
- });
+ BreakOnUserMove = true
+ };
+
+ _doAfter.DoAfter(doAfterEventArgs, insertEquipment);
}
- private void OnFinished(EntityUid uid, MechEquipmentComponent component, MechEquipmentInstallFinished args)
+ private void OnInsertEquipment(EntityUid uid, MechEquipmentComponent component, DoAfterEvent<InsertEquipmentEvent> args)
{
- component.TokenSource = null;
- _popup.PopupEntity(Loc.GetString("mech-equipment-finish-install", ("item", uid)), args.Mech);
- _mech.InsertEquipment(args.Mech, uid);
+ if (args.Handled || args.Cancelled || args.Args.Target == null)
+ return;
+
+ _popup.PopupEntity(Loc.GetString("mech-equipment-finish-install", ("item", uid)), args.Args.Target.Value);
+ _mech.InsertEquipment(args.Args.Target.Value, uid);
+
+ args.Handled = true;
}
- private void OnCancelled(EntityUid uid, MechEquipmentComponent component, MechEquipmentInstallCancelled args)
+ private sealed class InsertEquipmentEvent : EntityEventArgs
{
- component.TokenSource = null;
+
}
}
using System.Linq;
-using System.Threading;
using Content.Server.Atmos.EntitySystems;
using Content.Server.DoAfter;
using Content.Server.Mech.Components;
using Content.Server.Power.Components;
-using Content.Server.Tools;
using Content.Server.Wires;
using Content.Shared.ActionBlocker;
using Content.Shared.Damage;
+using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Content.Shared.Mech;
using Content.Shared.Mech.Components;
using Content.Shared.Mech.EntitySystems;
-using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.Tools.Components;
using Content.Shared.Verbs;
SubscribeLocalEvent<MechComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<MechComponent, GetVerbsEvent<AlternativeVerb>>(OnAlternativeVerb);
SubscribeLocalEvent<MechComponent, MechOpenUiEvent>(OnOpenUi);
- SubscribeLocalEvent<MechComponent, MechEntryFinishedEvent>(OnEntryFinished);
- SubscribeLocalEvent<MechComponent, MechEntryCanclledEvent>(OnEntryExitCancelled);
- SubscribeLocalEvent<MechComponent, MechExitFinishedEvent>(OnExitFinished);
- SubscribeLocalEvent<MechComponent, MechExitCanclledEvent>(OnEntryExitCancelled);
- SubscribeLocalEvent<MechComponent, MechRemoveBatteryFinishedEvent>(OnRemoveBatteryFinished);
- SubscribeLocalEvent<MechComponent, MechRemoveBatteryCancelledEvent>(OnRemoveBatteryCancelled);
+ SubscribeLocalEvent<MechComponent, DoAfterEvent<RemoveBatteryEvent>>(OnRemoveBattery);
+ SubscribeLocalEvent<MechComponent, DoAfterEvent<MechEntryEvent>>(OnMechEntry);
+ SubscribeLocalEvent<MechComponent, DoAfterEvent<MechExitEvent>>(OnMechExit);
SubscribeLocalEvent<MechComponent, DamageChangedEvent>(OnDamageChanged);
SubscribeLocalEvent<MechComponent, MechEquipmentRemoveMessage>(OnRemoveEquipmentMessage);
return;
}
- if (component.EntryTokenSource == null &&
- TryComp<ToolComponent>(args.Used, out var tool) &&
- tool.Qualities.Contains("Prying") &&
- component.BatterySlot.ContainedEntity != null)
+ if (TryComp<ToolComponent>(args.Used, out var tool) && tool.Qualities.Contains("Prying") && component.BatterySlot.ContainedEntity != null)
{
- component.EntryTokenSource = new();
- _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.BatteryRemovalDelay, component.EntryTokenSource.Token, uid, args.Target)
+ var removeBattery = new RemoveBatteryEvent();
+
+ var doAfterEventArgs = new DoAfterEventArgs(args.User, component.BatteryRemovalDelay, target: uid, used: args.Target)
{
BreakOnTargetMove = true,
- BreakOnUserMove = true,
- TargetFinishedEvent = new MechRemoveBatteryFinishedEvent(),
- TargetCancelledEvent = new MechRemoveBatteryCancelledEvent()
- });
+ BreakOnUserMove = true
+ };
+
+ _doAfter.DoAfter(doAfterEventArgs, removeBattery);
}
}
- private void OnRemoveBatteryFinished(EntityUid uid, MechComponent component, MechRemoveBatteryFinishedEvent args)
+ private void OnRemoveBattery(EntityUid uid, MechComponent component, DoAfterEvent<RemoveBatteryEvent> args)
{
- component.EntryTokenSource = null;
+ if (args.Cancelled || args.Handled)
+ return;
+
RemoveBattery(uid, component);
_actionBlocker.UpdateCanMove(uid);
- }
- private void OnRemoveBatteryCancelled(EntityUid uid, MechComponent component, MechRemoveBatteryCancelledEvent args)
- {
- component.EntryTokenSource = null;
+ args.Handled = true;
}
private void OnMapInit(EntityUid uid, MechComponent component, MapInitEvent args)
Text = Loc.GetString("mech-verb-enter"),
Act = () =>
{
- if (component.EntryTokenSource != null)
- return;
- component.EntryTokenSource = new CancellationTokenSource();
- _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.EntryDelay, component.EntryTokenSource.Token, uid)
+ var mechEntryEvent = new MechEntryEvent();
+ var doAfterEventArgs = new DoAfterEventArgs(args.User, component.EntryDelay, target: uid)
{
BreakOnUserMove = true,
BreakOnStun = true,
- TargetFinishedEvent = new MechEntryFinishedEvent(args.User),
- TargetCancelledEvent = new MechEntryCanclledEvent()
- });
+ };
+
+ _doAfter.DoAfter(doAfterEventArgs, mechEntryEvent);
}
};
var openUiVerb = new AlternativeVerb //can't hijack someone else's mech
Priority = 1, // Promote to top to make ejecting the ALT-click action
Act = () =>
{
- if (component.EntryTokenSource != null)
- return;
if (args.User == component.PilotSlot.ContainedEntity)
{
TryEject(uid, component);
return;
}
- component.EntryTokenSource = new CancellationTokenSource();
- _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ExitDelay, component.EntryTokenSource.Token, uid)
+ var mechExitEvent = new MechExitEvent();
+ var doAfterEventArgs = new DoAfterEventArgs(args.User, component.ExitDelay, target: uid)
{
BreakOnUserMove = true,
BreakOnTargetMove = true,
- BreakOnStun = true,
- TargetFinishedEvent = new MechExitFinishedEvent(),
- TargetCancelledEvent = new MechExitCanclledEvent()
- });
+ BreakOnStun = true
+ };
+
+ _doAfter.DoAfter(doAfterEventArgs, mechExitEvent);
}
};
args.Verbs.Add(ejectVerb);
}
}
- private void OnEntryFinished(EntityUid uid, MechComponent component, MechEntryFinishedEvent args)
+ private void OnMechEntry(EntityUid uid, MechComponent component, DoAfterEvent<MechEntryEvent> args)
{
- component.EntryTokenSource = null;
- TryInsert(uid, args.User, component);
+ if (args.Cancelled || args.Handled)
+ return;
+
+ TryInsert(uid, args.Args.User, component);
_actionBlocker.UpdateCanMove(uid);
+
+ args.Handled = true;
}
- private void OnExitFinished(EntityUid uid, MechComponent component, MechExitFinishedEvent args)
+ private void OnMechExit(EntityUid uid, MechComponent component, DoAfterEvent<MechExitEvent> args)
{
- component.EntryTokenSource = null;
+ if (args.Cancelled || args.Handled)
+ return;
+
TryEject(uid, component);
- }
- private void OnEntryExitCancelled(EntityUid uid, MechComponent component, EntityEventArgs args)
- {
- component.EntryTokenSource = null;
+ args.Handled = true;
}
private void OnDamageChanged(EntityUid uid, SharedMechComponent component, DamageChangedEvent args)
args.Handled = true;
}
#endregion
+
+ /// <summary>
+ /// Event raised when the battery is successfully removed from the mech,
+ /// on both success and failure
+ /// </summary>
+ private sealed class RemoveBatteryEvent : EntityEventArgs
+ {
+ }
+
+ /// <summary>
+ /// Event raised when a person enters a mech, on both success and failure
+ /// </summary>
+ private sealed class MechEntryEvent : EntityEventArgs
+ {
+ }
+
+ /// <summary>
+ /// Event raised when a person removes someone from a mech,
+ /// on both success and failure
+ /// </summary>
+ private sealed class MechExitEvent : EntityEventArgs
+ {
+ }
}
[RegisterComponent]
public sealed class BiomassReclaimerComponent : Component
{
- public CancellationTokenSource? CancelToken;
-
/// <summary>
/// This gets set for each mob it processes.
/// When it hits 0, there is a chance for the reclaimer to either spill blood or throw an item.
using Content.Server.DoAfter;
using Content.Server.Materials;
using Content.Server.Mind.Components;
+using Content.Shared.DoAfter;
using Content.Shared.Humanoid;
using Content.Shared.Interaction.Events;
using Content.Shared.Mobs.Components;
SubscribeLocalEvent<BiomassReclaimerComponent, UpgradeExamineEvent>(OnUpgradeExamine);
SubscribeLocalEvent<BiomassReclaimerComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<BiomassReclaimerComponent, SuicideEvent>(OnSuicide);
- SubscribeLocalEvent<ReclaimSuccessfulEvent>(OnReclaimSuccessful);
- SubscribeLocalEvent<ReclaimCancelledEvent>(OnReclaimCancelled);
+ SubscribeLocalEvent<BiomassReclaimerComponent, DoAfterEvent>(OnDoAfter);
}
private void OnSuicide(EntityUid uid, BiomassReclaimerComponent component, SuicideEvent args)
}
private void OnAfterInteractUsing(EntityUid uid, BiomassReclaimerComponent component, AfterInteractUsingEvent args)
{
- if (!args.CanReach)
- return;
-
- if (component.CancelToken != null || args.Target == null)
+ if (!args.CanReach || args.Target == null)
return;
if (!HasComp<MobStateComponent>(args.Used) || !CanGib(uid, args.Used, component))
return;
- component.CancelToken = new CancellationTokenSource();
- _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, 7f, component.CancelToken.Token, args.Target, args.Used)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, 7f, target:args.Target, used:args.Used)
{
- BroadcastFinishedEvent = new ReclaimSuccessfulEvent(args.User, args.Used, uid),
- BroadcastCancelledEvent = new ReclaimCancelledEvent(uid),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
args.AddPercentageUpgrade("biomass-reclaimer-component-upgrade-biomass-yield", component.YieldPerUnitMass / component.BaseYieldPerUnitMass);
}
- private void OnReclaimSuccessful(ReclaimSuccessfulEvent args)
+ private void OnDoAfter(EntityUid uid, BiomassReclaimerComponent component, DoAfterEvent args)
{
- if (!TryComp<BiomassReclaimerComponent>(args.Reclaimer, out var reclaimer))
+ if (args.Handled || args.Cancelled || args.Args.Target == null || HasComp<BiomassReclaimerComponent>(args.Args.Target.Value))
return;
- _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(args.User):player} used a biomass reclaimer to gib {ToPrettyString(args.Target):target} in {ToPrettyString(args.Reclaimer):reclaimer}");
- reclaimer.CancelToken = null;
- StartProcessing(args.Target, reclaimer);
- }
+ _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(args.Args.User):player} used a biomass reclaimer to gib {ToPrettyString(args.Args.Target.Value):target} in {ToPrettyString(uid):reclaimer}");
+ StartProcessing(args.Args.Target.Value, component);
- private void OnReclaimCancelled(ReclaimCancelledEvent args)
- {
- if (!TryComp<BiomassReclaimerComponent>(args.Reclaimer, out var reclaimer))
- return;
- reclaimer.CancelToken = null;
+ args.Handled = true;
}
private void StartProcessing(EntityUid toProcess, BiomassReclaimerComponent component, PhysicsComponent? physics = null)
return true;
}
-
- private readonly struct ReclaimCancelledEvent
- {
- public readonly EntityUid Reclaimer;
-
- public ReclaimCancelledEvent(EntityUid reclaimer)
- {
- Reclaimer = reclaimer;
- }
- }
-
- private readonly struct ReclaimSuccessfulEvent
- {
- public readonly EntityUid User;
- public readonly EntityUid Target;
- public readonly EntityUid Reclaimer;
- public ReclaimSuccessfulEvent(EntityUid user, EntityUid target, EntityUid reclaimer)
- {
- User = user;
- Target = target;
- Reclaimer = reclaimer;
- }
- }
}
}
[DataField("selfHealPenaltyMultiplier")]
public float SelfHealPenaltyMultiplier = 3f;
- public CancellationTokenSource? CancelToken = null;
-
/// <summary>
/// Sound played on healing begin
/// </summary>
/// </summary>
[DataField("scanDelay")]
public float ScanDelay = 0.8f;
- /// <summary>
- /// Token for interrupting scanning do after.
- /// </summary>
- public CancellationTokenSource? CancelToken;
+
public BoundUserInterface? UserInterface => Owner.GetUIOrNull(HealthAnalyzerUiKey.Key);
/// <summary>
-using System.Threading;
using Content.Server.Atmos;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Piping.Components;
using Content.Server.NodeContainer.NodeGroups;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components;
-using Content.Server.Tools;
using Content.Server.UserInterface;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Containers.ItemSlots;
+using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Emag.Systems;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Medical.Cryogenics;
using Content.Shared.MedicalScanner;
+using Content.Shared.Tools;
using Content.Shared.Tools.Components;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
- [Dependency] private readonly ToolSystem _toolSystem = default!;
+ [Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly MetaDataSystem _metaDataSystem = default!;
[Dependency] private readonly ReactiveSystem _reactiveSystem = default!;
SubscribeLocalEvent<CryoPodComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<CryoPodComponent, GetVerbsEvent<AlternativeVerb>>(AddAlternativeVerbs);
SubscribeLocalEvent<CryoPodComponent, GotEmaggedEvent>(OnEmagged);
- SubscribeLocalEvent<CryoPodComponent, DoInsertCryoPodEvent>(DoInsertCryoPod);
- SubscribeLocalEvent<CryoPodComponent, DoInsertCancelledCryoPodEvent>(DoInsertCancelCryoPod);
+ SubscribeLocalEvent<CryoPodComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<CryoPodComponent, CryoPodPryFinished>(OnCryoPodPryFinished);
SubscribeLocalEvent<CryoPodComponent, CryoPodPryInterrupted>(OnCryoPodPryInterrupted);
private void HandleDragDropOn(EntityUid uid, CryoPodComponent cryoPodComponent, ref DragDropTargetEvent args)
{
if (cryoPodComponent.BodyContainer.ContainedEntity != null)
- {
return;
- }
- if (cryoPodComponent.DragDropCancelToken != null)
- {
- cryoPodComponent.DragDropCancelToken.Cancel();
- cryoPodComponent.DragDropCancelToken = null;
- return;
- }
-
- cryoPodComponent.DragDropCancelToken = new CancellationTokenSource();
- var doAfterArgs = new DoAfterEventArgs(args.User, cryoPodComponent.EntryDelay, cryoPodComponent.DragDropCancelToken.Token, uid, args.Dragged)
+ var doAfterArgs = new DoAfterEventArgs(args.User, cryoPodComponent.EntryDelay, target:args.Dragged, used:uid)
{
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = false,
- TargetFinishedEvent = new DoInsertCryoPodEvent(args.Dragged),
- TargetCancelledEvent = new DoInsertCancelledCryoPodEvent()
};
_doAfterSystem.DoAfter(doAfterArgs);
args.Handled = true;
}
+ private void OnDoAfter(EntityUid uid, CryoPodComponent component, DoAfterEvent args)
+ {
+ if (args.Cancelled || args.Handled || args.Args.Target == null)
+ return;
+
+ InsertBody(uid, args.Args.Target.Value, component);
+
+ args.Handled = true;
+ }
+
private void OnActivateUIAttempt(EntityUid uid, CryoPodComponent cryoPodComponent, ActivatableUIOpenAttemptEvent args)
{
if (args.Cancelled)
return;
cryoPodComponent.IsPrying = true;
- _toolSystem.UseTool(args.Used, args.User, uid, 0f,
- cryoPodComponent.PryDelay, "Prying",
- new CryoPodPryFinished(), new CryoPodPryInterrupted(), uid);
+ var toolEvData = new ToolEventData(new CryoPodPryFinished(), targetEntity:uid);
+ _toolSystem.UseTool(args.Used, args.User, uid, cryoPodComponent.PryDelay, new [] {"Prying"}, toolEvData);
args.Handled = true;
}
private void OnExamined(EntityUid uid, CryoPodComponent component, ExaminedEvent args)
{
- var container = _itemSlotsSystem.GetItemOrNull(component.Owner, component.SolutionContainerName);
+ var container = _itemSlotsSystem.GetItemOrNull(uid, component.SolutionContainerName);
if (args.IsInDetailsRange && container != null && _solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var containerSolution))
{
args.PushMarkup(Loc.GetString("cryo-pod-examine", ("beaker", Name(container.Value))));
-using System.Threading;
using Content.Server.Administration.Logs;
using Content.Server.Body.Systems;
using Content.Server.DoAfter;
using Content.Shared.Audio;
using Content.Shared.Damage;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Mobs;
base.Initialize();
SubscribeLocalEvent<HealingComponent, UseInHandEvent>(OnHealingUse);
SubscribeLocalEvent<HealingComponent, AfterInteractEvent>(OnHealingAfterInteract);
- SubscribeLocalEvent<HealingCancelledEvent>(OnHealingCancelled);
- SubscribeLocalEvent<DamageableComponent, HealingCompleteEvent>(OnHealingComplete);
+ SubscribeLocalEvent<DamageableComponent, DoAfterEvent<HealingData>>(OnDoAfter);
}
- private void OnHealingComplete(EntityUid uid, DamageableComponent component, HealingCompleteEvent args)
+ private void OnDoAfter(EntityUid uid, DamageableComponent component, DoAfterEvent<HealingData> args)
{
- if (_mobStateSystem.IsDead(uid))
+ if (args.Handled || args.Cancelled || _mobStateSystem.IsDead(uid) || args.Args.Used == null)
return;
- if (TryComp<StackComponent>(args.Component.Owner, out var stack) && stack.Count < 1)
+ if (component.DamageContainerID is not null && !component.DamageContainerID.Equals(component.DamageContainerID))
return;
- if (component.DamageContainerID is not null &&
- !component.DamageContainerID.Equals(component.DamageContainerID))
- return;
-
- if (args.Component.BloodlossModifier != 0)
- {
- // Heal some bloodloss damage.
- _bloodstreamSystem.TryModifyBleedAmount(uid, args.Component.BloodlossModifier);
- }
+ // Heal some bloodloss damage.
+ if (args.AdditionalData.HealingComponent.BloodlossModifier != 0)
+ _bloodstreamSystem.TryModifyBleedAmount(uid, args.AdditionalData.HealingComponent.BloodlossModifier);
- var healed = _damageable.TryChangeDamage(uid, args.Component.Damage, true, origin: args.User);
+ var healed = _damageable.TryChangeDamage(uid, args.AdditionalData.HealingComponent.Damage, true, origin: args.Args.User);
- // Reverify that we can heal the damage.
if (healed == null)
return;
- _stacks.Use(args.Component.Owner, 1, stack);
+ // Reverify that we can heal the damage.
+ _stacks.Use(args.Args.Used.Value, 1, args.AdditionalData.Stack);
+
+ if (uid != args.Args.User)
+ _adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.Args.User):user} healed {EntityManager.ToPrettyString(uid):target} for {healed.Total:damage} damage");
- if (uid != args.User)
- _adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.User):user} healed {EntityManager.ToPrettyString(uid):target} for {healed.Total:damage} damage");
else
- _adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.User):user} healed themselves for {healed.Total:damage} damage");
+ _adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.Args.User):user} healed themselves for {healed.Total:damage} damage");
- if (args.Component.HealingEndSound != null)
- {
- _audio.PlayPvs(args.Component.HealingEndSound, uid,
- AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
- }
- }
+ if (args.AdditionalData.HealingComponent.HealingEndSound != null)
+ _audio.PlayPvs(args.AdditionalData.HealingComponent.HealingEndSound, uid, AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
- private static void OnHealingCancelled(HealingCancelledEvent ev)
- {
- ev.Component.CancelToken = null;
+ args.Handled = true;
}
private void OnHealingUse(EntityUid uid, HealingComponent component, UseInHandEvent args)
private bool TryHeal(EntityUid uid, EntityUid user, EntityUid target, HealingComponent component)
{
- if (component.CancelToken != null)
- {
- return false;
- }
-
- if (_mobStateSystem.IsDead(target))
- return false;
-
- if (!TryComp<DamageableComponent>(target, out var targetDamage))
+ if (_mobStateSystem.IsDead(target) || !TryComp<DamageableComponent>(target, out var targetDamage))
return false;
if (targetDamage.TotalDamage == 0)
if (component.DamageContainerID is not null && !component.DamageContainerID.Equals(targetDamage.DamageContainerID))
return false;
- if (user != target &&
- !_interactionSystem.InRangeUnobstructed(user, target, popup: true))
- {
+ if (user != target && !_interactionSystem.InRangeUnobstructed(user, target, popup: true))
return false;
- }
- if (TryComp<StackComponent>(component.Owner, out var stack) && stack.Count < 1)
+ if (!TryComp<StackComponent>(uid, out var stack) || stack.Count < 1)
return false;
- component.CancelToken = new CancellationTokenSource();
-
if (component.HealingBeginSound != null)
- {
- _audio.PlayPvs(component.HealingBeginSound, uid,
- AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
- }
+ _audio.PlayPvs(component.HealingBeginSound, uid, AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
var delay = user != target
? component.Delay
: component.Delay * GetScaledHealingPenalty(user, component);
- _doAfter.DoAfter(new DoAfterEventArgs(user, delay, component.CancelToken.Token, target)
+ var healingData = new HealingData(component, stack);
+
+ var doAfterEventArgs = new DoAfterEventArgs(user, delay, target: target, used: uid)
{
BreakOnUserMove = true,
BreakOnTargetMove = true,
// not being able to heal your own ticking damage would be frustrating.
BreakOnStun = true,
NeedHand = true,
- TargetFinishedEvent = new HealingCompleteEvent
- {
- User = user,
- Component = component,
- },
- BroadcastCancelledEvent = new HealingCancelledEvent
- {
- Component = component,
- },
// Juusstt in case damageble gets removed it avoids having to re-cancel the token. Won't need this when DoAfterEvent<T> gets added.
- PostCheck = () =>
- {
- component.CancelToken = null;
- return true;
- },
- });
+ PostCheck = () => true
+ };
+
+ _doAfter.DoAfter(doAfterEventArgs, healingData);
return true;
}
var output = component.Delay;
if (!TryComp<MobThresholdsComponent>(uid, out var mobThreshold) || !TryComp<DamageableComponent>(uid, out var damageable))
return output;
-
-
- if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var amount,
- mobThreshold))
- {
+ if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var amount, mobThreshold))
return 1;
- }
+
var percentDamage = (float) (damageable.TotalDamage / amount);
//basically make it scale from 1 to the multiplier.
var modifier = percentDamage * (component.SelfHealPenaltyMultiplier - 1) + 1;
return Math.Max(modifier, 1);
}
- private sealed class HealingCompleteEvent : EntityEventArgs
- {
- public EntityUid User;
- public HealingComponent Component = default!;
- }
-
- private sealed class HealingCancelledEvent : EntityEventArgs
+ private record struct HealingData(HealingComponent HealingComponent, StackComponent Stack)
{
- public HealingComponent Component = default!;
+ public HealingComponent HealingComponent = HealingComponent;
+ public StackComponent Stack = Stack;
}
}
-using System.Threading;
using Content.Server.DoAfter;
using Content.Server.Medical.Components;
using Content.Server.Disease;
using Content.Server.Popups;
using Content.Shared.Damage;
+using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Mobs.Components;
-using Content.Shared.Audio;
using Robust.Server.GameObjects;
-using Robust.Shared.Player;
using static Content.Shared.MedicalScanner.SharedHealthAnalyzerComponent;
namespace Content.Server.Medical
[Dependency] private readonly DiseaseSystem _disease = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
+ [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HealthAnalyzerComponent, ActivateInWorldEvent>(HandleActivateInWorld);
SubscribeLocalEvent<HealthAnalyzerComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<TargetScanSuccessfulEvent>(OnTargetScanSuccessful);
- SubscribeLocalEvent<ScanCancelledEvent>(OnScanCancelled);
+ SubscribeLocalEvent<HealthAnalyzerComponent, DoAfterEvent>(OnDoAfter);
}
private void HandleActivateInWorld(EntityUid uid, HealthAnalyzerComponent healthAnalyzer, ActivateInWorldEvent args)
private void OnAfterInteract(EntityUid uid, HealthAnalyzerComponent healthAnalyzer, AfterInteractEvent args)
{
- if (healthAnalyzer.CancelToken != null)
- {
- healthAnalyzer.CancelToken.Cancel();
- healthAnalyzer.CancelToken = null;
+ if (args.Target == null || !args.CanReach || !HasComp<MobStateComponent>(args.Target))
return;
- }
-
- if (args.Target == null)
- return;
-
- if (!args.CanReach)
- return;
-
- if (healthAnalyzer.CancelToken != null)
- return;
-
- if (!HasComp<MobStateComponent>(args.Target))
- return;
-
- healthAnalyzer.CancelToken = new CancellationTokenSource();
_audio.PlayPvs(healthAnalyzer.ScanningBeginSound, uid);
- _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, healthAnalyzer.ScanDelay, healthAnalyzer.CancelToken.Token, target: args.Target)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, healthAnalyzer.ScanDelay, target: args.Target, used:uid)
{
- BroadcastFinishedEvent = new TargetScanSuccessfulEvent(args.User, args.Target, healthAnalyzer),
- BroadcastCancelledEvent = new ScanCancelledEvent(healthAnalyzer),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
});
}
- private void OnTargetScanSuccessful(TargetScanSuccessfulEvent args)
+ private void OnDoAfter(EntityUid uid, HealthAnalyzerComponent component, DoAfterEvent args)
{
- args.Component.CancelToken = null;
+ if (args.Handled || args.Cancelled || args.Args.Target == null)
+ return;
- _audio.PlayPvs(args.Component.ScanningEndSound, args.User);
+ _audio.PlayPvs(component.ScanningEndSound, args.Args.User);
- UpdateScannedUser(args.Component.Owner, args.User, args.Target, args.Component);
+ UpdateScannedUser(uid, args.Args.User, args.Args.Target.Value, component);
// Below is for the traitor item
// Piggybacking off another component's doafter is complete CBT so I gave up
// and put it on the same component
- if (string.IsNullOrEmpty(args.Component.Disease) || args.Target == null)
+ if (string.IsNullOrEmpty(component.Disease))
+ {
+ args.Handled = true;
return;
+ }
- _disease.TryAddDisease(args.Target.Value, args.Component.Disease);
+ _disease.TryAddDisease(args.Args.Target.Value, component.Disease);
- if (args.User == args.Target)
+ if (args.Args.User == args.Args.Target)
{
- _popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-self", ("disease", args.Component.Disease)),
- args.User, args.User);
- return;
+ _popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-self", ("disease", component.Disease)),
+ args.Args.User, args.Args.User);
+ }
+
+
+ else
+ {
+ _popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-other", ("target", Identity.Entity(args.Args.Target.Value, EntityManager)),
+ ("disease", component.Disease)), args.Args.User, args.Args.User);
}
- _popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-other", ("target", Identity.Entity(args.Target.Value, EntityManager)), ("disease", args.Component.Disease)),
- args.User, args.User);
+
+ args.Handled = true;
}
private void OpenUserInterface(EntityUid user, HealthAnalyzerComponent healthAnalyzer)
{
- if (!TryComp<ActorComponent>(user, out var actor))
+ if (!TryComp<ActorComponent>(user, out var actor) || healthAnalyzer.UserInterface == null)
return;
- healthAnalyzer.UserInterface?.Open(actor.PlayerSession);
+ _uiSystem.OpenUi(healthAnalyzer.UserInterface ,actor.PlayerSession);
}
public void UpdateScannedUser(EntityUid uid, EntityUid user, EntityUid? target, HealthAnalyzerComponent? healthAnalyzer)
return;
OpenUserInterface(user, healthAnalyzer);
- healthAnalyzer.UserInterface?.SendMessage(new HealthAnalyzerScannedUserMessage(target));
- }
-
- private static void OnScanCancelled(ScanCancelledEvent args)
- {
- args.HealthAnalyzer.CancelToken = null;
- }
-
- private sealed class ScanCancelledEvent : EntityEventArgs
- {
- public readonly HealthAnalyzerComponent HealthAnalyzer;
- public ScanCancelledEvent(HealthAnalyzerComponent healthAnalyzer)
- {
- HealthAnalyzer = healthAnalyzer;
- }
- }
-
- private sealed class TargetScanSuccessfulEvent : EntityEventArgs
- {
- public EntityUid User { get; }
- public EntityUid? Target { get; }
- public HealthAnalyzerComponent Component { get; }
-
- public TargetScanSuccessfulEvent(EntityUid user, EntityUid? target, HealthAnalyzerComponent component)
- {
- User = user;
- Target = target;
- Component = component;
- }
+ _uiSystem.SendUiMessage(healthAnalyzer.UserInterface, new HealthAnalyzerScannedUserMessage(target));
}
}
}
{
public bool IsActive = false;
- public CancellationTokenSource? CancelToken;
-
[DataField("delay")]
public float Delay = 2.5f;
using Content.Shared.FixedPoint;
using Content.Shared.Inventory.Events;
using Content.Shared.Verbs;
-using Robust.Shared.Player;
-using System.Threading;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
+using Content.Shared.DoAfter;
namespace Content.Server.Medical
{
SubscribeLocalEvent<WearingStethoscopeComponent, GetVerbsEvent<InnateVerb>>(AddStethoscopeVerb);
SubscribeLocalEvent<StethoscopeComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<StethoscopeComponent, StethoscopeActionEvent>(OnStethoscopeAction);
- SubscribeLocalEvent<ListenSuccessfulEvent>(OnListenSuccess);
- SubscribeLocalEvent<ListenCancelledEvent>(OnListenCancelled);
+ SubscribeLocalEvent<StethoscopeComponent, DoAfterEvent>(OnDoAfter);
}
/// <summary>
{
Act = () =>
{
- StartListening(uid, args.Target, stetho); // start doafter
+ StartListening(component.Stethoscope, uid, args.Target, stetho); // start doafter
},
Text = Loc.GetString("stethoscope-verb"),
IconTexture = "Clothing/Neck/Misc/stethoscope.rsi/icon.png",
private void OnStethoscopeAction(EntityUid uid, StethoscopeComponent component, StethoscopeActionEvent args)
{
- StartListening(args.Performer, args.Target, component);
+ StartListening(uid, args.Performer, args.Target, component);
}
private void OnGetActions(EntityUid uid, StethoscopeComponent component, GetItemActionsEvent args)
args.Actions.Add(component.Action);
}
- // doafter succeeded / failed
- private void OnListenSuccess(ListenSuccessfulEvent ev)
- {
- ev.Component.CancelToken = null;
- ExamineWithStethoscope(ev.User, ev.Target);
- }
-
- private void OnListenCancelled(ListenCancelledEvent ev)
- {
- if (ev.Component == null)
- return;
- ev.Component.CancelToken = null;
- }
// construct the doafter and start it
- private void StartListening(EntityUid user, EntityUid target, StethoscopeComponent comp)
+ private void StartListening(EntityUid scope, EntityUid user, EntityUid target, StethoscopeComponent comp)
{
- comp.CancelToken = new CancellationTokenSource();
- _doAfterSystem.DoAfter(new DoAfterEventArgs(user, comp.Delay, comp.CancelToken.Token, target: target)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(user, comp.Delay, target: target, used:scope)
{
- BroadcastFinishedEvent = new ListenSuccessfulEvent(user, target, comp),
- BroadcastCancelledEvent = new ListenCancelledEvent(user, comp),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
});
}
+ private void OnDoAfter(EntityUid uid, StethoscopeComponent component, DoAfterEvent args)
+ {
+ if (args.Handled || args.Cancelled || args.Args.Target == null)
+ return;
+
+ ExamineWithStethoscope(args.Args.User, args.Args.Target.Value);
+ }
+
/// <summary>
/// Return a value based on the total oxyloss of the target.
/// Could be expanded in the future with reagent effects etc.
};
return msg;
}
-
- // events for the doafter
- private sealed class ListenSuccessfulEvent : EntityEventArgs
- {
- public EntityUid User;
- public EntityUid Target;
- public StethoscopeComponent Component;
-
- public ListenSuccessfulEvent(EntityUid user, EntityUid target, StethoscopeComponent component)
- {
- User = user;
- Target = target;
- Component = component;
- }
- }
-
- private sealed class ListenCancelledEvent : EntityEventArgs
- {
- public EntityUid Uid;
- public StethoscopeComponent Component;
-
- public ListenCancelledEvent(EntityUid uid, StethoscopeComponent component)
- {
- Uid = uid;
- Component = component;
- }
- }
-
}
public sealed class StethoscopeActionEvent : EntityTargetActionEvent {}
/// </summary>
public bool PlayedAlertSound = false;
- public CancellationToken? DisarmCancelToken = null;
-
public IPlayingAudioStream? AlertAudioStream = default;
}
}
using Content.Server.Explosion.EntitySystems;
using Content.Server.Popups;
using Content.Server.Station.Systems;
-using Content.Server.UserInterface;
using Content.Shared.Audio;
using Content.Shared.Construction.Components;
using Content.Shared.Containers.ItemSlots;
+using Content.Shared.DoAfter;
using Content.Shared.Nuke;
using Content.Shared.Popups;
-using Robust.Shared.Audio;
+using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Random;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly UserInterfaceSystem _ui = default!;
/// <summary>
/// Used to calculate when the nuke song should start playing for maximum kino with the nuke sfx
SubscribeLocalEvent<NukeComponent, NukeKeypadEnterMessage>(OnEnterButtonPressed);
// Doafter events
- SubscribeLocalEvent<NukeComponent, NukeDisarmSuccessEvent>(OnDisarmSuccess);
- SubscribeLocalEvent<NukeComponent, NukeDisarmCancelledEvent>(OnDisarmCancelled);
+ SubscribeLocalEvent<NukeComponent, DoAfterEvent>(OnDoAfter);
}
private void OnInit(EntityUid uid, NukeComponent component, ComponentInit args)
private void OnClearButtonPressed(EntityUid uid, NukeComponent component, NukeKeypadClearMessage args)
{
- PlaySound(uid, component.KeypadPressSound, 0f, component);
+ _audio.Play(component.KeypadPressSound, Filter.Pvs(uid), uid, true);
if (component.Status != NukeStatus.AWAIT_CODE)
return;
#region Doafter Events
- private void OnDisarmSuccess(EntityUid uid, NukeComponent component, NukeDisarmSuccessEvent args)
+ private void OnDoAfter(EntityUid uid, NukeComponent component, DoAfterEvent args)
{
- component.DisarmCancelToken = null;
+ if(args.Handled || args.Cancelled)
+ return;
+
DisarmBomb(uid, component);
- }
- private void OnDisarmCancelled(EntityUid uid, NukeComponent component, NukeDisarmCancelledEvent args)
- {
- component.DisarmCancelToken = null;
- }
+ var ev = new NukeDisarmSuccessEvent();
+ RaiseLocalEvent(ev);
+ args.Handled = true;
+ }
#endregion
private void TickCooldown(EntityUid uid, float frameTime, NukeComponent? nuke = null)
UpdateStatus(uid, nuke);
}
- UpdateUserInterface(nuke.Owner, nuke);
+ UpdateUserInterface(uid, nuke);
}
private void TickTimer(EntityUid uid, float frameTime, NukeComponent? nuke = null)
// play alert sound if time is running out
if (nuke.RemainingTime <= nuke.AlertSoundTime && !nuke.PlayedAlertSound)
{
- nuke.AlertAudioStream = SoundSystem.Play(nuke.AlertSound.GetSound(), Filter.Broadcast());
+ nuke.AlertAudioStream = _audio.Play(nuke.AlertSound, Filter.Broadcast(), uid, true);
_soundSystem.StopStationEventMusic(uid, StationEventMusicType.Nuke);
nuke.PlayedAlertSound = true;
}
{
component.Status = NukeStatus.AWAIT_ARM;
component.RemainingTime = component.Timer;
- PlaySound(uid, component.AccessGrantedSound, 0, component);
+ _audio.Play(component.AccessGrantedSound, Filter.Pvs(uid), uid, true);
}
else
{
component.EnteredCode = "";
- PlaySound(uid, component.AccessDeniedSound, 0, component);
+ _audio.Play(component.AccessDeniedSound, Filter.Pvs(uid), uid, true);
}
break;
if (!Resolve(uid, ref component))
return;
- var ui = component.Owner.GetUIOrNull(NukeUiKey.Key);
+ var ui = _ui.GetUiOrNull(uid, NukeUiKey.Key);
if (ui == null)
return;
(component.Status == NukeStatus.AWAIT_ARM ||
component.Status == NukeStatus.ARMED);
- var state = new NukeUiState()
+ var state = new NukeUiState
{
Status = component.Status,
RemainingTime = (int) component.RemainingTime,
CooldownTime = (int) component.CooldownTime
};
- ui.SetState(state);
+ _ui.SetUiState(ui, state);
}
private void PlayNukeKeypadSound(EntityUid uid, int number, NukeComponent? component = null)
// Don't double-dip on the octave shifting
component.LastPlayedKeypadSemitones = number == 0 ? component.LastPlayedKeypadSemitones : semitoneShift;
- SoundSystem.Play(component.KeypadPressSound.GetSound(), Filter.Pvs(uid), uid,
- AudioHelpers.ShiftSemitone(semitoneShift).WithVolume(-5f));
- }
-
- private void PlaySound(EntityUid uid, SoundSpecifier sound, float varyPitch = 0f,
- NukeComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- SoundSystem.Play(sound.GetSound(),
- Filter.Pvs(uid), uid, AudioHelpers.WithVariation(varyPitch).WithVolume(-5f));
+ _audio.Play(component.KeypadPressSound, Filter.Pvs(uid), uid, true, AudioHelpers.ShiftSemitone(semitoneShift).WithVolume(-5f));
}
public string GenerateRandomNumberString(int length)
var sender = Loc.GetString("nuke-component-announcement-sender");
_chatSystem.DispatchStationAnnouncement(uid, announcement, sender, false, null, Color.Red);
- NukeArmedAudio(component);
+ _soundSystem.PlayGlobalOnStation(uid, _audio.GetSound(component.ArmSound));
_itemSlots.SetLock(uid, component.DiskSlot, true);
nukeXform.Anchored = true;
_chatSystem.DispatchStationAnnouncement(uid, announcement, sender, false);
component.PlayedNukeSong = false;
- NukeDisarmedAudio(component);
+ _soundSystem.PlayGlobalOnStation(uid, _audio.GetSound(component.DisarmSound));
+ _soundSystem.StopStationEventMusic(uid, StationEventMusicType.Nuke);
// disable sound and reset it
component.PlayedAlertSound = false;
OwningStation = transform.GridUid,
});
- _soundSystem.StopStationEventMusic(component.Owner, StationEventMusicType.Nuke);
+ _soundSystem.StopStationEventMusic(uid, StationEventMusicType.Nuke);
EntityManager.DeleteEntity(uid);
}
private void DisarmBombDoafter(EntityUid uid, EntityUid user, NukeComponent nuke)
{
- nuke.DisarmCancelToken = new();
- var doafter = new DoAfterEventArgs(user, nuke.DisarmDoafterLength, nuke.DisarmCancelToken.Value, uid)
+ var doafter = new DoAfterEventArgs(user, nuke.DisarmDoafterLength, target: uid)
{
- TargetCancelledEvent = new NukeDisarmCancelledEvent(),
- TargetFinishedEvent = new NukeDisarmSuccessEvent(),
- BroadcastFinishedEvent = new NukeDisarmSuccessEvent(),
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
- NeedHand = true,
+ NeedHand = true
};
_doAfterSystem.DoAfter(doafter);
_popups.PopupEntity(Loc.GetString("nuke-component-doafter-warning"), user,
user, PopupType.LargeCaution);
}
-
- private void NukeArmedAudio(NukeComponent component)
- {
- _soundSystem.PlayGlobalOnStation(component.Owner, component.ArmSound.GetSound());
- }
-
- private void NukeDisarmedAudio(NukeComponent component)
- {
- _soundSystem.PlayGlobalOnStation(component.Owner, component.DisarmSound.GetSound());
- _soundSystem.StopStationEventMusic(component.Owner, StationEventMusicType.Nuke);
- }
}
public sealed class NukeExplodedEvent : EntityEventArgs
/// <summary>
/// Raised directed on the nuke when its disarm doafter is successful.
+ /// So the game knows not to end.
/// </summary>
public sealed class NukeDisarmSuccessEvent : EntityEventArgs
{
}
-
- /// <summary>
- /// Raised directed on the nuke when its disarm doafter is cancelled.
- /// </summary>
- public sealed class NukeDisarmCancelledEvent : EntityEventArgs
- {
-
- }
-
}
-using System.Threading;
using Content.Server.Nutrition.EntitySystems;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
/// </summary>
[DataField("forceFeedDelay")]
public float ForceFeedDelay = 3;
-
- /// <summary>
- /// Token for interrupting a do-after action (e.g., force feeding). If not null, implies component is
- /// currently "in use".
- /// </summary>
- public CancellationTokenSource? CancelToken;
}
}
-using System.Threading;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Nutrition.EntitySystems;
using Content.Shared.FixedPoint;
[DataField("forceFeedDelay")]
public float ForceFeedDelay = 3;
- /// <summary>
- /// Token for interrupting a do-after action (e.g., force feeding). If not null, implies component is
- /// currently "in use".
- /// </summary>
- public CancellationTokenSource? CancelToken;
-
[ViewVariables]
public int UsesRemaining
{
-using System.Threading;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Popups;
using Content.Shared.Administration.Logs;
using Content.Shared.Body.Components;
+using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.FixedPoint;
using Content.Shared.IdentityManagement;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly ReactiveSystem _reaction = default!;
public override void Initialize()
{
SubscribeLocalEvent<DrinkComponent, GetVerbsEvent<AlternativeVerb>>(AddDrinkVerb);
SubscribeLocalEvent<DrinkComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<DrinkComponent, SolutionTransferAttemptEvent>(OnTransferAttempt);
- SubscribeLocalEvent<BodyComponent, DrinkEvent>(OnDrink);
- SubscribeLocalEvent<DrinkCancelledEvent>(OnDrinkCancelled);
+ SubscribeLocalEvent<DrinkComponent, DoAfterEvent<DrinkData>>(OnDoAfter);
}
public bool IsEmpty(EntityUid uid, DrinkComponent? component = null)
if (args.Handled || args.Target == null || !args.CanReach)
return;
- args.Handled = TryDrink(args.User, args.Target.Value, component);
+ args.Handled = TryDrink(args.User, args.Target.Value, component, uid);
}
private void OnUse(EntityUid uid, DrinkComponent component, UseInHandEvent args)
{
- if (args.Handled) return;
+ if (args.Handled)
+ return;
if (!component.Opened)
{
return;
}
- args.Handled = TryDrink(args.User, args.User, component);
+ args.Handled = TryDrink(args.User, args.User, component, uid);
}
private void HandleLand(EntityUid uid, DrinkComponent component, ref LandEvent args)
}
}
- private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink)
+ private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item)
{
- // cannot stack do-afters
- if (drink.CancelToken != null)
- {
- return true;
- }
-
if (!EntityManager.HasComponent<BodyComponent>(target))
return false;
if (!drink.Opened)
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-not-open",
- ("owner", EntityManager.GetComponent<MetaDataComponent>(drink.Owner).EntityName)), drink.Owner, user);
+ ("owner", EntityManager.GetComponent<MetaDataComponent>(item).EntityName)), item, user);
return true;
}
- if (!_solutionContainerSystem.TryGetDrainableSolution(drink.Owner, out var drinkSolution) ||
+ if (!_solutionContainerSystem.TryGetDrainableSolution(item, out var drinkSolution) ||
drinkSolution.Volume <= 0)
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-is-empty",
- ("entity", EntityManager.GetComponent<MetaDataComponent>(drink.Owner).EntityName)), drink.Owner, user);
+ ("entity", EntityManager.GetComponent<MetaDataComponent>(item).EntityName)), item, user);
return true;
}
if (_foodSystem.IsMouthBlocked(target, user))
return true;
- if (!_interactionSystem.InRangeUnobstructed(user, drink.Owner, popup: true))
+ if (!_interactionSystem.InRangeUnobstructed(user, item, popup: true))
return true;
var forceDrink = user != target;
user, target);
// logging
- _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to drink {ToPrettyString(drink.Owner):drink} {SolutionContainerSystem.ToPrettyString(drinkSolution)}");
+ _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to drink {ToPrettyString(item):drink} {SolutionContainerSystem.ToPrettyString(drinkSolution)}");
}
else
{
// log voluntary drinking
- _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is drinking {ToPrettyString(drink.Owner):drink} {SolutionContainerSystem.ToPrettyString(drinkSolution)}");
+ _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is drinking {ToPrettyString(item):drink} {SolutionContainerSystem.ToPrettyString(drinkSolution)}");
}
-
- drink.CancelToken = new CancellationTokenSource();
var moveBreak = user != target;
var flavors = _flavorProfileSystem.GetLocalizedFlavorsMessage(user, drinkSolution);
- _doAfterSystem.DoAfter(new DoAfterEventArgs(user, forceDrink ? drink.ForceFeedDelay : drink.Delay, drink.CancelToken.Token, target, drink.Owner)
+ var drinkData = new DrinkData(drinkSolution, flavors);
+
+ var doAfterEventArgs = new DoAfterEventArgs(user, forceDrink ? drink.ForceFeedDelay : drink.Delay,
+ target: target, used: item)
{
BreakOnUserMove = moveBreak,
BreakOnDamage = true,
BreakOnTargetMove = moveBreak,
MovementThreshold = 0.01f,
DistanceThreshold = 1.0f,
- TargetFinishedEvent = new DrinkEvent(user, drink, drinkSolution, flavors),
- BroadcastCancelledEvent = new DrinkCancelledEvent(drink),
- NeedHand = true,
- });
+ NeedHand = true
+ };
+
+ _doAfterSystem.DoAfter(doAfterEventArgs, drinkData);
return true;
}
/// <summary>
/// Raised directed at a victim when someone has force fed them a drink.
/// </summary>
- private void OnDrink(EntityUid uid, BodyComponent body, DrinkEvent args)
+ private void OnDoAfter(EntityUid uid, DrinkComponent component, DoAfterEvent<DrinkData> args)
{
- if (args.Drink.Deleted)
+ if (args.Handled || args.Cancelled || component.Deleted)
+ return;
+
+ if (!TryComp<BodyComponent>(args.Args.Target, out var body))
return;
- args.Drink.CancelToken = null;
- var transferAmount = FixedPoint2.Min(args.Drink.TransferAmount, args.DrinkSolution.Volume);
- var drained = _solutionContainerSystem.Drain(args.Drink.Owner, args.DrinkSolution, transferAmount);
+ var transferAmount = FixedPoint2.Min(component.TransferAmount, args.AdditionalData.DrinkSolution.Volume);
+ var drained = _solutionContainerSystem.Drain(uid, args.AdditionalData.DrinkSolution, transferAmount);
- var forceDrink = uid != args.User;
+ var forceDrink = args.Args.Target.Value != args.Args.User;
- if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(uid, out var stomachs, body))
+ 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"),
- uid, args.User);
+ _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);
- if (EntityManager.HasComponent<RefillableSolutionComponent>(uid))
+ if (HasComp<RefillableSolutionComponent>(args.Args.Target.Value))
{
- _spillableSystem.SpillAt(args.User, drained, "PuddleSmear");
+ _spillableSystem.SpillAt(args.Args.User, drained, "PuddleSmear");
+ args.Handled = true;
return;
}
- _solutionContainerSystem.Refill(uid, args.DrinkSolution, drained);
+ _solutionContainerSystem.Refill(args.Args.Target.Value, args.AdditionalData.DrinkSolution, drained);
+ args.Handled = true;
return;
}
- var firstStomach = stomachs.FirstOrNull(
- stomach => _stomachSystem.CanTransferSolution((stomach.Comp).Owner, drained));
+ var firstStomach = stomachs.FirstOrNull(stomach => _stomachSystem.CanTransferSolution(stomach.Comp.Owner, drained));
- // All stomach are full or can't handle whatever solution we have.
+ //All stomachs are full or can't handle whatever solution we have.
if (firstStomach == null)
{
- _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough"),
- uid, uid);
+ _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough"), args.Args.Target.Value, args.Args.Target.Value);
if (forceDrink)
{
- _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough-other"),
- uid, args.User);
- _spillableSystem.SpillAt(uid, drained, "PuddleSmear");
+ _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");
}
else
- {
- _solutionContainerSystem.TryAddSolution(args.Drink.Owner, args.DrinkSolution, drained);
- }
+ _solutionContainerSystem.TryAddSolution(uid, args.AdditionalData.DrinkSolution, drained);
+ args.Handled = true;
return;
}
- var flavors = args.FlavorMessage;
+ var flavors = args.AdditionalData.FlavorMessage;
if (forceDrink)
{
- var targetName = Identity.Entity(uid, EntityManager);
- var userName = Identity.Entity(args.User, EntityManager);
+ var targetName = Identity.Entity(args.Args.Target.Value, EntityManager);
+ var userName = Identity.Entity(args.Args.User, EntityManager);
- _popupSystem.PopupEntity(
- Loc.GetString("drink-component-force-feed-success", ("user", userName), ("flavors", flavors)), uid, uid);
+ _popupSystem.PopupEntity(Loc.GetString("drink-component-force-feed-success", ("user", userName), ("flavors", flavors)), args.Args.Target.Value, args.Args.Target.Value);
_popupSystem.PopupEntity(
Loc.GetString("drink-component-force-feed-success-user", ("target", targetName)),
- args.User, args.User);
+ args.Args.User, args.Args.User);
// log successful forced drinking
- _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(args.User):user} forced {ToPrettyString(uid):target} to drink {ToPrettyString(args.Drink.Owner):drink}");
+ _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(uid):user} forced {ToPrettyString(args.Args.User):target} to drink {ToPrettyString(component.Owner):drink}");
}
else
{
_popupSystem.PopupEntity(
- Loc.GetString("drink-component-try-use-drink-success-slurp-taste", ("flavors", flavors)), args.User,
- args.User);
+ Loc.GetString("drink-component-try-use-drink-success-slurp-taste", ("flavors", flavors)), args.Args.User,
+ args.Args.User);
_popupSystem.PopupEntity(
- Loc.GetString("drink-component-try-use-drink-success-slurp"), args.User, Filter.PvsExcept(args.User), true);
+ Loc.GetString("drink-component-try-use-drink-success-slurp"), args.Args.User, Filter.PvsExcept(args.Args.User), true);
// log successful voluntary drinking
- _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.User):target} drank {ToPrettyString(args.Drink.Owner):drink}");
+ _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.Args.User):target} drank {ToPrettyString(uid):drink}");
}
- _audio.PlayPvs(_audio.GetSound(args.Drink.UseSound), uid, AudioParams.Default.WithVolume(-2f));
+ _audio.PlayPvs(_audio.GetSound(component.UseSound), args.Args.Target.Value, AudioParams.Default.WithVolume(-2f));
- drained.DoEntityReaction(uid, ReactionMethod.Ingestion);
+ _reaction.DoEntityReaction(args.Args.Target.Value, args.AdditionalData.DrinkSolution, ReactionMethod.Ingestion);
+ //TODO: Grab the stomach UIDs somehow without using Owner
_stomachSystem.TryTransferSolution(firstStomach.Value.Comp.Owner, drained, firstStomach.Value.Comp);
- }
- private static void OnDrinkCancelled(DrinkCancelledEvent args)
- {
- args.Drink.CancelToken = null;
+ args.Handled = true;
}
private void AddDrinkVerb(EntityUid uid, DrinkComponent component, GetVerbsEvent<AlternativeVerb> ev)
{
- if (component.CancelToken != null)
- return;
-
if (uid == ev.User ||
!ev.CanInteract ||
!ev.CanAccess ||
{
Act = () =>
{
- TryDrink(ev.User, ev.User, component);
+ TryDrink(ev.User, ev.User, component, uid);
},
IconTexture = "/Textures/Interface/VerbIcons/drink.svg.192dpi.png",
Text = Loc.GetString("drink-system-verb-drink"),
return remainingString;
}
+
+ private record struct DrinkData(Solution DrinkSolution, string FlavorMessage)
+ {
+ public readonly Solution DrinkSolution = DrinkSolution;
+ public readonly string FlavorMessage = FlavorMessage;
+ }
}
}
-using System.Threading;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Popups;
using Content.Shared.Administration.Logs;
using Content.Shared.Body.Components;
+using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
+ [Dependency] private readonly ReactiveSystem _reaction = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
SubscribeLocalEvent<FoodComponent, UseInHandEvent>(OnUseFoodInHand);
SubscribeLocalEvent<FoodComponent, AfterInteractEvent>(OnFeedFood);
SubscribeLocalEvent<FoodComponent, GetVerbsEvent<AlternativeVerb>>(AddEatVerb);
- SubscribeLocalEvent<BodyComponent, FeedEvent>(OnFeed);
- SubscribeLocalEvent<ForceFeedCancelledEvent>(OnFeedCancelled);
+ SubscribeLocalEvent<FoodComponent, DoAfterEvent<FoodData>>(OnDoAfter);
SubscribeLocalEvent<InventoryComponent, IngestionAttemptEvent>(OnInventoryIngestAttempt);
}
if (ev.Handled)
return;
- ev.Handled = TryFeed(ev.User, ev.User, foodComponent);
+ ev.Handled = TryFeed(ev.User, ev.User, uid, foodComponent);
}
/// <summary>
if (args.Handled || args.Target == null || !args.CanReach)
return;
- args.Handled = TryFeed(args.User, args.Target.Value, foodComponent);
+ args.Handled = TryFeed(args.User, args.Target.Value, uid, foodComponent);
}
- public bool TryFeed(EntityUid user, EntityUid target, FoodComponent food)
+ public bool TryFeed(EntityUid user, EntityUid target, EntityUid food, FoodComponent foodComp)
{
- // if currently being used to feed, cancel that action.
- if (food.CancelToken != null)
- {
- return true;
- }
-
- if (food.Owner == user || //Suppresses self-eating
- EntityManager.TryGetComponent<MobStateComponent>(food.Owner, out var mobState) && _mobStateSystem.IsAlive(food.Owner, mobState)) // Suppresses eating alive mobs
+ //Suppresses self-eating
+ 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))
return false;
- if (!_solutionContainerSystem.TryGetSolution(food.Owner, food.SolutionName, out var foodSolution))
+ if (!_solutionContainerSystem.TryGetSolution(food, foodComp.SolutionName, out var foodSolution))
return false;
- var flavors = _flavorProfileSystem.GetLocalizedFlavorsMessage(food.Owner, user, foodSolution);
+ var flavors = _flavorProfileSystem.GetLocalizedFlavorsMessage(food, user, foodSolution);
- if (food.UsesRemaining <= 0)
+ if (foodComp.UsesRemaining <= 0)
{
- _popupSystem.PopupEntity(Loc.GetString("food-system-try-use-food-is-empty",
- ("entity", food.Owner)), user, user);
- DeleteAndSpawnTrash(food, user);
+ _popupSystem.PopupEntity(Loc.GetString("food-system-try-use-food-is-empty", ("entity", food)), user, user);
+ DeleteAndSpawnTrash(foodComp, food, user);
return false;
}
if (IsMouthBlocked(target, user))
return false;
- if (!TryGetRequiredUtensils(user, food, out var utensils))
+ if (!TryGetRequiredUtensils(user, foodComp, out var utensils))
return false;
- if (!_interactionSystem.InRangeUnobstructed(user, food.Owner, popup: true))
+ if (!_interactionSystem.InRangeUnobstructed(user, food, popup: true))
return true;
var forceFeed = user != target;
- food.CancelToken = new CancellationTokenSource();
if (forceFeed)
{
user, target);
// logging
- _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to eat {ToPrettyString(food.Owner):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}");
+ _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to eat {ToPrettyString(food):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}");
}
else
{
// log voluntary eating
- _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is eating {ToPrettyString(food.Owner):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}");
+ _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is eating {ToPrettyString(food):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}");
}
var moveBreak = user != target;
- _doAfterSystem.DoAfter(new DoAfterEventArgs(user, forceFeed ? food.ForceFeedDelay : food.Delay, food.CancelToken.Token, target, food.Owner)
+ var foodData = new FoodData(foodSolution, flavors, utensils);
+
+ var doAfterEventArgs = new DoAfterEventArgs(user, forceFeed ? foodComp.ForceFeedDelay : foodComp.Delay, target: target, used: food)
{
BreakOnUserMove = moveBreak,
BreakOnDamage = true,
BreakOnTargetMove = moveBreak,
MovementThreshold = 0.01f,
DistanceThreshold = 1.0f,
- TargetFinishedEvent = new FeedEvent(user, food, foodSolution, flavors, utensils),
- BroadcastCancelledEvent = new ForceFeedCancelledEvent(food),
- NeedHand = true,
- });
+ NeedHand = true
+ };
+
+ _doAfterSystem.DoAfter(doAfterEventArgs, foodData);
return true;
}
- private void OnFeed(EntityUid uid, BodyComponent body, FeedEvent args)
+ private void OnDoAfter(EntityUid uid, FoodComponent component, DoAfterEvent<FoodData> args)
{
- if (args.Food.Deleted)
+ if (args.Cancelled || args.Handled || component.Deleted || args.Args.Target == null)
return;
- args.Food.CancelToken = null;
-
- if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(uid, out var stomachs, body))
+ if (!TryComp<BodyComponent>(args.Args.Target.Value, out var body))
return;
- var transferAmount = args.Food.TransferAmount != null
- ? FixedPoint2.Min((FixedPoint2) args.Food.TransferAmount, args.FoodSolution.Volume)
- : args.FoodSolution.Volume;
-
- var split = _solutionContainerSystem.SplitSolution((args.Food).Owner, args.FoodSolution, transferAmount);
+ if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(args.Args.Target.Value, out var stomachs, body))
+ return;
+ var transferAmount = component.TransferAmount != null ? FixedPoint2.Min((FixedPoint2) component.TransferAmount, args.AdditionalData.FoodSolution.Volume) : args.AdditionalData.FoodSolution.Volume;
- var firstStomach = stomachs.FirstOrNull(
- stomach => _stomachSystem.CanTransferSolution((stomach.Comp).Owner, split));
+ var split = _solutionContainerSystem.SplitSolution(uid, args.AdditionalData.FoodSolution, transferAmount);
+ //TODO: Get the stomach UID somehow without nabbing owner
+ var firstStomach = stomachs.FirstOrNull(stomach => _stomachSystem.CanTransferSolution(stomach.Comp.Owner, split));
- var forceFeed = uid != args.User;
+ 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.FoodSolution, split);
- _popupSystem.PopupEntity(
- forceFeed ?
- Loc.GetString("food-system-you-cannot-eat-any-more-other") :
- Loc.GetString("food-system-you-cannot-eat-any-more")
- , uid, args.User);
+ _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);
+ args.Handled = true;
return;
}
- split.DoEntityReaction(uid, ReactionMethod.Ingestion);
+ _reaction.DoEntityReaction(args.Args.Target.Value, args.AdditionalData.FoodSolution, ReactionMethod.Ingestion);
_stomachSystem.TryTransferSolution(firstStomach.Value.Comp.Owner, split, firstStomach.Value.Comp);
- var flavors = args.FlavorMessage;
+ var flavors = args.AdditionalData.FlavorMessage;
if (forceFeed)
{
- var targetName = Identity.Entity(uid, EntityManager);
- var userName = Identity.Entity(args.User, EntityManager);
+ var targetName = Identity.Entity(args.Args.Target.Value, EntityManager);
+ var userName = Identity.Entity(args.Args.User, EntityManager);
_popupSystem.PopupEntity(Loc.GetString("food-system-force-feed-success", ("user", userName), ("flavors", flavors)),
uid, uid);
- _popupSystem.PopupEntity(Loc.GetString("food-system-force-feed-success-user", ("target", targetName)),
- args.User, args.User);
+ _popupSystem.PopupEntity(Loc.GetString("food-system-force-feed-success-user", ("target", targetName)), args.Args.User, args.Args.User);
// log successful force feed
- _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(args.User):user} forced {ToPrettyString(uid):target} to eat {ToPrettyString(args.Food.Owner):food}");
+ _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(uid):user} forced {ToPrettyString(args.Args.User):target} to eat {ToPrettyString(uid):food}");
}
else
{
- _popupSystem.PopupEntity(Loc.GetString(args.Food.EatMessage, ("food", args.Food.Owner), ("flavors", flavors)), args.User, args.User);
+ _popupSystem.PopupEntity(Loc.GetString(component.EatMessage, ("foodComp", uid), ("flavors", flavors)), args.Args.User, args.Args.User);
// log successful voluntary eating
- _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.User):target} ate {ToPrettyString(args.Food.Owner):food}");
+ _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.Args.User):target} ate {ToPrettyString(uid):food}");
}
- SoundSystem.Play(args.Food.UseSound.GetSound(), Filter.Pvs(uid), uid, AudioParams.Default.WithVolume(-1f));
+ _audio.Play(component.UseSound, Filter.Pvs(args.Args.Target.Value), args.Args.Target.Value, true, AudioParams.Default.WithVolume(-1f));
// Try to break all used utensils
- foreach (var utensil in args.Utensils)
+ //TODO: Replace utensil owner with actual UID
+ foreach (var utensil in args.AdditionalData.Utensils)
{
- _utensilSystem.TryBreak((utensil).Owner, args.User);
+ _utensilSystem.TryBreak(utensil.Owner, args.Args.User);
}
- if (args.Food.UsesRemaining > 0)
+ if (component.UsesRemaining > 0)
+ {
+ args.Handled = true;
return;
+ }
+
+
+ if (string.IsNullOrEmpty(component.TrashPrototype))
+ EntityManager.QueueDeleteEntity(uid);
- if (string.IsNullOrEmpty(args.Food.TrashPrototype))
- EntityManager.QueueDeleteEntity(args.Food.Owner);
else
- DeleteAndSpawnTrash(args.Food, args.User);
+ DeleteAndSpawnTrash(component, uid, args.Args.User);
+
+ args.Handled = true;
}
- private void DeleteAndSpawnTrash(FoodComponent component, EntityUid? user = null)
+ private void DeleteAndSpawnTrash(FoodComponent component, EntityUid food, EntityUid? user = null)
{
//We're empty. Become trash.
- var position = Transform(component.Owner).MapPosition;
+ var position = Transform(food).MapPosition;
var finisher = EntityManager.SpawnEntity(component.TrashPrototype, position);
// If the user is holding the item
- if (user != null && _handsSystem.IsHolding(user.Value, component.Owner, out var hand))
+ if (user != null && _handsSystem.IsHolding(user.Value, food, out var hand))
{
- EntityManager.DeleteEntity((component).Owner);
+ EntityManager.DeleteEntity(food);
// Put the trash in the user's hand
_handsSystem.TryPickup(user.Value, finisher, hand);
return;
}
- EntityManager.QueueDeleteEntity(component.Owner);
+ EntityManager.QueueDeleteEntity(food);
}
private void AddEatVerb(EntityUid uid, FoodComponent component, GetVerbsEvent<AlternativeVerb> ev)
{
- if (component.CancelToken != null)
- return;
-
if (uid == ev.User ||
!ev.CanInteract ||
!ev.CanAccess ||
{
Act = () =>
{
- TryFeed(ev.User, ev.User, component);
+ TryFeed(ev.User, ev.User, uid, component);
},
IconTexture = "/Textures/Interface/VerbIcons/cutlery.svg.192dpi.png",
Text = Loc.GetString("food-system-verb-eat"),
return;
if (food.UsesRemaining <= 0)
- DeleteAndSpawnTrash(food);
+ DeleteAndSpawnTrash(food, uid);
var firstStomach = stomachs.FirstOrNull(
stomach => _stomachSystem.CanTransferSolution(((IComponent) stomach.Comp).Owner, foodSolution));
if (string.IsNullOrEmpty(food.TrashPrototype))
EntityManager.QueueDeleteEntity(food.Owner);
else
- DeleteAndSpawnTrash(food);
+ DeleteAndSpawnTrash(food, uid);
}
private bool TryGetRequiredUtensils(EntityUid user, FoodComponent component,
return true;
}
- private static void OnFeedCancelled(ForceFeedCancelledEvent args)
- {
- args.Food.CancelToken = null;
- }
-
/// <summary>
/// Block ingestion attempts based on the equipped mask or head-wear
/// </summary>
return attempt.Cancelled;
}
+
+ private record struct FoodData(Solution FoodSolution, string FlavorMessage, List<UtensilComponent> Utensils)
+ {
+ public readonly Solution FoodSolution = FoodSolution;
+ public readonly string FlavorMessage = FlavorMessage;
+ public readonly List<UtensilComponent> Utensils = Utensils;
+ }
}
}
//Prevents food usage with a wrong utensil
if ((food.Utensil & component.Types) == 0)
{
- _popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", food.Owner), ("utensil", component.Owner)), user, user);
+ _popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", target), ("utensil", component.Owner)), user, user);
return false;
}
if (!_interactionSystem.InRangeUnobstructed(user, target, popup: true))
return false;
- return _foodSystem.TryFeed(user, user, food);
+ return _foodSystem.TryFeed(user, user, target, food);
}
/// <summary>
-using Content.Server.Nutrition.Components;
-using Content.Shared.Chemistry.Components;
-
namespace Content.Server.Nutrition;
/// <summary>
/// </summary>
public EntityUid? Blocker = null;
}
-
-/// <summary>
-/// Raised directed at the food after a successful feed do-after.
-/// </summary>
-public sealed class FeedEvent : EntityEventArgs
-{
- public readonly EntityUid User;
- public readonly FoodComponent Food;
- public readonly Solution FoodSolution;
- public readonly string FlavorMessage;
- public readonly List<UtensilComponent> Utensils;
-
- public FeedEvent(EntityUid user, FoodComponent food, Solution foodSolution, string flavorMessage, List<UtensilComponent> utensils)
- {
- User = user;
- Food = food;
- FoodSolution = foodSolution;
- FlavorMessage = flavorMessage;
- Utensils = utensils;
- }
-}
-
-/// <summary>
-/// Raised directed at the food after a failed force-feed do-after.
-/// </summary>
-public sealed class ForceFeedCancelledEvent : EntityEventArgs
-{
- public readonly FoodComponent Food;
-
- public ForceFeedCancelledEvent(FoodComponent food)
- {
- Food = food;
- }
-}
-
-/// <summary>
-/// Raised directed at the drink after a successful force-drink do-after.
-/// </summary>
-public sealed class DrinkEvent : EntityEventArgs
-{
- public readonly EntityUid User;
- public readonly DrinkComponent Drink;
- public readonly Solution DrinkSolution;
- public readonly string FlavorMessage;
-
- public DrinkEvent(EntityUid user, DrinkComponent drink, Solution drinkSolution, string flavorMessage)
- {
- User = user;
- Drink = drink;
- DrinkSolution = drinkSolution;
- FlavorMessage = flavorMessage;
- }
-}
-
-/// <summary>
-/// Raised directed at the food after a failed force-dink do-after.
-/// </summary>
-public sealed class DrinkCancelledEvent : EntityEventArgs
-{
- public readonly DrinkComponent Drink;
-
- public DrinkCancelledEvent(DrinkComponent drink)
- {
- Drink = drink;
- }
-}
using Content.Server.Popups;
using Content.Server.Power.Components;
-using Content.Server.Tools;
-using Content.Server.Wires;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.APC;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Popups;
+using Content.Shared.Tools;
using Content.Shared.Tools.Components;
-using Content.Shared.Wires;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly ToolSystem _toolSystem = default!;
+ [Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
private const float ScrewTime = 2f;
{
if (!EntityManager.TryGetComponent(args.Used, out ToolComponent? tool))
return;
- if (_toolSystem.UseTool(args.Used, args.User, uid, 0f, ScrewTime, new[] { "Screwing" }, doAfterCompleteEvent: new ApcToolFinishedEvent(uid), toolComponent: tool))
- {
+
+ var toolEvData = new ToolEventData(new ApcToolFinishedEvent(uid), fuel: 0f);
+
+ if (_toolSystem.UseTool(args.Used, args.User, uid, ScrewTime, new [] { "Screwing" }, toolEvData, toolComponent:tool))
args.Handled = true;
- }
}
private void OnToolFinished(ApcToolFinishedEvent args)
}
if (component.IsApcOpen)
- {
SoundSystem.Play(component.ScrewdriverOpenSound.GetSound(), Filter.Pvs(args.Target), args.Target);
- }
else
- {
SoundSystem.Play(component.ScrewdriverCloseSound.GetSound(), Filter.Pvs(args.Target), args.Target);
- }
}
private void UpdatePanelAppearance(EntityUid uid, AppearanceComponent? appearance = null, ApcComponent? apc = null)
using Content.Server.Electrocution;
using Content.Server.Power.Components;
using Content.Server.Stack;
-using Content.Server.Tools;
using Content.Shared.Database;
using Content.Shared.Interaction;
+using Content.Shared.Tools;
+using Content.Shared.Tools.Components;
using Robust.Shared.Map;
namespace Content.Server.Power.EntitySystems;
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileManager = default!;
- [Dependency] private readonly ToolSystem _toolSystem = default!;
+ [Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!;
[Dependency] private readonly IAdminLogManager _adminLogs = default!;
if (args.Handled)
return;
- var ev = new CuttingFinishedEvent(args.User);
- args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, 0, cable.CuttingDelay, new[] { cable.CuttingQuality }, doAfterCompleteEvent: ev, doAfterEventTarget: uid);
+ var toolEvData = new ToolEventData(new CuttingFinishedEvent(args.User), targetEntity: uid);
+ args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, cable.CuttingDelay, new[] { cable.CuttingQuality }, toolEvData);
}
private void OnCableCut(EntityUid uid, CableComponent cable, CuttingFinishedEvent args)
using Content.Server.Popups;
using Content.Server.RCD.Components;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Database;
using Content.Shared.Interaction;
using Content.Shared.Popups;
+using Content.Shared.Tools;
+using Content.Shared.Tools.Components;
namespace Content.Server.Repairable
{
public sealed class RepairableSystem : EntitySystem
{
- [Dependency] private readonly ToolSystem _toolSystem = default!;
+ [Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly IAdminLogManager _adminLogger= default!;
if (args.User == args.Target)
delay *= component.SelfRepairPenalty;
+ var toolEvData = new ToolEventData(null);
+
// Can the tool actually repair this, does it have enough fuel?
- if (!await _toolSystem.UseTool(args.Used, args.User, uid, component.FuelCost, delay, component.QualityNeeded))
+ if (!_toolSystem.UseTool(args.Used, args.User, uid, delay, component.QualityNeeded, toolEvData, component.FuelCost))
return;
if (component.Damage != null)
-using System.Threading;
-
namespace Content.Server.Resist;
[RegisterComponent]
/// </summary>
[DataField("baseResistTime")]
public float BaseResistTime = 5f;
-
- /// <summary>
- /// Cancellation token used to cancel the DoAfter if the mob is removed before it's complete
- /// </summary>
- public CancellationTokenSource? CancelToken;
}
using Content.Server.Contests;
using Robust.Shared.Containers;
using Content.Server.Popups;
-using Robust.Shared.Player;
using Content.Shared.Storage;
using Content.Shared.Inventory;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.ActionBlocker;
+using Content.Shared.DoAfter;
using Content.Shared.Movement.Events;
using Content.Shared.Interaction.Events;
base.Initialize();
SubscribeLocalEvent<CanEscapeInventoryComponent, MoveInputEvent>(OnRelayMovement);
- SubscribeLocalEvent<CanEscapeInventoryComponent, EscapeDoAfterComplete>(OnEscapeComplete);
- SubscribeLocalEvent<CanEscapeInventoryComponent, EscapeDoAfterCancel>(OnEscapeFail);
+ SubscribeLocalEvent<CanEscapeInventoryComponent, DoAfterEvent<EscapeInventoryEvent>>(OnEscape);
SubscribeLocalEvent<CanEscapeInventoryComponent, DroppedEvent>(OnDropped);
}
private void OnRelayMovement(EntityUid uid, CanEscapeInventoryComponent component, ref MoveInputEvent args)
{
- if (component.CancelToken != null)
- return;
-
if (!_containerSystem.TryGetContainingContainer(uid, out var container) || !_actionBlockerSystem.CanInteract(uid, container.Owner))
return;
private void AttemptEscape(EntityUid user, EntityUid container, CanEscapeInventoryComponent component, float multiplier = 1f)
{
- component.CancelToken = new();
- var doAfterEventArgs = new DoAfterEventArgs(user, component.BaseResistTime * multiplier, component.CancelToken.Token, container)
+ var escapeEvent = new EscapeInventoryEvent();
+ var doAfterEventArgs = new DoAfterEventArgs(user, component.BaseResistTime * multiplier, target:container)
{
BreakOnTargetMove = false,
BreakOnUserMove = false,
BreakOnDamage = true,
BreakOnStun = true,
- NeedHand = false,
- UserFinishedEvent = new EscapeDoAfterComplete(),
- UserCancelledEvent = new EscapeDoAfterCancel(),
+ NeedHand = false
};
_popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting"), user, user);
_popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting-target"), container, container);
- _doAfterSystem.DoAfter(doAfterEventArgs);
+ _doAfterSystem.DoAfter(doAfterEventArgs, escapeEvent);
}
- private void OnEscapeComplete(EntityUid uid, CanEscapeInventoryComponent component, EscapeDoAfterComplete ev)
+ private void OnEscape(EntityUid uid, CanEscapeInventoryComponent component, DoAfterEvent<EscapeInventoryEvent> args)
{
- //Drops the mob on the tile below the container
+ if (args.Handled || args.Cancelled)
+ return;
+
Transform(uid).AttachParentToContainerOrGrid(EntityManager);
- component.CancelToken = null;
- }
- private void OnEscapeFail(EntityUid uid, CanEscapeInventoryComponent component, EscapeDoAfterCancel ev)
- {
- component.CancelToken = null;
+ args.Handled = true;
}
private void OnDropped(EntityUid uid, CanEscapeInventoryComponent component, DroppedEvent args)
{
- component.CancelToken?.Cancel();
+ //TODO: Enter cancel logic here
}
- private sealed class EscapeDoAfterComplete : EntityEventArgs { }
+ private sealed class EscapeInventoryEvent : EntityEventArgs
+ {
- private sealed class EscapeDoAfterCancel : EntityEventArgs { }
+ }
}
public bool IsResisting = false;
/// <summary>
- /// Cancellation token used to cancel the DoAfter if the container is opened before it's complete
+ /// Used to cancel the DoAfter when a locker is open
/// </summary>
- public CancellationTokenSource? CancelToken;
+ public Shared.DoAfter.DoAfter? DoAfter;
}
-using Content.Server.Storage.Components;
using Content.Server.DoAfter;
-using Robust.Shared.Containers;
using Content.Server.Popups;
-using Content.Shared.Movement.Events;
+using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
+using Content.Shared.DoAfter;
using Content.Shared.Lock;
+using Content.Shared.Movement.Events;
using Content.Shared.Popups;
+using Robust.Shared.Containers;
namespace Content.Server.Resist;
{
base.Initialize();
SubscribeLocalEvent<ResistLockerComponent, ContainerRelayMovementEntityEvent>(OnRelayMovement);
- SubscribeLocalEvent<ResistLockerComponent, ResistDoAfterComplete>(OnDoAfterComplete);
- SubscribeLocalEvent<ResistLockerComponent, ResistDoAfterCancelled>(OnDoAfterCancelled);
- SubscribeLocalEvent<ResistLockerComponent, EntRemovedFromContainerMessage>(OnRemovedFromContainer);
+ SubscribeLocalEvent<ResistLockerComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<ResistLockerComponent, EntRemovedFromContainerMessage>(OnRemoved);
}
private void OnRelayMovement(EntityUid uid, ResistLockerComponent component, ref ContainerRelayMovementEntityEvent args)
if (!Resolve(target, ref storageComponent, ref resistLockerComponent))
return;
- resistLockerComponent.CancelToken = new();
- var doAfterEventArgs = new DoAfterEventArgs(user, resistLockerComponent.ResistTime, resistLockerComponent.CancelToken.Token, target)
+ var doAfterEventArgs = new DoAfterEventArgs(user, resistLockerComponent.ResistTime, target:target)
{
BreakOnTargetMove = false,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
- NeedHand = false, //No hands 'cause we be kickin'
- TargetFinishedEvent = new ResistDoAfterComplete(user, target),
- TargetCancelledEvent = new ResistDoAfterCancelled(user)
+ NeedHand = false //No hands 'cause we be kickin'
};
resistLockerComponent.IsResisting = true;
_popupSystem.PopupEntity(Loc.GetString("resist-locker-component-start-resisting"), user, user, PopupType.Large);
- _doAfterSystem.DoAfter(doAfterEventArgs);
+ resistLockerComponent.DoAfter = _doAfterSystem.DoAfter(doAfterEventArgs);
}
- private void OnDoAfterComplete(EntityUid uid, ResistLockerComponent component, ResistDoAfterComplete ev)
+ private void OnRemoved(EntityUid uid, ResistLockerComponent component, EntRemovedFromContainerMessage args)
{
- component.IsResisting = false;
+ if (component.DoAfter != null)
+ _doAfterSystem.Cancel(uid, component.DoAfter);
+ }
- if (TryComp<EntityStorageComponent>(uid, out var storageComponent))
+ private void OnDoAfter(EntityUid uid, ResistLockerComponent component, DoAfterEvent args)
+ {
+ if (args.Cancelled)
{
- if (storageComponent.IsWeldedShut)
- storageComponent.IsWeldedShut = false;
-
- if (TryComp<LockComponent>(ev.Target, out var lockComponent))
- _lockSystem.Unlock(uid, ev.User, lockComponent);
-
- component.CancelToken = null;
- _entityStorage.TryOpenStorage(ev.User, storageComponent.Owner);
+ component.IsResisting = false;
+ _popupSystem.PopupEntity(Loc.GetString("resist-locker-component-resist-interrupted"), args.Args.User, args.Args.User, PopupType.Medium);
+ return;
}
- }
- private void OnDoAfterCancelled(EntityUid uid, ResistLockerComponent component, ResistDoAfterCancelled ev)
- {
- component.IsResisting = false;
- component.CancelToken = null;
- _popupSystem.PopupEntity(Loc.GetString("resist-locker-component-resist-interrupted"), ev.User, ev.User, PopupType.Medium);
- }
+ if (args.Handled || args.Args.Target == null)
+ return;
- private void OnRemovedFromContainer(EntityUid uid, ResistLockerComponent component, EntRemovedFromContainerMessage message)
- {
- component.CancelToken?.Cancel();
- }
+ component.IsResisting = false;
- private sealed class ResistDoAfterComplete : EntityEventArgs
- {
- public readonly EntityUid User;
- public readonly EntityUid Target;
- public ResistDoAfterComplete(EntityUid userUid, EntityUid target)
+ if (TryComp<EntityStorageComponent>(uid, out var storageComponent))
{
- User = userUid;
- Target = target;
- }
- }
+ if (storageComponent.IsWeldedShut)
+ storageComponent.IsWeldedShut = false;
- private sealed class ResistDoAfterCancelled : EntityEventArgs
- {
- public readonly EntityUid User;
+ if (TryComp<LockComponent>(args.Args.Target.Value, out var lockComponent))
+ _lockSystem.Unlock(uid, args.Args.User, lockComponent);
- public ResistDoAfterCancelled(EntityUid userUid)
- {
- User = userUid;
+ _entityStorage.TryOpenStorage(args.Args.User, uid);
}
+
+ args.Handled = true;
}
}
using Content.Shared.Popups;
using Content.Shared.Damage;
-using Content.Server.DoAfter;
using Content.Shared.Revenant;
using Robust.Shared.Random;
using Robust.Shared.Map;
using System.Linq;
using Content.Server.Maps;
using Content.Server.Revenant.Components;
+using Content.Shared.DoAfter;
using Content.Shared.Emag.Systems;
using Content.Shared.FixedPoint;
using Content.Shared.Humanoid;
private void InitializeAbilities()
{
SubscribeLocalEvent<RevenantComponent, InteractNoHandEvent>(OnInteract);
- SubscribeLocalEvent<RevenantComponent, SoulSearchDoAfterComplete>(OnSoulSearchComplete);
- SubscribeLocalEvent<RevenantComponent, SoulSearchDoAfterCancelled>(OnSoulSearchCancelled);
- SubscribeLocalEvent<RevenantComponent, HarvestDoAfterComplete>(OnHarvestComplete);
- SubscribeLocalEvent<RevenantComponent, HarvestDoAfterCancelled>(OnHarvestCancelled);
+ SubscribeLocalEvent<RevenantComponent, DoAfterEvent<SoulEvent>>(OnSoulSearch);
+ SubscribeLocalEvent<RevenantComponent, DoAfterEvent<HarvestEvent>>(OnHarvest);
SubscribeLocalEvent<RevenantComponent, RevenantDefileActionEvent>(OnDefileAction);
SubscribeLocalEvent<RevenantComponent, RevenantOverloadLightsActionEvent>(OnOverloadLightsAction);
private void BeginSoulSearchDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant)
{
- if (revenant.SoulSearchCancelToken != null)
- return;
-
_popup.PopupEntity(Loc.GetString("revenant-soul-searching", ("target", target)), uid, uid, PopupType.Medium);
- revenant.SoulSearchCancelToken = new();
- var searchDoAfter = new DoAfterEventArgs(uid, revenant.SoulSearchDuration, revenant.SoulSearchCancelToken.Token, target)
+ var soulSearchEvent = new SoulEvent();
+ var searchDoAfter = new DoAfterEventArgs(uid, revenant.SoulSearchDuration, target:target)
{
BreakOnUserMove = true,
- DistanceThreshold = 2,
- UserFinishedEvent = new SoulSearchDoAfterComplete(target),
- UserCancelledEvent = new SoulSearchDoAfterCancelled(),
+ DistanceThreshold = 2
};
- _doAfter.DoAfter(searchDoAfter);
+ _doAfter.DoAfter(searchDoAfter, soulSearchEvent);
}
- private void OnSoulSearchComplete(EntityUid uid, RevenantComponent component, SoulSearchDoAfterComplete args)
+ private void OnSoulSearch(EntityUid uid, RevenantComponent component, DoAfterEvent<SoulEvent> args)
{
- if (!TryComp<EssenceComponent>(args.Target, out var essence))
+ if (args.Handled || args.Cancelled)
+ return;
+
+ if (!TryComp<EssenceComponent>(args.Args.Target, out var essence))
return;
- component.SoulSearchCancelToken = null;
essence.SearchComplete = true;
string message;
message = "revenant-soul-yield-average";
break;
}
- _popup.PopupEntity(Loc.GetString(message, ("target", args.Target)), args.Target, uid, PopupType.Medium);
- }
+ _popup.PopupEntity(Loc.GetString(message, ("target", args.Args.Target)), args.Args.Target.Value, uid, PopupType.Medium);
- private void OnSoulSearchCancelled(EntityUid uid, RevenantComponent component, SoulSearchDoAfterCancelled args)
- {
- component.SoulSearchCancelToken = null;
+ args.Handled = true;
}
private void BeginHarvestDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant, EssenceComponent essence)
{
- if (revenant.HarvestCancelToken != null)
- return;
-
if (essence.Harvested)
{
_popup.PopupEntity(Loc.GetString("revenant-soul-harvested"), target, uid, PopupType.SmallCaution);
return;
}
- revenant.HarvestCancelToken = new();
- var doAfter = new DoAfterEventArgs(uid, revenant.HarvestDebuffs.X, revenant.HarvestCancelToken.Token, target)
+ var harvestEvent = new HarvestEvent();
+
+ var doAfter = new DoAfterEventArgs(uid, revenant.HarvestDebuffs.X, target:target)
{
DistanceThreshold = 2,
BreakOnUserMove = true,
- NeedHand = false,
- UserFinishedEvent = new HarvestDoAfterComplete(target),
- UserCancelledEvent = new HarvestDoAfterCancelled(),
+ NeedHand = false
};
_appearance.SetData(uid, RevenantVisuals.Harvesting, true);
target, PopupType.Large);
TryUseAbility(uid, revenant, 0, revenant.HarvestDebuffs);
- _doAfter.DoAfter(doAfter);
+ _doAfter.DoAfter(doAfter, harvestEvent);
}
- private void OnHarvestComplete(EntityUid uid, RevenantComponent component, HarvestDoAfterComplete args)
+ private void OnHarvest(EntityUid uid, RevenantComponent component, DoAfterEvent<HarvestEvent> args)
{
- component.HarvestCancelToken = null;
+ if (args.Cancelled)
+ {
+ _appearance.SetData(uid, RevenantVisuals.Harvesting, false);
+ return;
+ }
+
+ if (args.Handled || args.Args.Target == null)
+ return;
+
_appearance.SetData(uid, RevenantVisuals.Harvesting, false);
- if (!TryComp<EssenceComponent>(args.Target, out var essence))
+ if (!TryComp<EssenceComponent>(args.Args.Target, out var essence))
return;
- _popup.PopupEntity(Loc.GetString("revenant-soul-finish-harvest", ("target", args.Target)),
- args.Target, PopupType.LargeCaution);
+ _popup.PopupEntity(Loc.GetString("revenant-soul-finish-harvest", ("target", args.Args.Target)),
+ args.Args.Target.Value, PopupType.LargeCaution);
essence.Harvested = true;
ChangeEssenceAmount(uid, essence.EssenceAmount, component);
_store.TryAddCurrency(new Dictionary<string, FixedPoint2>
{ {component.StolenEssenceCurrencyPrototype, essence.EssenceAmount} }, uid);
- if (!HasComp<MobStateComponent>(args.Target))
+ if (!HasComp<MobStateComponent>(args.Args.Target))
return;
- if (_mobState.IsAlive(args.Target) || _mobState.IsCritical(args.Target))
+ if (_mobState.IsAlive(args.Args.Target.Value) || _mobState.IsCritical(args.Args.Target.Value))
{
_popup.PopupEntity(Loc.GetString("revenant-max-essence-increased"), uid, uid);
component.EssenceRegenCap += component.MaxEssenceUpgradeAmount;
//KILL THEMMMM
- if (!_mobThresholdSystem.TryGetThresholdForState(args.Target, MobState.Dead, out var damage))
+ if (!_mobThresholdSystem.TryGetThresholdForState(args.Args.Target.Value, MobState.Dead, out var damage))
return;
DamageSpecifier dspec = new();
dspec.DamageDict.Add("Poison", damage.Value);
- _damage.TryChangeDamage(args.Target, dspec, true, origin: uid);
- }
+ _damage.TryChangeDamage(args.Args.Target, dspec, true, origin: uid);
- private void OnHarvestCancelled(EntityUid uid, RevenantComponent component, HarvestDoAfterCancelled args)
- {
- component.HarvestCancelToken = null;
- _appearance.SetData(uid, RevenantVisuals.Harvesting, false);
+ args.Handled = true;
}
private void OnDefileAction(EntityUid uid, RevenantComponent component, RevenantDefileActionEvent args)
_emag.DoEmagEffect(ent, ent); //it emags itself. spooky.
}
}
+
+ private sealed class SoulEvent : EntityEventArgs
+ {
+
+ }
+
+ private sealed class HarvestEvent : EntityEventArgs
+ {
+
+ }
}
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public EntityUid? StuckTo;
+
+ /// <summary>
+ /// For the DoAfter event to tell if it should stick or unstick
+ /// </summary>
+ public bool Stick;
}
using Content.Server.Popups;
using Content.Server.Sticky.Components;
using Content.Server.Sticky.Events;
+using Content.Shared.DoAfter;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Sticky.Components;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent<StickSuccessfulEvent>(OnStickSuccessful);
- SubscribeLocalEvent<UnstickSuccessfulEvent>(OnUnstickSuccessful);
+ SubscribeLocalEvent<StickyComponent, DoAfterEvent>(OnStickSuccessful);
SubscribeLocalEvent<StickyComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<StickyComponent, GetVerbsEvent<Verb>>(AddUnstickVerb);
}
_popupSystem.PopupEntity(msg, user, user);
}
+ component.Stick = true;
+
// start sticking object to target
- _doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: target)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: target, used: uid)
{
- BroadcastFinishedEvent = new StickSuccessfulEvent(uid, user, target),
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
return true;
}
- private void OnStickSuccessful(StickSuccessfulEvent ev)
+ private void OnStickSuccessful(EntityUid uid, StickyComponent component, DoAfterEvent args)
{
- // check if entity still has sticky component
- if (!TryComp(ev.Uid, out StickyComponent? component))
+ if (args.Handled || args.Cancelled || args.Args.Target == null)
return;
- StickToEntity(ev.Uid, ev.Target, ev.User, component);
+ if (component.Stick)
+ StickToEntity(uid, args.Args.Target.Value, args.Args.User, component);
+
+ else
+ UnstickFromEntity(uid, args.Args.User, component);
+
+ args.Handled = true;
}
private void StartUnsticking(EntityUid uid, EntityUid user, StickyComponent? component = null)
_popupSystem.PopupEntity(msg, user, user);
}
+ component.Stick = false;
+
// start unsticking object
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: uid)
{
- BroadcastFinishedEvent = new UnstickSuccessfulEvent(uid, user),
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
}
}
- private void OnUnstickSuccessful(UnstickSuccessfulEvent ev)
- {
- // check if entity still has sticky component
- if (!TryComp(ev.Uid, out StickyComponent? component))
- return;
-
- UnstickFromEntity(ev.Uid, ev.User, component);
- }
-
public void StickToEntity(EntityUid uid, EntityUid target, EntityUid user, StickyComponent? component = null)
{
if (!Resolve(uid, ref component))
component.StuckTo = null;
RaiseLocalEvent(uid, new EntityUnstuckEvent(target, user), true);
}
-
- private sealed class StickSuccessfulEvent : EntityEventArgs
- {
- public readonly EntityUid Uid;
- public readonly EntityUid User;
- public readonly EntityUid Target;
-
- public StickSuccessfulEvent(EntityUid uid, EntityUid user, EntityUid target)
- {
- Uid = uid;
- User = user;
- Target = target;
- }
- }
-
- private sealed class UnstickSuccessfulEvent : EntityEventArgs
- {
- public readonly EntityUid Uid;
- public readonly EntityUid User;
-
- public UnstickSuccessfulEvent(EntityUid uid, EntityUid user)
- {
- Uid = uid;
- User = user;
- }
- }
}
-using System.Threading;
-
-namespace Content.Server.Storage.Components;
+namespace Content.Server.Storage.Components;
[RegisterComponent]
public sealed class BluespaceLockerComponent : Component
[DataField("pickLinksFromNonBluespaceLockers"), ViewVariables(VVAccess.ReadWrite)]
public bool PickLinksFromNonBluespaceLockers = true;
- public CancellationTokenSource? CancelToken;
-
/// <summary>
/// Determines if links automatically added get the source locker set as a target
/// </summary>
[DataField("areaInsert")]
public bool AreaInsert = false; // "Attacking" with the storage entity causes it to insert all nearby storables after a delay
- /// <summary>
- /// Token for interrupting area insert do after.
- /// </summary>
- public CancellationTokenSource? CancelToken;
-
[DataField("areaInsertRadius")]
public int AreaInsertRadius = 1;
-using System.Linq;
-using System.Threading;
+using System.Linq;
using Content.Server.DoAfter;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Mind.Components;
using Content.Server.Tools.Systems;
using Content.Shared.Access.Components;
using Content.Shared.Coordinates;
+using Content.Shared.DoAfter;
using Content.Shared.Lock;
using Content.Shared.Storage.Components;
using Robust.Shared.Random;
SubscribeLocalEvent<BluespaceLockerComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<BluespaceLockerComponent, StorageBeforeOpenEvent>(PreOpen);
SubscribeLocalEvent<BluespaceLockerComponent, StorageAfterCloseEvent>(PostClose);
- SubscribeLocalEvent<BluespaceLockerComponent, BluespaceLockerTeleportDelayComplete>(OnBluespaceLockerTeleportDelayComplete);
+ SubscribeLocalEvent<BluespaceLockerComponent, DoAfterEvent>(OnDoAfter);
}
private void OnStartup(EntityUid uid, BluespaceLockerComponent component, ComponentStartup args)
if (!Resolve(uid, ref entityStorageComponent))
return;
- if (component.CancelToken != null)
- {
- component.CancelToken.Cancel();
- component.CancelToken = null;
- return;
- }
-
if (!component.BehaviorProperties.ActOnOpen)
return;
PostClose(uid, component);
}
- private void OnBluespaceLockerTeleportDelayComplete(EntityUid uid, BluespaceLockerComponent component, BluespaceLockerTeleportDelayComplete args)
+ private void OnDoAfter(EntityUid uid, BluespaceLockerComponent component, DoAfterEvent args)
{
+ if (args.Handled || args.Cancelled)
+ return;
+
PostClose(uid, component, false);
+
+ args.Handled = true;
}
private void PostClose(EntityUid uid, BluespaceLockerComponent component, bool doDelay = true)
if (!Resolve(uid, ref entityStorageComponent))
return;
- component.CancelToken?.Cancel();
-
if (!component.BehaviorProperties.ActOnClose)
return;
if (doDelay && component.BehaviorProperties.Delay > 0)
{
EnsureComp<DoAfterComponent>(uid);
- component.CancelToken = new CancellationTokenSource();
- _doAfterSystem.DoAfter(
- new DoAfterEventArgs(uid, component.BehaviorProperties.Delay, component.CancelToken.Token)
- {
- UserFinishedEvent = new BluespaceLockerTeleportDelayComplete()
- });
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.BehaviorProperties.Delay));
return;
}
break;
}
}
-
- private sealed class BluespaceLockerTeleportDelayComplete : EntityEventArgs
- {
- }
}
using Content.Server.Disposal.Unit.Components;
using Content.Server.Disposal.Unit.EntitySystems;
using Content.Server.DoAfter;
+using Content.Shared.DoAfter;
using Content.Shared.Placeable;
+using Content.Shared.Storage;
using Robust.Shared.Containers;
using Robust.Shared.Random;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly DisposalUnitSystem _disposalUnitSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
public override void Initialize()
{
SubscribeLocalEvent<DumpableComponent, AfterInteractEvent>(OnAfterInteract, after: new[]{ typeof(StorageSystem) });
SubscribeLocalEvent<DumpableComponent, GetVerbsEvent<AlternativeVerb>>(AddDumpVerb);
SubscribeLocalEvent<DumpableComponent, GetVerbsEvent<UtilityVerb>>(AddUtilityVerbs);
- SubscribeLocalEvent<DumpCompletedEvent>(OnDumpCompleted);
- SubscribeLocalEvent<DumpCancelledEvent>(OnDumpCancelled);
+ SubscribeLocalEvent<DumpableComponent, DoAfterEvent>(OnDoAfter);
}
private void OnAfterInteract(EntityUid uid, DumpableComponent component, AfterInteractEvent args)
if (!args.CanReach)
return;
- if (!TryComp<ServerStorageComponent>(args.Used, out var storage))
- return;
-
- if (storage.StoredEntities == null || storage.StoredEntities.Count == 0 || storage.CancelToken != null)
- return;
-
if (HasComp<DisposalUnitComponent>(args.Target) || HasComp<PlaceableSurfaceComponent>(args.Target))
- {
- StartDoAfter(uid, args.Target.Value, args.User, component, storage);
- }
+ StartDoAfter(uid, args.Target.Value, args.User, component);
}
private void AddDumpVerb(EntityUid uid, DumpableComponent dumpable, GetVerbsEvent<AlternativeVerb> args)
{
Act = () =>
{
- StartDoAfter(uid, null, args.User, dumpable, storage, 0.6f);
+ StartDoAfter(uid, null, args.User, dumpable);//Had multiplier of 0.6f
},
Text = Loc.GetString("dump-verb-name"),
IconTexture = "/Textures/Interface/VerbIcons/drop.svg.192dpi.png",
{
Act = () =>
{
- StartDoAfter(uid, args.Target, args.User, dumpable, storage);
+ StartDoAfter(uid, args.Target, args.User, dumpable);
},
Text = Loc.GetString("dump-disposal-verb-name", ("unit", args.Target)),
IconEntity = uid
{
Act = () =>
{
- StartDoAfter(uid, args.Target, args.User, dumpable, storage);
+ StartDoAfter(uid, args.Target, args.User, dumpable);
},
Text = Loc.GetString("dump-placeable-verb-name", ("surface", args.Target)),
IconEntity = uid
}
}
- public void StartDoAfter(EntityUid storageUid, EntityUid? targetUid, EntityUid userUid, DumpableComponent dumpable, ServerStorageComponent storage, float multiplier = 1)
+ public void StartDoAfter(EntityUid storageUid, EntityUid? targetUid, EntityUid userUid, DumpableComponent dumpable)
{
- if (dumpable.CancelToken != null)
- return;
-
- if (storage.StoredEntities == null)
+ if (!TryComp<SharedStorageComponent>(storageUid, out var storage) || storage.StoredEntities == null)
return;
- float delay = storage.StoredEntities.Count * (float) dumpable.DelayPerItem.TotalSeconds * multiplier;
+ float delay = storage.StoredEntities.Count * (float) dumpable.DelayPerItem.TotalSeconds * dumpable.Multiplier;
- dumpable.CancelToken = new CancellationTokenSource();
- _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, delay, dumpable.CancelToken.Token, target: targetUid)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, delay, target: targetUid, used: storageUid)
{
- BroadcastFinishedEvent = new DumpCompletedEvent(dumpable, userUid, targetUid, storage.StoredEntities),
- BroadcastCancelledEvent = new DumpCancelledEvent(dumpable.Owner),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
});
}
-
- private void OnDumpCompleted(DumpCompletedEvent args)
+ private void OnDoAfter(EntityUid uid, DumpableComponent component, DoAfterEvent args)
{
- args.Component.CancelToken = null;
+ if (args.Handled || args.Cancelled || !TryComp<SharedStorageComponent>(uid, out var storage) || storage.StoredEntities == null)
+ return;
Queue<EntityUid> dumpQueue = new();
- foreach (var entity in args.StoredEntities)
+ foreach (var entity in storage.StoredEntities)
{
dumpQueue.Enqueue(entity);
}
- if (TryComp<DisposalUnitComponent>(args.Target, out var disposal))
- {
- foreach (var entity in dumpQueue)
- {
- _disposalUnitSystem.DoInsertDisposalUnit(args.Target.Value, entity, args.User);
- }
- return;
- }
-
foreach (var entity in dumpQueue)
{
var transform = Transform(entity);
- transform.AttachParentToContainerOrGrid(EntityManager);
- transform.LocalPosition = transform.LocalPosition + _random.NextVector2Box() / 2;
+ _container.AttachParentToContainerOrGrid(transform);
+ transform.LocalPosition += _random.NextVector2Box() / 2;
transform.LocalRotation = _random.NextAngle();
}
- if (HasComp<PlaceableSurfaceComponent>(args.Target))
+ if (args.Args.Target == null)
+ return;
+
+ if (HasComp<DisposalUnitComponent>(args.Args.Target.Value))
{
foreach (var entity in dumpQueue)
{
- Transform(entity).LocalPosition = Transform(args.Target.Value).LocalPosition + _random.NextVector2Box() / 4;
+ _disposalUnitSystem.DoInsertDisposalUnit(args.Args.Target.Value, entity, args.Args.User);
}
+ return;
}
- }
-
- private void OnDumpCancelled(DumpCancelledEvent args)
- {
- if (TryComp<DumpableComponent>(args.Uid, out var dumpable))
- dumpable.CancelToken = null;
- }
-
- private sealed class DumpCancelledEvent : EntityEventArgs
- {
- public readonly EntityUid Uid;
- public DumpCancelledEvent(EntityUid uid)
- {
- Uid = uid;
- }
- }
-
- private sealed class DumpCompletedEvent : EntityEventArgs
- {
- public DumpableComponent Component { get; }
- public EntityUid User { get; }
- public EntityUid? Target { get; }
- public IReadOnlyList<EntityUid> StoredEntities { get; }
- public DumpCompletedEvent(DumpableComponent component, EntityUid user, EntityUid? target, IReadOnlyList<EntityUid> storedEntities)
+ if (HasComp<PlaceableSurfaceComponent>(args.Args.Target.Value))
{
- Component = component;
- User = user;
- Target = target;
- StoredEntities = storedEntities;
+ foreach (var entity in dumpQueue)
+ {
+ Transform(entity).LocalPosition = Transform(args.Args.Target.Value).LocalPosition + _random.NextVector2Box() / 4;
+ }
}
}
}
using Content.Server.Hands.Components;
using Content.Server.Storage.Components;
using Content.Shared.Interaction;
-using Content.Shared.Movement;
using Content.Shared.Storage;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Content.Shared.ActionBlocker;
using Content.Shared.CombatMode;
using Content.Shared.Containers.ItemSlots;
+using Content.Shared.DoAfter;
using Content.Shared.Implants.Components;
using Content.Shared.Lock;
using Content.Shared.Movement.Events;
SubscribeLocalEvent<ServerStorageComponent, BoundUIClosedEvent>(OnBoundUIClosed);
SubscribeLocalEvent<ServerStorageComponent, EntRemovedFromContainerMessage>(OnStorageItemRemoved);
- SubscribeLocalEvent<ServerStorageComponent, AreaPickupCompleteEvent>(OnAreaPickupComplete);
- SubscribeLocalEvent<ServerStorageComponent, AreaPickupCancelledEvent>(OnAreaPickupCancelled);
+ SubscribeLocalEvent<ServerStorageComponent, DoAfterEvent<StorageData>>(OnDoAfter);
SubscribeLocalEvent<EntityStorageComponent, GetVerbsEvent<InteractionVerb>>(AddToggleOpenVerb);
SubscribeLocalEvent<EntityStorageComponent, ContainerRelayMovementEntityEvent>(OnRelayMovement);
SubscribeLocalEvent<StorageFillComponent, MapInitEvent>(OnStorageFillMapInit);
}
- private void OnAreaPickupCancelled(EntityUid uid, ServerStorageComponent component, AreaPickupCancelledEvent args)
- {
- component.CancelToken = null;
- }
-
- private void OnAreaPickupComplete(EntityUid uid, ServerStorageComponent component, AreaPickupCompleteEvent args)
- {
- component.CancelToken = null;
- var successfullyInserted = new List<EntityUid>();
- var successfullyInsertedPositions = new List<EntityCoordinates>();
- var itemQuery = GetEntityQuery<ItemComponent>();
- var xformQuery = GetEntityQuery<TransformComponent>();
- xformQuery.TryGetComponent(uid, out var xform);
-
- foreach (var entity in args.ValidStorables)
- {
- // Check again, situation may have changed for some entities, but we'll still pick up any that are valid
- if (_containerSystem.IsEntityInContainer(entity)
- || entity == args.User
- || !itemQuery.HasComponent(entity))
- continue;
-
- if (xform == null ||
- !xformQuery.TryGetComponent(entity, out var targetXform) ||
- targetXform.MapID != xform.MapID)
- {
- continue;
- }
-
- var position = EntityCoordinates.FromMap(
- xform.ParentUid.IsValid() ? xform.ParentUid : uid,
- new MapCoordinates(_transform.GetWorldPosition(targetXform, xformQuery),
- targetXform.MapID), EntityManager);
-
- if (PlayerInsertEntityInWorld(uid, args.User, entity, component))
- {
- successfullyInserted.Add(entity);
- successfullyInsertedPositions.Add(position);
- }
- }
-
- // If we picked up atleast one thing, play a sound and do a cool animation!
- if (successfullyInserted.Count > 0)
- {
- _audio.PlayPvs(component.StorageInsertSound, uid);
- RaiseNetworkEvent(new AnimateInsertingEntitiesEvent(uid, successfullyInserted, successfullyInsertedPositions));
- }
- }
-
private void OnComponentInit(EntityUid uid, ServerStorageComponent storageComp, ComponentInit args)
{
base.Initialize();
private void OnRelayMovement(EntityUid uid, EntityStorageComponent component, ref ContainerRelayMovementEntityEvent args)
{
- if (!EntityManager.HasComponent<HandsComponent>(args.Entity))
- return;
-
- if (_gameTiming.CurTime < component.LastInternalOpenAttempt + EntityStorageComponent.InternalOpenAttemptDelay)
+ if (!EntityManager.HasComponent<HandsComponent>(args.Entity) || _gameTiming.CurTime < component.LastInternalOpenAttempt + EntityStorageComponent.InternalOpenAttemptDelay)
return;
component.LastInternalOpenAttempt = _gameTiming.CurTime;
private void AddToggleOpenVerb(EntityUid uid, EntityStorageComponent component, GetVerbsEvent<InteractionVerb> args)
{
- if (!args.CanAccess || !args.CanInteract)
- return;
-
- if (!_entityStorage.CanOpen(args.User, args.Target, silent: true, component))
+ if (!args.CanAccess || !args.CanInteract || !_entityStorage.CanOpen(args.User, args.Target, silent: true, component))
return;
InteractionVerb verb = new();
private void AddOpenUiVerb(EntityUid uid, ServerStorageComponent component, GetVerbsEvent<ActivationVerb> args)
{
- if (!args.CanAccess || !args.CanInteract)
- return;
-
- if (TryComp<LockComponent>(uid, out var lockComponent) && lockComponent.Locked)
+ if (!args.CanAccess || !args.CanInteract || TryComp<LockComponent>(uid, out var lockComponent) && lockComponent.Locked)
return;
// Get the session for the user
return;
var entities = component.Storage?.ContainedEntities;
- if (entities == null || entities.Count == 0)
- return;
-
- if (TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked)
+ if (entities == null || entities.Count == 0 || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked)
return;
// if the target is storage, add a verb to transfer storage.
/// <returns>true if inserted, false otherwise</returns>
private void OnInteractUsing(EntityUid uid, ServerStorageComponent storageComp, InteractUsingEvent args)
{
- if (args.Handled)
- return;
-
- if (!storageComp.ClickInsert)
- return;
-
- if (TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked)
+ if (args.Handled || !storageComp.ClickInsert || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked)
return;
Logger.DebugS(storageComp.LoggerName, $"Storage (UID {uid}) attacked by user (UID {args.User}) with entity (UID {args.Used}).");
/// <returns></returns>
private void OnActivate(EntityUid uid, ServerStorageComponent storageComp, ActivateInWorldEvent args)
{
- if (args.Handled || _combatMode.IsInCombatMode(args.User))
- return;
-
- if (TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked)
+ if (args.Handled || _combatMode.IsInCombatMode(args.User) || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked)
return;
OpenStorageUI(uid, args.User, storageComp);
/// <returns></returns>
private async void AfterInteract(EntityUid uid, ServerStorageComponent storageComp, AfterInteractEvent args)
{
- if (!args.CanReach) return;
-
- if (storageComp.CancelToken != null)
- {
+ if (!args.CanReach)
return;
- }
// Pick up all entities in a radius around the clicked location.
// The last half of the if is because carpets exist and this is terrible
//If there's only one then let's be generous
if (validStorables.Count > 1)
{
- storageComp.CancelToken = new CancellationTokenSource();
- var doAfterArgs = new DoAfterEventArgs(args.User, 0.2f * validStorables.Count, storageComp.CancelToken.Token, target: uid)
+ var storageData = new StorageData(validStorables);
+ var doAfterArgs = new DoAfterEventArgs(args.User, 0.2f * validStorables.Count, target: uid)
{
BreakOnStun = true,
BreakOnDamage = true,
BreakOnUserMove = true,
- NeedHand = true,
- TargetCancelledEvent = new AreaPickupCancelledEvent(),
- TargetFinishedEvent = new AreaPickupCompleteEvent(args.User, validStorables),
+ NeedHand = true
};
- _doAfterSystem.DoAfter(doAfterArgs);
+ _doAfterSystem.DoAfter(doAfterArgs, storageData);
}
return;
}
}
+ private void OnDoAfter(EntityUid uid, ServerStorageComponent component, DoAfterEvent<StorageData> args)
+ {
+ if (args.Handled || args.Cancelled)
+ return;
+
+ var successfullyInserted = new List<EntityUid>();
+ var successfullyInsertedPositions = new List<EntityCoordinates>();
+ var itemQuery = GetEntityQuery<ItemComponent>();
+ var xformQuery = GetEntityQuery<TransformComponent>();
+ xformQuery.TryGetComponent(uid, out var xform);
+
+ foreach (var entity in args.AdditionalData.ValidStorables)
+ {
+ // Check again, situation may have changed for some entities, but we'll still pick up any that are valid
+ if (_containerSystem.IsEntityInContainer(entity)
+ || entity == args.Args.User
+ || !itemQuery.HasComponent(entity))
+ continue;
+
+ if (xform == null ||
+ !xformQuery.TryGetComponent(entity, out var targetXform) ||
+ targetXform.MapID != xform.MapID)
+ {
+ continue;
+ }
+
+ var position = EntityCoordinates.FromMap(
+ xform.ParentUid.IsValid() ? xform.ParentUid : uid,
+ new MapCoordinates(_transform.GetWorldPosition(targetXform, xformQuery),
+ targetXform.MapID), EntityManager);
+
+ if (PlayerInsertEntityInWorld(uid, args.Args.User, entity, component))
+ {
+ successfullyInserted.Add(entity);
+ successfullyInsertedPositions.Add(position);
+ }
+ }
+
+ // If we picked up atleast one thing, play a sound and do a cool animation!
+ if (successfullyInserted.Count > 0)
+ {
+ _audio.PlayPvs(component.StorageInsertSound, uid);
+ RaiseNetworkEvent(new AnimateInsertingEntitiesEvent(uid, successfullyInserted, successfullyInsertedPositions));
+ }
+
+ args.Handled = true;
+ }
+
private void OnDestroy(EntityUid uid, ServerStorageComponent storageComp, DestructionEventArgs args)
{
var storedEntities = storageComp.StoredEntities?.ToList();
return;
}
- if (!_actionBlockerSystem.CanInteract(player, args.InteractedItemUID))
- return;
-
- if (storageComp.Storage == null || !storageComp.Storage.Contains(args.InteractedItemUID))
+ if (!_actionBlockerSystem.CanInteract(player, args.InteractedItemUID) || storageComp.Storage == null || !storageComp.Storage.Contains(args.InteractedItemUID))
return;
// Does the player have hands?
{
if (_sharedHandsSystem.TryPickupAnyHand(player, args.InteractedItemUID, handsComp: hands)
&& storageComp.StorageRemoveSound != null)
- SoundSystem.Play(storageComp.StorageRemoveSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid, AudioParams.Default);
+ _audio.Play(storageComp.StorageRemoveSound, Filter.Pvs(uid, entityManager: EntityManager), uid, true, AudioParams.Default);
return;
}
UpdateStorageVisualization(uid, storageComp);
if (storageComp.StorageCloseSound is not null)
- SoundSystem.Play(storageComp.StorageCloseSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid, storageComp.StorageCloseSound.Params);
+ _audio.Play(storageComp.StorageCloseSound, Filter.Pvs(uid, entityManager: EntityManager), uid, true, storageComp.StorageCloseSound.Params);
}
}
return;
_appearance.SetData(uid, StorageVisuals.Open, storageComp.IsOpen, appearance);
- _appearance.SetData(uid, SharedBagOpenVisuals.BagState, storageComp.IsOpen ? SharedBagState.Open : SharedBagState.Closed, appearance);
+ _appearance.SetData(uid, SharedBagOpenVisuals.BagState, storageComp.IsOpen ? SharedBagState.Open : SharedBagState.Closed);
if (HasComp<ItemCounterComponent>(uid))
- _appearance.SetData(uid, StackVisuals.Hide, !storageComp.IsOpen, appearance);
+ _appearance.SetData(uid, StackVisuals.Hide, !storageComp.IsOpen);
}
private void RecalculateStorageUsed(ServerStorageComponent storageComp)
/// <returns>true if the entity was inserted, false otherwise</returns>
public bool Insert(EntityUid uid, EntityUid insertEnt, ServerStorageComponent? storageComp = null, bool playSound = true)
{
- if (!Resolve(uid, ref storageComp))
- return false;
-
- if (!CanInsert(uid, insertEnt, out _, storageComp) || storageComp.Storage?.Insert(insertEnt) == false)
+ if (!Resolve(uid, ref storageComp) || !CanInsert(uid, insertEnt, out _, storageComp) || storageComp.Storage?.Insert(insertEnt) == false)
return false;
if (playSound && storageComp.StorageInsertSound is not null)
- {
_audio.PlayPvs(storageComp.StorageInsertSound, uid);
- }
RecalculateStorageUsed(storageComp);
UpdateStorageUI(uid, storageComp);
/// <returns>true if inserted, false otherwise</returns>
public bool PlayerInsertHeldEntity(EntityUid uid, EntityUid player, ServerStorageComponent? storageComp = null)
{
- if (!Resolve(uid, ref storageComp))
- return false;
-
- if (!TryComp(player, out HandsComponent? hands) ||
- hands.ActiveHandEntity == null)
+ if (!Resolve(uid, ref storageComp) || !TryComp(player, out HandsComponent? hands) || hands.ActiveHandEntity == null)
return false;
var toInsert = hands.ActiveHandEntity;
/// <returns>true if inserted, false otherwise</returns>
public bool PlayerInsertEntityInWorld(EntityUid uid, EntityUid player, EntityUid toInsert, ServerStorageComponent? storageComp = null)
{
- if (!Resolve(uid, ref storageComp))
- return false;
-
- if (!_sharedInteractionSystem.InRangeUnobstructed(player, uid, popup: storageComp.ShowPopup))
+ if (!Resolve(uid, ref storageComp) || !_sharedInteractionSystem.InRangeUnobstructed(player, uid, popup: storageComp.ShowPopup))
return false;
if (!Insert(uid, toInsert, storageComp))
/// <param name="entity">The entity to open the UI for</param>
public void OpenStorageUI(EntityUid uid, EntityUid entity, ServerStorageComponent? storageComp = null)
{
- if (!Resolve(uid, ref storageComp))
- return;
-
- if (!TryComp(entity, out ActorComponent? player))
+ if (!Resolve(uid, ref storageComp) || !TryComp(entity, out ActorComponent? player))
return;
if (storageComp.StorageOpenSound is not null)
- SoundSystem.Play(storageComp.StorageOpenSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid, storageComp.StorageOpenSound.Params);
+ _audio.Play(storageComp.StorageOpenSound, Filter.Pvs(uid, entityManager: EntityManager), uid, true, storageComp.StorageOpenSound.Params);
Logger.DebugS(storageComp.LoggerName, $"Storage (UID {uid}) \"used\" by player session (UID {player.PlayerSession.AttachedEntity}).");
- _uiSystem.GetUiOrNull(uid, StorageUiKey.Key)?.Open(player.PlayerSession);
+ var bui = _uiSystem.GetUiOrNull(uid, StorageUiKey.Key);
+ if (bui != null)
+ _uiSystem.OpenUi(bui, player.PlayerSession);
}
/// <summary>
/// <param name="session"></param>
public void CloseNestedInterfaces(EntityUid uid, IPlayerSession session, ServerStorageComponent? storageComp = null)
{
- if (!Resolve(uid, ref storageComp))
- return;
-
- if (storageComp.StoredEntities == null)
+ if (!Resolve(uid, ref storageComp) || storageComp.StoredEntities == null)
return;
// for each containing thing
foreach (var entity in storageComp.StoredEntities)
{
if (TryComp(entity, out ServerStorageComponent? storedStorageComp))
- {
DebugTools.Assert(storedStorageComp != storageComp, $"Storage component contains itself!? Entity: {uid}");
- }
if (!TryComp(entity, out ServerUserInterfaceComponent? ui))
continue;
var state = new StorageBoundUserInterfaceState((List<EntityUid>) storageComp.Storage.ContainedEntities, storageComp.StorageUsed, storageComp.StorageCapacityMax);
- _uiSystem.GetUiOrNull(uid, StorageUiKey.Key)?.SetState(state);
+ var bui = _uiSystem.GetUiOrNull(uid, StorageUiKey.Key);
+ if (bui != null)
+ _uiSystem.SetUiState(bui, state);
}
private void Popup(EntityUid uid, EntityUid player, string message, ServerStorageComponent storageComp)
{
- if (!storageComp.ShowPopup) return;
+ if (!storageComp.ShowPopup)
+ return;
_popupSystem.PopupEntity(Loc.GetString(message), player, player);
}
- /// <summary>
- /// Raised on storage if it successfully completes area pickup.
- /// </summary>
- private sealed class AreaPickupCompleteEvent : EntityEventArgs
- {
- public EntityUid User;
- public List<EntityUid> ValidStorables;
-
- public AreaPickupCompleteEvent(EntityUid user, List<EntityUid> validStorables)
- {
- User = user;
- ValidStorables = validStorables;
- }
- }
-
- private sealed class AreaPickupCancelledEvent : EntityEventArgs
+ private record struct StorageData(List<EntityUid> validStorables)
{
-
+ public List<EntityUid> ValidStorables = validStorables;
}
}
}
using System.Threading;
using Content.Server.Administration.Logs;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Ensnaring.Components;
using Content.Shared.Interaction;
using Content.Shared.Strip;
if (!TryComp<EnsnaringComponent>(entity, out var ensnaring))
continue;
- _ensnaring.TryFree(component.Owner, ensnaring, user);
+ _ensnaring.TryFree(uid, entity, ensnaring, user);
return;
}
}
-using System.Threading;
using Content.Server.Administration.Logs;
using Content.Server.DoAfter;
+using Content.Shared.DoAfter;
using Content.Shared.Database;
using Content.Shared.Interaction.Events;
using Content.Shared.Teleportation.Components;
{
SubscribeLocalEvent<HandTeleporterComponent, UseInHandEvent>(OnUseInHand);
- SubscribeLocalEvent<HandTeleporterComponent, HandTeleporterSuccessEvent>(OnPortalSuccess);
- SubscribeLocalEvent<HandTeleporterComponent, HandTeleporterCancelledEvent>(OnPortalCancelled);
+ SubscribeLocalEvent<HandTeleporterComponent, DoAfterEvent>(OnDoAfter);
}
- private void OnPortalSuccess(EntityUid uid, HandTeleporterComponent component, HandTeleporterSuccessEvent args)
+ private void OnDoAfter(EntityUid uid, HandTeleporterComponent component, DoAfterEvent args)
{
- component.CancelToken = null;
- HandlePortalUpdating(uid, component, args.User);
- }
+ if (args.Cancelled || args.Handled)
+ return;
- private void OnPortalCancelled(EntityUid uid, HandTeleporterComponent component, HandTeleporterCancelledEvent args)
- {
- component.CancelToken = null;
+ HandlePortalUpdating(uid, component, args.Args.User);
+
+ args.Handled = true;
}
private void OnUseInHand(EntityUid uid, HandTeleporterComponent component, UseInHandEvent args)
if (Deleted(component.SecondPortal))
component.SecondPortal = null;
- if (component.CancelToken != null)
- {
- component.CancelToken.Cancel();
- return;
- }
-
if (component.FirstPortal != null && component.SecondPortal != null)
{
// handle removing portals immediately as opposed to a doafter
if (xform.ParentUid != xform.GridUid)
return;
- component.CancelToken = new CancellationTokenSource();
- var doafterArgs = new DoAfterEventArgs(args.User, component.PortalCreationDelay,
- component.CancelToken.Token, used: uid)
+ var doafterArgs = new DoAfterEventArgs(args.User, component.PortalCreationDelay, used: uid)
{
BreakOnDamage = true,
BreakOnStun = true,
BreakOnUserMove = true,
MovementThreshold = 0.5f,
- UsedCancelledEvent = new HandTeleporterCancelledEvent(),
- UsedFinishedEvent = new HandTeleporterSuccessEvent(args.User)
};
_doafter.DoAfter(doafterArgs);
using Content.Server.Popups;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
-using Content.Server.Tools;
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Buckle.Components;
using Content.Shared.Interaction.Events;
using Content.Shared.Popups;
using Content.Shared.Toilet;
+using Content.Shared.Tools;
using Content.Shared.Tools.Components;
using Robust.Shared.Audio;
using Robust.Shared.Player;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SecretStashSystem _secretStash = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly ToolSystem _toolSystem = default!;
+ [Dependency] private readonly SharedToolSystem _toolSystem = default!;
public override void Initialize()
{
return;
component.IsPrying = true;
+ var toolEvData = new ToolEventData(new ToiletPryFinished(uid));
+
// try to pry toilet cistern
- if (!_toolSystem.UseTool(args.Used, args.User, uid, 0f,
- component.PryLidTime, component.PryingQuality,
- new ToiletPryFinished(uid), new ToiletPryInterrupted(uid)))
+ if (!_toolSystem.UseTool(args.Used, args.User, uid, component.PryLidTime, new [] {component.PryingQuality}, toolEvData))
{
component.IsPrying = false;
return;
-using System.Threading;
-using Content.Shared.Tools;
+using Content.Shared.Tools;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Tools.Components;
[DataField("vacuumDelay")]
public float VacuumDelay = 1.75f;
-
- /// <summary>
- /// Used for do_afters.
- /// </summary>
- public CancellationTokenSource? CancelTokenSource = null;
}
[DataField("delay")]
public float Delay = 1f;
-
- /// <summary>
- /// Used for do_afters.
- /// </summary>
- public CancellationTokenSource? CancelToken = null;
}
}
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.Interaction;
+using Content.Shared.Tools;
using Content.Shared.Tools.Components;
-using Robust.Server.GameObjects;
namespace Content.Server.Tools.Systems;
public sealed class WeldableSystem : EntitySystem
{
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
- [Dependency] private readonly ToolSystem _toolSystem = default!;
+ [Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize()
if (!CanWeld(uid, tool, user, component))
return false;
- component.BeingWelded = _toolSystem.UseTool(tool, user, uid, component.FuelConsumption,
- component.WeldingTime.Seconds, component.WeldingQuality,
- new WeldFinishedEvent(user, tool), new WeldCancelledEvent(), uid);
+ var toolEvData = new ToolEventData(new WeldFinishedEvent(user, tool), targetEntity: uid);
+ component.BeingWelded = _toolSystem.UseTool(tool, user, uid, component.WeldingTime.Seconds, new[] { component.WeldingQuality }, toolEvData, fuel: component.FuelConsumption);
// Log attempt
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):user} is {(component.IsWelded ? "un" : "")}welding {ToPrettyString(uid):target} at {Transform(uid).Coordinates:targetlocation}");
-using System.Threading;
-using Content.Server.Administration.Logs;
+using Content.Server.Administration.Logs;
using Content.Server.Maps;
using Content.Server.Tools.Components;
using Content.Shared.Database;
{
SubscribeLocalEvent<LatticeCuttingComponent, AfterInteractEvent>(OnLatticeCuttingAfterInteract);
SubscribeLocalEvent<LatticeCuttingComponent, LatticeCuttingCompleteEvent>(OnLatticeCutComplete);
- SubscribeLocalEvent<LatticeCuttingComponent, LatticeCuttingCancelledEvent>(OnLatticeCutCancelled);
- }
-
- private void OnLatticeCutCancelled(EntityUid uid, LatticeCuttingComponent component, LatticeCuttingCancelledEvent args)
- {
- component.CancelTokenSource = null;
}
private void OnLatticeCutComplete(EntityUid uid, LatticeCuttingComponent component, LatticeCuttingCompleteEvent args)
{
- component.CancelTokenSource = null;
var gridUid = args.Coordinates.GetGridUid(EntityManager);
if (gridUid == null)
return;
if (args.Handled || !args.CanReach || args.Target != null)
return;
- if (TryCut(args.User, component, args.ClickLocation))
+ if (TryCut(uid, args.User, component, args.ClickLocation))
args.Handled = true;
}
- private bool TryCut(EntityUid user, LatticeCuttingComponent component, EntityCoordinates clickLocation)
+ private bool TryCut(EntityUid toolEntity, EntityUid user, LatticeCuttingComponent component, EntityCoordinates clickLocation)
{
- if (component.CancelTokenSource != null)
- return true;
-
ToolComponent? tool = null;
- if (component.ToolComponentNeeded && !TryComp<ToolComponent?>(component.Owner, out tool))
+ if (component.ToolComponentNeeded && !TryComp<ToolComponent?>(toolEntity, out tool))
return false;
if (!_mapManager.TryGetGrid(clickLocation.GetGridUid(EntityManager), out var mapGrid))
|| tile.IsBlockedTurf(true))
return false;
- var tokenSource = new CancellationTokenSource();
- component.CancelTokenSource = tokenSource;
+ var toolEvData = new ToolEventData(new LatticeCuttingCompleteEvent(clickLocation, user), targetEntity: toolEntity);
- if (!UseTool(component.Owner, user, null, 0f, component.Delay, new[] {component.QualityNeeded},
- new LatticeCuttingCompleteEvent
- {
- Coordinates = clickLocation,
- User = user
- }, new LatticeCuttingCancelledEvent(), toolComponent: tool, doAfterEventTarget: component.Owner,
- cancelToken: tokenSource.Token))
- component.CancelTokenSource = null;
+ if (!UseTool(toolEntity, user, null, component.Delay, new[] {component.QualityNeeded}, toolEvData, toolComponent: tool))
+ return false;
return true;
}
{
public EntityCoordinates Coordinates;
public EntityUid User;
- }
- private sealed class LatticeCuttingCancelledEvent : EntityEventArgs
- {
+ public LatticeCuttingCompleteEvent(EntityCoordinates coordinates, EntityUid user)
+ {
+ Coordinates = coordinates;
+ User = user;
+ }
}
}
{
SubscribeLocalEvent<TilePryingComponent, AfterInteractEvent>(OnTilePryingAfterInteract);
SubscribeLocalEvent<TilePryingComponent, TilePryingCompleteEvent>(OnTilePryComplete);
- SubscribeLocalEvent<TilePryingComponent, TilePryingCancelledEvent>(OnTilePryCancelled);
- }
-
- private void OnTilePryCancelled(EntityUid uid, TilePryingComponent component, TilePryingCancelledEvent args)
- {
- component.CancelToken = null;
}
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))
{
{
if (args.Handled || !args.CanReach || (args.Target != null && !HasComp<PuddleComponent>(args.Target))) return;
- if (TryPryTile(args.User, component, args.ClickLocation))
+ if (TryPryTile(uid, args.User, component, args.ClickLocation))
args.Handled = true;
}
- private bool TryPryTile(EntityUid user, TilePryingComponent component, EntityCoordinates clickLocation)
+ private bool TryPryTile(EntityUid toolEntity, EntityUid user, TilePryingComponent component, EntityCoordinates clickLocation)
{
- if (component.CancelToken != null)
- {
- return true;
- }
-
- if (!TryComp<ToolComponent?>(component.Owner, out var tool) && component.ToolComponentNeeded)
+ if (!TryComp<ToolComponent?>(toolEntity, out var tool) && component.ToolComponentNeeded)
return false;
if (!_mapManager.TryGetGrid(clickLocation.GetGridUid(EntityManager), out var mapGrid))
if (!tileDef.CanCrowbar)
return false;
- var token = new CancellationTokenSource();
- component.CancelToken = token;
-
- bool success = UseTool(
- component.Owner,
- user,
- null,
- 0f,
- component.Delay,
- new [] {component.QualityNeeded},
- new TilePryingCompleteEvent
- {
- Coordinates = clickLocation,
- },
- new TilePryingCancelledEvent(),
- toolComponent: tool,
- doAfterEventTarget: component.Owner,
- cancelToken: token.Token);
-
- if (!success)
- component.CancelToken = null;
+ var toolEvData = new ToolEventData(new TilePryingCompleteEvent(clickLocation), targetEntity:toolEntity);
+
+ if (!UseTool(toolEntity, user, null, component.Delay, new[] { component.QualityNeeded }, toolEvData, toolComponent: tool))
+ return false;
return true;
}
private sealed class TilePryingCompleteEvent : EntityEventArgs
{
- public EntityCoordinates Coordinates { get; init; }
+ public readonly EntityCoordinates Coordinates;
+
+ public TilePryingCompleteEvent(EntityCoordinates coordinates)
+ {
+ Coordinates = coordinates;
+ }
}
private sealed class TilePryingCancelledEvent : EntityEventArgs
using Content.Server.Atmos.EntitySystems;
using Content.Server.Chemistry.EntitySystems;
-using Content.Server.DoAfter;
using Content.Server.Popups;
-using Content.Shared.Audio;
-using Content.Shared.Item;
using Content.Shared.Tools;
-using Content.Shared.Tools.Components;
using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
using Robust.Shared.Map;
-using Robust.Shared.Player;
-using System.Threading;
-using System.Threading.Tasks;
namespace Content.Server.Tools
{
{
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
- [Dependency] private readonly SharedItemSystem _itemSystem = default!;
public override void Initialize()
{
InitializeTilePrying();
InitializeLatticeCutting();
InitializeWelders();
-
- SubscribeLocalEvent<ToolDoAfterComplete>(OnDoAfterComplete);
- SubscribeLocalEvent<ToolDoAfterCancelled>(OnDoAfterCancelled);
- }
-
- private void OnDoAfterComplete(ToolDoAfterComplete ev)
- {
- // Actually finish the tool use! Depending on whether that succeeds or not, either event will be broadcast.
- if(ToolFinishUse(ev.Uid, ev.UserUid, ev.Fuel))
- {
- if (ev.EventTarget != null)
- RaiseLocalEvent(ev.EventTarget.Value, ev.CompletedEvent, false);
- else
- RaiseLocalEvent(ev.CompletedEvent);
- }
- else if(ev.CancelledEvent != null)
- {
- if (ev.EventTarget != null)
- RaiseLocalEvent(ev.EventTarget.Value, ev.CancelledEvent, false);
- else
- RaiseLocalEvent(ev.CancelledEvent);
- }
- }
-
- private void OnDoAfterCancelled(ToolDoAfterCancelled ev)
- {
- if (ev.EventTarget != null)
- RaiseLocalEvent(ev.EventTarget.Value, ev.Event, false);
- else
- RaiseLocalEvent(ev.Event);
- }
-
- /// <summary>
- /// Whether a tool entity has the specified quality or not.
- /// </summary>
- public bool HasQuality(EntityUid uid, string quality, ToolComponent? tool = null)
- {
- return Resolve(uid, ref tool, false) && tool.Qualities.Contains(quality);
- }
-
- /// <summary>
- /// Whether a tool entity has all specified qualities or not.
- /// </summary>
- public bool HasAllQualities(EntityUid uid, IEnumerable<string> qualities, ToolComponent? tool = null)
- {
- return Resolve(uid, ref tool, false) && tool.Qualities.ContainsAll(qualities);
- }
-
- /// <summary>
- /// Sync version of UseTool.
- /// </summary>
- /// <param name="tool">The tool entity.</param>
- /// <param name="user">The entity using the tool.</param>
- /// <param name="target">Optionally, a target to use the tool on.</param>
- /// <param name="fuel">An optional amount of fuel or energy to consume-</param>
- /// <param name="doAfterDelay">A doAfter delay in seconds.</param>
- /// <param name="toolQualitiesNeeded">The tool qualities needed to use the tool.</param>
- /// <param name="doAfterCompleteEvent">An event to raise once the doAfter is completed successfully.</param>
- /// <param name="doAfterCancelledEvent">An event to raise once the doAfter is canceled.</param>
- /// <param name="doAfterEventTarget">Where to direct the do-after events. If null, events are broadcast</param>
- /// <param name="doAfterCheck">An optional check to perform for the doAfter.</param>
- /// <param name="toolComponent">The tool component.</param>
- /// <param name="cancelToken">Token to provide to do_after for cancelling</param>
- /// <returns>Whether initially, using the tool succeeded. If there's a doAfter delay, you'll need to listen to
- /// the <see cref="doAfterCompleteEvent"/> and <see cref="doAfterCancelledEvent"/> being broadcast
- /// to see whether using the tool succeeded or not. If the <see cref="doAfterDelay"/> is zero,
- /// this simply returns whether using the tool succeeded or not.</returns>
- public override bool UseTool(
- EntityUid tool,
- EntityUid user,
- EntityUid? target,
- float fuel,
- float doAfterDelay,
- IEnumerable<string> toolQualitiesNeeded,
- object? doAfterCompleteEvent = null,
- object? doAfterCancelledEvent = null,
- EntityUid? doAfterEventTarget = null,
- Func<bool>? doAfterCheck = null,
- ToolComponent? toolComponent = null,
- CancellationToken? 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))
- return false;
-
- var ev = new ToolUserAttemptUseEvent(user, target);
- RaiseLocalEvent(user, ref ev);
- if (ev.Cancelled)
- return false;
-
- if (!ToolStartUse(tool, user, fuel, toolQualitiesNeeded, toolComponent))
- return false;
-
- if (doAfterDelay > 0f)
- {
- var doAfterArgs = new DoAfterEventArgs(user, doAfterDelay / toolComponent.SpeedModifier, cancelToken ?? default, target)
- {
- ExtraCheck = doAfterCheck,
- BreakOnDamage = true,
- BreakOnStun = true,
- BreakOnTargetMove = true,
- BreakOnUserMove = true,
- NeedHand = true,
- BroadcastFinishedEvent = doAfterCompleteEvent != null ? new ToolDoAfterComplete(doAfterCompleteEvent, doAfterCancelledEvent, tool, user, fuel, doAfterEventTarget) : null,
- BroadcastCancelledEvent = doAfterCancelledEvent != null ? new ToolDoAfterCancelled(doAfterCancelledEvent, doAfterEventTarget) : null,
- };
-
- _doAfterSystem.DoAfter(doAfterArgs);
- return true;
- }
-
- return ToolFinishUse(tool, user, fuel, toolComponent);
- }
-
- /// <summary>
- /// Async version of UseTool.
- /// </summary>
- /// <param name="tool">The tool entity.</param>
- /// <param name="user">The entity using the tool.</param>
- /// <param name="target">Optionally, a target to use the tool on.</param>
- /// <param name="fuel">An optional amount of fuel or energy to consume-</param>
- /// <param name="doAfterDelay">A doAfter delay in seconds.</param>
- /// <param name="toolQualitiesNeeded">The tool qualities needed to use the tool.</param>
- /// <param name="doAfterCheck">An optional check to perform for the doAfter.</param>
- /// <param name="toolComponent">The tool component.</param>
- /// <returns>Whether using the tool succeeded or not.</returns>
- public async Task<bool> UseTool(EntityUid tool, EntityUid user, EntityUid? target, float fuel,
- float doAfterDelay, IEnumerable<string> toolQualitiesNeeded, Func<bool>? doAfterCheck = null,
- ToolComponent? toolComponent = 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))
- return false;
-
- var ev = new ToolUserAttemptUseEvent(user, target);
- RaiseLocalEvent(user, ref ev);
- if (ev.Cancelled)
- return false;
-
- if (!ToolStartUse(tool, user, fuel, toolQualitiesNeeded, toolComponent))
- return false;
-
- if (doAfterDelay > 0f)
- {
- var doAfterArgs = new DoAfterEventArgs(user, doAfterDelay / toolComponent.SpeedModifier, default, target)
- {
- ExtraCheck = doAfterCheck,
- BreakOnDamage = true,
- BreakOnStun = true,
- BreakOnTargetMove = true,
- BreakOnUserMove = true,
- NeedHand = true,
- };
-
- var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);
-
- if (result == DoAfterStatus.Cancelled)
- return false;
- }
-
- return ToolFinishUse(tool, user, fuel, toolComponent);
- }
-
- // This is hilariously long.
- /// <inheritdoc cref="UseTool(Robust.Shared.GameObjects.EntityUid,Robust.Shared.GameObjects.EntityUid,System.Nullable{Robust.Shared.GameObjects.EntityUid},float,float,System.Collections.Generic.IEnumerable{string},Robust.Shared.GameObjects.EntityUid,object,object,System.Func{bool}?,Content.Shared.Tools.Components.ToolComponent?)"/>
- public Task<bool> UseTool(EntityUid tool, EntityUid user, EntityUid? target, float fuel,
- float doAfterDelay, string toolQualityNeeded, Func<bool>? doAfterCheck = null,
- ToolComponent? toolComponent = null)
- {
- return UseTool(tool, user, target, fuel, doAfterDelay, new [] {toolQualityNeeded}, doAfterCheck, toolComponent);
- }
-
- private bool ToolStartUse(EntityUid tool, EntityUid user, float fuel, IEnumerable<string> toolQualitiesNeeded, ToolComponent? toolComponent = null)
- {
- if (!Resolve(tool, ref toolComponent))
- return false;
-
- if (!toolComponent.Qualities.ContainsAll(toolQualitiesNeeded))
- return false;
-
- var beforeAttempt = new ToolUseAttemptEvent(fuel, user);
- RaiseLocalEvent(tool, beforeAttempt, false);
-
- return !beforeAttempt.Cancelled;
- }
-
- private bool ToolFinishUse(EntityUid tool, EntityUid user, float fuel, ToolComponent? toolComponent = null)
- {
- if (!Resolve(tool, ref toolComponent))
- return false;
-
- var afterAttempt = new ToolUseFinishAttemptEvent(fuel, user);
- RaiseLocalEvent(tool, afterAttempt, false);
-
- if (afterAttempt.Cancelled)
- return false;
-
- if (toolComponent.UseSound != null)
- PlayToolSound(tool, toolComponent);
-
- return true;
- }
-
- public void PlayToolSound(EntityUid uid, ToolComponent? tool = null)
- {
- if (!Resolve(uid, ref tool))
- return;
-
- if (tool.UseSound is not {} sound)
- return;
-
- // Pass tool.Owner to Filter.Pvs to avoid a TryGetEntity call.
- SoundSystem.Play(sound.GetSound(), Filter.Pvs(tool.Owner),
- uid, AudioHelpers.WithVariation(0.175f).WithVolume(-5f));
}
public override void Update(float frameTime)
UpdateWelders(frameTime);
}
-
- private sealed class ToolDoAfterComplete : EntityEventArgs
- {
- public readonly object CompletedEvent;
- public readonly object? CancelledEvent;
- public readonly EntityUid Uid;
- public readonly EntityUid UserUid;
- public readonly float Fuel;
- public readonly EntityUid? EventTarget;
-
- public ToolDoAfterComplete(object completedEvent, object? cancelledEvent, EntityUid uid, EntityUid userUid, float fuel, EntityUid? eventTarget = null)
- {
- CompletedEvent = completedEvent;
- Uid = uid;
- UserUid = userUid;
- Fuel = fuel;
- CancelledEvent = cancelledEvent;
- EventTarget = eventTarget;
- }
- }
-
- private sealed class ToolDoAfterCancelled : EntityEventArgs
- {
- public readonly object Event;
- public readonly EntityUid? EventTarget;
-
- public ToolDoAfterCancelled(object @event, EntityUid? eventTarget = null)
- {
- Event = @event;
- EventTarget = eventTarget;
- }
- }
- }
-
- /// <summary>
- /// Attempt event called *before* any do afters to see if the tool usage should succeed or not.
- /// You can change the fuel consumption by changing the Fuel property.
- /// </summary>
- public sealed class ToolUseAttemptEvent : CancellableEntityEventArgs
- {
- public float Fuel { get; set; }
- public EntityUid User { get; }
-
- public ToolUseAttemptEvent(float fuel, EntityUid user)
- {
- Fuel = fuel;
- User = user;
- }
- }
-
- /// <summary>
- /// Event raised on the user of a tool to see if they can actually use it.
- /// </summary>
- [ByRefEvent]
- public struct ToolUserAttemptUseEvent
- {
- public EntityUid User;
- public EntityUid? Target;
- public bool Cancelled = false;
-
- public ToolUserAttemptUseEvent(EntityUid user, EntityUid? target)
- {
- User = user;
- Target = target;
- }
- }
-
- /// <summary>
- /// Attempt event called *after* any do afters to see if the tool usage should succeed or not.
- /// You can use this event to consume any fuel needed.
- /// </summary>
- public sealed class ToolUseFinishAttemptEvent : CancellableEntityEventArgs
- {
- public float Fuel { get; }
- public EntityUid User { get; }
-
- public ToolUseFinishAttemptEvent(float fuel, EntityUid user)
- {
- Fuel = fuel;
- }
}
}
[RegisterComponent]
public sealed class VendingMachineRestockComponent : Component
{
- public CancellationTokenSource? CancelToken;
-
/// <summary>
/// The time (in seconds) that it takes to restock a machine.
/// </summary>
using System.Linq;
-using System.Threading;
-using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
-using Robust.Shared.Prototypes;
using Content.Server.Cargo.Systems;
using Content.Server.DoAfter;
using Content.Server.Wires;
+using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.VendingMachines;
+using Robust.Server.GameObjects;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
namespace Content.Server.VendingMachines.Restock
{
SubscribeLocalEvent<VendingMachineRestockComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<VendingMachineRestockComponent, PriceCalculationEvent>(OnPriceCalculation);
- SubscribeLocalEvent<VendingMachineRestockComponent, RestockCancelledEvent>(OnRestockCancelled);
}
public bool TryAccessMachine(EntityUid uid,
- VendingMachineRestockComponent component,
+ VendingMachineRestockComponent restock,
VendingMachineComponent machineComponent,
EntityUid user,
EntityUid target)
{
- if (!TryComp<WiresComponent>(target, out var wires) || !wires.IsPanelOpen) {
+ if (!TryComp<WiresComponent>(target, out var wires) || !wires.IsPanelOpen)
+ {
_popupSystem.PopupCursor(Loc.GetString("vending-machine-restock-needs-panel-open",
("this", uid),
("user", user),
EntityUid user,
EntityUid target)
{
- if (!component.CanRestock.Contains(machineComponent.PackPrototypeId)) {
- _popupSystem.PopupCursor(Loc.GetString("vending-machine-restock-invalid-inventory",
- ("this", uid),
- ("user", user),
- ("target", target)
- ),
- user);
+ if (!component.CanRestock.Contains(machineComponent.PackPrototypeId))
+ {
+ _popupSystem.PopupCursor(Loc.GetString("vending-machine-restock-invalid-inventory", ("this", uid), ("user", user), ("target", target)), user);
return false;
}
private void OnAfterInteract(EntityUid uid, VendingMachineRestockComponent component, AfterInteractEvent args)
{
- if (component.CancelToken != null || args.Target == null || !args.CanReach)
+ if (args.Target == null || !args.CanReach)
return;
if (!TryComp<VendingMachineComponent>(args.Target, out var machineComponent))
if (!TryAccessMachine(uid, component, machineComponent, args.User, args.Target.Value))
return;
- component.CancelToken = new CancellationTokenSource();
-
- _doAfterSystem.DoAfter(new DoAfterEventArgs(
- args.User,
- (float) component.RestockDelay.TotalSeconds,
- component.CancelToken.Token,
- args.Target,
- args.Used)
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, (float) component.RestockDelay.TotalSeconds, target:args.Target, used:uid)
{
- TargetFinishedEvent = new VendingMachineRestockEvent(args.User, uid),
- UsedCancelledEvent = new RestockCancelledEvent(),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
NeedHand = true
});
- _popupSystem.PopupEntity(Loc.GetString("vending-machine-restock-start",
- ("this", uid),
- ("user", args.User),
- ("target", args.Target)
- ),
+ _popupSystem.PopupEntity(Loc.GetString("vending-machine-restock-start", ("this", uid), ("user", args.User), ("target", args.Target)),
args.User,
PopupType.Medium);
- _audioSystem.PlayPvs(component.SoundRestockStart, component.Owner,
- AudioParams.Default
- .WithVolume(-2f)
- .WithVariation(0.2f));
+ _audioSystem.PlayPvs(component.SoundRestockStart, component.Owner, AudioParams.Default.WithVolume(-2f).WithVariation(0.2f));
}
private void OnPriceCalculation(EntityUid uid, VendingMachineRestockComponent component, ref PriceCalculationEvent args)
args.Price += priceSets.Max();
}
-
- private void OnRestockCancelled(EntityUid uid, VendingMachineRestockComponent component, RestockCancelledEvent args)
- {
- component.CancelToken?.Cancel();
- component.CancelToken = null;
- }
-
- public readonly struct RestockCancelledEvent { }
}
}
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Damage;
using Content.Shared.Destructible;
+using Content.Shared.DoAfter;
using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems;
using Content.Shared.Popups;
SubscribeLocalEvent<VendingMachineComponent, VendingMachineSelfDispenseEvent>(OnSelfDispense);
- SubscribeLocalEvent<VendingMachineComponent, VendingMachineRestockEvent>(OnRestock);
+ SubscribeLocalEvent<VendingMachineComponent, DoAfterEvent>(OnDoAfter);
}
private void OnVendingPrice(EntityUid uid, VendingMachineComponent component, ref PriceCalculationEvent args)
EjectRandom(uid, throwItem: true, forceEject: false, component);
}
- private void OnRestock(EntityUid uid, VendingMachineComponent component, VendingMachineRestockEvent args)
+ private void OnDoAfter(EntityUid uid, VendingMachineComponent component, DoAfterEvent args)
{
- if (!TryComp<VendingMachineRestockComponent>(args.RestockBox, out var restockComponent))
+ if (args.Handled || args.Cancelled || args.Args.Used == null)
+ return;
+
+ if (!TryComp<VendingMachineRestockComponent>(args.Args.Used, out var restockComponent))
{
- _sawmill.Error($"{ToPrettyString(args.User)} tried to restock {ToPrettyString(uid)} with {ToPrettyString(args.RestockBox)} which did not have a VendingMachineRestockComponent.");
+ _sawmill.Error($"{ToPrettyString(args.Args.User)} tried to restock {ToPrettyString(uid)} with {ToPrettyString(args.Args.Used.Value)} which did not have a VendingMachineRestockComponent.");
return;
}
TryRestockInventory(uid, component);
- _popupSystem.PopupEntity(Loc.GetString("vending-machine-restock-done",
- ("this", args.RestockBox),
- ("user", args.User),
- ("target", uid)),
- args.User,
- PopupType.Medium);
+ _popupSystem.PopupEntity(Loc.GetString("vending-machine-restock-done", ("this", args.Args.Used), ("user", args.Args.User), ("target", uid)), args.Args.User, PopupType.Medium);
+
+ _audioSystem.PlayPvs(restockComponent.SoundRestockDone, uid, AudioParams.Default.WithVolume(-2f).WithVariation(0.2f));
- _audioSystem.PlayPvs(restockComponent.SoundRestockDone, component.Owner,
- AudioParams.Default
- .WithVolume(-2f)
- .WithVariation(0.2f));
+ Del(args.Args.Used.Value);
- Del(args.RestockBox);
+ args.Handled = true;
}
/// <summary>
UpdateVendingMachineInterfaceState(vendComponent);
TryUpdateVisualState(uid, vendComponent);
}
-
- }
-
- public sealed class VendingMachineRestockEvent : EntityEventArgs
- {
- public EntityUid User { get; }
- public EntityUid RestockBox { get; }
-
- public VendingMachineRestockEvent(EntityUid user, EntityUid restockBox)
- {
- User = user;
- RestockBox = restockBox;
- }
}
}
using Content.Shared.Verbs;
using Robust.Shared.Player;
using Content.Server.Actions.Events;
+using Content.Shared.DoAfter;
using Content.Shared.Weapons.Melee.Events;
base.Initialize();
SubscribeLocalEvent<WieldableComponent, UseInHandEvent>(OnUseInHand);
- SubscribeLocalEvent<WieldableComponent, ItemWieldedEvent>(OnItemWielded);
+ SubscribeLocalEvent<WieldableComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<WieldableComponent, ItemUnwieldedEvent>(OnItemUnwielded);
SubscribeLocalEvent<WieldableComponent, GotUnequippedHandEvent>(OnItemLeaveHand);
SubscribeLocalEvent<WieldableComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
if (ev.Cancelled)
return;
- var doargs = new DoAfterEventArgs(
- user,
- component.WieldTime,
- default,
- used)
+ var doargs = new DoAfterEventArgs(user, component.WieldTime, used:used)
{
BreakOnUserMove = false,
BreakOnDamage = true,
BreakOnStun = true,
- BreakOnTargetMove = true,
- TargetFinishedEvent = new ItemWieldedEvent(user),
- UserFinishedEvent = new WieldedItemEvent(used)
+ BreakOnTargetMove = true
};
_doAfter.DoAfter(doargs);
return;
var targEv = new ItemUnwieldedEvent(user);
- var userEv = new UnwieldedItemEvent(used);
RaiseLocalEvent(used, targEv);
- RaiseLocalEvent(user, userEv);
}
- private void OnItemWielded(EntityUid uid, WieldableComponent component, ItemWieldedEvent args)
+ private void OnDoAfter(EntityUid uid, WieldableComponent component, DoAfterEvent args)
{
- if (args.User == null)
- return;
-
- if (!CanWield(uid, component, args.User.Value) || component.Wielded)
+ if (args.Handled || args.Cancelled || !CanWield(uid, component, args.Args.User) || component.Wielded)
return;
if (TryComp<ItemComponent>(uid, out var item))
component.Wielded = true;
if (component.WieldSound != null)
- {
_audioSystem.PlayPvs(component.WieldSound, uid);
- }
- for (var i = 0; i < component.FreeHandsRequired; i++)
+ for (int i = 0; i < component.FreeHandsRequired; i++)
{
- _virtualItemSystem.TrySpawnVirtualItemInHand(uid, args.User.Value);
+ _virtualItemSystem.TrySpawnVirtualItemInHand(uid, args.Args.User);
}
- _popupSystem.PopupEntity(Loc.GetString("wieldable-component-successful-wield",
- ("item", uid)), args.User.Value, args.User.Value);
- _popupSystem.PopupEntity(Loc.GetString("wieldable-component-successful-wield-other",
- ("user", args.User.Value),("item", uid)), args.User.Value, Filter.PvsExcept(args.User.Value), true);
+ _popupSystem.PopupEntity(Loc.GetString("wieldable-component-successful-wield", ("item", uid)), args.Args.User, args.Args.User);
+ _popupSystem.PopupEntity(Loc.GetString("wieldable-component-successful-wield-other", ("user", args.Args.User),("item", uid)), args.Args.User, Filter.PvsExcept(args.Args.User), true);
+
+ args.Handled = true;
}
private void OnItemUnwielded(EntityUid uid, WieldableComponent component, ItemUnwieldedEvent args)
{
}
- /// <summary>
- /// Raised on the item that has been wielded.
- /// </summary>
- public sealed class ItemWieldedEvent : EntityEventArgs
- {
- public EntityUid? User;
-
- public ItemWieldedEvent(EntityUid? user = null)
- {
- User = user;
- }
- }
-
- /// <summary>
- /// Raised on the user who wielded the item.
- /// </summary>
- public sealed class WieldedItemEvent : EntityEventArgs
- {
- public EntityUid Item;
-
- public WieldedItemEvent(EntityUid item)
- {
- Item = item;
- }
- }
-
public sealed class BeforeUnwieldEvent : CancellableEntityEventArgs
{
}
}
}
- /// <summary>
- /// Raised on the user who unwielded the item.
- /// </summary>
- public sealed class UnwieldedItemEvent : EntityEventArgs
- {
- public EntityUid Item;
-
- public UnwieldedItemEvent(EntityUid item)
- {
- Item = item;
- }
- }
-
#endregion
}
using Content.Server.DoAfter;
using Content.Server.Hands.Components;
using Content.Server.Power.Components;
-using Content.Server.Tools;
+using Content.Shared.DoAfter;
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.GameTicking;
using Content.Shared.Interaction;
using Content.Shared.Popups;
+using Content.Shared.Tools;
using Content.Shared.Tools.Components;
using Content.Shared.Wires;
using Robust.Server.GameObjects;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly DoAfterSystem _doAfter = default!;
- [Dependency] private readonly ToolSystem _toolSystem = default!;
+ [Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
SubscribeLocalEvent<WiresComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<WiresComponent, TimedWireEvent>(OnTimedWire);
SubscribeLocalEvent<WiresComponent, PowerChangedEvent>(OnWiresPowered);
- SubscribeLocalEvent<WiresComponent, OnWireDoAfterEvent>(OnWireDoAfter);
- SubscribeLocalEvent<WiresComponent, OnWireDoAfterCancelEvent>(OnWireDoAfterCancel);
+ SubscribeLocalEvent<WiresComponent, DoAfterEvent<WireExtraData>>(OnDoAfter);
}
private void SetOrCreateWireLayout(EntityUid uid, WiresComponent? wires = null)
TryDoWireAction(uid, player, activeHandEntity, args.Id, args.Action, component, tool);
}
- private void OnWireDoAfter(EntityUid uid, WiresComponent component, OnWireDoAfterEvent args)
+ private void OnDoAfter(EntityUid uid, WiresComponent component, DoAfterEvent<WireExtraData> args)
{
- UpdateWires(args.Target, args.User, args.Tool, args.Id, args.Action, component);
- }
+ if (args.Cancelled)
+ {
+ component.WiresQueue.Remove(args.AdditionalData.Id);
+ return;
+ }
- private void OnWireDoAfterCancel(EntityUid uid, WiresComponent component, OnWireDoAfterCancelEvent args)
- {
- component.WiresQueue.Remove(args.Id);
+ if (args.Handled || args.Args.Target == null || args.Args.Used == null)
+ return;
+
+ UpdateWires(args.Args.Target.Value, args.Args.User, args.Args.Used.Value, args.AdditionalData.Id, args.AdditionalData.Action, component);
+
+ args.Handled = true;
}
private void OnInteractUsing(EntityUid uid, WiresComponent component, InteractUsingEvent args)
}
else if (!component.IsScrewing && _toolSystem.HasQuality(args.Used, "Screwing", tool))
{
- component.IsScrewing = _toolSystem.UseTool(args.Used, args.User, uid,
- 0f, ScrewTime, new[] { "Screwing" },
- new WireToolFinishedEvent(uid, args.User),
- new WireToolCanceledEvent(uid),
- toolComponent: tool);
+ var toolEvData = new ToolEventData(new WireToolFinishedEvent(uid, args.User));
+
+ component.IsScrewing = _toolSystem.UseTool(args.Used, args.User, uid, ScrewTime, new[] { "Screwing" }, toolEvData, toolComponent: tool);
args.Handled = component.IsScrewing;
// Log attempt
if (_toolTime > 0f)
{
- var args = new DoAfterEventArgs(user, _toolTime, default, used)
+ var data = new WireExtraData(action, id);
+ var args = new DoAfterEventArgs(user, _toolTime, target:used, used:toolEntity)
{
NeedHand = true,
BreakOnStun = true,
BreakOnDamage = true,
- BreakOnUserMove = true,
- TargetFinishedEvent = new OnWireDoAfterEvent
- {
- Target = used,
- User = user,
- Tool = toolEntity,
- Action = action,
- Id = id
- },
- TargetCancelledEvent = new OnWireDoAfterCancelEvent
- {
- Id = id
- }
+ BreakOnUserMove = true
};
- _doAfter.DoAfter(args);
+ _doAfter.DoAfter(args, data);
}
else
{
}
}
-
+ private record struct WireExtraData(WiresAction Action, int Id)
+ {
+ public WiresAction Action = Action;
+ public int Id = Id;
+ }
private void UpdateWires(EntityUid used, EntityUid user, EntityUid toolEntity, int id, WiresAction action, WiresComponent? wires = null, ToolComponent? tool = null)
{
}
public record struct WireToolCanceledEvent(EntityUid Target);
-
- private sealed class OnWireDoAfterEvent : EntityEventArgs
- {
- public EntityUid User { get; set; }
- public EntityUid Target { get; set; }
- public EntityUid Tool { get; set; }
- public WiresAction Action { get; set; }
- public int Id { get; set; }
- }
-
- private sealed class OnWireDoAfterCancelEvent : EntityEventArgs
- {
- public int Id { get; set; }
- }
#endregion
}
-using System.Threading;
using Content.Shared.Construction.EntitySystems;
using Content.Shared.Tools;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("delay")]
public float Delay = 1f;
-
- public CancellationTokenSource? CancelToken = null;
}
public abstract class BaseAnchoredAttemptEvent : CancellableEntityEventArgs
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.DoAfter;
+
+/// <summary>
+/// Added to entities that are currently performing any doafters.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed class ActiveDoAfterComponent : Component
+{
+
+}
--- /dev/null
+using System.Threading.Tasks;
+using Content.Shared.Hands.Components;
+using Robust.Shared.Map;
+using Robust.Shared.Serialization;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.DoAfter;
+[Serializable, NetSerializable]
+[DataDefinition]
+public sealed class DoAfter
+{
+ [NonSerialized]
+ [Obsolete]
+ public Task<DoAfterStatus> AsTask;
+
+ [NonSerialized]
+ [Obsolete("Will be obsolete for EventBus")]
+ public TaskCompletionSource<DoAfterStatus> Tcs;
+
+ //TODO: Should be merged into here
+ public readonly DoAfterEventArgs EventArgs;
+
+ //ID so the client DoAfterSystem can track
+ public byte ID;
+
+ public bool Cancelled = false;
+
+ //Cache the delay so the timer properly shows
+ public float Delay;
+
+ //Keep track of the time this DoAfter started
+ public TimeSpan StartTime;
+
+ //Keep track of the time this DoAfter was cancelled
+ public TimeSpan CancelledTime;
+
+ //How long has the do after been running?
+ public TimeSpan Elapsed = TimeSpan.Zero;
+
+ /// <summary>
+ /// Accrued time when cancelled.
+ /// </summary>
+ public TimeSpan CancelledElapsed = TimeSpan.Zero;
+
+ public EntityCoordinates UserGrid;
+ public EntityCoordinates TargetGrid;
+
+ [NonSerialized]
+ public Action<bool>? Done;
+
+#pragma warning disable RA0004
+ public DoAfterStatus Status => AsTask.IsCompletedSuccessfully ? AsTask.Result : DoAfterStatus.Running;
+#pragma warning restore RA0004
+
+ // NeedHand
+ public readonly string? ActiveHand;
+ public readonly EntityUid? ActiveItem;
+
+ public DoAfter(DoAfterEventArgs eventArgs, IEntityManager entityManager)
+ {
+ EventArgs = eventArgs;
+ StartTime = IoCManager.Resolve<IGameTiming>().CurTime;
+
+ if (eventArgs.BreakOnUserMove)
+ UserGrid = entityManager.GetComponent<TransformComponent>(eventArgs.User).Coordinates;
+
+ if (eventArgs.Target != null && eventArgs.BreakOnTargetMove)
+ // Target should never be null if the bool is set.
+ TargetGrid = entityManager.GetComponent<TransformComponent>(eventArgs.Target!.Value).Coordinates;
+
+ // For this we need to stay on the same hand slot and need the same item in that hand slot
+ // (or if there is no item there we need to keep it free).
+ if (eventArgs.NeedHand && entityManager.TryGetComponent(eventArgs.User, out SharedHandsComponent? handsComponent))
+ {
+ ActiveHand = handsComponent.ActiveHand?.Name;
+ ActiveItem = handsComponent.ActiveHandEntity;
+ }
+
+ Tcs = new TaskCompletionSource<DoAfterStatus>();
+ AsTask = Tcs.Task;
+ }
+}
--- /dev/null
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.DoAfter;
+
+[RegisterComponent, NetworkedComponent]
+public sealed class DoAfterComponent : Component
+{
+ [DataField("doAfters")]
+ public readonly Dictionary<byte, DoAfter> DoAfters = new();
+
+ [DataField("cancelledDoAfters")]
+ public readonly Dictionary<byte, DoAfter> CancelledDoAfters = new();
+
+ // So the client knows which one to update (and so we don't send all of the do_afters every time 1 updates)
+ // we'll just send them the index. Doesn't matter if it wraps around.
+ [DataField("runningIndex")]
+ public byte RunningIndex;
+}
+
+[Serializable, NetSerializable]
+public sealed class DoAfterComponentState : ComponentState
+{
+ public Dictionary<byte, DoAfter> DoAfters;
+
+ public DoAfterComponentState(Dictionary<byte, DoAfter> doAfters)
+ {
+ DoAfters = doAfters;
+ }
+}
+
+/// <summary>
+/// Use this event to raise your DoAfter events now.
+/// Check for cancelled, and if it is, then null the token there.
+/// </summary>
+/// TODO: Add a networked DoAfterEvent to pass in AdditionalData for the future
+[Serializable, NetSerializable]
+public sealed class DoAfterEvent : HandledEntityEventArgs
+{
+ public bool Cancelled;
+ public readonly DoAfterEventArgs Args;
+
+ public DoAfterEvent(bool cancelled, DoAfterEventArgs args)
+ {
+ Cancelled = cancelled;
+ Args = args;
+ }
+}
+
+/// <summary>
+/// Use this event to raise your DoAfter events now.
+/// Check for cancelled, and if it is, then null the token there.
+/// Can't be serialized
+/// </summary>
+/// TODO: Net/Serilization isn't supported so this needs to be networked somehow
+public sealed class DoAfterEvent<T> : HandledEntityEventArgs
+{
+ public T AdditionalData;
+ public bool Cancelled;
+ public readonly DoAfterEventArgs Args;
+
+ public DoAfterEvent(T additionalData, bool cancelled, DoAfterEventArgs args)
+ {
+ AdditionalData = additionalData;
+ Cancelled = cancelled;
+ Args = args;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed class CancelledDoAfterMessage : EntityEventArgs
+{
+ public EntityUid Uid;
+ public byte ID;
+
+ public CancelledDoAfterMessage(EntityUid uid, byte id)
+ {
+ Uid = uid;
+ ID = id;
+ }
+}
+
+[Serializable, NetSerializable]
+public enum DoAfterStatus : byte
+{
+ Running,
+ Cancelled,
+ Finished,
+}
--- /dev/null
+using System.Threading;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Serialization;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.DoAfter;
+//TODO: Merge into DoAfter
+[Serializable, NetSerializable]
+public sealed class DoAfterEventArgs
+{
+ /// <summary>
+ /// The entity invoking do_after
+ /// </summary>
+ public EntityUid User;
+
+ /// <summary>
+ /// How long does the do_after require to complete
+ /// </summary>
+ public float Delay;
+
+ /// <summary>
+ /// Applicable target (if relevant)
+ /// </summary>
+ public EntityUid? Target;
+
+ /// <summary>
+ /// Entity used by the User on the Target.
+ /// </summary>
+ public EntityUid? Used;
+
+ public bool RaiseOnUser = true;
+
+ public bool RaiseOnTarget = true;
+
+ public bool RaiseOnUsed = true;
+
+ /// <summary>
+ /// Manually cancel the do_after so it no longer runs
+ /// </summary>
+ [NonSerialized]
+ public CancellationToken CancelToken;
+
+ // Break the chains
+ /// <summary>
+ /// Whether we need to keep our active hand as is (i.e. can't change hand or change item).
+ /// This also covers requiring the hand to be free (if applicable).
+ /// </summary>
+ public bool NeedHand;
+
+ /// <summary>
+ /// If do_after stops when the user moves
+ /// </summary>
+ public bool BreakOnUserMove;
+
+ /// <summary>
+ /// If do_after stops when the target moves (if there is a target)
+ /// </summary>
+ public bool BreakOnTargetMove;
+
+ /// <summary>
+ /// Threshold for user and target movement
+ /// </summary>
+ public float MovementThreshold;
+
+ public bool BreakOnDamage;
+
+ /// <summary>
+ /// Threshold for user damage
+ /// </summary>
+ public FixedPoint2? DamageThreshold;
+ public bool BreakOnStun;
+
+ /// <summary>
+ /// Should the DoAfter event broadcast?
+ /// </summary>
+ public bool Broadcast;
+
+ /// <summary>
+ /// Threshold for distance user from the used OR target entities.
+ /// </summary>
+ public float? DistanceThreshold;
+
+ /// <summary>
+ /// Requires a function call once at the end (like InRangeUnobstructed).
+ /// </summary>
+ /// <remarks>
+ /// Anything that needs a pre-check should do it itself so no DoAfterState is ever sent to the client.
+ /// </remarks>
+ [NonSerialized]
+ //TODO: Replace with eventbus
+ public Func<bool>? PostCheck;
+
+ /// <summary>
+ /// Additional conditions that need to be met. Return false to cancel.
+ /// </summary>
+ [NonSerialized]
+ //TODO Replace with eventbus
+ public Func<bool>? ExtraCheck;
+
+ public DoAfterEventArgs(
+ EntityUid user,
+ float delay,
+ CancellationToken cancelToken = default,
+ EntityUid? target = null,
+ EntityUid? used = null)
+ {
+ User = user;
+ Delay = delay;
+ CancelToken = cancelToken;
+ Target = target;
+ Used = used;
+ MovementThreshold = 0.1f;
+ DamageThreshold = 1.0;
+
+ if (Target == null)
+ {
+ DebugTools.Assert(!BreakOnTargetMove);
+ BreakOnTargetMove = false;
+ }
+ }
+}
+++ /dev/null
-using Content.Shared.FixedPoint;
-using Robust.Shared.GameStates;
-using Robust.Shared.Map;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.DoAfter
-{
- [NetworkedComponent()]
- public abstract class SharedDoAfterComponent : Component
- {
- }
-
- [Serializable, NetSerializable]
- public sealed class DoAfterComponentState : ComponentState
- {
- public List<ClientDoAfter> DoAfters { get; }
-
- public DoAfterComponentState(List<ClientDoAfter> doAfters)
- {
- DoAfters = doAfters;
- }
- }
-
- [Serializable, NetSerializable]
- public sealed class CancelledDoAfterMessage : EntityEventArgs
- {
- public EntityUid Uid;
- public byte ID { get; }
-
- public CancelledDoAfterMessage(EntityUid uid, byte id)
- {
- Uid = uid;
- ID = id;
- }
- }
-
- // TODO: Merge this with the actual DoAfter
- /// <summary>
- /// We send a trimmed-down version of the DoAfter for the client for it to use.
- /// </summary>
- [Serializable, NetSerializable]
- public sealed class ClientDoAfter
- {
- public bool Cancelled = false;
-
- /// <summary>
- /// Accrued time when cancelled.
- /// </summary>
- public float CancelledAccumulator;
-
- // To see what these do look at DoAfter and DoAfterEventArgs
- public byte ID { get; }
-
- public TimeSpan StartTime { get; }
-
- public EntityCoordinates UserGrid { get; }
-
- public EntityCoordinates TargetGrid { get; }
-
- public EntityUid? Target { get; }
-
- public float Accumulator;
-
- public float Delay { get; }
-
- // TODO: The other ones need predicting
- public bool BreakOnUserMove { get; }
-
- public bool BreakOnTargetMove { get; }
-
- public float MovementThreshold { get; }
-
- public FixedPoint2 DamageThreshold { get; }
-
- public ClientDoAfter(byte id, EntityCoordinates userGrid, EntityCoordinates targetGrid, TimeSpan startTime,
- float delay, bool breakOnUserMove, bool breakOnTargetMove, float movementThreshold, FixedPoint2 damageThreshold, EntityUid? target = null)
- {
- ID = id;
- UserGrid = userGrid;
- TargetGrid = targetGrid;
- StartTime = startTime;
- Delay = delay;
- BreakOnUserMove = breakOnUserMove;
- BreakOnTargetMove = breakOnTargetMove;
- MovementThreshold = movementThreshold;
- DamageThreshold = damageThreshold;
- Target = target;
- }
- }
-}
--- /dev/null
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Shared.Damage;
+using Content.Shared.Hands.Components;
+using Content.Shared.Mobs;
+using Content.Shared.Stunnable;
+using Robust.Shared.GameStates;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.DoAfter;
+
+public abstract class SharedDoAfterSystem : EntitySystem
+{
+ [Dependency] protected readonly IGameTiming GameTiming = default!;
+
+ // We cache the list as to not allocate every update tick...
+ private readonly Queue<DoAfter> _pending = new();
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<DoAfterComponent, DamageChangedEvent>(OnDamage);
+ SubscribeLocalEvent<DoAfterComponent, MobStateChangedEvent>(OnStateChanged);
+ SubscribeLocalEvent<DoAfterComponent, ComponentGetState>(OnDoAfterGetState);
+ }
+
+ private void Add(EntityUid entity, DoAfterComponent component, DoAfter doAfter)
+ {
+ doAfter.ID = component.RunningIndex;
+ doAfter.Delay = doAfter.EventArgs.Delay;
+ component.DoAfters.Add(component.RunningIndex, doAfter);
+ EnsureComp<ActiveDoAfterComponent>(entity);
+ component.RunningIndex++;
+ Dirty(component);
+ }
+
+ private void OnDoAfterGetState(EntityUid uid, DoAfterComponent component, ref ComponentGetState args)
+ {
+ args.State = new DoAfterComponentState(component.DoAfters);
+ }
+
+ private void Cancelled(DoAfterComponent component, DoAfter doAfter)
+ {
+ if (!component.DoAfters.TryGetValue(doAfter.ID, out var index))
+ return;
+
+ component.DoAfters.Remove(doAfter.ID);
+
+ if (component.DoAfters.Count == 0)
+ RemComp<ActiveDoAfterComponent>(component.Owner);
+
+ RaiseNetworkEvent(new CancelledDoAfterMessage(component.Owner, index.ID));
+ }
+
+ /// <summary>
+ /// Call when the particular DoAfter is finished.
+ /// Client should be tracking this independently.
+ /// </summary>
+ private void Finished(DoAfterComponent component, DoAfter doAfter)
+ {
+ if (!component.DoAfters.ContainsKey(doAfter.ID))
+ return;
+
+ component.DoAfters.Remove(doAfter.ID);
+
+ if (component.DoAfters.Count == 0)
+ RemComp<ActiveDoAfterComponent>(component.Owner);
+ }
+
+ private void OnStateChanged(EntityUid uid, DoAfterComponent component, MobStateChangedEvent args)
+ {
+ if (args.NewMobState != MobState.Dead || args.NewMobState != MobState.Critical)
+ return;
+
+ foreach (var (_, doAfter) in component.DoAfters)
+ {
+ Cancel(uid, doAfter, component);
+ }
+ }
+
+ /// <summary>
+ /// Cancels DoAfter if it breaks on damage and it meets the threshold
+ /// </summary>
+ /// <param name="uid">The EntityUID of the user</param>
+ /// <param name="component"></param>
+ /// <param name="args"></param>
+ private void OnDamage(EntityUid uid, DoAfterComponent component, DamageChangedEvent args)
+ {
+ if (!args.InterruptsDoAfters || !args.DamageIncreased || args.DamageDelta == null)
+ return;
+
+ foreach (var doAfter in component.DoAfters.Values)
+ {
+ if (doAfter.EventArgs.BreakOnDamage && args.DamageDelta?.Total.Float() > doAfter.EventArgs.DamageThreshold)
+ Cancel(uid, doAfter, component);
+ }
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ foreach (var (_, comp) in EntityManager.EntityQuery<ActiveDoAfterComponent, DoAfterComponent>())
+ {
+ foreach (var doAfter in comp.DoAfters.Values.ToArray())
+ {
+ Run(comp.Owner, comp, doAfter);
+
+ switch (doAfter.Status)
+ {
+ case DoAfterStatus.Running:
+ break;
+ case DoAfterStatus.Cancelled:
+ _pending.Enqueue(doAfter);
+ break;
+ case DoAfterStatus.Finished:
+ _pending.Enqueue(doAfter);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ while (_pending.TryDequeue(out var doAfter))
+ {
+ if (doAfter.Status == DoAfterStatus.Cancelled)
+ {
+ Cancelled(comp, doAfter);
+
+ if (doAfter.Done != null)
+ doAfter.Done(true);
+ }
+
+ if (doAfter.Status == DoAfterStatus.Finished)
+ {
+ Finished(comp, doAfter);
+
+ if (doAfter.Done != null)
+ doAfter.Done(false);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Tasks that are delayed until the specified time has passed
+ /// These can be potentially cancelled by the user moving or when other things happen.
+ /// </summary>
+ /// <param name="eventArgs"></param>
+ /// <returns></returns>
+ [Obsolete("Use the synchronous version instead, DoAfter")]
+ public async Task<DoAfterStatus> WaitDoAfter(DoAfterEventArgs eventArgs)
+ {
+ var doAfter = CreateDoAfter(eventArgs);
+
+ await doAfter.AsTask;
+
+ return doAfter.Status;
+ }
+
+ /// <summary>
+ /// Creates a DoAfter without waiting for it to finish. You can use events with this.
+ /// These can be potentially cancelled by the user moving or when other things happen.
+ /// Use this when you need to send extra data with the DoAfter
+ /// </summary>
+ /// <param name="eventArgs">The DoAfterEventArgs</param>
+ /// <param name="data">The extra data sent over </param>
+ public void DoAfter<T>(DoAfterEventArgs eventArgs, T data)
+ {
+ var doAfter = CreateDoAfter(eventArgs);
+
+ doAfter.Done = cancelled => { Send(data, cancelled, eventArgs); };
+ }
+
+ /// <summary>
+ /// Creates a DoAfter without waiting for it to finish. You can use events with this.
+ /// These can be potentially cancelled by the user moving or when other things happen.
+ /// Use this if you don't have any extra data to send with the DoAfter
+ /// </summary>
+ /// <param name="eventArgs">The DoAfterEventArgs</param>
+ public DoAfter DoAfter(DoAfterEventArgs eventArgs)
+ {
+ var doAfter = CreateDoAfter(eventArgs);
+
+ doAfter.Done = cancelled => { Send(cancelled, eventArgs); };
+
+ return doAfter;
+ }
+
+ private DoAfter CreateDoAfter(DoAfterEventArgs eventArgs)
+ {
+ // Setup
+ eventArgs.CancelToken = new CancellationToken();
+ var doAfter = new DoAfter(eventArgs, EntityManager);
+ // Caller's gonna be responsible for this I guess
+ var doAfterComponent = Comp<DoAfterComponent>(eventArgs.User);
+ doAfter.ID = doAfterComponent.RunningIndex;
+ doAfter.StartTime = GameTiming.CurTime;
+ Add(eventArgs.User, doAfterComponent, doAfter);
+ return doAfter;
+ }
+
+ private void Run(EntityUid entity, DoAfterComponent comp, DoAfter doAfter)
+ {
+ switch (doAfter.Status)
+ {
+ case DoAfterStatus.Running:
+ break;
+ case DoAfterStatus.Cancelled:
+ case DoAfterStatus.Finished:
+ return;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ doAfter.Elapsed = GameTiming.CurTime - doAfter.StartTime;
+
+ if (IsFinished(doAfter))
+ {
+ if (!TryPostCheck(doAfter))
+ {
+ Cancel(entity, doAfter, comp);
+ }
+ else
+ {
+ doAfter.Tcs.SetResult(DoAfterStatus.Finished);
+ }
+
+ return;
+ }
+
+ if (IsCancelled(doAfter))
+ {
+ Cancel(entity, doAfter, comp);
+ }
+ }
+
+ private bool TryPostCheck(DoAfter doAfter)
+ {
+ return doAfter.EventArgs.PostCheck?.Invoke() != false;
+ }
+
+ private bool IsFinished(DoAfter doAfter)
+ {
+ var delay = TimeSpan.FromSeconds(doAfter.EventArgs.Delay);
+
+ if (doAfter.Elapsed <= delay)
+ return false;
+
+ return true;
+ }
+
+ private bool IsCancelled(DoAfter doAfter)
+ {
+ var eventArgs = doAfter.EventArgs;
+ var xForm = GetEntityQuery<TransformComponent>();
+
+ if (!Exists(eventArgs.User) || eventArgs.Target is { } target && !Exists(target))
+ return true;
+
+ if (eventArgs.CancelToken.IsCancellationRequested)
+ return true;
+
+ //TODO: Handle Inertia in space
+ if (eventArgs.BreakOnUserMove && !xForm.GetComponent(eventArgs.User).Coordinates
+ .InRange(EntityManager, doAfter.UserGrid, eventArgs.MovementThreshold))
+ return true;
+
+ if (eventArgs.Target != null && eventArgs.BreakOnTargetMove && !xForm.GetComponent(eventArgs.Target!.Value)
+ .Coordinates.InRange(EntityManager, doAfter.TargetGrid, eventArgs.MovementThreshold))
+ return true;
+
+ if (eventArgs.ExtraCheck != null && !eventArgs.ExtraCheck.Invoke())
+ return true;
+
+ if (eventArgs.BreakOnStun && HasComp<StunnedComponent>(eventArgs.User))
+ return true;
+
+ if (eventArgs.NeedHand)
+ {
+ if (!TryComp<SharedHandsComponent>(eventArgs.User, out var handsComp))
+ {
+ //TODO: Figure out active hand and item values
+
+ // If we had a hand but no longer have it that's still a paddlin'
+ if (doAfter.ActiveHand != null)
+ return true;
+ }
+ else
+ {
+ var currentActiveHand = handsComp.ActiveHand?.Name;
+ if (doAfter.ActiveHand != currentActiveHand)
+ return true;
+
+ var currentItem = handsComp.ActiveHandEntity;
+ if (doAfter.ActiveItem != currentItem)
+ return true;
+ }
+ }
+
+ if (eventArgs.DistanceThreshold != null)
+ {
+ var userXform = xForm.GetComponent(eventArgs.User);
+
+ if (eventArgs.Target != null && !eventArgs.User.Equals(eventArgs.Target))
+ {
+ //recalculate Target location in case Target has also moved
+ var targetCoords = xForm.GetComponent(eventArgs.Target.Value).Coordinates;
+ if (!userXform.Coordinates.InRange(EntityManager, targetCoords, eventArgs.DistanceThreshold.Value))
+ return true;
+ }
+
+ if (eventArgs.Used != null)
+ {
+ var usedCoords = xForm.GetComponent(eventArgs.Used.Value).Coordinates;
+ if (!userXform.Coordinates.InRange(EntityManager, usedCoords, eventArgs.DistanceThreshold.Value))
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void Cancel(EntityUid entity, DoAfter doAfter, DoAfterComponent? comp = null)
+ {
+ if (!Resolve(entity, ref comp, false))
+ return;
+
+ if (comp.CancelledDoAfters.ContainsKey(doAfter.ID))
+ return;
+
+ if (!comp.DoAfters.ContainsKey(doAfter.ID))
+ return;
+
+ doAfter.Cancelled = true;
+ doAfter.CancelledTime = GameTiming.CurTime;
+
+ var doAfterMessage = comp.DoAfters[doAfter.ID];
+ comp.CancelledDoAfters.Add(doAfter.ID, doAfterMessage);
+
+ if (doAfter.Status == DoAfterStatus.Running)
+ {
+ doAfter.Tcs.SetResult(DoAfterStatus.Cancelled);
+ }
+ }
+
+ /// <summary>
+ /// Send the DoAfter event, used where you don't need any extra data to send.
+ /// </summary>
+ /// <param name="cancelled"></param>
+ /// <param name="args"></param>
+ private void Send(bool cancelled, DoAfterEventArgs args)
+ {
+ var ev = new DoAfterEvent(cancelled, args);
+
+ RaiseDoAfterEvent(ev, args);
+ }
+
+ /// <summary>
+ /// Send the DoAfter event, used where you need extra data to send
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="cancelled"></param>
+ /// <param name="args"></param>
+ /// <typeparam name="T"></typeparam>
+ private void Send<T>(T data, bool cancelled, DoAfterEventArgs args)
+ {
+ var ev = new DoAfterEvent<T>(data, cancelled, args);
+
+ RaiseDoAfterEvent(ev, args);
+ }
+
+ private void RaiseDoAfterEvent<TEvent>(TEvent ev, DoAfterEventArgs args) where TEvent : notnull
+ {
+ if (EntityManager.EntityExists(args.User) && args.RaiseOnUser)
+ RaiseLocalEvent(args.User, ev, args.Broadcast);
+
+ if (args.Target is { } target && EntityManager.EntityExists(target) && args.RaiseOnTarget)
+ RaiseLocalEvent(target, ev, args.Broadcast);
+
+ if (args.Used is { } used && EntityManager.EntityExists(used) && args.RaiseOnUsed)
+ RaiseLocalEvent(used, ev, args.Broadcast);
+ }
+}
[DataField("canMoveBreakout")]
public bool CanMoveBreakout;
- public CancellationTokenSource? CancelToken;
}
/// <summary>
/// </summary>
[ViewVariables]
public EntityUid? EquipmentOwner;
-
- public CancellationTokenSource? TokenSource = null;
}
/// <summary>
-using System.Threading;
-using Content.Shared.Body.Components;
+using Content.Shared.Body.Components;
using Content.Shared.DragDrop;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
public bool IsPrying { get; set; }
- public CancellationTokenSource? DragDropCancelToken;
-
[Serializable, NetSerializable]
public enum CryoPodVisuals : byte
{
-using Content.Server.Medical.Components;
+using Content.Server.Medical.Components;
+using Content.Shared.DoAfter;
using Content.Shared.Body.Components;
using Content.Shared.DragDrop;
using Content.Shared.Emag.Systems;
args.Handled = true;
}
- protected void DoInsertCryoPod(EntityUid uid, SharedCryoPodComponent cryoPodComponent, DoInsertCryoPodEvent args)
- {
- cryoPodComponent.DragDropCancelToken = null;
- InsertBody(uid, args.ToInsert, cryoPodComponent);
- }
-
- protected void DoInsertCancelCryoPod(EntityUid uid, SharedCryoPodComponent cryoPodComponent, DoInsertCancelledCryoPodEvent args)
- {
- cryoPodComponent.DragDropCancelToken = null;
- }
-
protected void OnCryoPodPryFinished(EntityUid uid, SharedCryoPodComponent cryoPodComponent, CryoPodPryFinished args)
{
cryoPodComponent.IsPrying = false;
#region Event records
- protected record DoInsertCryoPodEvent(EntityUid ToInsert);
- protected record DoInsertCancelledCryoPodEvent;
protected record CryoPodPryFinished;
protected record CryoPodPryInterrupted;
return;
}
- if (!_toolSystem.UseTool(args.Used, args.User, uid, 0f, 0f, component.KeysExtractionMethod, toolComponent: tool))
+ var toolEvData = new ToolEventData(null);
+
+ if(!_toolSystem.UseTool(args.Used, args.User, uid, 0f, new[] { component.KeysExtractionMethod }, toolEvData, toolComponent: tool))
return;
var contained = component.KeyContainer.ContainedEntities.ToArray();
-using System.Threading;
using Content.Shared.Disease;
using Content.Shared.FixedPoint;
using Content.Shared.Store;
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("maxEssenceUpgradeAmount")]
public float MaxEssenceUpgradeAmount = 10;
-
- public CancellationTokenSource? SoulSearchCancelToken;
- public CancellationTokenSource? HarvestCancelToken;
#endregion
//In the nearby radius, causes various objects to be thrown, messed with, and containers opened
public TimeSpan DelayPerItem = TimeSpan.FromSeconds(0.2);
/// <summary>
- /// Cancellation token for the doafter.
+ /// The multiplier modifier
/// </summary>
- public CancellationTokenSource? CancelToken;
+ [DataField("multiplier")]
+ public float Multiplier = 1.0f;
}
}
-using System.Threading;
-using Content.Shared.Audio;
-using Robust.Shared.Audio;
+using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
[DataField("portalCreationDelay")]
public float PortalCreationDelay = 2.5f;
- public CancellationTokenSource? CancelToken = null;
}
-
-/// <summary>
-/// Raised on doafter success for creating a portal.
-/// </summary>
-public record HandTeleporterSuccessEvent(EntityUid User);
-
-/// <summary>
-/// Raised on doafter cancel for creating a portal.
-/// </summary>
-public record HandTeleporterCancelledEvent;
[DataField("useSound")]
public SoundSpecifier? UseSound { get; set; }
}
+
+ /// <summary>
+ /// Attempt event called *before* any do afters to see if the tool usage should succeed or not.
+ /// You can change the fuel consumption by changing the Fuel property.
+ /// </summary>
+ public sealed class ToolUseAttemptEvent : CancellableEntityEventArgs
+ {
+ public float Fuel { get; set; }
+ public EntityUid User { get; }
+
+ public ToolUseAttemptEvent(float fuel, EntityUid user)
+ {
+ Fuel = fuel;
+ User = user;
+ }
+ }
+
+ /// <summary>
+ /// Event raised on the user of a tool to see if they can actually use it.
+ /// </summary>
+ [ByRefEvent]
+ public struct ToolUserAttemptUseEvent
+ {
+ public EntityUid User;
+ public EntityUid? Target;
+ public bool Cancelled = false;
+
+ public ToolUserAttemptUseEvent(EntityUid user, EntityUid? target)
+ {
+ User = user;
+ Target = target;
+ }
+ }
+
+ /// <summary>
+ /// Attempt event called *after* any do afters to see if the tool usage should succeed or not.
+ /// You can use this event to consume any fuel needed.
+ /// </summary>
+ public sealed class ToolUseFinishAttemptEvent : CancellableEntityEventArgs
+ {
+ public float Fuel { get; }
+ public EntityUid User { get; }
+
+ public ToolUseFinishAttemptEvent(float fuel, EntityUid user)
+ {
+ Fuel = fuel;
+ }
+ }
+
+ public sealed class ToolEventData
+ {
+ public readonly Object? Ev;
+ public readonly Object? CancelledEv;
+ public readonly float Fuel;
+ public readonly EntityUid? TargetEntity;
+
+ public ToolEventData(Object? ev, float fuel = 0f, Object? cancelledEv = null, EntityUid? targetEntity = null)
+ {
+ Ev = ev;
+ CancelledEv = cancelledEv;
+ Fuel = fuel;
+ TargetEntity = targetEntity;
+ }
+ }
}
using System.Linq;
-using System.Threading;
+using Content.Shared.Audio;
+using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Tools.Components;
+using Robust.Shared.Audio;
using Robust.Shared.GameStates;
+using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Shared.Tools;
{
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
public override void Initialize()
{
SubscribeLocalEvent<MultipleToolComponent, ActivateInWorldEvent>(OnMultipleToolActivated);
SubscribeLocalEvent<MultipleToolComponent, ComponentGetState>(OnMultipleToolGetState);
SubscribeLocalEvent<MultipleToolComponent, ComponentHandleState>(OnMultipleToolHandleState);
+
+ SubscribeLocalEvent<ToolComponent, DoAfterEvent<ToolEventData>>(OnDoAfter);
+
+ SubscribeLocalEvent<ToolDoAfterComplete>(OnDoAfterComplete);
+ SubscribeLocalEvent<ToolDoAfterCancelled>(OnDoAfterCancelled);
}
- public bool UseTool(EntityUid tool, EntityUid user, EntityUid? target, float fuel,
- float doAfterDelay, string toolQualityNeeded, object? doAfterCompleteEvent = null, object? doAfterCancelledEvent = null, EntityUid? doAfterEventTarget = null,
- Func<bool>? doAfterCheck = null, ToolComponent? toolComponent = null)
+ private void OnDoAfter(EntityUid uid, ToolComponent component, DoAfterEvent<ToolEventData> args)
{
- return UseTool(tool, user, target, fuel, doAfterDelay, new[] { toolQualityNeeded },
- doAfterCompleteEvent, doAfterCancelledEvent, doAfterEventTarget, doAfterCheck, toolComponent);
+ if (args.Handled || args.Cancelled || args.AdditionalData.Ev == null)
+ return;
+
+ if (ToolFinishUse(uid, args.Args.User, args.AdditionalData.Fuel))
+ {
+ if (args.AdditionalData.TargetEntity != null)
+ RaiseLocalEvent(args.AdditionalData.TargetEntity.Value, args.AdditionalData.Ev);
+ else
+ RaiseLocalEvent(args.AdditionalData.Ev);
+
+ args.Handled = true;
+ }
+ else 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;
+ }
}
- public virtual bool UseTool(
- EntityUid tool,
- EntityUid user,
- EntityUid? target,
- float fuel,
- float doAfterDelay,
- IEnumerable<string> toolQualitiesNeeded,
- object? doAfterCompleteEvent = null,
- object? doAfterCancelledEvent = null,
- EntityUid? doAfterEventTarget = null,
- Func<bool>? doAfterCheck = null,
- ToolComponent? toolComponent = null,
- CancellationToken? cancelToken = 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)
{
- // predicted tools when.
- return false;
+ // 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))
+ return false;
+
+ var ev = new ToolUserAttemptUseEvent(user, target);
+ RaiseLocalEvent(user, ref ev);
+ if (ev.Cancelled)
+ return false;
+
+ if (!ToolStartUse(tool, user, fuel, toolQualitiesNeeded, toolComponent))
+ return false;
+
+ if (doAfterDelay > 0f)
+ {
+ var doAfterArgs = new DoAfterEventArgs(user, doAfterDelay / toolComponent.SpeedModifier, target:target, used:tool)
+ {
+ ExtraCheck = doAfterCheck,
+ BreakOnDamage = true,
+ BreakOnStun = true,
+ BreakOnTargetMove = true,
+ BreakOnUserMove = true,
+ NeedHand = true
+ };
+
+ _doAfterSystem.DoAfter(doAfterArgs, toolEventData);
+ return true;
+ }
+
+ return ToolFinishUse(tool, user, fuel, toolComponent);
+ }
+
+ public bool UseTool(EntityUid tool, EntityUid user, EntityUid? target, float doAfterDelay, string toolQualityNeeded,
+ ToolEventData toolEventData, float fuel = 0, ToolComponent? toolComponent = null,
+ Func<bool>? doAfterCheck = null)
+ {
+ return UseTool(tool, user, target, doAfterDelay, new[] { toolQualityNeeded }, toolEventData, fuel,
+ toolComponent, doAfterCheck);
}
private void OnMultipleToolHandleState(EntityUid uid, MultipleToolComponent component, ref ComponentHandleState args)
if (_protoMan.TryIndex(current.Behavior.First(), out ToolQualityPrototype? quality))
multiple.CurrentQualityName = Loc.GetString(quality.Name);
}
+
+ /// <summary>
+ /// Whether a tool entity has the specified quality or not.
+ /// </summary>
+ public bool HasQuality(EntityUid uid, string quality, ToolComponent? tool = null)
+ {
+ return Resolve(uid, ref tool, false) && tool.Qualities.Contains(quality);
+ }
+
+ /// <summary>
+ /// Whether a tool entity has all specified qualities or not.
+ /// </summary>
+ public bool HasAllQualities(EntityUid uid, IEnumerable<string> qualities, ToolComponent? tool = null)
+ {
+ return Resolve(uid, ref tool, false) && tool.Qualities.ContainsAll(qualities);
+ }
+
+
+ private bool ToolStartUse(EntityUid tool, EntityUid user, float fuel, IEnumerable<string> toolQualitiesNeeded, ToolComponent? toolComponent = null)
+ {
+ if (!Resolve(tool, ref toolComponent))
+ return false;
+
+ if (!toolComponent.Qualities.ContainsAll(toolQualitiesNeeded))
+ return false;
+
+ var beforeAttempt = new ToolUseAttemptEvent(fuel, user);
+ RaiseLocalEvent(tool, beforeAttempt, false);
+
+ return !beforeAttempt.Cancelled;
+ }
+
+ private bool ToolFinishUse(EntityUid tool, EntityUid user, float fuel, ToolComponent? toolComponent = null)
+ {
+ if (!Resolve(tool, ref toolComponent))
+ return false;
+
+ var afterAttempt = new ToolUseFinishAttemptEvent(fuel, user);
+ RaiseLocalEvent(tool, afterAttempt, false);
+
+ if (afterAttempt.Cancelled)
+ return false;
+
+ if (toolComponent.UseSound != null)
+ PlayToolSound(tool, toolComponent);
+
+ return true;
+ }
+
+ public void PlayToolSound(EntityUid uid, ToolComponent? tool = null)
+ {
+ if (!Resolve(uid, ref tool))
+ return;
+
+ if (tool.UseSound is not {} sound)
+ return;
+
+ // Pass tool.Owner to Filter.Pvs to avoid a TryGetEntity call.
+ SoundSystem.Play(sound.GetSound(), Filter.Pvs(tool.Owner),
+ uid, AudioHelpers.WithVariation(0.175f).WithVolume(-5f));
+ }
+
+ private void OnDoAfterComplete(ToolDoAfterComplete ev)
+ {
+ // Actually finish the tool use! Depending on whether that succeeds or not, either event will be broadcast.
+ if(ToolFinishUse(ev.Uid, ev.UserUid, ev.Fuel))
+ {
+ if (ev.EventTarget != null)
+ RaiseLocalEvent(ev.EventTarget.Value, ev.CompletedEvent, false);
+ else
+ RaiseLocalEvent(ev.CompletedEvent);
+ }
+ else if(ev.CancelledEvent != null)
+ {
+ if (ev.EventTarget != null)
+ RaiseLocalEvent(ev.EventTarget.Value, ev.CancelledEvent, false);
+ else
+ RaiseLocalEvent(ev.CancelledEvent);
+ }
+ }
+
+ private void OnDoAfterCancelled(ToolDoAfterCancelled ev)
+ {
+ if (ev.EventTarget != null)
+ RaiseLocalEvent(ev.EventTarget.Value, ev.Event, false);
+ else
+ RaiseLocalEvent(ev.Event);
+ }
+
+ private sealed class ToolDoAfterComplete : EntityEventArgs
+ {
+ public readonly object CompletedEvent;
+ public readonly object? CancelledEvent;
+ public readonly EntityUid Uid;
+ public readonly EntityUid UserUid;
+ public readonly float Fuel;
+ public readonly EntityUid? EventTarget;
+
+ public ToolDoAfterComplete(object completedEvent, object? cancelledEvent, EntityUid uid, EntityUid userUid, float fuel, EntityUid? eventTarget = null)
+ {
+ CompletedEvent = completedEvent;
+ Uid = uid;
+ UserUid = userUid;
+ Fuel = fuel;
+ CancelledEvent = cancelledEvent;
+ EventTarget = eventTarget;
+ }
+ }
+
+ private sealed class ToolDoAfterCancelled : EntityEventArgs
+ {
+ public readonly object Event;
+ public readonly EntityUid? EventTarget;
+
+ public ToolDoAfterCancelled(object @event, EntityUid? eventTarget = null)
+ {
+ Event = @event;
+ EventTarget = eventTarget;
+ }
+ }
}