DoAfter Refactor (#13225)
Co-authored-by: DrSmugleaf <drsmugleaf@gmail.com>
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
|
||||
@@ -16,7 +16,5 @@ namespace Content.Server.Body.Components
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("delay")]
|
||||
public float Delay = 3;
|
||||
|
||||
public CancellationTokenSource? CancelToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,4 @@ public sealed class PartExchangerComponent : Component
|
||||
public SoundSpecifier ExchangeSound = new SoundPathSpecifier("/Audio/Items/rped.ogg");
|
||||
|
||||
public IPlayingAudioStream? AudioStream;
|
||||
|
||||
public CancellationTokenSource? Token;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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!;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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 {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ namespace Content.Server.Medical.Components
|
||||
{
|
||||
public bool IsActive = false;
|
||||
|
||||
public CancellationTokenSource? CancelToken;
|
||||
|
||||
[DataField("delay")]
|
||||
public float Delay = 2.5f;
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -174,8 +174,6 @@ namespace Content.Server.Nuke
|
||||
/// </summary>
|
||||
public bool PlayedAlertSound = false;
|
||||
|
||||
public CancellationToken? DisarmCancelToken = null;
|
||||
|
||||
public IPlayingAudioStream? AlertAudioStream = default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user