+using System.Collections.Generic;
using Content.Shared.DoAfter;
+using Content.Shared.Interaction;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Reflection;
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);
{
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);
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();
+ }
}
}
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);
--- /dev/null
+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}");
+ }
+}
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;
[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()
_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);
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;
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)
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
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)
{
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;
+ }
}
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);
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)