DoAfter Refactor (#13225)

Co-authored-by: DrSmugleaf <drsmugleaf@gmail.com>
This commit is contained in:
keronshb
2023-02-24 19:01:25 -05:00
committed by GitHub
parent 7a9baa79c2
commit 9ebb452a3c
129 changed files with 2624 additions and 4132 deletions

View File

@@ -1,12 +0,0 @@
using Content.Shared.DoAfter;
namespace Content.Client.DoAfter
{
[RegisterComponent, Access(typeof(DoAfterSystem))]
public sealed class DoAfterComponent : SharedDoAfterComponent
{
public readonly Dictionary<byte, ClientDoAfter> DoAfters = new();
public readonly Dictionary<byte, ClientDoAfter> CancelledDoAfters = new();
}
}

View File

@@ -1,4 +1,4 @@
using Content.Client.Resources;
using Content.Shared.DoAfter;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
@@ -54,11 +54,11 @@ public sealed class DoAfterOverlay : Overlay
var index = 0;
var worldMatrix = Matrix3.CreateTranslation(worldPosition);
foreach (var (_, doAfter) in comp.DoAfters)
foreach (var doAfter in comp.DoAfters.Values)
{
var elapsed = doAfter.Accumulator;
var elapsed = doAfter.Elapsed;
var displayRatio = MathF.Min(1.0f,
elapsed / doAfter.Delay);
(float)elapsed.TotalSeconds / doAfter.Delay);
Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
@@ -94,7 +94,7 @@ public sealed class DoAfterOverlay : Overlay
// if we're cancelled then flick red / off.
if (cancelled)
{
var flash = Math.Floor(doAfter.CancelledAccumulator / flashTime) % 2 == 0;
var flash = Math.Floor((float)doAfter.CancelledElapsed.TotalSeconds / flashTime) % 2 == 0;
color = new Color(1f, 0f, 0f, flash ? 1f : 0f);
}
else

View File

@@ -1,223 +1,182 @@
using Content.Shared.DoAfter;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.DoAfter
namespace Content.Client.DoAfter;
/// <summary>
/// Handles events that need to happen after a certain amount of time where the event could be cancelled by factors
/// such as moving.
/// </summary>
public sealed class DoAfterSystem : SharedDoAfterSystem
{
[Dependency] private readonly IOverlayManager _overlay = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
/// <summary>
/// Handles events that need to happen after a certain amount of time where the event could be cancelled by factors
/// such as moving.
/// We'll use an excess time so stuff like finishing effects can show.
/// </summary>
[UsedImplicitly]
public sealed class DoAfterSystem : EntitySystem
public const float ExcessTime = 0.5f;
public override void Initialize()
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IOverlayManager _overlay = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
base.Initialize();
UpdatesOutsidePrediction = true;
SubscribeNetworkEvent<CancelledDoAfterMessage>(OnCancelledDoAfter);
SubscribeLocalEvent<DoAfterComponent, ComponentHandleState>(OnDoAfterHandleState);
_overlay.AddOverlay(new DoAfterOverlay(EntityManager, _prototype));
}
/// <summary>
/// We'll use an excess time so stuff like finishing effects can show.
/// </summary>
public const float ExcessTime = 0.5f;
public override void Shutdown()
{
base.Shutdown();
_overlay.RemoveOverlay<DoAfterOverlay>();
}
public override void Initialize()
private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent component, ref ComponentHandleState args)
{
if (args.Current is not DoAfterComponentState state)
return;
foreach (var (_, doAfter) in state.DoAfters)
{
base.Initialize();
UpdatesOutsidePrediction = true;
SubscribeNetworkEvent<CancelledDoAfterMessage>(OnCancelledDoAfter);
SubscribeLocalEvent<DoAfterComponent, ComponentHandleState>(OnDoAfterHandleState);
_overlay.AddOverlay(
new DoAfterOverlay(
EntityManager,
_prototype));
if (component.DoAfters.ContainsKey(doAfter.ID))
continue;
component.DoAfters.Add(doAfter.ID, doAfter);
}
}
public override void Shutdown()
private void OnCancelledDoAfter(CancelledDoAfterMessage ev)
{
if (!TryComp<DoAfterComponent>(ev.Uid, out var doAfter))
return;
Cancel(doAfter, ev.ID);
}
/// <summary>
/// Remove a DoAfter without showing a cancellation graphic.
/// </summary>
public void Remove(DoAfterComponent component, Shared.DoAfter.DoAfter doAfter, bool found = false)
{
component.DoAfters.Remove(doAfter.ID);
component.CancelledDoAfters.Remove(doAfter.ID);
}
/// <summary>
/// Mark a DoAfter as cancelled and show a cancellation graphic.
/// </summary>
/// Actual removal is handled by DoAfterEntitySystem.
public void Cancel(DoAfterComponent component, byte id)
{
if (component.CancelledDoAfters.ContainsKey(id))
return;
if (!component.DoAfters.ContainsKey(id))
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>())
{
base.Shutdown();
_overlay.RemoveOverlay<DoAfterOverlay>();
}
var doAfters = comp.DoAfters;
private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent component, ref ComponentHandleState args)
{
if (args.Current is not DoAfterComponentState state)
return;
if (doAfters.Count == 0)
continue;
var toRemove = new RemQueue<ClientDoAfter>();
var userGrid = xform.Coordinates;
var toRemove = new RemQueue<Shared.DoAfter.DoAfter>();
foreach (var (id, doAfter) in component.DoAfters)
// Check cancellations / finishes
foreach (var (id, doAfter) in doAfters)
{
var found = false;
foreach (var clientdoAfter in state.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)
{
if (clientdoAfter.ID == id)
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))
{
found = true;
break;
Cancel(comp, id);
continue;
}
}
if (!found)
if (doAfter.EventArgs.BreakOnTargetMove)
{
toRemove.Add(doAfter);
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(component, doAfter);
Remove(comp, doAfter);
}
foreach (var doAfter in state.DoAfters)
{
if (component.DoAfters.ContainsKey(doAfter.ID))
continue;
// Remove cancelled DoAfters after ExcessTime has elapsed
var toRemoveCancelled = new RemQueue<Shared.DoAfter.DoAfter>();
component.DoAfters.Add(doAfter.ID, doAfter);
foreach (var (_, doAfter) in comp.CancelledDoAfters)
{
var cancelledElapsed = (float)doAfter.CancelledElapsed.TotalSeconds;
if (cancelledElapsed > ExcessTime)
toRemoveCancelled.Add(doAfter);
}
}
private void OnCancelledDoAfter(CancelledDoAfterMessage ev)
{
if (!TryComp<DoAfterComponent>(ev.Uid, out var doAfter))
return;
Cancel(doAfter, ev.ID);
}
/// <summary>
/// Remove a DoAfter without showing a cancellation graphic.
/// </summary>
public void Remove(DoAfterComponent component, ClientDoAfter clientDoAfter)
{
component.DoAfters.Remove(clientDoAfter.ID);
var found = false;
component.CancelledDoAfters.Remove(clientDoAfter.ID);
if (!found)
component.DoAfters.Remove(clientDoAfter.ID);
}
/// <summary>
/// Mark a DoAfter as cancelled and show a cancellation graphic.
/// </summary>
/// Actual removal is handled by DoAfterEntitySystem.
public void Cancel(DoAfterComponent component, byte id)
{
if (component.CancelledDoAfters.ContainsKey(id))
return;
if (!component.DoAfters.ContainsKey(id))
return;
var doAfterMessage = component.DoAfters[id];
doAfterMessage.Cancelled = true;
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>())
foreach (var doAfter in toRemoveCancelled)
{
var doAfters = comp.DoAfters;
if (doAfters.Count == 0)
{
continue;
}
var userGrid = xform.Coordinates;
var toRemove = new RemQueue<ClientDoAfter>();
// 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 ((doAfter.Accumulator + doAfter.CancelledAccumulator) > doAfter.Delay + ExcessTime)
{
toRemove.Add(doAfter);
continue;
}
if (doAfter.Cancelled)
{
doAfter.CancelledAccumulator += frameTime;
continue;
}
doAfter.Accumulator += frameTime;
// Well we finished so don't try to predict cancels.
if (doAfter.Accumulator > 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.BreakOnUserMove)
{
if (!userGrid.InRange(EntityManager, doAfter.UserGrid, doAfter.MovementThreshold))
{
Cancel(comp, id);
continue;
}
}
if (doAfter.BreakOnTargetMove)
{
if (!EntityManager.Deleted(doAfter.Target) &&
!Transform(doAfter.Target.Value).Coordinates.InRange(EntityManager, doAfter.TargetGrid,
doAfter.MovementThreshold))
{
Cancel(comp, id);
continue;
}
}
}
foreach (var doAfter in toRemove)
{
Remove(comp, doAfter);
}
// Remove cancelled DoAfters after ExcessTime has elapsed
var toRemoveCancelled = new List<ClientDoAfter>();
foreach (var (_, doAfter) in comp.CancelledDoAfters)
{
if (doAfter.CancelledAccumulator > ExcessTime)
{
toRemoveCancelled.Add(doAfter);
}
}
foreach (var doAfter in toRemoveCancelled)
{
Remove(comp, doAfter);
}
Remove(comp, doAfter);
}
}
}

View File

@@ -18,8 +18,6 @@ public sealed class CryoPodSystem: SharedCryoPodSystem
SubscribeLocalEvent<CryoPodComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<CryoPodComponent, GetVerbsEvent<AlternativeVerb>>(AddAlternativeVerbs);
SubscribeLocalEvent<CryoPodComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<CryoPodComponent, DoInsertCryoPodEvent>(DoInsertCryoPod);
SubscribeLocalEvent<CryoPodComponent, DoInsertCancelledCryoPodEvent>(DoInsertCancelCryoPod);
SubscribeLocalEvent<CryoPodComponent, CryoPodPryFinished>(OnCryoPodPryFinished);
SubscribeLocalEvent<CryoPodComponent, CryoPodPryInterrupted>(OnCryoPodPryInterrupted);

View File

@@ -1,6 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
using Content.Server.DoAfter;
using Content.Shared.DoAfter;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -21,17 +22,34 @@ namespace Content.IntegrationTests.Tests.DoAfter
- type: DoAfter
";
public sealed class TestDoAfterSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<DoAfterEvent<TestDoAfterData>>(OnTestDoAfterFinishEvent);
}
private void OnTestDoAfterFinishEvent(DoAfterEvent<TestDoAfterData> ev)
{
ev.AdditionalData.Cancelled = ev.Cancelled;
}
}
private sealed class TestDoAfterData
{
public bool Cancelled;
};
[Test]
public async Task TestFinished()
{
Task<DoAfterStatus> task = null;
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes});
var server = pairTracker.Pair.Server;
await server.WaitIdleAsync();
var mapManager = server.ResolveDependency<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem<DoAfterSystem>();
var data = new TestDoAfterData();
// That it finishes successfully
await server.WaitPost(() =>
@@ -39,15 +57,12 @@ namespace Content.IntegrationTests.Tests.DoAfter
var tickTime = 1.0f / IoCManager.Resolve<IGameTiming>().TickRate;
var mob = entityManager.SpawnEntity("Dummy", MapCoordinates.Nullspace);
var cancelToken = new CancellationTokenSource();
var args = new DoAfterEventArgs(mob, tickTime / 2, cancelToken.Token);
task = doAfterSystem.WaitDoAfter(args);
var args = new DoAfterEventArgs(mob, tickTime / 2, cancelToken.Token) { Broadcast = true };
doAfterSystem.DoAfter(args, data);
});
await server.WaitRunTicks(1);
Assert.That(task.Status, Is.EqualTo(TaskStatus.RanToCompletion));
#pragma warning disable RA0004
Assert.That(task.Result == DoAfterStatus.Finished);
#pragma warning restore RA0004
Assert.That(data.Cancelled, Is.False);
await pairTracker.CleanReturnAsync();
}
@@ -55,13 +70,11 @@ namespace Content.IntegrationTests.Tests.DoAfter
[Test]
public async Task TestCancelled()
{
Task<DoAfterStatus> task = null;
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes});
var server = pairTracker.Pair.Server;
var entityManager = server.ResolveDependency<IEntityManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem<DoAfterSystem>();
var data = new TestDoAfterData();
await server.WaitPost(() =>
{
@@ -69,16 +82,13 @@ namespace Content.IntegrationTests.Tests.DoAfter
var mob = entityManager.SpawnEntity("Dummy", MapCoordinates.Nullspace);
var cancelToken = new CancellationTokenSource();
var args = new DoAfterEventArgs(mob, tickTime * 2, cancelToken.Token);
task = doAfterSystem.WaitDoAfter(args);
var args = new DoAfterEventArgs(mob, tickTime * 2, cancelToken.Token) { Broadcast = true };
doAfterSystem.DoAfter(args, data);
cancelToken.Cancel();
});
await server.WaitRunTicks(3);
Assert.That(task.Status, Is.EqualTo(TaskStatus.RanToCompletion));
#pragma warning disable RA0004
Assert.That(task.Result, Is.EqualTo(DoAfterStatus.Cancelled), $"Result was {task.Result}");
#pragma warning restore RA0004
Assert.That(data.Cancelled, Is.False);
await pairTracker.CleanReturnAsync();
}

View File

@@ -4,12 +4,12 @@ using Content.Server.Popups;
using Content.Server.UserInterface;
using Content.Shared.AirlockPainter;
using Content.Shared.AirlockPainter.Prototypes;
using Content.Shared.DoAfter;
using Content.Shared.Database;
using Content.Shared.Doors.Components;
using Content.Shared.Interaction;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Player;
namespace Content.Server.AirlockPainter
@@ -24,6 +24,7 @@ namespace Content.Server.AirlockPainter
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize()
@@ -33,27 +34,26 @@ namespace Content.Server.AirlockPainter
SubscribeLocalEvent<AirlockPainterComponent, AfterInteractEvent>(AfterInteractOn);
SubscribeLocalEvent<AirlockPainterComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<AirlockPainterComponent, AirlockPainterSpritePickedMessage>(OnSpritePicked);
SubscribeLocalEvent<AirlockPainterDoAfterComplete>(OnDoAfterComplete);
SubscribeLocalEvent<AirlockPainterDoAfterCancelled>(OnDoAfterCancelled);
SubscribeLocalEvent<AirlockPainterComponent, DoAfterEvent<AirlockPainterData>>(OnDoAfter);
}
private void OnDoAfterComplete(AirlockPainterDoAfterComplete ev)
private void OnDoAfter(EntityUid uid, AirlockPainterComponent component, DoAfterEvent<AirlockPainterData> args)
{
ev.Component.IsSpraying = false;
if (TryComp<AppearanceComponent>(ev.Target, out var appearance) &&
TryComp(ev.Target, out PaintableAirlockComponent? _))
if (args.Handled || args.Cancelled)
{
SoundSystem.Play(ev.Component.SpraySound.GetSound(), Filter.Pvs(ev.UsedTool, entityManager:EntityManager), ev.UsedTool);
_appearance.SetData(ev.Target, DoorVisuals.BaseRSI, ev.Sprite, appearance);
// Log success
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(ev.User):user} painted {ToPrettyString(ev.Target):target}");
component.IsSpraying = false;
return;
}
}
private void OnDoAfterCancelled(AirlockPainterDoAfterCancelled ev)
{
ev.Component.IsSpraying = false;
if (args.Args.Target != null)
{
_audio.Play(component.SpraySound, Filter.Pvs(uid, entityManager:EntityManager), uid, true);
_appearance.SetData(args.Args.Target.Value, DoorVisuals.BaseRSI, args.AdditionalData.Sprite);
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Args.User):user} painted {ToPrettyString(args.Args.Target.Value):target}");
component.IsSpraying = false;
}
args.Handled = true;
}
private void OnActivate(EntityUid uid, AirlockPainterComponent component, ActivateInWorldEvent args)
@@ -87,51 +87,22 @@ namespace Content.Server.AirlockPainter
return;
}
component.IsSpraying = true;
var doAfterEventArgs = new DoAfterEventArgs(args.User, component.SprayTime, default, target)
var airlockPainterData = new AirlockPainterData(sprite);
var doAfterEventArgs = new DoAfterEventArgs(args.User, component.SprayTime, target:target, used:uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
NeedHand = true,
BroadcastFinishedEvent = new AirlockPainterDoAfterComplete(uid, target, sprite, component, args.User),
BroadcastCancelledEvent = new AirlockPainterDoAfterCancelled(component),
};
_doAfterSystem.DoAfter(doAfterEventArgs);
_doAfterSystem.DoAfter(doAfterEventArgs, airlockPainterData);
// Log attempt
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} is painting {ToPrettyString(uid):target} to '{style}' at {Transform(uid).Coordinates:targetlocation}");
}
private sealed class AirlockPainterDoAfterComplete : EntityEventArgs
{
public readonly EntityUid User;
public readonly EntityUid UsedTool;
public readonly EntityUid Target;
public readonly string Sprite;
public readonly AirlockPainterComponent Component;
public AirlockPainterDoAfterComplete(EntityUid usedTool, EntityUid target, string sprite,
AirlockPainterComponent component, EntityUid user)
{
User = user;
UsedTool = usedTool;
Target = target;
Sprite = sprite;
Component = component;
}
}
private sealed class AirlockPainterDoAfterCancelled : EntityEventArgs
{
public readonly AirlockPainterComponent Component;
public AirlockPainterDoAfterCancelled(AirlockPainterComponent component)
{
Component = component;
}
}
private void OnSpritePicked(EntityUid uid, AirlockPainterComponent component, AirlockPainterSpritePickedMessage args)
{
component.Index = args.Index;
@@ -147,5 +118,10 @@ namespace Content.Server.AirlockPainter
_userInterfaceSystem.TrySetUiState(uid, AirlockPainterUiKey.Key,
new AirlockPainterBoundUserInterfaceState(component.Index));
}
private record struct AirlockPainterData(string Sprite)
{
public string Sprite = Sprite;
}
}
}

View File

@@ -18,7 +18,7 @@ public sealed class RemoveEnsnare : IAlertClick
if (!entManager.TryGetComponent(ensnare, out EnsnaringComponent? ensnaringComponent))
return;
entManager.EntitySysManager.GetEntitySystem<EnsnareableSystem>().TryFree(player, ensnaringComponent);
entManager.EntitySysManager.GetEntitySystem<EnsnareableSystem>().TryFree(player, ensnare, ensnaringComponent);
}
}
}

View File

@@ -4,11 +4,11 @@ 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.Verbs;
using Robust.Shared.Player;
namespace Content.Server.Animals.Systems
{
@@ -26,10 +26,8 @@ namespace Content.Server.Animals.Systems
base.Initialize();
SubscribeLocalEvent<UdderComponent, GetVerbsEvent<AlternativeVerb>>(AddMilkVerb);
SubscribeLocalEvent<UdderComponent, MilkingFinishedEvent>(OnMilkingFinished);
SubscribeLocalEvent<UdderComponent, MilkingFailEvent>(OnMilkingFailed);
SubscribeLocalEvent<UdderComponent, DoAfterEvent>(OnDoAfter);
}
public override void Update(float frameTime)
{
foreach (var udder in EntityManager.EntityQuery<UdderComponent>(false))
@@ -74,34 +72,41 @@ namespace Content.Server.Animals.Systems
udder.BeingMilked = true;
var doargs = new DoAfterEventArgs(userUid, 5, default, uid)
var doargs = new DoAfterEventArgs(userUid, 5, target:uid, used:containerUid)
{
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
MovementThreshold = 1.0f,
TargetFinishedEvent = new MilkingFinishedEvent(userUid, containerUid),
TargetCancelledEvent = new MilkingFailEvent()
MovementThreshold = 1.0f
};
_doAfterSystem.DoAfter(doargs);
}
private void OnMilkingFinished(EntityUid uid, UdderComponent udder, MilkingFinishedEvent ev)
private void OnDoAfter(EntityUid uid, UdderComponent component, DoAfterEvent args)
{
udder.BeingMilked = false;
if (args.Cancelled)
{
component.BeingMilked = false;
return;
}
if (!_solutionContainerSystem.TryGetSolution(uid, udder.TargetSolutionName, out var solution))
if (args.Handled || args.Args.Used == null)
return;
if (!_solutionContainerSystem.TryGetRefillableSolution(ev.ContainerUid, out var targetSolution))
component.BeingMilked = false;
if (!_solutionContainerSystem.TryGetSolution(uid, component.TargetSolutionName, out var solution))
return;
if (!_solutionContainerSystem.TryGetRefillableSolution(args.Args.Used.Value, out var targetSolution))
return;
var quantity = solution.Volume;
if(quantity == 0)
{
_popupSystem.PopupEntity(Loc.GetString("udder-system-dry"), uid, ev.UserUid);
_popupSystem.PopupEntity(Loc.GetString("udder-system-dry"), uid, args.Args.User);
return;
}
@@ -109,15 +114,12 @@ namespace Content.Server.Animals.Systems
quantity = targetSolution.AvailableVolume;
var split = _solutionContainerSystem.SplitSolution(uid, solution, quantity);
_solutionContainerSystem.TryAddSolution(ev.ContainerUid, targetSolution, split);
_solutionContainerSystem.TryAddSolution(args.Args.Used.Value, targetSolution, split);
_popupSystem.PopupEntity(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(ev.ContainerUid, EntityManager))), uid,
ev.UserUid, PopupType.Medium);
}
_popupSystem.PopupEntity(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), uid,
args.Args.User, PopupType.Medium);
private void OnMilkingFailed(EntityUid uid, UdderComponent component, MilkingFailEvent ev)
{
component.BeingMilked = false;
args.Handled = true;
}
private void AddMilkVerb(EntityUid uid, UdderComponent component, GetVerbsEvent<AlternativeVerb> args)
@@ -138,20 +140,5 @@ namespace Content.Server.Animals.Systems
};
args.Verbs.Add(verb);
}
private sealed class MilkingFinishedEvent : EntityEventArgs
{
public EntityUid UserUid;
public EntityUid ContainerUid;
public MilkingFinishedEvent(EntityUid userUid, EntityUid containerUid)
{
UserUid = userUid;
ContainerUid = containerUid;
}
}
private sealed class MilkingFailEvent : EntityEventArgs
{ }
}
}

View File

@@ -1,7 +1,7 @@
using Content.Server.Anomaly.Components;
using Content.Server.DoAfter;
using Content.Shared.Anomaly;
using Content.Shared.Anomaly.Components;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Robust.Server.GameObjects;
using Robust.Shared.Utility;
@@ -17,8 +17,7 @@ public sealed partial class AnomalySystem
{
SubscribeLocalEvent<AnomalyScannerComponent, BoundUIOpenedEvent>(OnScannerUiOpened);
SubscribeLocalEvent<AnomalyScannerComponent, AfterInteractEvent>(OnScannerAfterInteract);
SubscribeLocalEvent<AnomalyScannerComponent, AnomalyScanFinishedEvent>(OnScannerDoAfterFinished);
SubscribeLocalEvent<AnomalyScannerComponent, AnomalyScanCancelledEvent>(OnScannerDoAfterCancelled);
SubscribeLocalEvent<AnomalyScannerComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<AnomalyShutdownEvent>(OnScannerAnomalyShutdown);
SubscribeLocalEvent<AnomalySeverityChangedEvent>(OnScannerAnomalySeverityChanged);
@@ -73,38 +72,29 @@ public sealed partial class AnomalySystem
private void OnScannerAfterInteract(EntityUid uid, AnomalyScannerComponent component, AfterInteractEvent args)
{
if (component.TokenSource != null)
return;
if (args.Target is not { } target)
return;
if (!HasComp<AnomalyComponent>(target))
return;
component.TokenSource = new();
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ScanDoAfterDuration, component.TokenSource.Token, target, uid)
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ScanDoAfterDuration, target:target, used:uid)
{
DistanceThreshold = 2f,
UsedFinishedEvent = new AnomalyScanFinishedEvent(target, args.User),
UsedCancelledEvent = new AnomalyScanCancelledEvent()
DistanceThreshold = 2f
});
}
private void OnScannerDoAfterFinished(EntityUid uid, AnomalyScannerComponent component, AnomalyScanFinishedEvent args)
private void OnDoAfter(EntityUid uid, AnomalyScannerComponent component, DoAfterEvent args)
{
component.TokenSource = null;
if (args.Cancelled || args.Handled || args.Args.Target == null)
return;
Audio.PlayPvs(component.CompleteSound, uid);
Popup.PopupEntity(Loc.GetString("anomaly-scanner-component-scan-complete"), uid);
UpdateScannerWithNewAnomaly(uid, args.Anomaly, component);
UpdateScannerWithNewAnomaly(uid, args.Args.Target.Value, component);
if (TryComp<ActorComponent>(args.User, out var actor))
_ui.TryOpen(uid, AnomalyScannerUiKey.Key, actor.PlayerSession);
}
if (TryComp<ActorComponent>(args.Args.User, out var actor)) _ui.TryOpen(uid, AnomalyScannerUiKey.Key, actor.PlayerSession);
private void OnScannerDoAfterCancelled(EntityUid uid, AnomalyScannerComponent component, AnomalyScanCancelledEvent args)
{
component.TokenSource = null;
args.Handled = true;
}
public void UpdateScannerUi(EntityUid uid, AnomalyScannerComponent? component = null)

View File

@@ -7,6 +7,7 @@ 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;

View File

@@ -22,28 +22,9 @@ public sealed class AnomalyScannerComponent : Component
[DataField("scanDoAfterDuration")]
public float ScanDoAfterDuration = 5;
public CancellationTokenSource? TokenSource;
/// <summary>
/// The sound plays when the scan finished
/// </summary>
[DataField("completeSound")]
public SoundSpecifier? CompleteSound = new SoundPathSpecifier("/Audio/Items/beep.ogg");
}
public sealed class AnomalyScanFinishedEvent : EntityEventArgs
{
public EntityUid Anomaly;
public EntityUid User;
public AnomalyScanFinishedEvent(EntityUid anomaly, EntityUid user)
{
Anomaly = anomaly;
User = user;
}
}
public sealed class AnomalyScanCancelledEvent : EntityEventArgs
{
}

View File

@@ -16,7 +16,5 @@ namespace Content.Server.Body.Components
[ViewVariables(VVAccess.ReadWrite)]
[DataField("delay")]
public float Delay = 3;
public CancellationTokenSource? CancelToken = null;
}
}

View File

@@ -10,7 +10,7 @@ using Robust.Shared.Prototypes;
using Content.Shared.Verbs;
using Content.Server.Popups;
using Content.Server.DoAfter;
using System.Threading;
using Content.Shared.DoAfter;
namespace Content.Server.Body.Systems;
@@ -33,8 +33,7 @@ public sealed class InternalsSystem : EntitySystem
SubscribeLocalEvent<InternalsComponent, ComponentStartup>(OnInternalsStartup);
SubscribeLocalEvent<InternalsComponent, ComponentShutdown>(OnInternalsShutdown);
SubscribeLocalEvent<InternalsComponent, GetVerbsEvent<InteractionVerb>>(OnGetInteractionVerbs);
SubscribeLocalEvent<InternalsComponent, ToggleOtherInternalsCompleteEvent>(OnToggleOtherInternalsComplete);
SubscribeLocalEvent<InternalsComponent, ToggleOtherInternalsCancelledEvent>(OnToggleOtherInternalCanceled);
SubscribeLocalEvent<InternalsComponent, DoAfterEvent<InternalsData>>(OnDoAfter);
}
private void OnGetInteractionVerbs(EntityUid uid, InternalsComponent component, GetVerbsEvent<InteractionVerb> args)
@@ -59,9 +58,7 @@ public sealed class InternalsSystem : EntitySystem
public void ToggleInternals(EntityUid uid, EntityUid user, bool force, InternalsComponent? internals = null)
{
if (!Resolve(uid, ref internals, false))
{
return;
}
// Toggle off if they're on
if (AreInternalsWorking(internals))
@@ -77,7 +74,7 @@ public sealed class InternalsSystem : EntitySystem
return;
}
var tank = FindBestGasTank(internals);
var tank = FindBestGasTank(uid ,internals);
if (tank == null)
{
@@ -85,24 +82,26 @@ public sealed class InternalsSystem : EntitySystem
return;
}
var isUser = uid == user;
var internalsData = new InternalsData();
if (!force)
{
// Is the target not you? If yes, use a do-after to give them time to respond.
//If no, do a short delay. There's no reason it should be beyond 1 second.
var delay = uid != user ? internals.Delay : 1.0f;
var delay = !isUser ? internals.Delay : 1.0f;
internals.CancelToken?.Cancel();
internals.CancelToken = new CancellationTokenSource();
_doAfter.DoAfter(new DoAfterEventArgs(user, delay, internals.CancelToken.Token, uid)
_doAfter.DoAfter(new DoAfterEventArgs(user, delay, target:uid)
{
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
MovementThreshold = 0.1f,
TargetFinishedEvent = new ToggleOtherInternalsCompleteEvent(user, tank),
TargetCancelledEvent = new ToggleOtherInternalsCancelledEvent(),
});
RaiseOnUser = isUser,
RaiseOnTarget = !isUser
}, internalsData);
return;
}
@@ -110,20 +109,19 @@ public sealed class InternalsSystem : EntitySystem
_gasTank.ConnectToInternals(tank);
}
private void OnToggleOtherInternalsComplete(EntityUid uid, InternalsComponent component, ToggleOtherInternalsCompleteEvent ev)
private void OnDoAfter(EntityUid uid, InternalsComponent component, DoAfterEvent<InternalsData> args)
{
component.CancelToken = null;
ToggleInternals(uid, ev.User, true, component);
}
if (args.Cancelled || args.Handled)
return;
private static void OnToggleOtherInternalCanceled(EntityUid uid, InternalsComponent component, ToggleOtherInternalsCancelledEvent ev)
{
component.CancelToken = null;
ToggleInternals(uid, args.Args.User, true, component);
args.Handled = true;
}
private void OnInternalsStartup(EntityUid uid, InternalsComponent component, ComponentStartup args)
{
_alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
_alerts.ShowAlert(uid, AlertType.Internals, GetSeverity(component));
}
private void OnInternalsShutdown(EntityUid uid, InternalsComponent component, ComponentShutdown args)
@@ -138,7 +136,7 @@ public sealed class InternalsSystem : EntitySystem
var gasTank = Comp<GasTankComponent>(component.GasTankEntity!.Value);
args.Gas = _gasTank.RemoveAirVolume(gasTank, Atmospherics.BreathVolume);
// TODO: Should listen to gas tank updates instead I guess?
_alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
_alerts.ShowAlert(uid, AlertType.Internals, GetSeverity(component));
}
}
public void DisconnectBreathTool(InternalsComponent component)
@@ -168,12 +166,11 @@ public sealed class InternalsSystem : EntitySystem
public void DisconnectTank(InternalsComponent? component)
{
if (component == null) return;
if (component == null)
return;
if (TryComp(component.GasTankEntity, out GasTankComponent? tank))
{
_gasTank.DisconnectFromInternals(tank);
}
component.GasTankEntity = null;
_alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
@@ -185,9 +182,7 @@ public sealed class InternalsSystem : EntitySystem
return false;
if (TryComp(component.GasTankEntity, out GasTankComponent? tank))
{
_gasTank.DisconnectFromInternals(tank);
}
component.GasTankEntity = tankEntity;
_alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
@@ -203,7 +198,8 @@ public sealed class InternalsSystem : EntitySystem
private short GetSeverity(InternalsComponent component)
{
if (component.BreathToolEntity == null || !AreInternalsWorking(component)) return 2;
if (component.BreathToolEntity == null || !AreInternalsWorking(component))
return 2;
// If pressure in the tank is below low pressure threshhold, flash warning on internals UI
if (TryComp<GasTankComponent>(component.GasTankEntity, out var gasTank) && gasTank.IsLowPressure)
@@ -212,7 +208,7 @@ public sealed class InternalsSystem : EntitySystem
return 1;
}
public GasTankComponent? FindBestGasTank(InternalsComponent component)
public GasTankComponent? FindBestGasTank(EntityUid internalsOwner, InternalsComponent component)
{
// Prioritise
// 1. back equipped tanks
@@ -222,14 +218,14 @@ public sealed class InternalsSystem : EntitySystem
InventoryComponent? inventory = null;
ContainerManagerComponent? containerManager = null;
if (_inventory.TryGetSlotEntity(component.Owner, "back", out var backEntity, inventory, containerManager) &&
if (_inventory.TryGetSlotEntity(internalsOwner, "back", out var backEntity, inventory, containerManager) &&
TryComp<GasTankComponent>(backEntity, out var backGasTank) &&
_gasTank.CanConnectToInternals(backGasTank))
{
return backGasTank;
}
if (_inventory.TryGetSlotEntity(component.Owner, "suitstorage", out var entity, inventory, containerManager) &&
if (_inventory.TryGetSlotEntity(internalsOwner, "suitstorage", out var entity, inventory, containerManager) &&
TryComp<GasTankComponent>(entity, out var gasTank) &&
_gasTank.CanConnectToInternals(gasTank))
{
@@ -238,12 +234,10 @@ public sealed class InternalsSystem : EntitySystem
var tanks = new List<GasTankComponent>();
foreach (var hand in _hands.EnumerateHands(component.Owner))
foreach (var hand in _hands.EnumerateHands(internalsOwner))
{
if (TryComp(hand.HeldEntity, out gasTank) && _gasTank.CanConnectToInternals(gasTank))
{
tanks.Add(gasTank);
}
}
if (tanks.Count > 0)
@@ -252,16 +246,14 @@ public sealed class InternalsSystem : EntitySystem
return tanks[0];
}
if (Resolve(component.Owner, ref inventory, false))
if (Resolve(internalsOwner, ref inventory, false))
{
var enumerator = new InventorySystem.ContainerSlotEnumerator(component.Owner, inventory.TemplateId, _protoManager, _inventory, SlotFlags.POCKET | SlotFlags.BELT);
var enumerator = new InventorySystem.ContainerSlotEnumerator(internalsOwner, inventory.TemplateId, _protoManager, _inventory, SlotFlags.POCKET | SlotFlags.BELT);
while (enumerator.MoveNext(out var container))
{
if (TryComp(container.ContainedEntity, out gasTank) && _gasTank.CanConnectToInternals(gasTank))
{
tanks.Add(gasTank);
}
}
if (tanks.Count > 0)
@@ -273,11 +265,9 @@ public sealed class InternalsSystem : EntitySystem
return null;
}
private readonly record struct ToggleOtherInternalsCompleteEvent(EntityUid User, GasTankComponent Tank)
{
public readonly EntityUid User = User;
public readonly GasTankComponent Tank = Tank;
}
private readonly record struct ToggleOtherInternalsCancelledEvent;
private record struct InternalsData
{
}
}

View File

@@ -11,11 +11,6 @@ namespace Content.Server.Botany
[DataField("swabDelay")]
public float SwabDelay = 2f;
/// <summary>
/// Token for interrupting swabbing do after.
/// </summary>
public CancellationTokenSource? CancelToken;
/// <summary>
/// SeedData from the first plant that got swabbed.
/// </summary>

View File

@@ -1,142 +1,84 @@
using System.Threading;
using Content.Server.Botany.Components;
using Content.Server.DoAfter;
using Content.Server.Hands.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Popups;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Inventory;
using Content.Shared.Tools.Components;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Botany.Systems
namespace Content.Server.Botany.Systems;
public sealed class BotanySwabSystem : EntitySystem
{
public sealed class BotanySwabSystem : EntitySystem
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly MutationSystem _mutationSystem = default!;
public override void Initialize()
{
[Dependency] private readonly DoAfterSystem _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<DoAfterEvent>(OnDoAfter);
}
public override void Initialize()
/// <summary>
/// This handles swab examination text
/// so you can tell if they are used or not.
/// </summary>
private void OnExamined(EntityUid uid, BotanySwabComponent swab, ExaminedEvent args)
{
if (args.IsInDetailsRange)
{
base.Initialize();
SubscribeLocalEvent<BotanySwabComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<BotanySwabComponent, ExaminedEvent>(OnExamined);
// Private Events
SubscribeLocalEvent<TargetSwabSuccessfulEvent>(OnTargetSwabSuccessful);
SubscribeLocalEvent<SwabCancelledEvent>(OnSwabCancelled);
}
/// <summary>
/// Handles swabbing a plant.
/// </summary>
private void OnAfterInteract(EntityUid uid, BotanySwabComponent swab, AfterInteractEvent args)
{
if (swab.CancelToken != null)
{
swab.CancelToken.Cancel();
swab.CancelToken = null;
return;
}
if (args.Target == null || !args.CanReach)
return;
if (!TryComp<PlantHolderComponent>(args.Target, out var plant))
return;
swab.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, swab.CancelToken.Token, target: args.Target)
{
BroadcastFinishedEvent = new TargetSwabSuccessfulEvent(args.User, args.Target, swab, plant),
BroadcastCancelledEvent = new SwabCancelledEvent(swab),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
NeedHand = true
});
}
/// <summary>
/// This handles swab examination text
/// so you can tell if they are used or not.
/// </summary>
private void OnExamined(EntityUid uid, BotanySwabComponent swab, ExaminedEvent args)
{
if (args.IsInDetailsRange)
{
if (swab.SeedData != null)
args.PushMarkup(Loc.GetString("swab-used"));
else
args.PushMarkup(Loc.GetString("swab-unused"));
}
}
/// <summary>
/// Save seed data or cross-pollenate.
/// </summary>
private void OnTargetSwabSuccessful(TargetSwabSuccessfulEvent args)
{
if (args.Target == null)
return;
if (args.Swab.SeedData == null)
{
// Pick up pollen
args.Swab.SeedData = args.Plant.Seed;
_popupSystem.PopupEntity(Loc.GetString("botany-swab-from"), args.Target.Value, args.User);
}
if (swab.SeedData != null)
args.PushMarkup(Loc.GetString("swab-used"));
else
{
var old = args.Plant.Seed; // Save old plant pollen
if (old == null)
return;
args.Plant.Seed = _mutationSystem.Cross(args.Swab.SeedData, old); // Cross-pollenate
args.Swab.SeedData = old; // Transfer old plant pollen to swab
_popupSystem.PopupEntity(Loc.GetString("botany-swab-to"), args.Target.Value, args.User);
}
if (args.Swab.CancelToken != null)
{
args.Swab.CancelToken.Cancel();
args.Swab.CancelToken = null;
}
args.PushMarkup(Loc.GetString("swab-unused"));
}
}
private static void OnSwabCancelled(SwabCancelledEvent args)
/// <summary>
/// Handles swabbing a plant.
/// </summary>
private void OnAfterInteract(EntityUid uid, BotanySwabComponent swab, AfterInteractEvent args)
{
if (args.Target == null || !args.CanReach)
return;
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, target: args.Target, used: uid)
{
args.Swab.CancelToken = null;
}
Broadcast = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
NeedHand = true
});
}
private sealed class SwabCancelledEvent : EntityEventArgs
/// <summary>
/// Save seed data or cross-pollenate.
/// </summary>
private void OnDoAfter(DoAfterEvent args)
{
if (args.Cancelled || args.Handled || !TryComp<PlantHolderComponent>(args.Args.Target, out var plant) || !TryComp<BotanySwabComponent>(args.Args.Used, out var swab))
return;
if (swab.SeedData == null)
{
public readonly BotanySwabComponent Swab;
public SwabCancelledEvent(BotanySwabComponent swab)
{
Swab = swab;
}
// Pick up pollen
swab.SeedData = plant.Seed;
_popupSystem.PopupEntity(Loc.GetString("botany-swab-from"), args.Args.Target.Value, args.Args.User);
}
private sealed class TargetSwabSuccessfulEvent : EntityEventArgs
else
{
public EntityUid User { get; }
public EntityUid? Target { get; }
public BotanySwabComponent Swab { get; }
public PlantHolderComponent Plant { get; }
public TargetSwabSuccessfulEvent(EntityUid user, EntityUid? target, BotanySwabComponent swab, PlantHolderComponent plant)
{
User = user;
Target = target;
Swab = swab;
Plant = plant;
}
var old = plant.Seed;
if (old == null)
return;
plant.Seed = _mutationSystem.Cross(swab.SeedData, old); // Cross-pollenate
swab.SeedData = old; // Transfer old plant pollen to swab
_popupSystem.PopupEntity(Loc.GetString("botany-swab-to"), args.Args.Target.Value, args.Args.User);
}
args.Handled = true;
}
}

