Defibs will now also shock anyone still interacting with the target. (#35998)
* Defibs will now also shock anyone still interacting with the target. * Improvements to test readability * Apply fixes to other tests * Refactor the interacting entities query to use an event. * Include pullers as interacting with the entity they are pulling * Broadcast event * Use a constant * Convert new test to InteractionTest * Convert existing test * Add behaviour note * Revert "Convert existing test" This reverts commit b8a8f2f68e3733bdb6ec254faf955a42096d47d7. * Move new test into separate (InteractionTest) test file * Use ToServer * Use a constant for prototype id * Use ToServer * Add EntProtoId constructor * Add assertion failure messages * Manual cleanup of test entities * Remove obsolete flag * Add test summaries * Remove tuple constructor * Wrap entity deletion in WaitPost * Extend DoAfter interacting test with an extra mob
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Reflection;
|
||||
@@ -64,17 +66,16 @@ namespace Content.IntegrationTests.Tests.DoAfter
|
||||
var server = pair.Server;
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var entityManager = server.EntMan;
|
||||
var timing = server.ResolveDependency<IGameTiming>();
|
||||
var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem<SharedDoAfterSystem>();
|
||||
var doAfterSystem = entityManager.System<SharedDoAfterSystem>();
|
||||
var ev = new TestDoAfterEvent();
|
||||
|
||||
// That it finishes successfully
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var tickTime = 1.0f / timing.TickRate;
|
||||
var mob = entityManager.SpawnEntity("DoAfterDummy", MapCoordinates.Nullspace);
|
||||
var args = new DoAfterArgs(entityManager, mob, tickTime / 2, ev, null) { Broadcast = true };
|
||||
var args = new DoAfterArgs(entityManager, mob, timing.TickPeriod / 2, ev, null) { Broadcast = true };
|
||||
#pragma warning disable NUnit2045 // Interdependent assertions.
|
||||
Assert.That(doAfterSystem.TryStartDoAfter(args));
|
||||
Assert.That(ev.Cancelled, Is.False);
|
||||
@@ -92,23 +93,17 @@ namespace Content.IntegrationTests.Tests.DoAfter
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var entityManager = server.EntMan;
|
||||
var timing = server.ResolveDependency<IGameTiming>();
|
||||
var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem<SharedDoAfterSystem>();
|
||||
var doAfterSystem = entityManager.System<SharedDoAfterSystem>();
|
||||
var ev = new TestDoAfterEvent();
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var tickTime = 1.0f / timing.TickRate;
|
||||
|
||||
var mob = entityManager.SpawnEntity("DoAfterDummy", MapCoordinates.Nullspace);
|
||||
var args = new DoAfterArgs(entityManager, mob, tickTime * 2, ev, null) { Broadcast = true };
|
||||
var args = new DoAfterArgs(entityManager, mob, timing.TickPeriod * 2, ev, null) { Broadcast = true };
|
||||
|
||||
if (!doAfterSystem.TryStartDoAfter(args, out var id))
|
||||
{
|
||||
Assert.Fail();
|
||||
return;
|
||||
}
|
||||
Assert.That(doAfterSystem.TryStartDoAfter(args, out var id));
|
||||
|
||||
Assert.That(!ev.Cancelled);
|
||||
doAfterSystem.Cancel(id);
|
||||
@@ -121,5 +116,67 @@ namespace Content.IntegrationTests.Tests.DoAfter
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns two sets of mobs with a targeted DoAfter to check that the GetEntitiesInteractingWithTarget result
|
||||
/// includes the correct interacting entities.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestGetInteractingEntities()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
var entityManager = server.EntMan;
|
||||
var timing = server.ResolveDependency<IGameTiming>();
|
||||
var doAfterSystem = entityManager.System<SharedDoAfterSystem>();
|
||||
var interactionSystem = entityManager.System<SharedInteractionSystem>();
|
||||
var ev = new TestDoAfterEvent();
|
||||
|
||||
EntityUid mob = default;
|
||||
EntityUid target = default;
|
||||
|
||||
EntityUid mob2 = default;
|
||||
EntityUid mob3 = default;
|
||||
EntityUid target2 = default;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
// Spawn two targets to interact with
|
||||
target = entityManager.SpawnEntity("DoAfterDummy", MapCoordinates.Nullspace);
|
||||
target2 = entityManager.SpawnEntity("DoAfterDummy", MapCoordinates.Nullspace);
|
||||
|
||||
// Spawn a mob which is interacting with the first target
|
||||
mob = entityManager.SpawnEntity("DoAfterDummy", MapCoordinates.Nullspace);
|
||||
var args = new DoAfterArgs(entityManager, mob, timing.TickPeriod * 5, ev, null, target) { Broadcast = true };
|
||||
Assert.That(doAfterSystem.TryStartDoAfter(args));
|
||||
|
||||
// Spawn two more mobs which are interacting with the second target
|
||||
mob2 = entityManager.SpawnEntity("DoAfterDummy", MapCoordinates.Nullspace);
|
||||
var args2 = new DoAfterArgs(entityManager, mob2, timing.TickPeriod * 5, ev, null, target2) { Broadcast = true };
|
||||
Assert.That(doAfterSystem.TryStartDoAfter(args2));
|
||||
|
||||
mob3 = entityManager.SpawnEntity("DoAfterDummy", MapCoordinates.Nullspace);
|
||||
var args3 = new DoAfterArgs(entityManager, mob3, timing.TickPeriod * 5, ev, null, target2) { Broadcast = true };
|
||||
Assert.That(doAfterSystem.TryStartDoAfter(args3));
|
||||
});
|
||||
|
||||
var list = new HashSet<EntityUid>();
|
||||
interactionSystem.GetEntitiesInteractingWithTarget(target, list);
|
||||
Assert.That(list, Is.EquivalentTo([mob]), $"{mob} was not considered to be interacting with {target}");
|
||||
|
||||
interactionSystem.GetEntitiesInteractingWithTarget(target2, list);
|
||||
Assert.That(list, Is.EquivalentTo([mob2, mob3]), $"{mob2} and {mob3} were not considered to be interacting with {target2}");
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
entityManager.DeleteEntity(mob);
|
||||
entityManager.DeleteEntity(mob2);
|
||||
entityManager.DeleteEntity(mob3);
|
||||
entityManager.DeleteEntity(target);
|
||||
entityManager.DeleteEntity(target2);
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,9 @@ public abstract partial class InteractionTest
|
||||
public static implicit operator EntitySpecifier(string prototype)
|
||||
=> new(prototype, 1);
|
||||
|
||||
public static implicit operator EntitySpecifier(EntProtoId prototype)
|
||||
=> new(prototype.Id, 1);
|
||||
|
||||
public static implicit operator EntitySpecifier((string, int) tuple)
|
||||
=> new(tuple.Item1, tuple.Item2);
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Puller;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public sealed class InteractingEntitiesTest : InteractionTest
|
||||
{
|
||||
private static readonly EntProtoId MobHuman = "MobHuman";
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a Target mob, and a second mob which drags it,
|
||||
/// and checks that the dragger is considered to be interacting with the dragged mob.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task PullerIsConsideredInteractingTest()
|
||||
{
|
||||
await SpawnTarget(MobHuman);
|
||||
var puller = await SpawnEntity(MobHuman, ToServer(TargetCoords));
|
||||
|
||||
var pullSys = SEntMan.System<PullingSystem>();
|
||||
await Server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(pullSys.TryStartPull(puller, ToServer(Target.Value)),
|
||||
$"{puller} failed to start pulling {Target}");
|
||||
});
|
||||
|
||||
var list = new HashSet<EntityUid>();
|
||||
Server.System<SharedInteractionSystem>()
|
||||
.GetEntitiesInteractingWithTarget(ToServer(Target.Value), list);
|
||||
Assert.That(list, Is.EquivalentTo([puller]), $"{puller} was not considered to be interacting with {Target}");
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.PowerCell;
|
||||
using Content.Shared.Timing;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
@@ -44,6 +46,7 @@ public sealed class DefibrillatorSystem : EntitySystem
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
@@ -179,6 +182,18 @@ public sealed class DefibrillatorSystem : EntitySystem
|
||||
|
||||
_audio.PlayPvs(component.ZapSound, uid);
|
||||
_electrocution.TryDoElectrocution(target, null, component.ZapDamage, component.WritheDuration, true, ignoreInsulation: true);
|
||||
|
||||
var interacters = new HashSet<EntityUid>();
|
||||
_interactionSystem.GetEntitiesInteractingWithTarget(target, interacters);
|
||||
foreach (var other in interacters)
|
||||
{
|
||||
if (other == user)
|
||||
continue;
|
||||
|
||||
// Anyone else still operating on the target gets zapped too
|
||||
_electrocution.TryDoElectrocution(other, null, component.ZapDamage, component.WritheDuration, true);
|
||||
}
|
||||
|
||||
if (!TryComp<UseDelayComponent>(uid, out var useDelay))
|
||||
return;
|
||||
_useDelay.SetLength((uid, useDelay), component.ZapDelay, component.DelayId);
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -35,6 +36,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
|
||||
SubscribeLocalEvent<DoAfterComponent, EntityUnpausedEvent>(OnUnpaused);
|
||||
SubscribeLocalEvent<DoAfterComponent, ComponentGetState>(OnDoAfterGetState);
|
||||
SubscribeLocalEvent<DoAfterComponent, ComponentHandleState>(OnDoAfterHandleState);
|
||||
SubscribeLocalEvent<GetInteractingEntitiesEvent>(OnGetInteractingEntities);
|
||||
}
|
||||
|
||||
private void OnUnpaused(EntityUid uid, DoAfterComponent component, ref EntityUnpausedEvent args)
|
||||
@@ -131,6 +133,25 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
|
||||
EnsureComp<ActiveDoAfterComponent>(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds entities which have an active DoAfter matching the target.
|
||||
/// </summary>
|
||||
private void OnGetInteractingEntities(ref GetInteractingEntitiesEvent args)
|
||||
{
|
||||
var enumerator = EntityQueryEnumerator<ActiveDoAfterComponent, DoAfterComponent>();
|
||||
while (enumerator.MoveNext(out _, out var comp))
|
||||
{
|
||||
foreach (var doAfter in comp.DoAfters.Values)
|
||||
{
|
||||
if (doAfter.Cancelled || doAfter.Completed)
|
||||
continue;
|
||||
|
||||
if (doAfter.Args.Target == args.Target)
|
||||
args.InteractingEntities.Add(doAfter.Args.User);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Creation
|
||||
/// <summary>
|
||||
/// Tasks that are delayed until the specified time has passed
|
||||
|
||||
@@ -1465,6 +1465,18 @@ namespace Content.Shared.Interaction
|
||||
return ev.Handled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of entities which are currently considered to be interacting with the specified target entity.
|
||||
/// Note: the result set is cleared on call.
|
||||
/// </summary>
|
||||
public void GetEntitiesInteractingWithTarget(EntityUid target, HashSet<EntityUid> result)
|
||||
{
|
||||
result.Clear();
|
||||
|
||||
var ev = new GetInteractingEntitiesEvent(target, result);
|
||||
RaiseLocalEvent(target, ref ev, true);
|
||||
}
|
||||
|
||||
[Obsolete("Use ActionBlockerSystem")]
|
||||
public bool SupportsComplexInteractions(EntityUid user)
|
||||
{
|
||||
@@ -1542,4 +1554,14 @@ namespace Content.Shared.Interaction
|
||||
public bool Handled;
|
||||
public bool InRange = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised to allow systems to provide entities which are interacting with the target entity.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct GetInteractingEntitiesEvent(EntityUid Target, HashSet<EntityUid> InteractingEntities)
|
||||
{
|
||||
public readonly EntityUid Target = Target;
|
||||
public HashSet<EntityUid> InteractingEntities = InteractingEntities;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ public sealed class PullingSystem : EntitySystem
|
||||
SubscribeLocalEvent<PullableComponent, EntGotInsertedIntoContainerMessage>(OnPullableContainerInsert);
|
||||
SubscribeLocalEvent<PullableComponent, ModifyUncuffDurationEvent>(OnModifyUncuffDuration);
|
||||
SubscribeLocalEvent<PullableComponent, StopBeingPulledAlertEvent>(OnStopBeingPulledAlert);
|
||||
SubscribeLocalEvent<PullableComponent, GetInteractingEntitiesEvent>(OnGetInteractingEntities);
|
||||
|
||||
SubscribeLocalEvent<PullerComponent, UpdateMobStateEvent>(OnStateChanged, after: [typeof(MobThresholdSystem)]);
|
||||
SubscribeLocalEvent<PullerComponent, AfterAutoHandleStateEvent>(OnAfterState);
|
||||
@@ -161,6 +162,12 @@ public sealed class PullingSystem : EntitySystem
|
||||
StopPulling(ent, ent);
|
||||
}
|
||||
|
||||
private void OnGetInteractingEntities(Entity<PullableComponent> ent, ref GetInteractingEntitiesEvent args)
|
||||
{
|
||||
if (ent.Comp.Puller != null)
|
||||
args.InteractingEntities.Add(ent.Comp.Puller.Value);
|
||||
}
|
||||
|
||||
private void OnAfterState(Entity<PullerComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (ent.Comp.Pulling == null)
|
||||
|
||||
Reference in New Issue
Block a user