if (args.Current is not HandcuffComponentState state)
return;
- component.Cuffing = state.Cuffing;
component.OverlayIconState = state.IconState;
}
return;
component.CanStillInteract = cuffState.CanStillInteract;
- component.Uncuffing = cuffState.Uncuffing;
_actionBlocker.UpdateCanMove(uid);
var ev = new CuffedStateChangeEvent();
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.DoAfter;
public sealed class DoAfterOverlay : Overlay
{
private readonly IEntityManager _entManager;
+ private readonly IGameTiming _timing;
private readonly SharedTransformSystem _transform;
+ private readonly MetaDataSystem _meta;
private readonly Texture _barTexture;
private readonly ShaderInstance _shader;
+ /// <summary>
+ /// Flash time for cancelled DoAfters
+ /// </summary>
+ private const float FlashTime = 0.125f;
+
+ // Hardcoded width of the progress bar because it doesn't match the texture.
+ private const float StartX = 2;
+ private const float EndX = 22f;
+
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
- public DoAfterOverlay(IEntityManager entManager, IPrototypeManager protoManager)
+ public DoAfterOverlay(IEntityManager entManager, IPrototypeManager protoManager, IGameTiming timing)
{
_entManager = entManager;
+ _timing = timing;
_transform = _entManager.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
+ _meta = _entManager.EntitySysManager.GetEntitySystem<MetaDataSystem>();
var sprite = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Interface/Misc/progress_bar.rsi"), "icon");
_barTexture = _entManager.EntitySysManager.GetEntitySystem<SpriteSystem>().Frame0(sprite);
{
var handle = args.WorldHandle;
var rotation = args.Viewport.Eye?.Rotation ?? Angle.Zero;
- var spriteQuery = _entManager.GetEntityQuery<SpriteComponent>();
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
// If you use the display UI scale then need to set max(1f, displayscale) because 0 is valid.
var rotationMatrix = Matrix3.CreateRotation(-rotation);
handle.UseShader(_shader);
- // TODO: Need active DoAfter component (or alternatively just make DoAfter itself active)
- foreach (var comp in _entManager.EntityQuery<DoAfterComponent>(true))
+ var curTime = _timing.CurTime;
+
+ var bounds = args.WorldAABB.Enlarged(5f);
+
+ var metaQuery = _entManager.GetEntityQuery<MetaDataComponent>();
+ var enumerator = _entManager.AllEntityQueryEnumerator<ActiveDoAfterComponent, DoAfterComponent, SpriteComponent, TransformComponent>();
+ while (enumerator.MoveNext(out var uid, out _, out var comp, out var sprite, out var xform))
{
- if (comp.DoAfters.Count == 0 ||
- !xformQuery.TryGetComponent(comp.Owner, out var xform) ||
- xform.MapID != args.MapId)
- {
+ if (xform.MapID != args.MapId)
continue;
- }
- var worldPosition = _transform.GetWorldPosition(xform);
- var index = 0;
- var worldMatrix = Matrix3.CreateTranslation(worldPosition);
+ if (comp.DoAfters.Count == 0)
+ continue;
- foreach (var doAfter in comp.DoAfters.Values)
- {
- var elapsed = doAfter.Elapsed;
- var displayRatio = MathF.Min(1.0f,
- (float)elapsed.TotalSeconds / doAfter.Delay);
+ var worldPosition = _transform.GetWorldPosition(xform, xformQuery);
+ if (!bounds.Contains(worldPosition))
+ continue;
- Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
- Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
+ // If the entity is paused, we will draw the do-after as it was when the entity got paused.
+ var meta = metaQuery.GetComponent(uid);
+ var time = meta.EntityPaused
+ ? curTime - _meta.GetPauseTime(uid, meta)
+ : curTime;
- handle.SetTransform(matty);
- var offset = _barTexture.Height / scale * index;
+ var worldMatrix = Matrix3.CreateTranslation(worldPosition);
+ Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
+ Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
+ handle.SetTransform(matty);
+ var offset = 0f;
+
+ foreach (var doAfter in comp.DoAfters.Values)
+ {
// Use the sprite itself if we know its bounds. This means short or tall sprites don't get overlapped
// by the bar.
- float yOffset;
- if (spriteQuery.TryGetComponent(comp.Owner, out var sprite))
- {
- yOffset = sprite.Bounds.Height / 2f + 0.05f;
- }
- else
- {
- yOffset = 0.5f;
- }
+ float yOffset = sprite.Bounds.Height / 2f + 0.05f;
// Position above the entity (we've already applied the matrix transform to the entity itself)
// Offset by the texture size for every do_after we have.
// Draw the underlying bar texture
handle.DrawTexture(_barTexture, position);
- // Draw the bar itself
- var cancelled = doAfter.Cancelled;
Color color;
- const float flashTime = 0.125f;
+ float elapsedRatio;
// if we're cancelled then flick red / off.
- if (cancelled)
+ if (doAfter.CancelledTime != null)
{
- var flash = Math.Floor((float)doAfter.CancelledElapsed.TotalSeconds / flashTime) % 2 == 0;
+ var elapsed = doAfter.CancelledTime.Value - doAfter.StartTime;
+ elapsedRatio = (float) Math.Min(1, elapsed.TotalSeconds / doAfter.Args.Delay.TotalSeconds);
+ var cancelElapsed = (time - doAfter.CancelledTime.Value).TotalSeconds;
+ var flash = Math.Floor(cancelElapsed / FlashTime) % 2 == 0;
color = new Color(1f, 0f, 0f, flash ? 1f : 0f);
}
else
{
- color = GetProgressColor(displayRatio);
+ var elapsed = time - doAfter.StartTime;
+ elapsedRatio = (float) Math.Min(1, elapsed.TotalSeconds / doAfter.Args.Delay.TotalSeconds);
+ color = GetProgressColor(elapsedRatio);
}
- // Hardcoded width of the progress bar because it doesn't match the texture.
- const float startX = 2f;
- const float endX = 22f;
-
- var xProgress = (endX - startX) * displayRatio + startX;
-
- var box = new Box2(new Vector2(startX, 3f) / EyeManager.PixelsPerMeter, new Vector2(xProgress, 4f) / EyeManager.PixelsPerMeter);
+ var xProgress = (EndX - StartX) * elapsedRatio + StartX;
+ var box = new Box2(new Vector2(StartX, 3f) / EyeManager.PixelsPerMeter, new Vector2(xProgress, 4f) / EyeManager.PixelsPerMeter);
box = box.Translated(position);
handle.DrawRect(box, color);
-
- index++;
+ offset += _barTexture.Height / scale;
}
}
using Content.Shared.DoAfter;
+using Content.Shared.Hands.Components;
using Robust.Client.Graphics;
using Robust.Client.Player;
-using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
-using Robust.Shared.Utility;
namespace Content.Client.DoAfter;
[Dependency] private readonly IOverlayManager _overlay = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
-
- /// <summary>
- /// We'll use an excess time so stuff like finishing effects can show.
- /// </summary>
- public const float ExcessTime = 0.5f;
+ [Dependency] private readonly MetaDataSystem _metadata = default!;
public override void Initialize()
{
base.Initialize();
- UpdatesOutsidePrediction = true;
- SubscribeNetworkEvent<CancelledDoAfterMessage>(OnCancelledDoAfter);
- SubscribeLocalEvent<DoAfterComponent, ComponentHandleState>(OnDoAfterHandleState);
- _overlay.AddOverlay(new DoAfterOverlay(EntityManager, _prototype));
+ _overlay.AddOverlay(new DoAfterOverlay(EntityManager, _prototype, GameTiming));
}
public override void Shutdown()
_overlay.RemoveOverlay<DoAfterOverlay>();
}
- private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent component, ref ComponentHandleState args)
- {
- if (args.Current is not DoAfterComponentState state)
- return;
-
- foreach (var (_, doAfter) in state.DoAfters)
- {
- if (component.DoAfters.ContainsKey(doAfter.ID))
- continue;
-
- component.DoAfters.Add(doAfter.ID, doAfter);
- }
- }
-
- private void OnCancelledDoAfter(CancelledDoAfterMessage ev)
+ public override void Update(float frameTime)
{
- if (!TryComp<DoAfterComponent>(ev.Uid, out var doAfter))
- return;
+ // Currently this only predicts do afters initiated by the player.
- Cancel(doAfter, ev.ID);
- }
+ // TODO maybe predict do-afters if the local player is the target of some other players do-after? Specifically
+ // ones that depend on the target not moving, because the cancellation of those do afters should be readily
+ // predictable by clients.
- /// <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 playerEntity = _player.LocalPlayer?.ControlledEntity;
- /// <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))
+ if (!TryComp(playerEntity, out ActiveDoAfterComponent? active))
return;
- if (!component.DoAfters.ContainsKey(id))
+ if (_metadata.EntityPaused(playerEntity.Value))
return;
- var doAfterMessage = component.DoAfters[id];
- doAfterMessage.Cancelled = true;
- doAfterMessage.CancelledTime = GameTiming.CurTime;
- component.CancelledDoAfters.Add(id, doAfterMessage);
- }
-
- // TODO separate DoAfter & ActiveDoAfter components for the entity query.
- public override void Update(float frameTime)
- {
- if (!GameTiming.IsFirstTimePredicted)
- return;
-
- var playerEntity = _player.LocalPlayer?.ControlledEntity;
-
- foreach (var (comp, xform) in EntityQuery<DoAfterComponent, TransformComponent>())
- {
- var doAfters = comp.DoAfters;
-
- if (doAfters.Count == 0)
- continue;
-
- var userGrid = xform.Coordinates;
- var toRemove = new RemQueue<Shared.DoAfter.DoAfter>();
-
- // 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;
- }
-
- if (doAfter.Cancelled)
- {
- doAfter.CancelledElapsed = GameTiming.CurTime - doAfter.CancelledTime;
- continue;
- }
-
- doAfter.Elapsed = GameTiming.CurTime - doAfter.StartTime;
-
- // Well we finished so don't try to predict cancels.
- if ((float)doAfter.Elapsed.TotalSeconds > doAfter.Delay)
- continue;
-
- // Predictions
- if (comp.Owner != playerEntity)
- 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.EventArgs.BreakOnUserMove)
- {
- if (!userGrid.InRange(EntityManager, doAfter.UserGrid, doAfter.EventArgs.MovementThreshold))
- {
- Cancel(comp, id);
- continue;
- }
- }
-
- if (doAfter.EventArgs.BreakOnTargetMove)
- {
- if (!Deleted(doAfter.EventArgs.Target) &&
- !Transform(doAfter.EventArgs.Target.Value).Coordinates.InRange(EntityManager,
- doAfter.TargetGrid,
- doAfter.EventArgs.MovementThreshold))
- {
- Cancel(comp, id);
- continue;
- }
- }
- }
-
- 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);
- }
- }
+ var time = GameTiming.CurTime;
+ var comp = Comp<DoAfterComponent>(playerEntity.Value);
+ var xformQuery = GetEntityQuery<TransformComponent>();
+ var handsQuery = GetEntityQuery<SharedHandsComponent>();
+ Update(playerEntity.Value, active, comp, time, xformQuery, handsQuery);
}
}
SubscribeLocalEvent<CryoPodComponent, GetVerbsEvent<AlternativeVerb>>(AddAlternativeVerbs);
SubscribeLocalEvent<CryoPodComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<CryoPodComponent, CryoPodPryFinished>(OnCryoPodPryFinished);
- SubscribeLocalEvent<CryoPodComponent, CryoPodPryInterrupted>(OnCryoPodPryInterrupted);
SubscribeLocalEvent<CryoPodComponent, AppearanceChangeEvent>(OnAppearanceChange);
SubscribeLocalEvent<InsideCryoPodComponent, ComponentStartup>(OnCryoPodInsertion);
var gameplayStateLoad = UIManager.GetUIController<GameplayStateLoadController>();
gameplayStateLoad.OnScreenLoad += OnScreenLoad;
+ gameplayStateLoad.OnScreenUnload += OnScreenUnload;
+ }
+
+ private void OnScreenUnload()
+ {
+ var widget = UI;
+ if (widget != null)
+ widget.AlertPressed -= OnAlertPressed;
}
private void OnScreenLoad()
{
+ var widget = UI;
+ if (widget != null)
+ widget.AlertPressed += OnAlertPressed;
+
SyncAlerts();
}
{
UI?.SyncControls(system, system.AlertOrder, e);
}
-
- // The UI can change underneath us if the user switches between HUD layouts
- // So ensure we're subscribed to the AlertPressed callback.
- if (UI != null)
- {
- UI.AlertPressed -= OnAlertPressed; // Ensure we don't hook into the callback twice
- UI.AlertPressed += OnAlertPressed;
- }
}
public void OnSystemLoaded(ClientAlertsSystem system)
system.ClearAlerts -= SystemOnClearAlerts;
}
+
public void OnStateEntered(GameplayState state)
{
- if (UI != null)
- {
- UI.AlertPressed += OnAlertPressed;
- }
-
// initially populate the frame if system is available
SyncAlerts();
}
-using System.Threading;
+using System;
using System.Threading.Tasks;
-using Content.Server.DoAfter;
using Content.Shared.DoAfter;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
+using Robust.Shared.Reflection;
+using Robust.Shared.Serialization;
using Robust.Shared.Timing;
+using Robust.Shared.Utility;
namespace Content.IntegrationTests.Tests.DoAfter
{
- type: DoAfter
";
- public sealed class TestDoAfterSystem : EntitySystem
+ private sealed class TestDoAfterEvent : DoAfterEvent
{
- public override void Initialize()
+ public override DoAfterEvent Clone()
{
- SubscribeLocalEvent<DoAfterEvent<TestDoAfterData>>(OnTestDoAfterFinishEvent);
+ return this;
}
+ };
+
+ [Test]
+ public async Task TestSerializable()
+ {
+ await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true});
+ var server = pairTracker.Pair.Server;
+ await server.WaitIdleAsync();
+ var refMan = server.ResolveDependency<IReflectionManager>();
- private void OnTestDoAfterFinishEvent(DoAfterEvent<TestDoAfterData> ev)
+ await server.WaitPost(() =>
{
- ev.AdditionalData.Cancelled = ev.Cancelled;
- }
- }
+ Assert.Multiple(() =>
+ {
+ foreach (var type in refMan.GetAllChildren<DoAfterEvent>(true))
+ {
+ if (type.IsAbstract || type == typeof(TestDoAfterEvent))
+ continue;
- private sealed class TestDoAfterData
- {
- public bool Cancelled;
- };
+ Assert.That(type.HasCustomAttribute<NetSerializableAttribute>()
+ && type.HasCustomAttribute<SerializableAttribute>(),
+ $"{nameof(DoAfterEvent)} is not NetSerializable. Event: {type.Name}");
+ }
+ });
+ });
+
+ await pairTracker.CleanReturnAsync();
+ }
[Test]
public async Task TestFinished()
await server.WaitIdleAsync();
var entityManager = server.ResolveDependency<IEntityManager>();
- var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem<DoAfterSystem>();
- var data = new TestDoAfterData();
+ var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem<SharedDoAfterSystem>();
+ var ev = new TestDoAfterEvent();
// 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) { Broadcast = true };
- doAfterSystem.DoAfter(args, data);
+ var args = new DoAfterArgs(mob, tickTime / 2, ev, null) { Broadcast = true };
+ Assert.That(doAfterSystem.TryStartDoAfter(args));
+ Assert.That(ev.Cancelled, Is.False);
});
await server.WaitRunTicks(1);
- Assert.That(data.Cancelled, Is.False);
+ Assert.That(ev.Cancelled, Is.False);
await pairTracker.CleanReturnAsync();
}
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes});
var server = pairTracker.Pair.Server;
var entityManager = server.ResolveDependency<IEntityManager>();
- var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem<DoAfterSystem>();
- var data = new TestDoAfterData();
+ var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem<SharedDoAfterSystem>();
+ DoAfterId? id = default;
+ var ev = new TestDoAfterEvent();
+
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) { Broadcast = true };
- doAfterSystem.DoAfter(args, data);
- cancelToken.Cancel();
+ var args = new DoAfterArgs(mob, tickTime * 2, ev, null) { Broadcast = true };
+
+ if (!doAfterSystem.TryStartDoAfter(args, out id))
+ {
+ Assert.Fail();
+ return;
+ }
+
+ Assert.That(!ev.Cancelled);
+ doAfterSystem.Cancel(id);
+ Assert.That(ev.Cancelled);
+
});
await server.WaitRunTicks(3);
- Assert.That(data.Cancelled, Is.True);
+ Assert.That(ev.Cancelled);
await pairTracker.CleanReturnAsync();
}
using Content.Server.Administration.Logs;
-using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Server.UserInterface;
using Content.Shared.AirlockPainter;
using Content.Shared.Interaction;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
-using Robust.Shared.Player;
namespace Content.Server.AirlockPainter
{
{
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
SubscribeLocalEvent<AirlockPainterComponent, AfterInteractEvent>(AfterInteractOn);
SubscribeLocalEvent<AirlockPainterComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<AirlockPainterComponent, AirlockPainterSpritePickedMessage>(OnSpritePicked);
- SubscribeLocalEvent<AirlockPainterComponent, DoAfterEvent<AirlockPainterData>>(OnDoAfter);
+ SubscribeLocalEvent<AirlockPainterComponent, AirlockPainterDoAfterEvent>(OnDoAfter);
}
- private void OnDoAfter(EntityUid uid, AirlockPainterComponent component, DoAfterEvent<AirlockPainterData> args)
+ private void OnDoAfter(EntityUid uid, AirlockPainterComponent component, AirlockPainterDoAfterEvent args)
{
+ component.IsSpraying = false;
+
if (args.Handled || args.Cancelled)
- {
- component.IsSpraying = false;
return;
- }
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);
+ _audio.PlayPvs(component.SpraySound, uid);
+ _appearance.SetData(args.Args.Target.Value, DoorVisuals.BaseRSI, args.Sprite);
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Args.User):user} painted {ToPrettyString(args.Args.Target.Value):target}");
- component.IsSpraying = false;
}
args.Handled = true;
}
component.IsSpraying = true;
- var airlockPainterData = new AirlockPainterData(sprite);
- var doAfterEventArgs = new DoAfterEventArgs(args.User, component.SprayTime, target:target, used:uid)
+ var doAfterEventArgs = new DoAfterArgs(args.User, component.SprayTime, new AirlockPainterDoAfterEvent(sprite), uid, target: target, used: uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
- BreakOnStun = true,
NeedHand = true,
};
- _doAfterSystem.DoAfter(doAfterEventArgs, airlockPainterData);
+ _doAfterSystem.TryStartDoAfter(doAfterEventArgs);
// Log attempt
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} is painting {ToPrettyString(uid):target} to '{style}' at {Transform(uid).Coordinates:targetlocation}");
_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, ensnare, ensnaringComponent);
+ entManager.EntitySysManager.GetEntitySystem<EnsnareableSystem>().TryFree(player, player, ensnare, ensnaringComponent);
}
}
}
public float UpdateRate = 5;
public float AccumulatedFrameTime;
-
- public bool BeingMilked;
}
}
using Content.Server.Animals.Components;
using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Chemistry.EntitySystems;
-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.Udder;
using Content.Shared.Verbs;
namespace Content.Server.Animals.Systems
internal sealed class UdderSystem : EntitySystem
{
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
public override void Initialize()
base.Initialize();
SubscribeLocalEvent<UdderComponent, GetVerbsEvent<AlternativeVerb>>(AddMilkVerb);
- SubscribeLocalEvent<UdderComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<UdderComponent, MilkingDoAfterEvent>(OnDoAfter);
}
public override void Update(float frameTime)
{
if (!Resolve(uid, ref udder))
return;
- if (udder.BeingMilked)
- {
- _popupSystem.PopupEntity(Loc.GetString("udder-system-already-milking"), uid, userUid);
- return;
- }
-
- udder.BeingMilked = true;
-
- var doargs = new DoAfterEventArgs(userUid, 5, target:uid, used:containerUid)
+ var doargs = new DoAfterArgs(userUid, 5, new MilkingDoAfterEvent(), uid, uid, used: containerUid)
{
BreakOnUserMove = true,
BreakOnDamage = true,
- BreakOnStun = true,
BreakOnTargetMove = true,
- MovementThreshold = 1.0f
+ MovementThreshold = 1.0f,
};
- _doAfterSystem.DoAfter(doargs);
+ _doAfterSystem.TryStartDoAfter(doargs);
}
- private void OnDoAfter(EntityUid uid, UdderComponent component, DoAfterEvent args)
+ private void OnDoAfter(EntityUid uid, UdderComponent component, MilkingDoAfterEvent args)
{
- if (args.Cancelled)
- {
- component.BeingMilked = false;
+ if (args.Cancelled || args.Handled || args.Args.Used == null)
return;
- }
-
- if (args.Handled || args.Args.Used == null)
- return;
-
- component.BeingMilked = false;
if (!_solutionContainerSystem.TryGetSolution(uid, component.TargetSolutionName, out var solution))
return;
if (!_solutionContainerSystem.TryGetRefillableSolution(args.Args.Used.Value, out var targetSolution))
return;
+ args.Handled = true;
var quantity = solution.Volume;
if(quantity == 0)
{
_popupSystem.PopupEntity(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), uid,
args.Args.User, PopupType.Medium);
-
- args.Handled = true;
}
private void AddMilkVerb(EntityUid uid, UdderComponent component, GetVerbsEvent<AlternativeVerb> args)
{
SubscribeLocalEvent<AnomalyScannerComponent, BoundUIOpenedEvent>(OnScannerUiOpened);
SubscribeLocalEvent<AnomalyScannerComponent, AfterInteractEvent>(OnScannerAfterInteract);
- SubscribeLocalEvent<AnomalyScannerComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<AnomalyScannerComponent, ScannerDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<AnomalyShutdownEvent>(OnScannerAnomalyShutdown);
SubscribeLocalEvent<AnomalySeverityChangedEvent>(OnScannerAnomalySeverityChanged);
if (!HasComp<AnomalyComponent>(target))
return;
- _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ScanDoAfterDuration, target:target, used:uid)
+ _doAfter.TryStartDoAfter(new DoAfterArgs(args.User, component.ScanDoAfterDuration, new ScannerDoAfterEvent(), uid, target: target, used: uid)
{
DistanceThreshold = 2f
});
using Content.Server.Anomaly.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Audio;
-using Content.Server.DoAfter;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Materials;
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;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly AmbientSoundSystem _ambient = default!;
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly ExplosionSystem _explosion = default!;
[Dependency] private readonly MaterialStorageSystem _material = default!;
[Dependency] private readonly RadioSystem _radio = default!;
using Robust.Shared.Prototypes;
using Content.Shared.Verbs;
using Content.Server.Popups;
-using Content.Server.DoAfter;
using Content.Shared.DoAfter;
+using Content.Shared.Internals;
using Robust.Shared.Utility;
namespace Content.Server.Body.Systems;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly AtmosphereSystem _atmos = default!;
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly GasTankSystem _gasTank = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
SubscribeLocalEvent<InternalsComponent, ComponentStartup>(OnInternalsStartup);
SubscribeLocalEvent<InternalsComponent, ComponentShutdown>(OnInternalsShutdown);
SubscribeLocalEvent<InternalsComponent, GetVerbsEvent<InteractionVerb>>(OnGetInteractionVerbs);
- SubscribeLocalEvent<InternalsComponent, DoAfterEvent<InternalsData>>(OnDoAfter);
+ SubscribeLocalEvent<InternalsComponent, InternalsDoAfterEvent>(OnDoAfter);
}
private void OnGetInteractionVerbs(EntityUid uid, InternalsComponent component, GetVerbsEvent<InteractionVerb> args)
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 = !isUser ? internals.Delay : 1.0f;
- _doAfter.DoAfter(new DoAfterEventArgs(user, delay, target:uid)
+ _doAfter.TryStartDoAfter(new DoAfterArgs(user, delay, new InternalsDoAfterEvent(), uid, target: uid)
{
BreakOnUserMove = true,
BreakOnDamage = true,
- BreakOnStun = true,
BreakOnTargetMove = true,
MovementThreshold = 0.1f,
- RaiseOnUser = isUser,
- RaiseOnTarget = !isUser
- }, internalsData);
+ });
return;
}
_gasTank.ConnectToInternals(tank);
}
- private void OnDoAfter(EntityUid uid, InternalsComponent component, DoAfterEvent<InternalsData> args)
+ private void OnDoAfter(EntityUid uid, InternalsComponent component, InternalsDoAfterEvent args)
{
if (args.Cancelled || args.Handled)
return;
- ToggleInternals(uid, args.Args.User, true, component);
+ ToggleInternals(uid, args.User, true, component);
args.Handled = true;
}
return null;
}
-
- private record struct InternalsData
- {
-
- }
}
using Content.Server.Botany.Components;
-using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Interaction;
+using Content.Shared.Swab;
namespace Content.Server.Botany.Systems;
public sealed class BotanySwabSystem : EntitySystem
{
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly MutationSystem _mutationSystem = default!;
base.Initialize();
SubscribeLocalEvent<BotanySwabComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<BotanySwabComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<BotanySwabComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<BotanySwabComponent, BotanySwabDoAfterEvent>(OnDoAfter);
}
/// <summary>
if (args.Target == null || !args.CanReach || !HasComp<PlantHolderComponent>(args.Target))
return;
- _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, target: args.Target, used: uid)
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(args.User, swab.SwabDelay, new BotanySwabDoAfterEvent(), uid, target: args.Target, used: uid)
{
Broadcast = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
- BreakOnStun = true,
NeedHand = true
});
}
/// <summary>
/// Save seed data or cross-pollenate.
/// </summary>
- private void OnDoAfter(EntityUid uid, BotanySwabComponent component, DoAfterEvent args)
+ private void OnDoAfter(EntityUid uid, BotanySwabComponent swab, DoAfterEvent args)
{
- if (args.Cancelled || args.Handled || !TryComp<PlantHolderComponent>(args.Args.Target, out var plant) || !TryComp<BotanySwabComponent>(args.Args.Used, out var swab))
+ if (args.Cancelled || args.Handled || !TryComp<PlantHolderComponent>(args.Args.Target, out var plant))
return;
if (swab.SeedData == null)
using Content.Server.Body.Components;
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.Components.SolutionManager;
+using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
{
SubscribeLocalEvent<InjectorComponent, GetVerbsEvent<AlternativeVerb>>(AddSetTransferVerbs);
SubscribeLocalEvent<InjectorComponent, SolutionChangedEvent>(OnSolutionChange);
- SubscribeLocalEvent<InjectorComponent, DoAfterEvent>(OnInjectDoAfter);
+ SubscribeLocalEvent<InjectorComponent, InjectorDoAfterEvent>(OnInjectDoAfter);
SubscribeLocalEvent<InjectorComponent, ComponentStartup>(OnInjectorStartup);
SubscribeLocalEvent<InjectorComponent, UseInHandEvent>(OnInjectorUse);
SubscribeLocalEvent<InjectorComponent, AfterInteractEvent>(OnInjectorAfterInteract);
private void OnInjectDoAfter(EntityUid uid, InjectorComponent component, DoAfterEvent args)
{
- if (args.Cancelled)
- {
- component.IsInjecting = false;
- return;
- }
-
- if (args.Handled || args.Args.Target == null)
+ if (args.Cancelled || args.Handled || args.Args.Target == null)
return;
UseInjector(args.Args.Target.Value, args.Args.User, uid, component);
-
- component.IsInjecting = false;
args.Handled = true;
}
if (!_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution))
return;
- //If it found it's injecting
- if (component.IsInjecting)
- return;
-
var actualDelay = MathF.Max(component.Delay, 1f);
// Injections take 1 second longer per additional 5u
_adminLogger.Add(LogType.Ingestion, $"{EntityManager.ToPrettyString(user):user} is attempting to inject themselves with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}.");
}
- component.IsInjecting = true;
-
- _doAfter.DoAfter(new DoAfterEventArgs(user, actualDelay, target:target, used:injector)
+ _doAfter.TryStartDoAfter(new DoAfterArgs(user, actualDelay, new InjectorDoAfterEvent(), injector, target: target, used: injector)
{
- RaiseOnTarget = isTarget,
- RaiseOnUser = !isTarget,
BreakOnUserMove = true,
BreakOnDamage = true,
- BreakOnStun = true,
BreakOnTargetMove = true,
- MovementThreshold = 0.1f
+ MovementThreshold = 0.1f,
});
}
Dirty(component);
AfterDraw(component, injector);
}
+
}
using Content.Server.Administration.Logs;
using Content.Server.Body.Systems;
-using Content.Server.DoAfter;
using Content.Server.Interaction;
using Content.Server.Popups;
using Content.Shared.CombatMode;
using Content.Shared.Chemistry;
+using Content.Shared.DoAfter;
using Content.Shared.Mobs.Systems;
namespace Content.Server.Chemistry.EntitySystems;
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly InteractionSystem _interaction = default!;
[Dependency] private readonly BloodstreamSystem _blood = default!;
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly ReactiveSystem _reactiveSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
return true;
}
- public bool TryGetSolution(EntityUid uid, string name,
+ public bool TryGetSolution([NotNullWhen(true)] EntityUid? uid, string name,
[NotNullWhen(true)] out Solution? solution,
SolutionContainerManagerComponent? solutionsMgr = null)
{
- if (!Resolve(uid, ref solutionsMgr, false))
+ if (uid == null || !Resolve(uid.Value, ref solutionsMgr, false))
{
solution = null;
return false;
using Content.Server.Body.Systems;
using Content.Server.Climbing.Components;
-using Content.Server.DoAfter;
using Content.Server.Interaction;
using Content.Server.Popups;
using Content.Server.Stunnable;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly FixtureSystem _fixtureSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly InteractionSystem _interactionSystem = default!;
SubscribeLocalEvent<ClimbableComponent, GetVerbsEvent<AlternativeVerb>>(AddClimbableVerb);
SubscribeLocalEvent<ClimbableComponent, DragDropTargetEvent>(OnClimbableDragDrop);
- SubscribeLocalEvent<ClimbingComponent, DoAfterEvent<ClimbExtraEvent>>(OnDoAfter);
+ SubscribeLocalEvent<ClimbingComponent, ClimbDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<ClimbingComponent, EndCollideEvent>(OnClimbEndCollide);
SubscribeLocalEvent<ClimbingComponent, BuckleChangeEvent>(OnBuckleChange);
SubscribeLocalEvent<ClimbingComponent, ComponentGetState>(OnClimbingGetState);
if (_bonkSystem.TryBonk(entityToMove, climbable))
return;
- var ev = new ClimbExtraEvent();
-
- var args = new DoAfterEventArgs(user, component.ClimbDelay, target: climbable, used: entityToMove)
+ var args = new DoAfterArgs(user, component.ClimbDelay, new ClimbDoAfterEvent(), entityToMove, target: climbable, used: entityToMove)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
- BreakOnDamage = true,
- BreakOnStun = true,
- RaiseOnUser = false,
- RaiseOnTarget = false,
+ BreakOnDamage = true
};
- _doAfterSystem.DoAfter(args, ev);
+ _doAfterSystem.TryStartDoAfter(args);
}
- private void OnDoAfter(EntityUid uid, ClimbingComponent component, DoAfterEvent<ClimbExtraEvent> args)
+ private void OnDoAfter(EntityUid uid, ClimbingComponent component, ClimbDoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Args.Target == null || args.Args.Used == null)
return;
_fixtureRemoveQueue.Clear();
}
- private sealed class ClimbExtraEvent : EntityEventArgs
- {
- //Honestly this is only here because otherwise this activates on every single doafter on a human
- }
}
/// <summary>
using Content.Shared.Construction.Components;
using Content.Shared.Construction.EntitySystems;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Pulling.Components;
using Content.Shared.Tools;
private void OnUnanchorComplete(EntityUid uid, AnchorableComponent component, TryUnanchorCompletedEvent args)
{
+ if (args.Cancelled || args.Used is not { } used)
+ return;
+
var xform = Transform(uid);
- RaiseLocalEvent(uid, new BeforeUnanchoredEvent(args.User, args.Using));
+ RaiseLocalEvent(uid, new BeforeUnanchoredEvent(args.User, used));
xform.Anchored = false;
- RaiseLocalEvent(uid, new UserUnanchoredEvent(args.User, args.Using));
+ RaiseLocalEvent(uid, new UserUnanchoredEvent(args.User, used));
_popup.PopupEntity(Loc.GetString("anchorable-unanchored"), uid);
_adminLogger.Add(
LogType.Unanchor,
LogImpact.Low,
- $"{EntityManager.ToPrettyString(args.User):user} unanchored {EntityManager.ToPrettyString(uid):anchored} using {EntityManager.ToPrettyString(args.Using):using}"
+ $"{EntityManager.ToPrettyString(args.User):user} unanchored {EntityManager.ToPrettyString(uid):anchored} using {EntityManager.ToPrettyString(used):using}"
);
}
private void OnAnchorComplete(EntityUid uid, AnchorableComponent component, TryAnchorCompletedEvent args)
{
+ if (args.Cancelled || args.Used is not { } used)
+ return;
+
var xform = Transform(uid);
if (TryComp<PhysicsComponent>(uid, out var anchorBody) &&
!TileFree(xform.Coordinates, anchorBody))
if (component.Snap)
xform.Coordinates = xform.Coordinates.SnapToGrid(EntityManager, _mapManager);
- RaiseLocalEvent(uid, new BeforeAnchoredEvent(args.User, args.Using));
+ RaiseLocalEvent(uid, new BeforeAnchoredEvent(args.User, used));
xform.Anchored = true;
- RaiseLocalEvent(uid, new UserAnchoredEvent(args.User, args.Using));
+ RaiseLocalEvent(uid, new UserAnchoredEvent(args.User, used));
_popup.PopupEntity(Loc.GetString("anchorable-anchored"), uid);
_adminLogger.Add(
LogType.Anchor,
LogImpact.Low,
- $"{EntityManager.ToPrettyString(args.User):user} anchored {EntityManager.ToPrettyString(uid):anchored} using {EntityManager.ToPrettyString(args.Using):using}"
+ $"{EntityManager.ToPrettyString(args.User):user} anchored {EntityManager.ToPrettyString(uid):anchored} using {EntityManager.ToPrettyString(used):using}"
);
}
return;
}
- var toolEvData = new ToolEventData(new TryAnchorCompletedEvent(userUid, usingUid), targetEntity:uid);
- _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, toolEvData);
+ _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, new TryAnchorCompletedEvent());
}
/// <summary>
if (!Valid(uid, userUid, usingUid, false))
return;
- var toolEvData = new ToolEventData(new TryUnanchorCompletedEvent(userUid, usingUid), targetEntity:uid);
- _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, toolEvData);
+ _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, new TryUnanchorCompletedEvent());
}
/// <summary>
_adminLogger.Add(LogType.Anchor, LogImpact.Low, $"{ToPrettyString(userUid):user} is trying to anchor {ToPrettyString(uid):entity} to {transform.Coordinates:targetlocation}");
}
}
-
- private abstract class AnchorEvent : EntityEventArgs
- {
- public EntityUid User;
- public EntityUid Using;
-
- protected AnchorEvent(EntityUid userUid, EntityUid usingUid)
- {
- User = userUid;
- Using = usingUid;
- }
- }
-
- private sealed class TryUnanchorCompletedEvent : AnchorEvent
- {
- public TryUnanchorCompletedEvent(EntityUid userUid, EntityUid usingUid) : base(userUid, usingUid)
- {
- }
- }
-
-
- private sealed class TryAnchorCompletedEvent : AnchorEvent
- {
- public TryAnchorCompletedEvent(EntityUid userUid, EntityUid usingUid) : base(userUid, usingUid)
- {
- }
- }
}
}
using Content.Shared.Construction.Prototypes;
+using Content.Shared.DoAfter;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Construction.Components
[DataField("deconstructionTarget")]
public string? DeconstructionNode { get; set; } = "start";
- [ViewVariables]
- public bool WaitingDoAfter { get; set; } = false;
+ [DataField("doAfter")]
+ public DoAfterId? DoAfter;
[ViewVariables]
+ // TODO Force flush interaction queue before serializing to YAML.
+ // Otherwise you can end up with entities stuck in invalid states (e.g., waiting for DoAfters).
public readonly Queue<object> InteractionQueue = new();
}
}
[DataField("qualityNeeded", customTypeSerializer:typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
public string QualityNeeded = "Welding";
-
- public bool BeingWelded;
}
}
return null;
}
- var doAfterArgs = new DoAfterEventArgs(user, doAfterTime)
+ var doAfterArgs = new DoAfterArgs(user, doAfterTime, new AwaitedDoAfterEvent(), null)
{
BreakOnDamage = true,
- BreakOnStun = true,
BreakOnTargetMove = false,
BreakOnUserMove = true,
NeedHand = false,
using Content.Shared.Interaction;
using Content.Shared.Tools.Components;
using Robust.Shared.Containers;
+using Robust.Shared.Map;
using Robust.Shared.Utility;
#if EXCEPTION_TOLERANCE
// ReSharper disable once RedundantUsingDirective
private void InitializeInteractions()
{
- #region DoAfter Subscriptions
-
- // DoAfter handling.
- // The ConstructionDoAfter events are meant to be raised either directed or broadcast.
- // If they're raised broadcast, we will re-raise them as directed on the target.
- // This allows us to easily use the DoAfter system for our purposes.
- SubscribeLocalEvent<ConstructionDoAfterComplete>(OnDoAfterComplete);
- SubscribeLocalEvent<ConstructionDoAfterCancelled>(OnDoAfterCancelled);
- SubscribeLocalEvent<ConstructionComponent, ConstructionDoAfterComplete>(EnqueueEvent);
- SubscribeLocalEvent<ConstructionComponent, ConstructionDoAfterCancelled>(EnqueueEvent);
- SubscribeLocalEvent<ConstructionComponent, DoAfterEvent<ConstructionData>>(OnDoAfter);
-
- #endregion
+ SubscribeLocalEvent<ConstructionComponent, ConstructionInteractDoAfterEvent>(EnqueueEvent);
// Event handling. Add your subscriptions here! Just make sure they're all handled by EnqueueEvent.
SubscribeLocalEvent<ConstructionComponent, InteractUsingEvent>(EnqueueEvent, new []{typeof(AnchorableSystem)});
if (!CheckConditions(uid, edge.Conditions))
return HandleResult.False;
- // We can only perform the "step completed" logic if this returns true.
- if (HandleStep(uid, ev, step, validation, out var user, construction)
- is var handle and not HandleResult.True)
+ var handle = HandleStep(uid, ev, step, validation, out var user, construction);
+ if (handle is not HandleResult.True)
return handle;
+ // Handle step should never handle the interaction during validation.
+ DebugTools.Assert(!validation);
+
// We increase the step index, meaning we move to the next step!
construction.StepIndex++;
// Let HandleInteraction actually handle the event for this step.
// We can only perform the rest of our logic if it returns true.
- if (HandleInteraction(uid, ev, step, validation, out user, construction)
- is var handle and not HandleResult.True)
+ var handle = HandleInteraction(uid, ev, step, validation, out user, construction);
+ if (handle is not HandleResult.True)
return handle;
+ DebugTools.Assert(!validation);
+
// Actually perform the step completion actions, since the step was handled correctly.
PerformActions(uid, user, step.Completed);
return HandleResult.False;
// Whether this event is being re-handled after a DoAfter or not. Check DoAfterState for more info.
- var doAfterState = validation ? DoAfterState.Validation : DoAfterState.None;
-
- // Custom data from a prior HandleInteraction where a DoAfter was called...
- object? doAfterData = null;
+ var doAfterState = DoAfterState.None;
// The DoAfter events can only perform special logic when we're not validating events.
- if (!validation)
+ if (ev is ConstructionInteractDoAfterEvent interactDoAfter)
{
- // Some events are handled specially... Such as doAfter.
- switch (ev)
- {
- case ConstructionDoAfterComplete complete:
- {
- // DoAfter completed!
- ev = complete.WrappedEvent;
- doAfterState = DoAfterState.Completed;
- doAfterData = complete.CustomData;
- construction.WaitingDoAfter = false;
- break;
- }
+ if (!validation)
+ construction.DoAfter = null;
- case ConstructionDoAfterCancelled cancelled:
- {
- // DoAfter failed!
- ev = cancelled.WrappedEvent;
- doAfterState = DoAfterState.Cancelled;
- doAfterData = cancelled.CustomData;
- construction.WaitingDoAfter = false;
- break;
- }
- }
- }
+ if (interactDoAfter.Cancelled)
+ return HandleResult.False;
- // Can't perform any interactions while we're waiting for a DoAfter...
- // This also makes any event validation fail.
- if (construction.WaitingDoAfter)
- return HandleResult.False;
+ ev = new InteractUsingEvent(
+ interactDoAfter.User,
+ interactDoAfter.Used!.Value,
+ uid,
+ interactDoAfter.ClickLocation);
+
+ doAfterState = DoAfterState.Completed;
+ }
// The cases in this switch will handle the interaction and return
switch (step)
{
-
// --- CONSTRUCTION STEP EVENT HANDLING START ---
#region Construction Step Event Handling
// So you want to create your own custom step for construction?
if (ev is not InteractUsingEvent interactUsing)
break;
+ if (construction.DoAfter != null && !validation)
+ {
+ _doAfterSystem.Cancel(construction.DoAfter);
+ return HandleResult.False;
+ }
+
// TODO: Sanity checks.
user = interactUsing.User;
- // If this step's DoAfter was cancelled, we just fail the interaction.
- if (doAfterState == DoAfterState.Cancelled)
- return HandleResult.False;
-
var insert = interactUsing.Used;
// Since many things inherit this step, we delegate the "is this entity valid?" logic to them.
return HandleResult.False;
// If we're only testing whether this step would be handled by the given event, then we're done.
- if (doAfterState == DoAfterState.Validation)
+ if (validation)
return HandleResult.Validated;
// If we still haven't completed this step's DoAfter...
if (doAfterState == DoAfterState.None && insertStep.DoAfter > 0)
{
- // 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)
+ var doAfterEv = new ConstructionInteractDoAfterEvent(interactUsing);
+
+ var doAfterEventArgs = new DoAfterArgs(interactUsing.User, step.DoAfter, doAfterEv, uid, uid, interactUsing.Used)
{
BreakOnDamage = false,
- BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = true
};
- _doAfterSystem.DoAfter(doAfterEventArgs, constructionData);
+ var started = _doAfterSystem.TryStartDoAfter(doAfterEventArgs, out construction.DoAfter);
- // 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.
- construction.WaitingDoAfter = true;
+ if (!started)
+ return HandleResult.False;
+
+#if DEBUG
+ // Verify that the resulting DoAfter event will be handled by the current construction state.
+ // if it can't what is even the point of raising this DoAfter?
+ doAfterEv.DoAfter = new(default, doAfterEventArgs, default);
+ var result = HandleInteraction(uid, doAfterEv, step, validation: true, out _, construction);
+ DebugTools.Assert(result == HandleResult.Validated);
+#endif
return HandleResult.DoAfter;
}
if (ev is not InteractUsingEvent interactUsing)
break;
+ if (construction.DoAfter != null && !validation)
+ {
+ _doAfterSystem.Cancel(construction.DoAfter);
+ return HandleResult.False;
+ }
+
// TODO: Sanity checks.
user = interactUsing.User;
// If we're validating whether this event handles the step...
- if (doAfterState == DoAfterState.Validation)
+ if (validation)
{
// Then we only really need to check whether the tool entity has that quality or not.
+ // TODO fuel consumption?
return _toolSystem.HasQuality(interactUsing.Used, toolInsertStep.Tool)
- ? HandleResult.Validated : HandleResult.False;
+ ? HandleResult.Validated
+ : HandleResult.False;
}
// If we're handling an event after its DoAfter finished...
- if (doAfterState != DoAfterState.None)
- return doAfterState == DoAfterState.Completed ? HandleResult.True : HandleResult.False;
-
- 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!
- if (toolInsertStep.DoAfter <= 0)
- return HandleResult.True;
-
- construction.WaitingDoAfter = true;
- return HandleResult.DoAfter;
+ if (doAfterState == DoAfterState.Completed)
+ return HandleResult.True;
+
+ var result = _toolSystem.UseTool(
+ interactUsing.Used,
+ interactUsing.User,
+ uid,
+ TimeSpan.FromSeconds(toolInsertStep.DoAfter),
+ new [] { toolInsertStep.Tool },
+ new ConstructionInteractDoAfterEvent(interactUsing),
+ out construction.DoAfter,
+ fuel: toolInsertStep.Fuel);
+
+ return construction.DoAfter != null ? HandleResult.DoAfter : HandleResult.False;
}
case TemperatureConstructionGraphStep temperatureChangeStep:
{
- if (ev is not OnTemperatureChangeEvent) {
+ if (ev is not OnTemperatureChangeEvent)
break;
- }
if (TryComp<TemperatureComponent>(uid, out var tempComp))
{
/// in which case they will also be set as handled.</remarks>
private void EnqueueEvent(EntityUid uid, ConstructionComponent construction, object args)
{
- // Handled events get treated specially.
+ // For handled events, we will check if the event leads to a valid construction interaction.
+ // If it does, we mark the event as handled and then enqueue it as normal.
if (args is HandledEntityEventArgs handled)
{
// If they're already handled, we do nothing.
if (_queuedUpdates.Add(uid))
_constructionUpdateQueue.Enqueue(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;
- return;
- }
-
- RaiseLocalEvent(args.Args.Target.Value, args.AdditionalData.CompleteEvent);
- args.Handled = true;
- }
-
- private void OnDoAfterComplete(ConstructionDoAfterComplete ev)
- {
- // Make extra sure the target entity exists...
- if (!Exists(ev.TargetUid))
- return;
-
- // Re-raise this event, but directed on the target UID.
- RaiseLocalEvent(ev.TargetUid, ev, false);
- }
-
- private void OnDoAfterCancelled(ConstructionDoAfterCancelled ev)
- {
- // Make extra sure the target entity exists...
- if (!Exists(ev.TargetUid))
- return;
-
- // Re-raise this event, but directed on the target UID.
- RaiseLocalEvent(ev.TargetUid, ev, false);
- }
-
- #endregion
-
- #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.
- /// </summary>
- private sealed class ConstructionDoAfterComplete : EntityEventArgs
- {
- public readonly EntityUid TargetUid;
- public readonly object WrappedEvent;
- public readonly object? CustomData;
-
- public ConstructionDoAfterComplete(EntityUid targetUid, object wrappedEvent, object? customData = null)
- {
- TargetUid = targetUid;
- WrappedEvent = wrappedEvent;
- CustomData = customData;
- }
- }
-
- /// <summary>
- /// This event signals that a construction interaction's DoAfter has failed or has been cancelled.
- /// This wraps the original event and also keeps some custom data that event handlers might need.
- /// </summary>
- private sealed class ConstructionDoAfterCancelled : EntityEventArgs
- {
- public readonly EntityUid TargetUid;
- public readonly object WrappedEvent;
- public readonly object? CustomData;
-
- public ConstructionDoAfterCancelled(EntityUid targetUid, object wrappedEvent, object? customData = null)
- {
- TargetUid = targetUid;
- WrappedEvent = wrappedEvent;
- CustomData = customData;
- }
- }
-
#endregion
#region Internal Enum Definitions
/// </summary>
None,
- /// <summary>
- /// If Validation, we want to validate whether the specified event would handle the step or not.
- /// Will NOT modify the construction state at all.
- /// </summary>
- Validation,
-
/// <summary>
/// If Completed, this is the second (and last) time we're seeing this event, and
/// the doAfter that was called the first time successfully completed. Handle completion logic now.
/// </summary>
- Completed,
-
- /// <summary>
- /// If Cancelled, this is the second (and last) time we're seeing this event, and
- /// the doAfter that was called the first time was cancelled. Handle cleanup logic now.
- /// </summary>
- Cancelled
+ Completed
}
/// <summary>
using Content.Server.Construction.Components;
-using Content.Server.DoAfter;
using Content.Server.Stack;
using Content.Shared.Construction;
+using Content.Shared.DoAfter;
using Content.Shared.Tools;
using JetBrains.Annotations;
using Robust.Server.Containers;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
using System.Linq;
using Content.Server.Construction.Components;
-using Content.Server.DoAfter;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.DoAfter;
using Content.Shared.Construction.Components;
+using Content.Shared.Exchanger;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Robust.Shared.Containers;
public sealed class PartExchangerSystem : EntitySystem
{
[Dependency] private readonly ConstructionSystem _construction = default!;
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
SubscribeLocalEvent<PartExchangerComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<PartExchangerComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<PartExchangerComponent, ExchangerDoAfterEvent>(OnDoAfter);
}
private void OnDoAfter(EntityUid uid, PartExchangerComponent component, DoAfterEvent args)
{
+ component.AudioStream?.Stop();
if (args.Cancelled || args.Handled || args.Args.Target == null)
- {
- component.AudioStream?.Stop();
return;
- }
-
- component.AudioStream?.Stop();
if (!TryComp<MachineComponent>(args.Args.Target.Value, out var machine))
return;
component.AudioStream = _audio.PlayPvs(component.ExchangeSound, uid);
- _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ExchangeDuration, target:args.Target, used:args.Used)
+ _doAfter.TryStartDoAfter(new DoAfterArgs(args.User, component.ExchangeDuration, new ExchangerDoAfterEvent(), uid, target: args.Target, used: uid)
{
BreakOnDamage = true,
- BreakOnStun = true,
BreakOnUserMove = true
});
}
+
}
using Content.Server.Construction.Components;
using Content.Server.Stack;
+using Content.Shared.Construction;
+using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Stacks;
using Content.Shared.Tools;
-using Content.Shared.Tools.Components;
+using Robust.Shared.Serialization;
namespace Content.Server.Construction
{
{
base.Initialize();
SubscribeLocalEvent<WelderRefinableComponent, InteractUsingEvent>(OnInteractUsing);
+ SubscribeLocalEvent<WelderRefinableComponent, WelderRefineDoAfterEvent>(OnDoAfter);
}
- private async void OnInteractUsing(EntityUid uid, WelderRefinableComponent component, InteractUsingEvent args)
+ private void OnInteractUsing(EntityUid uid, WelderRefinableComponent component, InteractUsingEvent args)
{
- // check if object is welder
- if (!HasComp<ToolComponent>(args.Used))
+ if (args.Handled)
return;
- // check if someone is already welding object
- if (component.BeingWelded)
- return;
-
- component.BeingWelded = true;
-
- var toolEvData = new ToolEventData(null);
+ args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, component.RefineTime, component.QualityNeeded, new WelderRefineDoAfterEvent(), component.RefineFuel);
+ }
- if (!_toolSystem.UseTool(args.Used, args.User, uid, component.RefineTime, component.QualityNeeded, toolEvData, component.RefineFuel))
- {
- // failed to veld - abort refine
- component.BeingWelded = false;
+ private void OnDoAfter(EntityUid uid, WelderRefinableComponent component, WelderRefineDoAfterEvent args)
+ {
+ if (args.Cancelled)
return;
- }
// get last owner coordinates and delete it
var resultPosition = Transform(uid).Coordinates;
// TODO: If something has a stack... Just use a prototype with a single thing in the stack.
// This is not a good way to do it.
if (TryComp<StackComponent?>(droppedEnt, out var stack))
- _stackSystem.SetCount(droppedEnt,1, stack);
+ _stackSystem.SetCount(droppedEnt, 1, stack);
}
}
}
private void OnHandcuffGetState(EntityUid uid, HandcuffComponent component, ref ComponentGetState args)
{
- args.State = new HandcuffComponentState(component.OverlayIconState, component.Cuffing);
+ args.State = new HandcuffComponentState(component.OverlayIconState);
}
private void OnCuffableGetState(EntityUid uid, CuffableComponent component, ref ComponentGetState args)
TryComp(component.LastAddedCuffs, out cuffs);
args.State = new CuffableComponentState(component.CuffedHandCount,
component.CanStillInteract,
- component.Uncuffing,
cuffs?.CuffedRSI,
$"{cuffs?.OverlayIconState}-{component.CuffedHandCount}",
cuffs?.Color);
using Content.Shared.Interaction;
using Content.Shared.Inventory;
using Content.Shared.Examine;
-using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Server.Hands.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Station.Systems;
using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
-using Robust.Server.GameObjects;
+using Content.Shared.Swab;
namespace Content.Server.Disease
{
/// </summary>
public sealed class DiseaseDiagnosisSystem : EntitySystem
{
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
// Private Events
SubscribeLocalEvent<DiseaseDiagnoserComponent, DiseaseMachineFinishedEvent>(OnDiagnoserFinished);
SubscribeLocalEvent<DiseaseVaccineCreatorComponent, DiseaseMachineFinishedEvent>(OnVaccinatorFinished);
- SubscribeLocalEvent<DiseaseSwabComponent, DoAfterEvent>(OnSwabDoAfter);
+ SubscribeLocalEvent<DiseaseSwabComponent, DiseaseSwabDoAfterEvent>(OnSwabDoAfter);
}
private Queue<EntityUid> AddQueue = new();
return;
}
- var isTarget = args.User != args.Target;
-
- _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, target: args.Target, used: uid)
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(args.User, swab.SwabDelay, new DiseaseSwabDoAfterEvent(), uid, target: args.Target, used: uid)
{
- RaiseOnTarget = isTarget,
- RaiseOnUser = !isTarget,
BreakOnTargetMove = true,
BreakOnUserMove = true,
- BreakOnStun = true,
NeedHand = true
});
}
using Content.Server.Body.Systems;
using Content.Server.Chat.Systems;
using Content.Server.Disease.Components;
-using Content.Server.DoAfter;
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Popups;
using Content.Shared.Clothing.Components;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISerializationManager _serializationManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
// Handling stuff from other systems
SubscribeLocalEvent<DiseaseCarrierComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
// Private events stuff
- SubscribeLocalEvent<DiseaseVaccineComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<DiseaseVaccineComponent, VaccineDoAfterEvent>(OnDoAfter);
}
private Queue<EntityUid> AddQueue = new();
/// </summary>
private void OnAfterInteract(EntityUid uid, DiseaseVaccineComponent vaxx, AfterInteractEvent args)
{
- if (args.Target == null || !args.CanReach)
+ if (args.Target == null || !args.CanReach || args.Handled)
return;
+ args.Handled = true;
+
if (vaxx.Used)
{
_popupSystem.PopupEntity(Loc.GetString("vaxx-already-used"), args.User, args.User);
return;
}
- _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, vaxx.InjectDelay, target: args.Target, used:uid)
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(args.User, vaxx.InjectDelay, new VaccineDoAfterEvent(), uid, target: args.Target, used: uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
- BreakOnStun = true,
NeedHand = true
});
}
using Content.Server.Atmos.EntitySystems;
using Content.Server.Disposal.Tube.Components;
using Content.Server.Disposal.Unit.Components;
-using Content.Server.DoAfter;
using Content.Server.Hands.Components;
using Content.Server.Popups;
using Content.Server.Power.Components;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly AtmosphereSystem _atmosSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
SubscribeLocalEvent<DisposalUnitComponent, GetVerbsEvent<Verb>>(AddClimbInsideVerb);
// Units
- SubscribeLocalEvent<DisposalUnitComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<DisposalUnitComponent, DisposalDoAfterEvent>(OnDoAfter);
//UI
SubscribeLocalEvent<DisposalUnitComponent, SharedDisposalUnitComponent.UiButtonPressedMessage>(OnUiButtonPressed);
// 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, target:toInsertId, used:unitId)
+ var doAfterArgs = new DoAfterArgs(userId.Value, delay, new DisposalDoAfterEvent(), unitId, target: toInsertId, used: unitId)
{
BreakOnDamage = true,
- BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
- NeedHand = false,
- RaiseOnTarget = false,
- RaiseOnUser = false,
- RaiseOnUsed = true,
+ NeedHand = false
};
- _doAfterSystem.DoAfter(doAfterArgs);
+ _doAfterSystem.TryStartDoAfter(doAfterArgs);
return true;
}
using Content.Shared.DoAfter;
-using Content.Shared.Mobs;
using JetBrains.Annotations;
namespace Content.Server.DoAfter;
using Content.Shared.Tools;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
+using Content.Shared.DoAfter;
namespace Content.Server.Doors.Systems;
// Mob prying doors
SubscribeLocalEvent<DoorComponent, GetVerbsEvent<AlternativeVerb>>(OnDoorAltVerb);
- SubscribeLocalEvent<DoorComponent, PryFinishedEvent>(OnPryFinished);
- SubscribeLocalEvent<DoorComponent, PryCancelledEvent>(OnPryCancelled);
+ SubscribeLocalEvent<DoorComponent, DoorPryDoAfterEvent>(OnPryFinished);
SubscribeLocalEvent<DoorComponent, WeldableAttemptEvent>(OnWeldAttempt);
SubscribeLocalEvent<DoorComponent, WeldableChangedEvent>(OnWeldChanged);
SubscribeLocalEvent<DoorComponent, GotEmaggedEvent>(OnEmagged);
/// </summary>
public bool TryPryDoor(EntityUid target, EntityUid tool, EntityUid user, DoorComponent door, bool force = false)
{
- if (door.BeingPried)
- return false;
-
if (door.State == DoorState.Welded)
return false;
var modEv = new DoorGetPryTimeModifierEvent(user);
RaiseLocalEvent(target, modEv, false);
- door.BeingPried = true;
- var toolEvData = new ToolEventData(new PryFinishedEvent(), cancelledEv: new PryCancelledEvent(),targetEntity: target);
- _toolSystem.UseTool(tool, user, target, modEv.PryTimeModifier * door.PryTime, new[] { door.PryingQuality }, toolEvData);
+ _toolSystem.UseTool(tool, user, target, modEv.PryTimeModifier * door.PryTime, door.PryingQuality, new DoorPryDoAfterEvent());
return true; // we might not actually succeeded, but a do-after has started
}
- private void OnPryCancelled(EntityUid uid, DoorComponent door, PryCancelledEvent args)
+ private void OnPryFinished(EntityUid uid, DoorComponent door, DoAfterEvent args)
{
- door.BeingPried = false;
- }
-
- private void OnPryFinished(EntityUid uid, DoorComponent door, PryFinishedEvent args)
- {
- door.BeingPried = false;
+ if (args.Cancelled)
+ return;
if (door.State == DoorState.Closed)
StartOpening(uid, door);
}
}
-public sealed class PryFinishedEvent : EntityEventArgs { }
-public sealed class PryCancelledEvent : EntityEventArgs { }
-
using Content.Server.Body.Systems;
-using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Shared.Actions;
using Content.Shared.Chemistry.Components;
using Robust.Shared.Containers;
using Robust.Shared.Player;
-using System.Threading;
using Content.Server.Chat.Systems;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
[Dependency] private readonly ITileDefinitionManager _tileDef = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
SubscribeLocalEvent<DragonComponent, DragonSpawnRiftActionEvent>(OnDragonRift);
SubscribeLocalEvent<DragonComponent, RefreshMovementSpeedModifiersEvent>(OnDragonMove);
- SubscribeLocalEvent<DragonComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<DragonComponent, DragonDevourDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<DragonComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRiftRoundEnd);
}
- private void OnDoAfter(EntityUid uid, DragonComponent component, DoAfterEvent args)
+ private void OnDoAfter(EntityUid uid, DragonComponent component, DragonDevourDoAfterEvent args)
{
if (args.Handled || args.Cancelled)
return;
else if (args.Args.Target != null)
EntityManager.QueueDeleteEntity(args.Args.Target.Value);
- if (component.SoundDevour != null)
- _audioSystem.PlayPvs(component.SoundDevour, uid, component.SoundDevour.Params);
+ _audioSystem.PlayPvs(component.SoundDevour, uid);
}
public override void Update(float frameTime)
case MobState.Critical:
case MobState.Dead:
- _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.DevourTime, target:target)
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.DevourTime, new DragonDevourDoAfterEvent(), uid, target: target, used: uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
- BreakOnStun = true,
});
break;
default:
if (component.SoundStructureDevour != null)
_audioSystem.PlayPvs(component.SoundStructureDevour, uid, component.SoundStructureDevour.Params);
- _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.StructureDevourTime, target:target)
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.StructureDevourTime, new DragonDevourDoAfterEvent(), uid, target: target, used: uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
- BreakOnStun = true,
});
}
}
[DataField("doAfter")]
public float DoAfterTime = 0;
-
- public CancellationTokenSource TokenSource { get; } = new();
}
}
-using Content.Server.DoAfter;
using Content.Server.Engineering.Components;
using Content.Shared.DoAfter;
using Content.Shared.Hands.EntitySystems;
if (string.IsNullOrEmpty(component.Prototype))
return;
- if (component.DoAfterTime > 0 && TryGet<DoAfterSystem>(out var doAfterSystem))
+ if (component.DoAfterTime > 0 && TryGet<SharedDoAfterSystem>(out var doAfterSystem))
{
- var doAfterArgs = new DoAfterEventArgs(user, component.DoAfterTime, component.TokenSource.Token)
+ var doAfterArgs = new DoAfterArgs(user, component.DoAfterTime, new AwaitedDoAfterEvent(), null)
{
BreakOnUserMove = true,
- BreakOnStun = true,
};
var result = await doAfterSystem.WaitDoAfter(doAfterArgs);
if (result != DoAfterStatus.Finished)
return;
- component.TokenSource.Cancel();
}
if (component.Deleted || Deleted(component.Owner))
using Content.Server.Coordinates.Helpers;
-using Content.Server.DoAfter;
using Content.Server.Engineering.Components;
using Content.Server.Stack;
using Content.Shared.DoAfter;
public sealed class SpawnAfterInteractSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
public override void Initialize()
if (component.DoAfterTime > 0)
{
- var doAfterArgs = new DoAfterEventArgs(args.User, component.DoAfterTime)
+ var doAfterArgs = new DoAfterArgs(args.User, component.DoAfterTime, new AwaitedDoAfterEvent(), null)
{
BreakOnUserMove = true,
- BreakOnStun = true,
- PostCheck = IsTileClear,
};
var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);
return;
}
- if (component.Deleted || Deleted(component.Owner))
+ if (component.Deleted || !IsTileClear())
return;
if (EntityManager.TryGetComponent<StackComponent?>(component.Owner, out var stackComp)
-using System.Threading;
-using Content.Server.DoAfter;
using Content.Shared.Alert;
using Content.Shared.DoAfter;
+using Content.Shared.Ensnaring;
using Content.Shared.Ensnaring.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.StepTrigger.Systems;
public sealed partial class EnsnareableSystem
{
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
public void InitializeEnsnaring()
/// <summary>
/// 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="target">The entity that will be freed</param>
+ /// <param name="user">The entity that is freeing the target</param>
/// <param name="ensnare">The entity used to ensnare</param>
/// <param name="component">The ensnaring component</param>
- public void TryFree(EntityUid target, EntityUid ensnare, EnsnaringComponent component, EntityUid? user = null)
+ public void TryFree(EntityUid target, EntityUid user, EntityUid ensnare, EnsnaringComponent component)
{
//Don't do anything if they don't have the ensnareable component.
if (!HasComp<EnsnareableComponent>(target))
return;
- var isOwner = !(user != null && target != user);
- var freeTime = isOwner ? component.BreakoutTime : component.FreeTime;
- bool breakOnMove;
+ var freeTime = user == target ? component.BreakoutTime : component.FreeTime;
+ var breakOnMove = user != target || !component.CanMoveBreakout;
- if (isOwner)
- breakOnMove = !component.CanMoveBreakout;
- else
- breakOnMove = true;
-
- var doAfterEventArgs = new DoAfterEventArgs(target, freeTime, target: target, used:ensnare)
+ var doAfterEventArgs = new DoAfterArgs(user, freeTime, new EnsnareableDoAfterEvent(), target, target: target, used: ensnare)
{
BreakOnUserMove = breakOnMove,
BreakOnTargetMove = breakOnMove,
BreakOnDamage = false,
- BreakOnStun = true,
- NeedHand = true
+ NeedHand = true,
+ BlockDuplicate = true,
};
- _doAfter.DoAfter(doAfterEventArgs);
+ if (!_doAfter.TryStartDoAfter(doAfterEventArgs))
+ return;
- if (isOwner)
+ if (user == 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", ensnare), ("user", Identity.Entity(target, EntityManager))), user.Value, user.Value);
+ else
+ _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-other", ("ensnare", ensnare), ("user", Identity.Entity(target, EntityManager))), user, user);
}
/// <summary>
InitializeEnsnaring();
SubscribeLocalEvent<EnsnareableComponent, ComponentInit>(OnEnsnareableInit);
- SubscribeLocalEvent<EnsnareableComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<EnsnareableComponent, EnsnareableDoAfterEvent>(OnDoAfter);
}
private void OnEnsnareableInit(EntityUid uid, EnsnareableComponent component, ComponentInit args)
-using System.Linq;
using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Chemistry.EntitySystems;
-using Content.Server.DoAfter;
using Content.Server.Fluids.Components;
using Content.Server.Popups;
using Content.Shared.Chemistry.Components;
[UsedImplicitly]
public sealed class MoppingSystem : SharedMoppingSystem
{
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SpillableSystem _spillableSystem = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
base.Initialize();
SubscribeLocalEvent<AbsorbentComponent, ComponentInit>(OnAbsorbentInit);
SubscribeLocalEvent<AbsorbentComponent, AfterInteractEvent>(OnAfterInteract);
+ SubscribeLocalEvent<AbsorbentComponent, AbsorbantDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<AbsorbentComponent, SolutionChangedEvent>(OnAbsorbentSolutionChange);
- SubscribeLocalEvent<AbsorbentComponent, DoAfterEvent<AbsorbantData>>(OnDoAfter);
}
private void OnAbsorbentInit(EntityUid uid, AbsorbentComponent component, ComponentInit args)
if (!component.InteractingEntities.Add(target))
return;
- var aborbantData = new AbsorbantData(targetSolution, msg, sfx, transferAmount);
+ var ev = new AbsorbantDoAfterEvent(targetSolution, msg, sfx, transferAmount);
- var doAfterArgs = new DoAfterEventArgs(user, delay, target: target, used:used)
+ var doAfterArgs = new DoAfterArgs(user, delay, ev, used, target: target, used: used)
{
BreakOnUserMove = true,
- BreakOnStun = true,
BreakOnDamage = true,
MovementThreshold = 0.2f
};
- _doAfterSystem.DoAfter(doAfterArgs, aborbantData);
+ _doAfterSystem.TryStartDoAfter(doAfterArgs);
}
- private void OnDoAfter(EntityUid uid, AbsorbentComponent component, DoAfterEvent<AbsorbantData> args)
+ private void OnDoAfter(EntityUid uid, AbsorbentComponent component, AbsorbantDoAfterEvent args)
{
- if (args.Args.Target == null)
+ if (args.Target == null)
return;
- if (args.Cancelled)
- {
- //Remove the interacting entities or else it breaks the mop
- component.InteractingEntities.Remove(args.Args.Target.Value);
- return;
- }
+ component.InteractingEntities.Remove(args.Target.Value);
- if (args.Handled)
+ if (args.Cancelled || args.Handled)
return;
- _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);
+ _audio.PlayPvs(args.Sound, uid);
+ _popups.PopupEntity(Loc.GetString(args.Message, ("target", args.Target.Value), ("used", uid)), uid);
+ _solutionSystem.TryTransferSolution(args.Target.Value, uid, args.TargetSolution,
+ AbsorbentComponent.SolutionName, args.TransferAmount);
+ component.InteractingEntities.Remove(args.Target.Value);
args.Handled = true;
}
-
- private record struct AbsorbantData(string TargetSolution, string Message, SoundSpecifier Sound, FixedPoint2 TransferAmount)
- {
- public readonly string TargetSolution = TargetSolution;
- public readonly string Message = Message;
- public readonly SoundSpecifier Sound = Sound;
- public readonly FixedPoint2 TransferAmount = TransferAmount;
- }
}
using Content.Server.Administration.Logs;
using Content.Server.Chemistry.EntitySystems;
-using Content.Server.DoAfter;
using Content.Server.Fluids.Components;
using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry.Components;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.DoAfter;
+using Content.Shared.Spillable;
namespace Content.Server.Fluids.EntitySystems;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
[Dependency] private readonly IAdminLogManager _adminLogger= default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
public override void Initialize()
{
SubscribeLocalEvent<SpillableComponent, GetVerbsEvent<Verb>>(AddSpillVerb);
SubscribeLocalEvent<SpillableComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<SpillableComponent, SolutionSpikeOverflowEvent>(OnSpikeOverflow);
- SubscribeLocalEvent<SpillableComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<SpillableComponent, SpillDoAfterEvent>(OnDoAfter);
}
private void OnSpikeOverflow(EntityUid uid, SpillableComponent component, SolutionSpikeOverflowEvent args)
Verb verb = new();
verb.Text = Loc.GetString("spill-target-verb-get-data-text");
// TODO VERB ICONS spill icon? pouring out a glass/beaker?
- if (component.SpillDelay == null)
- {
- verb.Act = () =>
- {
- var puddleSolution = _solutionContainerSystem.SplitSolution(args.Target,
- solution, solution.Volume);
- SpillAt(puddleSolution, Transform(args.Target).Coordinates, "PuddleSmear");
- };
- }
- else
+
+ verb.Act = () =>
{
- verb.Act = () =>
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(args.User, component.SpillDelay ?? 0, new SpillDoAfterEvent(), uid, target: uid)
{
- _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, component.SpillDelay.Value, target:uid)
- {
- BreakOnTargetMove = true,
- BreakOnUserMove = true,
- BreakOnDamage = true,
- BreakOnStun = true,
- NeedHand = true
- });
- };
- }
+ BreakOnTargetMove = true,
+ BreakOnUserMove = true,
+ BreakOnDamage = true,
+ NeedHand = true,
+ });
+ };
verb.Impact = LogImpact.Medium; // dangerous reagent reaction are logged separately.
verb.DoContactInteraction = true;
args.Verbs.Add(verb);
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.Forensics;
using Content.Shared.IdentityManagement;
-using Robust.Shared.Serialization;
namespace Content.Server.Forensics
{
/// </summary>
public sealed class ForensicPadSystem : EntitySystem
{
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
base.Initialize();
SubscribeLocalEvent<ForensicPadComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<ForensicPadComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<ForensicPadComponent, DoAfterEvent<ForensicPadData>>(OnDoAfter);
+ SubscribeLocalEvent<ForensicPadComponent, ForensicPadDoAfterEvent>(OnDoAfter);
}
private void OnExamined(EntityUid uid, ForensicPadComponent component, ExaminedEvent args)
private void StartScan(EntityUid used, EntityUid user, EntityUid target, ForensicPadComponent pad, string sample)
{
- var padData = new ForensicPadData(sample);
+ var ev = new ForensicPadDoAfterEvent(sample);
- var doAfterEventArgs = new DoAfterEventArgs(user, pad.ScanDelay, target: target, used: used)
+ var doAfterEventArgs = new DoAfterArgs(user, pad.ScanDelay, ev, used, target: target, used: used)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
- BreakOnStun = true,
- NeedHand = true,
- RaiseOnUser = false
+ NeedHand = true
};
- _doAfterSystem.DoAfter(doAfterEventArgs, padData);
+ _doAfterSystem.TryStartDoAfter(doAfterEventArgs);
}
- private void OnDoAfter(EntityUid uid, ForensicPadComponent component, DoAfterEvent<ForensicPadData> args)
+ private void OnDoAfter(EntityUid uid, ForensicPadComponent padComponent, ForensicPadDoAfterEvent args)
{
- if (args.Handled
- || args.Cancelled
- || !EntityManager.TryGetComponent(args.Args.Used, out ForensicPadComponent? padComponent))
+ if (args.Handled || args.Cancelled)
{
return;
}
MetaData(uid).EntityName = Loc.GetString("forensic-pad-gloves-name", ("entity", args.Args.Target));
}
- padComponent.Sample = args.AdditionalData.Sample;
+ padComponent.Sample = args.Sample;
padComponent.Used = true;
args.Handled = true;
}
-
- private sealed class ForensicPadData
- {
- public string Sample;
-
- public ForensicPadData(string sample)
- {
- Sample = sample;
- }
- }
}
}
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Timing;
-using Content.Server.DoAfter;
using Content.Server.Paper;
using Content.Server.Popups;
using Content.Server.UserInterface;
public sealed class ForensicScannerSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly PaperSystem _paperSystem = default!;
SubscribeLocalEvent<ForensicScannerComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
SubscribeLocalEvent<ForensicScannerComponent, ForensicScannerPrintMessage>(OnPrint);
SubscribeLocalEvent<ForensicScannerComponent, ForensicScannerClearMessage>(OnClear);
- SubscribeLocalEvent<ForensicScannerComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<ForensicScannerComponent, ForensicScannerDoAfterEvent>(OnDoAfter);
}
private void UpdateUserInterface(EntityUid uid, ForensicScannerComponent component)
/// </remarks>
private void StartScan(EntityUid uid, ForensicScannerComponent component, EntityUid user, EntityUid target)
{
- _doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.ScanDelay, target: target, used: uid)
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(user, component.ScanDelay, new ForensicScannerDoAfterEvent(), uid, target: target, used: uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
- BreakOnStun = true,
NeedHand = true
});
}
public int MaxGatheringEntities = 1;
[ViewVariables]
+ [DataField("gatheringEntities")]
public readonly List<EntityUid> GatheringEntities = new();
}
}
-using System.Threading;
using Content.Server.Destructible;
-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.Gatherable;
using Content.Shared.Interaction;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly DestructibleSystem _destructible = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
base.Initialize();
SubscribeLocalEvent<GatherableComponent, InteractUsingEvent>(OnInteractUsing);
- SubscribeLocalEvent<GatherableComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<GatherableComponent, GatherableDoAfterEvent>(OnDoAfter);
}
private void OnInteractUsing(EntityUid uid, GatherableComponent component, InteractUsingEvent args)
var damageTime = (damageRequired / tool.Damage.Total).Float();
damageTime = Math.Max(1f, damageTime);
- var doAfter = new DoAfterEventArgs(args.User, damageTime, target: uid, used: args.Used)
+ var doAfter = new DoAfterArgs(args.User, damageTime, new GatherableDoAfterEvent(), uid, target: uid, used: args.Used)
{
BreakOnDamage = true,
- BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
MovementThreshold = 0.25f,
};
- _doAfterSystem.DoAfter(doAfter);
+ _doAfterSystem.TryStartDoAfter(doAfter);
}
- private void OnDoAfter(EntityUid uid, GatherableComponent component, DoAfterEvent args)
+ private void OnDoAfter(EntityUid uid, GatherableComponent component, GatherableDoAfterEvent args)
{
if(!TryComp<GatheringToolComponent>(args.Args.Used, out var tool) || args.Args.Target == null)
return;
+ tool.GatheringEntities.Remove(args.Args.Target.Value);
if (args.Handled || args.Cancelled)
- {
- tool.GatheringEntities.Remove(args.Args.Target.Value);
return;
- }
// Complete the gathering process
_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)
/// </summary>
[DataField("delay")]
public float InjectionDelay = 5f;
-
- public bool Injecting = false;
}
}
-using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Shared.Actions;
using Content.Shared.Audio;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
+using Content.Shared.Guardian;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
/// </summary>
public sealed class GuardianSystem : EntitySystem
{
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly DamageableSystem _damageSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionSystem = default!;
SubscribeLocalEvent<GuardianCreatorComponent, UseInHandEvent>(OnCreatorUse);
SubscribeLocalEvent<GuardianCreatorComponent, AfterInteractEvent>(OnCreatorInteract);
SubscribeLocalEvent<GuardianCreatorComponent, ExaminedEvent>(OnCreatorExamine);
- SubscribeLocalEvent<GuardianCreatorComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<GuardianCreatorComponent, GuardianCreatorDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<GuardianComponent, MoveEvent>(OnGuardianMove);
SubscribeLocalEvent<GuardianComponent, DamageChangedEvent>(OnGuardianDamaged);
return;
}
- if (component.Injecting)
- return;
-
- component.Injecting = true;
-
- _doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.InjectionDelay, target: target, used: injector)
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(user, component.InjectionDelay, new GuardianCreatorDoAfterEvent(), injector, target: target, used: injector)
{
BreakOnTargetMove = true,
BreakOnUserMove = true
return;
if (args.Cancelled || component.Deleted || component.Used || !_handsSystem.IsHolding(args.Args.User, uid, out _) || HasComp<GuardianHostComponent>(args.Args.Target))
- {
- component.Injecting = false;
return;
- }
var hostXform = Transform(args.Args.Target.Value);
var host = EnsureComp<GuardianHostComponent>(args.Args.Target.Value);
-using System.Threading;
-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.Implants.Components;
public sealed partial class ImplanterSystem : SharedImplanterSystem
{
[Dependency] private readonly PopupSystem _popup = default!;
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
public override void Initialize()
base.Initialize();
InitializeImplanted();
- SubscribeLocalEvent<ImplanterComponent, HandDeselectedEvent>(OnHandDeselect);
SubscribeLocalEvent<ImplanterComponent, AfterInteractEvent>(OnImplanterAfterInteract);
SubscribeLocalEvent<ImplanterComponent, ComponentGetState>(OnImplanterGetState);
- SubscribeLocalEvent<ImplanterComponent, DoAfterEvent<ImplantEvent>>(OnImplant);
- SubscribeLocalEvent<ImplanterComponent, DoAfterEvent<DrawEvent>>(OnDraw);
+ SubscribeLocalEvent<ImplanterComponent, ImplantEvent>(OnImplant);
+ SubscribeLocalEvent<ImplanterComponent, DrawEvent>(OnDraw);
}
private void OnImplanterAfterInteract(EntityUid uid, ImplanterComponent component, AfterInteractEvent args)
args.Handled = true;
}
- private void OnHandDeselect(EntityUid uid, ImplanterComponent component, HandDeselectedEvent args)
- {
- component.CancelToken?.Cancel();
- component.CancelToken = null;
- }
-
/// <summary>
/// Attempt to implant someone else.
/// </summary>
/// <param name="implanter">The implanter being used</param>
public void TryImplant(ImplanterComponent component, EntityUid user, EntityUid target, EntityUid implanter)
{
- if (component.CancelToken != null)
+ var args = new DoAfterArgs(user, component.ImplantTime, new ImplantEvent(), implanter, target: target, used: implanter)
+ {
+ BreakOnUserMove = true,
+ BreakOnTargetMove = true,
+ BreakOnDamage = true,
+ NeedHand = true,
+ };
+
+ if (!_doAfter.TryStartDoAfter(args))
return;
_popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, user);
var userName = Identity.Entity(user, EntityManager);
_popup.PopupEntity(Loc.GetString("implanter-component-implanting-target", ("user", userName)), user, target, PopupType.LargeCaution);
-
- component.CancelToken?.Cancel();
- component.CancelToken = new CancellationTokenSource();
-
- 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,
- NeedHand = true
- }, implantEvent);
}
/// <summary>
//TODO: Remove when surgery is in
public void TryDraw(ImplanterComponent component, EntityUid user, EntityUid target, EntityUid implanter)
{
- _popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, user);
-
- component.CancelToken?.Cancel();
- component.CancelToken = new CancellationTokenSource();
-
- var drawEvent = new DrawEvent();
-
- _doAfter.DoAfter(new DoAfterEventArgs(user, component.DrawTime, target:target,used:implanter)
+ var args = new DoAfterArgs(user, component.DrawTime, new DrawEvent(), implanter, target: target, used: implanter)
{
BreakOnUserMove = true,
BreakOnTargetMove = true,
BreakOnDamage = true,
- BreakOnStun = true,
- NeedHand = true
- }, drawEvent);
+ NeedHand = true,
+ };
+
+ if (_doAfter.TryStartDoAfter(args))
+ _popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, user);
+
}
private void OnImplanterGetState(EntityUid uid, ImplanterComponent component, ref ComponentGetState args)
args.State = new ImplanterComponentState(component.CurrentMode, component.ImplantOnly);
}
- private void OnImplant(EntityUid uid, ImplanterComponent component, DoAfterEvent<ImplantEvent> args)
+ private void OnImplant(EntityUid uid, ImplanterComponent component, ImplantEvent args)
{
- if (args.Cancelled)
- {
- component.CancelToken = null;
+ if (args.Cancelled || args.Handled || args.Target == null || args.Used == null)
return;
- }
- if (args.Handled || args.Args.Target == null || args.Args.Used == null)
- return;
-
- Implant(args.Args.Used.Value, args.Args.Target.Value, component);
+ Implant(args.Used.Value, args.Target.Value, component);
args.Handled = true;
- component.CancelToken = null;
}
- private void OnDraw(EntityUid uid, ImplanterComponent component, DoAfterEvent<DrawEvent> args)
+ private void OnDraw(EntityUid uid, ImplanterComponent component, DrawEvent args)
{
- if (args.Cancelled)
- {
- component.CancelToken = null;
- return;
- }
-
- if (args.Handled || args.Args.Used == null || args.Args.Target == null)
+ if (args.Cancelled || args.Handled || args.Used == null || args.Target == null)
return;
- Draw(args.Args.Used.Value, args.Args.User, args.Args.Target.Value, component);
+ Draw(args.Used.Value, args.User, args.Target.Value, component);
args.Handled = true;
- component.CancelToken = null;
- }
-
- private sealed class ImplantEvent : EntityEventArgs
- {
-
- }
-
- private sealed class DrawEvent : EntityEventArgs
- {
-
}
}
using Content.Server.Administration.Logs;
-using Content.Server.DoAfter;
using Content.Server.Kitchen.Components;
using Content.Server.Popups;
using Content.Shared.Database;
public sealed class KitchenSpikeSystem : SharedKitchenSpikeSystem
{
[Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly IAdminLogManager _logger = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
SubscribeLocalEvent<KitchenSpikeComponent, DragDropTargetEvent>(OnDragDrop);
//DoAfter
- SubscribeLocalEvent<KitchenSpikeComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<KitchenSpikeComponent, SpikeDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<KitchenSpikeComponent, SuicideEvent>(OnSuicide);
butcherable.BeingButchered = true;
component.InUse = true;
- var doAfterArgs = new DoAfterEventArgs(userUid, component.SpikeDelay + butcherable.ButcherDelay, target:victimUid, used:uid)
+ var doAfterArgs = new DoAfterArgs(userUid, component.SpikeDelay + butcherable.ButcherDelay, new SpikeDoAfterEvent(), uid, target: victimUid, used: uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
- BreakOnStun = true,
NeedHand = true
};
- _doAfter.DoAfter(doAfterArgs);
+ _doAfter.TryStartDoAfter(doAfterArgs);
return true;
}
using Content.Server.Body.Systems;
-using Content.Server.DoAfter;
using Content.Server.Kitchen.Components;
using Content.Shared.Body.Components;
using Content.Shared.Interaction;
using Content.Shared.Verbs;
using Content.Shared.Destructible;
using Content.Shared.DoAfter;
+using Content.Shared.Kitchen;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Robust.Server.Containers;
{
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly SharedDestructibleSystem _destructibleSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly ContainerSystem _containerSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
base.Initialize();
SubscribeLocalEvent<SharpComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<SharpComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<SharpComponent, SharpDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<ButcherableComponent, GetVerbsEvent<InteractionVerb>>(OnGetInteractionVerbs);
}
return;
var doAfter =
- new DoAfterEventArgs(user, sharp.ButcherDelayModifier * butcher.ButcherDelay, target: target, used: knife)
+ new DoAfterArgs(user, sharp.ButcherDelayModifier * butcher.ButcherDelay, new SharpDoAfterEvent(), knife, target: target, used: knife)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
- BreakOnStun = true,
NeedHand = true
};
- _doAfterSystem.DoAfter(doAfter);
+ _doAfterSystem.TryStartDoAfter(doAfter);
}
private void OnDoAfter(EntityUid uid, SharpComponent component, DoAfterEvent args)
using Content.Server.Administration.Logs;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Systems;
-using Content.Server.DoAfter;
using Content.Server.Ghost;
using Content.Server.Light.Components;
using Content.Server.MachineLinking.Events;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
SubscribeLocalEvent<PoweredLightComponent, PowerChangedEvent>(OnPowerChanged);
- SubscribeLocalEvent<PoweredLightComponent, DoAfterEvent>(OnDoAfter);
-
+ SubscribeLocalEvent<PoweredLightComponent, PoweredLightDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<PoweredLightComponent, EmpPulseEvent>(OnEmpPulse);
}
}
// removing a working bulb, so require a delay
- _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, light.EjectBulbDelay, target:uid)
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(userUid, light.EjectBulbDelay, new PoweredLightDoAfterEvent(), uid, target: uid)
{
BreakOnUserMove = true,
BreakOnDamage = true,
- BreakOnStun = true
});
args.Handled = true;
private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args)
{
args.Affected = true;
- TryDestroyBulb(uid, component);
+ TryDestroyBulb(uid, component);
}
}
}
-using System.Threading;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Coordinates.Helpers;
-using Content.Server.DoAfter;
using Content.Server.Doors.Systems;
using Content.Server.Magic.Events;
using Content.Server.Weapons.Ranged.Systems;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Content.Shared.Interaction.Events;
+using Content.Shared.Magic;
using Content.Shared.Maps;
using Content.Shared.Physics;
using Content.Shared.Spawners.Components;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly GunSystem _gunSystem = default!;
[Dependency] private readonly PhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
SubscribeLocalEvent<SpellbookComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<SpellbookComponent, UseInHandEvent>(OnUse);
- SubscribeLocalEvent<SpellbookComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<SpellbookComponent, SpellbookDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<InstantSpawnSpellEvent>(OnInstantSpawn);
SubscribeLocalEvent<TeleportSpellEvent>(OnTeleportSpell);
private void AttemptLearn(EntityUid uid, SpellbookComponent component, UseInHandEvent args)
{
- var doAfterEventArgs = new DoAfterEventArgs(args.User, component.LearnTime, target:uid)
+ var doAfterEventArgs = new DoAfterArgs(args.User, component.LearnTime, new SpellbookDoAfterEvent(), uid, target: uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
- BreakOnStun = true,
NeedHand = true //What, are you going to read with your eyes only??
};
- _doAfter.DoAfter(doAfterEventArgs);
+ _doAfter.TryStartDoAfter(doAfterEventArgs);
}
#region Spells
using System.Linq;
-using Content.Server.DoAfter;
using Content.Server.Interaction;
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;
{
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly MechSystem _mech = default!;
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly InteractionSystem _interaction = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly TransformSystem _transform = default!;
SubscribeLocalEvent<MechGrabberComponent, AttemptRemoveMechEquipmentEvent>(OnAttemptRemove);
SubscribeLocalEvent<MechGrabberComponent, InteractNoHandEvent>(OnInteract);
- SubscribeLocalEvent<MechGrabberComponent, DoAfterEvent>(OnMechGrab);
+ SubscribeLocalEvent<MechGrabberComponent, GrabberDoAfterEvent>(OnMechGrab);
}
private void OnGrabberMessage(EntityUid uid, MechGrabberComponent component, MechEquipmentUiMessageRelayEvent args)
args.Handled = true;
component.AudioStream = _audio.PlayPvs(component.GrabSound, uid);
- _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.GrabDelay, target:target, used:uid)
+ _doAfter.TryStartDoAfter(new DoAfterArgs(args.User, component.GrabDelay, new GrabberDoAfterEvent(), uid, target: target, used: uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true
-using Content.Server.DoAfter;
-using Content.Server.Mech.Components;
+using Content.Server.Mech.Components;
using Content.Server.Popups;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
public sealed class MechEquipmentSystem : EntitySystem
{
[Dependency] private readonly MechSystem _mech = default!;
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly PopupSystem _popup = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<MechEquipmentComponent, AfterInteractEvent>(OnUsed);
- SubscribeLocalEvent<MechEquipmentComponent, DoAfterEvent<InsertEquipmentEvent>>(OnInsertEquipment);
+ SubscribeLocalEvent<MechEquipmentComponent, InsertEquipmentEvent>(OnInsertEquipment);
}
private void OnUsed(EntityUid uid, MechEquipmentComponent component, AfterInteractEvent args)
_popup.PopupEntity(Loc.GetString("mech-equipment-begin-install", ("item", uid)), mech);
- var insertEquipment = new InsertEquipmentEvent();
- var doAfterEventArgs = new DoAfterEventArgs(args.User, component.InstallDuration, target: mech, used: uid)
+ var doAfterEventArgs = new DoAfterArgs(args.User, component.InstallDuration, new InsertEquipmentEvent(), uid, target: mech, used: uid)
{
- BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true
};
- _doAfter.DoAfter(doAfterEventArgs, insertEquipment);
+ _doAfter.TryStartDoAfter(doAfterEventArgs);
}
- private void OnInsertEquipment(EntityUid uid, MechEquipmentComponent component, DoAfterEvent<InsertEquipmentEvent> args)
+ private void OnInsertEquipment(EntityUid uid, MechEquipmentComponent component, InsertEquipmentEvent args)
{
if (args.Handled || args.Cancelled || args.Args.Target == null)
return;
args.Handled = true;
}
-
- private sealed class InsertEquipmentEvent : EntityEventArgs
- {
-
- }
}
using System.Linq;
using Content.Server.Atmos.EntitySystems;
-using Content.Server.DoAfter;
using Content.Server.Mech.Components;
using Content.Server.Power.Components;
using Content.Shared.ActionBlocker;
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
SubscribeLocalEvent<MechComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<MechComponent, GetVerbsEvent<AlternativeVerb>>(OnAlternativeVerb);
SubscribeLocalEvent<MechComponent, MechOpenUiEvent>(OnOpenUi);
- SubscribeLocalEvent<MechComponent, DoAfterEvent<RemoveBatteryEvent>>(OnRemoveBattery);
- SubscribeLocalEvent<MechComponent, DoAfterEvent<MechEntryEvent>>(OnMechEntry);
- SubscribeLocalEvent<MechComponent, DoAfterEvent<MechExitEvent>>(OnMechExit);
+ SubscribeLocalEvent<MechComponent, RemoveBatteryEvent>(OnRemoveBattery);
+ SubscribeLocalEvent<MechComponent, MechEntryEvent>(OnMechEntry);
+ SubscribeLocalEvent<MechComponent, MechExitEvent>(OnMechExit);
SubscribeLocalEvent<MechComponent, DamageChangedEvent>(OnDamageChanged);
SubscribeLocalEvent<MechComponent, MechEquipmentRemoveMessage>(OnRemoveEquipmentMessage);
if (TryComp<ToolComponent>(args.Used, out var tool) && tool.Qualities.Contains("Prying") && component.BatterySlot.ContainedEntity != null)
{
- var removeBattery = new RemoveBatteryEvent();
-
- var doAfterEventArgs = new DoAfterEventArgs(args.User, component.BatteryRemovalDelay, target: uid, used: args.Target)
+ var doAfterEventArgs = new DoAfterArgs(args.User, component.BatteryRemovalDelay, new RemoveBatteryEvent(), uid, target: uid, used: args.Target)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
- RaiseOnTarget = true,
- RaiseOnUsed = false,
- RaiseOnUser = false,
};
- _doAfter.DoAfter(doAfterEventArgs, removeBattery);
+ _doAfter.TryStartDoAfter(doAfterEventArgs);
}
}
- private void OnRemoveBattery(EntityUid uid, MechComponent component, DoAfterEvent<RemoveBatteryEvent> args)
+ private void OnRemoveBattery(EntityUid uid, MechComponent component, RemoveBatteryEvent args)
{
if (args.Cancelled || args.Handled)
return;
Text = Loc.GetString("mech-verb-enter"),
Act = () =>
{
- var mechEntryEvent = new MechEntryEvent();
- var doAfterEventArgs = new DoAfterEventArgs(args.User, component.EntryDelay, target: uid)
+ var doAfterEventArgs = new DoAfterArgs(args.User, component.EntryDelay, new MechEntryEvent(), uid, target: uid)
{
BreakOnUserMove = true,
- BreakOnStun = true,
- RaiseOnTarget = true,
- RaiseOnUsed = false,
- RaiseOnUser = false,
};
- _doAfter.DoAfter(doAfterEventArgs, mechEntryEvent);
+ _doAfter.TryStartDoAfter(doAfterEventArgs);
}
};
var openUiVerb = new AlternativeVerb //can't hijack someone else's mech
return;
}
- var mechExitEvent = new MechExitEvent();
- var doAfterEventArgs = new DoAfterEventArgs(args.User, component.ExitDelay, target: uid)
+ var doAfterEventArgs = new DoAfterArgs(args.User, component.ExitDelay, new MechExitEvent(), uid, target: uid)
{
BreakOnUserMove = true,
BreakOnTargetMove = true,
- BreakOnStun = true,
- RaiseOnTarget = true,
- RaiseOnUsed = false,
- RaiseOnUser = false,
};
- _doAfter.DoAfter(doAfterEventArgs, mechExitEvent);
+ _doAfter.TryStartDoAfter(doAfterEventArgs);
}
};
args.Verbs.Add(ejectVerb);
}
}
- private void OnMechEntry(EntityUid uid, MechComponent component, DoAfterEvent<MechEntryEvent> args)
+ private void OnMechEntry(EntityUid uid, MechComponent component, MechEntryEvent args)
{
if (args.Cancelled || args.Handled)
return;
args.Handled = true;
}
- private void OnMechExit(EntityUid uid, MechComponent component, DoAfterEvent<MechExitEvent> args)
+ private void OnMechExit(EntityUid uid, MechComponent component, MechExitEvent args)
{
if (args.Cancelled || args.Handled)
return;
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
- {
- }
}
-using System.Threading;
using Content.Shared.Interaction;
using Content.Shared.Audio;
using Content.Shared.Jittering;
using Content.Server.Body.Components;
using Content.Server.Climbing;
using Content.Server.Construction;
-using Content.Server.DoAfter;
using Content.Server.Materials;
using Content.Server.Mind.Components;
using Content.Shared.DoAfter;
using Robust.Shared.Configuration;
using Robust.Server.Player;
using Robust.Shared.Physics.Components;
-using Content.Shared.Humanoid;
+using Content.Shared.Medical;
namespace Content.Server.Medical.BiomassReclaimer
{
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly MaterialStorageSystem _material = default!;
SubscribeLocalEvent<BiomassReclaimerComponent, UpgradeExamineEvent>(OnUpgradeExamine);
SubscribeLocalEvent<BiomassReclaimerComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<BiomassReclaimerComponent, SuicideEvent>(OnSuicide);
- SubscribeLocalEvent<BiomassReclaimerComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<BiomassReclaimerComponent, ReclaimerDoAfterEvent>(OnDoAfter);
}
private void OnSuicide(EntityUid uid, BiomassReclaimerComponent component, SuicideEvent args)
if (!HasComp<MobStateComponent>(args.Used) || !CanGib(uid, args.Used, component))
return;
- _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, 7f, target:args.Target, used:args.Used)
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(args.User, 7f, new ReclaimerDoAfterEvent(), uid, target: args.Target, used: args.Used)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
- BreakOnStun = true,
NeedHand = true
});
}
-using System.Threading;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
+using Content.Shared.DoAfter;
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
[DataField("delay")]
public float Delay = 3f;
- /// <summary>
- /// Cancel token to prevent rapid healing
- /// </summary>
- [DataField("cancelToken")]
- public CancellationTokenSource? CancelToken;
-
/// <summary>
/// Delay multiplier when healing yourself.
/// </summary>
using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Climbing;
-using Content.Server.DoAfter;
using Content.Server.Medical.Components;
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.NodeGroups;
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;
using Robust.Shared.Timing;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
SubscribeLocalEvent<CryoPodComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<CryoPodComponent, GetVerbsEvent<AlternativeVerb>>(AddAlternativeVerbs);
SubscribeLocalEvent<CryoPodComponent, GotEmaggedEvent>(OnEmagged);
- SubscribeLocalEvent<CryoPodComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<CryoPodComponent, CryoPodDragFinished>(OnDragFinished);
SubscribeLocalEvent<CryoPodComponent, CryoPodPryFinished>(OnCryoPodPryFinished);
- SubscribeLocalEvent<CryoPodComponent, CryoPodPryInterrupted>(OnCryoPodPryInterrupted);
SubscribeLocalEvent<CryoPodComponent, AtmosDeviceUpdateEvent>(OnCryoPodUpdateAtmosphere);
SubscribeLocalEvent<CryoPodComponent, DragDropTargetEvent>(HandleDragDropOn);
if (cryoPodComponent.BodyContainer.ContainedEntity != null)
return;
- var doAfterArgs = new DoAfterEventArgs(args.User, cryoPodComponent.EntryDelay, target:args.Dragged, used:uid)
+ var doAfterArgs = new DoAfterArgs(args.User, cryoPodComponent.EntryDelay, new CryoPodDragFinished(), uid, target: args.Dragged, used: uid)
{
BreakOnDamage = true,
- BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = false,
};
- _doAfterSystem.DoAfter(doAfterArgs);
+ _doAfterSystem.TryStartDoAfter(doAfterArgs);
args.Handled = true;
}
- private void OnDoAfter(EntityUid uid, CryoPodComponent component, DoAfterEvent args)
+ private void OnDragFinished(EntityUid uid, CryoPodComponent component, CryoPodDragFinished args)
{
if (args.Cancelled || args.Handled || args.Args.Target == null)
return;
InsertBody(uid, args.Args.Target.Value, component);
-
args.Handled = true;
}
if (args.Handled || !cryoPodComponent.Locked || cryoPodComponent.BodyContainer.ContainedEntity == null)
return;
- if (TryComp(args.Used, out ToolComponent? tool)
- && tool.Qualities.Contains("Prying")) // Why aren't those enums?
- {
- if (cryoPodComponent.IsPrying)
- return;
- cryoPodComponent.IsPrying = true;
-
- var toolEvData = new ToolEventData(new CryoPodPryFinished(), targetEntity:uid);
- _toolSystem.UseTool(args.Used, args.User, uid, cryoPodComponent.PryDelay, new [] {"Prying"}, toolEvData);
-
- args.Handled = true;
- }
+ args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, cryoPodComponent.PryDelay, "Prying", new CryoPodPryFinished());
}
private void OnExamined(EntityUid uid, CryoPodComponent component, ExaminedEvent args)
-using System.Threading;
using Content.Server.Administration.Logs;
using Content.Server.Body.Systems;
-using Content.Server.DoAfter;
using Content.Server.Medical.Components;
using Content.Server.Stack;
using Content.Shared.Audio;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.DoAfter;
+using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
+using Content.Shared.Medical;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly StackSystem _stacks = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
base.Initialize();
SubscribeLocalEvent<HealingComponent, UseInHandEvent>(OnHealingUse);
SubscribeLocalEvent<HealingComponent, AfterInteractEvent>(OnHealingAfterInteract);
- SubscribeLocalEvent<DamageableComponent, DoAfterEvent<HealingData>>(OnDoAfter);
+ SubscribeLocalEvent<DamageableComponent, HealingDoAfterEvent>(OnDoAfter);
}
- private void OnDoAfter(EntityUid uid, DamageableComponent component, DoAfterEvent<HealingData> args)
+ private void OnDoAfter(EntityUid uid, DamageableComponent component, HealingDoAfterEvent args)
{
- if (args.Cancelled)
- {
- args.AdditionalData.HealingComponent.CancelToken = null;
+ if (!TryComp(args.Used, out HealingComponent? healing))
return;
- }
- if (args.Handled || args.Cancelled || _mobStateSystem.IsDead(uid) || args.Args.Used == null)
+ if (args.Handled || args.Cancelled || _mobStateSystem.IsDead(uid))
return;
if (component.DamageContainerID is not null && !component.DamageContainerID.Equals(component.DamageContainerID))
return;
// Heal some bloodloss damage.
- if (args.AdditionalData.HealingComponent.BloodlossModifier != 0)
- _bloodstreamSystem.TryModifyBleedAmount(uid, args.AdditionalData.HealingComponent.BloodlossModifier);
+ if (healing.BloodlossModifier != 0)
+ _bloodstreamSystem.TryModifyBleedAmount(uid, healing.BloodlossModifier);
- var healed = _damageable.TryChangeDamage(uid, args.AdditionalData.HealingComponent.Damage, true, origin: args.Args.User);
+ var healed = _damageable.TryChangeDamage(uid, healing.Damage, true, origin: args.Args.User);
- if (healed == null)
+ if (healed == null && healing.BloodlossModifier != 0)
return;
- // Reverify that we can heal the damage.
- _stacks.Use(args.Args.Used.Value, 1, args.AdditionalData.Stack);
+ var total = healed?.Total ?? FixedPoint2.Zero;
- 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");
+ // Reverify that we can heal the damage.
+ _stacks.Use(args.Used.Value, 1);
+ if (uid != args.User)
+ _adminLogger.Add(LogType.Healed,
+ $"{EntityManager.ToPrettyString(args.User):user} healed {EntityManager.ToPrettyString(uid):target} for {total:damage} damage");
else
- _adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.Args.User):user} healed themselves for {healed.Total:damage} damage");
+ _adminLogger.Add(LogType.Healed,
+ $"{EntityManager.ToPrettyString(args.User):user} healed themselves for {total:damage} damage");
- if (args.AdditionalData.HealingComponent.HealingEndSound != null)
- _audio.PlayPvs(args.AdditionalData.HealingComponent.HealingEndSound, uid, AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
+ _audio.PlayPvs(healing.HealingEndSound, uid, AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
- args.AdditionalData.HealingComponent.CancelToken = null;
args.Handled = true;
}
private bool TryHeal(EntityUid uid, EntityUid user, EntityUid target, HealingComponent component)
{
- if (_mobStateSystem.IsDead(target) || !TryComp<DamageableComponent>(target, out var targetDamage) || component.CancelToken != null)
+ if (_mobStateSystem.IsDead(target) || !TryComp<DamageableComponent>(target, out var targetDamage))
return false;
if (targetDamage.TotalDamage == 0)
return false;
- if (component.DamageContainerID is not null && !component.DamageContainerID.Equals(targetDamage.DamageContainerID))
+ if (component.DamageContainerID is not null &&
+ !component.DamageContainerID.Equals(targetDamage.DamageContainerID))
return false;
if (user != target && !_interactionSystem.InRangeUnobstructed(user, target, popup: true))
return false;
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 isNotSelf = user != target;
? component.Delay
: component.Delay * GetScaledHealingPenalty(user, component);
- component.CancelToken = new CancellationTokenSource();
-
- var healingData = new HealingData(component, stack);
-
- var doAfterEventArgs = new DoAfterEventArgs(user, delay, cancelToken: component.CancelToken.Token,target: target, used: uid)
- {
- //Raise the event on the target if it's not self, otherwise raise it on self.
- RaiseOnTarget = isNotSelf,
- RaiseOnUser = !isNotSelf,
- BreakOnUserMove = true,
- BreakOnTargetMove = true,
- // Didn't break on damage as they may be trying to prevent it and
- // not being able to heal your own ticking damage would be frustrating.
- BreakOnStun = true,
- NeedHand = true,
- // Juusstt in case damageble gets removed it avoids having to re-cancel the token. Won't need this when DoAfterEvent<T> gets added.
- PostCheck = () => true
- };
-
- _doAfter.DoAfter(doAfterEventArgs, healingData);
-
+ var doAfterEventArgs =
+ new DoAfterArgs(user, delay, new HealingDoAfterEvent(), target, target: target, used: uid)
+ {
+ //Raise the event on the target if it's not self, otherwise raise it on self.
+ BreakOnUserMove = true,
+ BreakOnTargetMove = true,
+ // Didn't break on damage as they may be trying to prevent it and
+ // not being able to heal your own ticking damage would be frustrating.
+ NeedHand = true,
+ };
+
+ _doAfter.TryStartDoAfter(doAfterEventArgs);
return true;
}
public float GetScaledHealingPenalty(EntityUid uid, HealingComponent component)
{
var output = component.Delay;
- if (!TryComp<MobThresholdsComponent>(uid, out var mobThreshold) || !TryComp<DamageableComponent>(uid, out var damageable))
+ 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))
return 1;
var modifier = percentDamage * (component.SelfHealPenaltyMultiplier - 1) + 1;
return Math.Max(modifier, 1);
}
-
- private record struct HealingData(HealingComponent HealingComponent, StackComponent Stack)
- {
- public HealingComponent HealingComponent = HealingComponent;
- public StackComponent Stack = Stack;
- }
}
-using Content.Server.DoAfter;
using Content.Server.Medical.Components;
using Content.Server.Disease;
using Content.Server.Popups;
using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
+using Content.Shared.MedicalScanner;
using Content.Shared.Mobs.Components;
using Robust.Server.GameObjects;
using static Content.Shared.MedicalScanner.SharedHealthAnalyzerComponent;
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly DiseaseSystem _disease = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
base.Initialize();
SubscribeLocalEvent<HealthAnalyzerComponent, ActivateInWorldEvent>(HandleActivateInWorld);
SubscribeLocalEvent<HealthAnalyzerComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<HealthAnalyzerComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<HealthAnalyzerComponent, HealthAnalyzerDoAfterEvent>(OnDoAfter);
}
private void HandleActivateInWorld(EntityUid uid, HealthAnalyzerComponent healthAnalyzer, ActivateInWorldEvent args)
_audio.PlayPvs(healthAnalyzer.ScanningBeginSound, uid);
- _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, healthAnalyzer.ScanDelay, target: args.Target, used:uid)
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(args.User, healthAnalyzer.ScanDelay, new HealthAnalyzerDoAfterEvent(), uid, target: args.Target, used: uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
- BreakOnStun = true,
NeedHand = true
});
}
using Content.Server.Body.Components;
-using Content.Server.DoAfter;
using Content.Server.Medical.Components;
using Content.Server.Popups;
using Content.Shared.Actions;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.DoAfter;
+using Content.Shared.Medical;
using Robust.Shared.Utility;
namespace Content.Server.Medical
public sealed class StethoscopeSystem : EntitySystem
{
[Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
public override void Initialize()
SubscribeLocalEvent<WearingStethoscopeComponent, GetVerbsEvent<InnateVerb>>(AddStethoscopeVerb);
SubscribeLocalEvent<StethoscopeComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<StethoscopeComponent, StethoscopeActionEvent>(OnStethoscopeAction);
- SubscribeLocalEvent<StethoscopeComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<StethoscopeComponent, StethoscopeDoAfterEvent>(OnDoAfter);
}
/// <summary>
// construct the doafter and start it
private void StartListening(EntityUid scope, EntityUid user, EntityUid target, StethoscopeComponent comp)
{
- _doAfterSystem.DoAfter(new DoAfterEventArgs(user, comp.Delay, target: target, used:scope)
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(user, comp.Delay, new StethoscopeDoAfterEvent(), scope, target: target, used: scope)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
- BreakOnStun = true,
NeedHand = true
});
}
if (doorQuery.TryGetComponent(ent, out var door) && door.State != DoorState.Open)
{
// TODO: Use the verb.
- if (door.State != DoorState.Opening && !door.BeingPried)
+
+ if (door.State != DoorState.Opening)
_doors.TryPryDoor(ent, uid, uid, door, true);
return SteeringObstacleStatus.Continuing;
using Content.Server.Audio;
using Content.Server.Chat.Systems;
using Content.Server.Coordinates.Helpers;
-using Content.Server.DoAfter;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Popups;
using Content.Server.Station.Systems;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly ServerGlobalSoundSystem _soundSystem = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
SubscribeLocalEvent<NukeComponent, NukeKeypadEnterMessage>(OnEnterButtonPressed);
// Doafter events
- SubscribeLocalEvent<NukeComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<NukeComponent, NukeDisarmDoAfterEvent>(OnDoAfter);
}
private void OnInit(EntityUid uid, NukeComponent component, ComponentInit args)
private void DisarmBombDoafter(EntityUid uid, EntityUid user, NukeComponent nuke)
{
- var doafter = new DoAfterEventArgs(user, nuke.DisarmDoafterLength, target: uid)
+ var doafter = new DoAfterArgs(user, nuke.DisarmDoafterLength, new NukeDisarmDoAfterEvent(), uid, target: uid)
{
BreakOnDamage = true,
- BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = true
};
- _doAfterSystem.DoAfter(doafter);
+ if (!_doAfterSystem.TryStartDoAfter(doafter))
+ return;
+
_popups.PopupEntity(Loc.GetString("nuke-component-doafter-warning"), user,
user, PopupType.LargeCaution);
}
using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
using Robust.Shared.Audio;
[DataField("burstSound")]
public SoundSpecifier BurstSound = new SoundPathSpecifier("/Audio/Effects/flash_bang.ogg");
- /// <summary>
- /// Is this drink being forced on someone else?
- /// </summary>
- /// <returns></returns>
- [DataField("forceDrink")]
- public bool ForceDrink;
-
- /// <summary>
- /// Is the entity currently drinking or trying to make someone else drink?
- /// </summary>
- [DataField("drinking")]
- public bool Drinking;
-
/// <summary>
/// How long it takes to drink this yourself.
/// </summary>
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
[DataField("eatMessage")]
public string EatMessage = "food-nom";
- /// <summary>
- /// Is this entity being forcefed?
- /// </summary>
- [DataField("forceFeed")]
- public bool ForceFeed;
-
- /// <summary>
- /// Is this entity eating or being fed?
- /// </summary>
- [DataField(("eating"))]
- public bool Eating;
-
/// <summary>
/// How long it takes to eat the food personally.
/// </summary>
using Content.Server.Body.Systems;
using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Chemistry.EntitySystems;
-using Content.Server.DoAfter;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Forensics;
using Content.Server.Nutrition.Components;
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.Interaction.Events;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
+using Content.Shared.Nutrition;
using Content.Shared.Nutrition.Components;
using Content.Shared.Throwing;
using Content.Shared.Verbs;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly StomachSystem _stomachSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly SpillableSystem _spillableSystem = default!;
{
base.Initialize();
+ // TODO add InteractNoHandEvent for entities like mice.
SubscribeLocalEvent<DrinkComponent, SolutionChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<DrinkComponent, ComponentInit>(OnDrinkInit);
SubscribeLocalEvent<DrinkComponent, LandEvent>(HandleLand);
SubscribeLocalEvent<DrinkComponent, GetVerbsEvent<AlternativeVerb>>(AddDrinkVerb);
SubscribeLocalEvent<DrinkComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<DrinkComponent, SolutionTransferAttemptEvent>(OnTransferAttempt);
- SubscribeLocalEvent<DrinkComponent, DoAfterEvent<DrinkData>>(OnDoAfter);
+ SubscribeLocalEvent<DrinkComponent, ConsumeDoAfterEvent>(OnDoAfter);
}
public bool IsEmpty(EntityUid uid, DrinkComponent? component = null)
private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item)
{
- if (!EntityManager.HasComponent<BodyComponent>(target) || drink.Drinking)
+ if (!EntityManager.HasComponent<BodyComponent>(target))
return false;
if (!drink.Opened)
return true;
}
+ if (drinkSolution.Name == null)
+ return false;
+
if (_foodSystem.IsMouthBlocked(target, user))
return true;
if (!_interactionSystem.InRangeUnobstructed(user, item, popup: true))
return true;
- drink.Drinking = true;
- drink.ForceDrink = user != target;
+ var forceDrink = user != target;
- if (drink.ForceDrink)
+ if (forceDrink)
{
var userName = Identity.Entity(user, EntityManager);
var flavors = _flavorProfileSystem.GetLocalizedFlavorsMessage(user, drinkSolution);
- var drinkData = new DrinkData(drinkSolution, flavors);
-
- var doAfterEventArgs = new DoAfterEventArgs(user, drink.ForceDrink ? drink.ForceFeedDelay : drink.Delay,
- target: target, used: item)
+ var doAfterEventArgs = new DoAfterArgs(
+ user,
+ forceDrink ? drink.ForceFeedDelay : drink.Delay,
+ new ConsumeDoAfterEvent(drinkSolution.Name, flavors),
+ eventTarget: item,
+ target: target,
+ used: item)
{
- RaiseOnTarget = user != target,
- RaiseOnUser = false,
- BreakOnUserMove = drink.ForceDrink,
+ BreakOnUserMove = forceDrink,
BreakOnDamage = true,
- BreakOnStun = true,
- BreakOnTargetMove = drink.ForceDrink,
+ BreakOnTargetMove = forceDrink,
MovementThreshold = 0.01f,
DistanceThreshold = 1.0f,
- NeedHand = true
+ // Mice and the like can eat without hands.
+ // TODO maybe set this based on some CanEatWithoutHands event or component?
+ NeedHand = forceDrink,
};
- _doAfterSystem.DoAfter(doAfterEventArgs, drinkData);
-
+ _doAfterSystem.TryStartDoAfter(doAfterEventArgs);
return true;
}
/// <summary>
/// Raised directed at a victim when someone has force fed them a drink.
/// </summary>
- private void OnDoAfter(EntityUid uid, DrinkComponent component, DoAfterEvent<DrinkData> args)
+ private void OnDoAfter(EntityUid uid, DrinkComponent component, ConsumeDoAfterEvent args)
{
- if (args.Cancelled)
- {
- component.ForceDrink = false;
- component.Drinking = false;
+ if (args.Handled || args.Cancelled || component.Deleted)
return;
- }
- if (args.Handled || component.Deleted)
+ if (!TryComp<BodyComponent>(args.Args.Target, out var body))
return;
- if (!TryComp<BodyComponent>(args.Args.Target, out var body))
+ if (!_solutionContainerSystem.TryGetSolution(args.Used, args.Solution, out var solution))
return;
- component.Drinking = false;
+ var transferAmount = FixedPoint2.Min(component.TransferAmount, solution.Volume);
+ var drained = _solutionContainerSystem.Drain(uid, solution, transferAmount);
+ var forceDrink = args.User != args.Target;
- var transferAmount = FixedPoint2.Min(component.TransferAmount, args.AdditionalData.DrinkSolution.Volume);
- var drained = _solutionContainerSystem.Drain(uid, args.AdditionalData.DrinkSolution, transferAmount);
+ //var forceDrink = args.Args.Target.Value != args.Args.User;
if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(args.Args.Target.Value, out var stomachs, body))
{
- _popupSystem.PopupEntity(component.ForceDrink ? Loc.GetString("drink-component-try-use-drink-cannot-drink-other") : Loc.GetString("drink-component-try-use-drink-had-enough"), args.Args.Target.Value, args.Args.User);
+ _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 (HasComp<RefillableSolutionComponent>(args.Args.Target.Value))
{
return;
}
- _solutionContainerSystem.Refill(args.Args.Target.Value, args.AdditionalData.DrinkSolution, drained);
+ _solutionContainerSystem.Refill(args.Args.Target.Value, solution, drained);
args.Handled = true;
return;
}
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough"), args.Args.Target.Value, args.Args.Target.Value);
- if (component.ForceDrink)
+ if (forceDrink)
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough-other"), args.Args.Target.Value, args.Args.User);
_spillableSystem.SpillAt(args.Args.Target.Value, drained, "PuddleSmear");
}
else
- _solutionContainerSystem.TryAddSolution(uid, args.AdditionalData.DrinkSolution, drained);
+ _solutionContainerSystem.TryAddSolution(uid, solution, drained);
args.Handled = true;
return;
}
- var flavors = args.AdditionalData.FlavorMessage;
+ var flavors = args.FlavorMessage;
- if (component.ForceDrink)
+ if (forceDrink)
{
var targetName = Identity.Entity(args.Args.Target.Value, EntityManager);
var userName = Identity.Entity(args.Args.User, EntityManager);
_audio.PlayPvs(_audio.GetSound(component.UseSound), args.Args.Target.Value, AudioParams.Default.WithVolume(-2f));
- _reaction.DoEntityReaction(args.Args.Target.Value, args.AdditionalData.DrinkSolution, ReactionMethod.Ingestion);
+ _reaction.DoEntityReaction(args.Args.Target.Value, solution, ReactionMethod.Ingestion);
//TODO: Grab the stomach UIDs somehow without using Owner
_stomachSystem.TryTransferSolution(firstStomach.Value.Comp.Owner, drained, firstStomach.Value.Comp);
-
- component.ForceDrink = false;
args.Handled = true;
var comp = EnsureComp<ForensicsComponent>(uid);
return remainingString;
}
-
- private record struct DrinkData(Solution DrinkSolution, string FlavorMessage)
- {
- public readonly Solution DrinkSolution = DrinkSolution;
- public readonly string FlavorMessage = FlavorMessage;
- }
}
}
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Chemistry.EntitySystems;
-using Content.Server.DoAfter;
using Content.Server.Hands.Components;
using Content.Server.Nutrition.Components;
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.Inventory;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
+using Content.Shared.Nutrition;
using Content.Shared.Verbs;
using Robust.Shared.Audio;
using Robust.Shared.Player;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly UtensilSystem _utensilSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
{
base.Initialize();
+ // TODO add InteractNoHandEvent for entities like mice.
SubscribeLocalEvent<FoodComponent, UseInHandEvent>(OnUseFoodInHand);
SubscribeLocalEvent<FoodComponent, AfterInteractEvent>(OnFeedFood);
SubscribeLocalEvent<FoodComponent, GetVerbsEvent<AlternativeVerb>>(AddEatVerb);
- SubscribeLocalEvent<FoodComponent, DoAfterEvent<FoodData>>(OnDoAfter);
+ SubscribeLocalEvent<FoodComponent, ConsumeDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<InventoryComponent, IngestionAttemptEvent>(OnInventoryIngestAttempt);
}
if (ev.Handled)
return;
- ev.Handled = TryFeed(ev.User, ev.User, uid, foodComponent);
+ ev.Handled = true;
+ 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, uid, foodComponent);
+ args.Handled = true;
+ TryFeed(args.User, args.Target.Value, uid, foodComponent);
}
public bool TryFeed(EntityUid user, EntityUid target, EntityUid food, FoodComponent foodComp)
return false;
// Target can't be fed or they're already eating
- if (!EntityManager.HasComponent<BodyComponent>(target) || foodComp.Eating)
+ if (!EntityManager.HasComponent<BodyComponent>(target))
return false;
- if (!_solutionContainerSystem.TryGetSolution(food, foodComp.SolutionName, out var foodSolution))
+ if (!_solutionContainerSystem.TryGetSolution(food, foodComp.SolutionName, out var foodSolution) || foodSolution.Name == null)
return false;
var flavors = _flavorProfileSystem.GetLocalizedFlavorsMessage(food, user, foodSolution);
if (IsMouthBlocked(target, user))
return false;
- if (!TryGetRequiredUtensils(user, foodComp, out var utensils))
- return false;
-
if (!_interactionSystem.InRangeUnobstructed(user, food, popup: true))
return true;
- foodComp.Eating = true;
- foodComp.ForceFeed = user != target;
+ if (!TryGetRequiredUtensils(user, foodComp, out _))
+ return true;
- if (foodComp.ForceFeed)
+ var forceFeed = user != target;
+
+ if (forceFeed)
{
var userName = Identity.Entity(user, EntityManager);
_popupSystem.PopupEntity(Loc.GetString("food-system-force-feed", ("user", userName)),
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is eating {ToPrettyString(food):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}");
}
- var foodData = new FoodData(foodSolution, flavors, utensils);
-
- var doAfterEventArgs = new DoAfterEventArgs(user, foodComp.ForceFeed ? foodComp.ForceFeedDelay : foodComp.Delay, target: target, used: food)
+ var doAfterEventArgs = new DoAfterArgs(
+ user,
+ forceFeed ? foodComp.ForceFeedDelay : foodComp.Delay,
+ new ConsumeDoAfterEvent(foodSolution.Name, flavors),
+ eventTarget: food,
+ target: target,
+ used: food)
{
- RaiseOnTarget = foodComp.ForceFeed,
- RaiseOnUser = false, //causes a crash if mice eat if true
- BreakOnUserMove = foodComp.ForceFeed,
+ BreakOnUserMove = forceFeed,
BreakOnDamage = true,
- BreakOnStun = true,
- BreakOnTargetMove = foodComp.ForceFeed,
+ BreakOnTargetMove = forceFeed,
MovementThreshold = 0.01f,
DistanceThreshold = 1.0f,
- NeedHand = true
+ // Mice and the like can eat without hands.
+ // TODO maybe set this based on some CanEatWithoutHands event or component?
+ NeedHand = forceFeed,
};
- _doAfterSystem.DoAfter(doAfterEventArgs, foodData);
-
+ _doAfterSystem.TryStartDoAfter(doAfterEventArgs);
return true;
-
}
- private void OnDoAfter(EntityUid uid, FoodComponent component, DoAfterEvent<FoodData> args)
+ private void OnDoAfter(EntityUid uid, FoodComponent component, ConsumeDoAfterEvent args)
{
- //Prevents the target from being force fed food but allows the user to chow down
- if (args.Cancelled)
- {
- component.Eating = false;
- component.ForceFeed = false;
- return;
- }
-
- if (args.Handled || component.Deleted || args.Args.Target == null)
+ if (args.Cancelled || args.Handled || component.Deleted || args.Args.Target == null)
return;
if (!TryComp<BodyComponent>(args.Args.Target.Value, out var body))
if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(args.Args.Target.Value, out var stomachs, body))
return;
- component.Eating = false;
+ if (!_solutionContainerSystem.TryGetSolution(args.Used, args.Solution, out var solution))
+ return;
- var transferAmount = component.TransferAmount != null ? FixedPoint2.Min((FixedPoint2) component.TransferAmount, args.AdditionalData.FoodSolution.Volume) : args.AdditionalData.FoodSolution.Volume;
+ if (!TryGetRequiredUtensils(args.User, component, out var utensils))
+ return;
+
+ args.Handled = true;
+
+ var transferAmount = component.TransferAmount != null ? FixedPoint2.Min((FixedPoint2) component.TransferAmount, solution.Volume) : solution.Volume;
- var split = _solutionContainerSystem.SplitSolution(uid, args.AdditionalData.FoodSolution, transferAmount);
+ var split = _solutionContainerSystem.SplitSolution(uid, solution, transferAmount);
//TODO: Get the stomach UID somehow without nabbing owner
var firstStomach = stomachs.FirstOrNull(stomach => _stomachSystem.CanTransferSolution(stomach.Comp.Owner, split));
+ var forceFeed = args.User != args.Target;
+
// No stomach so just popup a message that they can't eat.
if (firstStomach == null)
{
- _solutionContainerSystem.TryAddSolution(uid, args.AdditionalData.FoodSolution, split);
- _popupSystem.PopupEntity(component.ForceFeed ? Loc.GetString("food-system-you-cannot-eat-any-more-other") : Loc.GetString("food-system-you-cannot-eat-any-more"), args.Args.Target.Value, args.Args.User);
- args.Handled = true;
+ _solutionContainerSystem.TryAddSolution(uid, solution, 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);
return;
}
- _reaction.DoEntityReaction(args.Args.Target.Value, args.AdditionalData.FoodSolution, ReactionMethod.Ingestion);
+ _reaction.DoEntityReaction(args.Args.Target.Value, solution, ReactionMethod.Ingestion);
_stomachSystem.TryTransferSolution(firstStomach.Value.Comp.Owner, split, firstStomach.Value.Comp);
- var flavors = args.AdditionalData.FlavorMessage;
+ var flavors = args.FlavorMessage;
- if (component.ForceFeed)
+ if (forceFeed)
{
var targetName = Identity.Entity(args.Args.Target.Value, EntityManager);
var userName = Identity.Entity(args.Args.User, EntityManager);
// log successful force feed
_adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(uid):user} forced {ToPrettyString(args.Args.User):target} to eat {ToPrettyString(uid):food}");
- component.ForceFeed = false;
}
else
{
_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
- //TODO: Replace utensil owner with actual UID
- foreach (var utensil in args.AdditionalData.Utensils)
+ foreach (var utensil in utensils)
{
- _utensilSystem.TryBreak(utensil.Owner, args.Args.User);
+ _utensilSystem.TryBreak(utensil, args.Args.User);
}
if (component.UsesRemaining > 0)
- {
- args.Handled = true;
return;
- }
-
if (string.IsNullOrEmpty(component.TrashPrototype))
EntityManager.QueueDeleteEntity(uid);
else
DeleteAndSpawnTrash(component, uid, args.Args.User);
-
- args.Handled = true;
}
private void DeleteAndSpawnTrash(FoodComponent component, EntityUid food, EntityUid? user = null)
}
private bool TryGetRequiredUtensils(EntityUid user, FoodComponent component,
- out List<UtensilComponent> utensils, HandsComponent? hands = null)
+ out List<EntityUid> utensils, HandsComponent? hands = null)
{
- utensils = new List<UtensilComponent>();
+ utensils = new List<EntityUid>();
if (component.Utensil != UtensilType.None)
return true;
{
// Add to used list
usedTypes |= utensil.Types;
- utensils.Add(utensil);
+ utensils.Add(item);
}
}
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;
- }
}
}
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.APC;
+using Content.Shared.DoAfter;
using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Popups;
+using Content.Shared.Power;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
using JetBrains.Annotations;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
private const float ScrewTime = 2f;
SubscribeLocalEvent<ApcComponent, ApcToggleMainBreakerMessage>(OnToggleMainBreaker);
SubscribeLocalEvent<ApcComponent, GotEmaggedEvent>(OnEmagged);
- SubscribeLocalEvent<ApcToolFinishedEvent>(OnToolFinished);
+ SubscribeLocalEvent<ApcComponent, ApcToolFinishedEvent>(OnToolFinished);
SubscribeLocalEvent<ApcComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<ApcComponent, ExaminedEvent>(OnExamine);
if (!EntityManager.TryGetComponent(args.Used, out ToolComponent? tool))
return;
- var toolEvData = new ToolEventData(new ApcToolFinishedEvent(uid), fuel: 0f);
-
- if (_toolSystem.UseTool(args.Used, args.User, uid, ScrewTime, new [] { "Screwing" }, toolEvData, toolComponent:tool))
+ if (_toolSystem.UseTool(args.Used, args.User, uid, ScrewTime, "Screwing", new ApcToolFinishedEvent(), toolComponent:tool))
args.Handled = true;
}
- private void OnToolFinished(ApcToolFinishedEvent args)
+ private void OnToolFinished(EntityUid uid, ApcComponent component, ApcToolFinishedEvent args)
{
- if (!EntityManager.TryGetComponent(args.Target, out ApcComponent? component))
+ if (!args.Cancelled)
return;
- component.IsApcOpen = !component.IsApcOpen;
- if (TryComp(args.Target, out AppearanceComponent? appearance))
- {
- UpdatePanelAppearance(args.Target, appearance);
- }
+ component.IsApcOpen = !component.IsApcOpen;
+ UpdatePanelAppearance(uid, apc: component);
- 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);
+ // this will play on top of the normal screw driver tool sound.
+ var sound = component.IsApcOpen ? component.ScrewdriverOpenSound : component.ScrewdriverCloseSound;
+ _audio.PlayPvs(sound, uid);
}
private void UpdatePanelAppearance(EntityUid uid, AppearanceComponent? appearance = null, ApcComponent? apc = null)
_appearance.SetData(uid, ApcVisuals.PanelState, GetPanelState(apc), appearance);
}
- private sealed class ApcToolFinishedEvent : EntityEventArgs
- {
- public EntityUid Target { get; }
-
- public ApcToolFinishedEvent(EntityUid target)
- {
- Target = target;
- }
- }
-
private void OnExamine(EntityUid uid, ApcComponent component, ExaminedEvent args)
{
args.PushMarkup(Loc.GetString(component.IsApcOpen
using Content.Server.Power.Components;
using Content.Server.Stack;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
InitializeCablePlacer();
SubscribeLocalEvent<CableComponent, InteractUsingEvent>(OnInteractUsing);
- SubscribeLocalEvent<CableComponent, CuttingFinishedEvent>(OnCableCut);
+ SubscribeLocalEvent<CableComponent, CableCuttingFinishedEvent>(OnCableCut);
// Shouldn't need re-anchoring.
SubscribeLocalEvent<CableComponent, AnchorStateChangedEvent>(OnAnchorChanged);
}
if (args.Handled)
return;
- 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);
+ args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, cable.CuttingDelay, cable.CuttingQuality, new CableCuttingFinishedEvent());
}
- private void OnCableCut(EntityUid uid, CableComponent cable, CuttingFinishedEvent args)
+ private void OnCableCut(EntityUid uid, CableComponent cable, DoAfterEvent args)
{
+ if (args.Cancelled)
+ return;
+
if (_electrocutionSystem.TryDoElectrifiedAct(uid, args.User))
return;
QueueDel(uid);
}
}
-
-public sealed class CuttingFinishedEvent : EntityEventArgs
-{
- public EntityUid User;
-
- public CuttingFinishedEvent(EntityUid user)
- {
- User = user;
- }
-}
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField("ammo")]
public int CurrentAmmo = DefaultAmmoCount;
-
- public CancellationTokenSource? CancelToken = null;
}
}
-using System.Threading;
using Content.Server.Administration.Logs;
-using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Server.RCD.Components;
using Content.Shared.Database;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
if (args.Handled || !args.CanReach)
return;
- if (rcd.CancelToken != null)
- {
- rcd.CancelToken?.Cancel();
- rcd.CancelToken = null;
- args.Handled = true;
- return;
- }
-
if (!args.ClickLocation.IsValid(EntityManager)) return;
var clickLocationMod = args.ClickLocation;
var user = args.User;
//Using an RCD isn't instantaneous
- rcd.CancelToken = new CancellationTokenSource();
- var doAfterEventArgs = new DoAfterEventArgs(user, rcd.Delay, rcd.CancelToken.Token, args.Target)
+ var doAfterEventArgs = new DoAfterArgs(user, rcd.Delay, new AwaitedDoAfterEvent(), null, target: args.Target)
{
BreakOnDamage = true,
- BreakOnStun = true,
NeedHand = true,
ExtraCheck = () => IsRCDStillValid(rcd, args, mapGrid, tile, startingMode) //All of the sanity checks are here
};
var result = await _doAfterSystem.WaitDoAfter(doAfterEventArgs);
- rcd.CancelToken = null;
-
if (result == DoAfterStatus.Cancelled)
return;
using Content.Server.Administration.Logs;
using Content.Shared.Damage;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Popups;
+using Content.Shared.Repairable;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
namespace Content.Server.Repairable
{
- public sealed class RepairableSystem : EntitySystem
+ public sealed class RepairableSystem : SharedRepairableSystem
{
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
private void OnRepairFinished(EntityUid uid, RepairableComponent component, RepairFinishedEvent args)
{
+ if (args.Cancelled)
+ return;
+
if (!EntityManager.TryGetComponent(uid, out DamageableComponent? damageable) || damageable.TotalDamage == 0)
return;
_adminLogger.Add(LogType.Healed, $"{ToPrettyString(args.User):user} repaired {ToPrettyString(uid):target} back to full health");
}
-
uid.PopupMessage(args.User,
Loc.GetString("comp-repairable-repair",
("target", uid),
- ("tool", args.Used)));
+ ("tool", args.Used!)));
}
public async void Repair(EntityUid uid, RepairableComponent component, InteractUsingEvent args)
{
+ if (args.Handled)
+ return;
+
// Only try repair the target if it is damaged
if (!EntityManager.TryGetComponent(uid, out DamageableComponent? damageable) || damageable.TotalDamage == 0)
return;
if (args.User == args.Target)
delay *= component.SelfRepairPenalty;
- var toolEvData = new ToolEventData(new RepairFinishedEvent(args.User, args.Used), component.FuelCost, targetEntity:uid);
-
// Can the tool actually repair this, does it have enough fuel?
- if (!_toolSystem.UseTool(args.Used, args.User, uid, delay, component.QualityNeeded, toolEvData, component.FuelCost))
- return;
-
- args.Handled = true;
- }
- }
-
- public sealed class RepairFinishedEvent : EntityEventArgs
- {
- public EntityUid User;
- public EntityUid Used;
-
- public RepairFinishedEvent(EntityUid user, EntityUid used)
- {
- User = user;
- Used = used;
+ args.Handled = !_toolSystem.UseTool(args.Used, args.User, uid, delay, component.QualityNeeded, new RepairFinishedEvent(), component.FuelCost);
}
}
}
-using System.Threading;
+using Content.Shared.DoAfter;
namespace Content.Server.Resist;
[DataField("baseResistTime")]
public float BaseResistTime = 5f;
- [DataField("isEscaping")]
- public bool IsEscaping;
+ public bool IsEscaping => DoAfter != null;
- public CancellationTokenSource? CancelToken;
+ [DataField("doAfter")]
+ public DoAfterId? DoAfter;
}
-using System.Threading;
-using Content.Server.DoAfter;
using Content.Server.Contests;
using Robust.Shared.Containers;
using Content.Server.Popups;
using Content.Shared.DoAfter;
using Content.Shared.Movement.Events;
using Content.Shared.Interaction.Events;
+using Content.Shared.Resist;
namespace Content.Server.Resist;
public sealed class EscapeInventorySystem : EntitySystem
{
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
base.Initialize();
SubscribeLocalEvent<CanEscapeInventoryComponent, MoveInputEvent>(OnRelayMovement);
- SubscribeLocalEvent<CanEscapeInventoryComponent, DoAfterEvent<EscapeInventoryEvent>>(OnEscape);
+ SubscribeLocalEvent<CanEscapeInventoryComponent, EscapeInventoryEvent>(OnEscape);
SubscribeLocalEvent<CanEscapeInventoryComponent, DroppedEvent>(OnDropped);
}
if (component.IsEscaping)
return;
- component.CancelToken = new CancellationTokenSource();
- component.IsEscaping = true;
- var escapeEvent = new EscapeInventoryEvent();
- var doAfterEventArgs = new DoAfterEventArgs(user, component.BaseResistTime * multiplier, cancelToken: component.CancelToken.Token, target:container)
+ var doAfterEventArgs = new DoAfterArgs(user, component.BaseResistTime * multiplier, new EscapeInventoryEvent(), user, target: container)
{
BreakOnTargetMove = false,
BreakOnUserMove = true,
BreakOnDamage = true,
- BreakOnStun = true,
NeedHand = false
};
+ if (!_doAfterSystem.TryStartDoAfter(doAfterEventArgs, out component.DoAfter))
+ return;
+
+ Dirty(component);
_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, escapeEvent);
}
- private void OnEscape(EntityUid uid, CanEscapeInventoryComponent component, DoAfterEvent<EscapeInventoryEvent> args)
+ private void OnEscape(EntityUid uid, CanEscapeInventoryComponent component, EscapeInventoryEvent args)
{
- if (args.Cancelled)
- {
- component.CancelToken = null;
- component.IsEscaping = false;
- return;
- }
+ component.DoAfter = null;
+ Dirty(component);
- if (args.Handled)
+ if (args.Handled || args.Cancelled)
return;
Transform(uid).AttachParentToContainerOrGrid(EntityManager);
-
- component.CancelToken = null;
- component.IsEscaping = false;
args.Handled = true;
}
private void OnDropped(EntityUid uid, CanEscapeInventoryComponent component, DroppedEvent args)
{
- component.CancelToken?.Cancel();
- component.CancelToken = null;
- }
-
- private sealed class EscapeInventoryEvent : EntityEventArgs
- {
-
+ if (component.DoAfter != null)
+ _doAfterSystem.Cancel(component.DoAfter);
}
}
/// </summary>
[ViewVariables]
public bool IsResisting = false;
-
- public CancellationTokenSource? CancelToken;
}
-using System.Threading;
-using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Lock;
using Content.Shared.Movement.Events;
using Content.Shared.Popups;
-using Robust.Shared.Containers;
+using Content.Shared.Resist;
namespace Content.Server.Resist;
public sealed class ResistLockerSystem : EntitySystem
{
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly LockSystem _lockSystem = default!;
[Dependency] private readonly EntityStorageSystem _entityStorage = default!;
{
base.Initialize();
SubscribeLocalEvent<ResistLockerComponent, ContainerRelayMovementEntityEvent>(OnRelayMovement);
- SubscribeLocalEvent<ResistLockerComponent, DoAfterEvent<LockerDoAfterData>>(OnDoAfter);
- SubscribeLocalEvent<ResistLockerComponent, EntRemovedFromContainerMessage>(OnRemoved);
+ SubscribeLocalEvent<ResistLockerComponent, ResistLockerDoAfterEvent>(OnDoAfter);
}
private void OnRelayMovement(EntityUid uid, ResistLockerComponent component, ref ContainerRelayMovementEntityEvent args)
if (!Resolve(target, ref storageComponent, ref resistLockerComponent))
return;
- resistLockerComponent.CancelToken = new CancellationTokenSource();
-
- var doAfterEventArgs = new DoAfterEventArgs(user, resistLockerComponent.ResistTime, cancelToken:resistLockerComponent.CancelToken.Token, target:target)
+ var doAfterEventArgs = new DoAfterArgs(user, resistLockerComponent.ResistTime, new ResistLockerDoAfterEvent(), target, target: target)
{
BreakOnTargetMove = false,
BreakOnUserMove = true,
BreakOnDamage = true,
- BreakOnStun = true,
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, new LockerDoAfterData());
- }
-
- private void OnRemoved(EntityUid uid, ResistLockerComponent component, EntRemovedFromContainerMessage args)
- {
- component.CancelToken?.Cancel();
- component.CancelToken = null;
+ _doAfterSystem.TryStartDoAfter(doAfterEventArgs);
}
- private void OnDoAfter(EntityUid uid, ResistLockerComponent component, DoAfterEvent<LockerDoAfterData> args)
+ private void OnDoAfter(EntityUid uid, ResistLockerComponent component, DoAfterEvent args)
{
if (args.Cancelled)
{
component.IsResisting = false;
- component.CancelToken = null;
_popupSystem.PopupEntity(Loc.GetString("resist-locker-component-resist-interrupted"), args.Args.User, args.Args.User, PopupType.Medium);
return;
}
_entityStorage.TryOpenStorage(args.Args.User, uid);
}
- component.CancelToken = null;
args.Handled = true;
}
-
- private struct LockerDoAfterData
- {
- }
}
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Revenant.Components;
+using Content.Shared.Revenant.EntitySystems;
using Robust.Shared.Physics.Components;
using Robust.Shared.Utility;
private void InitializeAbilities()
{
SubscribeLocalEvent<RevenantComponent, InteractNoHandEvent>(OnInteract);
- SubscribeLocalEvent<RevenantComponent, DoAfterEvent<SoulEvent>>(OnSoulSearch);
- SubscribeLocalEvent<RevenantComponent, DoAfterEvent<HarvestEvent>>(OnHarvest);
+ SubscribeLocalEvent<RevenantComponent, SoulEvent>(OnSoulSearch);
+ SubscribeLocalEvent<RevenantComponent, HarvestEvent>(OnHarvest);
SubscribeLocalEvent<RevenantComponent, RevenantDefileActionEvent>(OnDefileAction);
SubscribeLocalEvent<RevenantComponent, RevenantOverloadLightsActionEvent>(OnOverloadLightsAction);
private void BeginSoulSearchDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant)
{
- _popup.PopupEntity(Loc.GetString("revenant-soul-searching", ("target", target)), uid, uid, PopupType.Medium);
- var soulSearchEvent = new SoulEvent();
- var searchDoAfter = new DoAfterEventArgs(uid, revenant.SoulSearchDuration, target:target)
+ var searchDoAfter = new DoAfterArgs(uid, revenant.SoulSearchDuration, new SoulEvent(), uid, target: target)
{
BreakOnUserMove = true,
DistanceThreshold = 2
};
- _doAfter.DoAfter(searchDoAfter, soulSearchEvent);
+
+ if (!_doAfter.TryStartDoAfter(searchDoAfter))
+ return;
+
+ _popup.PopupEntity(Loc.GetString("revenant-soul-searching", ("target", target)), uid, uid, PopupType.Medium);
}
- private void OnSoulSearch(EntityUid uid, RevenantComponent component, DoAfterEvent<SoulEvent> args)
+ private void OnSoulSearch(EntityUid uid, RevenantComponent component, SoulEvent args)
{
if (args.Handled || args.Cancelled)
return;
return;
}
- var harvestEvent = new HarvestEvent();
-
- var doAfter = new DoAfterEventArgs(uid, revenant.HarvestDebuffs.X, target:target)
+ var doAfter = new DoAfterArgs(uid, revenant.HarvestDebuffs.X, new HarvestEvent(), uid, target: target)
{
DistanceThreshold = 2,
BreakOnUserMove = true,
- NeedHand = false
+ RequireCanInteract = false, // stuns itself
};
+ if (!_doAfter.TryStartDoAfter(doAfter))
+ return;
+
_appearance.SetData(uid, RevenantVisuals.Harvesting, true);
_popup.PopupEntity(Loc.GetString("revenant-soul-begin-harvest", ("target", target)),
target, PopupType.Large);
TryUseAbility(uid, revenant, 0, revenant.HarvestDebuffs);
- _doAfter.DoAfter(doAfter, harvestEvent);
}
- private void OnHarvest(EntityUid uid, RevenantComponent component, DoAfterEvent<HarvestEvent> args)
+ private void OnHarvest(EntityUid uid, RevenantComponent component, HarvestEvent args)
{
if (args.Cancelled)
{
_emag.DoEmagEffect(ent, ent); //it emags itself. spooky.
}
}
-
- private sealed class SoulEvent : EntityEventArgs
- {
-
- }
-
- private sealed class HarvestEvent : EntityEventArgs
- {
-
- }
}
using Content.Shared.Alert;
using Content.Shared.Damage;
using Content.Shared.Interaction;
-using Content.Server.DoAfter;
using Content.Server.GameTicking;
using Content.Shared.Stunnable;
using Content.Shared.Revenant;
using Content.Shared.Tag;
using Content.Server.Store.Components;
using Content.Server.Store.Systems;
+using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Maps;
using Content.Shared.Mobs.Systems;
[Dependency] private readonly ActionsSystem _action = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly DamageableSystem _damage = default!;
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly PhysicsSystem _physics = default!;
-using Content.Server.DoAfter;
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;
using Content.Shared.Sticky.Components;
using Content.Shared.Verbs;
-using Robust.Server.GameObjects;
using Robust.Shared.Containers;
-using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Server.Sticky.Systems;
public sealed class StickySystem : EntitySystem
{
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent<StickyComponent, DoAfterEvent>(OnStickSuccessful);
+ SubscribeLocalEvent<StickyComponent, StickyDoAfterEvent>(OnStickFinished);
SubscribeLocalEvent<StickyComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<StickyComponent, GetVerbsEvent<Verb>>(AddUnstickVerb);
}
component.Stick = true;
// start sticking object to target
- _doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: target, used: uid)
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(user, delay, new StickyDoAfterEvent(), uid, target: target, used: uid)
{
- BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = true
return true;
}
- private void OnStickSuccessful(EntityUid uid, StickyComponent component, DoAfterEvent args)
+ private void OnStickFinished(EntityUid uid, StickyComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Args.Target == null)
return;
if (component.Stick)
StickToEntity(uid, args.Args.Target.Value, args.Args.User, component);
-
else
UnstickFromEntity(uid, args.Args.User, component);
component.Stick = false;
// start unsticking object
- _doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: uid)
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(user, delay, new StickyDoAfterEvent(), uid, target: uid)
{
- BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = true
using System.Linq;
-using Content.Server.DoAfter;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Mind.Components;
using Content.Server.Resist;
using Content.Shared.DoAfter;
using Content.Shared.Lock;
using Content.Shared.Storage.Components;
+using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Random;
using Robust.Shared.Timing;
[Dependency] private readonly EntityStorageSystem _entityStorage = default!;
[Dependency] private readonly WeldableSystem _weldableSystem = default!;
[Dependency] private readonly LockSystem _lockSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
public override void Initialize()
SubscribeLocalEvent<BluespaceLockerComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<BluespaceLockerComponent, StorageBeforeOpenEvent>(PreOpen);
SubscribeLocalEvent<BluespaceLockerComponent, StorageAfterCloseEvent>(PostClose);
- SubscribeLocalEvent<BluespaceLockerComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<BluespaceLockerComponent, BluespaceLockerDoAfterEvent>(OnDoAfter);
}
private void OnStartup(EntityUid uid, BluespaceLockerComponent component, ComponentStartup args)
{
EnsureComp<DoAfterComponent>(uid);
- _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.BehaviorProperties.Delay));
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.BehaviorProperties.Delay, new BluespaceLockerDoAfterEvent(), uid));
return;
}
-using System.Threading;
using Content.Shared.Interaction;
using Content.Server.Storage.Components;
using Content.Shared.Storage.Components;
using Content.Shared.Verbs;
using Content.Server.Disposal.Unit.Components;
using Content.Server.Disposal.Unit.EntitySystems;
-using Content.Server.DoAfter;
+using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
using Content.Shared.DoAfter;
using Content.Shared.Placeable;
using Content.Shared.Storage;
{
public sealed class DumpableSystem : EntitySystem
{
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly DisposalUnitSystem _disposalUnitSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
SubscribeLocalEvent<DumpableComponent, AfterInteractEvent>(OnAfterInteract, after: new[]{ typeof(StorageSystem) });
SubscribeLocalEvent<DumpableComponent, GetVerbsEvent<AlternativeVerb>>(AddDumpVerb);
SubscribeLocalEvent<DumpableComponent, GetVerbsEvent<UtilityVerb>>(AddUtilityVerbs);
- SubscribeLocalEvent<DumpableComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<DumpableComponent, DumpableDoAfterEvent>(OnDoAfter);
}
private void OnAfterInteract(EntityUid uid, DumpableComponent component, AfterInteractEvent args)
{
- if (!args.CanReach)
+ if (!args.CanReach || args.Handled)
return;
- if (HasComp<DisposalUnitComponent>(args.Target) || HasComp<PlaceableSurfaceComponent>(args.Target))
- StartDoAfter(uid, args.Target.Value, args.User, component);
+ if (!HasComp<DisposalUnitComponent>(args.Target) && !HasComp<PlaceableSurfaceComponent>(args.Target))
+ return;
+
+ StartDoAfter(uid, args.Target.Value, args.User, component);
+ args.Handled = true;
}
private void AddDumpVerb(EntityUid uid, DumpableComponent dumpable, GetVerbsEvent<AlternativeVerb> args)
{
Act = () =>
{
- StartDoAfter(uid, null, args.User, dumpable);//Had multiplier of 0.6f
+ StartDoAfter(uid, args.Target, args.User, dumpable);//Had multiplier of 0.6f
},
Text = Loc.GetString("dump-verb-name"),
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/drop.svg.192dpi.png")),
float delay = storage.StoredEntities.Count * (float) dumpable.DelayPerItem.TotalSeconds * dumpable.Multiplier;
- _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, delay, target: targetUid, used: storageUid)
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(userUid, delay, new DumpableDoAfterEvent(), storageUid, target: targetUid, used: storageUid)
{
- RaiseOnTarget = false,
BreakOnTargetMove = true,
BreakOnUserMove = true,
- BreakOnStun = true,
NeedHand = true
});
}
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
-using System.Threading;
-using Content.Server.DoAfter;
using Content.Server.Interaction;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Item;
using Content.Shared.DoAfter;
using Content.Shared.Implants.Components;
using Content.Shared.Lock;
-using Content.Shared.Movement.Events;
using Content.Server.Ghost.Components;
using Content.Server.Administration.Managers;
using Content.Shared.Administration;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IAdminManager _admin = default!;
[Dependency] private readonly ContainerSystem _containerSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!;
[Dependency] private readonly EntityStorageSystem _entityStorage = default!;
[Dependency] private readonly InteractionSystem _interactionSystem = default!;
SubscribeLocalEvent<ServerStorageComponent, BoundUIClosedEvent>(OnBoundUIClosed);
SubscribeLocalEvent<ServerStorageComponent, EntRemovedFromContainerMessage>(OnStorageItemRemoved);
- SubscribeLocalEvent<ServerStorageComponent, DoAfterEvent<StorageData>>(OnDoAfter);
+ SubscribeLocalEvent<ServerStorageComponent, AreaPickupDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<StorageFillComponent, MapInitEvent>(OnStorageFillMapInit);
}
//If there's only one then let's be generous
if (validStorables.Count > 1)
{
- var storageData = new StorageData(validStorables);
- var doAfterArgs = new DoAfterEventArgs(args.User, 0.2f * validStorables.Count, target: uid)
+ var doAfterArgs = new DoAfterArgs(args.User, 0.2f * validStorables.Count, new AreaPickupDoAfterEvent(validStorables), uid, target: uid)
{
- BreakOnStun = true,
BreakOnDamage = true,
BreakOnUserMove = true,
NeedHand = true
};
- _doAfterSystem.DoAfter(doAfterArgs, storageData);
+ _doAfterSystem.TryStartDoAfter(doAfterArgs);
}
return;
}
}
- private void OnDoAfter(EntityUid uid, ServerStorageComponent component, DoAfterEvent<StorageData> args)
+ private void OnDoAfter(EntityUid uid, ServerStorageComponent component, AreaPickupDoAfterEvent args)
{
if (args.Handled || args.Cancelled)
return;
var xformQuery = GetEntityQuery<TransformComponent>();
xformQuery.TryGetComponent(uid, out var xform);
- foreach (var entity in args.AdditionalData.ValidStorables)
+ foreach (var entity in args.Entities)
{
// Check again, situation may have changed for some entities, but we'll still pick up any that are valid
if (_containerSystem.IsEntityInContainer(entity)
_popupSystem.PopupEntity(Loc.GetString(message), player, player);
}
-
- private record struct StorageData(List<EntityUid> validStorables)
- {
- public List<EntityUid> ValidStorables = validStorables;
- }
}
}
using System.Linq;
-using Content.Server.DoAfter;
using Content.Server.Ensnaring;
using Content.Server.Hands.Components;
using Content.Shared.CombatMode;
using Content.Shared.Strip.Components;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
-using System.Threading;
using Content.Server.Administration.Logs;
using Content.Shared.Cuffs;
using Content.Shared.Cuffs.Components;
[Dependency] private readonly SharedCuffableSystem _cuffable = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
- [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly EnsnareableSystem _ensnaring = default!;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
if (!TryComp<EnsnaringComponent>(entity, out var ensnaring))
continue;
- _ensnaring.TryFree(uid, entity, ensnaring, user);
+ _ensnaring.TryFree(uid, user, entity, ensnaring);
return;
}
}
- private void OnStripButtonPressed(EntityUid uid, StrippableComponent component, StrippingSlotButtonPressed args)
+ private void OnStripButtonPressed(EntityUid target, StrippableComponent component, StrippingSlotButtonPressed args)
{
if (args.Session.AttachedEntity is not {Valid: true} user ||
!TryComp<HandsComponent>(user, out var userHands))
if (args.IsHand)
{
- StripHand(uid, user, args.Slot, component, userHands);
+ StripHand(target, user, args.Slot, component, userHands);
return;
}
- if (!TryComp<InventoryComponent>(component.Owner, out var inventory))
+ if (!TryComp<InventoryComponent>(target, out var inventory))
return;
- var hasEnt = _inventorySystem.TryGetSlotEntity(component.Owner, args.Slot, out _, inventory);
+ var hasEnt = _inventorySystem.TryGetSlotEntity(target, args.Slot, out var held, inventory);
if (userHands.ActiveHandEntity != null && !hasEnt)
- PlaceActiveHandItemInInventory(user, args.Slot, component);
+ PlaceActiveHandItemInInventory(user, target, userHands.ActiveHandEntity.Value, args.Slot, component);
else if (userHands.ActiveHandEntity == null && hasEnt)
- TakeItemFromInventory(user, args.Slot, component);
+ TakeItemFromInventory(user, target, held!.Value, args.Slot, component);
}
private void StripHand(EntityUid target, EntityUid user, string handId, StrippableComponent component, HandsComponent userHands)
return;
}
- if (hand.IsEmpty && userHands.ActiveHandEntity != null)
- PlaceActiveHandItemInHands(user, handId, component);
- else if (!hand.IsEmpty && userHands.ActiveHandEntity == null)
- TakeItemFromHands(user, handId, component);
+ if (userHands.ActiveHandEntity != null && hand.HeldEntity == null)
+ PlaceActiveHandItemInHands(user, target, userHands.ActiveHandEntity.Value, handId, component);
+ else if (userHands.ActiveHandEntity == null && hand.HeldEntity != null)
+ TakeItemFromHands(user,target, hand.HeldEntity.Value, handId, component);
}
public override void StartOpeningStripper(EntityUid user, StrippableComponent component, bool openInCombat = false)
if (args.Target == args.User)
return;
- if (!HasComp<ActorComponent>(args.User))
+ if (!TryComp<ActorComponent>(args.User, out var actor))
return;
- args.Handled = true;
StartOpeningStripper(args.User, component);
}
/// <summary>
/// Places item in user's active hand to an inventory slot.
/// </summary>
- private async void PlaceActiveHandItemInInventory(EntityUid user, string slot, StrippableComponent component)
+ private async void PlaceActiveHandItemInInventory(
+ EntityUid user,
+ EntityUid target,
+ EntityUid held,
+ string slot,
+ StrippableComponent component)
{
var userHands = Comp<HandsComponent>(user);
bool Check()
{
- if (userHands.ActiveHand?.HeldEntity is not { } held)
- {
- user.PopupMessageCursor(Loc.GetString("strippable-component-not-holding-anything"));
+ if (userHands.ActiveHandEntity != held)
return false;
- }
- if (!_handsSystem.CanDropHeld(user, userHands.ActiveHand))
+ if (!_handsSystem.CanDropHeld(user, userHands.ActiveHand!))
{
- user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop"));
+ _popup.PopupCursor(Loc.GetString("strippable-component-cannot-drop"), user);
return false;
}
- if (!_inventorySystem.HasSlot(component.Owner, slot))
- return false;
-
- if (_inventorySystem.TryGetSlotEntity(component.Owner, slot, out _))
+ if (_inventorySystem.TryGetSlotEntity(target, slot, out _))
{
- user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-occupied",("owner", component.Owner)));
+ _popup.PopupCursor(Loc.GetString("strippable-component-item-slot-occupied",("owner", target)), user);
return false;
}
- if (!_inventorySystem.CanEquip(user, component.Owner, held, slot, out _))
+ if (!_inventorySystem.CanEquip(user, target, held, slot, out _))
{
- user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-equip-message",("owner", component.Owner)));
+ _popup.PopupCursor(Loc.GetString("strippable-component-cannot-equip-message",("owner", target)), user);
return false;
}
return true;
}
- if (!_inventorySystem.TryGetSlot(component.Owner, slot, out var slotDef))
+ if (!_inventorySystem.TryGetSlot(target, slot, out var slotDef))
{
- Logger.Error($"{ToPrettyString(user)} attempted to place an item in a non-existent inventory slot ({slot}) on {ToPrettyString(component.Owner)}");
+ Logger.Error($"{ToPrettyString(user)} attempted to place an item in a non-existent inventory slot ({slot}) on {ToPrettyString(target)}");
return;
}
- var (time, stealth) = GetStripTimeModifiers(user, component.Owner, slotDef.StripTime);
+ var userEv = new BeforeStripEvent(slotDef.StripTime);
+ RaiseLocalEvent(user, userEv);
+ var ev = new BeforeGettingStrippedEvent(userEv.Time, userEv.Stealth);
+ RaiseLocalEvent(target, ev);
- var doAfterArgs = new DoAfterEventArgs(user, time, CancellationToken.None, component.Owner)
+ var doAfterArgs = new DoAfterArgs(user, ev.Time, new AwaitedDoAfterEvent(), null, target: target, used: held)
{
ExtraCheck = Check,
- BreakOnStun = true,
+ AttemptFrequency = AttemptFrequency.EveryTick,
BreakOnDamage = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = true,
+ DuplicateCondition = DuplicateConditions.SameTool // Block any other DoAfters featuring this same entity.
};
- if (!stealth && Check() && userHands.ActiveHandEntity != null)
+ if (!ev.Stealth && Check() && userHands.ActiveHandEntity != null)
{
var message = Loc.GetString("strippable-component-alert-owner-insert",
("user", Identity.Entity(user, EntityManager)), ("item", userHands.ActiveHandEntity));
- _popupSystem.PopupEntity(message, component.Owner, component.Owner, PopupType.Large);
+ _popup.PopupEntity(message, target, target, PopupType.Large);
}
- var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);
+ var result = await _doAfter.WaitDoAfter(doAfterArgs);
if (result != DoAfterStatus.Finished) return;
- if (userHands.ActiveHand?.HeldEntity is { } held
- && _handsSystem.TryDrop(user, userHands.ActiveHand, handsComp: userHands))
+ DebugTools.Assert(userHands.ActiveHand?.HeldEntity == held);
+
+ if (_handsSystem.TryDrop(user, handsComp: userHands))
{
- _inventorySystem.TryEquip(user, component.Owner, held, slot);
+ _inventorySystem.TryEquip(user, target, held, slot);
- _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has placed the item {ToPrettyString(held):item} in {ToPrettyString(component.Owner):target}'s {slot} slot");
+ _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has placed the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s {slot} slot");
}
}
/// <summary>
/// Places item in user's active hand in one of the entity's hands.
/// </summary>
- private async void PlaceActiveHandItemInHands(EntityUid user, string handName, StrippableComponent component)
+ private async void PlaceActiveHandItemInHands(
+ EntityUid user,
+ EntityUid target,
+ EntityUid held,
+ string handName,
+ StrippableComponent component)
{
- var hands = Comp<HandsComponent>(component.Owner);
+ var hands = Comp<HandsComponent>(target);
var userHands = Comp<HandsComponent>(user);
bool Check()
{
- if (userHands.ActiveHandEntity == null)
- {
- user.PopupMessageCursor(Loc.GetString("strippable-component-not-holding-anything"));
+ if (userHands.ActiveHandEntity != held)
return false;
- }
if (!_handsSystem.CanDropHeld(user, userHands.ActiveHand!))
{
- user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop"));
+ _popup.PopupCursor(Loc.GetString("strippable-component-cannot-drop"), user);
return false;
}
if (!hands.Hands.TryGetValue(handName, out var hand)
- || !_handsSystem.CanPickupToHand(component.Owner, userHands.ActiveHandEntity.Value, hand, checkActionBlocker: false, hands))
+ || !_handsSystem.CanPickupToHand(target, userHands.ActiveHandEntity.Value, hand, checkActionBlocker: false, hands))
{
- user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-put-message",("owner", component.Owner)));
+ _popup.PopupCursor(Loc.GetString("strippable-component-cannot-put-message",("owner", target)), user);
return false;
}
return true;
}
- var (time, stealth) = GetStripTimeModifiers(user, component.Owner, component.HandStripDelay);
+ var userEv = new BeforeStripEvent(component.HandStripDelay);
+ RaiseLocalEvent(user, userEv);
+ var ev = new BeforeGettingStrippedEvent(userEv.Time, userEv.Stealth);
+ RaiseLocalEvent(target, ev);
- var doAfterArgs = new DoAfterEventArgs(user, time, CancellationToken.None, component.Owner)
+ var doAfterArgs = new DoAfterArgs(user, ev.Time, new AwaitedDoAfterEvent(), null, target: target, used: held)
{
ExtraCheck = Check,
- BreakOnStun = true,
+ AttemptFrequency = AttemptFrequency.EveryTick,
BreakOnDamage = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = true,
+ DuplicateCondition = DuplicateConditions.SameTool
};
- if (!stealth
- && Check()
- && userHands.Hands.TryGetValue(handName, out var handSlot)
- && handSlot.HeldEntity != null)
- {
- _popupSystem.PopupEntity(
- Loc.GetString("strippable-component-alert-owner-insert",
- ("user", Identity.Entity(user, EntityManager)),
- ("item", handSlot.HeldEntity)),
- component.Owner, component.Owner, PopupType.Large);
- }
-
- var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);
+ var result = await _doAfter.WaitDoAfter(doAfterArgs);
if (result != DoAfterStatus.Finished) return;
- if (userHands.ActiveHandEntity is not { } held)
- return;
-
_handsSystem.TryDrop(user, checkActionBlocker: false, handsComp: userHands);
- _handsSystem.TryPickup(component.Owner, held, handName, checkActionBlocker: false, animateUser: true, animate: !stealth, handsComp: hands);
- _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has placed the item {ToPrettyString(held):item} in {ToPrettyString(component.Owner):target}'s hands");
+ _handsSystem.TryPickup(target, held, handName, checkActionBlocker: false, animateUser: true, handsComp: hands);
+ _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has placed the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands");
// hand update will trigger strippable update
}
/// <summary>
/// Takes an item from the inventory and places it in the user's active hand.
/// </summary>
- private async void TakeItemFromInventory(EntityUid user, string slot, StrippableComponent component)
+ private async void TakeItemFromInventory(
+ EntityUid user,
+ EntityUid target,
+ EntityUid item,
+ string slot,
+ StrippableComponent component)
{
bool Check()
{
- if (!_inventorySystem.HasSlot(component.Owner, slot))
- return false;
-
- if (!_inventorySystem.TryGetSlotEntity(component.Owner, slot, out _))
+ if (!_inventorySystem.TryGetSlotEntity(target, slot, out var ent) && ent == item)
{
- user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-free-message", ("owner", component.Owner)));
+ _popup.PopupCursor(Loc.GetString("strippable-component-item-slot-free-message", ("owner", target)), user);
return false;
}
- if (!_inventorySystem.CanUnequip(user, component.Owner, slot, out var reason))
+ if (!_inventorySystem.CanUnequip(user, target, slot, out var reason))
{
- user.PopupMessageCursor(reason);
+ _popup.PopupCursor(reason, user);
return false;
}
return true;
}
- if (!_inventorySystem.TryGetSlot(component.Owner, slot, out var slotDef))
+ if (!_inventorySystem.TryGetSlot(target, slot, out var slotDef))
{
- Logger.Error($"{ToPrettyString(user)} attempted to take an item from a non-existent inventory slot ({slot}) on {ToPrettyString(component.Owner)}");
+ Logger.Error($"{ToPrettyString(user)} attempted to take an item from a non-existent inventory slot ({slot}) on {ToPrettyString(target)}");
return;
}
- var (time, stealth) = GetStripTimeModifiers(user, component.Owner, slotDef.StripTime);
+ var userEv = new BeforeStripEvent(slotDef.StripTime);
+ RaiseLocalEvent(user, userEv);
+ var ev = new BeforeGettingStrippedEvent(userEv.Time, userEv.Stealth);
+ RaiseLocalEvent(target, ev);
- var doAfterArgs = new DoAfterEventArgs(user, time, CancellationToken.None, component.Owner)
+ var doAfterArgs = new DoAfterArgs(user, ev.Time, new AwaitedDoAfterEvent(), null, target: target, used: item)
{
ExtraCheck = Check,
- BreakOnStun = true,
+ AttemptFrequency = AttemptFrequency.EveryTick,
BreakOnDamage = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
+ NeedHand = true,
+ BreakOnHandChange = false, // allow simultaneously removing multiple items.
+ DuplicateCondition = DuplicateConditions.SameTool
};
- if (!stealth && Check())
+ if (!ev.Stealth && Check())
{
if (slotDef.StripHidden)
{
- _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-hidden", ("slot", slot)), component.Owner,
- component.Owner, PopupType.Large);
+ _popup.PopupEntity(Loc.GetString("strippable-component-alert-owner-hidden", ("slot", slot)), target,
+ target, PopupType.Large);
}
else if (_inventorySystem.TryGetSlotEntity(component.Owner, slot, out var slotItem))
{
- _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner", ("user", Identity.Entity(user, EntityManager)), ("item", slotItem)), component.Owner,
- component.Owner, PopupType.Large);
+ _popup.PopupEntity(Loc.GetString("strippable-component-alert-owner", ("user", Identity.Entity(user, EntityManager)), ("item", slotItem)), target,
+ target, PopupType.Large);
}
}
- var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);
+ var result = await _doAfter.WaitDoAfter(doAfterArgs);
if (result != DoAfterStatus.Finished) return;
- if (_inventorySystem.TryGetSlotEntity(component.Owner, slot, out var item) && _inventorySystem.TryUnequip(user, component.Owner, slot))
- {
- // Raise a dropped event, so that things like gas tank internals properly deactivate when stripping
- RaiseLocalEvent(item.Value, new DroppedEvent(user), true);
+ if (!_inventorySystem.TryUnequip(user, component.Owner, slot))
+ return;
+
+ // Raise a dropped event, so that things like gas tank internals properly deactivate when stripping
+ RaiseLocalEvent(item, new DroppedEvent(user), true);
+
+ _handsSystem.PickupOrDrop(user, item);
+ _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}");
- _handsSystem.PickupOrDrop(user, item.Value, animate: !stealth);
- _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has stripped the item {ToPrettyString(item.Value):item} from {ToPrettyString(component.Owner):target}");
- }
}
/// <summary>
/// Takes an item from a hand and places it in the user's active hand.
/// </summary>
- private async void TakeItemFromHands(EntityUid user, string handName, StrippableComponent component)
+ private async void TakeItemFromHands(EntityUid user, EntityUid target, EntityUid item, string handName, StrippableComponent component)
{
- var hands = Comp<HandsComponent>(component.Owner);
+ var hands = Comp<HandsComponent>(target);
var userHands = Comp<HandsComponent>(user);
bool Check()
{
- if (!hands.Hands.TryGetValue(handName, out var hand) || hand.HeldEntity == null)
+ if (!hands.Hands.TryGetValue(handName, out var hand) || hand.HeldEntity != item)
{
- user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-free-message",("owner", component.Owner)));
+ _popup.PopupCursor(Loc.GetString("strippable-component-item-slot-free-message",("owner", target)), user);
return false;
}
if (HasComp<HandVirtualItemComponent>(hand.HeldEntity))
return false;
- if (!_handsSystem.CanDropHeld(component.Owner, hand, false))
+ if (!_handsSystem.CanDropHeld(target, hand, false))
{
- user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop-message",("owner", component.Owner)));
+ _popup.PopupCursor(Loc.GetString("strippable-component-cannot-drop-message",("owner", target)), user);
return false;
}
return true;
}
- var (time, stealth) = GetStripTimeModifiers(user, component.Owner, component.HandStripDelay);
+ var userEv = new BeforeStripEvent(component.HandStripDelay);
+ RaiseLocalEvent(user, userEv);
+ var ev = new BeforeGettingStrippedEvent(userEv.Time, userEv.Stealth);
+ RaiseLocalEvent(target, ev);
- var doAfterArgs = new DoAfterEventArgs(user, time, CancellationToken.None, component.Owner)
+ var doAfterArgs = new DoAfterArgs(user, ev.Time, new AwaitedDoAfterEvent(), null, target: target, used: item)
{
ExtraCheck = Check,
- BreakOnStun = true,
+ AttemptFrequency = AttemptFrequency.EveryTick,
BreakOnDamage = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
+ NeedHand = true,
+ BreakOnHandChange = false, // allow simultaneously removing multiple items.
+ DuplicateCondition = DuplicateConditions.SameTool
};
- if (!stealth
- && Check()
- && hands.Hands.TryGetValue(handName, out var handSlot)
- && handSlot.HeldEntity != null)
+ if (Check() && hands.Hands.TryGetValue(handName, out var handSlot) && handSlot.HeldEntity != null)
{
- _popupSystem.PopupEntity(
+ _popup.PopupEntity(
Loc.GetString("strippable-component-alert-owner",
- ("user", Identity.Entity(user, EntityManager)),
- ("item", handSlot.HeldEntity)),
- component.Owner, component.Owner);
+ ("user", Identity.Entity(user, EntityManager)), ("item", item)),
+ component.Owner,
+ component.Owner);
}
- var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);
+ var result = await _doAfter.WaitDoAfter(doAfterArgs);
if (result != DoAfterStatus.Finished) return;
- if (!hands.Hands.TryGetValue(handName, out var hand) || hand.HeldEntity is not { } held)
- return;
-
- _handsSystem.TryDrop(component.Owner, hand, checkActionBlocker: false, handsComp: hands);
- _handsSystem.PickupOrDrop(user, held, handsComp: userHands, animate: !stealth);
+ _handsSystem.TryDrop(target, item, checkActionBlocker: false, handsComp: hands);
+ _handsSystem.PickupOrDrop(user, item, handsComp: userHands);
// hand update will trigger strippable update
- _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has stripped the item {ToPrettyString(held):item} from {ToPrettyString(component.Owner):target}");
+ _adminLogger.Add(LogType.Stripping, LogImpact.Medium,
+ $"{ToPrettyString(user):user} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}");
}
}
}
using Content.Server.Administration.Logs;
-using Content.Server.DoAfter;
using Content.Shared.DoAfter;
using Content.Shared.Database;
using Content.Shared.Interaction.Events;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly LinkedEntitySystem _link = default!;
[Dependency] private readonly AudioSystem _audio = default!;
- [Dependency] private readonly DoAfterSystem _doafter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doafter = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<HandTeleporterComponent, UseInHandEvent>(OnUseInHand);
- SubscribeLocalEvent<HandTeleporterComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<HandTeleporterComponent, TeleporterDoAfterEvent>(OnDoAfter);
}
private void OnDoAfter(EntityUid uid, HandTeleporterComponent component, DoAfterEvent args)
if (xform.ParentUid != xform.GridUid)
return;
- var doafterArgs = new DoAfterEventArgs(args.User, component.PortalCreationDelay, used: uid)
+ var doafterArgs = new DoAfterArgs(args.User, component.PortalCreationDelay, new TeleporterDoAfterEvent(), uid, used: uid)
{
BreakOnDamage = true,
- BreakOnStun = true,
BreakOnUserMove = true,
MovementThreshold = 0.5f,
};
- _doafter.DoAfter(doafterArgs);
+ _doafter.TryStartDoAfter(doafterArgs);
}
}
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Buckle.Components;
+using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
SubscribeLocalEvent<ToiletComponent, InteractHandEvent>(OnInteractHand, new []{typeof(BuckleSystem)});
SubscribeLocalEvent<ToiletComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<ToiletComponent, SuicideEvent>(OnSuicide);
- SubscribeLocalEvent<ToiletPryFinished>(OnToiletPried);
- SubscribeLocalEvent<ToiletPryInterrupted>(OnToiletInterrupt);
+ SubscribeLocalEvent<ToiletComponent, ToiletPryDoAfterEvent>(OnToiletPried);
}
private void OnSuicide(EntityUid uid, ToiletComponent component, SuicideEvent args)
return;
// are player trying place or lift of cistern lid?
- if (EntityManager.TryGetComponent(args.Used, out ToolComponent? tool)
- && tool.Qualities.Contains(component.PryingQuality))
+ if (_toolSystem.UseTool(args.Used, args.User, uid, component.PryLidTime, component.PryingQuality, new ToiletPryDoAfterEvent()))
{
- // check if someone is already prying this toilet
- if (component.IsPrying)
- return;
- component.IsPrying = true;
-
- var toolEvData = new ToolEventData(new ToiletPryFinished(uid));
-
- // try to pry toilet cistern
- if (!_toolSystem.UseTool(args.Used, args.User, uid, component.PryLidTime, new [] {component.PryingQuality}, toolEvData))
- {
- component.IsPrying = false;
- return;
- }
-
args.Handled = true;
}
// maybe player trying to hide something inside cistern?
else if (component.LidOpen)
{
- args.Handled = _secretStash.TryHideItem(uid, args.User, args.Used);
+ args.Handled = true;
+ _secretStash.TryHideItem(uid, args.User, args.Used);
}
}
}
}
- private void OnToiletInterrupt(ToiletPryInterrupted ev)
- {
- if (!EntityManager.TryGetComponent(ev.Uid, out ToiletComponent? toilet))
- return;
-
- toilet.IsPrying = false;
- }
-
- private void OnToiletPried(ToiletPryFinished ev)
+ private void OnToiletPried(EntityUid uid, ToiletComponent toilet, ToiletPryDoAfterEvent args)
{
- if (!EntityManager.TryGetComponent(ev.Uid, out ToiletComponent? toilet))
+ if (args.Cancelled)
return;
- toilet.IsPrying = false;
toilet.LidOpen = !toilet.LidOpen;
- UpdateSprite(ev.Uid, toilet);
+ UpdateSprite(uid, toilet);
}
public void ToggleToiletSeat(EntityUid uid, ToiletComponent? component = null)
_appearance.SetData(uid, ToiletVisuals.SeatUp, component.IsSeatUp, appearance);
}
}
-
- public sealed class ToiletPryFinished : EntityEventArgs
- {
- public EntityUid Uid;
-
- public ToiletPryFinished(EntityUid uid)
- {
- Uid = uid;
- }
- }
-
- public sealed class ToiletPryInterrupted : EntityEventArgs
- {
- public EntityUid Uid;
-
- public ToiletPryInterrupted(EntityUid uid)
- {
- Uid = uid;
- }
- }
}
[RegisterComponent]
public sealed class LatticeCuttingComponent : Component
{
- [DataField("toolComponentNeeded")]
- public bool ToolComponentNeeded = true;
-
[DataField("qualityNeeded", customTypeSerializer:typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
public string QualityNeeded = "Cutting";
[DataField("delay")]
public float Delay = 1f;
-
- [DataField("cancelToken")]
- public CancellationTokenSource? CancelToken;
}
}
[ViewVariables(VVAccess.ReadWrite)]
public string? WeldedExamineMessage = "weldable-component-examine-is-welded";
- /// <summary>
- /// Whether something is currently using a welder on this so DoAfter isn't spammed.
- /// </summary>
- [ViewVariables(VVAccess.ReadOnly)]
- public bool BeingWelded;
-
/// <summary>
/// Is this entity currently welded shut?
/// </summary>
- [ViewVariables(VVAccess.ReadOnly)]
+ [DataField("isWelded")]
public bool IsWelded;
}
using Content.Server.Administration.Logs;
using Content.Server.Tools.Components;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
+using Content.Shared.Tools.Systems;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Systems;
base.Initialize();
SubscribeLocalEvent<WeldableComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<WeldableComponent, WeldFinishedEvent>(OnWeldFinished);
- SubscribeLocalEvent<WeldableComponent, WeldCancelledEvent>(OnWeldCanceled);
SubscribeLocalEvent<LayerChangeOnWeldComponent, WeldableChangedEvent>(OnWeldChanged);
SubscribeLocalEvent<WeldableComponent, ExaminedEvent>(OnExamine);
}
return false;
// Basic checks
- if (!component.Weldable || component.BeingWelded)
- return false;
- if (!_toolSystem.HasQuality(tool, component.WeldingQuality))
+ if (!component.Weldable)
return false;
// Other component systems
if (!CanWeld(uid, tool, user, component))
return false;
- var toolEvData = new ToolEventData(new WeldFinishedEvent(user, tool), cancelledEv: new WeldCancelledEvent(),targetEntity: uid);
- component.BeingWelded = _toolSystem.UseTool(tool, user, uid, component.WeldingTime.Seconds, new[] { component.WeldingQuality }, toolEvData, fuel: component.FuelConsumption);
+ if (!_toolSystem.UseTool(tool, user, uid, component.WeldingTime.Seconds, component.WeldingQuality, new WeldFinishedEvent(), fuel: component.FuelConsumption))
+ return false;
// Log attempt
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):user} is {(component.IsWelded ? "un" : "")}welding {ToPrettyString(uid):target} at {Transform(uid).Coordinates:targetlocation}");
private void OnWeldFinished(EntityUid uid, WeldableComponent component, WeldFinishedEvent args)
{
- component.BeingWelded = false;
+ if (args.Cancelled || args.Used == null)
+ return;
// Check if target is still valid
- if (!CanWeld(uid, args.Tool, args.User, component))
+ if (!CanWeld(uid, args.Used.Value, args.User, component))
return;
component.IsWelded = !component.IsWelded;
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} {(!component.IsWelded ? "un" : "")}welded {ToPrettyString(uid):target}");
}
- private void OnWeldCanceled(EntityUid uid, WeldableComponent component, WeldCancelledEvent args)
- {
- component.BeingWelded = false;
- }
-
private void OnWeldChanged(EntityUid uid, LayerChangeOnWeldComponent component, WeldableChangedEvent args)
{
if (!TryComp<FixturesComponent>(uid, out var fixtures))
return;
component.WeldingTime = time;
}
-
- /// <summary>
- /// Raised after welding do_after has finished. It doesn't guarantee success,
- /// use <see cref="WeldableChangedEvent"/> to get updated status.
- /// </summary>
- private sealed class WeldFinishedEvent : EntityEventArgs
- {
- public readonly EntityUid User;
- public readonly EntityUid Tool;
-
- public WeldFinishedEvent(EntityUid user, EntityUid tool)
- {
- User = user;
- Tool = tool;
- }
- }
-
- /// <summary>
- /// Raised when entity welding has failed.
- /// </summary>
- private sealed class WeldCancelledEvent : EntityEventArgs
- {
-
- }
}
/// <summary>
using Content.Server.Maps;
using Content.Server.Tools.Components;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Maps;
using Content.Shared.Tools.Components;
private void OnLatticeCutComplete(EntityUid uid, LatticeCuttingComponent component, LatticeCuttingCompleteEvent args)
{
+ if (args.Cancelled)
+ return;
+
var gridUid = args.Coordinates.GetGridUid(EntityManager);
if (gridUid == null)
return;
private bool TryCut(EntityUid toolEntity, EntityUid user, LatticeCuttingComponent component, EntityCoordinates clickLocation)
{
- ToolComponent? tool = null;
- if (component.ToolComponentNeeded && !TryComp<ToolComponent?>(toolEntity, out tool))
- return false;
-
if (!_mapManager.TryGetGrid(clickLocation.GetGridUid(EntityManager), out var mapGrid))
return false;
|| tile.IsBlockedTurf(true))
return false;
- var toolEvData = new ToolEventData(new LatticeCuttingCompleteEvent(clickLocation, user), targetEntity: toolEntity);
-
- if (!UseTool(toolEntity, user, null, component.Delay, new[] {component.QualityNeeded}, toolEvData, toolComponent: tool))
- return false;
-
- return true;
- }
-
- private sealed class LatticeCuttingCompleteEvent : EntityEventArgs
- {
- public EntityCoordinates Coordinates;
- public EntityUid User;
-
- public LatticeCuttingCompleteEvent(EntityCoordinates coordinates, EntityUid user)
- {
- Coordinates = coordinates;
- User = user;
- }
+ var ev = new LatticeCuttingCompleteEvent(clickLocation);
+ return UseTool(toolEntity, user, toolEntity, component.Delay, component.QualityNeeded, ev);
}
}
using System.Threading;
using Content.Server.Fluids.Components;
using Content.Server.Tools.Components;
+using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Maps;
using Content.Shared.Tools.Components;
private void InitializeTilePrying()
{
SubscribeLocalEvent<TilePryingComponent, AfterInteractEvent>(OnTilePryingAfterInteract);
- SubscribeLocalEvent<TilePryingComponent, TilePryingCompleteEvent>(OnTilePryComplete);
- SubscribeLocalEvent<TilePryingComponent, TilePryingCancelledEvent>(OnTilePryCancelled);
+ SubscribeLocalEvent<TilePryingComponent, TilePryingDoAfterEvent>(OnTilePryComplete);
}
private void OnTilePryingAfterInteract(EntityUid uid, TilePryingComponent component, AfterInteractEvent args)
args.Handled = true;
}
- private void OnTilePryComplete(EntityUid uid, TilePryingComponent component, TilePryingCompleteEvent args)
+ private void OnTilePryComplete(EntityUid uid, TilePryingComponent component, TilePryingDoAfterEvent args)
{
- component.CancelToken = null;
+ if (args.Cancelled)
+ return;
+
var gridUid = args.Coordinates.GetGridUid(EntityManager);
if (!_mapManager.TryGetGrid(gridUid, out var grid))
{
_tile.PryTile(tile);
}
- private void OnTilePryCancelled(EntityUid uid, TilePryingComponent component, TilePryingCancelledEvent args)
- {
- component.CancelToken = null;
- }
-
private bool TryPryTile(EntityUid toolEntity, EntityUid user, TilePryingComponent component, EntityCoordinates clickLocation)
{
- if (!TryComp<ToolComponent?>(toolEntity, out var tool) && component.ToolComponentNeeded || component.CancelToken != null)
+ 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;
- component.CancelToken = new CancellationTokenSource();
-
- var toolEvData = new ToolEventData(new TilePryingCompleteEvent(clickLocation), cancelledEv:new TilePryingCancelledEvent() ,targetEntity:toolEntity);
-
- if (!UseTool(toolEntity, user, null, component.Delay, new[] { component.QualityNeeded }, toolEvData, toolComponent: tool, cancelToken: component.CancelToken))
- return false;
-
- return true;
- }
-
- private sealed class TilePryingCompleteEvent : EntityEventArgs
- {
- public readonly EntityCoordinates Coordinates;
-
- public TilePryingCompleteEvent(EntityCoordinates coordinates)
- {
- Coordinates = coordinates;
- }
- }
-
- private sealed class TilePryingCancelledEvent : EntityEventArgs
- {
+ var ev = new TilePryingDoAfterEvent(clickLocation);
+ return UseTool(toolEntity, user, toolEntity, component.Delay, component.QualityNeeded, ev, toolComponent: tool);
}
}
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Tools.Components;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
+using Robust.Shared.Utility;
namespace Content.Server.Tools
{
SubscribeLocalEvent<WelderComponent, SolutionChangedEvent>(OnWelderSolutionChange);
SubscribeLocalEvent<WelderComponent, ActivateInWorldEvent>(OnWelderActivate);
SubscribeLocalEvent<WelderComponent, AfterInteractEvent>(OnWelderAfterInteract);
- SubscribeLocalEvent<WelderComponent, ToolUseAttemptEvent>(OnWelderToolUseAttempt);
- SubscribeLocalEvent<WelderComponent, ToolUseFinishAttemptEvent>(OnWelderToolUseFinishAttempt);
+ SubscribeLocalEvent<WelderComponent, DoAfterAttemptEvent<ToolDoAfterEvent>>(OnWelderToolUseAttempt);
+ SubscribeLocalEvent<WelderComponent, ToolDoAfterEvent>(OnWelderDoAfter);
SubscribeLocalEvent<WelderComponent, ComponentShutdown>(OnWelderShutdown);
SubscribeLocalEvent<WelderComponent, ComponentGetState>(OnWelderGetState);
SubscribeLocalEvent<WelderComponent, MeleeHitEvent>(OnMeleeHit);
args.Handled = true;
}
- private void OnWelderToolUseAttempt(EntityUid uid, WelderComponent welder, ToolUseAttemptEvent args)
+ private void OnWelderToolUseAttempt(EntityUid uid, WelderComponent welder, DoAfterAttemptEvent<ToolDoAfterEvent> args)
{
- if (args.Cancelled)
- return;
+ DebugTools.Assert(args.Event.Fuel > 0);
+ var user = args.DoAfter.Args.User;
if (!welder.Lit)
{
- _popupSystem.PopupEntity(Loc.GetString("welder-component-welder-not-lit-message"), uid, args.User);
+ _popupSystem.PopupEntity(Loc.GetString("welder-component-welder-not-lit-message"), uid, user);
args.Cancel();
return;
}
var (fuel, _) = GetWelderFuelAndCapacity(uid, welder);
- if (FixedPoint2.New(args.Fuel) > fuel)
+ if (FixedPoint2.New(args.Event.Fuel) > fuel)
{
- _popupSystem.PopupEntity(Loc.GetString("welder-component-cannot-weld-message"), uid, args.User);
+ _popupSystem.PopupEntity(Loc.GetString("welder-component-cannot-weld-message"), uid, user);
args.Cancel();
}
}
- private void OnWelderToolUseFinishAttempt(EntityUid uid, WelderComponent welder, ToolUseFinishAttemptEvent args)
+ private void OnWelderDoAfter(EntityUid uid, WelderComponent welder, ToolDoAfterEvent args)
{
if (args.Cancelled)
return;
- if (!welder.Lit)
- {
- _popupSystem.PopupEntity(Loc.GetString("welder-component-welder-not-lit-message"), uid, args.User);
- args.Cancel();
- return;
- }
-
- var (fuel, _) = GetWelderFuelAndCapacity(uid, welder);
-
- var neededFuel = FixedPoint2.New(args.Fuel);
-
- if (neededFuel > fuel)
- {
- _popupSystem.PopupEntity(Loc.GetString("welder-component-cannot-weld-message"), uid, args.User);
- args.Cancel();
- }
-
if (!_solutionContainerSystem.TryGetSolution(uid, welder.FuelSolution, out var solution))
- {
- args.Cancel();
return;
- }
- solution.RemoveReagent(welder.FuelReagent, neededFuel);
+ solution.RemoveReagent(welder.FuelReagent, FixedPoint2.New(args.Fuel));
_entityManager.Dirty(welder);
}
using System.Linq;
using Content.Server.Cargo.Systems;
-using Content.Server.DoAfter;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Popups;
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly AudioSystem _audioSystem = default!;
[Dependency] private readonly PricingSystem _pricingSystem = default!;
private void OnAfterInteract(EntityUid uid, VendingMachineRestockComponent component, AfterInteractEvent args)
{
- if (args.Target == null || !args.CanReach)
+ if (args.Target == null || !args.CanReach || args.Handled)
return;
if (!TryComp<VendingMachineComponent>(args.Target, out var machineComponent))
if (!TryAccessMachine(uid, component, machineComponent, args.User, args.Target.Value))
return;
- _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, (float) component.RestockDelay.TotalSeconds, target:args.Target, used:uid)
+ args.Handled = true;
+
+ var doAfterArgs = new DoAfterArgs(args.User, (float) component.RestockDelay.TotalSeconds, new RestockDoAfterEvent(), args.Target,
+ target: args.Target, used: uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
- BreakOnStun = true,
BreakOnDamage = true,
NeedHand = true
- });
+ };
+
+ if (!_doAfterSystem.TryStartDoAfter(doAfterArgs))
+ return;
_popupSystem.PopupEntity(Loc.GetString("vending-machine-restock-start", ("this", uid), ("user", args.User), ("target", args.Target)),
args.User,
SubscribeLocalEvent<VendingMachineComponent, VendingMachineSelfDispenseEvent>(OnSelfDispense);
- SubscribeLocalEvent<VendingMachineComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<VendingMachineComponent, RestockDoAfterEvent>(OnDoAfter);
}
private void OnVendingPrice(EntityUid uid, VendingMachineComponent component, ref PriceCalculationEvent args)
-using Content.Server.DoAfter;
using Content.Server.Hands.Components;
using Content.Server.Hands.Systems;
using Content.Server.Wieldable.Components;
using Content.Server.Actions.Events;
using Content.Shared.DoAfter;
using Content.Shared.Weapons.Melee.Events;
+using Content.Shared.Wieldable;
namespace Content.Server.Wieldable
{
public sealed class WieldableSystem : EntitySystem
{
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly HandVirtualItemSystem _virtualItemSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedItemSystem _itemSystem = default!;
base.Initialize();
SubscribeLocalEvent<WieldableComponent, UseInHandEvent>(OnUseInHand);
- SubscribeLocalEvent<WieldableComponent, DoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<WieldableComponent, WieldableDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<WieldableComponent, ItemUnwieldedEvent>(OnItemUnwielded);
SubscribeLocalEvent<WieldableComponent, GotUnequippedHandEvent>(OnItemLeaveHand);
SubscribeLocalEvent<WieldableComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
if (ev.Cancelled)
return;
- var doargs = new DoAfterEventArgs(user, component.WieldTime, used:used)
+ var doargs = new DoAfterArgs(user, component.WieldTime, new WieldableDoAfterEvent(), used, used: used)
{
BreakOnUserMove = false,
- BreakOnDamage = true,
- BreakOnStun = true,
- BreakOnTargetMove = true
+ BreakOnDamage = true
};
- _doAfter.DoAfter(doargs);
+ _doAfter.TryStartDoAfter(doargs);
}
/// <summary>
using System.Linq;
using System.Threading;
using Content.Server.Administration.Logs;
-using Content.Server.DoAfter;
using Content.Server.Hands.Components;
using Content.Server.Power.Components;
using Content.Shared.DoAfter;
{
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = 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!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
-
- private IRobustRandom _random = new RobustRandom();
+ [Dependency] private readonly IRobustRandom _random = default!;
// This is where all the wire layouts are stored.
[ViewVariables] private readonly Dictionary<string, WireLayout> _layouts = new();
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
// this is a broadcast event
- SubscribeLocalEvent<WireToolFinishedEvent>(OnToolFinished);
- SubscribeLocalEvent<WireToolCanceledEvent>(OnToolCanceled);
+ SubscribeLocalEvent<WiresPanelComponent, WirePanelDoAfterEvent>(OnPanelDoAfter);
SubscribeLocalEvent<WiresComponent, ComponentStartup>(OnWiresStartup);
SubscribeLocalEvent<WiresComponent, WiresActionMessage>(OnWiresActionMessage);
SubscribeLocalEvent<WiresComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<WiresComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<WiresComponent, TimedWireEvent>(OnTimedWire);
SubscribeLocalEvent<WiresComponent, PowerChangedEvent>(OnWiresPowered);
- SubscribeLocalEvent<WiresComponent, DoAfterEvent<WireExtraData>>(OnDoAfter);
+ SubscribeLocalEvent<WiresComponent, WireDoAfterEvent>(OnDoAfter);
}
private void SetOrCreateWireLayout(EntityUid uid, WiresComponent? wires = null)
TryDoWireAction(uid, player, activeHandEntity, args.Id, args.Action, component, tool);
}
- private void OnDoAfter(EntityUid uid, WiresComponent component, DoAfterEvent<WireExtraData> args)
+ private void OnDoAfter(EntityUid uid, WiresComponent component, WireDoAfterEvent args)
{
if (args.Cancelled)
{
- component.WiresQueue.Remove(args.AdditionalData.Id);
+ component.WiresQueue.Remove(args.Id);
return;
}
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);
+ UpdateWires(args.Args.Target.Value, args.Args.User, args.Args.Used.Value, args.Id, args.Action, component);
args.Handled = true;
}
private void OnInteractUsing(EntityUid uid, WiresComponent component, InteractUsingEvent args)
{
+ if (args.Handled)
+ return;
+
if (!TryComp<ToolComponent>(args.Used, out var tool) || !TryComp<WiresPanelComponent>(uid, out var panel))
return;
+
if (panel.Open &&
(_toolSystem.HasQuality(args.Used, "Cutting", tool) ||
_toolSystem.HasQuality(args.Used, "Pulsing", tool)))
{
- if (EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
- {
- var ui = _uiSystem.GetUiOrNull(uid, WiresUiKey.Key);
- if (ui != null)
- _uiSystem.OpenUi(ui, actor.PlayerSession);
- args.Handled = true;
- }
+ if (TryComp(args.User, out ActorComponent? actor))
+ _uiSystem.TryOpen(uid, WiresUiKey.Key, actor.PlayerSession);
}
- else if (!panel.IsScrewing && _toolSystem.HasQuality(args.Used, "Screwing", tool))
+ else if (_toolSystem.UseTool(args.Used, args.User, uid, ScrewTime, "Screwing", new WirePanelDoAfterEvent(), toolComponent: tool))
{
- var toolEvData = new ToolEventData(new WireToolFinishedEvent(uid, args.User), cancelledEv: new WireToolCanceledEvent(uid));
-
- panel.IsScrewing = _toolSystem.UseTool(args.Used, args.User, uid, ScrewTime, new[] { "Screwing" }, toolEvData, toolComponent: tool);
- args.Handled = panel.IsScrewing;
-
- // Log attempt
- _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} is screwing {ToPrettyString(uid):target}'s {(panel.Open ? "open" : "closed")} maintenance panel at {Transform(uid).Coordinates:targetlocation}");
+ _adminLogger.Add(LogType.Action, LogImpact.Low,
+ $"{ToPrettyString(args.User):user} is screwing {ToPrettyString(uid):target}'s {(panel.Open ? "open" : "closed")} maintenance panel at {Transform(uid).Coordinates:targetlocation}");
}
}
- private void OnToolFinished(WireToolFinishedEvent args)
+ private void OnPanelDoAfter(EntityUid uid, WiresPanelComponent panel, WirePanelDoAfterEvent args)
{
- if (!TryComp<WiresPanelComponent>((args.Target), out var panel))
+ if (args.Cancelled)
return;
- panel.IsScrewing = false;
- TogglePanel(args.Target, panel, !panel.Open);
-
- // Log success
- _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} screwed {ToPrettyString(args.Target):target}'s maintenance panel {(panel.Open ? "open" : "closed")}");
+ TogglePanel(uid, panel, !panel.Open);
+ UpdateAppearance(uid, panel);
+ _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} screwed {ToPrettyString(uid):target}'s maintenance panel {(panel.Open ? "open" : "closed")}");
if (panel.Open)
{
- _audio.PlayPvs(panel.ScrewdriverOpenSound, args.Target);
+ _audio.PlayPvs(panel.ScrewdriverOpenSound, uid);
}
else
{
- _audio.PlayPvs(panel.ScrewdriverCloseSound, args.Target);
- var ui = _uiSystem.GetUiOrNull(args.Target, WiresUiKey.Key);
- if (ui != null)
- {
- _uiSystem.CloseAll(ui);
- }
+ _audio.PlayPvs(panel.ScrewdriverCloseSound, uid);
+ _uiSystem.TryCloseAll(uid, WiresUiKey.Key);
}
-
- Dirty(panel);
- }
-
- private void OnToolCanceled(WireToolCanceledEvent ev)
- {
- if (!TryComp<WiresPanelComponent>(ev.Target, out var component))
- return;
-
- component.IsScrewing = false;
}
private void OnMapInit(EntityUid uid, WiresComponent component, MapInitEvent args)
_appearance.SetData(uid, WiresVisuals.MaintenancePanelState, panel.Open && panel.Visible, appearance);
}
- private void TryDoWireAction(EntityUid used, EntityUid user, EntityUid toolEntity, int id, WiresAction action, WiresComponent? wires = null, ToolComponent? tool = null)
+ private void TryDoWireAction(EntityUid target, EntityUid user, EntityUid toolEntity, int id, WiresAction action, WiresComponent? wires = null, ToolComponent? tool = null)
{
- if (!Resolve(used, ref wires)
+ if (!Resolve(target, ref wires)
|| !Resolve(toolEntity, ref tool))
return;
if (wires.WiresQueue.Contains(id))
return;
- var wire = TryGetWire(used, id, wires);
+ var wire = TryGetWire(target, id, wires);
if (wire == null)
return;
if (_toolTime > 0f)
{
- var data = new WireExtraData(action, id);
- var args = new DoAfterEventArgs(user, _toolTime, target: used, used: toolEntity)
+ var args = new DoAfterArgs(user, _toolTime, new WireDoAfterEvent(action, id), target, target: target, used: toolEntity)
{
NeedHand = true,
- BreakOnStun = true,
BreakOnDamage = true,
BreakOnUserMove = true
};
- _doAfter.DoAfter(args, data);
+ _doAfter.TryStartDoAfter(args);
}
else
{
- UpdateWires(used, user, toolEntity, id, action, wires);
+ UpdateWires(target, user, toolEntity, id, action, wires);
}
}
- 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)
{
if (!Resolve(used, ref wires))
break;
}
- _toolSystem.PlayToolSound(toolEntity, tool);
+ _toolSystem.PlayToolSound(toolEntity, tool, user);
if (wire.Action == null || wire.Action.Cut(user, wire))
{
wire.IsCut = true;
break;
}
- _toolSystem.PlayToolSound(toolEntity, tool);
+ _toolSystem.PlayToolSound(toolEntity, tool, user);
if (wire.Action == null || wire.Action.Mend(user, wire))
{
wire.IsCut = false;
private void Reset(RoundRestartCleanupEvent args)
{
_layouts.Clear();
- _random = new RobustRandom();
}
#endregion
-
- #region Events
- private sealed class WireToolFinishedEvent : EntityEventArgs
- {
- public EntityUid User { get; }
- public EntityUid Target { get; }
-
- public WireToolFinishedEvent(EntityUid target, EntityUid user)
- {
- Target = target;
- User = user;
- }
- }
-
- public record struct WireToolCanceledEvent(EntityUid Target);
- #endregion
}
public sealed class Wire
if (ev.Cancelled)
return false;
- if (target == null)
+ if (target == null || target == user)
return true;
var targetEv = new GettingInteractedWithAttemptEvent(user, target);
+using Content.Shared.DoAfter;
using Robust.Shared.Serialization;
namespace Content.Shared.AirlockPainter
SelectedStyle = selectedStyle;
}
}
+
+ [Serializable, NetSerializable]
+ public sealed class AirlockPainterDoAfterEvent : DoAfterEvent
+ {
+ [DataField("sprite", required: true)]
+ public readonly string Sprite = default!;
+
+ private AirlockPainterDoAfterEvent()
+ {
+ }
+
+ public AirlockPainterDoAfterEvent(string sprite)
+ {
+ Sprite = sprite;
+ }
+
+ public override DoAfterEvent Clone() => this;
+ }
}
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Anomaly;
+
+[Serializable, NetSerializable]
+public sealed class ScannerDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
+using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Chemistry.Components
{
+ [Serializable, NetSerializable]
+ public sealed class InjectorDoAfterEvent : SimpleDoAfterEvent
+ {
+ }
+
/// <summary>
/// Shared class for injectors & syringes
/// </summary>
[NetworkedComponent, ComponentProtoName("Injector")]
public abstract class SharedInjectorComponent : Component
{
- /// <summary>
- /// Checks to see if the entity being injected
- /// </summary>
- [DataField("isInjecting")]
- public bool IsInjecting;
-
/// <summary>
/// Component data used for net updates. Used by client for item status ui
/// </summary>
public FixedPoint2 TotalVolume { get; }
public InjectorToggleMode CurrentMode { get; }
- public InjectorComponentState(FixedPoint2 currentVolume, FixedPoint2 totalVolume, InjectorToggleMode currentMode)
+ public InjectorComponentState(FixedPoint2 currentVolume, FixedPoint2 totalVolume,
+ InjectorToggleMode currentMode)
{
CurrentVolume = currentVolume;
TotalVolume = totalVolume;
+using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Movement.Events;
+using Robust.Shared.Serialization;
namespace Content.Shared.Climbing;
{
args.CanDrop = HasComp<ClimbingComponent>(args.Dragged);
}
+
+ [Serializable, NetSerializable]
+ protected sealed class ClimbDoAfterEvent : SimpleDoAfterEvent
+ {
+ }
}
/// </summary>
[DataField("verbText")]
public string? VerbText;
-
- // prevent duplicate doafters
- public byte? DoAfterId;
}
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Clothing.EntitySystems;
SubscribeLocalEvent<ToggleableClothingComponent, InventoryRelayedEvent<GetVerbsEvent<EquipmentVerb>>>(GetRelayedVerbs);
SubscribeLocalEvent<ToggleableClothingComponent, GetVerbsEvent<EquipmentVerb>>(OnGetVerbs);
SubscribeLocalEvent<AttachedClothingComponent, GetVerbsEvent<EquipmentVerb>>(OnGetAttachedStripVerbsEvent);
- SubscribeLocalEvent<ToggleableClothingComponent, DoAfterEvent<ToggleClothingEvent>>(OnDoAfterComplete);
+ SubscribeLocalEvent<ToggleableClothingComponent, ToggleClothingDoAfterEvent>(OnDoAfterComplete);
}
private void GetRelayedVerbs(EntityUid uid, ToggleableClothingComponent component, InventoryRelayedEvent<GetVerbsEvent<EquipmentVerb>> args)
private void StartDoAfter(EntityUid user, EntityUid item, EntityUid wearer, ToggleableClothingComponent component)
{
- // TODO predict do afters & networked clothing toggle.
- if (_net.IsClient)
- return;
-
- if (component.DoAfterId != null || component.StripDelay == null)
+ if (component.StripDelay == null)
return;
var (time, stealth) = _strippable.GetStripTimeModifiers(user, wearer, (float) component.StripDelay.Value.TotalSeconds);
- if (!stealth)
- {
- var popup = Loc.GetString("strippable-component-alert-owner-interact", ("user", Identity.Entity(user, EntityManager)), ("item", item));
- _popupSystem.PopupEntity(popup, wearer, wearer, PopupType.Large);
- }
-
- var args = new DoAfterEventArgs(user, time, default, wearer, item)
+ var args = new DoAfterArgs(user, time, new ToggleClothingDoAfterEvent(), item, wearer, item)
{
BreakOnDamage = true,
- BreakOnStun = true,
BreakOnTargetMove = true,
- RaiseOnTarget = false,
- RaiseOnUsed = true,
- RaiseOnUser = false,
// This should just re-use the BUI range checks & cancel the do after if the BUI closes. But that is all
// server-side at the moment.
// TODO BUI REFACTOR.
DistanceThreshold = 2,
};
- var doAfter = _doAfter.DoAfter(args, new ToggleClothingEvent() { Performer = user });
- component.DoAfterId = doAfter.ID;
+ if (!_doAfter.TryStartDoAfter(args))
+ return;
+
+ if (!stealth)
+ {
+ var popup = Loc.GetString("strippable-component-alert-owner-interact", ("user", Identity.Entity(user, EntityManager)), ("item", item));
+ _popupSystem.PopupEntity(popup, wearer, wearer, PopupType.Large);
+ }
}
private void OnGetAttachedStripVerbsEvent(EntityUid uid, AttachedClothingComponent component, GetVerbsEvent<EquipmentVerb> args)
OnGetVerbs(component.AttachedUid, Comp<ToggleableClothingComponent>(component.AttachedUid), args);
}
- private void OnDoAfterComplete(EntityUid uid, ToggleableClothingComponent component, DoAfterEvent<ToggleClothingEvent> args)
+ private void OnDoAfterComplete(EntityUid uid, ToggleableClothingComponent component, ToggleClothingDoAfterEvent args)
{
- DebugTools.Assert(component.DoAfterId == args.Id);
- component.DoAfterId = null;
-
if (args.Cancelled)
return;
- OnToggleClothing(uid, component, args.AdditionalData);
+ ToggleClothing(args.User, uid, component);
}
public override void Update(float frameTime)
/// </summary>
private void OnToggleClothing(EntityUid uid, ToggleableClothingComponent component, ToggleClothingEvent args)
{
- if (args.Handled || component.Container == null || component.ClothingUid == null)
+ if (args.Handled)
+ return;
+
+ args.Handled = true;
+ ToggleClothing(args.Performer, uid, component);
+ }
+
+ private void ToggleClothing(EntityUid user, EntityUid target, ToggleableClothingComponent component)
+ {
+ if (component.Container == null || component.ClothingUid == null)
return;
- var parent = Transform(uid).ParentUid;
+ var parent = Transform(target).ParentUid;
if (component.Container.ContainedEntity == null)
_inventorySystem.TryUnequip(parent, component.Slot);
else if (_inventorySystem.TryGetSlotEntity(parent, component.Slot, out var existing))
{
_popupSystem.PopupEntity(Loc.GetString("toggleable-clothing-remove-first", ("entity", existing)),
- args.Performer, args.Performer);
+ user, user);
}
else
_inventorySystem.TryEquip(parent, component.ClothingUid.Value, component.Slot);
-
- args.Handled = true;
}
private void OnGetActions(EntityUid uid, ToggleableClothingComponent component, GetItemActionsEvent args)
}
}
-public sealed class ToggleClothingEvent : InstantActionEvent { }
+public sealed class ToggleClothingEvent : InstantActionEvent
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class ToggleClothingDoAfterEvent : SimpleDoAfterEvent
+{
+}
using Content.Shared.Construction.Components;
using Content.Shared.Containers.ItemSlots;
+using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Pulling.Components;
using Content.Shared.Tools.Components;
+using Robust.Shared.Serialization;
namespace Content.Shared.Construction.EntitySystems;
ToolComponent? usingTool = null)
{
// Thanks tool system.
+
+ // TODO tool system is fixed now, make this actually shared.
+ }
+
+ [Serializable, NetSerializable]
+ protected sealed class TryUnanchorCompletedEvent : SimpleDoAfterEvent
+ {
+ }
+
+ [Serializable, NetSerializable]
+ protected sealed class TryAnchorCompletedEvent : SimpleDoAfterEvent
+ {
}
}
--- /dev/null
+using Content.Shared.DoAfter;
+using Content.Shared.Interaction;
+using Robust.Shared.Map;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Construction;
+
+/// <summary>
+/// Sent client -> server to to tell the server that we started building
+/// a structure-construction.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class TryStartStructureConstructionMessage : EntityEventArgs
+{
+ /// <summary>
+ /// Position to start building.
+ /// </summary>
+ public readonly EntityCoordinates Location;
+
+ /// <summary>
+ /// The construction prototype to start building.
+ /// </summary>
+ public readonly string PrototypeName;
+
+ public readonly Angle Angle;
+
+ /// <summary>
+ /// Identifier to be sent back in the acknowledgement so that the client can clean up its ghost.
+ /// </summary>
+ public readonly int Ack;
+
+ public TryStartStructureConstructionMessage(EntityCoordinates loc, string prototypeName, Angle angle, int ack)
+ {
+ Location = loc;
+ PrototypeName = prototypeName;
+ Angle = angle;
+ Ack = ack;
+ }
+}
+
+/// <summary>
+/// Sent client -> server to to tell the server that we started building
+/// an item-construction.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class TryStartItemConstructionMessage : EntityEventArgs
+{
+ /// <summary>
+ /// The construction prototype to start building.
+ /// </summary>
+ public readonly string PrototypeName;
+
+ public TryStartItemConstructionMessage(string prototypeName)
+ {
+ PrototypeName = prototypeName;
+ }
+}
+
+/// <summary>
+/// Sent server -> client to tell the client that a ghost has started to be constructed.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class AckStructureConstructionMessage : EntityEventArgs
+{
+ public readonly int GhostId;
+
+ public AckStructureConstructionMessage(int ghostId)
+ {
+ GhostId = ghostId;
+ }
+}
+
+/// <summary>
+/// Sent client -> server to request a specific construction guide.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class RequestConstructionGuide : EntityEventArgs
+{
+ public readonly string ConstructionId;
+
+ public RequestConstructionGuide(string constructionId)
+ {
+ ConstructionId = constructionId;
+ }
+}
+
+/// <summary>
+/// Sent server -> client as a response to a <see cref="RequestConstructionGuide"/> net message.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class ResponseConstructionGuide : EntityEventArgs
+{
+ public readonly string ConstructionId;
+ public readonly ConstructionGuide Guide;
+
+ public ResponseConstructionGuide(string constructionId, ConstructionGuide guide)
+ {
+ ConstructionId = constructionId;
+ Guide = guide;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed class ConstructionInteractDoAfterEvent : DoAfterEvent
+{
+ [DataField("clickLocation")]
+ public readonly EntityCoordinates ClickLocation;
+
+ private ConstructionInteractDoAfterEvent()
+ {
+ }
+
+ public ConstructionInteractDoAfterEvent(InteractUsingEvent ev)
+ {
+ ClickLocation = ev.ClickLocation;
+ }
+
+ public override DoAfterEvent Clone() => this;
+}
+
+[Serializable, NetSerializable]
+public sealed class WelderRefineDoAfterEvent : SimpleDoAfterEvent
+{
+}
using System.Linq;
using Robust.Shared.Map;
-using Robust.Shared.Serialization;
using static Content.Shared.Interaction.SharedInteractionSystem;
namespace Content.Shared.Construction
var ignored = grid.GetAnchoredEntities(coords).ToHashSet();
return e => ignored.Contains(e);
}
-
- /// <summary>
- /// Sent client -> server to to tell the server that we started building
- /// a structure-construction.
- /// </summary>
- [Serializable, NetSerializable]
- public sealed class TryStartStructureConstructionMessage : EntityEventArgs
- {
- /// <summary>
- /// Position to start building.
- /// </summary>
- public readonly EntityCoordinates Location;
-
- /// <summary>
- /// The construction prototype to start building.
- /// </summary>
- public readonly string PrototypeName;
-
- public readonly Angle Angle;
-
- /// <summary>
- /// Identifier to be sent back in the acknowledgement so that the client can clean up its ghost.
- /// </summary>
- public readonly int Ack;
-
- public TryStartStructureConstructionMessage(EntityCoordinates loc, string prototypeName, Angle angle, int ack)
- {
- Location = loc;
- PrototypeName = prototypeName;
- Angle = angle;
- Ack = ack;
- }
- }
-
- /// <summary>
- /// Sent client -> server to to tell the server that we started building
- /// an item-construction.
- /// </summary>
- [Serializable, NetSerializable]
- public sealed class TryStartItemConstructionMessage : EntityEventArgs
- {
- /// <summary>
- /// The construction prototype to start building.
- /// </summary>
- public readonly string PrototypeName;
-
- public TryStartItemConstructionMessage(string prototypeName)
- {
- PrototypeName = prototypeName;
- }
- }
-
- /// <summary>
- /// Sent server -> client to tell the client that a ghost has started to be constructed.
- /// </summary>
- [Serializable, NetSerializable]
- public sealed class AckStructureConstructionMessage : EntityEventArgs
- {
- public readonly int GhostId;
-
- public AckStructureConstructionMessage(int ghostId)
- {
- GhostId = ghostId;
- }
- }
-
- /// <summary>
- /// Sent client -> server to request a specific construction guide.
- /// </summary>
- [Serializable, NetSerializable]
- public sealed class RequestConstructionGuide : EntityEventArgs
- {
- public readonly string ConstructionId;
-
- public RequestConstructionGuide(string constructionId)
- {
- ConstructionId = constructionId;
- }
- }
-
- /// <summary>
- /// Sent server -> client as a response to a <see cref="RequestConstructionGuide"/> net message.
- /// </summary>
- [Serializable, NetSerializable]
- public sealed class ResponseConstructionGuide : EntityEventArgs
- {
- public readonly string ConstructionId;
- public readonly ConstructionGuide Guide;
-
- public ResponseConstructionGuide(string constructionId, ConstructionGuide guide)
- {
- ConstructionId = constructionId;
- Guide = guide;
- }
- }
}
}
/// </summary>
[DataField("canStillInteract"), ViewVariables(VVAccess.ReadWrite)]
public bool CanStillInteract = true;
-
- /// <summary>
- /// Whether or not the entity is currently in the process of being uncuffed.
- /// </summary>
- [DataField("uncuffing"), ViewVariables(VVAccess.ReadWrite)]
- public bool Uncuffing;
}
[Serializable, NetSerializable]
public sealed class CuffableComponentState : ComponentState
{
public readonly bool CanStillInteract;
- public readonly bool Uncuffing;
public readonly int NumHandsCuffed;
public readonly string? RSI;
public readonly string? IconState;
public readonly Color? Color;
- public CuffableComponentState(int numHandsCuffed, bool canStillInteract, bool uncuffing, string? rsiPath, string? iconState, Color? color)
+ public CuffableComponentState(int numHandsCuffed, bool canStillInteract, string? rsiPath, string? iconState, Color? color)
{
NumHandsCuffed = numHandsCuffed;
CanStillInteract = canStillInteract;
- Uncuffing = uncuffing;
RSI = rsiPath;
IconState = iconState;
Color = color;
[DataField("endUncuffSound"), ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier EndUncuffSound = new SoundPathSpecifier("/Audio/Items/Handcuffs/cuff_takeoff_end.ogg");
-
- /// <summary>
- /// Used to prevent DoAfter getting spammed.
- /// </summary>
- [DataField("cuffing"), ViewVariables(VVAccess.ReadWrite)]
- public bool Cuffing;
}
[Serializable, NetSerializable]
public sealed class HandcuffComponentState : ComponentState
{
public readonly string? IconState;
- public readonly bool Cuffing;
- public HandcuffComponentState(string? iconState, bool cuffing)
+ public HandcuffComponentState(string? iconState)
{
IconState = iconState;
- Cuffing = cuffing;
}
}
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Player;
+using Robust.Shared.Serialization;
namespace Content.Shared.Cuffs
{
+ // TODO remove all the IsServer() checks.
public abstract class SharedCuffableSystem : EntitySystem
{
[Dependency] private readonly IComponentFactory _componentFactory = default!;
SubscribeLocalEvent<CuffableComponent, IsUnequippingAttemptEvent>(OnUnequipAttempt);
SubscribeLocalEvent<CuffableComponent, BeingPulledAttemptEvent>(OnBeingPulledAttempt);
SubscribeLocalEvent<CuffableComponent, GetVerbsEvent<Verb>>(AddUncuffVerb);
- SubscribeLocalEvent<CuffableComponent, DoAfterEvent<UnCuffDoAfter>>(OnCuffableDoAfter);
+ SubscribeLocalEvent<CuffableComponent, UnCuffDoAfterEvent>(OnCuffableDoAfter);
SubscribeLocalEvent<CuffableComponent, PullStartedMessage>(OnPull);
SubscribeLocalEvent<CuffableComponent, PullStoppedMessage>(OnPull);
SubscribeLocalEvent<CuffableComponent, DropAttemptEvent>(CheckAct);
SubscribeLocalEvent<HandcuffComponent, AfterInteractEvent>(OnCuffAfterInteract);
SubscribeLocalEvent<HandcuffComponent, MeleeHitEvent>(OnCuffMeleeHit);
- SubscribeLocalEvent<HandcuffComponent, DoAfterEvent<AddCuffDoAfter>>(OnAddCuffDoAfter);
+ SubscribeLocalEvent<HandcuffComponent, AddCuffDoAfterEvent>(OnAddCuffDoAfter);
}
args.Verbs.Add(verb);
}
- private void OnCuffableDoAfter(EntityUid uid, CuffableComponent component, DoAfterEvent<UnCuffDoAfter> args)
+ private void OnCuffableDoAfter(EntityUid uid, CuffableComponent component, UnCuffDoAfterEvent args)
{
- component.Uncuffing = false;
-
if (args.Args.Target is not { } target || args.Args.Used is not { } used)
return;
if (args.Handled)
return;
args.Handled = true;
- Dirty(component);
-
var user = args.Args.User;
if (!args.Cancelled)
args.Handled = true;
}
- private void OnAddCuffDoAfter(EntityUid uid, HandcuffComponent component, DoAfterEvent<AddCuffDoAfter> args)
+ private void OnAddCuffDoAfter(EntityUid uid, HandcuffComponent component, AddCuffDoAfterEvent args)
{
var user = args.Args.User;
if (args.Handled)
return;
args.Handled = true;
- component.Cuffing = false;
if (!args.Cancelled && TryAddNewCuffs(target, user, uid, cuffable))
{
- _audio.PlayPvs(component.EndCuffSound, uid);
+ _audio.PlayPredicted(component.EndCuffSound, uid, user);
if (!_net.IsServer)
return;
}
else
{
+ // TODO Fix popup message wording
+ // This message assumes that the user being handcuffed is the one that caused the handcuff to fail.
+
_popup.PopupEntity(Loc.GetString("handcuff-component-cuff-interrupt-message",
("targetName", Identity.Name(target, EntityManager, user))), user, user);
_popup.PopupEntity(Loc.GetString("handcuff-component-cuff-interrupt-other-message",
if (!Resolve(handcuff, ref handcuffComponent) || !Resolve(target, ref cuffable, false))
return;
- if (handcuffComponent.Cuffing)
- return;
-
if (!TryComp<SharedHandsComponent?>(target, out var hands))
{
if (_net.IsServer)
return;
}
+ var cuffTime = handcuffComponent.CuffTime;
+
+ if (HasComp<StunnedComponent>(target))
+ cuffTime = MathF.Max(0.1f, cuffTime - handcuffComponent.StunBonus);
+
+ if (HasComp<DisarmProneComponent>(target))
+ cuffTime = 0.0f; // cuff them instantly.
+
+ var doAfterEventArgs = new DoAfterArgs(user, cuffTime, new AddCuffDoAfterEvent(), handcuff, target, handcuff)
+ {
+ BreakOnTargetMove = true,
+ BreakOnUserMove = true,
+ BreakOnDamage = true,
+ NeedHand = true
+ };
+
+ if (!_doAfter.TryStartDoAfter(doAfterEventArgs))
+ return;
+
if (_net.IsServer)
{
_popup.PopupEntity(Loc.GetString("handcuff-component-start-cuffing-observer",
}
_audio.PlayPvs(handcuffComponent.StartCuffSound, handcuff);
-
- var cuffTime = handcuffComponent.CuffTime;
-
- if (HasComp<StunnedComponent>(target))
- cuffTime = MathF.Max(0.1f, cuffTime - handcuffComponent.StunBonus);
-
- if (HasComp<DisarmProneComponent>(target))
- cuffTime = 0.0f; // cuff them instantly.
-
- var doAfterEventArgs = new DoAfterEventArgs(user, cuffTime, default, target, handcuff)
- {
- RaiseOnUser = false,
- RaiseOnTarget = false,
- RaiseOnUsed = true,
- BreakOnTargetMove = true,
- BreakOnUserMove = true,
- BreakOnDamage = true,
- BreakOnStun = true,
- NeedHand = true
- };
-
- handcuffComponent.Cuffing = true;
- if (_net.IsServer)
- _doAfter.DoAfter(doAfterEventArgs, new AddCuffDoAfter());
}
/// <summary>
if (!Resolve(target, ref cuffable))
return;
- if (cuffable.Uncuffing)
- return;
-
var isOwner = user == target;
if (cuffsToRemove == null)
return;
}
- if (_net.IsServer)
- _popup.PopupEntity(Loc.GetString("cuffable-component-start-removing-cuffs-message"), user, user);
-
- _audio.PlayPredicted(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, target, user);
-
var uncuffTime = isOwner ? cuff.BreakoutTime : cuff.UncuffTime;
- var doAfterEventArgs = new DoAfterEventArgs(user, uncuffTime, default, target, cuffsToRemove)
+ var doAfterEventArgs = new DoAfterArgs(user, uncuffTime, new UnCuffDoAfterEvent(), target, target, cuffsToRemove)
{
- RaiseOnTarget = true,
- RaiseOnUsed = false,
- RaiseOnUser = false,
BreakOnUserMove = true,
BreakOnTargetMove = true,
BreakOnDamage = true,
- BreakOnStun = true,
- NeedHand = true
+ NeedHand = true,
+ RequireCanInteract = false, // Trust in UncuffAttemptEvent
};
- cuffable.Uncuffing = true;
- Dirty(cuffable);
- if (_net.IsServer)
- _doAfter.DoAfter(doAfterEventArgs, new UnCuffDoAfter());
+ if (!_doAfter.TryStartDoAfter(doAfterEventArgs))
+ return;
+
+ _popup.PopupEntity(Loc.GetString("cuffable-component-start-removing-cuffs-message"), user, Filter.Local(), false);
+ _audio.PlayPredicted(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, target, user);
}
public void Uncuff(EntityUid target, EntityUid user, EntityUid cuffsToRemove, CuffableComponent? cuffable = null, HandcuffComponent? cuff = null)
if (!Resolve(target, ref cuffable) || !Resolve(cuffsToRemove, ref cuff))
return;
+ var attempt = new UncuffAttemptEvent(user, target);
+ RaiseLocalEvent(user, ref attempt);
+ if (attempt.Cancelled)
+ return;
+
_audio.PlayPvs(cuff.EndUncuffSound, target);
cuffable.Container.Remove(cuffsToRemove);
return component.Container.ContainedEntities;
}
- private struct UnCuffDoAfter
+ [Serializable, NetSerializable]
+ private sealed class UnCuffDoAfterEvent : SimpleDoAfterEvent
{
}
- private struct AddCuffDoAfter
+ [Serializable, NetSerializable]
+ private sealed class AddCuffDoAfterEvent : SimpleDoAfterEvent
{
}
}
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Disease.Events;
+
+[Serializable, NetSerializable]
+public sealed class VaccineDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
using Content.Shared.Body.Components;
using Content.Shared.Disposal.Components;
+using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Emag.Systems;
using Content.Shared.Item;
using JetBrains.Annotations;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
+using Robust.Shared.Serialization;
using Robust.Shared.Timing;
namespace Content.Shared.Disposal
{
+ [Serializable, NetSerializable]
+ public sealed class DisposalDoAfterEvent : SimpleDoAfterEvent
+ {
+ }
+
[UsedImplicitly]
public abstract class SharedDisposalUnitSystem : EntitySystem
{
SubscribeLocalEvent<SharedDisposalUnitComponent, GotEmaggedEvent>(OnEmagged);
}
- private void OnPreventCollide(EntityUid uid, SharedDisposalUnitComponent component, ref PreventCollideEvent args)
+ private void OnPreventCollide(EntityUid uid, SharedDisposalUnitComponent component,
+ ref PreventCollideEvent args)
{
var otherBody = args.BodyB.Owner;
/// <summary>
/// Added to entities that are currently performing any doafters.
/// </summary>
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent]
public sealed class ActiveDoAfterComponent : Component
{
-
}
-using System.Threading.Tasks;
-using Content.Shared.Hands.Components;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
-using Robust.Shared.Timing;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+using Robust.Shared.Utility;
namespace Content.Shared.DoAfter;
+
[Serializable, NetSerializable]
[DataDefinition]
+[Access(typeof(SharedDoAfterSystem))]
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;
+ [DataField("index", required:true)]
+ public ushort Index;
- public bool Cancelled = false;
+ public DoAfterId Id => new(Args.User, Index);
- //Cache the delay so the timer properly shows
- public float Delay;
+ [IncludeDataField]
+ public readonly DoAfterArgs Args = default!;
- //Keep track of the time this DoAfter started
+ /// <summary>
+ /// Time at which this do after was started.
+ /// </summary>
+ [DataField("startTime", customTypeSerializer: typeof(TimeOffsetSerializer), required:true)]
public TimeSpan StartTime;
- //Keep track of the time this DoAfter was cancelled
- public TimeSpan CancelledTime;
+ /// <summary>
+ /// The time at which this do after was canceled
+ /// </summary>
+ [DataField("cancelledTime", customTypeSerializer: typeof(TimeOffsetSerializer), required:true)]
+ public TimeSpan? CancelledTime;
+
+ /// <summary>
+ /// If true, this do after has finished, passed the final checks, and has raised its events.
+ /// </summary>
+ [DataField("completed")]
+ public bool Completed;
- //How long has the do after been running?
- public TimeSpan Elapsed = TimeSpan.Zero;
+ /// <summary>
+ /// Whether the do after has been canceled.
+ /// </summary>
+ public bool Cancelled => CancelledTime != null;
/// <summary>
- /// Accrued time when cancelled.
+ /// Position of the user relative to their parent when the do after was started.
/// </summary>
- public TimeSpan CancelledElapsed = TimeSpan.Zero;
+ [DataField("userPosition")]
+ public EntityCoordinates UserPosition;
- public EntityCoordinates UserGrid;
- public EntityCoordinates TargetGrid;
+ /// <summary>
+ /// Position of the target relative to their parent when the do after was started.
+ /// </summary>
+ [DataField("targetPosition")]
+ public EntityCoordinates TargetPosition;
- [NonSerialized]
- public Action<bool>? Done;
+ /// <summary>
+ /// If <see cref="DoAfterArgs.NeedHand"/> is true, this is the hand that was selected when the doafter started.
+ /// </summary>
+ [DataField("activeHand")]
+ public string? InitialHand;
-#pragma warning disable RA0004
- public DoAfterStatus Status => AsTask.IsCompletedSuccessfully ? AsTask.Result : DoAfterStatus.Running;
-#pragma warning restore RA0004
+ /// <summary>
+ /// If <see cref="NeedHand"/> is true, this is the entity that was in the active hand when the doafter started.
+ /// </summary>
+ [DataField("activeItem")]
+ public EntityUid? InitialItem;
- // NeedHand
- public readonly string? ActiveHand;
- public readonly EntityUid? ActiveItem;
+ // cached attempt event for the sake of avoiding unnecessary reflection every time this needs to be raised.
+ [NonSerialized] public object? AttemptEvent;
- public DoAfter(DoAfterEventArgs eventArgs, IEntityManager entityManager)
+ private DoAfter()
{
- 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;
+ public DoAfter(ushort index, DoAfterArgs args, TimeSpan startTime)
+ {
+ Index = index;
- // 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))
+ if (args.Target == null)
{
- ActiveHand = handsComponent.ActiveHand?.Name;
- ActiveItem = handsComponent.ActiveHandEntity;
+ DebugTools.Assert(!args.BreakOnTargetMove);
+ args.BreakOnTargetMove = false;
}
- Tcs = new TaskCompletionSource<DoAfterStatus>();
- AsTask = Tcs.Task;
+ Args = args;
+ StartTime = startTime;
+ }
+
+ public DoAfter(DoAfter other)
+ {
+ Index = other.Index;
+ Args = new(other.Args);
+ StartTime = other.StartTime;
+ CancelledTime = other.CancelledTime;
+ Completed = other.Completed;
+ UserPosition = other.UserPosition;
+ TargetPosition = other.TargetPosition;
+ InitialHand = other.InitialHand;
+ InitialItem = other.InitialItem;
}
}
+
+/// <summary>
+/// Simple struct that contains data required to uniquely identify a doAfter.
+/// </summary>
+/// <remarks>
+/// Can be used to track currently active do-afters to prevent simultaneous do-afters.
+/// </remarks>
+public record struct DoAfterId(EntityUid Uid, ushort Index);
--- /dev/null
+using Content.Shared.FixedPoint;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.DoAfter;
+
+[Serializable, NetSerializable]
+[DataDefinition]
+public sealed class DoAfterArgs
+{
+ /// <summary>
+ /// The entity invoking do_after
+ /// </summary>
+ [DataField("user", required: true)]
+ public readonly EntityUid User;
+
+ /// <summary>
+ /// How long does the do_after require to complete
+ /// </summary>
+ [DataField("delay", required: true)]
+ public readonly TimeSpan Delay;
+
+ /// <summary>
+ /// Applicable target (if relevant)
+ /// </summary>
+ [DataField("target")]
+ public readonly EntityUid? Target;
+
+ /// <summary>
+ /// Entity used by the User on the Target.
+ /// </summary>
+ [DataField("using")]
+ public readonly EntityUid? Used;
+
+ #region Event options
+ /// <summary>
+ /// The event that will get raised when the DoAfter has finished. If null, this will simply raise a <see cref="SimpleDoAfterEvent"/>
+ /// </summary>
+ [DataField("event", required: true)]
+ public readonly DoAfterEvent Event = default!;
+
+ /// <summary>
+ /// This option determines how frequently the DoAfterAttempt event will get raised. Defaults to never raising the
+ /// event.
+ /// </summary>
+ [DataField("attemptEventFrequency")]
+ public AttemptFrequency AttemptFrequency;
+
+ /// <summary>
+ /// Entity which will receive the directed event. If null, no directed event will be raised.
+ /// </summary>
+ [DataField("eventTarget")]
+ public readonly EntityUid? EventTarget;
+
+ /// <summary>
+ /// Should the DoAfter event broadcast? If this is false, then <see cref="EventTarget"/> should be a valid entity.
+ /// </summary>
+ [DataField("broadcast")]
+ public bool Broadcast;
+ #endregion
+
+ #region Break/Cancellation Options
+ // Break the chains
+ /// <summary>
+ /// Whether or not this do after requires the user to have hands.
+ /// </summary>
+ [DataField("needHand")]
+ public bool NeedHand;
+
+ /// <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). This does nothing if <see cref="NeedHand"/> is false.
+ /// </summary>
+ [DataField("breakOnHandChange")]
+ public bool BreakOnHandChange = true;
+
+ /// <summary>
+ /// If do_after stops when the user moves
+ /// </summary>
+ [DataField("breakOnUserMove")]
+ public bool BreakOnUserMove;
+
+ /// <summary>
+ /// If do_after stops when the target moves (if there is a target)
+ /// </summary>
+ [DataField("breakOnTargetMove")]
+ public bool BreakOnTargetMove;
+
+ /// <summary>
+ /// Threshold for user and target movement
+ /// </summary>
+ [DataField("movementThreshold")]
+ public float MovementThreshold = 0.1f;
+
+ /// <summary>
+ /// Threshold for distance user from the used OR target entities.
+ /// </summary>
+ [DataField("distanceThreshold")]
+ public float? DistanceThreshold;
+
+ /// <summary>
+ /// Whether damage will cancel the DoAfter. See also <see cref="DamageThreshold"/>.
+ /// </summary>
+ [DataField("breakOnDamage")]
+ public bool BreakOnDamage;
+
+ /// <summary>
+ /// Threshold for user damage. This damage has to be dealt in a single event, not over time.
+ /// </summary>
+ [DataField("damageThreshold")]
+ public FixedPoint2 DamageThreshold = 1;
+
+ /// <summary>
+ /// If true, this DoAfter will be canceled if the user can no longer interact with the target.
+ /// </summary>
+ [DataField("requireCanInteract")]
+ public bool RequireCanInteract = true;
+ #endregion
+
+ #region Duplicates
+ /// <summary>
+ /// If true, this will prevent duplicate DoAfters from being started See also <see cref="DuplicateConditions"/>.
+ /// </summary>
+ /// <remarks>
+ /// Note that this will block even if the duplicate is cancelled because either DoAfter had
+ /// <see cref="CancelDuplicate"/> enabled.
+ /// </remarks>
+ [DataField("blockDuplicate")]
+ public bool BlockDuplicate = true;
+
+ /// <summary>
+ /// If true, this will cancel any duplicate DoAfters when attempting to add a new DoAfter. See also
+ /// <see cref="DuplicateConditions"/>.
+ /// </summary>
+ [DataField("cancelDuplicate")]
+ public bool CancelDuplicate = true;
+
+ /// <summary>
+ /// These flags determine what DoAfter properties are used to determine whether one DoAfter is a duplicate of
+ /// another.
+ /// </summary>
+ /// <remarks>
+ /// Note that both DoAfters may have their own conditions, and they will be considered duplicated if either set
+ /// of conditions is satisfied.
+ /// </remarks>
+ [DataField("duplicateCondition")]
+ public DuplicateConditions DuplicateCondition = DuplicateConditions.All;
+ #endregion
+
+ /// <summary>
+ /// Additional conditions that need to be met. Return false to cancel.
+ /// </summary>
+ [NonSerialized]
+ [Obsolete("Use checkEvent instead")]
+ public Func<bool>? ExtraCheck;
+
+ #region Constructors
+
+ /// <summary>
+ /// Creates a new set of DoAfter arguments.
+ /// </summary>
+ /// <param name="user">The user that will perform the DoAfter</param>
+ /// <param name="delay">The time it takes for the DoAfter to complete</param>
+ /// <param name="event">The event that will be raised when the DoAfter has ended (completed or cancelled).</param>
+ /// <param name="eventTarget">The entity at which the event will be directed. If null, the event will not be directed.</param>
+ /// <param name="target">The entity being targeted by the DoAFter. Not the same as <see cref="EventTarget"/></param>.
+ /// <param name="used">The entity being used during the DoAfter. E.g., a tool</param>
+ public DoAfterArgs(
+ EntityUid user,
+ TimeSpan delay,
+ DoAfterEvent @event,
+ EntityUid? eventTarget,
+ EntityUid? target = null,
+ EntityUid? used = null)
+ {
+ User = user;
+ Delay = delay;
+ Target = target;
+ Used = used;
+ EventTarget = eventTarget;
+ Event = @event;
+ }
+
+ private DoAfterArgs()
+ {
+ }
+
+ /// <summary>
+ /// Creates a new set of DoAfter arguments.
+ /// </summary>
+ /// <param name="user">The user that will perform the DoAfter</param>
+ /// <param name="seconds">The time it takes for the DoAfter to complete, in seconds</param>
+ /// <param name="event">The event that will be raised when the DoAfter has ended (completed or cancelled).</param>
+ /// <param name="eventTarget">The entity at which the event will be directed. If null, the event will not be directed.</param>
+ /// <param name="target">The entity being targeted by the DoAfter. Not the same as <see cref="EventTarget"/></param>.
+ /// <param name="used">The entity being used during the DoAfter. E.g., a tool</param>
+ public DoAfterArgs(
+ EntityUid user,
+ float seconds,
+ DoAfterEvent @event,
+ EntityUid? eventTarget,
+ EntityUid? target = null,
+ EntityUid? used = null)
+ : this(user, TimeSpan.FromSeconds(seconds), @event, eventTarget, target, used)
+ {
+ }
+
+ #endregion
+
+ public DoAfterArgs(DoAfterArgs other)
+ {
+ User = other.User;
+ Delay = other.Delay;
+ Target = other.Target;
+ Used = other.Used;
+ EventTarget = other.EventTarget;
+ Broadcast = other.Broadcast;
+ NeedHand = other.NeedHand;
+ BreakOnHandChange = other.BreakOnHandChange;
+ BreakOnUserMove = other.BreakOnUserMove;
+ BreakOnTargetMove = other.BreakOnTargetMove;
+ MovementThreshold = other.MovementThreshold;
+ DistanceThreshold = other.DistanceThreshold;
+ BreakOnDamage = other.BreakOnDamage;
+ DamageThreshold = other.DamageThreshold;
+ RequireCanInteract = other.RequireCanInteract;
+ AttemptFrequency = other.AttemptFrequency;
+ BlockDuplicate = other.BlockDuplicate;
+ CancelDuplicate = other.CancelDuplicate;
+ DuplicateCondition = other.DuplicateCondition;
+
+ Event = other.Event.Clone();
+ }
+}
+
+/// <summary>
+/// See <see cref="DoAfterArgs.DuplicateCondition"/>.
+/// </summary>
+[Flags]
+public enum DuplicateConditions : byte
+{
+ /// <summary>
+ /// This DoAfter will consider any other DoAfter with the same user to be a duplicate.
+ /// </summary>
+ None = 0,
+
+ /// <summary>
+ /// Requires that <see cref="Used"/> refers to the same entity in order to be considered a duplicate.
+ /// </summary>
+ /// <remarks>
+ /// E.g., if all checks are enabled for stripping, then stripping different articles of clothing on the same
+ /// mob would be allowed. If instead this check were disabled, then any stripping actions on the same target
+ /// would be considered duplicates, so you would only be able to take one piece of clothing at a time.
+ /// </remarks>
+ SameTool = 1 << 1,
+
+ /// <summary>
+ /// Requires that <see cref="Target"/> refers to the same entity in order to be considered a duplicate.
+ /// </summary>
+ /// <remarks>
+ /// E.g., if all checks are enabled for mining, then using the same pickaxe to mine different rocks will be
+ /// allowed. If instead this check were disabled, then the trying to mine a different rock with the same
+ /// pickaxe would be considered a duplicate DoAfter.
+ /// </remarks>
+ SameTarget = 1 << 2,
+
+ /// <summary>
+ /// Requires that the <see cref="Event"/> types match in order to be considered a duplicate.
+ /// </summary>
+ /// <remarks>
+ /// If your DoAfter should block other unrelated DoAfters involving the same set of entities, you may want
+ /// to disable this condition. E.g. force feeding a donk pocket and forcefully giving someone a donk pocket
+ /// should be mutually exclusive, even though the DoAfters have unrelated effects.
+ /// </remarks>
+ SameEvent = 1 << 3,
+
+ All = SameTool | SameTarget | SameEvent,
+}
+
+public enum AttemptFrequency : byte
+{
+ /// <summary>
+ /// Never raise the attempt event.
+ /// </summary>
+ Never = 0,
+
+ /// <summary>
+ /// Raises the attempt event when the DoAfter is about to start or end.
+ /// </summary>
+ StartAndEnd = 1,
+
+ /// <summary>
+ /// Raise the attempt event every tick while the DoAfter is running.
+ /// </summary>
+ EveryTick = 2
+}
+using System.Threading.Tasks;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.DoAfter;
[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedDoAfterSystem))]
public sealed class DoAfterComponent : Component
{
- [DataField("doAfters")]
- public readonly Dictionary<byte, DoAfter> DoAfters = new();
+ [DataField("nextId")]
+ public ushort NextId;
- [DataField("cancelledDoAfters")]
- public readonly Dictionary<byte, DoAfter> CancelledDoAfters = new();
+ [DataField("doAfters")]
+ public readonly Dictionary<ushort, DoAfter> 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.
- [DataField("runningIndex")]
- public byte RunningIndex;
+ // Used by obsolete async do afters
+ public readonly Dictionary<ushort, TaskCompletionSource<DoAfterStatus>> AwaitedDoAfters = new();
}
[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 byte Id;
- public readonly DoAfterEventArgs Args;
-
- public DoAfterEvent(bool cancelled, DoAfterEventArgs args, byte id)
- {
- Cancelled = cancelled;
- Args = args;
- Id = id;
- }
-}
-
-/// <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 byte Id;
- public readonly DoAfterEventArgs Args;
-
- public DoAfterEvent(T additionalData, bool cancelled, DoAfterEventArgs args, byte id)
- {
- AdditionalData = additionalData;
- Cancelled = cancelled;
- Args = args;
- Id = id;
- }
-}
-
-[Serializable, NetSerializable]
-public sealed class CancelledDoAfterMessage : EntityEventArgs
-{
- public EntityUid Uid;
- public byte ID;
+ public readonly ushort NextId;
+ public readonly Dictionary<ushort, DoAfter> DoAfters;
- public CancelledDoAfterMessage(EntityUid uid, byte id)
+ public DoAfterComponentState(DoAfterComponent component)
{
- Uid = uid;
- ID = id;
+ NextId = component.NextId;
+ DoAfters = component.DoAfters;
}
}
[Serializable, NetSerializable]
public enum DoAfterStatus : byte
{
+ Invalid,
Running,
Cancelled,
Finished,
--- /dev/null
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.DoAfter;
+
+/// <summary>
+/// Base type for events that get raised when a do-after is canceled or finished.
+/// </summary>
+[Serializable, NetSerializable]
+[ImplicitDataDefinitionForInheritors]
+public abstract class DoAfterEvent : HandledEntityEventArgs
+{
+ /// <summary>
+ /// The do after that triggered this event. This will be set by the do after system before the event is raised.
+ /// </summary>
+ [NonSerialized]
+ public DoAfter DoAfter = default!;
+
+ /// <summary>
+ /// Duplicate the current event. This is used by state handling, and should copy by value unless the reference
+ /// types are immutable.
+ /// </summary>
+ public abstract DoAfterEvent Clone();
+
+ #region Convenience properties
+ public bool Cancelled => DoAfter.Cancelled;
+ public EntityUid User => DoAfter.Args.User;
+ public EntityUid? Target => DoAfter.Args.Target;
+ public EntityUid? Used => DoAfter.Args.Used;
+ public DoAfterArgs Args => DoAfter.Args;
+ #endregion
+}
+
+/// <summary>
+/// Blank / empty event for simple do afters that carry no information.
+/// </summary>
+/// <remarks>
+/// This just exists as a convenience to avoid having to re-implement Clone() for every simply DoAfterEvent.
+/// If an event actually contains data, it should actually override Clone().
+/// </remarks>
+[Serializable, NetSerializable]
+public abstract class SimpleDoAfterEvent : DoAfterEvent
+{
+ // TODO: Find some way to enforce that inheritors don't store data?
+ // Alternatively, I just need to allow generics to be networked.
+ // E.g., then a SimpleDoAfter<TEvent> would just raise a TEvent event.
+ // But afaik generic event types currently can't be serialized for networking or YAML.
+
+ public override DoAfterEvent Clone() => this;
+}
+
+// Placeholder for obsolete async do afters
+[Serializable, NetSerializable]
+[Obsolete("Dont use async DoAfters")]
+public sealed class AwaitedDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
+/// <summary>
+/// This event will optionally get raised every tick while a do-after is in progress to check whether the do-after
+/// should be canceled.
+/// </summary>
+public sealed class DoAfterAttemptEvent<TEvent> : CancellableEntityEventArgs where TEvent : DoAfterEvent
+{
+ /// <summary>
+ /// The do after that triggered this event.
+ /// </summary>
+ public readonly DoAfter DoAfter;
+
+ /// <summary>
+ /// The event that the DoAfter will raise after sucesfully finishing. Given that this event has the data
+ /// required to perform the interaction, it should also contain the data required to validate/attempt the
+ /// interaction.
+ /// </summary>
+ public readonly TEvent Event;
+
+ public DoAfterAttemptEvent(DoAfter doAfter, TEvent @event)
+ {
+ DoAfter = doAfter;
+ Event = @event;
+ }
+}
+++ /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.Hands.Components;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.DoAfter;
+
+public abstract partial class SharedDoAfterSystem : EntitySystem
+{
+ [Dependency] private readonly IDynamicTypeFactory _factory = default!;
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var time = GameTiming.CurTime;
+ var xformQuery = GetEntityQuery<TransformComponent>();
+ var handsQuery = GetEntityQuery<SharedHandsComponent>();
+
+ var enumerator = EntityQueryEnumerator<ActiveDoAfterComponent, DoAfterComponent>();
+ while (enumerator.MoveNext(out var uid, out var active, out var comp))
+ {
+ Update(uid, active, comp, time, xformQuery, handsQuery);
+ }
+ }
+
+ protected void Update(
+ EntityUid uid,
+ ActiveDoAfterComponent active,
+ DoAfterComponent comp,
+ TimeSpan time,
+ EntityQuery<TransformComponent> xformQuery,
+ EntityQuery<SharedHandsComponent> handsQuery)
+ {
+ var dirty = false;
+
+ foreach (var doAfter in comp.DoAfters.Values)
+ {
+ if (doAfter.CancelledTime != null)
+ {
+ if (time - doAfter.CancelledTime.Value > ExcessTime)
+ {
+ comp.DoAfters.Remove(doAfter.Index);
+ dirty = true;
+ }
+ continue;
+ }
+
+ if (doAfter.Completed)
+ {
+ if (time - doAfter.StartTime > doAfter.Args.Delay + ExcessTime)
+ {
+ comp.DoAfters.Remove(doAfter.Index);
+ dirty = true;
+ }
+ continue;
+ }
+
+ if (ShouldCancel(doAfter, xformQuery, handsQuery))
+ {
+ InternalCancel(doAfter, comp);
+ dirty = true;
+ continue;
+ }
+
+ if (time - doAfter.StartTime >= doAfter.Args.Delay)
+ {
+ TryComplete(doAfter, comp);
+ dirty = true;
+ }
+ }
+
+ if (dirty)
+ Dirty(comp);
+
+ if (comp.DoAfters.Count == 0)
+ RemCompDeferred(uid, active);
+ }
+
+ private bool TryAttemptEvent(DoAfter doAfter)
+ {
+ var args = doAfter.Args;
+
+ if (args.ExtraCheck?.Invoke() == false)
+ return false;
+
+ if (doAfter.AttemptEvent == null)
+ {
+ // I feel like this is somewhat cursed, but its the only way I can think of without having to just send
+ // redundant data over the network and increasing DoAfter boilerplate.
+ var evType = typeof(DoAfterAttemptEvent<>).MakeGenericType(args.Event.GetType());
+ doAfter.AttemptEvent = _factory.CreateInstance(evType, new object[] { doAfter, args.Event });
+ }
+
+ if (args.EventTarget != null)
+ RaiseLocalEvent(args.EventTarget.Value, doAfter.AttemptEvent, args.Broadcast);
+ else
+ RaiseLocalEvent(doAfter.AttemptEvent);
+
+ var ev = (CancellableEntityEventArgs) doAfter.AttemptEvent;
+ if (!ev.Cancelled)
+ return true;
+
+ ev.Uncancel();
+ return false;
+ }
+
+ private void TryComplete(DoAfter doAfter, DoAfterComponent component)
+ {
+ if (doAfter.Cancelled || doAfter.Completed)
+ return;
+
+ // Perform final check (if required)
+ if (doAfter.Args.AttemptFrequency == AttemptFrequency.StartAndEnd
+ && !TryAttemptEvent(doAfter))
+ {
+ InternalCancel(doAfter, component);
+ return;
+ }
+
+ doAfter.Completed = true;
+ RaiseDoAfterEvents(doAfter, component);
+ }
+
+ private bool ShouldCancel(DoAfter doAfter,
+ EntityQuery<TransformComponent> xformQuery,
+ EntityQuery<SharedHandsComponent> handsQuery)
+ {
+ var args = doAfter.Args;
+
+ //re-using xformQuery for Exists() checks.
+ if (args.Used is { } used && !xformQuery.HasComponent(used))
+ return true;
+
+ if (args.EventTarget is {Valid: true} eventTarget && !xformQuery.HasComponent(eventTarget))
+ return true;
+
+ if (!xformQuery.TryGetComponent(args.User, out var userXform))
+ return true;
+
+ TransformComponent? targetXform = null;
+ if (args.Target is { } target && !xformQuery.TryGetComponent(target, out targetXform))
+ return true;
+
+ TransformComponent? usedXform = null;
+ if (args.Used is { } @using && !xformQuery.TryGetComponent(@using, out usedXform))
+ return true;
+
+ // TODO: Handle Inertia in space
+ // TODO: Re-use existing xform query for these calculations.
+ if (args.BreakOnUserMove && !userXform.Coordinates
+ .InRange(EntityManager, _transform, doAfter.UserPosition, args.MovementThreshold))
+ return true;
+
+ if (args.BreakOnTargetMove)
+ {
+ DebugTools.Assert(targetXform != null, "Break on move is true, but no target specified?");
+ if (targetXform != null && !targetXform.Coordinates.InRange(EntityManager, _transform,
+ doAfter.TargetPosition, args.MovementThreshold))
+ return true;
+ }
+
+ if (args.AttemptFrequency == AttemptFrequency.EveryTick && !TryAttemptEvent(doAfter))
+ return true;
+
+ if (args.NeedHand)
+ {
+ if (!handsQuery.TryGetComponent(args.User, out var hands) || hands.Count == 0)
+ return true;
+
+ if (args.BreakOnHandChange && (hands.ActiveHand?.Name != doAfter.InitialHand
+ || hands.ActiveHandEntity != doAfter.InitialItem))
+ {
+ return true;
+ }
+ }
+
+ if (args.RequireCanInteract && !_actionBlocker.CanInteract(args.User, args.Target))
+ return true;
+
+ if (args.DistanceThreshold != null)
+ {
+ if (targetXform != null
+ && !args.User.Equals(args.Target)
+ && !userXform.Coordinates.InRange(EntityManager, _transform, targetXform.Coordinates,
+ args.DistanceThreshold.Value))
+ {
+ return true;
+ }
+
+ if (usedXform != null
+ && !userXform.Coordinates.InRange(EntityManager, _transform, usedXform.Coordinates,
+ args.DistanceThreshold.Value))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
-using System.Linq;
-using System.Threading;
+using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
+using Content.Shared.ActionBlocker;
using Content.Shared.Damage;
using Content.Shared.Hands.Components;
using Content.Shared.Mobs;
-using Content.Shared.Stunnable;
using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Shared.DoAfter;
-public abstract class SharedDoAfterSystem : EntitySystem
+public abstract partial class SharedDoAfterSystem : EntitySystem
{
[Dependency] protected readonly IGameTiming GameTiming = default!;
+ [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
- // We cache the list as to not allocate every update tick...
- private readonly Queue<DoAfter> _pending = new();
+ /// <summary>
+ /// We'll use an excess time so stuff like finishing effects can show.
+ /// </summary>
+ private static readonly TimeSpan ExcessTime = TimeSpan.FromSeconds(0.5f);
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DoAfterComponent, DamageChangedEvent>(OnDamage);
+ SubscribeLocalEvent<DoAfterComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<DoAfterComponent, MobStateChangedEvent>(OnStateChanged);
SubscribeLocalEvent<DoAfterComponent, ComponentGetState>(OnDoAfterGetState);
+ SubscribeLocalEvent<DoAfterComponent, ComponentHandleState>(OnDoAfterHandleState);
}
- public bool DoAfterExists(EntityUid uid, DoAfter doAFter, DoAfterComponent? component = null)
- => DoAfterExists(uid, doAFter.ID, component);
-
- public bool DoAfterExists(EntityUid uid, byte id, DoAfterComponent? component = null)
+ private void OnUnpaused(EntityUid uid, DoAfterComponent component, ref EntityUnpausedEvent args)
{
- if (!Resolve(uid, ref component))
- return false;
-
- return component.DoAfters.ContainsKey(id);
- }
+ foreach (var doAfter in component.DoAfters.Values)
+ {
+ doAfter.StartTime += args.PausedTime;
+ if (doAfter.CancelledTime != null)
+ doAfter.CancelledTime = doAfter.CancelledTime.Value + args.PausedTime;
+ }
- 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)
+ foreach (var doAfter in component.DoAfters.Values)
{
- Cancel(uid, doAfter, component);
+ InternalCancel(doAfter, component);
}
+ Dirty(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;
+ var delta = args.DamageDelta?.Total;
+
+ var dirty = false;
foreach (var doAfter in component.DoAfters.Values)
{
- if (doAfter.EventArgs.BreakOnDamage && args.DamageDelta?.Total.Float() > doAfter.EventArgs.DamageThreshold)
- Cancel(uid, doAfter, component);
+ if (doAfter.Args.BreakOnDamage && delta >= doAfter.Args.DamageThreshold)
+ {
+ InternalCancel(doAfter, component);
+ dirty = true;
+ }
}
+
+ if (dirty)
+ Dirty(component);
}
- public override void Update(float frameTime)
+ private void RaiseDoAfterEvents(DoAfter doAfter, DoAfterComponent component)
{
- base.Update(frameTime);
+ var ev = doAfter.Args.Event;
+ ev.DoAfter = doAfter;
- foreach (var (_, comp) in EntityManager.EntityQuery<ActiveDoAfterComponent, DoAfterComponent>())
- {
- //Don't run the doafter if its comp or owner is deleted.
- if (EntityManager.Deleted(comp.Owner) || comp.Deleted)
- continue;
+ if (Exists(doAfter.Args.EventTarget))
+ RaiseLocalEvent(doAfter.Args.EventTarget.Value, (object)ev, doAfter.Args.Broadcast);
+ else if (doAfter.Args.Broadcast)
+ RaiseLocalEvent((object)ev);
- 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();
- }
- }
+ if (component.AwaitedDoAfters.Remove(doAfter.Index, out var tcs))
+ tcs.SetResult(doAfter.Cancelled ? DoAfterStatus.Cancelled : DoAfterStatus.Finished);
+ }
- while (_pending.TryDequeue(out var doAfter))
- {
- if (doAfter.Status == DoAfterStatus.Cancelled)
- {
- Cancelled(comp, doAfter);
+ private void OnDoAfterGetState(EntityUid uid, DoAfterComponent comp, ref ComponentGetState args)
+ {
+ args.State = new DoAfterComponentState(comp);
+ }
- if (doAfter.Done != null)
- doAfter.Done(true);
- }
+ private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent comp, ref ComponentHandleState args)
+ {
+ if (args.Current is not DoAfterComponentState state)
+ return;
- if (doAfter.Status == DoAfterStatus.Finished)
- {
- Finished(comp, doAfter);
+ // Note that the client may have correctly predicted the creation of a do-after, but that doesn't guarantee that
+ // the contents of the do-after data are correct. So this just takes the brute force approach and completely
+ // overwrites the state.
- if (doAfter.Done != null)
- doAfter.Done(false);
- }
- }
+ comp.DoAfters.Clear();
+ foreach (var (id, doAfter) in state.DoAfters)
+ {
+ comp.DoAfters.Add(id, new(doAfter));
}
+
+ comp.NextId = state.NextId;
+ DebugTools.Assert(!comp.DoAfters.ContainsKey(comp.NextId));
+
+ if (comp.DoAfters.Count == 0)
+ RemCompDeferred<ActiveDoAfterComponent>(uid);
+ else
+ EnsureComp<ActiveDoAfterComponent>(uid);
}
+
+ #region Creation
/// <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)
+ // TODO remove this, as well as AwaitedDoAfterEvent and DoAfterComponent.AwaitedDoAfters
+ [Obsolete("Use the synchronous version instead.")]
+ public async Task<DoAfterStatus> WaitDoAfter(DoAfterArgs doAfter, DoAfterComponent? component = null)
{
- var doAfter = CreateDoAfter(eventArgs);
+ if (!Resolve(doAfter.User, ref component))
+ return DoAfterStatus.Cancelled;
- await doAfter.AsTask;
+ if (!TryStartDoAfter(doAfter, out var id, component))
+ return DoAfterStatus.Cancelled;
- return doAfter.Status;
+ var tcs = new TaskCompletionSource<DoAfterStatus>();
+ component.AwaitedDoAfters.Add(id.Value.Index, tcs);
+ return await tcs.Task;
}
/// <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
+ /// Attempts to start a new DoAfter. Note that even if this function returns true, an interaction may have
+ /// occured, as starting a duplicate DoAfter may cancel currently running DoAfters.
/// </summary>
- /// <param name="eventArgs">The DoAfterEventArgs</param>
- /// <param name="data">The extra data sent over </param>
- public DoAfter DoAfter<T>(DoAfterEventArgs eventArgs, T data)
- {
- var doAfter = CreateDoAfter(eventArgs);
- doAfter.Done = cancelled => { Send(data, cancelled, eventArgs, doAfter.ID); };
- return doAfter;
- }
+ /// <param name="args">The DoAfter arguments</param>
+ /// <param name="component">The user's DoAfter component</param>
+ /// <returns></returns>
+ public bool TryStartDoAfter(DoAfterArgs args, DoAfterComponent? component = null)
+ => TryStartDoAfter(args, out _, component);
/// <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
+ /// Attempts to start a new DoAfter. Note that even if this function returns false, an interaction may have
+ /// occured, as starting a duplicate DoAfter may cancel currently running DoAfters.
/// </summary>
- /// <param name="eventArgs">The DoAfterEventArgs</param>
- public DoAfter DoAfter(DoAfterEventArgs eventArgs)
+ /// <param name="args">The DoAfter arguments</param>
+ /// <param name="id">The Id of the newly started DoAfter</param>
+ /// <param name="comp">The user's DoAfter component</param>
+ /// <returns></returns>
+ public bool TryStartDoAfter(DoAfterArgs args, [NotNullWhen(true)] out DoAfterId? id, DoAfterComponent? comp = null)
{
- var doAfter = CreateDoAfter(eventArgs);
- doAfter.Done = cancelled => { Send(cancelled, eventArgs, doAfter.ID); };
- return doAfter;
- }
+ DebugTools.Assert(args.Broadcast || Exists(args.EventTarget) || args.Event.GetType() == typeof(AwaitedDoAfterEvent));
+ DebugTools.Assert(args.Event.GetType().HasCustomAttribute<NetSerializableAttribute>()
+ || args.Event.GetType().Namespace is {} ns && ns.StartsWith("Content.IntegrationTests"), // classes defined in tests cannot be marked as serializable.
+ $"Do after event is not serializable. Event: {args.Event.GetType()}");
- 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);
- doAfter.ID = doAfterComponent.RunningIndex;
- doAfter.StartTime = GameTiming.CurTime;
- Add(eventArgs.User, doAfterComponent, doAfter);
- return doAfter;
- }
+ if (!Resolve(args.User, ref comp))
+ {
+ Logger.Error($"Attempting to start a doAfter with invalid user: {ToPrettyString(args.User)}.");
+ id = null;
+ return false;
+ }
- private void Run(EntityUid entity, DoAfterComponent comp, DoAfter doAfter)
- {
- switch (doAfter.Status)
+ // Duplicate blocking & cancellation.
+ if (!ProcessDuplicates(args, comp))
{
- case DoAfterStatus.Running:
- break;
- case DoAfterStatus.Cancelled:
- case DoAfterStatus.Finished:
- return;
- default:
- throw new ArgumentOutOfRangeException();
+ id = null;
+ return false;
}
- doAfter.Elapsed = GameTiming.CurTime - doAfter.StartTime;
+ id = new DoAfterId(args.User, comp.NextId++);
+ var doAfter = new DoAfter(id.Value.Index, args, GameTiming.CurTime);
- if (IsFinished(doAfter))
- {
- if (!TryPostCheck(doAfter))
- {
- Cancel(entity, doAfter, comp);
- }
- else
- {
- doAfter.Tcs.SetResult(DoAfterStatus.Finished);
- }
+ if (args.BreakOnUserMove)
+ doAfter.UserPosition = Transform(args.User).Coordinates;
- return;
- }
+ if (args.Target != null && args.BreakOnTargetMove)
+ // Target should never be null if the bool is set.
+ doAfter.TargetPosition = Transform(args.Target.Value).Coordinates;
- if (IsCancelled(doAfter))
+ // 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 (args.NeedHand && args.BreakOnHandChange)
{
- Cancel(entity, doAfter, comp);
- }
- }
+ if (!TryComp(args.User, out SharedHandsComponent? handsComponent))
+ return false;
- private bool TryPostCheck(DoAfter doAfter)
- {
- return doAfter.EventArgs.PostCheck?.Invoke() != false;
- }
+ doAfter.InitialHand = handsComponent.ActiveHand?.Name;
+ doAfter.InitialItem = handsComponent.ActiveHandEntity;
+ }
- private bool IsFinished(DoAfter doAfter)
- {
- var delay = TimeSpan.FromSeconds(doAfter.EventArgs.Delay);
+ // Inital checks
+ if (ShouldCancel(doAfter, GetEntityQuery<TransformComponent>(), GetEntityQuery<SharedHandsComponent>()))
+ return false;
- if (doAfter.Elapsed <= delay)
+ if (args.AttemptFrequency == AttemptFrequency.StartAndEnd && !TryAttemptEvent(doAfter))
return false;
+ if (args.Delay <= TimeSpan.Zero)
+ {
+ RaiseDoAfterEvents(doAfter, comp);
+ // We don't store instant do-afters. This is just a lazy way of hiding them from client-side visuals.
+ return true;
+ }
+
+ comp.DoAfters.Add(doAfter.Index, doAfter);
+ EnsureComp<ActiveDoAfterComponent>(args.User);
+ Dirty(comp);
+ args.Event.DoAfter = doAfter;
return true;
}
- private bool IsCancelled(DoAfter doAfter)
+ /// <summary>
+ /// Cancel any applicable duplicate DoAfters and return whether or not the new DoAfter should be created.
+ /// </summary>
+ private bool ProcessDuplicates(DoAfterArgs args, DoAfterComponent component)
{
- var eventArgs = doAfter.EventArgs;
- var xForm = GetEntityQuery<TransformComponent>();
+ var blocked = false;
+ foreach (var existing in component.DoAfters.Values)
+ {
+ if (existing.Cancelled || existing.Completed)
+ continue;
- if (!Exists(eventArgs.User) || eventArgs.Target is { } target && !Exists(target))
- return true;
+ if (!IsDuplicate(existing.Args, args))
+ continue;
- if (eventArgs.CancelToken.IsCancellationRequested)
- return true;
+ blocked = blocked | args.BlockDuplicate | existing.Args.BlockDuplicate;
- //TODO: Handle Inertia in space
- if (eventArgs.BreakOnUserMove && !xForm.GetComponent(eventArgs.User).Coordinates
- .InRange(EntityManager, doAfter.UserGrid, eventArgs.MovementThreshold))
- return true;
+ if (args.CancelDuplicate || existing.Args.CancelDuplicate)
+ Cancel(args.User, existing.Index, component);
+ }
- if (eventArgs.Target != null && eventArgs.BreakOnTargetMove && !xForm.GetComponent(eventArgs.Target!.Value)
- .Coordinates.InRange(EntityManager, doAfter.TargetGrid, eventArgs.MovementThreshold))
- return true;
+ return !blocked;
+ }
- if (eventArgs.ExtraCheck != null && !eventArgs.ExtraCheck.Invoke())
+ private bool IsDuplicate(DoAfterArgs args, DoAfterArgs otherArgs)
+ {
+ if (IsDuplicate(args, otherArgs, args.DuplicateCondition))
return true;
- if (eventArgs.BreakOnStun && HasComp<StunnedComponent>(eventArgs.User))
- return true;
+ if (args.DuplicateCondition == otherArgs.DuplicateCondition)
+ return false;
- if (eventArgs.NeedHand)
- {
- if (!TryComp<SharedHandsComponent>(eventArgs.User, out var handsComp))
- {
- //TODO: Figure out active hand and item values
+ return IsDuplicate(args, otherArgs, otherArgs.DuplicateCondition);
+ }
- // 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;
+ private bool IsDuplicate(DoAfterArgs args, DoAfterArgs otherArgs, DuplicateConditions conditions )
+ {
+ if ((conditions & DuplicateConditions.SameTarget) != 0
+ && args.Target != otherArgs.Target)
+ {
+ return false;
+ }
- var currentItem = handsComp.ActiveHandEntity;
- if (doAfter.ActiveItem != currentItem)
- return true;
- }
+ if ((conditions & DuplicateConditions.SameTool) != 0
+ && args.Used != otherArgs.Used)
+ {
+ return false;
}
- if (eventArgs.DistanceThreshold != null)
+ if ((conditions & DuplicateConditions.SameEvent) != 0
+ && args.Event.GetType() != otherArgs.Event.GetType())
{
- var userXform = xForm.GetComponent(eventArgs.User);
+ return false;
+ }
- 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;
- }
+ 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;
- }
- }
+ #endregion
- return false;
+ #region Cancellation
+ /// <summary>
+ /// Cancels an active DoAfter.
+ /// </summary>
+ public void Cancel(DoAfterId? id, DoAfterComponent? comp = null)
+ {
+ if (id != null)
+ Cancel(id.Value.Uid, id.Value.Index, comp);
}
- public void Cancel(EntityUid entity, DoAfter doAfter, DoAfterComponent? comp = null)
+ /// <summary>
+ /// Cancels an active DoAfter.
+ /// </summary>
+ public void Cancel(EntityUid entity, ushort id, DoAfterComponent? comp = null)
{
if (!Resolve(entity, ref comp, false))
return;
- if (comp.CancelledDoAfters.ContainsKey(doAfter.ID))
+ if (!comp.DoAfters.TryGetValue(id, out var doAfter))
+ {
+ Logger.Error($"Attempted to cancel do after with an invalid id ({id}) on entity {ToPrettyString(entity)}");
return;
+ }
- if (!comp.DoAfters.ContainsKey(doAfter.ID))
+ InternalCancel(doAfter, comp);
+ Dirty(comp);
+ }
+
+ private void InternalCancel(DoAfter doAfter, DoAfterComponent component)
+ {
+ if (doAfter.Cancelled || doAfter.Completed)
return;
- doAfter.Cancelled = true;
+ // Caller is responsible for dirtying the component.
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);
- }
+ RaiseDoAfterEvents(doAfter, component);
}
+ #endregion
+ #region Query
/// <summary>
- /// Send the DoAfter event, used where you don't need any extra data to send.
+ /// Returns the current status of a DoAfter
/// </summary>
- /// <param name="cancelled"></param>
- /// <param name="args"></param>
- private void Send(bool cancelled, DoAfterEventArgs args, byte Id)
+ public DoAfterStatus GetStatus(DoAfterId? id, DoAfterComponent? comp = null)
{
- var ev = new DoAfterEvent(cancelled, args, Id);
-
- RaiseDoAfterEvent(ev, args);
+ if (id != null)
+ return GetStatus(id.Value.Uid, id.Value.Index, comp);
+ else
+ return DoAfterStatus.Invalid;
}
/// <summary>
- /// Send the DoAfter event, used where you need extra data to send
+ /// Returns the current status of a DoAfter
/// </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, byte id)
+ public DoAfterStatus GetStatus(EntityUid entity, ushort id, DoAfterComponent? comp = null)
{
- var ev = new DoAfterEvent<T>(data, cancelled, args, id);
+ if (!Resolve(entity, ref comp, false))
+ return DoAfterStatus.Invalid;
- RaiseDoAfterEvent(ev, args);
- }
+ if (!comp.DoAfters.TryGetValue(id, out var doAfter))
+ return DoAfterStatus.Invalid;
- private void RaiseDoAfterEvent<TEvent>(TEvent ev, DoAfterEventArgs args) where TEvent : notnull
- {
- if (args.RaiseOnUser && Exists(args.User))
- RaiseLocalEvent(args.User, ev, args.Broadcast);
+ if (doAfter.Cancelled)
+ return DoAfterStatus.Cancelled;
- if (args.RaiseOnTarget && args.Target is { } target && Exists(target))
- {
- DebugTools.Assert(!args.RaiseOnUser || args.Target != args.User);
- DebugTools.Assert(!args.RaiseOnUsed || args.Target != args.Used);
- RaiseLocalEvent(target, ev, args.Broadcast);
- }
+ if (GameTiming.CurTime - doAfter.StartTime < doAfter.Args.Delay)
+ return DoAfterStatus.Running;
- if (args.RaiseOnUsed && args.Used is { } used && Exists(used))
- {
- DebugTools.Assert(!args.RaiseOnUser || args.Used != args.User);
- RaiseLocalEvent(used, ev, args.Broadcast);
- }
+ // Theres the chance here that the DoAfter hasn't actually finished yet if the system's update hasn't run yet.
+ // This would also mean the post-DoAfter checks haven't run yet. But whatever, I can't be bothered tracking and
+ // networking whether a do-after has raised its events or not.
+ return DoAfterStatus.Finished;
}
+ #endregion
}
public bool Partial;
#endregion
- public bool BeingPried;
-
#region Sounds
/// <summary>
/// Sound to play when the door opens.
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Timing;
using System.Linq;
+using Content.Shared.DoAfter;
using Content.Shared.Tag;
using Content.Shared.Tools.Components;
using Content.Shared.Verbs;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Physics.Systems;
+using Robust.Shared.Serialization;
namespace Content.Shared.Doors.Systems;
#endregion
protected abstract void PlaySound(EntityUid uid, SoundSpecifier soundSpecifier, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted);
+
+ [Serializable, NetSerializable]
+ protected sealed class DoorPryDoAfterEvent : SimpleDoAfterEvent
+ {
+ }
}
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Dragon;
+
+[Serializable, NetSerializable]
+public sealed class DragonDevourDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
+using Content.Shared.DoAfter;
using Content.Shared.Ensnaring.Components;
using Content.Shared.Movement.Systems;
using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
namespace Content.Shared.Ensnaring;
+[Serializable, NetSerializable]
+public sealed class EnsnareableDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
public abstract class SharedEnsnareableSystem : EntitySystem
{
[Dependency] private readonly MovementSpeedModifierSystem _speedModifier = default!;
Appearance.SetData(uid, EnsnareableVisuals.IsEnsnared, component.IsEnsnared, appearance);
}
- private void MovementSpeedModify(EntityUid uid, EnsnareableComponent component, RefreshMovementSpeedModifiersEvent args)
+ private void MovementSpeedModify(EntityUid uid, EnsnareableComponent component,
+ RefreshMovementSpeedModifiersEvent args)
{
if (!component.IsEnsnared)
return;
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Exchanger;
+
+[Serializable, NetSerializable]
+public sealed class ExchangerDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
--- /dev/null
+using Content.Shared.DoAfter;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Audio;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Fluids;
+
+[Serializable, NetSerializable]
+public sealed class AbsorbantDoAfterEvent : DoAfterEvent
+{
+ [DataField("solution", required: true)]
+ public readonly string TargetSolution = default!;
+
+ [DataField("message", required: true)]
+ public readonly string Message = default!;
+
+ [DataField("sound", required: true)]
+ public readonly SoundSpecifier Sound = default!;
+
+ [DataField("transferAmount", required: true)]
+ public readonly FixedPoint2 TransferAmount;
+
+ private AbsorbantDoAfterEvent()
+ {
+ }
+
+ public AbsorbantDoAfterEvent(string targetSolution, string message, SoundSpecifier sound, FixedPoint2 transferAmount)
+ {
+ TargetSolution = targetSolution;
+ Message = message;
+ Sound = sound;
+ TransferAmount = transferAmount;
+ }
+
+ public override DoAfterEvent Clone() => this;
+}
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Forensics;
+
+[Serializable, NetSerializable]
+public sealed class ForensicScannerDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class ForensicPadDoAfterEvent : DoAfterEvent
+{
+ [DataField("sample", required: true)] public readonly string Sample = default!;
+
+ private ForensicPadDoAfterEvent()
+ {
+ }
+
+ public ForensicPadDoAfterEvent(string sample)
+ {
+ Sample = sample;
+ }
+
+ public override DoAfterEvent Clone() => this;
+}
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Gatherable;
+
+[Serializable, NetSerializable]
+public sealed class GatherableDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Guardian;
+
+[Serializable, NetSerializable]
+public sealed class GuardianCreatorDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
using System.Threading;
using Content.Shared.Containers.ItemSlots;
+using Content.Shared.DoAfter;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
public ItemSlot ImplanterSlot = new();
public bool UiUpdateNeeded;
-
- public CancellationTokenSource? CancelToken;
}
[Serializable, NetSerializable]
using System.Linq;
using Content.Shared.Containers.ItemSlots;
+using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Implants.Components;
using Content.Shared.Popups;
using Robust.Shared.Containers;
using Robust.Shared.Player;
+using Robust.Shared.Serialization;
namespace Content.Shared.Implants;
SubscribeLocalEvent<ImplanterComponent, ComponentInit>(OnImplanterInit);
SubscribeLocalEvent<ImplanterComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
-
}
private void OnImplanterInit(EntityUid uid, ImplanterComponent component, ComponentInit args)
{
var implantName = Identity.Entity(implant, EntityManager);
var targetName = Identity.Entity(target, EntityManager);
- var failedPermanentMessage = Loc.GetString("implanter-draw-failed-permanent", ("implant", implantName), ("target", targetName));
+ var failedPermanentMessage = Loc.GetString("implanter-draw-failed-permanent",
+ ("implant", implantName), ("target", targetName));
_popup.PopupEntity(failedPermanentMessage, target, user);
permanentFound = implantComp.Permanent;
continue;
else if (component.CurrentMode == ImplanterToggleMode.Inject && component.ImplantOnly)
{
_appearance.SetData(component.Owner, ImplanterVisuals.Full, implantFound, appearance);
- _appearance.SetData(component.Owner, ImplanterImplantOnlyVisuals.ImplantOnly, component.ImplantOnly, appearance);
+ _appearance.SetData(component.Owner, ImplanterImplantOnlyVisuals.ImplantOnly, component.ImplantOnly,
+ appearance);
}
else
_appearance.SetData(component.Owner, ImplanterVisuals.Full, implantFound, appearance);
}
}
+
+[Serializable, NetSerializable]
+public sealed class ImplantEvent : SimpleDoAfterEvent
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class DrawEvent : SimpleDoAfterEvent
+{
+}
return;
// all interactions should only happen when in range / unobstructed, so no range check is needed
- //TODO: See why this is firing off multiple times
var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation);
RaiseLocalEvent(target, interactUsingEvent, true);
DoContactInteraction(user, used, interactUsingEvent);
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Internals;
+
+[Serializable, NetSerializable]
+public sealed class InternalsDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
+using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Kitchen.Components;
using Content.Shared.Nutrition.Components;
+using Robust.Shared.Serialization;
namespace Content.Shared.Kitchen;
args.CanDrop = true;
}
}
+
+[Serializable, NetSerializable]
+public sealed class SpikeDoAfterEvent : SimpleDoAfterEvent
+{
+}
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Kitchen;
+
+[Serializable, NetSerializable]
+public sealed class SharpDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Light;
+
+[Serializable, NetSerializable]
+public sealed class PoweredLightDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Magic;
+
+[Serializable, NetSerializable]
+public sealed class SpellbookDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Destructible;
+using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
using Robust.Shared.Timing;
namespace Content.Shared.Mech.EntitySystems;
}
#region State Handling
+
private void OnGetState(EntityUid uid, SharedMechComponent component, ref ComponentGetState args)
{
args.State = new MechComponentState
component.Mech = state.Mech;
}
+
#endregion
private void OnToggleEquipmentAction(EntityUid uid, SharedMechComponent component, MechToggleEquipmentEvent args)
rider.Mech = mech;
Dirty(rider);
- _actions.AddAction(pilot, new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechCycleAction)), mech);
- _actions.AddAction(pilot, new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechUiAction)), mech);
- _actions.AddAction(pilot, new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechEjectAction)), mech);
+ _actions.AddAction(pilot,
+ new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechCycleAction)), mech);
+ _actions.AddAction(pilot, new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechUiAction)),
+ mech);
+ _actions.AddAction(pilot,
+ new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechEjectAction)), mech);
}
private void RemoveUser(EntityUid mech, EntityUid pilot)
/// <param name="toInsert"></param>
/// <param name="component"></param>
/// <param name="equipmentComponent"></param>
- public void InsertEquipment(EntityUid uid, EntityUid toInsert, SharedMechComponent? component = null, MechEquipmentComponent? equipmentComponent = null)
+ public void InsertEquipment(EntityUid uid, EntityUid toInsert, SharedMechComponent? component = null,
+ MechEquipmentComponent? equipmentComponent = null)
{
if (!Resolve(uid, ref component))
return;
/// <param name="component"></param>
/// <param name="equipmentComponent"></param>
/// <param name="forced">Whether or not the removal can be cancelled</param>
- public void RemoveEquipment(EntityUid uid, EntityUid toRemove, SharedMechComponent? component = null, MechEquipmentComponent? equipmentComponent = null, bool forced = false)
+ public void RemoveEquipment(EntityUid uid, EntityUid toRemove, SharedMechComponent? component = null,
+ MechEquipmentComponent? equipmentComponent = null, bool forced = false)
{
if (!Resolve(uid, ref component))
return;
if (attemptev.Cancelled)
return;
}
+
var ev = new MechEquipmentRemovedEvent(uid);
RaiseLocalEvent(toRemove, ref ev);
/// </remarks>
public virtual void UpdateUserInterface(EntityUid uid, SharedMechComponent? component = null)
{
-
}
/// <summary>
args.Cancel();
}
- private void UpdateAppearance(EntityUid uid, SharedMechComponent ? component = null, AppearanceComponent? appearance = null)
+ private void UpdateAppearance(EntityUid uid, SharedMechComponent? component = null,
+ AppearanceComponent? appearance = null)
{
if (!Resolve(uid, ref component, ref appearance, false))
return;
_appearance.SetData(uid, MechVisuals.Broken, component.Broken, appearance);
}
}
+
+/// <summary>
+/// Event raised when the battery is successfully removed from the mech,
+/// on both success and failure
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class RemoveBatteryEvent : SimpleDoAfterEvent
+{
+}
+
+/// <summary>
+/// Event raised when a person removes someone from a mech,
+/// on both success and failure
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class MechExitEvent : SimpleDoAfterEvent
+{
+}
+
+/// <summary>
+/// Event raised when a person enters a mech, on both success and failure
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class MechEntryEvent : SimpleDoAfterEvent
+{
+}
using System.Threading;
+using Content.Shared.DoAfter;
using Content.Shared.Mech.Components;
+using Robust.Shared.Serialization;
namespace Content.Shared.Mech.Equipment.Components;
/// <summary>
/// How long does it take to install this piece of equipment
/// </summary>
- [DataField("installDuration")]
- public float InstallDuration = 5;
+ [DataField("installDuration")] public float InstallDuration = 5;
/// <summary>
/// The mech that the equipment is inside of.
/// </summary>
- [ViewVariables]
- public EntityUid? EquipmentOwner;
+ [ViewVariables] public EntityUid? EquipmentOwner;
}
/// <summary>
public sealed class MechEquipmentInstallCancelled : EntityEventArgs
{
}
+
+[Serializable, NetSerializable]
+public sealed class GrabberDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class InsertEquipmentEvent : SimpleDoAfterEvent
+{
+}
+
[DataField("permaLocked")]
public bool PermaLocked { get; set; }
- public bool IsPrying { get; set; }
-
[Serializable, NetSerializable]
public enum CryoPodVisuals : byte
{
using Content.Shared.Verbs;
using Robust.Shared.Containers;
using Robust.Shared.Player;
+using Robust.Shared.Serialization;
namespace Content.Shared.Medical.Cryogenics;
protected void OnCryoPodPryFinished(EntityUid uid, SharedCryoPodComponent cryoPodComponent, CryoPodPryFinished args)
{
- cryoPodComponent.IsPrying = false;
+ if (args.Cancelled)
+ return;
+
EjectBody(uid, cryoPodComponent);
}
- protected void OnCryoPodPryInterrupted(EntityUid uid, SharedCryoPodComponent cryoPodComponent, CryoPodPryInterrupted args)
+ [Serializable, NetSerializable]
+ public sealed class CryoPodPryFinished : SimpleDoAfterEvent
{
- cryoPodComponent.IsPrying = false;
}
- #region Event records
-
- protected record CryoPodPryFinished;
- protected record CryoPodPryInterrupted;
-
- #endregion
+ [Serializable, NetSerializable]
+ public sealed class CryoPodDragFinished : SimpleDoAfterEvent
+ {
+ }
}
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Medical;
+
+[Serializable, NetSerializable]
+public sealed class HealingDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Medical;
+
+[Serializable, NetSerializable]
+public sealed class ReclaimerDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Medical;
+
+[Serializable, NetSerializable]
+public sealed class StethoscopeDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
+using Content.Shared.DoAfter;
using Robust.Shared.Serialization;
namespace Content.Shared.MedicalScanner
Key
}
}
+
+ [Serializable, NetSerializable]
+ public sealed class HealthAnalyzerDoAfterEvent : SimpleDoAfterEvent
+ {
+ }
}
+using Content.Shared.DoAfter;
using Robust.Shared.Serialization;
namespace Content.Shared.Nuke
public int MaxCodeLength;
public bool AllowArm;
}
+
+ [Serializable, NetSerializable]
+ public sealed class NukeDisarmDoAfterEvent : SimpleDoAfterEvent
+ {
+ }
}
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Nutrition;
+
+/// <summary>
+/// Do after even for food and drink.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class ConsumeDoAfterEvent : DoAfterEvent
+{
+ [DataField("solution", required: true)]
+ public readonly string Solution = default!;
+
+ [DataField("flavorMessage", required: true)]
+ public readonly string FlavorMessage = default!;
+
+ private ConsumeDoAfterEvent()
+ {
+ }
+
+ public ConsumeDoAfterEvent(string solution, string flavorMessage)
+ {
+ Solution = solution;
+ FlavorMessage = flavorMessage;
+ }
+
+ public override DoAfterEvent Clone() => this;
+}
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Power;
+
+[Serializable, NetSerializable]
+public sealed class ApcToolFinishedEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
public Container KeyContainer = default!;
public const string KeyContainerName = "key_slots";
- /// <summary>
- /// Blocks multiple attempts to remove the key
- /// </summary>
- [DataField("removing")]
- public bool Removing;
-
/// <summary>
/// Combined set of radio channels provided by all contained keys.
/// </summary>
using System.Linq;
using Content.Shared.Chat;
+using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
using Robust.Shared.Timing;
namespace Content.Shared.Radio.EntitySystems;
SubscribeLocalEvent<EncryptionKeyHolderComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
SubscribeLocalEvent<EncryptionKeyHolderComponent, EntRemovedFromContainerMessage>(OnContainerModified);
SubscribeLocalEvent<EncryptionKeyHolderComponent, EncryptionRemovalFinishedEvent>(OnKeyRemoval);
- SubscribeLocalEvent<EncryptionKeyHolderComponent, EncryptionRemovalCancelledEvent>(OnKeyCancelled);
- }
-
- private void OnKeyCancelled(EntityUid uid, EncryptionKeyHolderComponent component, EncryptionRemovalCancelledEvent args)
- {
- component.Removing = false;
}
private void OnKeyRemoval(EntityUid uid, EncryptionKeyHolderComponent component, EncryptionRemovalFinishedEvent args)
{
+ if (args.Cancelled)
+ return;
+
var contained = component.KeyContainer.ContainedEntities.ToArray();
_container.EmptyContainer(component.KeyContainer, reparent: false);
foreach (var ent in contained)
// if tool use ever gets predicted this needs changing.
_popup.PopupEntity(Loc.GetString("encryption-keys-all-extracted"), uid, args.User);
_audio.PlayPvs(component.KeyExtractionSound, uid);
- component.Removing = false;
}
public void UpdateChannels(EntityUid uid, EncryptionKeyHolderComponent component)
private void OnInteractUsing(EntityUid uid, EncryptionKeyHolderComponent component, InteractUsingEvent args)
{
- if (!TryComp<ContainerManagerComponent>(uid, out var _) || args.Handled || component.Removing)
+ if ( args.Handled || !TryComp<ContainerManagerComponent>(uid, out var storage))
return;
+
+ args.Handled = true;
+
if (!component.KeysUnlocked)
{
if (_net.IsClient && _timing.IsFirstTimePredicted)
_popup.PopupEntity(Loc.GetString("encryption-keys-are-locked"), uid, args.User);
return;
}
+
if (TryComp<EncryptionKeyComponent>(args.Used, out var key))
{
TryInsertKey(uid, component, args);
return;
}
- if (_net.IsServer)
- {
- //This is honestly the poor mans fix because the InteractUsingEvent fires off 12 times
- component.Removing = true;
- var toolEvData = new ToolEventData(new EncryptionRemovalFinishedEvent(args.User), cancelledEv: new EncryptionRemovalCancelledEvent(), targetEntity: uid);
- if (_tool.UseTool(args.Used, args.User, uid, 1f, new[] { component.KeysExtractionMethod }, toolEvData, toolComponent: tool))
- args.Handled = true;
- }
+ _tool.UseTool(args.Used, args.User, uid, 1f, component.KeysExtractionMethod, new EncryptionRemovalFinishedEvent(), toolComponent: tool);
}
private void OnStartup(EntityUid uid, EncryptionKeyHolderComponent component, ComponentStartup args)
}
}
- public sealed class EncryptionRemovalFinishedEvent : EntityEventArgs
- {
- public EntityUid User;
-
- public EncryptionRemovalFinishedEvent(EntityUid user)
- {
- User = user;
- }
- }
-
- public sealed class EncryptionRemovalCancelledEvent : EntityEventArgs
+ [Serializable, NetSerializable]
+ public sealed class EncryptionRemovalFinishedEvent : SimpleDoAfterEvent
{
-
}
}
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Repairable;
+
+public abstract class SharedRepairableSystem : EntitySystem
+{
+ [Serializable, NetSerializable]
+ protected sealed class RepairFinishedEvent : SimpleDoAfterEvent
+ {
+ }
+}
+
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Resist;
+
+[Serializable, NetSerializable]
+public sealed class EscapeInventoryEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Resist;
+
+[Serializable, NetSerializable]
+public sealed class ResistLockerDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
using Content.Shared.Actions;
+using Content.Shared.DoAfter;
using Robust.Shared.Serialization;
namespace Content.Shared.Revenant;
+[Serializable, NetSerializable]
+public sealed class SoulEvent : SimpleDoAfterEvent
+{
+}
+
public sealed class SoulSearchDoAfterComplete : EntityEventArgs
{
public readonly EntityUid Target;
}
}
-public sealed class SoulSearchDoAfterCancelled : EntityEventArgs { }
+public sealed class SoulSearchDoAfterCancelled : EntityEventArgs
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class HarvestEvent : SimpleDoAfterEvent
+{
+}
public sealed class HarvestDoAfterComplete : EntityEventArgs
{
}
}
-public sealed class HarvestDoAfterCancelled : EntityEventArgs { }
-public sealed class RevenantShopActionEvent : InstantActionEvent { }
-public sealed class RevenantDefileActionEvent : InstantActionEvent { }
-public sealed class RevenantOverloadLightsActionEvent : InstantActionEvent { }
-public sealed class RevenantBlightActionEvent : InstantActionEvent { }
-public sealed class RevenantMalfunctionActionEvent : InstantActionEvent { }
+public sealed class HarvestDoAfterCancelled : EntityEventArgs
+{
+}
+
+public sealed class RevenantShopActionEvent : InstantActionEvent
+{
+}
+
+public sealed class RevenantDefileActionEvent : InstantActionEvent
+{
+}
+
+public sealed class RevenantOverloadLightsActionEvent : InstantActionEvent
+{
+}
+
+public sealed class RevenantBlightActionEvent : InstantActionEvent
+{
+}
+
+public sealed class RevenantMalfunctionActionEvent : InstantActionEvent
+{
+}
+
[NetSerializable, Serializable]
public enum RevenantVisuals : byte
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Spillable;
+
+[Serializable, NetSerializable]
+public sealed class SpillDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Sticky;
+
+[Serializable, NetSerializable]
+public sealed class StickyDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
using System.Threading;
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
namespace Content.Shared.Storage.Components
{
+ [Serializable, NetSerializable]
+ public sealed class DumpableDoAfterEvent : SimpleDoAfterEvent
+ {
+ }
+
/// <summary>
/// Lets you dump this container on the ground using a verb,
/// or when interacting with it on a disposal unit or placeable surface.
/// <summary>
/// How long each item adds to the doafter.
/// </summary>
- [DataField("delayPerItem")]
- public TimeSpan DelayPerItem = TimeSpan.FromSeconds(0.2);
+ [DataField("delayPerItem")] public TimeSpan DelayPerItem = TimeSpan.FromSeconds(0.2);
/// <summary>
/// The multiplier modifier
/// </summary>
- [DataField("multiplier")]
- public float Multiplier = 1.0f;
+ [DataField("multiplier")] public float Multiplier = 1.0f;
}
}
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Storage.EntitySystems;
+
+[Serializable, NetSerializable]
+public sealed class BluespaceLockerDoAfterEvent : SimpleDoAfterEvent
+{
+}
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Storage;
+
+[Serializable, NetSerializable]
+public sealed class AreaPickupDoAfterEvent : DoAfterEvent
+{
+ [DataField("entities", required: true)]
+ public readonly IReadOnlyList<EntityUid> Entities = default!;
+
+ private AreaPickupDoAfterEvent()
+ {
+ }
+
+ public AreaPickupDoAfterEvent(List<EntityUid> entities)
+ {
+ Entities = entities;
+ }
+
+ public override DoAfterEvent Clone() => this;
+}
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Swab;
+
+[Serializable, NetSerializable]
+public sealed class DiseaseSwabDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class BotanySwabDoAfterEvent : SimpleDoAfterEvent
+{
+}
-using Robust.Shared.Audio;
+using Content.Shared.DoAfter;
+using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Teleportation.Components;
[ViewVariables, DataField("secondPortal")]
public EntityUid? SecondPortal = null;
- [DataField("firstPortalPrototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
+ [DataField("firstPortalPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string FirstPortalPrototype = "PortalRed";
- [DataField("secondPortalPrototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
+ [DataField("secondPortalPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string SecondPortalPrototype = "PortalBlue";
- [DataField("newPortalSound")]
- public SoundSpecifier NewPortalSound = new SoundPathSpecifier("/Audio/Machines/high_tech_confirm.ogg")
- {
- Params = AudioParams.Default.WithVolume(-2f)
- };
+ [DataField("newPortalSound")] public SoundSpecifier NewPortalSound =
+ new SoundPathSpecifier("/Audio/Machines/high_tech_confirm.ogg")
+ {
+ Params = AudioParams.Default.WithVolume(-2f)
+ };
[DataField("clearPortalsSound")]
public SoundSpecifier ClearPortalsSound = new SoundPathSpecifier("/Audio/Machines/button.ogg");
/// <summary>
/// Delay for creating the portals in seconds.
/// </summary>
- [DataField("portalCreationDelay")]
- public float PortalCreationDelay = 2.5f;
+ [DataField("portalCreationDelay")] public float PortalCreationDelay = 2.5f;
+}
+[Serializable, NetSerializable]
+public sealed class TeleporterDoAfterEvent : SimpleDoAfterEvent
+{
}
+using Content.Shared.DoAfter;
using Content.Shared.Tools;
using Robust.Shared.Audio;
+using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Toilet
[DataField("toggleSound")]
public SoundSpecifier ToggleSound = new SoundPathSpecifier("/Audio/Effects/toilet_seat_down.ogg");
+ [DataField("lidOpen")]
public bool LidOpen = false;
+
+ [DataField("isSeatUp")]
public bool IsSeatUp = false;
- public bool IsPrying = false;
+ }
+
+ [Serializable, NetSerializable]
+ public sealed class ToiletPryDoAfterEvent : SimpleDoAfterEvent
+ {
}
}
[ByRefEvent]
public struct ToolUserAttemptUseEvent
{
- public EntityUid User;
public EntityUid? Target;
public bool Cancelled = false;
- public ToolUserAttemptUseEvent(EntityUid user, EntityUid? target)
+ public ToolUserAttemptUseEvent(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)
- {
- User = 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;
-public abstract class SharedToolSystem : EntitySystem
+public abstract partial class SharedToolSystem : EntitySystem
{
- [Dependency] private readonly IPrototypeManager _protoMan = default!;
- [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
- [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
-
- public override void Initialize()
+ public void InitializeMultipleTool()
{
SubscribeLocalEvent<MultipleToolComponent, ComponentStartup>(OnMultipleToolStartup);
SubscribeLocalEvent<MultipleToolComponent, ActivateInWorldEvent>(OnMultipleToolActivated);
SubscribeLocalEvent<MultipleToolComponent, ComponentGetState>(OnMultipleToolGetState);
SubscribeLocalEvent<MultipleToolComponent, ComponentHandleState>(OnMultipleToolHandleState);
-
- SubscribeLocalEvent<ToolComponent, DoAfterEvent<ToolEventData>>(OnDoAfter);
-
- SubscribeLocalEvent<ToolDoAfterComplete>(OnDoAfterComplete);
- SubscribeLocalEvent<ToolDoAfterCancelled>(OnDoAfterCancelled);
- }
-
- private void OnDoAfter(EntityUid uid, ToolComponent component, DoAfterEvent<ToolEventData> args)
- {
- if (args.Handled || args.AdditionalData.Ev == null)
- return;
-
- if (args.Cancelled || !ToolFinishUse(uid, args.Args.User, args.AdditionalData.Fuel))
- {
- 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;
- }
-
- return;
- }
-
- if (args.AdditionalData.TargetEntity != null)
- RaiseLocalEvent(args.AdditionalData.TargetEntity.Value, args.AdditionalData.Ev);
- else
- RaiseLocalEvent(args.AdditionalData.Ev);
-
- args.Handled = true;
- }
-
- public bool UseTool(EntityUid tool, EntityUid user, EntityUid? target, float doAfterDelay, IEnumerable<string> toolQualitiesNeeded, ToolEventData toolEventData, float fuel = 0f, ToolComponent? toolComponent = null, Func<bool>? doAfterCheck = null, CancellationTokenSource? cancelToken = null)
- {
- // No logging here, after all that'd mean the caller would need to check if the component is there or not.
- if (!Resolve(tool, ref toolComponent, false))
- 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:cancelToken?.Token ?? default, 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;
- }
- }
}
--- /dev/null
+using Content.Shared.DoAfter;
+using Content.Shared.Tools.Components;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Tools;
+
+public abstract partial class SharedToolSystem : EntitySystem
+{
+ [Dependency] private readonly IPrototypeManager _protoMan = default!;
+ [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+
+ public override void Initialize()
+ {
+ InitializeMultipleTool();
+ SubscribeLocalEvent<ToolComponent, ToolDoAfterEvent>(OnDoAfter);
+ }
+
+ private void OnDoAfter(EntityUid uid, ToolComponent tool, ToolDoAfterEvent args)
+ {
+ PlayToolSound(uid, tool, args.User);
+ var ev = args.WrappedEvent;
+ ev.DoAfter = args.DoAfter;
+
+ if (args.OriginalTarget != null)
+ RaiseLocalEvent(args.OriginalTarget.Value, (object) ev);
+ else
+ RaiseLocalEvent((object) ev);
+ }
+
+ public void PlayToolSound(EntityUid uid, ToolComponent tool, EntityUid? user)
+ {
+ if (tool.UseSound == null)
+ return;
+
+ _audioSystem.PlayPredicted(tool.UseSound, uid, user, tool.UseSound.Params.WithVariation(0.175f).AddVolume(-5f));
+ }
+
+ /// <summary>
+ /// Attempts to use a tool on some entity, which will start a DoAfter. Returns true if an interaction occurred.
+ /// Note that this does not mean the interaction was successful, you need to listen for the DoAfter event.
+ /// </summary>
+ /// <param name="tool">The tool to use</param>
+ /// <param name="user">The entity using the tool</param>
+ /// <param name="target">The entity that the tool is being used on. This is also the entity that will receive the
+ /// event. If null, the event will be broadcast</param>
+ /// <param name="doAfterDelay">The base tool use delay (seconds). This will be modified by the tool's quality</param>
+ /// <param name="toolQualitiesNeeded">The qualities needed for this tool to work.</param>
+ /// <param name="doAfterEv">The event that will be raised when the tool has finished (including cancellation). Event
+ /// will be directed at the tool target.</param>
+ /// <param name="fuel">Amount of fuel that should be taken from the tool.</param>
+ /// <param name="toolComponent">The tool component.</param>
+ /// <returns>Returns true if any interaction takes place.</returns>
+ public bool UseTool(
+ EntityUid tool,
+ EntityUid user,
+ EntityUid? target,
+ float doAfterDelay,
+ IEnumerable<string> toolQualitiesNeeded,
+ DoAfterEvent doAfterEv,
+ float fuel = 0f,
+ ToolComponent? toolComponent = null)
+ {
+ return UseTool(tool,
+ user,
+ target,
+ TimeSpan.FromSeconds(doAfterDelay),
+ toolQualitiesNeeded,
+ doAfterEv,
+ out _,
+ fuel,
+ toolComponent);
+ }
+
+ /// <summary>
+ /// Attempts to use a tool on some entity, which will start a DoAfter. Returns true if an interaction occurred.
+ /// Note that this does not mean the interaction was successful, you need to listen for the DoAfter event.
+ /// </summary>
+ /// <param name="tool">The tool to use</param>
+ /// <param name="user">The entity using the tool</param>
+ /// <param name="target">The entity that the tool is being used on. This is also the entity that will receive the
+ /// event. If null, the event will be broadcast</param>
+ /// <param name="delay">The base tool use delay. This will be modified by the tool's quality</param>
+ /// <param name="toolQualitiesNeeded">The qualities needed for this tool to work.</param>
+ /// <param name="doAfterEv">The event that will be raised when the tool has finished (including cancellation). Event
+ /// will be directed at the tool target.</param>
+ /// <param name="id">The id of the DoAfter that was created. This may be null even if the function returns true in
+ /// the event that this tool-use cancelled an existing DoAfter</param>
+ /// <param name="fuel">Amount of fuel that should be taken from the tool.</param>
+ /// <param name="toolComponent">The tool component.</param>
+ /// <returns>Returns true if any interaction takes place.</returns>
+ public bool UseTool(
+ EntityUid tool,
+ EntityUid user,
+ EntityUid? target,
+ TimeSpan delay,
+ IEnumerable<string> toolQualitiesNeeded,
+ DoAfterEvent doAfterEv,
+ out DoAfterId? id,
+ float fuel = 0f,
+ ToolComponent? toolComponent = null)
+ {
+ id = null;
+ if (!Resolve(tool, ref toolComponent, false))
+ return false;
+
+ if (!CanStartToolUse(tool, user, target, fuel, toolQualitiesNeeded, toolComponent))
+ return false;
+
+ var toolEvent = new ToolDoAfterEvent(fuel, doAfterEv, target);
+ var doAfterArgs = new DoAfterArgs(user, delay / toolComponent.SpeedModifier, toolEvent, tool, target: target, used: tool)
+ {
+ BreakOnDamage = true,
+ BreakOnTargetMove = true,
+ BreakOnUserMove = true,
+ NeedHand = true,
+ AttemptFrequency = fuel <= 0 ? AttemptFrequency.Never : AttemptFrequency.EveryTick
+ };
+
+ _doAfterSystem.TryStartDoAfter(doAfterArgs, out id);
+ return true;
+ }
+
+ /// <summary>
+ /// Attempts to use a tool on some entity, which will start a DoAfter. Returns true if an interaction occurred.
+ /// Note that this does not mean the interaction was successful, you need to listen for the DoAfter event.
+ /// </summary>
+ /// <param name="tool">The tool to use</param>
+ /// <param name="user">The entity using the tool</param>
+ /// <param name="target">The entity that the tool is being used on. This is also the entity that will receive the
+ /// event. If null, the event will be broadcast</param>
+ /// <param name="doAfterDelay">The base tool use delay (seconds). This will be modified by the tool's quality</param>
+ /// <param name="toolQualityNeeded">The quality needed for this tool to work.</param>
+ /// <param name="doAfterEv">The event that will be raised when the tool has finished (including cancellation). Event
+ /// will be directed at the tool target.</param>
+ /// <param name="id">The id of the DoAfter that was created. This may be null even if the function returns true in
+ /// the event that this tool-use cancelled an existing DoAfter</param>
+ /// <param name="fuel">Amount of fuel that should be taken from the tool.</param>
+ /// <param name="toolComponent">The tool component.</param>
+ /// <returns>Returns true if any interaction takes place.</returns>
+ public bool UseTool(
+ EntityUid tool,
+ EntityUid user,
+ EntityUid? target,
+ float doAfterDelay,
+ string toolQualityNeeded,
+ DoAfterEvent doAfterEv,
+ float fuel = 0,
+ ToolComponent? toolComponent = null)
+ {
+ return UseTool(tool,
+ user,
+ target,
+ TimeSpan.FromSeconds(doAfterDelay),
+ new[] { toolQualityNeeded },
+ doAfterEv,
+ out _,
+ fuel,
+ toolComponent);
+ }
+
+ /// <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 CanStartToolUse(EntityUid tool, EntityUid user, EntityUid? target, float fuel, IEnumerable<string> toolQualitiesNeeded, ToolComponent? toolComponent = null)
+ {
+ if (!Resolve(tool, ref toolComponent))
+ return false;
+
+ var ev = new ToolUserAttemptUseEvent(target);
+ RaiseLocalEvent(user, ref ev);
+ if (ev.Cancelled)
+ return false;
+
+ if (!toolComponent.Qualities.ContainsAll(toolQualitiesNeeded))
+ return false;
+
+ var beforeAttempt = new ToolUseAttemptEvent(fuel, user);
+ RaiseLocalEvent(tool, beforeAttempt, false);
+
+ return !beforeAttempt.Cancelled;
+ }
+
+ #region DoAfterEvents
+
+ [Serializable, NetSerializable]
+ protected sealed class ToolDoAfterEvent : DoAfterEvent
+ {
+ [DataField("fuel")]
+ public readonly float Fuel;
+
+ /// <summary>
+ /// Entity that the wrapped do after event will get directed at. If null, event will be broadcast.
+ /// </summary>
+ [DataField("target")]
+ public readonly EntityUid? OriginalTarget;
+
+ [DataField("wrappedEvent")]
+ public readonly DoAfterEvent WrappedEvent = default!;
+
+ private ToolDoAfterEvent()
+ {
+ }
+
+ public ToolDoAfterEvent(float fuel, DoAfterEvent wrappedEvent, EntityUid? originalTarget)
+ {
+ DebugTools.Assert(wrappedEvent.GetType().HasCustomAttribute<NetSerializableAttribute>(), "Tool event is not serializable");
+
+ Fuel = fuel;
+ WrappedEvent = wrappedEvent;
+ OriginalTarget = originalTarget;
+ }
+
+ public override DoAfterEvent Clone()
+ {
+ var evClone = WrappedEvent.Clone();
+
+ // Most DoAfter events are immutable
+ if (evClone == WrappedEvent)
+ return this;
+
+ return new ToolDoAfterEvent(Fuel, evClone, OriginalTarget);
+ }
+ }
+
+ [Serializable, NetSerializable]
+ protected sealed class LatticeCuttingCompleteEvent : DoAfterEvent
+ {
+ [DataField("coordinates", required:true)]
+ public readonly EntityCoordinates Coordinates;
+
+ private LatticeCuttingCompleteEvent()
+ {
+ }
+
+ public LatticeCuttingCompleteEvent(EntityCoordinates coordinates)
+ {
+ Coordinates = coordinates;
+ }
+
+ public override DoAfterEvent Clone() => this;
+ }
+
+ [Serializable, NetSerializable]
+ protected sealed class TilePryingDoAfterEvent : DoAfterEvent
+ {
+ [DataField("coordinates", required:true)]
+ public readonly EntityCoordinates Coordinates;
+
+ private TilePryingDoAfterEvent()
+ {
+ }
+
+ public TilePryingDoAfterEvent(EntityCoordinates coordinates)
+ {
+ Coordinates = coordinates;
+ }
+
+ public override DoAfterEvent Clone() => this;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed class CableCuttingFinishedEvent : SimpleDoAfterEvent
+{
+}
+
+#endregion
+
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Tools.Systems;
+
+/// <summary>
+/// Raised after welding do_after has finished. It doesn't guarantee success,
+/// use <see cref="WeldableChangedEvent"/> to get updated status.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class WeldFinishedEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Udder;
+
+[Serializable, NetSerializable]
+public sealed class MilkingDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
using Content.Shared.Emag.Components;
using Robust.Shared.Prototypes;
using System.Linq;
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
namespace Content.Shared.VendingMachines;
}
}
+[Serializable, NetSerializable]
+public sealed class RestockDoAfterEvent : SimpleDoAfterEvent
+{
+}
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Wieldable;
+
+[Serializable, NetSerializable]
+public sealed class WieldableDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
--- /dev/null
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Wires;
+
+[Serializable, NetSerializable]
+public sealed class WireDoAfterEvent : DoAfterEvent
+{
+ [DataField("action", required: true)]
+ public readonly WiresAction Action;
+
+ [DataField("id", required: true)]
+ public readonly int Id;
+
+ private WireDoAfterEvent()
+ {
+ }
+
+ public WireDoAfterEvent(WiresAction action, int id)
+ {
+ Action = action;
+ Id = id;
+ }
+
+ public override DoAfterEvent Clone() => this;
+}
using System.Diagnostics.CodeAnalysis;
+using Content.Shared.DoAfter;
using JetBrains.Annotations;
using Robust.Shared.Serialization;
namespace Content.Shared.Wires
{
+ [Serializable, NetSerializable]
+ public sealed class WirePanelDoAfterEvent : SimpleDoAfterEvent
+ {
+ }
+
[Serializable, NetSerializable]
public enum WiresVisuals : byte
{
[ViewVariables]
public bool Visible = true;
- /// <summary>
- /// Marks if maintenance panel being open/closed by someone with a screwdriver.
- /// Prevents do after spam.
- /// </summary>
- public bool IsScrewing;
-
[DataField("screwdriverOpenSound")]
public SoundSpecifier ScrewdriverOpenSound = new SoundPathSpecifier("/Audio/Machines/screwdriveropen.ogg");