View File

@@ -1,4 +1,3 @@
using System.Threading;
using Content.Shared.Chemistry.Components;
using Content.Shared.FixedPoint;
@@ -63,12 +62,6 @@ namespace Content.Server.Chemistry.Components
[DataField("delay")]
public float Delay = 5;
/// <summary>
/// Token for interrupting a do-after action (e.g., injection another player). If not null, implies
/// component is currently "in use".
/// </summary>
public CancellationTokenSource? CancelToken;
[DataField("toggleState")] private InjectorToggleMode _toggleState;
/// <summary>

View File

@@ -1,19 +1,15 @@
using Content.Server.Body.Components;
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.CombatMode;
using Content.Server.DoAfter;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
using Content.Shared.Hands;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Robust.Shared.GameStates;
using Robust.Shared.Player;
using System.Threading;
using Content.Shared.DoAfter;
using Content.Shared.Mobs.Components;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
@@ -32,14 +28,11 @@ public sealed partial class ChemistrySystem
{
SubscribeLocalEvent<InjectorComponent, GetVerbsEvent<AlternativeVerb>>(AddSetTransferVerbs);
SubscribeLocalEvent<InjectorComponent, SolutionChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<InjectorComponent, HandDeselectedEvent>(OnInjectorDeselected);
SubscribeLocalEvent<InjectorComponent, DoAfterEvent>(OnInjectDoAfter);
SubscribeLocalEvent<InjectorComponent, ComponentStartup>(OnInjectorStartup);
SubscribeLocalEvent<InjectorComponent, UseInHandEvent>(OnInjectorUse);
SubscribeLocalEvent<InjectorComponent, AfterInteractEvent>(OnInjectorAfterInteract);
SubscribeLocalEvent<InjectorComponent, ComponentGetState>(OnInjectorGetState);
SubscribeLocalEvent<InjectionCompleteEvent>(OnInjectionComplete);
SubscribeLocalEvent<InjectionCancelledEvent>(OnInjectionCancelled);
}
private void AddSetTransferVerbs(EntityUid uid, InjectorComponent component, GetVerbsEvent<AlternativeVerb> args)
@@ -63,7 +56,7 @@ public sealed partial class ChemistrySystem
verb.Act = () =>
{
component.TransferAmount = FixedPoint2.New(amount);
args.User.PopupMessage(Loc.GetString("comp-solution-transfer-set-amount", ("amount", amount)));
_popup.PopupEntity(Loc.GetString("comp-solution-transfer-set-amount", ("amount", amount)), args.User, args.User);
};
// we want to sort by size, not alphabetically by the verb text.
@@ -74,38 +67,27 @@ public sealed partial class ChemistrySystem
}
}
private static void OnInjectionCancelled(InjectionCancelledEvent ev)
{
ev.Component.CancelToken = null;
}
private void OnInjectionComplete(InjectionCompleteEvent ev)
{
ev.Component.CancelToken = null;
UseInjector(ev.Target, ev.User, ev.Component);
}
private void UseInjector(EntityUid target, EntityUid user, InjectorComponent component)
private void UseInjector(EntityUid target, EntityUid user, EntityUid injector, InjectorComponent component)
{
// Handle injecting/drawing for solutions
if (component.ToggleState == SharedInjectorComponent.InjectorToggleMode.Inject)
{
if (_solutions.TryGetInjectableSolution(target, out var injectableSolution))
{
TryInject(component, target, injectableSolution, user, false);
TryInject(component, injector, target, injectableSolution, user, false);
}
else if (_solutions.TryGetRefillableSolution(target, out var refillableSolution))
{
TryInject(component, target, refillableSolution, user, true);
TryInject(component, injector, target, refillableSolution, user, true);
}
else if (TryComp<BloodstreamComponent>(target, out var bloodstream))
{
TryInjectIntoBloodstream(component, bloodstream, user);
TryInjectIntoBloodstream(component, injector, target, bloodstream, user);
}
else
{
_popup.PopupEntity(Loc.GetString("injector-component-cannot-transfer-message",
("target", Identity.Entity(target, EntityManager))), component.Owner, user);
("target", Identity.Entity(target, EntityManager))), injector, user);
}
}
else if (component.ToggleState == SharedInjectorComponent.InjectorToggleMode.Draw)
@@ -113,29 +95,23 @@ public sealed partial class ChemistrySystem
// Draw from a bloodstream, if the target has that
if (TryComp<BloodstreamComponent>(target, out var stream))
{
TryDraw(component, target, stream.BloodSolution, user, stream);
TryDraw(component, injector, target, stream.BloodSolution, user, stream);
return;
}
// Draw from an object (food, beaker, etc)
if (_solutions.TryGetDrawableSolution(target, out var drawableSolution))
{
TryDraw(component, target, drawableSolution, user);
TryDraw(component, injector, target, drawableSolution, user);
}
else
{
_popup.PopupEntity(Loc.GetString("injector-component-cannot-draw-message",
("target", Identity.Entity(target, EntityManager))), component.Owner, user);
("target", Identity.Entity(target, EntityManager))), injector, user);
}
}
}
private static void OnInjectorDeselected(EntityUid uid, InjectorComponent component, HandDeselectedEvent args)
{
component.CancelToken?.Cancel();
component.CancelToken = null;
}
private void OnSolutionChange(EntityUid uid, InjectorComponent component, SolutionChangedEvent args)
{
Dirty(component);
@@ -151,38 +127,38 @@ public sealed partial class ChemistrySystem
args.State = new SharedInjectorComponent.InjectorComponentState(currentVolume, maxVolume, component.ToggleState);
}
private void OnInjectDoAfter(EntityUid uid, InjectorComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Args.Target == null)
return;
UseInjector(args.Args.Target.Value, args.Args.User, uid, component);
args.Handled = true;
}
private void OnInjectorAfterInteract(EntityUid uid, InjectorComponent component, AfterInteractEvent args)
{
if (args.Handled || !args.CanReach)
return;
if (component.CancelToken != null)
{
args.Handled = true;
return;
}
//Make sure we have the attacking entity
if (args.Target is not { Valid: true } target ||
!HasComp<SolutionContainerManagerComponent>(uid))
{
if (args.Target is not { Valid: true } target || !HasComp<SolutionContainerManagerComponent>(uid))
return;
}
// Is the target a mob? If yes, use a do-after to give them time to respond.
if (HasComp<MobStateComponent>(target) ||
HasComp<BloodstreamComponent>(target))
if (HasComp<MobStateComponent>(target) || HasComp<BloodstreamComponent>(target))
{
// Are use using an injector capible of targeting a mob?
if (component.IgnoreMobs)
return;
InjectDoAfter(component, args.User, target);
InjectDoAfter(component, args.User, target, uid);
args.Handled = true;
return;
}
UseInjector(target, args.User, component);
UseInjector(target, args.User, uid, component);
args.Handled = true;
}
@@ -197,14 +173,14 @@ public sealed partial class ChemistrySystem
if (args.Handled)
return;
Toggle(component, args.User);
Toggle(component, args.User, uid);
args.Handled = true;
}
/// <summary>
/// Toggle between draw/inject state if applicable
/// </summary>
private void Toggle(InjectorComponent component, EntityUid user)
private void Toggle(InjectorComponent component, EntityUid user, EntityUid injector)
{
if (component.InjectOnly)
{
@@ -226,18 +202,18 @@ public sealed partial class ChemistrySystem
throw new ArgumentOutOfRangeException();
}
_popup.PopupEntity(Loc.GetString(msg), component.Owner, user);
_popup.PopupEntity(Loc.GetString(msg), injector, user);
}
/// <summary>
/// Send informative pop-up messages and wait for a do-after to complete.
/// </summary>
private void InjectDoAfter(InjectorComponent component, EntityUid user, EntityUid target)
private void InjectDoAfter(InjectorComponent component, EntityUid user, EntityUid target, EntityUid injector)
{
// Create a pop-up for the user
_popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, user);
if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution))
if (!_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution))
return;
var actualDelay = MathF.Max(component.Delay, 1f);
@@ -277,62 +253,48 @@ public sealed partial class ChemistrySystem
actualDelay /= 2;
if (component.ToggleState == SharedInjectorComponent.InjectorToggleMode.Inject)
_adminLogger.Add(LogType.Ingestion,
$"{EntityManager.ToPrettyString(user):user} is attempting to inject themselves with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}.");
_adminLogger.Add(LogType.Ingestion, $"{EntityManager.ToPrettyString(user):user} is attempting to inject themselves with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}.");
}
component.CancelToken = new CancellationTokenSource();
_doAfter.DoAfter(new DoAfterEventArgs(user, actualDelay, component.CancelToken.Token, target)
_doAfter.DoAfter(new DoAfterEventArgs(user, actualDelay, target:target, used:injector)
{
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
MovementThreshold = 0.1f,
BroadcastFinishedEvent = new InjectionCompleteEvent()
{
Component = component,
User = user,
Target = target,
},
BroadcastCancelledEvent = new InjectionCancelledEvent()
{
Component = component,
}
MovementThreshold = 0.1f
});
}
private void TryInjectIntoBloodstream(InjectorComponent component, BloodstreamComponent targetBloodstream, EntityUid user)
private void TryInjectIntoBloodstream(InjectorComponent component, EntityUid injector, EntityUid target, BloodstreamComponent targetBloodstream, EntityUid user)
{
// Get transfer amount. May be smaller than _transferAmount if not enough room
var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetBloodstream.ChemicalSolution.AvailableVolume);
if (realTransferAmount <= 0)
{
_popup.PopupEntity(Loc.GetString("injector-component-cannot-inject-message", ("target", Identity.Entity(targetBloodstream.Owner, EntityManager))),
component.Owner, user);
_popup.PopupEntity(Loc.GetString("injector-component-cannot-inject-message", ("target", Identity.Entity(target, EntityManager))), injector, user);
return;
}
// Move units from attackSolution to targetSolution
var removedSolution = _solutions.SplitSolution(user, targetBloodstream.ChemicalSolution, realTransferAmount);
_blood.TryAddToChemicals((targetBloodstream).Owner, removedSolution, targetBloodstream);
_blood.TryAddToChemicals(target, removedSolution, targetBloodstream);
removedSolution.DoEntityReaction(targetBloodstream.Owner, ReactionMethod.Injection);
_reactiveSystem.DoEntityReaction(target, removedSolution, ReactionMethod.Injection);
_popup.PopupEntity(Loc.GetString("injector-component-inject-success-message",
("amount", removedSolution.Volume),
("target", Identity.Entity(targetBloodstream.Owner, EntityManager))), component.Owner, user);
("target", Identity.Entity(target, EntityManager))), injector, user);
Dirty(component);
AfterInject(component);
AfterInject(component, injector);
}
private void TryInject(InjectorComponent component, EntityUid targetEntity, Solution targetSolution, EntityUid user, bool asRefill)
private void TryInject(InjectorComponent component, EntityUid injector, EntityUid targetEntity, Solution targetSolution, EntityUid user, bool asRefill)
{
if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)
if (!_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution)
|| solution.Volume == 0)
{
return;
@@ -344,14 +306,14 @@ public sealed partial class ChemistrySystem
if (realTransferAmount <= 0)
{
_popup.PopupEntity(Loc.GetString("injector-component-target-already-full-message", ("target", Identity.Entity(targetEntity, EntityManager))),
component.Owner, user);
injector, user);
return;
}
// Move units from attackSolution to targetSolution
var removedSolution = _solutions.SplitSolution(component.Owner, solution, realTransferAmount);
var removedSolution = _solutions.SplitSolution(injector, solution, realTransferAmount);
removedSolution.DoEntityReaction(targetEntity, ReactionMethod.Injection);
_reactiveSystem.DoEntityReaction(targetEntity, removedSolution, ReactionMethod.Injection);
if (!asRefill)
{
@@ -364,35 +326,35 @@ public sealed partial class ChemistrySystem
_popup.PopupEntity(Loc.GetString("injector-component-transfer-success-message",
("amount", removedSolution.Volume),
("target", Identity.Entity(targetEntity, EntityManager))), component.Owner, user);
("target", Identity.Entity(targetEntity, EntityManager))), injector, user);
Dirty(component);
AfterInject(component);
AfterInject(component, injector);
}
private void AfterInject(InjectorComponent component)
private void AfterInject(InjectorComponent component, EntityUid injector)
{
// Automatically set syringe to draw after completely draining it.
if (_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)
if (_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution)
&& solution.Volume == 0)
{
component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Draw;
}
}
private void AfterDraw(InjectorComponent component)
private void AfterDraw(InjectorComponent component, EntityUid injector)
{
// Automatically set syringe to inject after completely filling it.
if (_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)
if (_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution)
&& solution.AvailableVolume == 0)
{
component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Inject;
}
}
private void TryDraw(InjectorComponent component, EntityUid targetEntity, Solution targetSolution, EntityUid user, BloodstreamComponent? stream = null)
private void TryDraw(InjectorComponent component, EntityUid injector, EntityUid targetEntity, Solution targetSolution, EntityUid user, BloodstreamComponent? stream = null)
{
if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)
if (!_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution)
|| solution.AvailableVolume == 0)
{
return;
@@ -404,34 +366,34 @@ public sealed partial class ChemistrySystem
if (realTransferAmount <= 0)
{
_popup.PopupEntity(Loc.GetString("injector-component-target-is-empty-message", ("target", Identity.Entity(targetEntity, EntityManager))),
component.Owner, user);
injector, user);
return;
}
// We have some snowflaked behavior for streams.
if (stream != null)
{
DrawFromBlood(user, targetEntity, component, solution, stream, realTransferAmount);
DrawFromBlood(user, injector, targetEntity, component, solution, stream, realTransferAmount);
return;
}
// Move units from attackSolution to targetSolution
var removedSolution = _solutions.Draw(targetEntity, targetSolution, realTransferAmount);
if (!_solutions.TryAddSolution(component.Owner, solution, removedSolution))
if (!_solutions.TryAddSolution(injector, solution, removedSolution))
{
return;
}
_popup.PopupEntity(Loc.GetString("injector-component-draw-success-message",
("amount", removedSolution.Volume),
("target", Identity.Entity(targetEntity, EntityManager))), component.Owner, user);
("target", Identity.Entity(targetEntity, EntityManager))), injector, user);
Dirty(component);
AfterDraw(component);
AfterDraw(component, injector);
}
private void DrawFromBlood(EntityUid user, EntityUid target, InjectorComponent component, Solution injectorSolution, BloodstreamComponent stream, FixedPoint2 transferAmount)
private void DrawFromBlood(EntityUid user, EntityUid injector, EntityUid target, InjectorComponent component, Solution injectorSolution, BloodstreamComponent stream, FixedPoint2 transferAmount)
{
var drawAmount = (float) transferAmount;
var bloodAmount = drawAmount;
@@ -445,25 +407,14 @@ public sealed partial class ChemistrySystem
var bloodTemp = stream.BloodSolution.SplitSolution(bloodAmount);
var chemTemp = stream.ChemicalSolution.SplitSolution(chemAmount);
_solutions.TryAddSolution(component.Owner, injectorSolution, bloodTemp);
_solutions.TryAddSolution(component.Owner, injectorSolution, chemTemp);
_solutions.TryAddSolution(injector, injectorSolution, bloodTemp);
_solutions.TryAddSolution(injector, injectorSolution, chemTemp);
_popup.PopupEntity(Loc.GetString("injector-component-draw-success-message",
("amount", transferAmount),
("target", Identity.Entity(target, EntityManager))), component.Owner, user);
("target", Identity.Entity(target, EntityManager))), injector, user);
Dirty(component);
AfterDraw(component);
}
private sealed class InjectionCompleteEvent : EntityEventArgs
{
public InjectorComponent Component { get; init; } = default!;
public EntityUid User { get; init; }
public EntityUid Target { get; init; }
}
private sealed class InjectionCancelledEvent : EntityEventArgs
{
public InjectorComponent Component { get; init; } = default!;
AfterDraw(component, injector);
}
}

View File

@@ -11,6 +11,7 @@ using Content.Shared.Buckle.Components;
using Content.Shared.Climbing;
using Content.Shared.Climbing.Events;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.GameTicking;
using Content.Shared.IdentityManagement;
@@ -18,8 +19,6 @@ using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.GameStates;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
@@ -34,7 +33,6 @@ namespace Content.Server.Climbing;
[UsedImplicitly]
public sealed class ClimbSystem : SharedClimbSystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
@@ -43,7 +41,6 @@ public sealed class ClimbSystem : SharedClimbSystem
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly InteractionSystem _interactionSystem = default!;
[Dependency] private readonly StunSystem _stunSystem = default!;
[Dependency] private readonly AudioSystem _audioSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly BonkSystem _bonkSystem = default!;
@@ -60,7 +57,7 @@ public sealed class ClimbSystem : SharedClimbSystem
SubscribeLocalEvent<ClimbableComponent, GetVerbsEvent<AlternativeVerb>>(AddClimbableVerb);
SubscribeLocalEvent<ClimbableComponent, DragDropTargetEvent>(OnClimbableDragDrop);
SubscribeLocalEvent<ClimbingComponent, ClimbFinishedEvent>(OnClimbFinished);
SubscribeLocalEvent<ClimbingComponent, DoAfterEvent<ClimbExtraEvent>>(OnDoAfter);
SubscribeLocalEvent<ClimbingComponent, EndCollideEvent>(OnClimbEndCollide);
SubscribeLocalEvent<ClimbingComponent, BuckleChangeEvent>(OnBuckleChange);
SubscribeLocalEvent<ClimbingComponent, ComponentGetState>(OnClimbingGetState);
@@ -117,19 +114,29 @@ public sealed class ClimbSystem : SharedClimbSystem
if (_bonkSystem.TryBonk(entityToMove, climbable))
return;
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.ClimbDelay, default, climbable, entityToMove)
var ev = new ClimbExtraEvent();
var args = new DoAfterEventArgs(user, component.ClimbDelay, target: climbable, used: entityToMove)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
UsedFinishedEvent = new ClimbFinishedEvent(user, climbable, entityToMove)
});
RaiseOnUser = user == entityToMove,
RaiseOnTarget = user != entityToMove
};
_doAfterSystem.DoAfter(args, ev);
}
private void OnClimbFinished(EntityUid uid, ClimbingComponent climbing, ClimbFinishedEvent args)
private void OnDoAfter(EntityUid uid, ClimbingComponent component, DoAfterEvent<ClimbExtraEvent> args)
{
Climb(uid, args.User, args.Instigator, args.Climbable, climbing: climbing);
if (args.Handled || args.Cancelled || args.Args.Target == null || args.Args.Used == null)
return;
Climb(uid, args.Args.User, args.Args.Used.Value, args.Args.Target.Value, climbing: component);
args.Handled = true;
}
private void Climb(EntityUid uid, EntityUid user, EntityUid instigator, EntityUid climbable, bool silent = false, ClimbingComponent? climbing = null,
@@ -428,20 +435,11 @@ public sealed class ClimbSystem : SharedClimbSystem
{
_fixtureRemoveQueue.Clear();
}
}
internal sealed class ClimbFinishedEvent : EntityEventArgs
{
public ClimbFinishedEvent(EntityUid user, EntityUid climbable, EntityUid instigator)
private sealed class ClimbExtraEvent : EntityEventArgs
{
User = user;
Climbable = climbable;
Instigator = instigator;
//Honestly this is only here because otherwise this activates on every single doafter on a human
}
public EntityUid User { get; }
public EntityUid Climbable { get; }
public EntityUid Instigator { get; }
}
/// <summary>

View File

@@ -1,19 +1,16 @@
using System.Threading;
using Content.Server.Administration.Logs;
using Content.Server.Coordinates.Helpers;
using Content.Server.Popups;
using Content.Server.Pulling;
using Content.Server.Tools;
using Content.Shared.Construction.Components;
using Content.Shared.Construction.EntitySystems;
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.Pulling.Components;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
namespace Content.Server.Construction
{
@@ -22,16 +19,14 @@ namespace Content.Server.Construction
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly ToolSystem _tool = default!;
[Dependency] private readonly SharedToolSystem _tool = default!;
[Dependency] private readonly PullingSystem _pulling = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AnchorableComponent, TryAnchorCompletedEvent>(OnAnchorComplete);
SubscribeLocalEvent<AnchorableComponent, TryAnchorCancelledEvent>(OnAnchorCancelled);
SubscribeLocalEvent<AnchorableComponent, TryUnanchorCompletedEvent>(OnUnanchorComplete);
SubscribeLocalEvent<AnchorableComponent, TryUnanchorCancelledEvent>(OnUnanchorCancelled);
SubscribeLocalEvent<AnchorableComponent, ExaminedEvent>(OnAnchoredExamine);
}
@@ -42,14 +37,8 @@ namespace Content.Server.Construction
args.PushMarkup(Loc.GetString(messageId, ("target", uid)));
}
private void OnUnanchorCancelled(EntityUid uid, AnchorableComponent component, TryUnanchorCancelledEvent args)
{
component.CancelToken = null;
}
private void OnUnanchorComplete(EntityUid uid, AnchorableComponent component, TryUnanchorCompletedEvent args)
{
component.CancelToken = null;
var xform = Transform(uid);
RaiseLocalEvent(uid, new BeforeUnanchoredEvent(args.User, args.Using));
@@ -65,14 +54,8 @@ namespace Content.Server.Construction
);
}
private void OnAnchorCancelled(EntityUid uid, AnchorableComponent component, TryAnchorCancelledEvent args)
{
component.CancelToken = null;
}
private void OnAnchorComplete(EntityUid uid, AnchorableComponent component, TryAnchorCompletedEvent args)
{
component.CancelToken = null;
var xform = Transform(uid);
if (TryComp<PhysicsComponent>(uid, out var anchorBody) &&
!TileFree(xform.Coordinates, anchorBody))
@@ -144,8 +127,7 @@ namespace Content.Server.Construction
/// <returns>true if it is valid, false otherwise</returns>
private bool Valid(EntityUid uid, EntityUid userUid, EntityUid usingUid, bool anchoring, AnchorableComponent? anchorable = null, ToolComponent? usingTool = null)
{
if (!Resolve(uid, ref anchorable) ||
anchorable.CancelToken != null)
if (!Resolve(uid, ref anchorable))
return false;
if (!Resolve(usingUid, ref usingTool))
@@ -194,10 +176,8 @@ namespace Content.Server.Construction
return;
}
anchorable.CancelToken = new CancellationTokenSource();
_tool.UseTool(usingUid, userUid, uid, 0f, anchorable.Delay, usingTool.Qualities,
new TryAnchorCompletedEvent(userUid, usingUid), new TryAnchorCancelledEvent(userUid, usingUid), uid, cancelToken: anchorable.CancelToken.Token);
var toolEvData = new ToolEventData(new TryAnchorCompletedEvent(userUid, usingUid), targetEntity:uid);
_tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, toolEvData);
}
/// <summary>
@@ -209,18 +189,17 @@ namespace Content.Server.Construction
TransformComponent? transform = null,
ToolComponent? usingTool = null)
{
if (!Resolve(uid, ref anchorable, ref transform) ||
anchorable.CancelToken != null)
if (!Resolve(uid, ref anchorable, ref transform))
return;
if (!Resolve(usingUid, ref usingTool)) return;
if (!Resolve(usingUid, ref usingTool))
return;
if (!Valid(uid, userUid, usingUid, false)) return;
if (!Valid(uid, userUid, usingUid, false))
return;
anchorable.CancelToken = new CancellationTokenSource();
_tool.UseTool(usingUid, userUid, uid, 0f, anchorable.Delay, usingTool.Qualities,
new TryUnanchorCompletedEvent(userUid, usingUid), new TryUnanchorCancelledEvent(userUid, usingUid), uid, cancelToken: anchorable.CancelToken.Token);
var toolEvData = new ToolEventData(new TryUnanchorCompletedEvent(userUid, usingUid), targetEntity:uid);
_tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, toolEvData);
}
/// <summary>
@@ -271,12 +250,6 @@ namespace Content.Server.Construction
}
}
private sealed class TryUnanchorCancelledEvent : AnchorEvent
{
public TryUnanchorCancelledEvent(EntityUid userUid, EntityUid usingUid) : base(userUid, usingUid)
{
}
}
private sealed class TryAnchorCompletedEvent : AnchorEvent
{
@@ -284,12 +257,5 @@ namespace Content.Server.Construction
{
}
}
private sealed class TryAnchorCancelledEvent : AnchorEvent
{
public TryAnchorCancelledEvent(EntityUid userUid, EntityUid usingUid) : base(userUid, usingUid)
{
}
}
}
}

View File

@@ -27,6 +27,4 @@ public sealed class PartExchangerComponent : Component
public SoundSpecifier ExchangeSound = new SoundPathSpecifier("/Audio/Items/rped.ogg");
public IPlayingAudioStream? AudioStream;
public CancellationTokenSource? Token;
}

View File

@@ -11,6 +11,7 @@ using Content.Shared.Construction.Prototypes;
using Content.Shared.Construction.Steps;
using Content.Shared.Coordinates;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Inventory;

View File

@@ -1,15 +1,15 @@
using Content.Server.Administration.Logs;
using Content.Server.Construction.Components;
using Content.Server.DoAfter;
using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems;
using Content.Shared.Construction;
using Content.Shared.Construction.EntitySystems;
using Content.Shared.Construction.Steps;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Tools.Components;
using Robust.Shared.Containers;
#if EXCEPTION_TOLERANCE
// ReSharper disable once RedundantUsingDirective
using Robust.Shared.Exceptions;
#endif
@@ -36,6 +36,7 @@ namespace Content.Server.Construction
SubscribeLocalEvent<ConstructionDoAfterCancelled>(OnDoAfterCancelled);
SubscribeLocalEvent<ConstructionComponent, ConstructionDoAfterComplete>(EnqueueEvent);
SubscribeLocalEvent<ConstructionComponent, ConstructionDoAfterCancelled>(EnqueueEvent);
SubscribeLocalEvent<ConstructionComponent, DoAfterEvent<ConstructionData>>(OnDoAfter);
#endregion
@@ -300,20 +301,19 @@ namespace Content.Server.Construction
// If we still haven't completed this step's DoAfter...
if (doAfterState == DoAfterState.None && insertStep.DoAfter > 0)
{
_doAfterSystem.DoAfter(
new DoAfterEventArgs(interactUsing.User, step.DoAfter, default, interactUsing.Target)
{
BreakOnDamage = false,
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = true,
// These events will be broadcast and handled by this very same system, that will
// raise them directed to the target. These events wrap the original event.
var constructionData = new ConstructionData(new ConstructionDoAfterComplete(uid, ev), new ConstructionDoAfterCancelled(uid, ev));
var doAfterEventArgs = new DoAfterEventArgs(interactUsing.User, step.DoAfter, target: interactUsing.Target)
{
BreakOnDamage = false,
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = true
};
// These events will be broadcast and handled by this very same system, that will
// raise them directed to the target. These events wrap the original event.
BroadcastFinishedEvent = new ConstructionDoAfterComplete(uid, ev),
BroadcastCancelledEvent = new ConstructionDoAfterCancelled(uid, ev)
});
_doAfterSystem.DoAfter(doAfterEventArgs, constructionData);
// To properly signal that we're waiting for a DoAfter, we have to set the flag on the component
// and then also return the DoAfter HandleResult.
@@ -375,9 +375,9 @@ namespace Content.Server.Construction
if (doAfterState != DoAfterState.None)
return doAfterState == DoAfterState.Completed ? HandleResult.True : HandleResult.False;
if (!_toolSystem.UseTool(interactUsing.Used, interactUsing.User,
uid, toolInsertStep.Fuel, toolInsertStep.DoAfter, toolInsertStep.Tool,
new ConstructionDoAfterComplete(uid, ev), new ConstructionDoAfterCancelled(uid, ev)))
var toolEvData = new ToolEventData(new ConstructionDoAfterComplete(uid, ev), toolInsertStep.Fuel, new ConstructionDoAfterCancelled(uid, ev));
if(!_toolSystem.UseTool(interactUsing.Used, interactUsing.User, uid, toolInsertStep.DoAfter, new [] {toolInsertStep.Tool}, toolEvData))
return HandleResult.False;
// In the case we're not waiting for a doAfter, then this step is complete!
@@ -546,6 +546,21 @@ namespace Content.Server.Construction
_constructionUpdateQueue.Add(uid);
}
private void OnDoAfter(EntityUid uid, ConstructionComponent component, DoAfterEvent<ConstructionData> args)
{
if (!Exists(args.Args.Target) || args.Handled)
return;
if (args.Cancelled)
{
RaiseLocalEvent(args.Args.Target.Value, args.AdditionalData.CancelEvent);
args.Handled = true;
}
RaiseLocalEvent(args.Args.Target.Value, args.AdditionalData.CompleteEvent);
args.Handled = true;
}
private void OnDoAfterComplete(ConstructionDoAfterComplete ev)
{
// Make extra sure the target entity exists...
@@ -570,6 +585,18 @@ namespace Content.Server.Construction
#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.

View File

@@ -1,8 +1,8 @@
using Content.Server.Construction.Components;
using Content.Server.DoAfter;
using Content.Server.Stack;
using Content.Server.Tools;
using Content.Shared.Construction;
using Content.Shared.Tools;
using JetBrains.Annotations;
using Robust.Server.Containers;
using Robust.Shared.Prototypes;
@@ -22,7 +22,7 @@ namespace Content.Server.Construction
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
[Dependency] private readonly ToolSystem _toolSystem = default!;
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
private const string SawmillName = "Construction";
private ISawmill _sawmill = default!;

View File

@@ -1,15 +1,14 @@
using System.Linq;
using System.Threading;
using Content.Server.Construction.Components;
using Content.Server.DoAfter;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Server.Wires;
using Content.Shared.DoAfter;
using Content.Shared.Construction.Components;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Server.Construction;
@@ -27,16 +26,20 @@ public sealed class PartExchangerSystem : EntitySystem
public override void Initialize()
{
SubscribeLocalEvent<PartExchangerComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<PartExchangerComponent, RpedExchangeFinishedEvent>(OnFinished);
SubscribeLocalEvent<PartExchangerComponent, RpedExchangeCancelledEvent>(OnCancelled);
SubscribeLocalEvent<PartExchangerComponent, DoAfterEvent>(OnDoAfter);
}
private void OnFinished(EntityUid uid, PartExchangerComponent component, RpedExchangeFinishedEvent args)
private void OnDoAfter(EntityUid uid, PartExchangerComponent component, DoAfterEvent args)
{
component.Token = null;
if (args.Cancelled || args.Handled || args.Args.Target == null)
{
component.AudioStream?.Stop();
return;
}
component.AudioStream?.Stop();
if (!TryComp<MachineComponent>(args.Target, out var machine))
if (!TryComp<MachineComponent>(args.Args.Target.Value, out var machine))
return;
if (!TryComp<ServerStorageComponent>(uid, out var storage) || storage.Storage == null)
@@ -59,7 +62,7 @@ public sealed class PartExchangerSystem : EntitySystem
if (TryComp<MachinePartComponent>(ent, out var part))
{
machineParts.Add(part);
_container.RemoveEntity(machine.Owner, ent);
_container.RemoveEntity(args.Args.Target.Value, ent);
}
}
@@ -85,19 +88,12 @@ public sealed class PartExchangerSystem : EntitySystem
_storage.Insert(uid, unused.Owner, null, false);
}
_construction.RefreshParts(machine);
}
private void OnCancelled(EntityUid uid, PartExchangerComponent component, RpedExchangeCancelledEvent args)
{
component.Token = null;
component.AudioStream?.Stop();
args.Handled = true;
}
private void OnAfterInteract(EntityUid uid, PartExchangerComponent component, AfterInteractEvent args)
{
if (component.Token != null)
return;
if (component.DoDistanceCheck && !args.CanReach)
return;
@@ -116,28 +112,11 @@ public sealed class PartExchangerSystem : EntitySystem
component.AudioStream = _audio.PlayPvs(component.ExchangeSound, uid);
component.Token = new CancellationTokenSource();
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ExchangeDuration, component.Token.Token, args.Target, args.Used)
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ExchangeDuration, target:args.Target, used:args.Used)
{
BreakOnDamage = true,
BreakOnStun = true,
BreakOnUserMove = true,
UsedFinishedEvent = new RpedExchangeFinishedEvent(args.Target.Value),
UsedCancelledEvent = new RpedExchangeCancelledEvent()
BreakOnUserMove = true
});
}
}
public sealed class RpedExchangeFinishedEvent : EntityEventArgs
{
public readonly EntityUid Target;
public RpedExchangeFinishedEvent(EntityUid target)
{
Target = target;
}
}
public readonly struct RpedExchangeCancelledEvent
{
}

View File

@@ -1,15 +1,15 @@
using Content.Server.Construction.Components;
using Content.Server.Tools;
using Content.Server.Stack;
using Content.Shared.Interaction;
using Content.Shared.Stacks;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
namespace Content.Server.Construction
{
public sealed class RefiningSystem : EntitySystem
{
[Dependency] private readonly ToolSystem _toolSystem = default!;
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
public override void Initialize()
{
@@ -29,7 +29,9 @@ namespace Content.Server.Construction
component.BeingWelded = true;
if (!await _toolSystem.UseTool(args.Used, args.User, uid, component.RefineFuel, component.RefineTime, component.QualityNeeded))
var toolEvData = new ToolEventData(null);
if (!_toolSystem.UseTool(args.Used, args.User, uid, component.RefineTime, component.QualityNeeded, toolEvData, component.RefineFuel))
{
// failed to veld - abort refine
component.BeingWelded = false;

View File

@@ -18,6 +18,7 @@ using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Content.Server.Recycling.Components;
using Content.Shared.DoAfter;
using Robust.Shared.Map;
namespace Content.Server.Cuffs.Components

View File

@@ -3,6 +3,7 @@ using Content.Server.Administration.Logs;
using Content.Server.DoAfter;
using Content.Shared.Cuffs.Components;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.Popups;
using Content.Shared.Stunnable;
using Robust.Shared.Audio;

View File

@@ -1,4 +1,3 @@
using System.Threading;
using Content.Shared.Disease;
namespace Content.Server.Disease.Components
@@ -20,10 +19,6 @@ namespace Content.Server.Disease.Components
/// </summary>
public bool Used = false;
/// <summary>
/// Token for interrupting swabbing do after.
/// </summary>
public CancellationTokenSource? CancelToken;
/// <summary>
/// The disease prototype currently on the swab
/// </summary>
[ViewVariables]

View File

@@ -18,10 +18,6 @@ namespace Content.Server.Disease.Components
/// If this vaccine has been used
/// </summary>
public bool Used = false;
/// <summary>
/// Token for interrupting injection do after.
/// </summary>
public CancellationTokenSource? CancelToken;
/// <summary>
/// The disease prototype currently on the vaccine

View File

@@ -1,4 +1,3 @@
using System.Threading;
using Content.Server.Disease.Components;
using Content.Shared.Disease;
using Content.Shared.Interaction;
@@ -17,6 +16,7 @@ using Robust.Shared.Audio;
using Robust.Shared.Utility;
using Content.Shared.Tools.Components;
using Content.Server.Station.Systems;
using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Robust.Server.GameObjects;
@@ -47,8 +47,7 @@ namespace Content.Server.Disease
// Private Events
SubscribeLocalEvent<DiseaseDiagnoserComponent, DiseaseMachineFinishedEvent>(OnDiagnoserFinished);
SubscribeLocalEvent<DiseaseVaccineCreatorComponent, DiseaseMachineFinishedEvent>(OnVaccinatorFinished);
SubscribeLocalEvent<TargetSwabSuccessfulEvent>(OnTargetSwabSuccessful);
SubscribeLocalEvent<SwabCancelledEvent>(OnSwabCancelled);
SubscribeLocalEvent<DoAfterEvent>(OnSwabDoAfter);
}
private Queue<EntityUid> AddQueue = new();
@@ -100,18 +99,9 @@ namespace Content.Server.Disease
/// </summary>
private void OnAfterInteract(EntityUid uid, DiseaseSwabComponent swab, AfterInteractEvent args)
{
if (swab.CancelToken != null)
{
swab.CancelToken.Cancel();
swab.CancelToken = null;
return;
}
if (args.Target == null || !args.CanReach)
return;
if (!TryComp<DiseaseCarrierComponent>(args.Target, out var carrier))
return;
if (swab.Used)
{
_popupSystem.PopupEntity(Loc.GetString("swab-already-used"), args.User, args.User);
@@ -126,11 +116,8 @@ namespace Content.Server.Disease
return;
}
swab.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, swab.CancelToken.Token, target: args.Target)
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, target: args.Target, used: uid)
{
BroadcastFinishedEvent = new TargetSwabSuccessfulEvent(args.User, args.Target, swab, carrier),
BroadcastCancelledEvent = new SwabCancelledEvent(swab),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
@@ -309,35 +296,26 @@ namespace Content.Server.Disease
{
UpdateAppearance(uid, args.Powered, false);
}
///
/// Private events
///
/// <summary>
/// Copies a disease prototype to the swab
/// after the doafter completes.
/// </summary>
private void OnTargetSwabSuccessful(TargetSwabSuccessfulEvent args)
private void OnSwabDoAfter(DoAfterEvent args)
{
if (args.Target == null)
if (args.Handled || args.Cancelled || !TryComp<DiseaseCarrierComponent>(args.Args.Target, out var carrier) || !TryComp<DiseaseSwabComponent>(args.Args.Target, out var swab))
return;
args.Swab.Used = true;
_popupSystem.PopupEntity(Loc.GetString("swab-swabbed", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.Target.Value, args.User);
swab.Used = true;
_popupSystem.PopupEntity(Loc.GetString("swab-swabbed", ("target", Identity.Entity(args.Args.Target.Value, EntityManager))), args.Args.Target.Value, args.Args.User);
if (args.Swab.Disease != null || args.Carrier.Diseases.Count == 0)
if (swab.Disease != null || carrier.Diseases.Count == 0)
return;
args.Swab.Disease = _random.Pick(args.Carrier.Diseases);
swab.Disease = _random.Pick(carrier.Diseases);
}
/// <summary>
/// Cancels the swab doafter if needed.
/// </summary>
private static void OnSwabCancelled(SwabCancelledEvent args)
{
args.Swab.CancelToken = null;
}
/// <summary>
/// Prints a diagnostic report with its findings.
@@ -410,38 +388,6 @@ namespace Content.Server.Disease
vaxxComp.Disease = args.Machine.Disease;
}
/// <summary>
/// Cancels the mouth-swabbing doafter
/// </summary>
private sealed class SwabCancelledEvent : EntityEventArgs
{
public readonly DiseaseSwabComponent Swab;
public SwabCancelledEvent(DiseaseSwabComponent swab)
{
Swab = swab;
}
}
/// <summary>
/// Fires if the doafter for swabbing someone's mouth succeeds
/// </summary>
private sealed class TargetSwabSuccessfulEvent : EntityEventArgs
{
public EntityUid User { get; }
public EntityUid? Target { get; }
public DiseaseSwabComponent Swab { get; }
public DiseaseCarrierComponent Carrier { get; }
public TargetSwabSuccessfulEvent(EntityUid user, EntityUid? target, DiseaseSwabComponent swab, DiseaseCarrierComponent carrier)
{
User = user;
Target = target;
Swab = swab;
Carrier = carrier;
}
}
/// <summary>
/// Fires when a disease machine is done
/// with its production delay and ready to

View File

@@ -1,4 +1,3 @@
using System.Threading;
using Content.Server.Body.Systems;
using Content.Server.Chat.Systems;
using Content.Server.Disease.Components;
@@ -9,6 +8,7 @@ using Content.Shared.Clothing.Components;
using Content.Shared.Disease;
using Content.Shared.Disease.Components;
using Content.Shared.Disease.Events;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
@@ -20,7 +20,6 @@ using Content.Shared.Mobs.Systems;
using Content.Shared.Rejuvenate;
using Robust.Shared.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager;
@@ -59,8 +58,7 @@ namespace Content.Server.Disease
// Handling stuff from other systems
SubscribeLocalEvent<DiseaseCarrierComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
// Private events stuff
SubscribeLocalEvent<TargetVaxxSuccessfulEvent>(OnTargetVaxxSuccessful);
SubscribeLocalEvent<VaxxCancelledEvent>(OnVaxxCancelled);
SubscribeLocalEvent<DiseaseVaccineComponent, DoAfterEvent>(OnDoAfter);
}
private Queue<EntityUid> AddQueue = new();
@@ -278,22 +276,7 @@ namespace Content.Server.Disease
/// </summary>
private void OnAfterInteract(EntityUid uid, DiseaseVaccineComponent vaxx, AfterInteractEvent args)
{
if (vaxx.CancelToken != null)
{
vaxx.CancelToken.Cancel();
vaxx.CancelToken = null;
return;
}
if (args.Target == null)
return;
if (!args.CanReach)
return;
if (vaxx.CancelToken != null)
return;
if (!TryComp<DiseaseCarrierComponent>(args.Target, out var carrier))
if (args.Target == null || !args.CanReach)
return;
if (vaxx.Used)
@@ -302,11 +285,8 @@ namespace Content.Server.Disease
return;
}
vaxx.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, vaxx.InjectDelay, vaxx.CancelToken.Token, target: args.Target)
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, vaxx.InjectDelay, target: args.Target, used:uid)
{
BroadcastFinishedEvent = new TargetVaxxSuccessfulEvent(args.User, args.Target, vaxx, carrier),
BroadcastCancelledEvent = new VaxxCancelledEvent(vaxx),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
@@ -350,7 +330,6 @@ namespace Content.Server.Disease
}
}
///
/// Helper functions
///
@@ -490,60 +469,23 @@ namespace Content.Server.Disease
carrier.PastDiseases.Add(disease);
}
///
/// Private Events Stuff
///
/// <summary>
/// Injects the vaccine into the target
/// if the doafter is completed
/// </summary>
private void OnTargetVaxxSuccessful(TargetVaxxSuccessfulEvent args)
private void OnDoAfter(EntityUid uid, DiseaseVaccineComponent component, DoAfterEvent args)
{
if (args.Vaxx.Disease == null)
if (args.Handled || args.Cancelled || !TryComp<DiseaseCarrierComponent>(args.Args.Target, out var carrier) || component.Disease == null)
return;
Vaccinate(args.Carrier, args.Vaxx.Disease);
EntityManager.DeleteEntity(args.Vaxx.Owner);
}
/// <summary>
/// Cancels the vaccine doafter
/// </summary>
private static void OnVaxxCancelled(VaxxCancelledEvent args)
{
args.Vaxx.CancelToken = null;
}
/// These two are standard doafter stuff you can ignore
private sealed class VaxxCancelledEvent : EntityEventArgs
{
public readonly DiseaseVaccineComponent Vaxx;
public VaxxCancelledEvent(DiseaseVaccineComponent vaxx)
{
Vaxx = vaxx;
}
}
private sealed class TargetVaxxSuccessfulEvent : EntityEventArgs
{
public EntityUid User { get; }
public EntityUid? Target { get; }
public DiseaseVaccineComponent Vaxx { get; }
public DiseaseCarrierComponent Carrier { get; }
public TargetVaxxSuccessfulEvent(EntityUid user, EntityUid? target, DiseaseVaccineComponent vaxx, DiseaseCarrierComponent carrier)
{
User = user;
Target = target;
Vaxx = vaxx;
Carrier = carrier;
}
Vaccinate(carrier, component.Disease);
EntityManager.DeleteEntity(uid);
args.Handled = true;
}
}
/// <summary>
/// This event is fired by chems
/// and other brute-force rather than
/// specific cures. It will roll the dice to attempt
/// to cure each disease on the target
/// </summary>
/// <summary>
/// This event is fired by chems
/// and other brute-force rather than
/// specific cures. It will roll the dice to attempt
/// to cure each disease on the target
/// </summary>
public sealed class CureDiseaseAttemptEvent : EntityEventArgs
{
public float CureChance { get; }

View File

@@ -16,6 +16,7 @@ using Content.Shared.Database;
using Content.Shared.Destructible;
using Content.Shared.Disposal;
using Content.Shared.Disposal.Components;
using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
@@ -28,7 +29,6 @@ using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Map.Components;
@@ -78,7 +78,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
SubscribeLocalEvent<DisposalUnitComponent, GetVerbsEvent<Verb>>(AddClimbInsideVerb);
// Units
SubscribeLocalEvent<DoInsertDisposalUnitEvent>(DoInsertDisposalUnit);
SubscribeLocalEvent<DisposalUnitComponent, DoAfterEvent>(OnDoAfter);
//UI
SubscribeLocalEvent<DisposalUnitComponent, SharedDisposalUnitComponent.UiButtonPressedMessage>(OnUiButtonPressed);
@@ -155,28 +155,21 @@ namespace Content.Server.Disposal.Unit.EntitySystems
{
_handsSystem.TryDropIntoContainer(args.User, args.Using.Value, component.Container, checkActionBlocker: false, args.Hands);
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} inserted {ToPrettyString(args.Using.Value)} into {ToPrettyString(uid)}");
AfterInsert(uid, component, args.Using.Value);
AfterInsert(uid, component, args.Using.Value, args.User);
}
};
args.Verbs.Add(insertVerb);
}
private void DoInsertDisposalUnit(DoInsertDisposalUnitEvent ev)
private void OnDoAfter(EntityUid uid, DisposalUnitComponent component, DoAfterEvent args)
{
var toInsert = ev.ToInsert;
if (!TryComp(ev.Unit, out DisposalUnitComponent? unit))
{
if (args.Handled || args.Cancelled || args.Args.Target == null || args.Args.Used == null)
return;
}
if (!unit.Container.Insert(toInsert))
return;
if (ev.User != null)
_adminLogger.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(ev.User.Value):player} inserted {ToPrettyString(toInsert)} into {ToPrettyString(ev.Unit)}");
AfterInsert(ev.Unit, unit, toInsert);
AfterInsert(uid, component, args.Args.Target.Value, args.Args.User);
args.Handled = true;
}
public void DoInsertDisposalUnit(EntityUid uid, EntityUid toInsert, EntityUid user, DisposalUnitComponent? disposal = null)
@@ -188,7 +181,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
return;
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(user):player} inserted {ToPrettyString(toInsert)} into {ToPrettyString(uid)}");
AfterInsert(uid, disposal, toInsert);
AfterInsert(uid, disposal, toInsert, user);
}
public override void Update(float frameTime)
@@ -273,7 +266,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
}
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} inserted {ToPrettyString(args.Used)} into {ToPrettyString(uid)}");
AfterInsert(uid, component, args.Used);
AfterInsert(uid, component, args.Used, args.User);
args.Handled = true;
}
@@ -454,6 +447,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
TryComp(ejectedId, out PhysicsComponent? body))
{
// TODO: We need to use a specific collision method (which sloth hasn't coded yet) for actual bounds overlaps.
// TODO: Come do this sloth :^)
// Check for itemcomp as we won't just block the disposal unit "sleeping" for something it can't collide with anyway.
if (!HasComp<ItemComponent>(ejectedId)
&& _lookup.GetWorldAABB(ejectedId).Intersects(disposalsBounds!.Value))
@@ -485,24 +479,22 @@ namespace Content.Server.Disposal.Unit.EntitySystems
return false;
var delay = userId == toInsertId ? unit.EntryDelay : unit.DraggedEntryDelay;
var ev = new DoInsertDisposalUnitEvent(userId, toInsertId, unitId);
if (delay <= 0 || userId == null)
{
DoInsertDisposalUnit(ev);
AfterInsert(unitId, unit, toInsertId, userId);
return true;
}
// Can't check if our target AND disposals moves currently so we'll just check target.
// if you really want to check if disposals moves then add a predicate.
var doAfterArgs = new DoAfterEventArgs(userId.Value, delay, default, toInsertId)
var doAfterArgs = new DoAfterEventArgs(userId.Value, delay, target:toInsertId, used:unitId)
{
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = false,
BroadcastFinishedEvent = ev
};
_doAfterSystem.DoAfter(doAfterArgs);
@@ -727,8 +719,14 @@ namespace Content.Server.Disposal.Unit.EntitySystems
}, component.AutomaticEngageToken.Token);
}
public void AfterInsert(EntityUid uid, DisposalUnitComponent component, EntityUid inserted)
public void AfterInsert(EntityUid uid, DisposalUnitComponent component, EntityUid inserted, EntityUid? user = null)
{
if (!component.Container.Insert(inserted))
return;
if (user != inserted && user != null)
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(user.Value):player} inserted {ToPrettyString(inserted)} into {ToPrettyString(uid)}");
TryQueueEngage(uid, component);
if (TryComp(inserted, out ActorComponent? actor))

View File

@@ -1,209 +0,0 @@
using System.Threading.Tasks;
using Content.Server.Hands.Components;
using Content.Shared.Stunnable;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Content.Server.DoAfter
{
public sealed class DoAfter
{
public Task<DoAfterStatus> AsTask { get; }
private TaskCompletionSource<DoAfterStatus> Tcs { get; }
public readonly DoAfterEventArgs EventArgs;
public TimeSpan StartTime { get; }
public float Elapsed { get; set; }
public EntityCoordinates UserGrid { get; }
public EntityCoordinates TargetGrid { get; }
#pragma warning disable RA0004
public DoAfterStatus Status => AsTask.IsCompletedSuccessfully ? AsTask.Result : DoAfterStatus.Running;
#pragma warning restore RA0004
// NeedHand
private readonly string? _activeHand;
private readonly EntityUid? _activeItem;
public DoAfter(DoAfterEventArgs eventArgs, IEntityManager entityManager)
{
EventArgs = eventArgs;
StartTime = IoCManager.Resolve<IGameTiming>().CurTime;
if (eventArgs.BreakOnUserMove)
{
UserGrid = entityManager.GetComponent<TransformComponent>(eventArgs.User).Coordinates;
}
if (eventArgs.Target != null && eventArgs.BreakOnTargetMove)
{
// Target should never be null if the bool is set.
TargetGrid = entityManager.GetComponent<TransformComponent>(eventArgs.Target!.Value).Coordinates;
}
// For this we need to stay on the same hand slot and need the same item in that hand slot
// (or if there is no item there we need to keep it free).
if (eventArgs.NeedHand && entityManager.TryGetComponent(eventArgs.User, out HandsComponent? handsComponent))
{
_activeHand = handsComponent.ActiveHand?.Name;
_activeItem = handsComponent.ActiveHandEntity;
}
Tcs = new TaskCompletionSource<DoAfterStatus>();
AsTask = Tcs.Task;
}
public void Cancel()
{
if (Status == DoAfterStatus.Running)
Tcs.SetResult(DoAfterStatus.Cancelled);
}
public void Run(float frameTime, IEntityManager entityManager)
{
switch (Status)
{
case DoAfterStatus.Running:
break;
case DoAfterStatus.Cancelled:
case DoAfterStatus.Finished:
return;
default:
throw new ArgumentOutOfRangeException();
}
Elapsed += frameTime;
if (IsFinished())
{
// Do the final checks here
if (!TryPostCheck())
{
Tcs.SetResult(DoAfterStatus.Cancelled);
}
else
{
Tcs.SetResult(DoAfterStatus.Finished);
}
return;
}
if (IsCancelled(entityManager))
{
Tcs.SetResult(DoAfterStatus.Cancelled);
}
}
private bool IsCancelled(IEntityManager entityManager)
{
if (!entityManager.EntityExists(EventArgs.User) || EventArgs.Target is {} target && !entityManager.EntityExists(target))
{
return true;
}
//https://github.com/tgstation/tgstation/blob/1aa293ea337283a0191140a878eeba319221e5df/code/__HELPERS/mobs.dm
if (EventArgs.CancelToken.IsCancellationRequested)
{
return true;
}
// TODO :Handle inertia in space.
if (EventArgs.BreakOnUserMove && !entityManager.GetComponent<TransformComponent>(EventArgs.User).Coordinates.InRange(
entityManager, UserGrid, EventArgs.MovementThreshold))
{
return true;
}
if (EventArgs.Target != null &&
EventArgs.BreakOnTargetMove &&
!entityManager.GetComponent<TransformComponent>(EventArgs.Target!.Value).Coordinates.InRange(entityManager, TargetGrid, EventArgs.MovementThreshold))
{
return true;
}
if (EventArgs.ExtraCheck != null && !EventArgs.ExtraCheck.Invoke())
{
return true;
}
if (EventArgs.BreakOnStun &&
entityManager.HasComponent<StunnedComponent>(EventArgs.User))
{
return true;
}
if (EventArgs.NeedHand)
{
if (!entityManager.TryGetComponent(EventArgs.User, out HandsComponent? handsComponent))
{
// If we had a hand but no longer have it that's still a paddlin'
if (_activeHand != null)
{
return true;
}
}
else
{
var currentActiveHand = handsComponent.ActiveHand?.Name;
if (_activeHand != currentActiveHand)
{
return true;
}
var currentItem = handsComponent.ActiveHandEntity;
if (_activeItem != currentItem)
{
return true;
}
}
}
if (EventArgs.DistanceThreshold != null)
{
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
TransformComponent? userXform = null;
// Check user distance to target AND used entities.
if (EventArgs.Target != null && !EventArgs.User.Equals(EventArgs.Target))
{
//recalculate Target location in case Target has also moved
var targetCoordinates = xformQuery.GetComponent(EventArgs.Target.Value).Coordinates;
userXform ??= xformQuery.GetComponent(EventArgs.User);
if (!userXform.Coordinates.InRange(entityManager, targetCoordinates, EventArgs.DistanceThreshold.Value))
return true;
}
if (EventArgs.Used != null)
{
var targetCoordinates = xformQuery.GetComponent(EventArgs.Used.Value).Coordinates;
userXform ??= xformQuery.GetComponent(EventArgs.User);
if (!userXform.Coordinates.InRange(entityManager, targetCoordinates, EventArgs.DistanceThreshold.Value))
return true;
}
}
return false;
}
private bool TryPostCheck()
{
return EventArgs.PostCheck?.Invoke() != false;
}
private bool IsFinished()
{
if (Elapsed <= EventArgs.Delay)
{
return false;
}
return true;
}
}
}

View File

@@ -1,20 +0,0 @@
using Content.Shared.DoAfter;
namespace Content.Server.DoAfter
{
[RegisterComponent, Access(typeof(DoAfterSystem))]
public sealed class DoAfterComponent : SharedDoAfterComponent
{
public readonly Dictionary<DoAfter, byte> DoAfters = new();
// So the client knows which one to update (and so we don't send all of the do_afters every time 1 updates)
// we'll just send them the index. Doesn't matter if it wraps around.
public byte RunningIndex;
}
/// <summary>
/// Added to entities that are currently performing any doafters.
/// </summary>
[RegisterComponent]
public sealed class ActiveDoAfterComponent : Component {}
}

View File

@@ -1,144 +0,0 @@
using System.Threading;
using Content.Shared.FixedPoint;
using Robust.Shared.Utility;
namespace Content.Server.DoAfter
{
public sealed class DoAfterEventArgs
{
/// <summary>
/// The entity invoking do_after
/// </summary>
public EntityUid User { get; }
/// <summary>
/// How long does the do_after require to complete
/// </summary>
public float Delay { get; }
/// <summary>
/// Applicable target (if relevant)
/// </summary>
public EntityUid? Target { get; }
/// <summary>
/// Entity used by the User on the Target.
/// </summary>
public EntityUid? Used { get; set; }
/// <summary>
/// Manually cancel the do_after so it no longer runs
/// </summary>
public CancellationToken CancelToken { get; }
// Break the chains
/// <summary>
/// Whether we need to keep our active hand as is (i.e. can't change hand or change item).
/// This also covers requiring the hand to be free (if applicable).
/// </summary>
public bool NeedHand { get; set; }
/// <summary>
/// If do_after stops when the user moves
/// </summary>
public bool BreakOnUserMove { get; set; }
/// <summary>
/// If do_after stops when the target moves (if there is a target)
/// </summary>
public bool BreakOnTargetMove { get; set; }
/// <summary>
/// Threshold for user and target movement
/// </summary>
public float MovementThreshold { get; set; }
public bool BreakOnDamage { get; set; }
/// <summary>
/// Threshold for user damage
/// </summary>
public FixedPoint2 DamageThreshold { get; set; }
public bool BreakOnStun { get; set; }
/// <summary>
/// Threshold for distance user from the used OR target entities.
/// </summary>
public float? DistanceThreshold { get; set; }
/// <summary>
/// Requires a function call once at the end (like InRangeUnobstructed).
/// </summary>
/// <remarks>
/// Anything that needs a pre-check should do it itself so no DoAfterState is ever sent to the client.
/// </remarks>
public Func<bool>? PostCheck { get; set; } = null;
/// <summary>
/// Additional conditions that need to be met. Return false to cancel.
/// </summary>
public Func<bool>? ExtraCheck { get; set; }
/// <summary>
/// Event to be raised directed to the <see cref="User"/> entity when the DoAfter is cancelled.
/// </summary>
public object? UserCancelledEvent { get; set; }
/// <summary>
/// Event to be raised directed to the <see cref="User"/> entity when the DoAfter is finished successfully.
/// </summary>
public object? UserFinishedEvent { get; set; }
/// <summary>
/// Event to be raised directed to the <see cref="Used"/> entity when the DoAfter is cancelled.
/// </summary>
public object? UsedCancelledEvent { get; set; }
/// <summary>
/// Event to be raised directed to the <see cref="Used"/> entity when the DoAfter is finished successfully.
/// </summary>
public object? UsedFinishedEvent { get; set; }
/// <summary>
/// Event to be raised directed to the <see cref="Target"/> entity when the DoAfter is cancelled.
/// </summary>
public object? TargetCancelledEvent { get; set; }
/// <summary>
/// Event to be raised directed to the <see cref="Target"/> entity when the DoAfter is finished successfully.
/// </summary>
public object? TargetFinishedEvent { get; set; }
/// <summary>
/// Event to be broadcast when the DoAfter is cancelled.
/// </summary>
public object? BroadcastCancelledEvent { get; set; }
/// <summary>
/// Event to be broadcast when the DoAfter is finished successfully.
/// </summary>
public object? BroadcastFinishedEvent { get; set; }
public DoAfterEventArgs(
EntityUid user,
float delay,
CancellationToken cancelToken = default,
EntityUid? target = null,
EntityUid? used = null)
{
User = user;
Delay = delay;
CancelToken = cancelToken;
Target = target;
Used = used;
MovementThreshold = 0.1f;
DamageThreshold = 1.0;
if (Target == null)
{
DebugTools.Assert(!BreakOnTargetMove);
BreakOnTargetMove = false;
}
}
}
}

View File

@@ -1,227 +1,11 @@
using System.Linq;
using System.Threading.Tasks;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.Mobs;
using JetBrains.Annotations;
using Robust.Shared.GameStates;
namespace Content.Server.DoAfter
namespace Content.Server.DoAfter;
[UsedImplicitly]
public sealed class DoAfterSystem : SharedDoAfterSystem
{
[UsedImplicitly]
public sealed class DoAfterSystem : EntitySystem
{
// We cache these lists as to not allocate them every update tick...
private readonly Queue<DoAfter> _cancelled = new();
private readonly Queue<DoAfter> _finished = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DoAfterComponent, DamageChangedEvent>(OnDamage);
SubscribeLocalEvent<DoAfterComponent, MobStateChangedEvent>(OnStateChanged);
SubscribeLocalEvent<DoAfterComponent, ComponentGetState>(OnDoAfterGetState);
}
public void Add(DoAfterComponent component, DoAfter doAfter)
{
component.DoAfters.Add(doAfter, component.RunningIndex);
EnsureComp<ActiveDoAfterComponent>(component.Owner);
component.RunningIndex++;
Dirty(component);
}
public void Cancelled(DoAfterComponent component, DoAfter doAfter)
{
if (!component.DoAfters.TryGetValue(doAfter, out var index))
return;
component.DoAfters.Remove(doAfter);
if (component.DoAfters.Count == 0)
{
RemComp<ActiveDoAfterComponent>(component.Owner);
}
RaiseNetworkEvent(new CancelledDoAfterMessage(component.Owner, index));
}
/// <summary>
/// Call when the particular DoAfter is finished.
/// Client should be tracking this independently.
/// </summary>
public void Finished(DoAfterComponent component, DoAfter doAfter)
{
if (!component.DoAfters.ContainsKey(doAfter))
return;
component.DoAfters.Remove(doAfter);
if (component.DoAfters.Count == 0)
{
RemComp<ActiveDoAfterComponent>(component.Owner);
}
}
private void OnDoAfterGetState(EntityUid uid, DoAfterComponent component, ref ComponentGetState args)
{
var toAdd = new List<ClientDoAfter>(component.DoAfters.Count);
foreach (var (doAfter, _) in component.DoAfters)
{
// THE ALMIGHTY PYRAMID
var clientDoAfter = new ClientDoAfter(
component.DoAfters[doAfter],
doAfter.UserGrid,
doAfter.TargetGrid,
doAfter.StartTime,
doAfter.EventArgs.Delay,
doAfter.EventArgs.BreakOnUserMove,
doAfter.EventArgs.BreakOnTargetMove,
doAfter.EventArgs.MovementThreshold,
doAfter.EventArgs.DamageThreshold,
doAfter.EventArgs.Target);
toAdd.Add(clientDoAfter);
}
args.State = new DoAfterComponentState(toAdd);
}
private void OnStateChanged(EntityUid uid, DoAfterComponent component, MobStateChangedEvent args)
{
if (args.NewMobState == MobState.Alive)
return;
foreach (var (doAfter, _) in component.DoAfters)
{
doAfter.Cancel();
}
}
/// <summary>
/// Cancels DoAfter if it breaks on damage and it meets the threshold
/// </summary>
/// <param name="_">
/// The EntityUID of the user
/// </param>
/// <param name="component"></param>
/// <param name="args"></param>
public void OnDamage(EntityUid _, DoAfterComponent component, DamageChangedEvent args)
{
if (!args.InterruptsDoAfters || !args.DamageIncreased || args.DamageDelta == null)
return;
foreach (var (doAfter, _) in component.DoAfters)
{
if (doAfter.EventArgs.BreakOnDamage && args.DamageDelta?.Total.Float() > doAfter.EventArgs.DamageThreshold)
{
doAfter.Cancel();
}
}
}
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var (_, comp) in EntityManager.EntityQuery<ActiveDoAfterComponent, DoAfterComponent>())
{
foreach (var (doAfter, _) in comp.DoAfters.ToArray())
{
doAfter.Run(frameTime, EntityManager);
switch (doAfter.Status)
{
case DoAfterStatus.Running:
break;
case DoAfterStatus.Cancelled:
_cancelled.Enqueue(doAfter);
break;
case DoAfterStatus.Finished:
_finished.Enqueue(doAfter);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
while (_cancelled.TryDequeue(out var doAfter))
{
Cancelled(comp, doAfter);
if(EntityManager.EntityExists(doAfter.EventArgs.User) && doAfter.EventArgs.UserCancelledEvent != null)
RaiseLocalEvent(doAfter.EventArgs.User, doAfter.EventArgs.UserCancelledEvent, false);
if (doAfter.EventArgs.Used is {} used && EntityManager.EntityExists(used) && doAfter.EventArgs.UsedCancelledEvent != null)
RaiseLocalEvent(used, doAfter.EventArgs.UsedCancelledEvent);
if(doAfter.EventArgs.Target is {} target && EntityManager.EntityExists(target) && doAfter.EventArgs.TargetCancelledEvent != null)
RaiseLocalEvent(target, doAfter.EventArgs.TargetCancelledEvent, false);
if(doAfter.EventArgs.BroadcastCancelledEvent != null)
RaiseLocalEvent(doAfter.EventArgs.BroadcastCancelledEvent);
}
while (_finished.TryDequeue(out var doAfter))
{
Finished(comp, doAfter);
if(EntityManager.EntityExists(doAfter.EventArgs.User) && doAfter.EventArgs.UserFinishedEvent != null)
RaiseLocalEvent(doAfter.EventArgs.User, doAfter.EventArgs.UserFinishedEvent, false);
if(doAfter.EventArgs.Used is {} used && EntityManager.EntityExists(used) && doAfter.EventArgs.UsedFinishedEvent != null)
RaiseLocalEvent(used, doAfter.EventArgs.UsedFinishedEvent);
if(doAfter.EventArgs.Target is {} target && EntityManager.EntityExists(target) && doAfter.EventArgs.TargetFinishedEvent != null)
RaiseLocalEvent(target, doAfter.EventArgs.TargetFinishedEvent, false);
if(doAfter.EventArgs.BroadcastFinishedEvent != null)
RaiseLocalEvent(doAfter.EventArgs.BroadcastFinishedEvent);
}
}
}
/// <summary>
/// Tasks that are delayed until the specified time has passed
/// These can be potentially cancelled by the user moving or when other things happen.
/// </summary>
/// <param name="eventArgs"></param>
/// <returns></returns>
public async Task<DoAfterStatus> WaitDoAfter(DoAfterEventArgs eventArgs)
{
var doAfter = CreateDoAfter(eventArgs);
await doAfter.AsTask;
return doAfter.Status;
}
/// <summary>
/// Creates a DoAfter without waiting for it to finish. You can use events with this.
/// These can be potentially cancelled by the user moving or when other things happen.
/// </summary>
/// <param name="eventArgs"></param>
public void DoAfter(DoAfterEventArgs eventArgs)
{
CreateDoAfter(eventArgs);
}
private DoAfter CreateDoAfter(DoAfterEventArgs eventArgs)
{
// Setup
var doAfter = new DoAfter(eventArgs, EntityManager);
// Caller's gonna be responsible for this I guess
var doAfterComponent = Comp<DoAfterComponent>(eventArgs.User);
Add(doAfterComponent, doAfter);
return doAfter;
}
}
public enum DoAfterStatus : byte
{
Running,
Cancelled,
Finished,
}
}

View File

@@ -2,8 +2,6 @@ using Content.Server.Access;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Construction;
using Content.Server.Doors.Components;
using Content.Server.Tools;
using Content.Server.Tools.Systems;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
@@ -17,9 +15,9 @@ using Content.Shared.Tools.Components;
using Content.Shared.Verbs;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using System.Linq;
using Content.Server.Power.EntitySystems;
using Content.Shared.Tools;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
@@ -31,7 +29,7 @@ public sealed class DoorSystem : SharedDoorSystem
[Dependency] private readonly AirlockSystem _airlock = default!;
[Dependency] private readonly AirtightSystem _airtightSystem = default!;
[Dependency] private readonly ConstructionSystem _constructionSystem = default!;
[Dependency] private readonly ToolSystem _toolSystem = default!;
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
public override void Initialize()
@@ -197,9 +195,8 @@ public sealed class DoorSystem : SharedDoorSystem
RaiseLocalEvent(target, modEv, false);
door.BeingPried = true;
_toolSystem.UseTool(tool, user, target, 0f, modEv.PryTimeModifier * door.PryTime, door.PryingQuality,
new PryFinishedEvent(), new PryCancelledEvent(), target);
var toolEvData = new ToolEventData(new PryFinishedEvent(), cancelledEv: new PryCancelledEvent(),targetEntity: target);
_toolSystem.UseTool(tool, user, target, modEv.PryTimeModifier * door.PryTime, new[] { door.PryingQuality }, toolEvData);
return true; // we might not actually succeeded, but a do-after has started
}

View File

@@ -102,8 +102,6 @@ namespace Content.Server.Dragon
Params = AudioParams.Default.WithVolume(3f),
};
public CancellationTokenSource? CancelToken;
[ViewVariables(VVAccess.ReadWrite), DataField("devourWhitelist")]
public EntityWhitelist? DevourWhitelist = new()
{

View File

@@ -9,7 +9,6 @@ using System.Threading;
using Content.Server.Chat.Systems;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
using Content.Server.Humanoid;
using Content.Server.NPC;
using Content.Shared.Damage;
using Content.Shared.Dragon;
@@ -20,6 +19,7 @@ using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Random;
using Content.Server.NPC.Systems;
using Content.Shared.DoAfter;
using Content.Shared.Humanoid;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
@@ -59,13 +59,12 @@ namespace Content.Server.Dragon
SubscribeLocalEvent<DragonComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<DragonComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<DragonComponent, DragonDevourComplete>(OnDragonDevourComplete);
SubscribeLocalEvent<DragonComponent, DragonDevourActionEvent>(OnDevourAction);
SubscribeLocalEvent<DragonComponent, DragonSpawnRiftActionEvent>(OnDragonRift);
SubscribeLocalEvent<DragonComponent, RefreshMovementSpeedModifiersEvent>(OnDragonMove);
SubscribeLocalEvent<DragonComponent, DragonStructureDevourComplete>(OnDragonStructureDevourComplete);
SubscribeLocalEvent<DragonComponent, DragonDevourCancelledEvent>(OnDragonDevourCancelled);
SubscribeLocalEvent<DragonComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<DragonComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<DragonRiftComponent, ComponentShutdown>(OnRiftShutdown);
@@ -76,6 +75,30 @@ namespace Content.Server.Dragon
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRiftRoundEnd);
}
private void OnDoAfter(EntityUid uid, DragonComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled)
return;
var ichorInjection = new Solution(component.DevourChem, component.DevourHealRate);
//Humanoid devours allow dragon to get eggs, corpses included
if (HasComp<HumanoidAppearanceComponent>(args.Args.Target))
{
ichorInjection.ScaleSolution(0.5f);
component.DragonStomach.Insert(args.Args.Target.Value);
_bloodstreamSystem.TryAddToChemicals(uid, ichorInjection);
}
//TODO: Figure out a better way of removing structures via devour that still entails standing still and waiting for a DoAfter. Somehow.
//If it's not human, it must be a structure
else if (args.Args.Target != null)
EntityManager.QueueDeleteEntity(args.Args.Target.Value);
if (component.SoundDevour != null)
_audioSystem.PlayPvs(component.SoundDevour, uid, component.SoundDevour.Params);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
@@ -292,39 +315,6 @@ namespace Content.Server.Dragon
}
}
private void OnDragonDevourCancelled(EntityUid uid, DragonComponent component, DragonDevourCancelledEvent args)
{
component.CancelToken = null;
}
private void OnDragonDevourComplete(EntityUid uid, DragonComponent component, DragonDevourComplete args)
{
component.CancelToken = null;
var ichorInjection = new Solution(component.DevourChem, component.DevourHealRate);
//Humanoid devours allow dragon to get eggs, corpses included
if (!EntityManager.HasComponent<HumanoidAppearanceComponent>(args.Target))
{
ichorInjection.ScaleSolution(0.5f);
}
_bloodstreamSystem.TryAddToChemicals(uid, ichorInjection);
component.DragonStomach.Insert(args.Target);
if (component.SoundDevour != null)
_audioSystem.PlayPvs(component.SoundDevour, uid, component.SoundDevour.Params);
}
private void OnDragonStructureDevourComplete(EntityUid uid, DragonComponent component, DragonStructureDevourComplete args)
{
component.CancelToken = null;
//TODO: Figure out a better way of removing structures via devour that still entails standing still and waiting for a DoAfter. Somehow.
EntityManager.QueueDeleteEntity(args.Target);
if (component.SoundDevour != null)
_audioSystem.PlayPvs(component.SoundDevour, uid, component.SoundDevour.Params);
}
private void Roar(DragonComponent component)
{
if (component.SoundRoar != null)
@@ -351,12 +341,8 @@ namespace Content.Server.Dragon
/// </summary>
private void OnDevourAction(EntityUid uid, DragonComponent component, DragonDevourActionEvent args)
{
if (component.CancelToken != null ||
args.Handled ||
component.DevourWhitelist?.IsValid(args.Target, EntityManager) != true)
{
if (args.Handled || component.DevourWhitelist?.IsValid(args.Target, EntityManager) != true)
return;
}
args.Handled = true;
var target = args.Target;
@@ -368,12 +354,9 @@ namespace Content.Server.Dragon
{
case MobState.Critical:
case MobState.Dead:
component.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.DevourTime, component.CancelToken.Token, target)
_doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.DevourTime, target:target)
{
UserFinishedEvent = new DragonDevourComplete(uid, target),
UserCancelledEvent = new DragonDevourCancelledEvent(),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
@@ -392,42 +375,12 @@ namespace Content.Server.Dragon
if (component.SoundStructureDevour != null)
_audioSystem.PlayPvs(component.SoundStructureDevour, uid, component.SoundStructureDevour.Params);
component.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.StructureDevourTime, component.CancelToken.Token, target)
_doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.StructureDevourTime, target:target)
{
UserFinishedEvent = new DragonStructureDevourComplete(uid, target),
UserCancelledEvent = new DragonDevourCancelledEvent(),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
});
}
private sealed class DragonDevourComplete : EntityEventArgs
{
public EntityUid User { get; }
public EntityUid Target { get; }
public DragonDevourComplete(EntityUid user, EntityUid target)
{
User = user;
Target = target;
}
}
private sealed class DragonStructureDevourComplete : EntityEventArgs
{
public EntityUid User { get; }
public EntityUid Target { get; }
public DragonStructureDevourComplete(EntityUid user, EntityUid target)
{
User = user;
Target = target;
}
}
private sealed class DragonDevourCancelledEvent : EntityEventArgs {}
}
}

View File

@@ -1,5 +1,6 @@
using Content.Server.DoAfter;
using Content.Server.Engineering.Components;
using Content.Shared.DoAfter;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Verbs;
using JetBrains.Annotations;

View File

@@ -2,6 +2,7 @@ using Content.Server.Coordinates.Helpers;
using Content.Server.DoAfter;
using Content.Server.Engineering.Components;
using Content.Server.Stack;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Maps;
using Content.Shared.Stacks;

View File

@@ -1,6 +1,7 @@
using System.Threading;
using Content.Server.DoAfter;
using Content.Shared.Alert;
using Content.Shared.DoAfter;
using Content.Shared.Ensnaring.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.StepTrigger.Systems;
@@ -27,7 +28,7 @@ public sealed partial class EnsnareableSystem
return;
if (ensnared.IsEnsnared)
ForceFree(component);
ForceFree(uid, component);
}
private void AttemptStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggerAttemptEvent args)
@@ -37,7 +38,7 @@ public sealed partial class EnsnareableSystem
private void OnStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggeredEvent args)
{
TryEnsnare(args.Tripper, component);
TryEnsnare(args.Tripper, uid, component);
}
private void OnThrowHit(EntityUid uid, EnsnaringComponent component, ThrowDoHitEvent args)
@@ -45,26 +46,27 @@ public sealed partial class EnsnareableSystem
if (!component.CanThrowTrigger)
return;
TryEnsnare(args.Target, component);
TryEnsnare(args.Target, uid, component);
}
/// <summary>
/// Used where you want to try to ensnare an entity with the <see cref="EnsnareableComponent"/>
/// </summary>
/// <param name="target">The entity that will be ensnared</param>
/// <paramref name="ensnare"> The entity that is used to ensnare</param>
/// <param name="component">The ensnaring component</param>
public void TryEnsnare(EntityUid target, EnsnaringComponent component)
public void TryEnsnare(EntityUid target, EntityUid ensnare, EnsnaringComponent component)
{
//Don't do anything if they don't have the ensnareable component.
if (!TryComp<EnsnareableComponent>(target, out var ensnareable))
return;
component.Ensnared = target;
ensnareable.Container.Insert(component.Owner);
ensnareable.Container.Insert(ensnare);
ensnareable.IsEnsnared = true;
Dirty(ensnareable);
UpdateAlert(ensnareable);
UpdateAlert(ensnare, ensnareable);
var ev = new EnsnareEvent(component.WalkSpeed, component.SprintSpeed);
RaiseLocalEvent(target, ev);
}
@@ -73,18 +75,14 @@ public sealed partial class EnsnareableSystem
/// Used where you want to try to free an entity with the <see cref="EnsnareableComponent"/>
/// </summary>
/// <param name="target">The entity that will be free</param>
/// <param name="ensnare">The entity used to ensnare</param>
/// <param name="component">The ensnaring component</param>
public void TryFree(EntityUid target, EnsnaringComponent component, EntityUid? user = null)
public void TryFree(EntityUid target, EntityUid ensnare, EnsnaringComponent component, EntityUid? user = null)
{
//Don't do anything if they don't have the ensnareable component.
if (!HasComp<EnsnareableComponent>(target))
return;
if (component.CancelToken != null)
return;
component.CancelToken = new CancellationTokenSource();
var isOwner = !(user != null && target != user);
var freeTime = isOwner ? component.BreakoutTime : component.FreeTime;
bool breakOnMove;
@@ -94,55 +92,47 @@ public sealed partial class EnsnareableSystem
else
breakOnMove = true;
var doAfterEventArgs = new DoAfterEventArgs(target, freeTime, component.CancelToken.Token, target)
var doAfterEventArgs = new DoAfterEventArgs(target, freeTime, target: target, used:ensnare)
{
BreakOnUserMove = breakOnMove,
BreakOnTargetMove = breakOnMove,
BreakOnDamage = false,
BreakOnStun = true,
NeedHand = true,
TargetFinishedEvent = new FreeEnsnareDoAfterComplete(component.Owner),
TargetCancelledEvent = new FreeEnsnareDoAfterCancel(component.Owner),
NeedHand = true
};
_doAfter.DoAfter(doAfterEventArgs);
if (isOwner)
_popup.PopupEntity(Loc.GetString("ensnare-component-try-free", ("ensnare", component.Owner)), target, target);
_popup.PopupEntity(Loc.GetString("ensnare-component-try-free", ("ensnare", ensnare)), target, target);
if (!isOwner && user != null)
{
_popup.PopupEntity(Loc.GetString("ensnare-component-try-free-other", ("ensnare", component.Owner), ("user", Identity.Entity(target, EntityManager))), user.Value, user.Value);
}
_popup.PopupEntity(Loc.GetString("ensnare-component-try-free-other", ("ensnare", ensnare), ("user", Identity.Entity(target, EntityManager))), user.Value, user.Value);
}
/// <summary>
/// Used to force free someone for things like if the <see cref="EnsnaringComponent"/> is removed
/// </summary>
public void ForceFree(EnsnaringComponent component)
public void ForceFree(EntityUid ensnare, EnsnaringComponent component)
{
if (!TryComp<EnsnareableComponent>(component.Ensnared, out var ensnareable))
return;
ensnareable.Container.ForceRemove(component.Owner);
ensnareable.Container.ForceRemove(ensnare);
ensnareable.IsEnsnared = false;
Dirty(ensnareable);
component.Ensnared = null;
UpdateAlert(ensnareable);
UpdateAlert(ensnare, ensnareable);
var ev = new EnsnareRemoveEvent();
RaiseLocalEvent(component.Owner, ev);
RaiseLocalEvent(ensnare, ev);
}
public void UpdateAlert(EnsnareableComponent component)
public void UpdateAlert(EntityUid ensnare, EnsnareableComponent component)
{
if (!component.IsEnsnared)
{
_alerts.ClearAlert(component.Owner, AlertType.Ensnared);
}
_alerts.ClearAlert(ensnare, AlertType.Ensnared);
else
{
_alerts.ShowAlert(component.Owner, AlertType.Ensnared);
}
_alerts.ShowAlert(ensnare, AlertType.Ensnared);
}
}

View File

@@ -1,4 +1,5 @@
using Content.Server.Popups;
using Content.Shared.DoAfter;
using Content.Shared.Ensnaring;
using Content.Shared.Ensnaring.Components;
using Content.Shared.Popups;
@@ -19,43 +20,36 @@ public sealed partial class EnsnareableSystem : SharedEnsnareableSystem
InitializeEnsnaring();
SubscribeLocalEvent<EnsnareableComponent, ComponentInit>(OnEnsnareableInit);
SubscribeLocalEvent<EnsnareableComponent, FreeEnsnareDoAfterComplete>(OnFreeComplete);
SubscribeLocalEvent<EnsnareableComponent, FreeEnsnareDoAfterCancel>(OnFreeFail);
SubscribeLocalEvent<EnsnareableComponent, DoAfterEvent>(OnDoAfter);
}
private void OnEnsnareableInit(EntityUid uid, EnsnareableComponent component, ComponentInit args)
{
component.Container = _container.EnsureContainer<Container>(component.Owner, "ensnare");
component.Container = _container.EnsureContainer<Container>(uid, "ensnare");
}
private void OnFreeComplete(EntityUid uid, EnsnareableComponent component, FreeEnsnareDoAfterComplete args)
private void OnDoAfter(EntityUid uid, EnsnareableComponent component, DoAfterEvent args)
{
if (!TryComp<EnsnaringComponent>(args.EnsnaringEntity, out var ensnaring))
if (args.Handled || !TryComp<EnsnaringComponent>(args.Args.Used, out var ensnaring))
return;
component.Container.Remove(args.EnsnaringEntity);
if (args.Cancelled)
{
_popup.PopupEntity(Loc.GetString("ensnare-component-try-free-fail", ("ensnare", args.Args.Used)), uid, uid, PopupType.Large);
return;
}
component.Container.Remove(args.Args.Used.Value);
component.IsEnsnared = false;
Dirty(component);
ensnaring.Ensnared = null;
_popup.PopupEntity(Loc.GetString("ensnare-component-try-free-complete", ("ensnare", args.EnsnaringEntity)),
uid, uid, PopupType.Large);
_popup.PopupEntity(Loc.GetString("ensnare-component-try-free-complete", ("ensnare", args.Args.Used)), uid, uid, PopupType.Large);
UpdateAlert(component);
UpdateAlert(args.Args.Used.Value, component);
var ev = new EnsnareRemoveEvent();
RaiseLocalEvent(uid, ev);
ensnaring.CancelToken = null;
}
private void OnFreeFail(EntityUid uid, EnsnareableComponent component, FreeEnsnareDoAfterCancel args)
{
if (!TryComp<EnsnaringComponent>(args.EnsnaringEntity, out var ensnaring))
return;
ensnaring.CancelToken = null;
_popup.PopupEntity(Loc.GetString("ensnare-component-try-free-fail", ("ensnare", args.EnsnaringEntity)),
uid, uid, PopupType.Large);
args.Handled = true;
}
}

View File

@@ -5,6 +5,7 @@ using Content.Shared.Clothing.Components;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Server.Tools;
using Content.Shared.Tools.Components;
namespace Content.Server.Eye.Blinding.EyeProtection
{

View File

@@ -1,5 +1,3 @@
using System.Threading;
namespace Content.Server.Fluids.Components;
[RegisterComponent]
@@ -17,6 +15,4 @@ public sealed class SpillableComponent : Component
[DataField("spillDelay")]
public float? SpillDelay;
public CancellationTokenSource? CancelToken;
}

View File

@@ -5,6 +5,7 @@ using Content.Server.DoAfter;
using Content.Server.Fluids.Components;
using Content.Server.Popups;
using Content.Shared.Chemistry.Components;
using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids;
using Content.Shared.Interaction;
@@ -34,9 +35,7 @@ public sealed class MoppingSystem : SharedMoppingSystem
base.Initialize();
SubscribeLocalEvent<AbsorbentComponent, ComponentInit>(OnAbsorbentInit);
SubscribeLocalEvent<AbsorbentComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<AbsorbentComponent, SolutionChangedEvent>(OnAbsorbentSolutionChange);
SubscribeLocalEvent<TransferCancelledEvent>(OnTransferCancelled);
SubscribeLocalEvent<TransferCompleteEvent>(OnTransferComplete);
SubscribeLocalEvent<AbsorbentComponent, DoAfterEvent<AbsorbantData>>(OnDoAfter);
}
private void OnAbsorbentInit(EntityUid uid, AbsorbentComponent component, ComponentInit args)
@@ -256,63 +255,38 @@ public sealed class MoppingSystem : SharedMoppingSystem
if (!component.InteractingEntities.Add(target))
return;
var doAfterArgs = new DoAfterEventArgs(user, delay, target: target)
var aborbantData = new AbsorbantData(targetSolution, msg, sfx, transferAmount);
var doAfterArgs = new DoAfterEventArgs(user, delay, target: target, used:used)
{
BreakOnUserMove = true,
BreakOnStun = true,
BreakOnDamage = true,
MovementThreshold = 0.2f,
BroadcastCancelledEvent = new TransferCancelledEvent(target, component),
BroadcastFinishedEvent = new TransferCompleteEvent(used, target, component, targetSolution, msg, sfx, transferAmount)
MovementThreshold = 0.2f
};
_doAfterSystem.DoAfter(doAfterArgs);
_doAfterSystem.DoAfter(doAfterArgs, aborbantData);
}
private void OnTransferComplete(TransferCompleteEvent ev)
private void OnDoAfter(EntityUid uid, AbsorbentComponent component, DoAfterEvent<AbsorbantData> args)
{
_audio.PlayPvs(ev.Sound, ev.Tool);
_popups.PopupEntity(ev.Message, ev.Tool);
_solutionSystem.TryTransferSolution(ev.Target, ev.Tool, ev.TargetSolution, AbsorbentComponent.SolutionName, ev.TransferAmount);
ev.Component.InteractingEntities.Remove(ev.Target);
if (args.Handled || args.Cancelled || args.Args.Target == null)
return;
_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);
args.Handled = true;
}
private void OnTransferCancelled(TransferCancelledEvent ev)
private record struct AbsorbantData(string TargetSolution, string Message, SoundSpecifier Sound, FixedPoint2 TransferAmount)
{
ev.Component.InteractingEntities.Remove(ev.Target);
}
}
public sealed class TransferCompleteEvent : EntityEventArgs
{
public readonly EntityUid Tool;
public readonly EntityUid Target;
public readonly AbsorbentComponent Component;
public readonly string TargetSolution;
public readonly string Message;
public readonly SoundSpecifier Sound;
public readonly FixedPoint2 TransferAmount;
public TransferCompleteEvent(EntityUid tool, EntityUid target, AbsorbentComponent component, string targetSolution, string message, SoundSpecifier sound, FixedPoint2 transferAmount)
{
Tool = tool;
Target = target;
Component = component;
TargetSolution = targetSolution;
Message = Loc.GetString(message, ("target", target), ("used", tool));
Sound = sound;
TransferAmount = transferAmount;
}
}
public sealed class TransferCancelledEvent : EntityEventArgs
{
public readonly EntityUid Target;
public readonly AbsorbentComponent Component;
public TransferCancelledEvent(EntityUid target, AbsorbentComponent component)
{
Target = target;
Component = component;
public readonly string TargetSolution = TargetSolution;
public readonly string Message = Message;
public readonly SoundSpecifier Sound = Sound;
public readonly FixedPoint2 TransferAmount = TransferAmount;
}
}

View File

@@ -16,7 +16,7 @@ using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Content.Shared.DoAfter;
namespace Content.Server.Fluids.EntitySystems;
@@ -38,8 +38,7 @@ public sealed class SpillableSystem : EntitySystem
SubscribeLocalEvent<SpillableComponent, GetVerbsEvent<Verb>>(AddSpillVerb);
SubscribeLocalEvent<SpillableComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<SpillableComponent, SolutionSpikeOverflowEvent>(OnSpikeOverflow);
SubscribeLocalEvent<SpillableComponent, SpillFinishedEvent>(OnSpillFinished);
SubscribeLocalEvent<SpillableComponent, SpillCancelledEvent>(OnSpillCancelled);
SubscribeLocalEvent<SpillableComponent, DoAfterEvent>(OnDoAfter);
}
private void OnSpikeOverflow(EntityUid uid, SpillableComponent component, SolutionSpikeOverflowEvent args)
@@ -142,20 +141,14 @@ public sealed class SpillableSystem : EntitySystem
{
verb.Act = () =>
{
if (component.CancelToken == null)
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, component.SpillDelay.Value, target:uid)
{
component.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, component.SpillDelay.Value, component.CancelToken.Token, component.Owner)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
NeedHand = true,
TargetFinishedEvent = new SpillFinishedEvent(args.User, component.Owner, solution),
TargetCancelledEvent = new SpillCancelledEvent(component.Owner)
});
}
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
NeedHand = true
});
};
}
verb.Impact = LogImpact.Medium; // dangerous reagent reaction are logged separately.
@@ -263,46 +256,19 @@ public sealed class SpillableSystem : EntitySystem
return puddleComponent;
}
private void OnSpillFinished(EntityUid uid, SpillableComponent component, SpillFinishedEvent ev)
private void OnDoAfter(EntityUid uid, SpillableComponent component, DoAfterEvent args)
{
component.CancelToken = null;
//solution gone by other means before doafter completes
if (ev.Solution == null || ev.Solution.Volume == 0)
if (args.Handled || args.Cancelled || args.Args.Target == null)
return;
var puddleSolution = _solutionContainerSystem.SplitSolution(uid,
ev.Solution, ev.Solution.Volume);
//solution gone by other means before doafter completes
if (!_solutionContainerSystem.TryGetDrainableSolution(uid, out var solution) || solution.Volume == 0)
return;
SpillAt(puddleSolution, Transform(component.Owner).Coordinates, "PuddleSmear");
}
var puddleSolution = _solutionContainerSystem.SplitSolution(uid, solution, solution.Volume);
private void OnSpillCancelled(EntityUid uid, SpillableComponent component, SpillCancelledEvent ev)
{
component.CancelToken = null;
}
SpillAt(puddleSolution, Transform(uid).Coordinates, "PuddleSmear");
internal sealed class SpillFinishedEvent : EntityEventArgs
{
public SpillFinishedEvent(EntityUid user, EntityUid spillable, Solution solution)
{
User = user;
Spillable = spillable;
Solution = solution;
}
public EntityUid User { get; }
public EntityUid Spillable { get; }
public Solution Solution { get; }
}
private sealed class SpillCancelledEvent : EntityEventArgs
{
public EntityUid Spillable;
public SpillCancelledEvent(EntityUid spillable)
{
Spillable = spillable;
}
args.Handled = true;
}
}

View File

@@ -1,5 +1,3 @@
using System.Threading;
namespace Content.Server.Forensics
{
/// <summary>
@@ -8,8 +6,6 @@ namespace Content.Server.Forensics
[RegisterComponent]
public sealed class ForensicPadComponent : Component
{
public CancellationTokenSource? CancelToken;
[DataField("scanDelay")]
public float ScanDelay = 3.0f;

View File

@@ -1,11 +1,11 @@
using System.Threading;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Inventory;
using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Robust.Shared.Player;
using Robust.Shared.Serialization;
namespace Content.Server.Forensics
{
@@ -16,7 +16,6 @@ namespace Content.Server.Forensics
{
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
public override void Initialize()
@@ -24,8 +23,7 @@ namespace Content.Server.Forensics
base.Initialize();
SubscribeLocalEvent<ForensicPadComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<ForensicPadComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<TargetPadSuccessfulEvent>(OnTargetPadSuccessful);
SubscribeLocalEvent<PadCancelledEvent>(OnPadCancelled);
SubscribeLocalEvent<ForensicPadComponent, DoAfterEvent<ForensicPadData>>(OnDoAfter);
}
private void OnExamined(EntityUid uid, ForensicPadComponent component, ExaminedEvent args)
@@ -44,7 +42,7 @@ namespace Content.Server.Forensics
private void OnAfterInteract(EntityUid uid, ForensicPadComponent component, AfterInteractEvent args)
{
if (component.CancelToken != null || !args.CanReach || args.Target == null)
if (!args.CanReach || args.Target == null)
return;
if (HasComp<ForensicScannerComponent>(args.Target))
@@ -71,74 +69,58 @@ namespace Content.Server.Forensics
_popupSystem.PopupEntity(Loc.GetString("forensic-pad-start-scan-user", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.Target.Value, args.User);
_popupSystem.PopupEntity(Loc.GetString("forensic-pad-start-scan-target", ("user", Identity.Entity(args.User, EntityManager))), args.Target.Value, args.Target.Value);
}
StartScan(args.User, args.Target.Value, component, fingerprint.Fingerprint);
StartScan(uid, args.User, args.Target.Value, component, fingerprint.Fingerprint);
return;
}
if (TryComp<FiberComponent>(args.Target, out var fiber))
StartScan(args.User, args.Target.Value, component, string.IsNullOrEmpty(fiber.FiberColor) ? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial)) : Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial)));
StartScan(uid, args.User, args.Target.Value, component, string.IsNullOrEmpty(fiber.FiberColor) ? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial)) : Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial)));
}
private void StartScan(EntityUid user, EntityUid target, ForensicPadComponent pad, string sample)
private void StartScan(EntityUid used, EntityUid user, EntityUid target, ForensicPadComponent pad, string sample)
{
pad.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, pad.ScanDelay, pad.CancelToken.Token, target: target)
var padData = new ForensicPadData(sample);
var doAfterEventArgs = new DoAfterEventArgs(user, pad.ScanDelay, target: target, used: used)
{
BroadcastFinishedEvent = new TargetPadSuccessfulEvent(user, target, pad.Owner, sample),
BroadcastCancelledEvent = new PadCancelledEvent(pad.Owner),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
NeedHand = true
});
};
_doAfterSystem.DoAfter(doAfterEventArgs, padData);
}
/// <summary>
/// When the forensic pad is successfully used, take their fingerprint sample and flag the pad as used.
/// </summary>
private void OnTargetPadSuccessful(TargetPadSuccessfulEvent ev)
private void OnDoAfter(EntityUid uid, ForensicPadComponent component, DoAfterEvent<ForensicPadData> args)
{
if (!EntityManager.TryGetComponent(ev.Pad, out ForensicPadComponent? component))
return;
if (HasComp<FingerprintComponent>(ev.Target))
MetaData(component.Owner).EntityName = Loc.GetString("forensic-pad-fingerprint-name", ("entity", ev.Target));
else
MetaData(component.Owner).EntityName = Loc.GetString("forensic-pad-gloves-name", ("entity", ev.Target));
component.CancelToken = null;
component.Sample = ev.Sample;
component.Used = true;
}
private void OnPadCancelled(PadCancelledEvent ev)
{
if (!EntityManager.TryGetComponent(ev.Pad, out ForensicPadComponent? component))
return;
component.CancelToken = null;
}
private sealed class PadCancelledEvent : EntityEventArgs
{
public EntityUid Pad;
public PadCancelledEvent(EntityUid pad)
if (args.Handled
|| args.Cancelled
|| !EntityManager.TryGetComponent(args.Args.Used, out ForensicPadComponent? padComponent))
{
Pad = pad;
return;
}
if (args.Args.Target != null)
{
if (HasComp<FingerprintComponent>(args.Args.Target))
MetaData(uid).EntityName = Loc.GetString("forensic-pad-fingerprint-name", ("entity", args.Args.Target));
else
MetaData(uid).EntityName = Loc.GetString("forensic-pad-gloves-name", ("entity", args.Args.Target));
}
padComponent.Sample = args.AdditionalData.Sample;
padComponent.Used = true;
args.Handled = true;
}
private sealed class TargetPadSuccessfulEvent : EntityEventArgs
private sealed class ForensicPadData
{
public EntityUid User;
public EntityUid Target;
public EntityUid Pad;
public string Sample = string.Empty;
public string Sample;
public TargetPadSuccessfulEvent(EntityUid user, EntityUid target, EntityUid pad, string sample)
public ForensicPadData(string sample)
{
User = user;
Target = target;
Pad = pad;
Sample = sample;
}
}

View File

@@ -1,14 +1,13 @@
using System.Linq;
using System.Text; // todo: remove this stinky LINQy
using System.Threading;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Content.Server.DoAfter;
using Content.Server.Paper;
using Content.Server.Popups;
using Content.Server.UserInterface;
using Content.Shared.DoAfter;
using Content.Shared.Forensics;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
@@ -40,8 +39,7 @@ namespace Content.Server.Forensics
SubscribeLocalEvent<ForensicScannerComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
SubscribeLocalEvent<ForensicScannerComponent, ForensicScannerPrintMessage>(OnPrint);
SubscribeLocalEvent<ForensicScannerComponent, ForensicScannerClearMessage>(OnClear);
SubscribeLocalEvent<TargetScanSuccessfulEvent>(OnTargetScanSuccessful);
SubscribeLocalEvent<ScanCancelledEvent>(OnScanCancelled);
SubscribeLocalEvent<ForensicScannerComponent, DoAfterEvent>(OnDoAfter);
}
private void UpdateUserInterface(EntityUid uid, ForensicScannerComponent component)
@@ -54,40 +52,35 @@ namespace Content.Server.Forensics
component.PrintReadyAt);
if (!_uiSystem.TrySetUiState(uid, ForensicScannerUiKey.Key, state))
{
_sawmill.Warning($"{ToPrettyString(uid)} was unable to set UI state.");
}
}
private void OnScanCancelled(ScanCancelledEvent ev)
private void OnDoAfter(EntityUid uid, ForensicScannerComponent component, DoAfterEvent args)
{
if (!EntityManager.TryGetComponent(ev.Scanner, out ForensicScannerComponent? scanner))
if (args.Handled || args.Cancelled)
return;
scanner.CancelToken = null;
}
private void OnTargetScanSuccessful(TargetScanSuccessfulEvent ev)
{
if (!EntityManager.TryGetComponent(ev.Scanner, out ForensicScannerComponent? scanner))
if (!EntityManager.TryGetComponent(uid, out ForensicScannerComponent? scanner))
return;
scanner.CancelToken = null;
if (args.Args.Target != null)
{
if (!TryComp<ForensicsComponent>(args.Args.Target, out var forensics))
{
scanner.Fingerprints = new();
scanner.Fibers = new();
}
if (!TryComp<ForensicsComponent>(ev.Target, out var forensics))
{
scanner.Fingerprints = new();
scanner.Fibers = new();
}
else
{
scanner.Fingerprints = forensics.Fingerprints.ToList();
scanner.Fibers = forensics.Fibers.ToList();
else
{
scanner.Fingerprints = forensics.Fingerprints.ToList();
scanner.Fibers = forensics.Fibers.ToList();
}
scanner.LastScannedName = MetaData(args.Args.Target.Value).EntityName;
}
scanner.LastScannedName = MetaData(ev.Target).EntityName;
OpenUserInterface(ev.User, scanner);
OpenUserInterface(args.Args.User, scanner);
}
/// <remarks>
@@ -95,11 +88,8 @@ namespace Content.Server.Forensics
/// </remarks>
private void StartScan(EntityUid uid, ForensicScannerComponent component, EntityUid user, EntityUid target)
{
component.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.ScanDelay, component.CancelToken.Token, target: target)
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.ScanDelay, target: target, used: uid)
{
BroadcastFinishedEvent = new TargetScanSuccessfulEvent(user, target, component.Owner),
BroadcastCancelledEvent = new ScanCancelledEvent(component.Owner),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
@@ -244,28 +234,5 @@ namespace Content.Server.Forensics
UpdateUserInterface(uid, component);
}
private sealed class ScanCancelledEvent : EntityEventArgs
{
public EntityUid Scanner;
public ScanCancelledEvent(EntityUid scanner)
{
Scanner = scanner;
}
}
private sealed class TargetScanSuccessfulEvent : EntityEventArgs
{
public EntityUid User;
public EntityUid Target;
public EntityUid Scanner;
public TargetScanSuccessfulEvent(EntityUid user, EntityUid target, EntityUid scanner)
{
User = user;
Target = target;
Scanner = scanner;
}
}
}
}

View File

@@ -32,6 +32,6 @@ namespace Content.Server.Gatherable.Components
public int MaxGatheringEntities = 1;
[ViewVariables]
public readonly Dictionary<EntityUid, CancellationTokenSource> GatheringEntities = new();
public readonly List<EntityUid> GatheringEntities = new();
}
}

View File

@@ -3,6 +3,7 @@ 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.Interaction;
@@ -27,15 +28,12 @@ public sealed class GatherableSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<GatherableComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<GatheringDoafterCancel>(OnDoafterCancel);
SubscribeLocalEvent<GatherableComponent, GatheringDoafterSuccess>(OnDoafterSuccess);
SubscribeLocalEvent<GatherableComponent, DoAfterEvent>(OnDoAfter);
}
private void OnInteractUsing(EntityUid uid, GatherableComponent component, InteractUsingEvent args)
{
if (!TryComp<GatheringToolComponent>(args.Used, out var tool) ||
component.ToolWhitelist?.IsValid(args.Used) == false ||
tool.GatheringEntities.TryGetValue(uid, out var cancelToken))
if (!TryComp<GatheringToolComponent>(args.Used, out var tool) || component.ToolWhitelist?.IsValid(args.Used) == false)
return;
// Can't gather too many entities at once.
@@ -46,38 +44,39 @@ public sealed class GatherableSystem : EntitySystem
var damageTime = (damageRequired / tool.Damage.Total).Float();
damageTime = Math.Max(1f, damageTime);
cancelToken = new CancellationTokenSource();
tool.GatheringEntities[uid] = cancelToken;
var doAfter = new DoAfterEventArgs(args.User, damageTime, cancelToken.Token, uid)
var doAfter = new DoAfterEventArgs(args.User, damageTime, target: uid, used: args.Used)
{
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
MovementThreshold = 0.25f,
BroadcastCancelledEvent = new GatheringDoafterCancel { Tool = args.Used, Resource = uid },
TargetFinishedEvent = new GatheringDoafterSuccess { Tool = args.Used, Resource = uid, Player = args.User }
};
_doAfterSystem.DoAfter(doAfter);
}
private void OnDoafterSuccess(EntityUid uid, GatherableComponent component, GatheringDoafterSuccess ev)
private void OnDoAfter(EntityUid uid, GatherableComponent component, DoAfterEvent args)
{
if (!TryComp(ev.Tool, out GatheringToolComponent? tool))
if(!TryComp<GatheringToolComponent>(args.Args.Used, out var tool) || args.Args.Target == null)
return;
if (args.Handled || args.Cancelled)
{
tool.GatheringEntities.Remove(args.Args.Target.Value);
return;
}
// Complete the gathering process
_destructible.DestroyEntity(uid);
_audio.PlayPvs(tool.GatheringSound, ev.Resource);
tool.GatheringEntities.Remove(ev.Resource);
_destructible.DestroyEntity(args.Args.Target.Value);
_audio.PlayPvs(tool.GatheringSound, args.Args.Target.Value);
tool.GatheringEntities.Remove(args.Args.Target.Value);
// Spawn the loot!
if (component.MappedLoot == null)
return;
var playerPos = Transform(ev.Player).MapPosition;
var playerPos = Transform(args.Args.User).MapPosition;
foreach (var (tag, table) in component.MappedLoot)
{
@@ -91,27 +90,7 @@ public sealed class GatherableSystem : EntitySystem
var spawnPos = playerPos.Offset(_random.NextVector2(0.3f));
Spawn(spawnLoot[0], spawnPos);
}
}
private void OnDoafterCancel(GatheringDoafterCancel ev)
{
if (!TryComp<GatheringToolComponent>(ev.Tool, out var tool))
return;
tool.GatheringEntities.Remove(ev.Resource);
}
private sealed class GatheringDoafterCancel : EntityEventArgs
{
public EntityUid Tool;
public EntityUid Resource;
}
private sealed class GatheringDoafterSuccess : EntityEventArgs
{
public EntityUid Tool;
public EntityUid Resource;
public EntityUid Player;
args.Handled = true;
}
}

View File

@@ -3,6 +3,7 @@ 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.Hands.EntitySystems;
using Content.Shared.Interaction;
@@ -10,7 +11,6 @@ using Content.Shared.Interaction.Events;
using Content.Shared.Mobs;
using Content.Shared.Popups;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Utility;
@@ -27,6 +27,7 @@ namespace Content.Server.Guardian
[Dependency] private readonly DamageableSystem _damageSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
@@ -34,8 +35,7 @@ namespace Content.Server.Guardian
SubscribeLocalEvent<GuardianCreatorComponent, UseInHandEvent>(OnCreatorUse);
SubscribeLocalEvent<GuardianCreatorComponent, AfterInteractEvent>(OnCreatorInteract);
SubscribeLocalEvent<GuardianCreatorComponent, ExaminedEvent>(OnCreatorExamine);
SubscribeLocalEvent<GuardianCreatorInjectedEvent>(OnCreatorInject);
SubscribeLocalEvent<GuardianCreatorInjectCancelledEvent>(OnCreatorCancelled);
SubscribeLocalEvent<GuardianCreatorComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<GuardianComponent, MoveEvent>(OnGuardianMove);
SubscribeLocalEvent<GuardianComponent, DamageChangedEvent>(OnGuardianDamaged);
@@ -58,7 +58,7 @@ namespace Content.Server.Guardian
return;
if (component.HostedGuardian != null)
ToggleGuardian(component);
ToggleGuardian(uid, component);
args.Handled = true;
}
@@ -67,19 +67,18 @@ namespace Content.Server.Guardian
{
var host = component.Host;
if (!TryComp<GuardianHostComponent>(host, out var hostComponent)) return;
if (LifeStage(host) >= EntityLifeStage.MapInitialized)
if (!TryComp<GuardianHostComponent>(host, out var hostComponent) || LifeStage(host) >= EntityLifeStage.MapInitialized)
return;
RetractGuardian(hostComponent, component);
RetractGuardian(host, hostComponent, uid, component);
}
private void OnGuardianPlayer(EntityUid uid, GuardianComponent component, PlayerAttachedEvent args)
{
var host = component.Host;
if (!HasComp<GuardianHostComponent>(host)) return;
if (!HasComp<GuardianHostComponent>(host))
return;
_popupSystem.PopupEntity(Loc.GetString("guardian-available"), host, host);
}
@@ -92,7 +91,9 @@ namespace Content.Server.Guardian
private void OnHostShutdown(EntityUid uid, GuardianHostComponent component, ComponentShutdown args)
{
if (component.HostedGuardian == null) return;
if (component.HostedGuardian == null)
return;
EntityManager.QueueDeleteEntity(component.HostedGuardian.Value);
_actionSystem.RemoveAction(uid, component.Action);
}
@@ -107,19 +108,15 @@ namespace Content.Server.Guardian
args.Cancel();
}
public void ToggleGuardian(GuardianHostComponent hostComponent)
public void ToggleGuardian(EntityUid user, GuardianHostComponent hostComponent)
{
if (hostComponent.HostedGuardian == null ||
!TryComp(hostComponent.HostedGuardian, out GuardianComponent? guardianComponent)) return;
if (hostComponent.HostedGuardian == null || !TryComp<GuardianComponent>(hostComponent.HostedGuardian, out var guardianComponent))
return;
if (guardianComponent.GuardianLoose)
{
RetractGuardian(hostComponent, guardianComponent);
}
RetractGuardian(user, hostComponent, hostComponent.HostedGuardian.Value, guardianComponent);
else
{
ReleaseGuardian(hostComponent, guardianComponent);
}
ReleaseGuardian(user, hostComponent, hostComponent.HostedGuardian.Value, guardianComponent);
}
/// <summary>
@@ -127,24 +124,22 @@ namespace Content.Server.Guardian
/// </summary>
private void OnCreatorUse(EntityUid uid, GuardianCreatorComponent component, UseInHandEvent args)
{
if (args.Handled) return;
args.Handled = true;
UseCreator(args.User, args.User, component);
if (args.Handled)
return;
//args.Handled = true;
UseCreator(args.User, args.User, uid, component);
}
private void OnCreatorInteract(EntityUid uid, GuardianCreatorComponent component, AfterInteractEvent args)
{
if (args.Handled || args.Target == null || !args.CanReach) return;
args.Handled = true;
UseCreator(args.User, args.Target.Value, component);
}
if (args.Handled || args.Target == null || !args.CanReach)
return;
private void OnCreatorCancelled(GuardianCreatorInjectCancelledEvent ev)
{
ev.Component.Injecting = false;
//args.Handled = true;
UseCreator(args.User, args.Target.Value, uid, component);
}
private void UseCreator(EntityUid user, EntityUid target, GuardianCreatorComponent component)
private void UseCreator(EntityUid user, EntityUid target, EntityUid injector, GuardianCreatorComponent component)
{
if (component.Used)
{
@@ -166,55 +161,52 @@ namespace Content.Server.Guardian
return;
}
if (component.Injecting) return;
if (component.Injecting)
return;
component.Injecting = true;
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.InjectionDelay, target: target)
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.InjectionDelay, target: target, used: injector)
{
BroadcastFinishedEvent = new GuardianCreatorInjectedEvent(user, target, component),
BroadcastCancelledEvent = new GuardianCreatorInjectCancelledEvent(target, component),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnUserMove = true
});
}
private void OnCreatorInject(GuardianCreatorInjectedEvent ev)
private void OnDoAfter(EntityUid uid, GuardianCreatorComponent component, DoAfterEvent args)
{
var comp = ev.Component;
if (args.Handled || args.Args.Target == null)
return;
if (comp.Deleted ||
comp.Used ||
!_handsSystem.IsHolding(ev.User, comp.Owner, out _) ||
HasComp<GuardianHostComponent>(ev.Target))
if (args.Cancelled || component.Deleted || component.Used || !_handsSystem.IsHolding(args.Args.User, uid, out _) || HasComp<GuardianHostComponent>(args.Args.Target))
{
comp.Injecting = false;
component.Injecting = false;
return;
}
var hostXform = EntityManager.GetComponent<TransformComponent>(ev.Target);
var host = EntityManager.EnsureComponent<GuardianHostComponent>(ev.Target);
var hostXform = Transform(args.Args.Target.Value);
var host = EnsureComp<GuardianHostComponent>(args.Args.Target.Value);
// Use map position so it's not inadvertantly parented to the host + if it's in a container it spawns outside I guess.
var guardian = EntityManager.SpawnEntity(comp.GuardianProto, hostXform.MapPosition);
var guardian = Spawn(component.GuardianProto, hostXform.MapPosition);
host.GuardianContainer.Insert(guardian);
host.HostedGuardian = guardian;
if (TryComp(guardian, out GuardianComponent? guardianComponent))
if (TryComp<GuardianComponent>(guardian, out var guardianComp))
{
guardianComponent.Host = ev.Target;
SoundSystem.Play("/Audio/Effects/guardian_inject.ogg", Filter.Pvs(ev.Target), ev.Target);
_popupSystem.PopupEntity(Loc.GetString("guardian-created"), ev.Target, ev.Target);
guardianComp.Host = args.Args.Target.Value;
_audio.Play("/Audio/Effects/guardian_inject.ogg", Filter.Pvs(args.Args.Target.Value), args.Args.Target.Value, true);
_popupSystem.PopupEntity(Loc.GetString("guardian-created"), args.Args.Target.Value, args.Args.Target.Value);
// Exhaust the activator
comp.Used = true;
component.Used = true;
}
else
{
Logger.ErrorS("guardian", $"Tried to spawn a guardian that doesn't have {nameof(GuardianComponent)}");
EntityManager.QueueDeleteEntity(guardian);
}
args.Handled = true;
}
/// <summary>
@@ -222,16 +214,18 @@ namespace Content.Server.Guardian
/// </summary>
private void OnHostStateChange(EntityUid uid, GuardianHostComponent component, MobStateChangedEvent args)
{
if (component.HostedGuardian == null) return;
if (component.HostedGuardian == null)
return;
if (args.NewMobState == MobState.Critical)
{
_popupSystem.PopupEntity(Loc.GetString("guardian-critical-warn"), component.HostedGuardian.Value, component.HostedGuardian.Value);
SoundSystem.Play("/Audio/Effects/guardian_warn.ogg", Filter.Pvs(component.HostedGuardian.Value), component.HostedGuardian.Value);
_audio.Play("/Audio/Effects/guardian_warn.ogg", Filter.Pvs(component.HostedGuardian.Value), component.HostedGuardian.Value, true);
}
else if (args.NewMobState == MobState.Dead)
{
SoundSystem.Play("/Audio/Voice/Human/malescream_guardian.ogg", Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.20f));
//TODO: Replace WithVariation with datafield
_audio.Play("/Audio/Voice/Human/malescream_guardian.ogg", Filter.Pvs(uid), uid, true, AudioHelpers.WithVariation(0.20f));
EntityManager.RemoveComponent<GuardianHostComponent>(uid);
}
}
@@ -241,7 +235,8 @@ namespace Content.Server.Guardian
/// </summary>
private void OnGuardianDamaged(EntityUid uid, GuardianComponent component, DamageChangedEvent args)
{
if (args.DamageDelta == null) return;
if (args.DamageDelta == null)
return;
_damageSystem.TryChangeDamage(component.Host, args.DamageDelta * component.DamageShare, origin: args.Origin);
_popupSystem.PopupEntity(Loc.GetString("guardian-entity-taking-damage"), component.Host, component.Host);
@@ -254,9 +249,7 @@ namespace Content.Server.Guardian
private void OnCreatorExamine(EntityUid uid, GuardianCreatorComponent component, ExaminedEvent args)
{
if (component.Used)
{
args.PushMarkup(Loc.GetString("guardian-activator-empty-examine"));
}
}
/// <summary>
@@ -266,7 +259,10 @@ namespace Content.Server.Guardian
{
if (component.HostedGuardian == null ||
!TryComp(component.HostedGuardian, out GuardianComponent? guardianComponent) ||
!guardianComponent.GuardianLoose) return;
!guardianComponent.GuardianLoose)
{
return;
}
CheckGuardianMove(uid, component.HostedGuardian.Value, component);
}
@@ -276,7 +272,8 @@ namespace Content.Server.Guardian
/// </summary>
private void OnGuardianMove(EntityUid uid, GuardianComponent component, ref MoveEvent args)
{
if (!component.GuardianLoose) return;
if (!component.GuardianLoose)
return;
CheckGuardianMove(component.Host, uid, guardianComponent: component);
}
@@ -298,78 +295,51 @@ namespace Content.Server.Guardian
return;
}
if (!guardianComponent.GuardianLoose) return;
if (!guardianComponent.GuardianLoose)
return;
if (!guardianXform.Coordinates.InRange(EntityManager, hostXform.Coordinates, guardianComponent.DistanceAllowed))
{
RetractGuardian(hostComponent, guardianComponent);
}
RetractGuardian(hostUid, hostComponent, guardianUid, guardianComponent);
}
private bool CanRelease(GuardianHostComponent host, GuardianComponent guardian)
private bool CanRelease(EntityUid guardian)
{
return HasComp<ActorComponent>(guardian.Owner);
return HasComp<ActorComponent>(guardian);
}
private void ReleaseGuardian(GuardianHostComponent hostComponent, GuardianComponent guardianComponent)
private void ReleaseGuardian(EntityUid host, GuardianHostComponent hostComponent, EntityUid guardian, GuardianComponent guardianComponent)
{
if (guardianComponent.GuardianLoose)
{
DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardian));
return;
}
if (!CanRelease(hostComponent, guardianComponent))
if (!CanRelease(guardian))
{
_popupSystem.PopupEntity(Loc.GetString("guardian-no-soul"), hostComponent.Owner, hostComponent.Owner);
_popupSystem.PopupEntity(Loc.GetString("guardian-no-soul"), host, host);
return;
}
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
hostComponent.GuardianContainer.Remove(guardianComponent.Owner);
DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardian));
hostComponent.GuardianContainer.Remove(guardian);
DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardian));
guardianComponent.GuardianLoose = true;
}
private void RetractGuardian(GuardianHostComponent hostComponent, GuardianComponent guardianComponent)
private void RetractGuardian(EntityUid host,GuardianHostComponent hostComponent, EntityUid guardian, GuardianComponent guardianComponent)
{
if (!guardianComponent.GuardianLoose)
{
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardian));
return;
}
hostComponent.GuardianContainer.Insert(guardianComponent.Owner);
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
_popupSystem.PopupEntity(Loc.GetString("guardian-entity-recall"), hostComponent.Owner);
hostComponent.GuardianContainer.Insert(guardian);
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardian));
_popupSystem.PopupEntity(Loc.GetString("guardian-entity-recall"), host);
guardianComponent.GuardianLoose = false;
}
private sealed class GuardianCreatorInjectedEvent : EntityEventArgs
{
public EntityUid User { get; }
public EntityUid Target { get; }
public GuardianCreatorComponent Component { get; }
public GuardianCreatorInjectedEvent(EntityUid user, EntityUid target, GuardianCreatorComponent component)
{
User = user;
Target = target;
Component = component;
}
}
private sealed class GuardianCreatorInjectCancelledEvent : EntityEventArgs
{
public EntityUid Target { get; }
public GuardianCreatorComponent Component { get; }
public GuardianCreatorInjectCancelledEvent(EntityUid target, GuardianCreatorComponent component)
{
Target = target;
Component = component;
}
}
}
}

View File

@@ -2,6 +2,7 @@ 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;
@@ -11,7 +12,6 @@ using Content.Shared.Mobs.Components;
using Content.Shared.Popups;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Player;
namespace Content.Server.Implants;
@@ -30,9 +30,8 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
SubscribeLocalEvent<ImplanterComponent, AfterInteractEvent>(OnImplanterAfterInteract);
SubscribeLocalEvent<ImplanterComponent, ComponentGetState>(OnImplanterGetState);
SubscribeLocalEvent<ImplanterComponent, ImplanterImplantCompleteEvent>(OnImplantAttemptSuccess);
SubscribeLocalEvent<ImplanterComponent, ImplanterDrawCompleteEvent>(OnDrawAttemptSuccess);
SubscribeLocalEvent<ImplanterComponent, ImplanterCancelledEvent>(OnImplantAttemptFail);
SubscribeLocalEvent<ImplanterComponent, DoAfterEvent<ImplantEvent>>(OnImplant);
SubscribeLocalEvent<ImplanterComponent, DoAfterEvent<DrawEvent>>(OnDraw);
}
private void OnImplanterAfterInteract(EntityUid uid, ImplanterComponent component, AfterInteractEvent args)
@@ -40,12 +39,6 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
if (args.Target == null || !args.CanReach || args.Handled)
return;
if (component.CancelToken != null)
{
args.Handled = true;
return;
}
//Simplemobs and regular mobs should be injectable, but only regular mobs have mind.
//So just don't implant/draw anything that isn't living or is a guardian
//TODO: Rework a bit when surgery is in to work with implant cases
@@ -84,6 +77,9 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
/// <param name="implanter">The implanter being used</param>
public void TryImplant(ImplanterComponent component, EntityUid user, EntityUid target, EntityUid implanter)
{
if (component.CancelToken != null)
return;
_popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, user);
var userName = Identity.Entity(user, EntityManager);
@@ -92,15 +88,16 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
component.CancelToken?.Cancel();
component.CancelToken = new CancellationTokenSource();
_doAfter.DoAfter(new DoAfterEventArgs(user, component.ImplantTime, component.CancelToken.Token, target, implanter)
var implantEvent = new ImplantEvent();
_doAfter.DoAfter(new DoAfterEventArgs(user, component.ImplantTime, component.CancelToken.Token,target:target, used:implanter)
{
BreakOnUserMove = true,
BreakOnTargetMove = true,
BreakOnDamage = true,
BreakOnStun = true,
UsedFinishedEvent = new ImplanterImplantCompleteEvent(implanter, target),
UserCancelledEvent = new ImplanterCancelledEvent()
});
NeedHand = true
}, implantEvent);
}
/// <summary>
@@ -118,15 +115,16 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
component.CancelToken?.Cancel();
component.CancelToken = new CancellationTokenSource();
_doAfter.DoAfter(new DoAfterEventArgs(user, component.DrawTime, component.CancelToken.Token, target ,implanter)
var drawEvent = new DrawEvent();
_doAfter.DoAfter(new DoAfterEventArgs(user, component.DrawTime, target:target,used:implanter)
{
BreakOnUserMove = true,
BreakOnTargetMove = true,
BreakOnDamage = true,
BreakOnStun = true,
UsedFinishedEvent = new ImplanterDrawCompleteEvent(implanter, user, target),
UsedCancelledEvent = new ImplanterCancelledEvent()
});
NeedHand = true
}, drawEvent);
}
private void OnImplanterGetState(EntityUid uid, ImplanterComponent component, ref ComponentGetState args)
@@ -134,55 +132,47 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
args.State = new ImplanterComponentState(component.CurrentMode, component.ImplantOnly);
}
private void OnImplantAttemptSuccess(EntityUid uid, ImplanterComponent component, ImplanterImplantCompleteEvent args)
private void OnImplant(EntityUid uid, ImplanterComponent component, DoAfterEvent<ImplantEvent> args)
{
component.CancelToken?.Cancel();
component.CancelToken = null;
Implant(args.Implanter, args.Target, component);
}
private void OnDrawAttemptSuccess(EntityUid uid, ImplanterComponent component, ImplanterDrawCompleteEvent args)
{
component.CancelToken?.Cancel();
component.CancelToken = null;
Draw(args.Implanter, args.User, args.Target, component);
}
private void OnImplantAttemptFail(EntityUid uid, ImplanterComponent component, ImplanterCancelledEvent args)
{
component.CancelToken?.Cancel();
component.CancelToken = null;
}
private sealed class ImplanterImplantCompleteEvent : EntityEventArgs
{
public EntityUid Implanter;
public EntityUid Target;
public ImplanterImplantCompleteEvent(EntityUid implanter, EntityUid target)
if (args.Cancelled)
{
Implanter = implanter;
Target = target;
component.CancelToken = null;
return;
}
if (args.Handled || args.Args.Target == null || args.Args.Used == null)
return;
Implant(args.Args.Used.Value, args.Args.Target.Value, component);
args.Handled = true;
component.CancelToken = null;
}
private sealed class ImplanterCancelledEvent : EntityEventArgs
private void OnDraw(EntityUid uid, ImplanterComponent component, DoAfterEvent<DrawEvent> args)
{
}
private sealed class ImplanterDrawCompleteEvent : EntityEventArgs
{
public EntityUid Implanter;
public EntityUid User;
public EntityUid Target;
public ImplanterDrawCompleteEvent(EntityUid implanter, EntityUid user, EntityUid target)
if (args.Cancelled)
{
Implanter = implanter;
User = user;
Target = target;
component.CancelToken = null;
return;
}
if (args.Handled || args.Args.Used == null || args.Args.Target == null)
return;
Draw(args.Args.Used.Value, args.Args.User, args.Args.Target.Value, component);
args.Handled = true;
component.CancelToken = null;
}
private sealed class ImplantEvent : EntityEventArgs
{
}
private sealed class DrawEvent : EntityEventArgs
{
}
}

View File

@@ -1,14 +1,13 @@
using Content.Server.Administration.Logs;
using Content.Server.DoAfter;
using Content.Server.Kitchen.Components;
using Content.Server.Nutrition.Components;
using Content.Server.Popups;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Nutrition.Components;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Content.Shared.Storage;
using Robust.Shared.Random;
@@ -18,7 +17,6 @@ using Content.Shared.Kitchen;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;
using Robust.Server.GameObjects;
namespace Content.Server.Kitchen.EntitySystems
{
@@ -30,6 +28,7 @@ namespace Content.Server.Kitchen.EntitySystems
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
@@ -40,8 +39,7 @@ namespace Content.Server.Kitchen.EntitySystems
SubscribeLocalEvent<KitchenSpikeComponent, DragDropTargetEvent>(OnDragDrop);
//DoAfter
SubscribeLocalEvent<KitchenSpikeComponent, SpikingFinishedEvent>(OnSpikingFinished);
SubscribeLocalEvent<KitchenSpikeComponent, SpikingFailEvent>(OnSpikingFail);
SubscribeLocalEvent<KitchenSpikeComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<KitchenSpikeComponent, SuicideEvent>(OnSuicide);
@@ -56,35 +54,32 @@ namespace Content.Server.Kitchen.EntitySystems
private void OnSuicide(EntityUid uid, KitchenSpikeComponent component, SuicideEvent args)
{
if (args.Handled) return;
if (args.Handled)
return;
args.SetHandled(SuicideKind.Piercing);
var victim = args.Victim;
var othersMessage = Loc.GetString("comp-kitchen-spike-suicide-other", ("victim", victim));
victim.PopupMessageOtherClients(othersMessage);
_popupSystem.PopupEntity(othersMessage, victim);
var selfMessage = Loc.GetString("comp-kitchen-spike-suicide-self");
victim.PopupMessage(selfMessage);
_popupSystem.PopupEntity(selfMessage, victim, victim);
}
private void OnSpikingFail(EntityUid uid, KitchenSpikeComponent component, SpikingFailEvent args)
private void OnDoAfter(EntityUid uid, KitchenSpikeComponent component, DoAfterEvent args)
{
component.InUse = false;
if (args.Args.Target == null)
return;
if (EntityManager.TryGetComponent<ButcherableComponent>(args.VictimUid, out var butcherable))
butcherable.BeingButchered = false;
}
private void OnSpikingFinished(EntityUid uid, KitchenSpikeComponent component, SpikingFinishedEvent args)
{
component.InUse = false;
if (EntityManager.TryGetComponent<ButcherableComponent>(args.VictimUid, out var butcherable))
if (TryComp<ButcherableComponent>(args.Args.Target.Value, out var butcherable))
butcherable.BeingButchered = false;
if (Spikeable(uid, args.UserUid, args.VictimUid, component, butcherable))
{
Spike(uid, args.UserUid, args.VictimUid, component);
}
if (args.Handled || args.Cancelled)
return;
if (Spikeable(uid, args.Args.User, args.Args.Target.Value, component, butcherable))
Spike(uid, args.Args.User, args.Args.Target.Value, component);
args.Handled = true;
}
private void OnDragDrop(EntityUid uid, KitchenSpikeComponent component, ref DragDropTargetEvent args)
@@ -96,8 +91,8 @@ namespace Content.Server.Kitchen.EntitySystems
if (Spikeable(uid, args.User, args.Dragged, component))
TrySpike(uid, args.User, args.Dragged, component);
}
private void OnInteractHand(EntityUid uid, KitchenSpikeComponent component, InteractHandEvent args)
{
if (args.Handled)
@@ -142,7 +137,7 @@ namespace Content.Server.Kitchen.EntitySystems
// TODO: Need to be able to leave them on the spike to do DoT, see ss13.
EntityManager.QueueDeleteEntity(victimUid);
SoundSystem.Play(component.SpikeSound.GetSound(), Filter.Pvs(uid), uid);
_audio.Play(component.SpikeSound, Filter.Pvs(uid), uid, true);
}
private bool TryGetPiece(EntityUid uid, EntityUid user, EntityUid used,
@@ -164,9 +159,7 @@ namespace Content.Server.Kitchen.EntitySystems
Loc.GetString("comp-kitchen-spike-meat-name", ("name", Name(ent)), ("victim", component.Victim));
if (component.PrototypesToSpawn.Count != 0)
{
_popupSystem.PopupEntity(component.MeatSource1p, uid, user, PopupType.MediumCaution);
}
else
{
UpdateAppearance(uid, null, component);
@@ -243,42 +236,18 @@ namespace Content.Server.Kitchen.EntitySystems
butcherable.BeingButchered = true;
component.InUse = true;
var doAfterArgs = new DoAfterEventArgs(userUid, component.SpikeDelay + butcherable.ButcherDelay, default, uid)
var doAfterArgs = new DoAfterEventArgs(userUid, component.SpikeDelay + butcherable.ButcherDelay, target:victimUid, used:uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
NeedHand = true,
TargetFinishedEvent = new SpikingFinishedEvent(userUid, victimUid),
TargetCancelledEvent = new SpikingFailEvent(victimUid)
NeedHand = true
};
_doAfter.DoAfter(doAfterArgs);
return true;
}
private sealed class SpikingFinishedEvent : EntityEventArgs
{
public EntityUid VictimUid;
public EntityUid UserUid;
public SpikingFinishedEvent(EntityUid userUid, EntityUid victimUid)
{
UserUid = userUid;
VictimUid = victimUid;
}
}
private sealed class SpikingFailEvent : EntityEventArgs
{
public EntityUid VictimUid;
public SpikingFailEvent(EntityUid victimUid)
{
VictimUid = victimUid;
}
}
}
}

View File

@@ -1,4 +1,4 @@
using Content.Server.Body.Systems;
using Content.Server.Body.Systems;
using Content.Server.DoAfter;
using Content.Server.Kitchen.Components;
using Content.Shared.Body.Components;
@@ -8,10 +8,10 @@ using Content.Shared.Popups;
using Content.Shared.Storage;
using Content.Shared.Verbs;
using Content.Shared.Destructible;
using Content.Shared.DoAfter;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Robust.Server.Containers;
using Robust.Shared.Player;
using Robust.Shared.Random;
namespace Content.Server.Kitchen.EntitySystems;
@@ -31,8 +31,7 @@ public sealed class SharpSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<SharpComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<SharpButcherDoafterComplete>(OnDoafterComplete);
SubscribeLocalEvent<SharpButcherDoafterCancelled>(OnDoafterCancelled);
SubscribeLocalEvent<SharpComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<ButcherableComponent, GetVerbsEvent<InteractionVerb>>(OnGetInteractionVerbs);
}
@@ -63,64 +62,56 @@ public sealed class SharpSystem : EntitySystem
return;
var doAfter =
new DoAfterEventArgs(user, sharp.ButcherDelayModifier * butcher.ButcherDelay, default, target)
new DoAfterEventArgs(user, sharp.ButcherDelayModifier * butcher.ButcherDelay, target: target, used: knife)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
NeedHand = true,
BroadcastFinishedEvent = new SharpButcherDoafterComplete { User = user, Entity = target, Sharp = knife },
BroadcastCancelledEvent = new SharpButcherDoafterCancelled { Entity = target, Sharp = knife }
NeedHand = true
};
_doAfterSystem.DoAfter(doAfter);
}
private void OnDoafterComplete(SharpButcherDoafterComplete ev)
private void OnDoAfter(EntityUid uid, SharpComponent component, DoAfterEvent args)
{
if (!TryComp<ButcherableComponent>(ev.Entity, out var butcher))
if (args.Handled || args.Cancelled || !TryComp<ButcherableComponent>(args.Args.Target, out var butcher))
return;
if (!TryComp<SharpComponent>(ev.Sharp, out var sharp))
return;
component.Butchering.Remove(args.Args.Target.Value);
sharp.Butchering.Remove(ev.Entity);
if (_containerSystem.IsEntityInContainer(ev.Entity))
if (_containerSystem.IsEntityInContainer(args.Args.Target.Value))
{
args.Handled = true;
return;
}
var spawnEntities = EntitySpawnCollection.GetSpawns(butcher.SpawnedEntities, _robustRandom);
var coords = Transform(ev.Entity).MapPosition;
EntityUid popupEnt = default;
var coords = Transform(args.Args.Target.Value).MapPosition;
EntityUid popupEnt = default!;
foreach (var proto in spawnEntities)
{
// distribute the spawned items randomly in a small radius around the origin
popupEnt = Spawn(proto, coords.Offset(_robustRandom.NextVector2(0.25f)));
}
var hasBody = TryComp<BodyComponent>(ev.Entity, out var body);
var hasBody = TryComp<BodyComponent>(args.Args.Target.Value, out var body);
// only show a big popup when butchering living things.
var popupType = PopupType.Small;
if (hasBody)
popupType = PopupType.LargeCaution;
_popupSystem.PopupEntity(Loc.GetString("butcherable-knife-butchered-success", ("target", ev.Entity), ("knife", ev.Sharp)),
popupEnt, ev.User, popupType);
_popupSystem.PopupEntity(Loc.GetString("butcherable-knife-butchered-success", ("target", args.Args.Target.Value), ("knife", uid)),
popupEnt, args.Args.User, popupType);
if (hasBody)
_bodySystem.GibBody(body!.Owner, body: body);
_bodySystem.GibBody(args.Args.Target.Value, body: body);
_destructibleSystem.DestroyEntity(ev.Entity);
}
_destructibleSystem.DestroyEntity(args.Args.Target.Value);
private void OnDoafterCancelled(SharpButcherDoafterCancelled ev)
{
if (!TryComp<SharpComponent>(ev.Sharp, out var sharp))
return;
sharp.Butchering.Remove(ev.Entity);
args.Handled = true;
}
private void OnGetInteractionVerbs(EntityUid uid, ButcherableComponent component, GetVerbsEvent<InteractionVerb> args)
@@ -165,16 +156,3 @@ public sealed class SharpSystem : EntitySystem
args.Verbs.Add(verb);
}
}
public sealed class SharpButcherDoafterComplete : EntityEventArgs
{
public EntityUid Entity;
public EntityUid Sharp;
public EntityUid User;
}
public sealed class SharpButcherDoafterCancelled : EntityEventArgs
{
public EntityUid Entity;
public EntityUid Sharp;
}

View File

@@ -64,8 +64,6 @@ namespace Content.Server.Light.Components
[DataField("togglePort", customTypeSerializer: typeof(PrototypeIdSerializer<ReceiverPortPrototype>))]
public string TogglePort = "Toggle";
public CancellationTokenSource? CancelToken;
/// <summary>
/// How long it takes to eject a bulb from this
/// </summary>

View File

@@ -21,7 +21,7 @@ using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using System.Threading;
using Content.Shared.DoAfter;
namespace Content.Server.Light.EntitySystems
{
@@ -40,6 +40,7 @@ namespace Content.Server.Light.EntitySystems
[Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2);
@@ -61,8 +62,7 @@ namespace Content.Server.Light.EntitySystems
SubscribeLocalEvent<PoweredLightComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<PoweredLightComponent, EjectBulbCompleteEvent>(OnEjectBulbComplete);
SubscribeLocalEvent<PoweredLightComponent, EjectBulbCancelledEvent>(OnEjectBulbCancelled);
SubscribeLocalEvent<PoweredLightComponent, DoAfterEvent>(OnDoAfter);
}
private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args)
@@ -95,9 +95,6 @@ namespace Content.Server.Light.EntitySystems
if (args.Handled)
return;
if (light.CancelToken != null)
return;
// check if light has bulb to eject
var bulbUid = GetBulb(uid, light);
if (bulbUid == null)
@@ -122,10 +119,9 @@ namespace Content.Server.Light.EntitySystems
var damage = _damageableSystem.TryChangeDamage(userUid, light.Damage, origin: userUid);
if (damage != null)
_adminLogger.Add(LogType.Damaged,
$"{ToPrettyString(args.User):user} burned their hand on {ToPrettyString(args.Target):target} and received {damage.Total:damage} damage");
_adminLogger.Add(LogType.Damaged, $"{ToPrettyString(args.User):user} burned their hand on {ToPrettyString(args.Target):target} and received {damage.Total:damage} damage");
SoundSystem.Play(light.BurnHandSound.GetSound(), Filter.Pvs(uid), uid);
_audio.Play(light.BurnHandSound, Filter.Pvs(uid), uid, true);
args.Handled = true;
return;
@@ -141,22 +137,11 @@ namespace Content.Server.Light.EntitySystems
}
// removing a working bulb, so require a delay
light.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, light.EjectBulbDelay, light.CancelToken.Token, uid)
_doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, light.EjectBulbDelay, target:uid)
{
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
TargetFinishedEvent = new EjectBulbCompleteEvent()
{
Component = light,
User = userUid,
Target = uid,
},
TargetCancelledEvent = new EjectBulbCancelledEvent()
{
Component = light,
}
BreakOnStun = true
});
args.Handled = true;
@@ -287,7 +272,7 @@ namespace Content.Server.Light.EntitySystems
if (time > light.LastThunk + ThunkDelay)
{
light.LastThunk = time;
SoundSystem.Play(light.TurnOnSound.GetSound(), Filter.Pvs(uid), uid, AudioParams.Default.WithVolume(-10f));
_audio.Play(light.TurnOnSound, Filter.Pvs(uid), uid, true, AudioParams.Default.WithVolume(-10f));
}
}
else
@@ -358,7 +343,7 @@ namespace Content.Server.Light.EntitySystems
light.IsBlinking = isNowBlinking;
if (!EntityManager.TryGetComponent(light.Owner, out AppearanceComponent? appearance))
if (!EntityManager.TryGetComponent(uid, out AppearanceComponent? appearance))
return;
_appearance.SetData(uid, PoweredLightVisuals.Blinking, isNowBlinking, appearance);
@@ -427,27 +412,14 @@ namespace Content.Server.Light.EntitySystems
UpdateLight(uid, light);
}
private void OnEjectBulbComplete(EntityUid uid, PoweredLightComponent component, EjectBulbCompleteEvent args)
private void OnDoAfter(EntityUid uid, PoweredLightComponent component, DoAfterEvent args)
{
args.Component.CancelToken = null;
EjectBulb(args.Target, args.User, args.Component);
}
if (args.Handled || args.Cancelled || args.Args.Target == null)
return;
private static void OnEjectBulbCancelled(EntityUid uid, PoweredLightComponent component, EjectBulbCancelledEvent args)
{
args.Component.CancelToken = null;
}
EjectBulb(args.Args.Target.Value, args.Args.User, component);
private sealed class EjectBulbCompleteEvent : EntityEventArgs
{
public PoweredLightComponent Component { get; init; } = default!;
public EntityUid User { get; init; }
public EntityUid Target { get; init; }
}
private sealed class EjectBulbCancelledEvent : EntityEventArgs
{
public PoweredLightComponent Component { get; init; } = default!;
args.Handled = true;
}
}
}

View File

@@ -1,5 +1,4 @@
using System.Threading;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Actions.ActionTypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
namespace Content.Server.Magic;
@@ -30,6 +29,4 @@ public sealed class SpellbookComponent : Component
[DataField("learnTime")]
public float LearnTime = .75f;
public CancellationTokenSource? CancelToken;
}

View File

@@ -9,6 +9,7 @@ using Content.Server.Weapons.Ranged.Systems;
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Body.Components;
using Content.Shared.DoAfter;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Content.Shared.Interaction.Events;
@@ -53,8 +54,7 @@ public sealed class MagicSystem : EntitySystem
SubscribeLocalEvent<SpellbookComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<SpellbookComponent, UseInHandEvent>(OnUse);
SubscribeLocalEvent<SpellbookComponent, LearnDoAfterComplete>(OnLearnComplete);
SubscribeLocalEvent<SpellbookComponent, LearnDoAfterCancel>(OnLearnCancel);
SubscribeLocalEvent<SpellbookComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<InstantSpawnSpellEvent>(OnInstantSpawn);
SubscribeLocalEvent<TeleportSpellEvent>(OnTeleportSpell);
@@ -65,6 +65,15 @@ public sealed class MagicSystem : EntitySystem
SubscribeLocalEvent<ChangeComponentsSpellEvent>(OnChangeComponentsSpell);
}
private void OnDoAfter(EntityUid uid, SpellbookComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled)
return;
_actionsSystem.AddActions(args.Args.User, component.Spells, uid);
args.Handled = true;
}
private void OnInit(EntityUid uid, SpellbookComponent component, ComponentInit args)
{
//Negative charges means the spell can be used without it running out.
@@ -102,35 +111,18 @@ public sealed class MagicSystem : EntitySystem
private void AttemptLearn(EntityUid uid, SpellbookComponent component, UseInHandEvent args)
{
if (component.CancelToken != null) return;
component.CancelToken = new CancellationTokenSource();
var doAfterEventArgs = new DoAfterEventArgs(args.User, component.LearnTime, component.CancelToken.Token, uid)
var doAfterEventArgs = new DoAfterEventArgs(args.User, component.LearnTime, target:uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
NeedHand = true, //What, are you going to read with your eyes only??
TargetFinishedEvent = new LearnDoAfterComplete(args.User),
TargetCancelledEvent = new LearnDoAfterCancel(),
NeedHand = true //What, are you going to read with your eyes only??
};
_doAfter.DoAfter(doAfterEventArgs);
}
private void OnLearnComplete(EntityUid uid, SpellbookComponent component, LearnDoAfterComplete ev)
{
component.CancelToken = null;
_actionsSystem.AddActions(ev.User, component.Spells, uid);
}
private void OnLearnCancel(EntityUid uid, SpellbookComponent component, LearnDoAfterCancel args)
{
component.CancelToken = null;
}
#region Spells
/// <summary>
@@ -383,20 +375,4 @@ public sealed class MagicSystem : EntitySystem
}
#endregion
#region DoAfterClasses
private sealed class LearnDoAfterComplete : EntityEventArgs
{
public readonly EntityUid User;
public LearnDoAfterComplete(EntityUid uid)
{
User = uid;
}
}
private sealed class LearnDoAfterCancel : EntityEventArgs { }
#endregion
}

View File

@@ -1,5 +1,4 @@
using System.Threading;
using Content.Server.Atmos;
using Content.Server.Atmos;
using Content.Shared.Mech.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
@@ -32,8 +31,6 @@ public sealed class MechComponent : SharedMechComponent
[DataField("batteryRemovalDelay")]
public float BatteryRemovalDelay = 2;
public CancellationTokenSource? EntryTokenSource;
/// <summary>
/// Whether or not the mech is airtight.
/// </summary>

View File

@@ -46,29 +46,4 @@ public sealed class MechGrabberComponent : Component
[ViewVariables(VVAccess.ReadWrite)]
public Container ItemContainer = default!;
public CancellationTokenSource? Token;
}
/// <summary>
/// Event raised on the user when the grab is complete.
/// </summary>
public sealed class MechGrabberGrabFinishedEvent : EntityEventArgs
{
/// <summary>
/// The thing that was grabbed.
/// </summary>
public EntityUid Grabbed;
public MechGrabberGrabFinishedEvent(EntityUid grabbed)
{
Grabbed = grabbed;
}
}
/// <summary>
/// Event raised on the user when the grab fails
/// </summary>
public sealed class MechGrabberGrabCancelledEvent : EntityEventArgs
{
}

View File

@@ -4,6 +4,8 @@ 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;
@@ -39,8 +41,7 @@ public sealed class MechGrabberSystem : EntitySystem
SubscribeLocalEvent<MechGrabberComponent, AttemptRemoveMechEquipmentEvent>(OnAttemptRemove);
SubscribeLocalEvent<MechGrabberComponent, InteractNoHandEvent>(OnInteract);
SubscribeLocalEvent<MechGrabberComponent, MechGrabberGrabFinishedEvent>(OnGrabFinished);
SubscribeLocalEvent<MechGrabberComponent, MechGrabberGrabCancelledEvent>(OnGrabCancelled);
SubscribeLocalEvent<MechGrabberComponent, DoAfterEvent>(OnMechGrab);
}
private void OnGrabberMessage(EntityUid uid, MechGrabberComponent component, MechEquipmentUiMessageRelayEvent args)
@@ -144,40 +145,37 @@ public sealed class MechGrabberSystem : EntitySystem
if (mech.Energy + component.GrabEnergyDelta < 0)
return;
if (component.Token != null)
return;
if (!_interaction.InRangeUnobstructed(args.User, target))
return;
args.Handled = true;
component.Token = new();
component.AudioStream = _audio.PlayPvs(component.GrabSound, uid);
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.GrabDelay, component.Token.Token, target, uid)
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.GrabDelay, target:target, used:uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
UsedFinishedEvent = new MechGrabberGrabFinishedEvent(target),
UsedCancelledEvent = new MechGrabberGrabCancelledEvent()
BreakOnUserMove = true
});
}
private void OnGrabFinished(EntityUid uid, MechGrabberComponent component, MechGrabberGrabFinishedEvent args)
private void OnMechGrab(EntityUid uid, MechGrabberComponent component, DoAfterEvent args)
{
component.Token = null;
if (args.Cancelled)
{
component.AudioStream?.Stop();
return;
}
if (args.Handled || args.Args.Target == null)
return;
if (!TryComp<MechEquipmentComponent>(uid, out var equipmentComponent) || equipmentComponent.EquipmentOwner == null)
return;
if (!_mech.TryChangeEnergy(equipmentComponent.EquipmentOwner.Value, component.GrabEnergyDelta))
return;
component.ItemContainer.Insert(args.Grabbed);
component.ItemContainer.Insert(args.Args.Target.Value);
_mech.UpdateUserInterface(equipmentComponent.EquipmentOwner.Value);
}
private void OnGrabCancelled(EntityUid uid, MechGrabberComponent component, MechGrabberGrabCancelledEvent args)
{
component.AudioStream?.Stop();
component.Token = null;
args.Handled = true;
}
}

View File

@@ -1,9 +1,9 @@
using Content.Server.DoAfter;
using Content.Server.Mech.Components;
using Content.Server.Popups;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Mech.Equipment.Components;
using Robust.Shared.Player;
namespace Content.Server.Mech.Systems;
@@ -20,15 +20,11 @@ public sealed class MechEquipmentSystem : EntitySystem
public override void Initialize()
{
SubscribeLocalEvent<MechEquipmentComponent, AfterInteractEvent>(OnUsed);
SubscribeLocalEvent<MechEquipmentComponent, MechEquipmentInstallFinished>(OnFinished);
SubscribeLocalEvent<MechEquipmentComponent, MechEquipmentInstallCancelled>(OnCancelled);
SubscribeLocalEvent<MechEquipmentComponent, DoAfterEvent<InsertEquipmentEvent>>(OnInsertEquipment);
}
private void OnUsed(EntityUid uid, MechEquipmentComponent component, AfterInteractEvent args)
{
if (component.TokenSource != null)
return;
if (args.Handled || !args.CanReach || args.Target == null)
return;
@@ -50,26 +46,30 @@ public sealed class MechEquipmentSystem : EntitySystem
_popup.PopupEntity(Loc.GetString("mech-equipment-begin-install", ("item", uid)), mech);
component.TokenSource = new();
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.InstallDuration, component.TokenSource.Token, mech, uid)
var insertEquipment = new InsertEquipmentEvent();
var doAfterEventArgs = new DoAfterEventArgs(args.User, component.InstallDuration, target: mech, used: uid)
{
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
UsedFinishedEvent = new MechEquipmentInstallFinished(mech),
UsedCancelledEvent = new MechEquipmentInstallCancelled()
});
BreakOnUserMove = true
};
_doAfter.DoAfter(doAfterEventArgs, insertEquipment);
}
private void OnFinished(EntityUid uid, MechEquipmentComponent component, MechEquipmentInstallFinished args)
private void OnInsertEquipment(EntityUid uid, MechEquipmentComponent component, DoAfterEvent<InsertEquipmentEvent> args)
{
component.TokenSource = null;
_popup.PopupEntity(Loc.GetString("mech-equipment-finish-install", ("item", uid)), args.Mech);
_mech.InsertEquipment(args.Mech, uid);
if (args.Handled || args.Cancelled || args.Args.Target == null)
return;
_popup.PopupEntity(Loc.GetString("mech-equipment-finish-install", ("item", uid)), args.Args.Target.Value);
_mech.InsertEquipment(args.Args.Target.Value, uid);
args.Handled = true;
}
private void OnCancelled(EntityUid uid, MechEquipmentComponent component, MechEquipmentInstallCancelled args)
private sealed class InsertEquipmentEvent : EntityEventArgs
{
component.TokenSource = null;
}
}

View File

@@ -1,19 +1,17 @@
using System.Linq;
using System.Threading;
using Content.Server.Atmos.EntitySystems;
using Content.Server.DoAfter;
using Content.Server.Mech.Components;
using Content.Server.Power.Components;
using Content.Server.Tools;
using Content.Server.Wires;
using Content.Shared.ActionBlocker;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Content.Shared.Mech;
using Content.Shared.Mech.Components;
using Content.Shared.Mech.EntitySystems;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.Tools.Components;
using Content.Shared.Verbs;
@@ -47,12 +45,9 @@ public sealed class MechSystem : SharedMechSystem
SubscribeLocalEvent<MechComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<MechComponent, GetVerbsEvent<AlternativeVerb>>(OnAlternativeVerb);
SubscribeLocalEvent<MechComponent, MechOpenUiEvent>(OnOpenUi);
SubscribeLocalEvent<MechComponent, MechEntryFinishedEvent>(OnEntryFinished);
SubscribeLocalEvent<MechComponent, MechEntryCanclledEvent>(OnEntryExitCancelled);
SubscribeLocalEvent<MechComponent, MechExitFinishedEvent>(OnExitFinished);
SubscribeLocalEvent<MechComponent, MechExitCanclledEvent>(OnEntryExitCancelled);
SubscribeLocalEvent<MechComponent, MechRemoveBatteryFinishedEvent>(OnRemoveBatteryFinished);
SubscribeLocalEvent<MechComponent, MechRemoveBatteryCancelledEvent>(OnRemoveBatteryCancelled);
SubscribeLocalEvent<MechComponent, DoAfterEvent<RemoveBatteryEvent>>(OnRemoveBattery);
SubscribeLocalEvent<MechComponent, DoAfterEvent<MechEntryEvent>>(OnMechEntry);
SubscribeLocalEvent<MechComponent, DoAfterEvent<MechExitEvent>>(OnMechExit);
SubscribeLocalEvent<MechComponent, DamageChangedEvent>(OnDamageChanged);
SubscribeLocalEvent<MechComponent, MechEquipmentRemoveMessage>(OnRemoveEquipmentMessage);
@@ -87,32 +82,29 @@ public sealed class MechSystem : SharedMechSystem
return;
}
if (component.EntryTokenSource == null &&
TryComp<ToolComponent>(args.Used, out var tool) &&
tool.Qualities.Contains("Prying") &&
component.BatterySlot.ContainedEntity != null)
if (TryComp<ToolComponent>(args.Used, out var tool) && tool.Qualities.Contains("Prying") && component.BatterySlot.ContainedEntity != null)
{
component.EntryTokenSource = new();
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.BatteryRemovalDelay, component.EntryTokenSource.Token, uid, args.Target)
var removeBattery = new RemoveBatteryEvent();
var doAfterEventArgs = new DoAfterEventArgs(args.User, component.BatteryRemovalDelay, target: uid, used: args.Target)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
TargetFinishedEvent = new MechRemoveBatteryFinishedEvent(),
TargetCancelledEvent = new MechRemoveBatteryCancelledEvent()
});
BreakOnUserMove = true
};
_doAfter.DoAfter(doAfterEventArgs, removeBattery);
}
}
private void OnRemoveBatteryFinished(EntityUid uid, MechComponent component, MechRemoveBatteryFinishedEvent args)
private void OnRemoveBattery(EntityUid uid, MechComponent component, DoAfterEvent<RemoveBatteryEvent> args)
{
component.EntryTokenSource = null;
if (args.Cancelled || args.Handled)
return;
RemoveBattery(uid, component);
_actionBlocker.UpdateCanMove(uid);
}
private void OnRemoveBatteryCancelled(EntityUid uid, MechComponent component, MechRemoveBatteryCancelledEvent args)
{
component.EntryTokenSource = null;
args.Handled = true;
}
private void OnMapInit(EntityUid uid, MechComponent component, MapInitEvent args)
@@ -171,16 +163,14 @@ public sealed class MechSystem : SharedMechSystem
Text = Loc.GetString("mech-verb-enter"),
Act = () =>
{
if (component.EntryTokenSource != null)
return;
component.EntryTokenSource = new CancellationTokenSource();
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.EntryDelay, component.EntryTokenSource.Token, uid)
var mechEntryEvent = new MechEntryEvent();
var doAfterEventArgs = new DoAfterEventArgs(args.User, component.EntryDelay, target: uid)
{
BreakOnUserMove = true,
BreakOnStun = true,
TargetFinishedEvent = new MechEntryFinishedEvent(args.User),
TargetCancelledEvent = new MechEntryCanclledEvent()
});
};
_doAfter.DoAfter(doAfterEventArgs, mechEntryEvent);
}
};
var openUiVerb = new AlternativeVerb //can't hijack someone else's mech
@@ -199,45 +189,46 @@ public sealed class MechSystem : SharedMechSystem
Priority = 1, // Promote to top to make ejecting the ALT-click action
Act = () =>
{
if (component.EntryTokenSource != null)
return;
if (args.User == component.PilotSlot.ContainedEntity)
{
TryEject(uid, component);
return;
}
component.EntryTokenSource = new CancellationTokenSource();
_doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ExitDelay, component.EntryTokenSource.Token, uid)
var mechExitEvent = new MechExitEvent();
var doAfterEventArgs = new DoAfterEventArgs(args.User, component.ExitDelay, target: uid)
{
BreakOnUserMove = true,
BreakOnTargetMove = true,
BreakOnStun = true,
TargetFinishedEvent = new MechExitFinishedEvent(),
TargetCancelledEvent = new MechExitCanclledEvent()
});
BreakOnStun = true
};
_doAfter.DoAfter(doAfterEventArgs, mechExitEvent);
}
};
args.Verbs.Add(ejectVerb);
}
}
private void OnEntryFinished(EntityUid uid, MechComponent component, MechEntryFinishedEvent args)
private void OnMechEntry(EntityUid uid, MechComponent component, DoAfterEvent<MechEntryEvent> args)
{
component.EntryTokenSource = null;
TryInsert(uid, args.User, component);
if (args.Cancelled || args.Handled)
return;
TryInsert(uid, args.Args.User, component);
_actionBlocker.UpdateCanMove(uid);
args.Handled = true;
}
private void OnExitFinished(EntityUid uid, MechComponent component, MechExitFinishedEvent args)
private void OnMechExit(EntityUid uid, MechComponent component, DoAfterEvent<MechExitEvent> args)
{
component.EntryTokenSource = null;
if (args.Cancelled || args.Handled)
return;
TryEject(uid, component);
}
private void OnEntryExitCancelled(EntityUid uid, MechComponent component, EntityEventArgs args)
{
component.EntryTokenSource = null;
args.Handled = true;
}
private void OnDamageChanged(EntityUid uid, SharedMechComponent component, DamageChangedEvent args)
@@ -454,4 +445,27 @@ public sealed class MechSystem : SharedMechSystem
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
{
}
}

View File

@@ -9,8 +9,6 @@ namespace Content.Server.Medical.BiomassReclaimer
[RegisterComponent]
public sealed class BiomassReclaimerComponent : Component
{
public CancellationTokenSource? CancelToken;
/// <summary>
/// This gets set for each mob it processes.
/// When it hits 0, there is a chance for the reclaimer to either spill blood or throw an item.

View File

@@ -17,6 +17,7 @@ using Content.Server.Construction;
using Content.Server.DoAfter;
using Content.Server.Materials;
using Content.Server.Mind.Components;
using Content.Shared.DoAfter;
using Content.Shared.Humanoid;
using Content.Shared.Interaction.Events;
using Content.Shared.Mobs.Components;
@@ -96,8 +97,7 @@ namespace Content.Server.Medical.BiomassReclaimer
SubscribeLocalEvent<BiomassReclaimerComponent, UpgradeExamineEvent>(OnUpgradeExamine);
SubscribeLocalEvent<BiomassReclaimerComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<BiomassReclaimerComponent, SuicideEvent>(OnSuicide);
SubscribeLocalEvent<ReclaimSuccessfulEvent>(OnReclaimSuccessful);
SubscribeLocalEvent<ReclaimCancelledEvent>(OnReclaimCancelled);
SubscribeLocalEvent<BiomassReclaimerComponent, DoAfterEvent>(OnDoAfter);
}
private void OnSuicide(EntityUid uid, BiomassReclaimerComponent component, SuicideEvent args)
@@ -146,20 +146,14 @@ namespace Content.Server.Medical.BiomassReclaimer
}
private void OnAfterInteractUsing(EntityUid uid, BiomassReclaimerComponent component, AfterInteractUsingEvent args)
{
if (!args.CanReach)
return;
if (component.CancelToken != null || args.Target == null)
if (!args.CanReach || args.Target == null)
return;
if (!HasComp<MobStateComponent>(args.Used) || !CanGib(uid, args.Used, component))
return;
component.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, 7f, component.CancelToken.Token, args.Target, args.Used)
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, 7f, target:args.Target, used:args.Used)
{
BroadcastFinishedEvent = new ReclaimSuccessfulEvent(args.User, args.Used, uid),
BroadcastCancelledEvent = new ReclaimCancelledEvent(uid),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
@@ -200,21 +194,15 @@ namespace Content.Server.Medical.BiomassReclaimer
args.AddPercentageUpgrade("biomass-reclaimer-component-upgrade-biomass-yield", component.YieldPerUnitMass / component.BaseYieldPerUnitMass);
}
private void OnReclaimSuccessful(ReclaimSuccessfulEvent args)
private void OnDoAfter(EntityUid uid, BiomassReclaimerComponent component, DoAfterEvent args)
{
if (!TryComp<BiomassReclaimerComponent>(args.Reclaimer, out var reclaimer))
if (args.Handled || args.Cancelled || args.Args.Target == null || HasComp<BiomassReclaimerComponent>(args.Args.Target.Value))
return;
_adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(args.User):player} used a biomass reclaimer to gib {ToPrettyString(args.Target):target} in {ToPrettyString(args.Reclaimer):reclaimer}");
reclaimer.CancelToken = null;
StartProcessing(args.Target, reclaimer);
}
_adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(args.Args.User):player} used a biomass reclaimer to gib {ToPrettyString(args.Args.Target.Value):target} in {ToPrettyString(uid):reclaimer}");
StartProcessing(args.Args.Target.Value, component);
private void OnReclaimCancelled(ReclaimCancelledEvent args)
{
if (!TryComp<BiomassReclaimerComponent>(args.Reclaimer, out var reclaimer))
return;
reclaimer.CancelToken = null;
args.Handled = true;
}
private void StartProcessing(EntityUid toProcess, BiomassReclaimerComponent component, PhysicsComponent? physics = null)
@@ -266,28 +254,5 @@ namespace Content.Server.Medical.BiomassReclaimer
return true;
}
private readonly struct ReclaimCancelledEvent
{
public readonly EntityUid Reclaimer;
public ReclaimCancelledEvent(EntityUid reclaimer)
{
Reclaimer = reclaimer;
}
}
private readonly struct ReclaimSuccessfulEvent
{
public readonly EntityUid User;
public readonly EntityUid Target;
public readonly EntityUid Reclaimer;
public ReclaimSuccessfulEvent(EntityUid user, EntityUid target, EntityUid reclaimer)
{
User = user;
Target = target;
Reclaimer = reclaimer;
}
}
}
}

View File

@@ -45,8 +45,6 @@ namespace Content.Server.Medical.Components
[DataField("selfHealPenaltyMultiplier")]
public float SelfHealPenaltyMultiplier = 3f;
public CancellationTokenSource? CancelToken = null;
/// <summary>
/// Sound played on healing begin
/// </summary>

View File

@@ -20,10 +20,7 @@ namespace Content.Server.Medical.Components
/// </summary>
[DataField("scanDelay")]
public float ScanDelay = 0.8f;
/// <summary>
/// Token for interrupting scanning do after.
/// </summary>
public CancellationTokenSource? CancelToken;
public BoundUserInterface? UserInterface => Owner.GetUIOrNull(HealthAnalyzerUiKey.Key);
/// <summary>

View File

@@ -1,4 +1,3 @@
using System.Threading;
using Content.Server.Atmos;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Piping.Components;
@@ -14,18 +13,19 @@ using Content.Server.NodeContainer;
using Content.Server.NodeContainer.NodeGroups;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components;
using Content.Server.Tools;
using Content.Server.UserInterface;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Emag.Systems;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Medical.Cryogenics;
using Content.Shared.MedicalScanner;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
@@ -44,7 +44,7 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly ToolSystem _toolSystem = default!;
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly MetaDataSystem _metaDataSystem = default!;
[Dependency] private readonly ReactiveSystem _reactiveSystem = default!;
@@ -57,8 +57,7 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem
SubscribeLocalEvent<CryoPodComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<CryoPodComponent, GetVerbsEvent<AlternativeVerb>>(AddAlternativeVerbs);
SubscribeLocalEvent<CryoPodComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<CryoPodComponent, DoInsertCryoPodEvent>(DoInsertCryoPod);
SubscribeLocalEvent<CryoPodComponent, DoInsertCancelledCryoPodEvent>(DoInsertCancelCryoPod);
SubscribeLocalEvent<CryoPodComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<CryoPodComponent, CryoPodPryFinished>(OnCryoPodPryFinished);
SubscribeLocalEvent<CryoPodComponent, CryoPodPryInterrupted>(OnCryoPodPryInterrupted);
@@ -130,32 +129,30 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem
private void HandleDragDropOn(EntityUid uid, CryoPodComponent cryoPodComponent, ref DragDropTargetEvent args)
{
if (cryoPodComponent.BodyContainer.ContainedEntity != null)
{
return;
}
if (cryoPodComponent.DragDropCancelToken != null)
{
cryoPodComponent.DragDropCancelToken.Cancel();
cryoPodComponent.DragDropCancelToken = null;
return;
}
cryoPodComponent.DragDropCancelToken = new CancellationTokenSource();
var doAfterArgs = new DoAfterEventArgs(args.User, cryoPodComponent.EntryDelay, cryoPodComponent.DragDropCancelToken.Token, uid, args.Dragged)
var doAfterArgs = new DoAfterEventArgs(args.User, cryoPodComponent.EntryDelay, target:args.Dragged, used:uid)
{
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = false,
TargetFinishedEvent = new DoInsertCryoPodEvent(args.Dragged),
TargetCancelledEvent = new DoInsertCancelledCryoPodEvent()
};
_doAfterSystem.DoAfter(doAfterArgs);
args.Handled = true;
}
private void OnDoAfter(EntityUid uid, CryoPodComponent component, DoAfterEvent args)
{
if (args.Cancelled || args.Handled || args.Args.Target == null)
return;
InsertBody(uid, args.Args.Target.Value, component);
args.Handled = true;
}
private void OnActivateUIAttempt(EntityUid uid, CryoPodComponent cryoPodComponent, ActivatableUIOpenAttemptEvent args)
{
if (args.Cancelled)
@@ -190,9 +187,8 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem
return;
cryoPodComponent.IsPrying = true;
_toolSystem.UseTool(args.Used, args.User, uid, 0f,
cryoPodComponent.PryDelay, "Prying",
new CryoPodPryFinished(), new CryoPodPryInterrupted(), uid);
var toolEvData = new ToolEventData(new CryoPodPryFinished(), targetEntity:uid);
_toolSystem.UseTool(args.Used, args.User, uid, cryoPodComponent.PryDelay, new [] {"Prying"}, toolEvData);
args.Handled = true;
}
@@ -200,7 +196,7 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem
private void OnExamined(EntityUid uid, CryoPodComponent component, ExaminedEvent args)
{
var container = _itemSlotsSystem.GetItemOrNull(component.Owner, component.SolutionContainerName);
var container = _itemSlotsSystem.GetItemOrNull(uid, component.SolutionContainerName);
if (args.IsInDetailsRange && container != null && _solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var containerSolution))
{
args.PushMarkup(Loc.GetString("cryo-pod-examine", ("beaker", Name(container.Value))));

View File

@@ -1,4 +1,3 @@
using System.Threading;
using Content.Server.Administration.Logs;
using Content.Server.Body.Systems;
using Content.Server.DoAfter;
@@ -7,6 +6,7 @@ using Content.Server.Stack;
using Content.Shared.Audio;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Mobs;
@@ -35,51 +35,39 @@ public sealed class HealingSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<HealingComponent, UseInHandEvent>(OnHealingUse);
SubscribeLocalEvent<HealingComponent, AfterInteractEvent>(OnHealingAfterInteract);
SubscribeLocalEvent<HealingCancelledEvent>(OnHealingCancelled);
SubscribeLocalEvent<DamageableComponent, HealingCompleteEvent>(OnHealingComplete);
SubscribeLocalEvent<DamageableComponent, DoAfterEvent<HealingData>>(OnDoAfter);
}
private void OnHealingComplete(EntityUid uid, DamageableComponent component, HealingCompleteEvent args)
private void OnDoAfter(EntityUid uid, DamageableComponent component, DoAfterEvent<HealingData> args)
{
if (_mobStateSystem.IsDead(uid))
if (args.Handled || args.Cancelled || _mobStateSystem.IsDead(uid) || args.Args.Used == null)
return;
if (TryComp<StackComponent>(args.Component.Owner, out var stack) && stack.Count < 1)
if (component.DamageContainerID is not null && !component.DamageContainerID.Equals(component.DamageContainerID))
return;
if (component.DamageContainerID is not null &&
!component.DamageContainerID.Equals(component.DamageContainerID))
return;
// Heal some bloodloss damage.
if (args.AdditionalData.HealingComponent.BloodlossModifier != 0)
_bloodstreamSystem.TryModifyBleedAmount(uid, args.AdditionalData.HealingComponent.BloodlossModifier);
if (args.Component.BloodlossModifier != 0)
{
// Heal some bloodloss damage.
_bloodstreamSystem.TryModifyBleedAmount(uid, args.Component.BloodlossModifier);
}
var healed = _damageable.TryChangeDamage(uid, args.AdditionalData.HealingComponent.Damage, true, origin: args.Args.User);
var healed = _damageable.TryChangeDamage(uid, args.Component.Damage, true, origin: args.User);
// Reverify that we can heal the damage.
if (healed == null)
return;
_stacks.Use(args.Component.Owner, 1, stack);
// Reverify that we can heal the damage.
_stacks.Use(args.Args.Used.Value, 1, args.AdditionalData.Stack);
if (uid != args.Args.User)
_adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.Args.User):user} healed {EntityManager.ToPrettyString(uid):target} for {healed.Total:damage} damage");
if (uid != args.User)
_adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.User):user} healed {EntityManager.ToPrettyString(uid):target} for {healed.Total:damage} damage");
else
_adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.User):user} healed themselves for {healed.Total:damage} damage");
_adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.Args.User):user} healed themselves for {healed.Total:damage} damage");
if (args.Component.HealingEndSound != null)
{
_audio.PlayPvs(args.Component.HealingEndSound, uid,
AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
}
}
if (args.AdditionalData.HealingComponent.HealingEndSound != null)
_audio.PlayPvs(args.AdditionalData.HealingComponent.HealingEndSound, uid, AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
private static void OnHealingCancelled(HealingCancelledEvent ev)
{
ev.Component.CancelToken = null;
args.Handled = true;
}
private void OnHealingUse(EntityUid uid, HealingComponent component, UseInHandEvent args)
@@ -102,15 +90,7 @@ public sealed class HealingSystem : EntitySystem
private bool TryHeal(EntityUid uid, EntityUid user, EntityUid target, HealingComponent component)
{
if (component.CancelToken != null)
{
return false;
}
if (_mobStateSystem.IsDead(target))
return false;
if (!TryComp<DamageableComponent>(target, out var targetDamage))
if (_mobStateSystem.IsDead(target) || !TryComp<DamageableComponent>(target, out var targetDamage))
return false;
if (targetDamage.TotalDamage == 0)
@@ -119,28 +99,22 @@ public sealed class HealingSystem : EntitySystem
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 (TryComp<StackComponent>(component.Owner, out var stack) && stack.Count < 1)
if (user != target && !_interactionSystem.InRangeUnobstructed(user, target, popup: true))
return false;
component.CancelToken = new CancellationTokenSource();
if (!TryComp<StackComponent>(uid, out var stack) || stack.Count < 1)
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 delay = user != target
? component.Delay
: component.Delay * GetScaledHealingPenalty(user, component);
_doAfter.DoAfter(new DoAfterEventArgs(user, delay, component.CancelToken.Token, target)
var healingData = new HealingData(component, stack);
var doAfterEventArgs = new DoAfterEventArgs(user, delay, target: target, used: uid)
{
BreakOnUserMove = true,
BreakOnTargetMove = true,
@@ -148,22 +122,11 @@ public sealed class HealingSystem : EntitySystem
// not being able to heal your own ticking damage would be frustrating.
BreakOnStun = true,
NeedHand = true,
TargetFinishedEvent = new HealingCompleteEvent
{
User = user,
Component = component,
},
BroadcastCancelledEvent = new HealingCancelledEvent
{
Component = component,
},
// Juusstt in case damageble gets removed it avoids having to re-cancel the token. Won't need this when DoAfterEvent<T> gets added.
PostCheck = () =>
{
component.CancelToken = null;
return true;
},
});
PostCheck = () => true
};
_doAfter.DoAfter(doAfterEventArgs, healingData);
return true;
}
@@ -179,27 +142,18 @@ public sealed class HealingSystem : EntitySystem
var output = component.Delay;
if (!TryComp<MobThresholdsComponent>(uid, out var mobThreshold) || !TryComp<DamageableComponent>(uid, out var damageable))
return output;
if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var amount,
mobThreshold))
{
if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var amount, mobThreshold))
return 1;
}
var percentDamage = (float) (damageable.TotalDamage / amount);
//basically make it scale from 1 to the multiplier.
var modifier = percentDamage * (component.SelfHealPenaltyMultiplier - 1) + 1;
return Math.Max(modifier, 1);
}
private sealed class HealingCompleteEvent : EntityEventArgs
private record struct HealingData(HealingComponent HealingComponent, StackComponent Stack)
{
public EntityUid User;
public HealingComponent Component = default!;
}
private sealed class HealingCancelledEvent : EntityEventArgs
{
public HealingComponent Component = default!;
public HealingComponent HealingComponent = HealingComponent;
public StackComponent Stack = Stack;
}
}

View File

@@ -1,15 +1,13 @@
using System.Threading;
using Content.Server.DoAfter;
using Content.Server.Medical.Components;
using Content.Server.Disease;
using Content.Server.Popups;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Mobs.Components;
using Content.Shared.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Player;
using static Content.Shared.MedicalScanner.SharedHealthAnalyzerComponent;
namespace Content.Server.Medical
@@ -20,14 +18,14 @@ namespace Content.Server.Medical
[Dependency] private readonly DiseaseSystem _disease = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HealthAnalyzerComponent, ActivateInWorldEvent>(HandleActivateInWorld);
SubscribeLocalEvent<HealthAnalyzerComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<TargetScanSuccessfulEvent>(OnTargetScanSuccessful);
SubscribeLocalEvent<ScanCancelledEvent>(OnScanCancelled);
SubscribeLocalEvent<HealthAnalyzerComponent, DoAfterEvent>(OnDoAfter);
}
private void HandleActivateInWorld(EntityUid uid, HealthAnalyzerComponent healthAnalyzer, ActivateInWorldEvent args)
@@ -37,33 +35,13 @@ namespace Content.Server.Medical
private void OnAfterInteract(EntityUid uid, HealthAnalyzerComponent healthAnalyzer, AfterInteractEvent args)
{
if (healthAnalyzer.CancelToken != null)
{
healthAnalyzer.CancelToken.Cancel();
healthAnalyzer.CancelToken = null;
if (args.Target == null || !args.CanReach || !HasComp<MobStateComponent>(args.Target))
return;
}
if (args.Target == null)
return;
if (!args.CanReach)
return;
if (healthAnalyzer.CancelToken != null)
return;
if (!HasComp<MobStateComponent>(args.Target))
return;
healthAnalyzer.CancelToken = new CancellationTokenSource();
_audio.PlayPvs(healthAnalyzer.ScanningBeginSound, uid);
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, healthAnalyzer.ScanDelay, healthAnalyzer.CancelToken.Token, target: args.Target)
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, healthAnalyzer.ScanDelay, target: args.Target, used:uid)
{
BroadcastFinishedEvent = new TargetScanSuccessfulEvent(args.User, args.Target, healthAnalyzer),
BroadcastCancelledEvent = new ScanCancelledEvent(healthAnalyzer),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
@@ -71,37 +49,47 @@ namespace Content.Server.Medical
});
}
private void OnTargetScanSuccessful(TargetScanSuccessfulEvent args)
private void OnDoAfter(EntityUid uid, HealthAnalyzerComponent component, DoAfterEvent args)
{
args.Component.CancelToken = null;
if (args.Handled || args.Cancelled || args.Args.Target == null)
return;
_audio.PlayPvs(args.Component.ScanningEndSound, args.User);
_audio.PlayPvs(component.ScanningEndSound, args.Args.User);
UpdateScannedUser(args.Component.Owner, args.User, args.Target, args.Component);
UpdateScannedUser(uid, args.Args.User, args.Args.Target.Value, component);
// Below is for the traitor item
// Piggybacking off another component's doafter is complete CBT so I gave up
// and put it on the same component
if (string.IsNullOrEmpty(args.Component.Disease) || args.Target == null)
return;
_disease.TryAddDisease(args.Target.Value, args.Component.Disease);
if (args.User == args.Target)
if (string.IsNullOrEmpty(component.Disease))
{
_popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-self", ("disease", args.Component.Disease)),
args.User, args.User);
args.Handled = true;
return;
}
_popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-other", ("target", Identity.Entity(args.Target.Value, EntityManager)), ("disease", args.Component.Disease)),
args.User, args.User);
_disease.TryAddDisease(args.Args.Target.Value, component.Disease);
if (args.Args.User == args.Args.Target)
{
_popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-self", ("disease", component.Disease)),
args.Args.User, args.Args.User);
}
else
{
_popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-other", ("target", Identity.Entity(args.Args.Target.Value, EntityManager)),
("disease", component.Disease)), args.Args.User, args.Args.User);
}
args.Handled = true;
}
private void OpenUserInterface(EntityUid user, HealthAnalyzerComponent healthAnalyzer)
{
if (!TryComp<ActorComponent>(user, out var actor))
if (!TryComp<ActorComponent>(user, out var actor) || healthAnalyzer.UserInterface == null)
return;
healthAnalyzer.UserInterface?.Open(actor.PlayerSession);
_uiSystem.OpenUi(healthAnalyzer.UserInterface ,actor.PlayerSession);
}
public void UpdateScannedUser(EntityUid uid, EntityUid user, EntityUid? target, HealthAnalyzerComponent? healthAnalyzer)
@@ -116,35 +104,7 @@ namespace Content.Server.Medical
return;
OpenUserInterface(user, healthAnalyzer);
healthAnalyzer.UserInterface?.SendMessage(new HealthAnalyzerScannedUserMessage(target));
}
private static void OnScanCancelled(ScanCancelledEvent args)
{
args.HealthAnalyzer.CancelToken = null;
}
private sealed class ScanCancelledEvent : EntityEventArgs
{
public readonly HealthAnalyzerComponent HealthAnalyzer;
public ScanCancelledEvent(HealthAnalyzerComponent healthAnalyzer)
{
HealthAnalyzer = healthAnalyzer;
}
}
private sealed class TargetScanSuccessfulEvent : EntityEventArgs
{
public EntityUid User { get; }
public EntityUid? Target { get; }
public HealthAnalyzerComponent Component { get; }
public TargetScanSuccessfulEvent(EntityUid user, EntityUid? target, HealthAnalyzerComponent component)
{
User = user;
Target = target;
Component = component;
}
_uiSystem.SendUiMessage(healthAnalyzer.UserInterface, new HealthAnalyzerScannedUserMessage(target));
}
}
}

View File

@@ -12,8 +12,6 @@ namespace Content.Server.Medical.Components
{
public bool IsActive = false;
public CancellationTokenSource? CancelToken;
[DataField("delay")]
public float Delay = 2.5f;

View File

@@ -8,10 +8,9 @@ using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.Inventory.Events;
using Content.Shared.Verbs;
using Robust.Shared.Player;
using System.Threading;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.DoAfter;
namespace Content.Server.Medical
{
@@ -29,8 +28,7 @@ namespace Content.Server.Medical
SubscribeLocalEvent<WearingStethoscopeComponent, GetVerbsEvent<InnateVerb>>(AddStethoscopeVerb);
SubscribeLocalEvent<StethoscopeComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<StethoscopeComponent, StethoscopeActionEvent>(OnStethoscopeAction);
SubscribeLocalEvent<ListenSuccessfulEvent>(OnListenSuccess);
SubscribeLocalEvent<ListenCancelledEvent>(OnListenCancelled);
SubscribeLocalEvent<StethoscopeComponent, DoAfterEvent>(OnDoAfter);
}
/// <summary>
@@ -81,7 +79,7 @@ namespace Content.Server.Medical
{
Act = () =>
{
StartListening(uid, args.Target, stetho); // start doafter
StartListening(component.Stethoscope, uid, args.Target, stetho); // start doafter
},
Text = Loc.GetString("stethoscope-verb"),
IconTexture = "Clothing/Neck/Misc/stethoscope.rsi/icon.png",
@@ -93,7 +91,7 @@ namespace Content.Server.Medical
private void OnStethoscopeAction(EntityUid uid, StethoscopeComponent component, StethoscopeActionEvent args)
{
StartListening(args.Performer, args.Target, component);
StartListening(uid, args.Performer, args.Target, component);
}
private void OnGetActions(EntityUid uid, StethoscopeComponent component, GetItemActionsEvent args)
@@ -101,27 +99,11 @@ namespace Content.Server.Medical
args.Actions.Add(component.Action);
}
// doafter succeeded / failed
private void OnListenSuccess(ListenSuccessfulEvent ev)
{
ev.Component.CancelToken = null;
ExamineWithStethoscope(ev.User, ev.Target);
}
private void OnListenCancelled(ListenCancelledEvent ev)
{
if (ev.Component == null)
return;
ev.Component.CancelToken = null;
}
// construct the doafter and start it
private void StartListening(EntityUid user, EntityUid target, StethoscopeComponent comp)
private void StartListening(EntityUid scope, EntityUid user, EntityUid target, StethoscopeComponent comp)
{
comp.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, comp.Delay, comp.CancelToken.Token, target: target)
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, comp.Delay, target: target, used:scope)
{
BroadcastFinishedEvent = new ListenSuccessfulEvent(user, target, comp),
BroadcastCancelledEvent = new ListenCancelledEvent(user, comp),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
@@ -129,6 +111,14 @@ namespace Content.Server.Medical
});
}
private void OnDoAfter(EntityUid uid, StethoscopeComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Args.Target == null)
return;
ExamineWithStethoscope(args.Args.User, args.Args.Target.Value);
}
/// <summary>
/// Return a value based on the total oxyloss of the target.
/// Could be expanded in the future with reagent effects etc.
@@ -165,34 +155,6 @@ namespace Content.Server.Medical
};
return msg;
}
// events for the doafter
private sealed class ListenSuccessfulEvent : EntityEventArgs
{
public EntityUid User;
public EntityUid Target;
public StethoscopeComponent Component;
public ListenSuccessfulEvent(EntityUid user, EntityUid target, StethoscopeComponent component)
{
User = user;
Target = target;
Component = component;
}
}
private sealed class ListenCancelledEvent : EntityEventArgs
{
public EntityUid Uid;
public StethoscopeComponent Component;
public ListenCancelledEvent(EntityUid uid, StethoscopeComponent component)
{
Uid = uid;
Component = component;
}
}
}
public sealed class StethoscopeActionEvent : EntityTargetActionEvent {}

View File

@@ -174,8 +174,6 @@ namespace Content.Server.Nuke
/// </summary>
public bool PlayedAlertSound = false;
public CancellationToken? DisarmCancelToken = null;
public IPlayingAudioStream? AlertAudioStream = default;
}
}

View File

@@ -6,13 +6,13 @@ using Content.Server.DoAfter;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Popups;
using Content.Server.Station.Systems;
using Content.Server.UserInterface;
using Content.Shared.Audio;
using Content.Shared.Construction.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.DoAfter;
using Content.Shared.Nuke;
using Content.Shared.Popups;
using Robust.Shared.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Random;
@@ -30,6 +30,8 @@ namespace Content.Server.Nuke
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
/// <summary>
/// Used to calculate when the nuke song should start playing for maximum kino with the nuke sfx
@@ -65,8 +67,7 @@ namespace Content.Server.Nuke
SubscribeLocalEvent<NukeComponent, NukeKeypadEnterMessage>(OnEnterButtonPressed);
// Doafter events
SubscribeLocalEvent<NukeComponent, NukeDisarmSuccessEvent>(OnDisarmSuccess);
SubscribeLocalEvent<NukeComponent, NukeDisarmCancelledEvent>(OnDisarmCancelled);
SubscribeLocalEvent<NukeComponent, DoAfterEvent>(OnDoAfter);
}
private void OnInit(EntityUid uid, NukeComponent component, ComponentInit args)
@@ -211,7 +212,7 @@ namespace Content.Server.Nuke
private void OnClearButtonPressed(EntityUid uid, NukeComponent component, NukeKeypadClearMessage args)
{
PlaySound(uid, component.KeypadPressSound, 0f, component);
_audio.Play(component.KeypadPressSound, Filter.Pvs(uid), uid, true);
if (component.Status != NukeStatus.AWAIT_CODE)
return;
@@ -241,17 +242,18 @@ namespace Content.Server.Nuke
#region Doafter Events
private void OnDisarmSuccess(EntityUid uid, NukeComponent component, NukeDisarmSuccessEvent args)
private void OnDoAfter(EntityUid uid, NukeComponent component, DoAfterEvent args)
{
component.DisarmCancelToken = null;
if(args.Handled || args.Cancelled)
return;
DisarmBomb(uid, component);
}
private void OnDisarmCancelled(EntityUid uid, NukeComponent component, NukeDisarmCancelledEvent args)
{
component.DisarmCancelToken = null;
}
var ev = new NukeDisarmSuccessEvent();
RaiseLocalEvent(ev);
args.Handled = true;
}
#endregion
private void TickCooldown(EntityUid uid, float frameTime, NukeComponent? nuke = null)
@@ -268,7 +270,7 @@ namespace Content.Server.Nuke
UpdateStatus(uid, nuke);
}
UpdateUserInterface(nuke.Owner, nuke);
UpdateUserInterface(uid, nuke);
}
private void TickTimer(EntityUid uid, float frameTime, NukeComponent? nuke = null)
@@ -289,7 +291,7 @@ namespace Content.Server.Nuke
// play alert sound if time is running out
if (nuke.RemainingTime <= nuke.AlertSoundTime && !nuke.PlayedAlertSound)
{
nuke.AlertAudioStream = SoundSystem.Play(nuke.AlertSound.GetSound(), Filter.Broadcast());
nuke.AlertAudioStream = _audio.Play(nuke.AlertSound, Filter.Broadcast(), uid, true);
_soundSystem.StopStationEventMusic(uid, StationEventMusicType.Nuke);
nuke.PlayedAlertSound = true;
}
@@ -329,12 +331,12 @@ namespace Content.Server.Nuke
{
component.Status = NukeStatus.AWAIT_ARM;
component.RemainingTime = component.Timer;
PlaySound(uid, component.AccessGrantedSound, 0, component);
_audio.Play(component.AccessGrantedSound, Filter.Pvs(uid), uid, true);
}
else
{
component.EnteredCode = "";
PlaySound(uid, component.AccessDeniedSound, 0, component);
_audio.Play(component.AccessDeniedSound, Filter.Pvs(uid), uid, true);
}
break;
@@ -353,7 +355,7 @@ namespace Content.Server.Nuke
if (!Resolve(uid, ref component))
return;
var ui = component.Owner.GetUIOrNull(NukeUiKey.Key);
var ui = _ui.GetUiOrNull(uid, NukeUiKey.Key);
if (ui == null)
return;
@@ -365,7 +367,7 @@ namespace Content.Server.Nuke
(component.Status == NukeStatus.AWAIT_ARM ||
component.Status == NukeStatus.ARMED);
var state = new NukeUiState()
var state = new NukeUiState
{
Status = component.Status,
RemainingTime = (int) component.RemainingTime,
@@ -377,7 +379,7 @@ namespace Content.Server.Nuke
CooldownTime = (int) component.CooldownTime
};
ui.SetState(state);
_ui.SetUiState(ui, state);
}
private void PlayNukeKeypadSound(EntityUid uid, int number, NukeComponent? component = null)
@@ -407,18 +409,7 @@ namespace Content.Server.Nuke
// Don't double-dip on the octave shifting
component.LastPlayedKeypadSemitones = number == 0 ? component.LastPlayedKeypadSemitones : semitoneShift;
SoundSystem.Play(component.KeypadPressSound.GetSound(), Filter.Pvs(uid), uid,
AudioHelpers.ShiftSemitone(semitoneShift).WithVolume(-5f));
}
private void PlaySound(EntityUid uid, SoundSpecifier sound, float varyPitch = 0f,
NukeComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
SoundSystem.Play(sound.GetSound(),
Filter.Pvs(uid), uid, AudioHelpers.WithVariation(varyPitch).WithVolume(-5f));
_audio.Play(component.KeypadPressSound, Filter.Pvs(uid), uid, true, AudioHelpers.ShiftSemitone(semitoneShift).WithVolume(-5f));
}
public string GenerateRandomNumberString(int length)
@@ -465,7 +456,7 @@ namespace Content.Server.Nuke
var sender = Loc.GetString("nuke-component-announcement-sender");
_chatSystem.DispatchStationAnnouncement(uid, announcement, sender, false, null, Color.Red);
NukeArmedAudio(component);
_soundSystem.PlayGlobalOnStation(uid, _audio.GetSound(component.ArmSound));
_itemSlots.SetLock(uid, component.DiskSlot, true);
nukeXform.Anchored = true;
@@ -494,7 +485,8 @@ namespace Content.Server.Nuke
_chatSystem.DispatchStationAnnouncement(uid, announcement, sender, false);
component.PlayedNukeSong = false;
NukeDisarmedAudio(component);
_soundSystem.PlayGlobalOnStation(uid, _audio.GetSound(component.DisarmSound));
_soundSystem.StopStationEventMusic(uid, StationEventMusicType.Nuke);
// disable sound and reset it
component.PlayedAlertSound = false;
@@ -547,7 +539,7 @@ namespace Content.Server.Nuke
OwningStation = transform.GridUid,
});
_soundSystem.StopStationEventMusic(component.Owner, StationEventMusicType.Nuke);
_soundSystem.StopStationEventMusic(uid, StationEventMusicType.Nuke);
EntityManager.DeleteEntity(uid);
}
@@ -567,34 +559,19 @@ namespace Content.Server.Nuke
private void DisarmBombDoafter(EntityUid uid, EntityUid user, NukeComponent nuke)
{
nuke.DisarmCancelToken = new();
var doafter = new DoAfterEventArgs(user, nuke.DisarmDoafterLength, nuke.DisarmCancelToken.Value, uid)
var doafter = new DoAfterEventArgs(user, nuke.DisarmDoafterLength, target: uid)
{
TargetCancelledEvent = new NukeDisarmCancelledEvent(),
TargetFinishedEvent = new NukeDisarmSuccessEvent(),
BroadcastFinishedEvent = new NukeDisarmSuccessEvent(),
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = true,
NeedHand = true
};
_doAfterSystem.DoAfter(doafter);
_popups.PopupEntity(Loc.GetString("nuke-component-doafter-warning"), user,
user, PopupType.LargeCaution);
}
private void NukeArmedAudio(NukeComponent component)
{
_soundSystem.PlayGlobalOnStation(component.Owner, component.ArmSound.GetSound());
}
private void NukeDisarmedAudio(NukeComponent component)
{
_soundSystem.PlayGlobalOnStation(component.Owner, component.DisarmSound.GetSound());
_soundSystem.StopStationEventMusic(component.Owner, StationEventMusicType.Nuke);
}
}
public sealed class NukeExplodedEvent : EntityEventArgs
@@ -604,18 +581,10 @@ namespace Content.Server.Nuke
/// <summary>
/// Raised directed on the nuke when its disarm doafter is successful.
/// So the game knows not to end.
/// </summary>
public sealed class NukeDisarmSuccessEvent : EntityEventArgs
{
}
/// <summary>
/// Raised directed on the nuke when its disarm doafter is cancelled.
/// </summary>
public sealed class NukeDisarmCancelledEvent : EntityEventArgs
{
}
}

View File

@@ -1,4 +1,3 @@
using System.Threading;
using Content.Server.Nutrition.EntitySystems;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
@@ -46,11 +45,5 @@ namespace Content.Server.Nutrition.Components
/// </summary>
[DataField("forceFeedDelay")]
public float ForceFeedDelay = 3;
/// <summary>
/// Token for interrupting a do-after action (e.g., force feeding). If not null, implies component is
/// currently "in use".
/// </summary>
public CancellationTokenSource? CancelToken;
}
}

View File

@@ -1,4 +1,3 @@
using System.Threading;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Nutrition.EntitySystems;
using Content.Shared.FixedPoint;
@@ -54,12 +53,6 @@ namespace Content.Server.Nutrition.Components
[DataField("forceFeedDelay")]
public float ForceFeedDelay = 3;
/// <summary>
/// Token for interrupting a do-after action (e.g., force feeding). If not null, implies component is
/// currently "in use".
/// </summary>
public CancellationTokenSource? CancelToken;
[ViewVariables]
public int UsesRemaining
{

View File

@@ -1,4 +1,3 @@
using System.Threading;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Chemistry.Components.SolutionManager;
@@ -9,8 +8,11 @@ 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.Examine;
using Content.Shared.FixedPoint;
using Content.Shared.IdentityManagement;
@@ -46,6 +48,7 @@ namespace Content.Server.Nutrition.EntitySystems
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ReactiveSystem _reaction = default!;
public override void Initialize()
{
@@ -59,8 +62,7 @@ namespace Content.Server.Nutrition.EntitySystems
SubscribeLocalEvent<DrinkComponent, GetVerbsEvent<AlternativeVerb>>(AddDrinkVerb);
SubscribeLocalEvent<DrinkComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<DrinkComponent, SolutionTransferAttemptEvent>(OnTransferAttempt);
SubscribeLocalEvent<BodyComponent, DrinkEvent>(OnDrink);
SubscribeLocalEvent<DrinkCancelledEvent>(OnDrinkCancelled);
SubscribeLocalEvent<DrinkComponent, DoAfterEvent<DrinkData>>(OnDoAfter);
}
public bool IsEmpty(EntityUid uid, DrinkComponent? component = null)
@@ -126,12 +128,13 @@ namespace Content.Server.Nutrition.EntitySystems
if (args.Handled || args.Target == null || !args.CanReach)
return;
args.Handled = TryDrink(args.User, args.Target.Value, component);
args.Handled = TryDrink(args.User, args.Target.Value, component, uid);
}
private void OnUse(EntityUid uid, DrinkComponent component, UseInHandEvent args)
{
if (args.Handled) return;
if (args.Handled)
return;
if (!component.Opened)
{
@@ -142,7 +145,7 @@ namespace Content.Server.Nutrition.EntitySystems
return;
}
args.Handled = TryDrink(args.User, args.User, component);
args.Handled = TryDrink(args.User, args.User, component, uid);
}
private void HandleLand(EntityUid uid, DrinkComponent component, ref LandEvent args)
@@ -212,36 +215,30 @@ namespace Content.Server.Nutrition.EntitySystems
}
}
private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink)
private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item)
{
// cannot stack do-afters
if (drink.CancelToken != null)
{
return true;
}
if (!EntityManager.HasComponent<BodyComponent>(target))
return false;
if (!drink.Opened)
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-not-open",
("owner", EntityManager.GetComponent<MetaDataComponent>(drink.Owner).EntityName)), drink.Owner, user);
("owner", EntityManager.GetComponent<MetaDataComponent>(item).EntityName)), item, user);
return true;
}
if (!_solutionContainerSystem.TryGetDrainableSolution(drink.Owner, out var drinkSolution) ||
if (!_solutionContainerSystem.TryGetDrainableSolution(item, out var drinkSolution) ||
drinkSolution.Volume <= 0)
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-is-empty",
("entity", EntityManager.GetComponent<MetaDataComponent>(drink.Owner).EntityName)), drink.Owner, user);
("entity", EntityManager.GetComponent<MetaDataComponent>(item).EntityName)), item, user);
return true;
}
if (_foodSystem.IsMouthBlocked(target, user))
return true;
if (!_interactionSystem.InRangeUnobstructed(user, drink.Owner, popup: true))
if (!_interactionSystem.InRangeUnobstructed(user, item, popup: true))
return true;
var forceDrink = user != target;
@@ -254,20 +251,21 @@ namespace Content.Server.Nutrition.EntitySystems
user, target);
// logging
_adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to drink {ToPrettyString(drink.Owner):drink} {SolutionContainerSystem.ToPrettyString(drinkSolution)}");
_adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to drink {ToPrettyString(item):drink} {SolutionContainerSystem.ToPrettyString(drinkSolution)}");
}
else
{
// log voluntary drinking
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is drinking {ToPrettyString(drink.Owner):drink} {SolutionContainerSystem.ToPrettyString(drinkSolution)}");
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is drinking {ToPrettyString(item):drink} {SolutionContainerSystem.ToPrettyString(drinkSolution)}");
}
drink.CancelToken = new CancellationTokenSource();
var moveBreak = user != target;
var flavors = _flavorProfileSystem.GetLocalizedFlavorsMessage(user, drinkSolution);
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, forceDrink ? drink.ForceFeedDelay : drink.Delay, drink.CancelToken.Token, target, drink.Owner)
var drinkData = new DrinkData(drinkSolution, flavors);
var doAfterEventArgs = new DoAfterEventArgs(user, forceDrink ? drink.ForceFeedDelay : drink.Delay,
target: target, used: item)
{
BreakOnUserMove = moveBreak,
BreakOnDamage = true,
@@ -275,10 +273,10 @@ namespace Content.Server.Nutrition.EntitySystems
BreakOnTargetMove = moveBreak,
MovementThreshold = 0.01f,
DistanceThreshold = 1.0f,
TargetFinishedEvent = new DrinkEvent(user, drink, drinkSolution, flavors),
BroadcastCancelledEvent = new DrinkCancelledEvent(drink),
NeedHand = true,
});
NeedHand = true
};
_doAfterSystem.DoAfter(doAfterEventArgs, drinkData);
return true;
}
@@ -286,103 +284,93 @@ namespace Content.Server.Nutrition.EntitySystems
/// <summary>
/// Raised directed at a victim when someone has force fed them a drink.
/// </summary>
private void OnDrink(EntityUid uid, BodyComponent body, DrinkEvent args)
private void OnDoAfter(EntityUid uid, DrinkComponent component, DoAfterEvent<DrinkData> args)
{
if (args.Drink.Deleted)
if (args.Handled || args.Cancelled || component.Deleted)
return;
args.Drink.CancelToken = null;
var transferAmount = FixedPoint2.Min(args.Drink.TransferAmount, args.DrinkSolution.Volume);
var drained = _solutionContainerSystem.Drain(args.Drink.Owner, args.DrinkSolution, transferAmount);
if (!TryComp<BodyComponent>(args.Args.Target, out var body))
return;
var forceDrink = uid != args.User;
var transferAmount = FixedPoint2.Min(component.TransferAmount, args.AdditionalData.DrinkSolution.Volume);
var drained = _solutionContainerSystem.Drain(uid, args.AdditionalData.DrinkSolution, transferAmount);
if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(uid, out var stomachs, body))
var forceDrink = args.Args.Target.Value != args.Args.User;
if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(args.Args.Target.Value, out var stomachs, body))
{
_popupSystem.PopupEntity(
forceDrink ?
Loc.GetString("drink-component-try-use-drink-cannot-drink-other") :
Loc.GetString("drink-component-try-use-drink-had-enough"),
uid, args.User);
_popupSystem.PopupEntity(forceDrink ? Loc.GetString("drink-component-try-use-drink-cannot-drink-other") : Loc.GetString("drink-component-try-use-drink-had-enough"), args.Args.Target.Value, args.Args.User);
if (EntityManager.HasComponent<RefillableSolutionComponent>(uid))
if (HasComp<RefillableSolutionComponent>(args.Args.Target.Value))
{
_spillableSystem.SpillAt(args.User, drained, "PuddleSmear");
_spillableSystem.SpillAt(args.Args.User, drained, "PuddleSmear");
args.Handled = true;
return;
}
_solutionContainerSystem.Refill(uid, args.DrinkSolution, drained);
_solutionContainerSystem.Refill(args.Args.Target.Value, args.AdditionalData.DrinkSolution, drained);
args.Handled = true;
return;
}
var firstStomach = stomachs.FirstOrNull(
stomach => _stomachSystem.CanTransferSolution((stomach.Comp).Owner, drained));
var firstStomach = stomachs.FirstOrNull(stomach => _stomachSystem.CanTransferSolution(stomach.Comp.Owner, drained));
// All stomach are full or can't handle whatever solution we have.
//All stomachs are full or can't handle whatever solution we have.
if (firstStomach == null)
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough"),
uid, uid);
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough"), args.Args.Target.Value, args.Args.Target.Value);
if (forceDrink)
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough-other"),
uid, args.User);
_spillableSystem.SpillAt(uid, drained, "PuddleSmear");
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough-other"), args.Args.Target.Value, args.Args.User);
_spillableSystem.SpillAt(args.Args.Target.Value, drained, "PuddleSmear");
}
else
{
_solutionContainerSystem.TryAddSolution(args.Drink.Owner, args.DrinkSolution, drained);
}
_solutionContainerSystem.TryAddSolution(uid, args.AdditionalData.DrinkSolution, drained);
args.Handled = true;
return;
}
var flavors = args.FlavorMessage;
var flavors = args.AdditionalData.FlavorMessage;
if (forceDrink)
{
var targetName = Identity.Entity(uid, EntityManager);
var userName = Identity.Entity(args.User, EntityManager);
var targetName = Identity.Entity(args.Args.Target.Value, EntityManager);
var userName = Identity.Entity(args.Args.User, EntityManager);
_popupSystem.PopupEntity(
Loc.GetString("drink-component-force-feed-success", ("user", userName), ("flavors", flavors)), uid, uid);
_popupSystem.PopupEntity(Loc.GetString("drink-component-force-feed-success", ("user", userName), ("flavors", flavors)), args.Args.Target.Value, args.Args.Target.Value);
_popupSystem.PopupEntity(
Loc.GetString("drink-component-force-feed-success-user", ("target", targetName)),
args.User, args.User);
args.Args.User, args.Args.User);
// log successful forced drinking
_adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(args.User):user} forced {ToPrettyString(uid):target} to drink {ToPrettyString(args.Drink.Owner):drink}");
_adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(uid):user} forced {ToPrettyString(args.Args.User):target} to drink {ToPrettyString(component.Owner):drink}");
}
else
{
_popupSystem.PopupEntity(
Loc.GetString("drink-component-try-use-drink-success-slurp-taste", ("flavors", flavors)), args.User,
args.User);
Loc.GetString("drink-component-try-use-drink-success-slurp-taste", ("flavors", flavors)), args.Args.User,
args.Args.User);
_popupSystem.PopupEntity(
Loc.GetString("drink-component-try-use-drink-success-slurp"), args.User, Filter.PvsExcept(args.User), true);
Loc.GetString("drink-component-try-use-drink-success-slurp"), args.Args.User, Filter.PvsExcept(args.Args.User), true);
// log successful voluntary drinking
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.User):target} drank {ToPrettyString(args.Drink.Owner):drink}");
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.Args.User):target} drank {ToPrettyString(uid):drink}");
}
_audio.PlayPvs(_audio.GetSound(args.Drink.UseSound), uid, AudioParams.Default.WithVolume(-2f));
_audio.PlayPvs(_audio.GetSound(component.UseSound), args.Args.Target.Value, AudioParams.Default.WithVolume(-2f));
drained.DoEntityReaction(uid, ReactionMethod.Ingestion);
_reaction.DoEntityReaction(args.Args.Target.Value, args.AdditionalData.DrinkSolution, ReactionMethod.Ingestion);
//TODO: Grab the stomach UIDs somehow without using Owner
_stomachSystem.TryTransferSolution(firstStomach.Value.Comp.Owner, drained, firstStomach.Value.Comp);
}
private static void OnDrinkCancelled(DrinkCancelledEvent args)
{
args.Drink.CancelToken = null;
args.Handled = true;
}
private void AddDrinkVerb(EntityUid uid, DrinkComponent component, GetVerbsEvent<AlternativeVerb> ev)
{
if (component.CancelToken != null)
return;
if (uid == ev.User ||
!ev.CanInteract ||
!ev.CanAccess ||
@@ -397,7 +385,7 @@ namespace Content.Server.Nutrition.EntitySystems
{
Act = () =>
{
TryDrink(ev.User, ev.User, component);
TryDrink(ev.User, ev.User, component, uid);
},
IconTexture = "/Textures/Interface/VerbIcons/drink.svg.192dpi.png",
Text = Loc.GetString("drink-system-verb-drink"),
@@ -418,5 +406,11 @@ namespace Content.Server.Nutrition.EntitySystems
return remainingString;
}
private record struct DrinkData(Solution DrinkSolution, string FlavorMessage)
{
public readonly Solution DrinkSolution = DrinkSolution;
public readonly string FlavorMessage = FlavorMessage;
}
}
}

View File

@@ -1,4 +1,3 @@
using System.Threading;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Chemistry.EntitySystems;
@@ -8,8 +7,11 @@ 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.FixedPoint;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
@@ -42,6 +44,8 @@ namespace Content.Server.Nutrition.EntitySystems
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly ReactiveSystem _reaction = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
@@ -50,8 +54,7 @@ namespace Content.Server.Nutrition.EntitySystems
SubscribeLocalEvent<FoodComponent, UseInHandEvent>(OnUseFoodInHand);
SubscribeLocalEvent<FoodComponent, AfterInteractEvent>(OnFeedFood);
SubscribeLocalEvent<FoodComponent, GetVerbsEvent<AlternativeVerb>>(AddEatVerb);
SubscribeLocalEvent<BodyComponent, FeedEvent>(OnFeed);
SubscribeLocalEvent<ForceFeedCancelledEvent>(OnFeedCancelled);
SubscribeLocalEvent<FoodComponent, DoAfterEvent<FoodData>>(OnDoAfter);
SubscribeLocalEvent<InventoryComponent, IngestionAttemptEvent>(OnInventoryIngestAttempt);
}
@@ -63,7 +66,7 @@ namespace Content.Server.Nutrition.EntitySystems
if (ev.Handled)
return;
ev.Handled = TryFeed(ev.User, ev.User, foodComponent);
ev.Handled = TryFeed(ev.User, ev.User, uid, foodComponent);
}
/// <summary>
@@ -74,49 +77,41 @@ namespace Content.Server.Nutrition.EntitySystems
if (args.Handled || args.Target == null || !args.CanReach)
return;
args.Handled = TryFeed(args.User, args.Target.Value, foodComponent);
args.Handled = TryFeed(args.User, args.Target.Value, uid, foodComponent);
}
public bool TryFeed(EntityUid user, EntityUid target, FoodComponent food)
public bool TryFeed(EntityUid user, EntityUid target, EntityUid food, FoodComponent foodComp)
{
// if currently being used to feed, cancel that action.
if (food.CancelToken != null)
{
return true;
}
if (food.Owner == user || //Suppresses self-eating
EntityManager.TryGetComponent<MobStateComponent>(food.Owner, out var mobState) && _mobStateSystem.IsAlive(food.Owner, mobState)) // Suppresses eating alive mobs
//Suppresses self-eating
if (food == user || EntityManager.TryGetComponent<MobStateComponent>(food, out var mobState) && _mobStateSystem.IsAlive(food, mobState)) // Suppresses eating alive mobs
return false;
// Target can't be fed
if (!EntityManager.HasComponent<BodyComponent>(target))
return false;
if (!_solutionContainerSystem.TryGetSolution(food.Owner, food.SolutionName, out var foodSolution))
if (!_solutionContainerSystem.TryGetSolution(food, foodComp.SolutionName, out var foodSolution))
return false;
var flavors = _flavorProfileSystem.GetLocalizedFlavorsMessage(food.Owner, user, foodSolution);
var flavors = _flavorProfileSystem.GetLocalizedFlavorsMessage(food, user, foodSolution);
if (food.UsesRemaining <= 0)
if (foodComp.UsesRemaining <= 0)
{
_popupSystem.PopupEntity(Loc.GetString("food-system-try-use-food-is-empty",
("entity", food.Owner)), user, user);
DeleteAndSpawnTrash(food, user);
_popupSystem.PopupEntity(Loc.GetString("food-system-try-use-food-is-empty", ("entity", food)), user, user);
DeleteAndSpawnTrash(foodComp, food, user);
return false;
}
if (IsMouthBlocked(target, user))
return false;
if (!TryGetRequiredUtensils(user, food, out var utensils))
if (!TryGetRequiredUtensils(user, foodComp, out var utensils))
return false;
if (!_interactionSystem.InRangeUnobstructed(user, food.Owner, popup: true))
if (!_interactionSystem.InRangeUnobstructed(user, food, popup: true))
return true;
var forceFeed = user != target;
food.CancelToken = new CancellationTokenSource();
if (forceFeed)
{
@@ -125,17 +120,19 @@ namespace Content.Server.Nutrition.EntitySystems
user, target);
// logging
_adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to eat {ToPrettyString(food.Owner):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}");
_adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to eat {ToPrettyString(food):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}");
}
else
{
// log voluntary eating
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is eating {ToPrettyString(food.Owner):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}");
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is eating {ToPrettyString(food):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}");
}
var moveBreak = user != target;
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, forceFeed ? food.ForceFeedDelay : food.Delay, food.CancelToken.Token, target, food.Owner)
var foodData = new FoodData(foodSolution, flavors, utensils);
var doAfterEventArgs = new DoAfterEventArgs(user, forceFeed ? foodComp.ForceFeedDelay : foodComp.Delay, target: target, used: food)
{
BreakOnUserMove = moveBreak,
BreakOnDamage = true,
@@ -143,116 +140,114 @@ namespace Content.Server.Nutrition.EntitySystems
BreakOnTargetMove = moveBreak,
MovementThreshold = 0.01f,
DistanceThreshold = 1.0f,
TargetFinishedEvent = new FeedEvent(user, food, foodSolution, flavors, utensils),
BroadcastCancelledEvent = new ForceFeedCancelledEvent(food),
NeedHand = true,
});
NeedHand = true
};
_doAfterSystem.DoAfter(doAfterEventArgs, foodData);
return true;
}
private void OnFeed(EntityUid uid, BodyComponent body, FeedEvent args)
private void OnDoAfter(EntityUid uid, FoodComponent component, DoAfterEvent<FoodData> args)
{
if (args.Food.Deleted)
if (args.Cancelled || args.Handled || component.Deleted || args.Args.Target == null)
return;
args.Food.CancelToken = null;
if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(uid, out var stomachs, body))
if (!TryComp<BodyComponent>(args.Args.Target.Value, out var body))
return;
var transferAmount = args.Food.TransferAmount != null
? FixedPoint2.Min((FixedPoint2) args.Food.TransferAmount, args.FoodSolution.Volume)
: args.FoodSolution.Volume;
if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(args.Args.Target.Value, out var stomachs, body))
return;
var split = _solutionContainerSystem.SplitSolution((args.Food).Owner, args.FoodSolution, transferAmount);
var transferAmount = component.TransferAmount != null ? FixedPoint2.Min((FixedPoint2) component.TransferAmount, args.AdditionalData.FoodSolution.Volume) : args.AdditionalData.FoodSolution.Volume;
var split = _solutionContainerSystem.SplitSolution(uid, args.AdditionalData.FoodSolution, transferAmount);
//TODO: Get the stomach UID somehow without nabbing owner
var firstStomach = stomachs.FirstOrNull(stomach => _stomachSystem.CanTransferSolution(stomach.Comp.Owner, split));
var firstStomach = stomachs.FirstOrNull(
stomach => _stomachSystem.CanTransferSolution((stomach.Comp).Owner, split));
var forceFeed = uid != args.User;
var forceFeed = args.Args.Target.Value != args.Args.User;
// No stomach so just popup a message that they can't eat.
if (firstStomach == null)
{
_solutionContainerSystem.TryAddSolution(uid, args.FoodSolution, split);
_popupSystem.PopupEntity(
forceFeed ?
Loc.GetString("food-system-you-cannot-eat-any-more-other") :
Loc.GetString("food-system-you-cannot-eat-any-more")
, uid, args.User);
_solutionContainerSystem.TryAddSolution(uid, args.AdditionalData.FoodSolution, split);
_popupSystem.PopupEntity(forceFeed ? Loc.GetString("food-system-you-cannot-eat-any-more-other") : Loc.GetString("food-system-you-cannot-eat-any-more"), args.Args.Target.Value, args.Args.User);
args.Handled = true;
return;
}
split.DoEntityReaction(uid, ReactionMethod.Ingestion);
_reaction.DoEntityReaction(args.Args.Target.Value, args.AdditionalData.FoodSolution, ReactionMethod.Ingestion);
_stomachSystem.TryTransferSolution(firstStomach.Value.Comp.Owner, split, firstStomach.Value.Comp);
var flavors = args.FlavorMessage;
var flavors = args.AdditionalData.FlavorMessage;
if (forceFeed)
{
var targetName = Identity.Entity(uid, EntityManager);
var userName = Identity.Entity(args.User, EntityManager);
var targetName = Identity.Entity(args.Args.Target.Value, EntityManager);
var userName = Identity.Entity(args.Args.User, EntityManager);
_popupSystem.PopupEntity(Loc.GetString("food-system-force-feed-success", ("user", userName), ("flavors", flavors)),
uid, uid);
_popupSystem.PopupEntity(Loc.GetString("food-system-force-feed-success-user", ("target", targetName)),
args.User, args.User);
_popupSystem.PopupEntity(Loc.GetString("food-system-force-feed-success-user", ("target", targetName)), args.Args.User, args.Args.User);
// log successful force feed
_adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(args.User):user} forced {ToPrettyString(uid):target} to eat {ToPrettyString(args.Food.Owner):food}");
_adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(uid):user} forced {ToPrettyString(args.Args.User):target} to eat {ToPrettyString(uid):food}");
}
else
{
_popupSystem.PopupEntity(Loc.GetString(args.Food.EatMessage, ("food", args.Food.Owner), ("flavors", flavors)), args.User, args.User);
_popupSystem.PopupEntity(Loc.GetString(component.EatMessage, ("foodComp", uid), ("flavors", flavors)), args.Args.User, args.Args.User);
// log successful voluntary eating
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.User):target} ate {ToPrettyString(args.Food.Owner):food}");
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.Args.User):target} ate {ToPrettyString(uid):food}");
}
SoundSystem.Play(args.Food.UseSound.GetSound(), Filter.Pvs(uid), uid, AudioParams.Default.WithVolume(-1f));
_audio.Play(component.UseSound, Filter.Pvs(args.Args.Target.Value), args.Args.Target.Value, true, AudioParams.Default.WithVolume(-1f));
// Try to break all used utensils
foreach (var utensil in args.Utensils)
//TODO: Replace utensil owner with actual UID
foreach (var utensil in args.AdditionalData.Utensils)
{
_utensilSystem.TryBreak((utensil).Owner, args.User);
_utensilSystem.TryBreak(utensil.Owner, args.Args.User);
}
if (args.Food.UsesRemaining > 0)
if (component.UsesRemaining > 0)
{
args.Handled = true;
return;
}
if (string.IsNullOrEmpty(component.TrashPrototype))
EntityManager.QueueDeleteEntity(uid);
if (string.IsNullOrEmpty(args.Food.TrashPrototype))
EntityManager.QueueDeleteEntity(args.Food.Owner);
else
DeleteAndSpawnTrash(args.Food, args.User);
DeleteAndSpawnTrash(component, uid, args.Args.User);
args.Handled = true;
}
private void DeleteAndSpawnTrash(FoodComponent component, EntityUid? user = null)
private void DeleteAndSpawnTrash(FoodComponent component, EntityUid food, EntityUid? user = null)
{
//We're empty. Become trash.
var position = Transform(component.Owner).MapPosition;
var position = Transform(food).MapPosition;
var finisher = EntityManager.SpawnEntity(component.TrashPrototype, position);
// If the user is holding the item
if (user != null && _handsSystem.IsHolding(user.Value, component.Owner, out var hand))
if (user != null && _handsSystem.IsHolding(user.Value, food, out var hand))
{
EntityManager.DeleteEntity((component).Owner);
EntityManager.DeleteEntity(food);
// Put the trash in the user's hand
_handsSystem.TryPickup(user.Value, finisher, hand);
return;
}
EntityManager.QueueDeleteEntity(component.Owner);
EntityManager.QueueDeleteEntity(food);
}
private void AddEatVerb(EntityUid uid, FoodComponent component, GetVerbsEvent<AlternativeVerb> ev)
{
if (component.CancelToken != null)
return;
if (uid == ev.User ||
!ev.CanInteract ||
!ev.CanAccess ||
@@ -267,7 +262,7 @@ namespace Content.Server.Nutrition.EntitySystems
{
Act = () =>
{
TryFeed(ev.User, ev.User, component);
TryFeed(ev.User, ev.User, uid, component);
},
IconTexture = "/Textures/Interface/VerbIcons/cutlery.svg.192dpi.png",
Text = Loc.GetString("food-system-verb-eat"),
@@ -296,7 +291,7 @@ namespace Content.Server.Nutrition.EntitySystems
return;
if (food.UsesRemaining <= 0)
DeleteAndSpawnTrash(food);
DeleteAndSpawnTrash(food, uid);
var firstStomach = stomachs.FirstOrNull(
stomach => _stomachSystem.CanTransferSolution(((IComponent) stomach.Comp).Owner, foodSolution));
@@ -320,7 +315,7 @@ namespace Content.Server.Nutrition.EntitySystems
if (string.IsNullOrEmpty(food.TrashPrototype))
EntityManager.QueueDeleteEntity(food.Owner);
else
DeleteAndSpawnTrash(food);
DeleteAndSpawnTrash(food, uid);
}
private bool TryGetRequiredUtensils(EntityUid user, FoodComponent component,
@@ -361,11 +356,6 @@ namespace Content.Server.Nutrition.EntitySystems
return true;
}
private static void OnFeedCancelled(ForceFeedCancelledEvent args)
{
args.Food.CancelToken = null;
}
/// <summary>
/// Block ingestion attempts based on the equipped mask or head-wear
/// </summary>
@@ -415,5 +405,12 @@ namespace Content.Server.Nutrition.EntitySystems
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;
}
}
}

View File

@@ -44,14 +44,14 @@ namespace Content.Server.Nutrition.EntitySystems
//Prevents food usage with a wrong utensil
if ((food.Utensil & component.Types) == 0)
{
_popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", food.Owner), ("utensil", component.Owner)), user, user);
_popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", target), ("utensil", component.Owner)), user, user);
return false;
}
if (!_interactionSystem.InRangeUnobstructed(user, target, popup: true))
return false;
return _foodSystem.TryFeed(user, user, food);
return _foodSystem.TryFeed(user, user, target, food);
}
/// <summary>

View File

@@ -1,6 +1,3 @@
using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry.Components;
namespace Content.Server.Nutrition;
/// <summary>
@@ -13,69 +10,3 @@ public sealed class IngestionAttemptEvent : CancellableEntityEventArgs
/// </summary>
public EntityUid? Blocker = null;
}
/// <summary>
/// Raised directed at the food after a successful feed do-after.
/// </summary>
public sealed class FeedEvent : EntityEventArgs
{
public readonly EntityUid User;
public readonly FoodComponent Food;
public readonly Solution FoodSolution;
public readonly string FlavorMessage;
public readonly List<UtensilComponent> Utensils;
public FeedEvent(EntityUid user, FoodComponent food, Solution foodSolution, string flavorMessage, List<UtensilComponent> utensils)
{
User = user;
Food = food;
FoodSolution = foodSolution;
FlavorMessage = flavorMessage;
Utensils = utensils;
}
}
/// <summary>
/// Raised directed at the food after a failed force-feed do-after.
/// </summary>
public sealed class ForceFeedCancelledEvent : EntityEventArgs
{
public readonly FoodComponent Food;
public ForceFeedCancelledEvent(FoodComponent food)
{
Food = food;
}
}
/// <summary>
/// Raised directed at the drink after a successful force-drink do-after.
/// </summary>
public sealed class DrinkEvent : EntityEventArgs
{
public readonly EntityUid User;
public readonly DrinkComponent Drink;
public readonly Solution DrinkSolution;
public readonly string FlavorMessage;
public DrinkEvent(EntityUid user, DrinkComponent drink, Solution drinkSolution, string flavorMessage)
{
User = user;
Drink = drink;
DrinkSolution = drinkSolution;
FlavorMessage = flavorMessage;
}
}
/// <summary>
/// Raised directed at the food after a failed force-dink do-after.
/// </summary>
public sealed class DrinkCancelledEvent : EntityEventArgs
{
public readonly DrinkComponent Drink;
public DrinkCancelledEvent(DrinkComponent drink)
{
Drink = drink;
}
}

View File

@@ -1,7 +1,5 @@
using Content.Server.Popups;
using Content.Server.Power.Components;
using Content.Server.Tools;
using Content.Server.Wires;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.APC;
@@ -10,8 +8,8 @@ using Content.Shared.Emag.Systems;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
using Content.Shared.Wires;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
@@ -27,7 +25,7 @@ namespace Content.Server.Power.EntitySystems
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ToolSystem _toolSystem = default!;
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
private const float ScrewTime = 2f;
@@ -201,10 +199,11 @@ namespace Content.Server.Power.EntitySystems
{
if (!EntityManager.TryGetComponent(args.Used, out ToolComponent? tool))
return;
if (_toolSystem.UseTool(args.Used, args.User, uid, 0f, ScrewTime, new[] { "Screwing" }, doAfterCompleteEvent: new ApcToolFinishedEvent(uid), toolComponent: tool))
{
var toolEvData = new ToolEventData(new ApcToolFinishedEvent(uid), fuel: 0f);
if (_toolSystem.UseTool(args.Used, args.User, uid, ScrewTime, new [] { "Screwing" }, toolEvData, toolComponent:tool))
args.Handled = true;
}
}
private void OnToolFinished(ApcToolFinishedEvent args)
@@ -219,13 +218,9 @@ namespace Content.Server.Power.EntitySystems
}
if (component.IsApcOpen)
{
SoundSystem.Play(component.ScrewdriverOpenSound.GetSound(), Filter.Pvs(args.Target), args.Target);
}
else
{
SoundSystem.Play(component.ScrewdriverCloseSound.GetSound(), Filter.Pvs(args.Target), args.Target);
}
}
private void UpdatePanelAppearance(EntityUid uid, AppearanceComponent? appearance = null, ApcComponent? apc = null)

View File

@@ -2,9 +2,10 @@ using Content.Server.Administration.Logs;
using Content.Server.Electrocution;
using Content.Server.Power.Components;
using Content.Server.Stack;
using Content.Server.Tools;
using Content.Shared.Database;
using Content.Shared.Interaction;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
using Robust.Shared.Map;
namespace Content.Server.Power.EntitySystems;
@@ -13,7 +14,7 @@ public sealed partial class CableSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileManager = default!;
[Dependency] private readonly ToolSystem _toolSystem = default!;
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!;
[Dependency] private readonly IAdminLogManager _adminLogs = default!;
@@ -35,8 +36,8 @@ public sealed partial class CableSystem : EntitySystem
if (args.Handled)
return;
var ev = new CuttingFinishedEvent(args.User);
args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, 0, cable.CuttingDelay, new[] { cable.CuttingQuality }, doAfterCompleteEvent: ev, doAfterEventTarget: uid);
var toolEvData = new ToolEventData(new CuttingFinishedEvent(args.User), targetEntity: uid);
args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, cable.CuttingDelay, new[] { cable.CuttingQuality }, toolEvData);
}
private void OnCableCut(EntityUid uid, CableComponent cable, CuttingFinishedEvent args)

View File

@@ -4,6 +4,7 @@ using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Server.RCD.Components;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;

View File

@@ -4,12 +4,14 @@ using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
namespace Content.Server.Repairable
{
public sealed class RepairableSystem : EntitySystem
{
[Dependency] private readonly ToolSystem _toolSystem = default!;
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly IAdminLogManager _adminLogger= default!;
@@ -30,8 +32,10 @@ namespace Content.Server.Repairable
if (args.User == args.Target)
delay *= component.SelfRepairPenalty;
var toolEvData = new ToolEventData(null);
// Can the tool actually repair this, does it have enough fuel?
if (!await _toolSystem.UseTool(args.Used, args.User, uid, component.FuelCost, delay, component.QualityNeeded))
if (!_toolSystem.UseTool(args.Used, args.User, uid, delay, component.QualityNeeded, toolEvData, component.FuelCost))
return;
if (component.Damage != null)

View File

@@ -1,5 +1,3 @@
using System.Threading;
namespace Content.Server.Resist;
[RegisterComponent]
@@ -10,9 +8,4 @@ public sealed class CanEscapeInventoryComponent : Component
/// </summary>
[DataField("baseResistTime")]
public float BaseResistTime = 5f;
/// <summary>
/// Cancellation token used to cancel the DoAfter if the mob is removed before it's complete
/// </summary>
public CancellationTokenSource? CancelToken;
}

View File

@@ -2,11 +2,11 @@ using Content.Server.DoAfter;
using Content.Server.Contests;
using Robust.Shared.Containers;
using Content.Server.Popups;
using Robust.Shared.Player;
using Content.Shared.Storage;
using Content.Shared.Inventory;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.ActionBlocker;
using Content.Shared.DoAfter;
using Content.Shared.Movement.Events;
using Content.Shared.Interaction.Events;
@@ -31,16 +31,12 @@ public sealed class EscapeInventorySystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<CanEscapeInventoryComponent, MoveInputEvent>(OnRelayMovement);
SubscribeLocalEvent<CanEscapeInventoryComponent, EscapeDoAfterComplete>(OnEscapeComplete);
SubscribeLocalEvent<CanEscapeInventoryComponent, EscapeDoAfterCancel>(OnEscapeFail);
SubscribeLocalEvent<CanEscapeInventoryComponent, DoAfterEvent<EscapeInventoryEvent>>(OnEscape);
SubscribeLocalEvent<CanEscapeInventoryComponent, DroppedEvent>(OnDropped);
}
private void OnRelayMovement(EntityUid uid, CanEscapeInventoryComponent component, ref MoveInputEvent args)
{
if (component.CancelToken != null)
return;
if (!_containerSystem.TryGetContainingContainer(uid, out var container) || !_actionBlockerSystem.CanInteract(uid, container.Owner))
return;
@@ -69,41 +65,38 @@ public sealed class EscapeInventorySystem : EntitySystem
private void AttemptEscape(EntityUid user, EntityUid container, CanEscapeInventoryComponent component, float multiplier = 1f)
{
component.CancelToken = new();
var doAfterEventArgs = new DoAfterEventArgs(user, component.BaseResistTime * multiplier, component.CancelToken.Token, container)
var escapeEvent = new EscapeInventoryEvent();
var doAfterEventArgs = new DoAfterEventArgs(user, component.BaseResistTime * multiplier, target:container)
{
BreakOnTargetMove = false,
BreakOnUserMove = false,
BreakOnDamage = true,
BreakOnStun = true,
NeedHand = false,
UserFinishedEvent = new EscapeDoAfterComplete(),
UserCancelledEvent = new EscapeDoAfterCancel(),
NeedHand = false
};
_popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting"), user, user);
_popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting-target"), container, container);
_doAfterSystem.DoAfter(doAfterEventArgs);
_doAfterSystem.DoAfter(doAfterEventArgs, escapeEvent);
}
private void OnEscapeComplete(EntityUid uid, CanEscapeInventoryComponent component, EscapeDoAfterComplete ev)
private void OnEscape(EntityUid uid, CanEscapeInventoryComponent component, DoAfterEvent<EscapeInventoryEvent> args)
{
//Drops the mob on the tile below the container
if (args.Handled || args.Cancelled)
return;
Transform(uid).AttachParentToContainerOrGrid(EntityManager);
component.CancelToken = null;
}
private void OnEscapeFail(EntityUid uid, CanEscapeInventoryComponent component, EscapeDoAfterCancel ev)
{
component.CancelToken = null;
args.Handled = true;
}
private void OnDropped(EntityUid uid, CanEscapeInventoryComponent component, DroppedEvent args)
{
component.CancelToken?.Cancel();
//TODO: Enter cancel logic here
}
private sealed class EscapeDoAfterComplete : EntityEventArgs { }
private sealed class EscapeInventoryEvent : EntityEventArgs
{
private sealed class EscapeDoAfterCancel : EntityEventArgs { }
}
}

View File

@@ -19,7 +19,7 @@ public sealed class ResistLockerComponent : Component
public bool IsResisting = false;
/// <summary>
/// Cancellation token used to cancel the DoAfter if the container is opened before it's complete
/// Used to cancel the DoAfter when a locker is open
/// </summary>
public CancellationTokenSource? CancelToken;
public Shared.DoAfter.DoAfter? DoAfter;
}

View File

@@ -1,11 +1,12 @@
using Content.Server.Storage.Components;
using Content.Server.DoAfter;
using Robust.Shared.Containers;
using Content.Server.Popups;
using Content.Shared.Movement.Events;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.DoAfter;
using Content.Shared.Lock;
using Content.Shared.Movement.Events;
using Content.Shared.Popups;
using Robust.Shared.Containers;
namespace Content.Server.Resist;
@@ -20,9 +21,8 @@ public sealed class ResistLockerSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<ResistLockerComponent, ContainerRelayMovementEntityEvent>(OnRelayMovement);
SubscribeLocalEvent<ResistLockerComponent, ResistDoAfterComplete>(OnDoAfterComplete);
SubscribeLocalEvent<ResistLockerComponent, ResistDoAfterCancelled>(OnDoAfterCancelled);
SubscribeLocalEvent<ResistLockerComponent, EntRemovedFromContainerMessage>(OnRemovedFromContainer);
SubscribeLocalEvent<ResistLockerComponent, DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<ResistLockerComponent, EntRemovedFromContainerMessage>(OnRemoved);
}
private void OnRelayMovement(EntityUid uid, ResistLockerComponent component, ref ContainerRelayMovementEntityEvent args)
@@ -44,25 +44,38 @@ public sealed class ResistLockerSystem : EntitySystem
if (!Resolve(target, ref storageComponent, ref resistLockerComponent))
return;
resistLockerComponent.CancelToken = new();
var doAfterEventArgs = new DoAfterEventArgs(user, resistLockerComponent.ResistTime, resistLockerComponent.CancelToken.Token, target)
var doAfterEventArgs = new DoAfterEventArgs(user, resistLockerComponent.ResistTime, target:target)
{
BreakOnTargetMove = false,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
NeedHand = false, //No hands 'cause we be kickin'
TargetFinishedEvent = new ResistDoAfterComplete(user, target),
TargetCancelledEvent = new ResistDoAfterCancelled(user)
NeedHand = false //No hands 'cause we be kickin'
};
resistLockerComponent.IsResisting = true;
_popupSystem.PopupEntity(Loc.GetString("resist-locker-component-start-resisting"), user, user, PopupType.Large);
_doAfterSystem.DoAfter(doAfterEventArgs);
resistLockerComponent.DoAfter = _doAfterSystem.DoAfter(doAfterEventArgs);
}
private void OnDoAfterComplete(EntityUid uid, ResistLockerComponent component, ResistDoAfterComplete ev)
private void OnRemoved(EntityUid uid, ResistLockerComponent component, EntRemovedFromContainerMessage args)
{
if (component.DoAfter != null)
_doAfterSystem.Cancel(uid, component.DoAfter);
}
private void OnDoAfter(EntityUid uid, ResistLockerComponent component, DoAfterEvent args)
{
if (args.Cancelled)
{
component.IsResisting = false;
_popupSystem.PopupEntity(Loc.GetString("resist-locker-component-resist-interrupted"), args.Args.User, args.Args.User, PopupType.Medium);
return;
}
if (args.Handled || args.Args.Target == null)
return;
component.IsResisting = false;
if (TryComp<EntityStorageComponent>(uid, out var storageComponent))
@@ -70,44 +83,12 @@ public sealed class ResistLockerSystem : EntitySystem
if (storageComponent.IsWeldedShut)
storageComponent.IsWeldedShut = false;
if (TryComp<LockComponent>(ev.Target, out var lockComponent))
_lockSystem.Unlock(uid, ev.User, lockComponent);
if (TryComp<LockComponent>(args.Args.Target.Value, out var lockComponent))
_lockSystem.Unlock(uid, args.Args.User, lockComponent);
component.CancelToken = null;
_entityStorage.TryOpenStorage(ev.User, storageComponent.Owner);
_entityStorage.TryOpenStorage(args.Args.User, uid);
}
}
private void OnDoAfterCancelled(EntityUid uid, ResistLockerComponent component, ResistDoAfterCancelled ev)
{
component.IsResisting = false;
component.CancelToken = null;
_popupSystem.PopupEntity(Loc.GetString("resist-locker-component-resist-interrupted"), ev.User, ev.User, PopupType.Medium);
}
private void OnRemovedFromContainer(EntityUid uid, ResistLockerComponent component, EntRemovedFromContainerMessage message)
{
component.CancelToken?.Cancel();
}
private sealed class ResistDoAfterComplete : EntityEventArgs
{
public readonly EntityUid User;
public readonly EntityUid Target;
public ResistDoAfterComplete(EntityUid userUid, EntityUid target)
{
User = userUid;
Target = target;
}
}
private sealed class ResistDoAfterCancelled : EntityEventArgs
{
public readonly EntityUid User;
public ResistDoAfterCancelled(EntityUid userUid)
{
User = userUid;
}
args.Handled = true;
}
}

View File

@@ -1,6 +1,5 @@
using Content.Shared.Popups;
using Content.Shared.Damage;
using Content.Server.DoAfter;
using Content.Shared.Revenant;
using Robust.Shared.Random;
using Robust.Shared.Map;
@@ -19,6 +18,7 @@ using Content.Shared.Bed.Sleep;
using System.Linq;
using Content.Server.Maps;
using Content.Server.Revenant.Components;
using Content.Shared.DoAfter;
using Content.Shared.Emag.Systems;
using Content.Shared.FixedPoint;
using Content.Shared.Humanoid;
@@ -46,10 +46,8 @@ public sealed partial class RevenantSystem
private void InitializeAbilities()
{
SubscribeLocalEvent<RevenantComponent, InteractNoHandEvent>(OnInteract);
SubscribeLocalEvent<RevenantComponent, SoulSearchDoAfterComplete>(OnSoulSearchComplete);
SubscribeLocalEvent<RevenantComponent, SoulSearchDoAfterCancelled>(OnSoulSearchCancelled);
SubscribeLocalEvent<RevenantComponent, HarvestDoAfterComplete>(OnHarvestComplete);
SubscribeLocalEvent<RevenantComponent, HarvestDoAfterCancelled>(OnHarvestCancelled);
SubscribeLocalEvent<RevenantComponent, DoAfterEvent<SoulEvent>>(OnSoulSearch);
SubscribeLocalEvent<RevenantComponent, DoAfterEvent<HarvestEvent>>(OnHarvest);
SubscribeLocalEvent<RevenantComponent, RevenantDefileActionEvent>(OnDefileAction);
SubscribeLocalEvent<RevenantComponent, RevenantOverloadLightsActionEvent>(OnOverloadLightsAction);
@@ -86,26 +84,23 @@ public sealed partial class RevenantSystem
private void BeginSoulSearchDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant)
{
if (revenant.SoulSearchCancelToken != null)
return;
_popup.PopupEntity(Loc.GetString("revenant-soul-searching", ("target", target)), uid, uid, PopupType.Medium);
revenant.SoulSearchCancelToken = new();
var searchDoAfter = new DoAfterEventArgs(uid, revenant.SoulSearchDuration, revenant.SoulSearchCancelToken.Token, target)
var soulSearchEvent = new SoulEvent();
var searchDoAfter = new DoAfterEventArgs(uid, revenant.SoulSearchDuration, target:target)
{
BreakOnUserMove = true,
DistanceThreshold = 2,
UserFinishedEvent = new SoulSearchDoAfterComplete(target),
UserCancelledEvent = new SoulSearchDoAfterCancelled(),
DistanceThreshold = 2
};
_doAfter.DoAfter(searchDoAfter);
_doAfter.DoAfter(searchDoAfter, soulSearchEvent);
}
private void OnSoulSearchComplete(EntityUid uid, RevenantComponent component, SoulSearchDoAfterComplete args)
private void OnSoulSearch(EntityUid uid, RevenantComponent component, DoAfterEvent<SoulEvent> args)
{
if (!TryComp<EssenceComponent>(args.Target, out var essence))
if (args.Handled || args.Cancelled)
return;
if (!TryComp<EssenceComponent>(args.Args.Target, out var essence))
return;
component.SoulSearchCancelToken = null;
essence.SearchComplete = true;
string message;
@@ -121,19 +116,13 @@ public sealed partial class RevenantSystem
message = "revenant-soul-yield-average";
break;
}
_popup.PopupEntity(Loc.GetString(message, ("target", args.Target)), args.Target, uid, PopupType.Medium);
}
_popup.PopupEntity(Loc.GetString(message, ("target", args.Args.Target)), args.Args.Target.Value, uid, PopupType.Medium);
private void OnSoulSearchCancelled(EntityUid uid, RevenantComponent component, SoulSearchDoAfterCancelled args)
{
component.SoulSearchCancelToken = null;
args.Handled = true;
}
private void BeginHarvestDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant, EssenceComponent essence)
{
if (revenant.HarvestCancelToken != null)
return;
if (essence.Harvested)
{
_popup.PopupEntity(Loc.GetString("revenant-soul-harvested"), target, uid, PopupType.SmallCaution);
@@ -146,14 +135,13 @@ public sealed partial class RevenantSystem
return;
}
revenant.HarvestCancelToken = new();
var doAfter = new DoAfterEventArgs(uid, revenant.HarvestDebuffs.X, revenant.HarvestCancelToken.Token, target)
var harvestEvent = new HarvestEvent();
var doAfter = new DoAfterEventArgs(uid, revenant.HarvestDebuffs.X, target:target)
{
DistanceThreshold = 2,
BreakOnUserMove = true,
NeedHand = false,
UserFinishedEvent = new HarvestDoAfterComplete(target),
UserCancelledEvent = new HarvestDoAfterCancelled(),
NeedHand = false
};
_appearance.SetData(uid, RevenantVisuals.Harvesting, true);
@@ -162,29 +150,37 @@ public sealed partial class RevenantSystem
target, PopupType.Large);
TryUseAbility(uid, revenant, 0, revenant.HarvestDebuffs);
_doAfter.DoAfter(doAfter);
_doAfter.DoAfter(doAfter, harvestEvent);
}
private void OnHarvestComplete(EntityUid uid, RevenantComponent component, HarvestDoAfterComplete args)
private void OnHarvest(EntityUid uid, RevenantComponent component, DoAfterEvent<HarvestEvent> args)
{
component.HarvestCancelToken = null;
_appearance.SetData(uid, RevenantVisuals.Harvesting, false);
if (args.Cancelled)
{
_appearance.SetData(uid, RevenantVisuals.Harvesting, false);
return;
}
if (!TryComp<EssenceComponent>(args.Target, out var essence))
if (args.Handled || args.Args.Target == null)
return;
_popup.PopupEntity(Loc.GetString("revenant-soul-finish-harvest", ("target", args.Target)),
args.Target, PopupType.LargeCaution);
_appearance.SetData(uid, RevenantVisuals.Harvesting, false);
if (!TryComp<EssenceComponent>(args.Args.Target, out var essence))
return;
_popup.PopupEntity(Loc.GetString("revenant-soul-finish-harvest", ("target", args.Args.Target)),
args.Args.Target.Value, PopupType.LargeCaution);
essence.Harvested = true;
ChangeEssenceAmount(uid, essence.EssenceAmount, component);
_store.TryAddCurrency(new Dictionary<string, FixedPoint2>
{ {component.StolenEssenceCurrencyPrototype, essence.EssenceAmount} }, uid);
if (!HasComp<MobStateComponent>(args.Target))
if (!HasComp<MobStateComponent>(args.Args.Target))
return;
if (_mobState.IsAlive(args.Target) || _mobState.IsCritical(args.Target))
if (_mobState.IsAlive(args.Args.Target.Value) || _mobState.IsCritical(args.Args.Target.Value))
{
_popup.PopupEntity(Loc.GetString("revenant-max-essence-increased"), uid, uid);
component.EssenceRegenCap += component.MaxEssenceUpgradeAmount;
@@ -192,17 +188,13 @@ public sealed partial class RevenantSystem
//KILL THEMMMM
if (!_mobThresholdSystem.TryGetThresholdForState(args.Target, MobState.Dead, out var damage))
if (!_mobThresholdSystem.TryGetThresholdForState(args.Args.Target.Value, MobState.Dead, out var damage))
return;
DamageSpecifier dspec = new();
dspec.DamageDict.Add("Poison", damage.Value);
_damage.TryChangeDamage(args.Target, dspec, true, origin: uid);
}
_damage.TryChangeDamage(args.Args.Target, dspec, true, origin: uid);
private void OnHarvestCancelled(EntityUid uid, RevenantComponent component, HarvestDoAfterCancelled args)
{
component.HarvestCancelToken = null;
_appearance.SetData(uid, RevenantVisuals.Harvesting, false);
args.Handled = true;
}
private void OnDefileAction(EntityUid uid, RevenantComponent component, RevenantDefileActionEvent args)
@@ -335,4 +327,14 @@ public sealed partial class RevenantSystem
_emag.DoEmagEffect(ent, ent); //it emags itself. spooky.
}
}
private sealed class SoulEvent : EntityEventArgs
{
}
private sealed class HarvestEvent : EntityEventArgs
{
}
}

View File

@@ -80,4 +80,9 @@ public sealed class StickyComponent : Component
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public EntityUid? StuckTo;
/// <summary>
/// For the DoAfter event to tell if it should stick or unstick
/// </summary>
public bool Stick;
}

View File

@@ -2,6 +2,7 @@ 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.Components;
@@ -26,8 +27,7 @@ public sealed class StickySystem : EntitySystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StickSuccessfulEvent>(OnStickSuccessful);
SubscribeLocalEvent<UnstickSuccessfulEvent>(OnUnstickSuccessful);
SubscribeLocalEvent<StickyComponent, DoAfterEvent>(OnStickSuccessful);
SubscribeLocalEvent<StickyComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<StickyComponent, GetVerbsEvent<Verb>>(AddUnstickVerb);
}
@@ -84,10 +84,11 @@ public sealed class StickySystem : EntitySystem
_popupSystem.PopupEntity(msg, user, user);
}
component.Stick = true;
// start sticking object to target
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: target)
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: target, used: uid)
{
BroadcastFinishedEvent = new StickSuccessfulEvent(uid, user, target),
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
@@ -103,13 +104,18 @@ public sealed class StickySystem : EntitySystem
return true;
}
private void OnStickSuccessful(StickSuccessfulEvent ev)
private void OnStickSuccessful(EntityUid uid, StickyComponent component, DoAfterEvent args)
{
// check if entity still has sticky component
if (!TryComp(ev.Uid, out StickyComponent? component))
if (args.Handled || args.Cancelled || args.Args.Target == null)
return;
StickToEntity(ev.Uid, ev.Target, ev.User, component);
if (component.Stick)
StickToEntity(uid, args.Args.Target.Value, args.Args.User, component);
else
UnstickFromEntity(uid, args.Args.User, component);
args.Handled = true;
}
private void StartUnsticking(EntityUid uid, EntityUid user, StickyComponent? component = null)
@@ -127,10 +133,11 @@ public sealed class StickySystem : EntitySystem
_popupSystem.PopupEntity(msg, user, user);
}
component.Stick = false;
// start unsticking object
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: uid)
{
BroadcastFinishedEvent = new UnstickSuccessfulEvent(uid, user),
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
@@ -144,15 +151,6 @@ public sealed class StickySystem : EntitySystem
}
}
private void OnUnstickSuccessful(UnstickSuccessfulEvent ev)
{
// check if entity still has sticky component
if (!TryComp(ev.Uid, out StickyComponent? component))
return;
UnstickFromEntity(ev.Uid, ev.User, component);
}
public void StickToEntity(EntityUid uid, EntityUid target, EntityUid user, StickyComponent? component = null)
{
if (!Resolve(uid, ref component))
@@ -215,30 +213,4 @@ public sealed class StickySystem : EntitySystem
component.StuckTo = null;
RaiseLocalEvent(uid, new EntityUnstuckEvent(target, user), true);
}
private sealed class StickSuccessfulEvent : EntityEventArgs
{
public readonly EntityUid Uid;
public readonly EntityUid User;
public readonly EntityUid Target;
public StickSuccessfulEvent(EntityUid uid, EntityUid user, EntityUid target)
{
Uid = uid;
User = user;
Target = target;
}
}
private sealed class UnstickSuccessfulEvent : EntityEventArgs
{
public readonly EntityUid Uid;
public readonly EntityUid User;
public UnstickSuccessfulEvent(EntityUid uid, EntityUid user)
{
Uid = uid;
User = user;
}
}
}

View File

@@ -1,6 +1,4 @@
using System.Threading;
namespace Content.Server.Storage.Components;
namespace Content.Server.Storage.Components;
[RegisterComponent]
public sealed class BluespaceLockerComponent : Component
@@ -55,8 +53,6 @@ public sealed class BluespaceLockerComponent : Component
[DataField("pickLinksFromNonBluespaceLockers"), ViewVariables(VVAccess.ReadWrite)]
public bool PickLinksFromNonBluespaceLockers = true;
public CancellationTokenSource? CancelToken;
/// <summary>
/// Determines if links automatically added get the source locker set as a target
/// </summary>

View File

@@ -30,11 +30,6 @@ namespace Content.Server.Storage.Components
[DataField("areaInsert")]
public bool AreaInsert = false; // "Attacking" with the storage entity causes it to insert all nearby storables after a delay
/// <summary>
/// Token for interrupting area insert do after.
/// </summary>
public CancellationTokenSource? CancelToken;
[DataField("areaInsertRadius")]
public int AreaInsertRadius = 1;

View File

@@ -1,5 +1,4 @@
using System.Linq;
using System.Threading;
using System.Linq;
using Content.Server.DoAfter;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Mind.Components;
@@ -9,6 +8,7 @@ using Content.Server.Storage.Components;
using Content.Server.Tools.Systems;
using Content.Shared.Access.Components;
using Content.Shared.Coordinates;
using Content.Shared.DoAfter;
using Content.Shared.Lock;
using Content.Shared.Storage.Components;
using Robust.Shared.Random;
@@ -33,7 +33,7 @@ public sealed class BluespaceLockerSystem : EntitySystem
SubscribeLocalEvent<BluespaceLockerComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<BluespaceLockerComponent, StorageBeforeOpenEvent>(PreOpen);
SubscribeLocalEvent<BluespaceLockerComponent, StorageAfterCloseEvent>(PostClose);
SubscribeLocalEvent<BluespaceLockerComponent, BluespaceLockerTeleportDelayComplete>(OnBluespaceLockerTeleportDelayComplete);
SubscribeLocalEvent<BluespaceLockerComponent, DoAfterEvent>(OnDoAfter);
}
private void OnStartup(EntityUid uid, BluespaceLockerComponent component, ComponentStartup args)
@@ -67,13 +67,6 @@ public sealed class BluespaceLockerSystem : EntitySystem
if (!Resolve(uid, ref entityStorageComponent))
return;
if (component.CancelToken != null)
{
component.CancelToken.Cancel();
component.CancelToken = null;
return;
}
if (!component.BehaviorProperties.ActOnOpen)
return;
@@ -265,9 +258,14 @@ public sealed class BluespaceLockerSystem : EntitySystem
PostClose(uid, component);
}
private void OnBluespaceLockerTeleportDelayComplete(EntityUid uid, BluespaceLockerComponent component, BluespaceLockerTeleportDelayComplete args)
private void OnDoAfter(EntityUid uid, BluespaceLockerComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled)
return;
PostClose(uid, component, false);
args.Handled = true;
}
private void PostClose(EntityUid uid, BluespaceLockerComponent component, bool doDelay = true)
@@ -278,8 +276,6 @@ public sealed class BluespaceLockerSystem : EntitySystem
if (!Resolve(uid, ref entityStorageComponent))
return;
component.CancelToken?.Cancel();
if (!component.BehaviorProperties.ActOnClose)
return;
@@ -287,13 +283,8 @@ public sealed class BluespaceLockerSystem : EntitySystem
if (doDelay && component.BehaviorProperties.Delay > 0)
{
EnsureComp<DoAfterComponent>(uid);
component.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(
new DoAfterEventArgs(uid, component.BehaviorProperties.Delay, component.CancelToken.Token)
{
UserFinishedEvent = new BluespaceLockerTeleportDelayComplete()
});
_doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.BehaviorProperties.Delay));
return;
}
@@ -400,8 +391,4 @@ public sealed class BluespaceLockerSystem : EntitySystem
break;
}
}
private sealed class BluespaceLockerTeleportDelayComplete : EntityEventArgs
{
}
}

View File

@@ -6,7 +6,9 @@ using Content.Shared.Verbs;
using Content.Server.Disposal.Unit.Components;
using Content.Server.Disposal.Unit.EntitySystems;
using Content.Server.DoAfter;
using Content.Shared.DoAfter;
using Content.Shared.Placeable;
using Content.Shared.Storage;
using Robust.Shared.Containers;
using Robust.Shared.Random;
@@ -17,6 +19,7 @@ namespace Content.Server.Storage.EntitySystems
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly DisposalUnitSystem _disposalUnitSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
public override void Initialize()
{
@@ -24,8 +27,7 @@ namespace Content.Server.Storage.EntitySystems
SubscribeLocalEvent<DumpableComponent, AfterInteractEvent>(OnAfterInteract, after: new[]{ typeof(StorageSystem) });
SubscribeLocalEvent<DumpableComponent, GetVerbsEvent<AlternativeVerb>>(AddDumpVerb);
SubscribeLocalEvent<DumpableComponent, GetVerbsEvent<UtilityVerb>>(AddUtilityVerbs);
SubscribeLocalEvent<DumpCompletedEvent>(OnDumpCompleted);
SubscribeLocalEvent<DumpCancelledEvent>(OnDumpCancelled);
SubscribeLocalEvent<DumpableComponent, DoAfterEvent>(OnDoAfter);
}
private void OnAfterInteract(EntityUid uid, DumpableComponent component, AfterInteractEvent args)
@@ -33,16 +35,8 @@ namespace Content.Server.Storage.EntitySystems
if (!args.CanReach)
return;
if (!TryComp<ServerStorageComponent>(args.Used, out var storage))
return;
if (storage.StoredEntities == null || storage.StoredEntities.Count == 0 || storage.CancelToken != null)
return;
if (HasComp<DisposalUnitComponent>(args.Target) || HasComp<PlaceableSurfaceComponent>(args.Target))
{
StartDoAfter(uid, args.Target.Value, args.User, component, storage);
}
StartDoAfter(uid, args.Target.Value, args.User, component);
}
private void AddDumpVerb(EntityUid uid, DumpableComponent dumpable, GetVerbsEvent<AlternativeVerb> args)
@@ -57,7 +51,7 @@ namespace Content.Server.Storage.EntitySystems
{
Act = () =>
{
StartDoAfter(uid, null, args.User, dumpable, storage, 0.6f);
StartDoAfter(uid, null, args.User, dumpable);//Had multiplier of 0.6f
},
Text = Loc.GetString("dump-verb-name"),
IconTexture = "/Textures/Interface/VerbIcons/drop.svg.192dpi.png",
@@ -79,7 +73,7 @@ namespace Content.Server.Storage.EntitySystems
{
Act = () =>
{
StartDoAfter(uid, args.Target, args.User, dumpable, storage);
StartDoAfter(uid, args.Target, args.User, dumpable);
},
Text = Loc.GetString("dump-disposal-verb-name", ("unit", args.Target)),
IconEntity = uid
@@ -93,7 +87,7 @@ namespace Content.Server.Storage.EntitySystems
{
Act = () =>
{
StartDoAfter(uid, args.Target, args.User, dumpable, storage);
StartDoAfter(uid, args.Target, args.User, dumpable);
},
Text = Loc.GetString("dump-placeable-verb-name", ("surface", args.Target)),
IconEntity = uid
@@ -102,21 +96,15 @@ namespace Content.Server.Storage.EntitySystems
}
}
public void StartDoAfter(EntityUid storageUid, EntityUid? targetUid, EntityUid userUid, DumpableComponent dumpable, ServerStorageComponent storage, float multiplier = 1)
public void StartDoAfter(EntityUid storageUid, EntityUid? targetUid, EntityUid userUid, DumpableComponent dumpable)
{
if (dumpable.CancelToken != null)
if (!TryComp<SharedStorageComponent>(storageUid, out var storage) || storage.StoredEntities == null)
return;
if (storage.StoredEntities == null)
return;
float delay = storage.StoredEntities.Count * (float) dumpable.DelayPerItem.TotalSeconds * dumpable.Multiplier;
float delay = storage.StoredEntities.Count * (float) dumpable.DelayPerItem.TotalSeconds * multiplier;
dumpable.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, delay, dumpable.CancelToken.Token, target: targetUid)
_doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, delay, target: targetUid, used: storageUid)
{
BroadcastFinishedEvent = new DumpCompletedEvent(dumpable, userUid, targetUid, storage.StoredEntities),
BroadcastCancelledEvent = new DumpCancelledEvent(dumpable.Owner),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
@@ -124,71 +112,43 @@ namespace Content.Server.Storage.EntitySystems
});
}
private void OnDumpCompleted(DumpCompletedEvent args)
private void OnDoAfter(EntityUid uid, DumpableComponent component, DoAfterEvent args)
{
args.Component.CancelToken = null;
if (args.Handled || args.Cancelled || !TryComp<SharedStorageComponent>(uid, out var storage) || storage.StoredEntities == null)
return;
Queue<EntityUid> dumpQueue = new();
foreach (var entity in args.StoredEntities)
foreach (var entity in storage.StoredEntities)
{
dumpQueue.Enqueue(entity);
}
if (TryComp<DisposalUnitComponent>(args.Target, out var disposal))
{
foreach (var entity in dumpQueue)
{
_disposalUnitSystem.DoInsertDisposalUnit(args.Target.Value, entity, args.User);
}
return;
}
foreach (var entity in dumpQueue)
{
var transform = Transform(entity);
transform.AttachParentToContainerOrGrid(EntityManager);
transform.LocalPosition = transform.LocalPosition + _random.NextVector2Box() / 2;
_container.AttachParentToContainerOrGrid(transform);
transform.LocalPosition += _random.NextVector2Box() / 2;
transform.LocalRotation = _random.NextAngle();
}
if (HasComp<PlaceableSurfaceComponent>(args.Target))
if (args.Args.Target == null)
return;
if (HasComp<DisposalUnitComponent>(args.Args.Target.Value))
{
foreach (var entity in dumpQueue)
{
Transform(entity).LocalPosition = Transform(args.Target.Value).LocalPosition + _random.NextVector2Box() / 4;
_disposalUnitSystem.DoInsertDisposalUnit(args.Args.Target.Value, entity, args.Args.User);
}
return;
}
}
private void OnDumpCancelled(DumpCancelledEvent args)
{
if (TryComp<DumpableComponent>(args.Uid, out var dumpable))
dumpable.CancelToken = null;
}
private sealed class DumpCancelledEvent : EntityEventArgs
{
public readonly EntityUid Uid;
public DumpCancelledEvent(EntityUid uid)
if (HasComp<PlaceableSurfaceComponent>(args.Args.Target.Value))
{
Uid = uid;
}
}
private sealed class DumpCompletedEvent : EntityEventArgs
{
public DumpableComponent Component { get; }
public EntityUid User { get; }
public EntityUid? Target { get; }
public IReadOnlyList<EntityUid> StoredEntities { get; }
public DumpCompletedEvent(DumpableComponent component, EntityUid user, EntityUid? target, IReadOnlyList<EntityUid> storedEntities)
{
Component = component;
User = user;
Target = target;
StoredEntities = storedEntities;
foreach (var entity in dumpQueue)
{
Transform(entity).LocalPosition = Transform(args.Args.Target.Value).LocalPosition + _random.NextVector2Box() / 4;
}
}
}
}

View File

@@ -2,7 +2,6 @@ using System.Linq;
using Content.Server.Hands.Components;
using Content.Server.Storage.Components;
using Content.Shared.Interaction;
using Content.Shared.Movement;
using Content.Shared.Storage;
using Content.Shared.Verbs;
using JetBrains.Annotations;
@@ -30,6 +29,7 @@ using static Content.Shared.Storage.SharedStorageComponent;
using Content.Shared.ActionBlocker;
using Content.Shared.CombatMode;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.DoAfter;
using Content.Shared.Implants.Components;
using Content.Shared.Lock;
using Content.Shared.Movement.Events;
@@ -75,8 +75,7 @@ namespace Content.Server.Storage.EntitySystems
SubscribeLocalEvent<ServerStorageComponent, BoundUIClosedEvent>(OnBoundUIClosed);
SubscribeLocalEvent<ServerStorageComponent, EntRemovedFromContainerMessage>(OnStorageItemRemoved);
SubscribeLocalEvent<ServerStorageComponent, AreaPickupCompleteEvent>(OnAreaPickupComplete);
SubscribeLocalEvent<ServerStorageComponent, AreaPickupCancelledEvent>(OnAreaPickupCancelled);
SubscribeLocalEvent<ServerStorageComponent, DoAfterEvent<StorageData>>(OnDoAfter);
SubscribeLocalEvent<EntityStorageComponent, GetVerbsEvent<InteractionVerb>>(AddToggleOpenVerb);
SubscribeLocalEvent<EntityStorageComponent, ContainerRelayMovementEntityEvent>(OnRelayMovement);
@@ -84,55 +83,6 @@ namespace Content.Server.Storage.EntitySystems
SubscribeLocalEvent<StorageFillComponent, MapInitEvent>(OnStorageFillMapInit);
}
private void OnAreaPickupCancelled(EntityUid uid, ServerStorageComponent component, AreaPickupCancelledEvent args)
{
component.CancelToken = null;
}
private void OnAreaPickupComplete(EntityUid uid, ServerStorageComponent component, AreaPickupCompleteEvent args)
{
component.CancelToken = null;
var successfullyInserted = new List<EntityUid>();
var successfullyInsertedPositions = new List<EntityCoordinates>();
var itemQuery = GetEntityQuery<ItemComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
xformQuery.TryGetComponent(uid, out var xform);
foreach (var entity in args.ValidStorables)
{
// Check again, situation may have changed for some entities, but we'll still pick up any that are valid
if (_containerSystem.IsEntityInContainer(entity)
|| entity == args.User
|| !itemQuery.HasComponent(entity))
continue;
if (xform == null ||
!xformQuery.TryGetComponent(entity, out var targetXform) ||
targetXform.MapID != xform.MapID)
{
continue;
}
var position = EntityCoordinates.FromMap(
xform.ParentUid.IsValid() ? xform.ParentUid : uid,
new MapCoordinates(_transform.GetWorldPosition(targetXform, xformQuery),
targetXform.MapID), EntityManager);
if (PlayerInsertEntityInWorld(uid, args.User, entity, component))
{
successfullyInserted.Add(entity);
successfullyInsertedPositions.Add(position);
}
}
// If we picked up atleast one thing, play a sound and do a cool animation!
if (successfullyInserted.Count > 0)
{
_audio.PlayPvs(component.StorageInsertSound, uid);
RaiseNetworkEvent(new AnimateInsertingEntitiesEvent(uid, successfullyInserted, successfullyInsertedPositions));
}
}
private void OnComponentInit(EntityUid uid, ServerStorageComponent storageComp, ComponentInit args)
{
base.Initialize();
@@ -147,10 +97,7 @@ namespace Content.Server.Storage.EntitySystems
private void OnRelayMovement(EntityUid uid, EntityStorageComponent component, ref ContainerRelayMovementEntityEvent args)
{
if (!EntityManager.HasComponent<HandsComponent>(args.Entity))
return;
if (_gameTiming.CurTime < component.LastInternalOpenAttempt + EntityStorageComponent.InternalOpenAttemptDelay)
if (!EntityManager.HasComponent<HandsComponent>(args.Entity) || _gameTiming.CurTime < component.LastInternalOpenAttempt + EntityStorageComponent.InternalOpenAttemptDelay)
return;
component.LastInternalOpenAttempt = _gameTiming.CurTime;
@@ -163,10 +110,7 @@ namespace Content.Server.Storage.EntitySystems
private void AddToggleOpenVerb(EntityUid uid, EntityStorageComponent component, GetVerbsEvent<InteractionVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
if (!_entityStorage.CanOpen(args.User, args.Target, silent: true, component))
if (!args.CanAccess || !args.CanInteract || !_entityStorage.CanOpen(args.User, args.Target, silent: true, component))
return;
InteractionVerb verb = new();
@@ -186,10 +130,7 @@ namespace Content.Server.Storage.EntitySystems
private void AddOpenUiVerb(EntityUid uid, ServerStorageComponent component, GetVerbsEvent<ActivationVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
if (TryComp<LockComponent>(uid, out var lockComponent) && lockComponent.Locked)
if (!args.CanAccess || !args.CanInteract || TryComp<LockComponent>(uid, out var lockComponent) && lockComponent.Locked)
return;
// Get the session for the user
@@ -222,10 +163,7 @@ namespace Content.Server.Storage.EntitySystems
return;
var entities = component.Storage?.ContainedEntities;
if (entities == null || entities.Count == 0)
return;
if (TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked)
if (entities == null || entities.Count == 0 || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked)
return;
// if the target is storage, add a verb to transfer storage.
@@ -249,13 +187,7 @@ namespace Content.Server.Storage.EntitySystems
/// <returns>true if inserted, false otherwise</returns>
private void OnInteractUsing(EntityUid uid, ServerStorageComponent storageComp, InteractUsingEvent args)
{
if (args.Handled)
return;
if (!storageComp.ClickInsert)
return;
if (TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked)
if (args.Handled || !storageComp.ClickInsert || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked)
return;
Logger.DebugS(storageComp.LoggerName, $"Storage (UID {uid}) attacked by user (UID {args.User}) with entity (UID {args.Used}).");
@@ -273,10 +205,7 @@ namespace Content.Server.Storage.EntitySystems
/// <returns></returns>
private void OnActivate(EntityUid uid, ServerStorageComponent storageComp, ActivateInWorldEvent args)
{
if (args.Handled || _combatMode.IsInCombatMode(args.User))
return;
if (TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked)
if (args.Handled || _combatMode.IsInCombatMode(args.User) || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked)
return;
OpenStorageUI(uid, args.User, storageComp);
@@ -300,12 +229,8 @@ namespace Content.Server.Storage.EntitySystems
/// <returns></returns>
private async void AfterInteract(EntityUid uid, ServerStorageComponent storageComp, AfterInteractEvent args)
{
if (!args.CanReach) return;
if (storageComp.CancelToken != null)
{
if (!args.CanReach)
return;
}
// Pick up all entities in a radius around the clicked location.
// The last half of the if is because carpets exist and this is terrible
@@ -328,18 +253,16 @@ namespace Content.Server.Storage.EntitySystems
//If there's only one then let's be generous
if (validStorables.Count > 1)
{
storageComp.CancelToken = new CancellationTokenSource();
var doAfterArgs = new DoAfterEventArgs(args.User, 0.2f * validStorables.Count, storageComp.CancelToken.Token, target: uid)
var storageData = new StorageData(validStorables);
var doAfterArgs = new DoAfterEventArgs(args.User, 0.2f * validStorables.Count, target: uid)
{
BreakOnStun = true,
BreakOnDamage = true,
BreakOnUserMove = true,
NeedHand = true,
TargetCancelledEvent = new AreaPickupCancelledEvent(),
TargetFinishedEvent = new AreaPickupCompleteEvent(args.User, validStorables),
NeedHand = true
};
_doAfterSystem.DoAfter(doAfterArgs);
_doAfterSystem.DoAfter(doAfterArgs, storageData);
}
return;
@@ -374,6 +297,54 @@ namespace Content.Server.Storage.EntitySystems
}
}
private void OnDoAfter(EntityUid uid, ServerStorageComponent component, DoAfterEvent<StorageData> args)
{
if (args.Handled || args.Cancelled)
return;
var successfullyInserted = new List<EntityUid>();
var successfullyInsertedPositions = new List<EntityCoordinates>();
var itemQuery = GetEntityQuery<ItemComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
xformQuery.TryGetComponent(uid, out var xform);
foreach (var entity in args.AdditionalData.ValidStorables)
{
// Check again, situation may have changed for some entities, but we'll still pick up any that are valid
if (_containerSystem.IsEntityInContainer(entity)
|| entity == args.Args.User
|| !itemQuery.HasComponent(entity))
continue;
if (xform == null ||
!xformQuery.TryGetComponent(entity, out var targetXform) ||
targetXform.MapID != xform.MapID)
{
continue;
}
var position = EntityCoordinates.FromMap(
xform.ParentUid.IsValid() ? xform.ParentUid : uid,
new MapCoordinates(_transform.GetWorldPosition(targetXform, xformQuery),
targetXform.MapID), EntityManager);
if (PlayerInsertEntityInWorld(uid, args.Args.User, entity, component))
{
successfullyInserted.Add(entity);
successfullyInsertedPositions.Add(position);
}
}
// If we picked up atleast one thing, play a sound and do a cool animation!
if (successfullyInserted.Count > 0)
{
_audio.PlayPvs(component.StorageInsertSound, uid);
RaiseNetworkEvent(new AnimateInsertingEntitiesEvent(uid, successfullyInserted, successfullyInsertedPositions));
}
args.Handled = true;
}
private void OnDestroy(EntityUid uid, ServerStorageComponent storageComp, DestructionEventArgs args)
{
var storedEntities = storageComp.StoredEntities?.ToList();
@@ -404,10 +375,7 @@ namespace Content.Server.Storage.EntitySystems
return;
}
if (!_actionBlockerSystem.CanInteract(player, args.InteractedItemUID))
return;
if (storageComp.Storage == null || !storageComp.Storage.Contains(args.InteractedItemUID))
if (!_actionBlockerSystem.CanInteract(player, args.InteractedItemUID) || storageComp.Storage == null || !storageComp.Storage.Contains(args.InteractedItemUID))
return;
// Does the player have hands?
@@ -419,7 +387,7 @@ namespace Content.Server.Storage.EntitySystems
{
if (_sharedHandsSystem.TryPickupAnyHand(player, args.InteractedItemUID, handsComp: hands)
&& storageComp.StorageRemoveSound != null)
SoundSystem.Play(storageComp.StorageRemoveSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid, AudioParams.Default);
_audio.Play(storageComp.StorageRemoveSound, Filter.Pvs(uid, entityManager: EntityManager), uid, true, AudioParams.Default);
return;
}
@@ -457,7 +425,7 @@ namespace Content.Server.Storage.EntitySystems
UpdateStorageVisualization(uid, storageComp);
if (storageComp.StorageCloseSound is not null)
SoundSystem.Play(storageComp.StorageCloseSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid, storageComp.StorageCloseSound.Params);
_audio.Play(storageComp.StorageCloseSound, Filter.Pvs(uid, entityManager: EntityManager), uid, true, storageComp.StorageCloseSound.Params);
}
}
@@ -473,10 +441,10 @@ namespace Content.Server.Storage.EntitySystems
return;
_appearance.SetData(uid, StorageVisuals.Open, storageComp.IsOpen, appearance);
_appearance.SetData(uid, SharedBagOpenVisuals.BagState, storageComp.IsOpen ? SharedBagState.Open : SharedBagState.Closed, appearance);
_appearance.SetData(uid, SharedBagOpenVisuals.BagState, storageComp.IsOpen ? SharedBagState.Open : SharedBagState.Closed);
if (HasComp<ItemCounterComponent>(uid))
_appearance.SetData(uid, StackVisuals.Hide, !storageComp.IsOpen, appearance);
_appearance.SetData(uid, StackVisuals.Hide, !storageComp.IsOpen);
}
private void RecalculateStorageUsed(ServerStorageComponent storageComp)
@@ -581,16 +549,11 @@ namespace Content.Server.Storage.EntitySystems
/// <returns>true if the entity was inserted, false otherwise</returns>
public bool Insert(EntityUid uid, EntityUid insertEnt, ServerStorageComponent? storageComp = null, bool playSound = true)
{
if (!Resolve(uid, ref storageComp))
return false;
if (!CanInsert(uid, insertEnt, out _, storageComp) || storageComp.Storage?.Insert(insertEnt) == false)
if (!Resolve(uid, ref storageComp) || !CanInsert(uid, insertEnt, out _, storageComp) || storageComp.Storage?.Insert(insertEnt) == false)
return false;
if (playSound && storageComp.StorageInsertSound is not null)
{
_audio.PlayPvs(storageComp.StorageInsertSound, uid);
}
RecalculateStorageUsed(storageComp);
UpdateStorageUI(uid, storageComp);
@@ -617,11 +580,7 @@ namespace Content.Server.Storage.EntitySystems
/// <returns>true if inserted, false otherwise</returns>
public bool PlayerInsertHeldEntity(EntityUid uid, EntityUid player, ServerStorageComponent? storageComp = null)
{
if (!Resolve(uid, ref storageComp))
return false;
if (!TryComp(player, out HandsComponent? hands) ||
hands.ActiveHandEntity == null)
if (!Resolve(uid, ref storageComp) || !TryComp(player, out HandsComponent? hands) || hands.ActiveHandEntity == null)
return false;
var toInsert = hands.ActiveHandEntity;
@@ -643,10 +602,7 @@ namespace Content.Server.Storage.EntitySystems
/// <returns>true if inserted, false otherwise</returns>
public bool PlayerInsertEntityInWorld(EntityUid uid, EntityUid player, EntityUid toInsert, ServerStorageComponent? storageComp = null)
{
if (!Resolve(uid, ref storageComp))
return false;
if (!_sharedInteractionSystem.InRangeUnobstructed(player, uid, popup: storageComp.ShowPopup))
if (!Resolve(uid, ref storageComp) || !_sharedInteractionSystem.InRangeUnobstructed(player, uid, popup: storageComp.ShowPopup))
return false;
if (!Insert(uid, toInsert, storageComp))
@@ -663,18 +619,17 @@ namespace Content.Server.Storage.EntitySystems
/// <param name="entity">The entity to open the UI for</param>
public void OpenStorageUI(EntityUid uid, EntityUid entity, ServerStorageComponent? storageComp = null)
{
if (!Resolve(uid, ref storageComp))
return;
if (!TryComp(entity, out ActorComponent? player))
if (!Resolve(uid, ref storageComp) || !TryComp(entity, out ActorComponent? player))
return;
if (storageComp.StorageOpenSound is not null)
SoundSystem.Play(storageComp.StorageOpenSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid, storageComp.StorageOpenSound.Params);
_audio.Play(storageComp.StorageOpenSound, Filter.Pvs(uid, entityManager: EntityManager), uid, true, storageComp.StorageOpenSound.Params);
Logger.DebugS(storageComp.LoggerName, $"Storage (UID {uid}) \"used\" by player session (UID {player.PlayerSession.AttachedEntity}).");
_uiSystem.GetUiOrNull(uid, StorageUiKey.Key)?.Open(player.PlayerSession);
var bui = _uiSystem.GetUiOrNull(uid, StorageUiKey.Key);
if (bui != null)
_uiSystem.OpenUi(bui, player.PlayerSession);
}
/// <summary>
@@ -683,10 +638,7 @@ namespace Content.Server.Storage.EntitySystems
/// <param name="session"></param>
public void CloseNestedInterfaces(EntityUid uid, IPlayerSession session, ServerStorageComponent? storageComp = null)
{
if (!Resolve(uid, ref storageComp))
return;
if (storageComp.StoredEntities == null)
if (!Resolve(uid, ref storageComp) || storageComp.StoredEntities == null)
return;
// for each containing thing
@@ -697,9 +649,7 @@ namespace Content.Server.Storage.EntitySystems
foreach (var entity in storageComp.StoredEntities)
{
if (TryComp(entity, out ServerStorageComponent? storedStorageComp))
{
DebugTools.Assert(storedStorageComp != storageComp, $"Storage component contains itself!? Entity: {uid}");
}
if (!TryComp(entity, out ServerUserInterfaceComponent? ui))
continue;
@@ -718,34 +668,22 @@ namespace Content.Server.Storage.EntitySystems
var state = new StorageBoundUserInterfaceState((List<EntityUid>) storageComp.Storage.ContainedEntities, storageComp.StorageUsed, storageComp.StorageCapacityMax);
_uiSystem.GetUiOrNull(uid, StorageUiKey.Key)?.SetState(state);
var bui = _uiSystem.GetUiOrNull(uid, StorageUiKey.Key);
if (bui != null)
_uiSystem.SetUiState(bui, state);
}
private void Popup(EntityUid uid, EntityUid player, string message, ServerStorageComponent storageComp)
{
if (!storageComp.ShowPopup) return;
if (!storageComp.ShowPopup)
return;
_popupSystem.PopupEntity(Loc.GetString(message), player, player);
}
/// <summary>
/// Raised on storage if it successfully completes area pickup.
/// </summary>
private sealed class AreaPickupCompleteEvent : EntityEventArgs
private record struct StorageData(List<EntityUid> validStorables)
{
public EntityUid User;
public List<EntityUid> ValidStorables;
public AreaPickupCompleteEvent(EntityUid user, List<EntityUid> validStorables)
{
User = user;
ValidStorables = validStorables;
}
}
private sealed class AreaPickupCancelledEvent : EntityEventArgs
{
public List<EntityUid> ValidStorables = validStorables;
}
}
}

View File

@@ -15,6 +15,7 @@ using Robust.Server.GameObjects;
using System.Threading;
using Content.Server.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.Ensnaring.Components;
using Content.Shared.Interaction;
using Content.Shared.Strip;
@@ -56,7 +57,7 @@ namespace Content.Server.Strip
if (!TryComp<EnsnaringComponent>(entity, out var ensnaring))
continue;
_ensnaring.TryFree(component.Owner, ensnaring, user);
_ensnaring.TryFree(uid, entity, ensnaring, user);
return;
}
}

View File

@@ -1,6 +1,6 @@
using System.Threading;
using Content.Server.Administration.Logs;
using Content.Server.DoAfter;
using Content.Shared.DoAfter;
using Content.Shared.Database;
using Content.Shared.Interaction.Events;
using Content.Shared.Teleportation.Components;
@@ -24,19 +24,17 @@ public sealed class HandTeleporterSystem : EntitySystem
{
SubscribeLocalEvent<HandTeleporterComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<HandTeleporterComponent, HandTeleporterSuccessEvent>(OnPortalSuccess);
SubscribeLocalEvent<HandTeleporterComponent, HandTeleporterCancelledEvent>(OnPortalCancelled);
SubscribeLocalEvent<HandTeleporterComponent, DoAfterEvent>(OnDoAfter);
}
private void OnPortalSuccess(EntityUid uid, HandTeleporterComponent component, HandTeleporterSuccessEvent args)
private void OnDoAfter(EntityUid uid, HandTeleporterComponent component, DoAfterEvent args)
{
component.CancelToken = null;
HandlePortalUpdating(uid, component, args.User);
}
if (args.Cancelled || args.Handled)
return;
private void OnPortalCancelled(EntityUid uid, HandTeleporterComponent component, HandTeleporterCancelledEvent args)
{
component.CancelToken = null;
HandlePortalUpdating(uid, component, args.Args.User);
args.Handled = true;
}
private void OnUseInHand(EntityUid uid, HandTeleporterComponent component, UseInHandEvent args)
@@ -47,12 +45,6 @@ public sealed class HandTeleporterSystem : EntitySystem
if (Deleted(component.SecondPortal))
component.SecondPortal = null;
if (component.CancelToken != null)
{
component.CancelToken.Cancel();
return;
}
if (component.FirstPortal != null && component.SecondPortal != null)
{
// handle removing portals immediately as opposed to a doafter
@@ -64,16 +56,12 @@ public sealed class HandTeleporterSystem : EntitySystem
if (xform.ParentUid != xform.GridUid)
return;
component.CancelToken = new CancellationTokenSource();
var doafterArgs = new DoAfterEventArgs(args.User, component.PortalCreationDelay,
component.CancelToken.Token, used: uid)
var doafterArgs = new DoAfterEventArgs(args.User, component.PortalCreationDelay, used: uid)
{
BreakOnDamage = true,
BreakOnStun = true,
BreakOnUserMove = true,
MovementThreshold = 0.5f,
UsedCancelledEvent = new HandTeleporterCancelledEvent(),
UsedFinishedEvent = new HandTeleporterSuccessEvent(args.User)
};
_doafter.DoAfter(doafterArgs);

Some files were not shown because too many files have changed in this diff Show More