Interaction clean up (#4091)
* Rename and clean up interaction events * Fix hand equip events * Refactor duplicate client input validation * Rename Use handler * Move unneeded InRangeUnobstructed methods to extensions only * Clean up UseInteractions * Clean up ActivateItemInWorld * Replace explicit range check with InRangeUnobstructed Remove TransformComponent check, since transform is guaranteed now. * Revert transform check removal * More cleanup * Reorder interaction checks * Rename attack eventargs to interact * Test V1 * Add interaction test * Fix interaction test * Fix container interaction test * Rename interaction methods * Rename player to user and attacked to target * Clean up InteractAfter * Clean up InRangeUnobstructed usages * Rename attack to interact and weapon to used * Changed can't reach message to only play when holding something Cleaned up bracket formatting * Fix Airtight validation check * Remove extra words in comments * Fix FaceClick rotation * Move duplicate map check and face to method * Fix test
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Weapon.Melee;
|
||||
using Content.Server.GameObjects.EntitySystems.Click;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using NUnit.Framework;
|
||||
@@ -17,20 +18,300 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
|
||||
[TestOf(typeof(InteractionSystem))]
|
||||
public class InteractionSystemTests : ContentIntegrationTest
|
||||
{
|
||||
[Reflect(false)]
|
||||
private class TestAttackEntitySystem : EntitySystem
|
||||
{
|
||||
public EntityEventHandler<ClickAttackEvent> ClickAttackEvent;
|
||||
public EntityEventHandler<InteractUsingEvent> InteractUsingEvent;
|
||||
public EntityEventHandler<AttackHandEvent> InteractHandEvent;
|
||||
const string PROTOTYPES = @"
|
||||
- type: entity
|
||||
id: DummyDebugWall
|
||||
components:
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
fixtures:
|
||||
- shape:
|
||||
!type:PhysShapeAabb
|
||||
bounds: ""-0.25,-0.25,0.25,0.25""
|
||||
layer:
|
||||
- MobMask
|
||||
mask:
|
||||
- MobMask
|
||||
";
|
||||
|
||||
public override void Initialize()
|
||||
[Test]
|
||||
public async Task InteractionTest()
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ClickAttackEvent>((e) => ClickAttackEvent?.Invoke(e));
|
||||
SubscribeLocalEvent<InteractUsingEvent>((e) => InteractUsingEvent?.Invoke(e));
|
||||
SubscribeLocalEvent<AttackHandEvent>((e) => InteractHandEvent?.Invoke(e));
|
||||
}
|
||||
ContentBeforeIoC = () =>
|
||||
{
|
||||
IoCManager.Resolve<IEntitySystemManager>().LoadExtraSystemType<TestInteractionSystem>();
|
||||
}
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
|
||||
var mapId = MapId.Nullspace;
|
||||
var coords = MapCoordinates.Nullspace;
|
||||
server.Assert(() =>
|
||||
{
|
||||
mapId = mapManager.CreateMap();
|
||||
coords = new MapCoordinates(Vector2.Zero, mapId);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
IEntity user = null;
|
||||
IEntity target = null;
|
||||
IEntity item = null;
|
||||
|
||||
server.Assert(() =>
|
||||
{
|
||||
user = entityManager.SpawnEntity(null, coords);
|
||||
user.EnsureComponent<HandsComponent>().AddHand("hand");
|
||||
target = entityManager.SpawnEntity(null, coords);
|
||||
item = entityManager.SpawnEntity(null, coords);
|
||||
item.EnsureComponent<ItemComponent>();
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
var entitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||
Assert.That(entitySystemManager.TryGetEntitySystem<InteractionSystem>(out var interactionSystem));
|
||||
Assert.That(entitySystemManager.TryGetEntitySystem<TestInteractionSystem>(out var testInteractionSystem));
|
||||
|
||||
var attack = false;
|
||||
var interactUsing = false;
|
||||
var interactHand = false;
|
||||
server.Assert(() =>
|
||||
{
|
||||
testInteractionSystem.AttackEvent = (_, _, ev) => { Assert.That(ev.Target, Is.EqualTo(target.Uid)); attack = true; };
|
||||
testInteractionSystem.InteractUsingEvent = (ev) => { Assert.That(ev.Target, Is.EqualTo(target)); interactUsing = true; };
|
||||
testInteractionSystem.InteractHandEvent = (ev) => { Assert.That(ev.Target, Is.EqualTo(target)); interactHand = true; };
|
||||
|
||||
interactionSystem.DoAttack(user, target.Transform.Coordinates, false, target.Uid);
|
||||
interactionSystem.UserInteraction(user, target.Transform.Coordinates, target.Uid);
|
||||
Assert.That(attack);
|
||||
Assert.That(interactUsing, Is.False);
|
||||
Assert.That(interactHand);
|
||||
|
||||
Assert.That(user.TryGetComponent<HandsComponent>(out var hands));
|
||||
Assert.That(hands.PutInHand(item.GetComponent<ItemComponent>()));
|
||||
|
||||
interactionSystem.UserInteraction(user, target.Transform.Coordinates, target.Uid);
|
||||
Assert.That(interactUsing);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task InteractionObstructionTest()
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
ContentBeforeIoC = () =>
|
||||
{
|
||||
IoCManager.Resolve<IEntitySystemManager>().LoadExtraSystemType<TestInteractionSystem>();
|
||||
},
|
||||
ExtraPrototypes = PROTOTYPES
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
|
||||
var mapId = MapId.Nullspace;
|
||||
var coords = MapCoordinates.Nullspace;
|
||||
server.Assert(() =>
|
||||
{
|
||||
mapId = mapManager.CreateMap();
|
||||
coords = new MapCoordinates(Vector2.Zero, mapId);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
IEntity user = null;
|
||||
IEntity target = null;
|
||||
IEntity item = null;
|
||||
IEntity wall = null;
|
||||
|
||||
server.Assert(() =>
|
||||
{
|
||||
user = entityManager.SpawnEntity(null, coords);
|
||||
user.EnsureComponent<HandsComponent>().AddHand("hand");
|
||||
target = entityManager.SpawnEntity(null, new MapCoordinates((1.9f, 0), mapId));
|
||||
item = entityManager.SpawnEntity(null, coords);
|
||||
item.EnsureComponent<ItemComponent>();
|
||||
wall = entityManager.SpawnEntity("DummyDebugWall", new MapCoordinates((1, 0), user.Transform.MapID));
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
var entitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||
Assert.That(entitySystemManager.TryGetEntitySystem<InteractionSystem>(out var interactionSystem));
|
||||
Assert.That(entitySystemManager.TryGetEntitySystem<TestInteractionSystem>(out var testInteractionSystem));
|
||||
|
||||
var attack = false;
|
||||
var interactUsing = false;
|
||||
var interactHand = false;
|
||||
server.Assert(() =>
|
||||
{
|
||||
testInteractionSystem.AttackEvent = (_, _, ev) => { Assert.That(ev.Target, Is.EqualTo(target.Uid)); attack = true; };
|
||||
testInteractionSystem.InteractUsingEvent = (ev) => { Assert.That(ev.Target, Is.EqualTo(target)); interactUsing = true; };
|
||||
testInteractionSystem.InteractHandEvent = (ev) => { Assert.That(ev.Target, Is.EqualTo(target)); interactHand = true; };
|
||||
|
||||
interactionSystem.DoAttack(user, target.Transform.Coordinates, false, target.Uid);
|
||||
interactionSystem.UserInteraction(user, target.Transform.Coordinates, target.Uid);
|
||||
Assert.That(attack, Is.False);
|
||||
Assert.That(interactUsing, Is.False);
|
||||
Assert.That(interactHand, Is.False);
|
||||
|
||||
Assert.That(user.TryGetComponent<HandsComponent>(out var hands));
|
||||
Assert.That(hands.PutInHand(item.GetComponent<ItemComponent>()));
|
||||
|
||||
interactionSystem.UserInteraction(user, target.Transform.Coordinates, target.Uid);
|
||||
Assert.That(interactUsing, Is.False);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task InteractionInRangeTest()
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
ContentBeforeIoC = () =>
|
||||
{
|
||||
IoCManager.Resolve<IEntitySystemManager>().LoadExtraSystemType<TestInteractionSystem>();
|
||||
}
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
|
||||
var mapId = MapId.Nullspace;
|
||||
var coords = MapCoordinates.Nullspace;
|
||||
server.Assert(() =>
|
||||
{
|
||||
mapId = mapManager.CreateMap();
|
||||
coords = new MapCoordinates(Vector2.Zero, mapId);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
IEntity user = null;
|
||||
IEntity target = null;
|
||||
IEntity item = null;
|
||||
|
||||
server.Assert(() =>
|
||||
{
|
||||
user = entityManager.SpawnEntity(null, coords);
|
||||
user.EnsureComponent<HandsComponent>().AddHand("hand");
|
||||
target = entityManager.SpawnEntity(null, new MapCoordinates((InteractionSystem.InteractionRange - 0.1f, 0), mapId));
|
||||
item = entityManager.SpawnEntity(null, coords);
|
||||
item.EnsureComponent<ItemComponent>();
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
var entitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||
Assert.That(entitySystemManager.TryGetEntitySystem<InteractionSystem>(out var interactionSystem));
|
||||
Assert.That(entitySystemManager.TryGetEntitySystem<TestInteractionSystem>(out var testInteractionSystem));
|
||||
|
||||
var attack = false;
|
||||
var interactUsing = false;
|
||||
var interactHand = false;
|
||||
server.Assert(() =>
|
||||
{
|
||||
testInteractionSystem.AttackEvent = (_, _, ev) => { Assert.That(ev.Target, Is.EqualTo(target.Uid)); attack = true; };
|
||||
testInteractionSystem.InteractUsingEvent = (ev) => { Assert.That(ev.Target, Is.EqualTo(target)); interactUsing = true; };
|
||||
testInteractionSystem.InteractHandEvent = (ev) => { Assert.That(ev.Target, Is.EqualTo(target)); interactHand = true; };
|
||||
|
||||
interactionSystem.DoAttack(user, target.Transform.Coordinates, false, target.Uid);
|
||||
interactionSystem.UserInteraction(user, target.Transform.Coordinates, target.Uid);
|
||||
Assert.That(attack);
|
||||
Assert.That(interactUsing, Is.False);
|
||||
Assert.That(interactHand);
|
||||
|
||||
Assert.That(user.TryGetComponent<HandsComponent>(out var hands));
|
||||
Assert.That(hands.PutInHand(item.GetComponent<ItemComponent>()));
|
||||
|
||||
interactionSystem.UserInteraction(user, target.Transform.Coordinates, target.Uid);
|
||||
Assert.That(interactUsing);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task InteractionOutOfRangeTest()
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
ContentBeforeIoC = () =>
|
||||
{
|
||||
IoCManager.Resolve<IEntitySystemManager>().LoadExtraSystemType<TestInteractionSystem>();
|
||||
}
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
|
||||
var mapId = MapId.Nullspace;
|
||||
var coords = MapCoordinates.Nullspace;
|
||||
server.Assert(() =>
|
||||
{
|
||||
mapId = mapManager.CreateMap();
|
||||
coords = new MapCoordinates(Vector2.Zero, mapId);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
IEntity user = null;
|
||||
IEntity target = null;
|
||||
IEntity item = null;
|
||||
|
||||
server.Assert(() =>
|
||||
{
|
||||
user = entityManager.SpawnEntity(null, coords);
|
||||
user.EnsureComponent<HandsComponent>().AddHand("hand");
|
||||
target = entityManager.SpawnEntity(null, new MapCoordinates((InteractionSystem.InteractionRange, 0), mapId));
|
||||
item = entityManager.SpawnEntity(null, coords);
|
||||
item.EnsureComponent<ItemComponent>();
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
var entitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||
Assert.That(entitySystemManager.TryGetEntitySystem<InteractionSystem>(out var interactionSystem));
|
||||
Assert.That(entitySystemManager.TryGetEntitySystem<TestInteractionSystem>(out var testInteractionSystem));
|
||||
|
||||
var attack = false;
|
||||
var interactUsing = false;
|
||||
var interactHand = false;
|
||||
server.Assert(() =>
|
||||
{
|
||||
testInteractionSystem.AttackEvent = (_, _, ev) => { Assert.That(ev.Target, Is.EqualTo(target.Uid)); attack = true; };
|
||||
testInteractionSystem.InteractUsingEvent = (ev) => { Assert.That(ev.Target, Is.EqualTo(target)); interactUsing = true; };
|
||||
testInteractionSystem.InteractHandEvent = (ev) => { Assert.That(ev.Target, Is.EqualTo(target)); interactHand = true; };
|
||||
|
||||
interactionSystem.DoAttack(user, target.Transform.Coordinates, false, target.Uid);
|
||||
interactionSystem.UserInteraction(user, target.Transform.Coordinates, target.Uid);
|
||||
Assert.That(attack, Is.False);
|
||||
Assert.That(interactUsing, Is.False);
|
||||
Assert.That(interactHand, Is.False);
|
||||
|
||||
Assert.That(user.TryGetComponent<HandsComponent>(out var hands));
|
||||
Assert.That(hands.PutInHand(item.GetComponent<ItemComponent>()));
|
||||
|
||||
interactionSystem.UserInteraction(user, target.Transform.Coordinates, target.Uid);
|
||||
Assert.That(interactUsing, Is.False);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -40,8 +321,9 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
|
||||
{
|
||||
ContentBeforeIoC = () =>
|
||||
{
|
||||
IoCManager.Resolve<IEntitySystemManager>().LoadExtraSystemType<TestAttackEntitySystem>();
|
||||
}
|
||||
IoCManager.Resolve<IEntitySystemManager>().LoadExtraSystemType<TestInteractionSystem>();
|
||||
},
|
||||
FailureLogLevel = Robust.Shared.Log.LogLevel.Error
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
@@ -49,23 +331,38 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
|
||||
IEntity origin = null;
|
||||
IEntity other = null;
|
||||
var mapId = MapId.Nullspace;
|
||||
var coords = MapCoordinates.Nullspace;
|
||||
server.Assert(() =>
|
||||
{
|
||||
mapId = mapManager.CreateMap();
|
||||
coords = new MapCoordinates(Vector2.Zero, mapId);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
IEntity user = null;
|
||||
IEntity target = null;
|
||||
IEntity item = null;
|
||||
IEntity containerEntity = null;
|
||||
IContainer container = null;
|
||||
|
||||
server.Assert(() =>
|
||||
{
|
||||
var mapId = mapManager.CreateMap();
|
||||
var coordinates = new MapCoordinates(Vector2.Zero, mapId);
|
||||
|
||||
origin = entityManager.SpawnEntity(null, coordinates);
|
||||
origin.EnsureComponent<HandsComponent>().AddHand("hand");
|
||||
other = entityManager.SpawnEntity(null, coordinates);
|
||||
containerEntity = entityManager.SpawnEntity(null, coordinates);
|
||||
user = entityManager.SpawnEntity(null, coords);
|
||||
user.EnsureComponent<HandsComponent>().AddHand("hand");
|
||||
target = entityManager.SpawnEntity(null, coords);
|
||||
item = entityManager.SpawnEntity(null, coords);
|
||||
item.EnsureComponent<ItemComponent>();
|
||||
containerEntity = entityManager.SpawnEntity(null, coords);
|
||||
container = ContainerHelpers.EnsureContainer<Container>(containerEntity, "InteractionTestContainer");
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
var entitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||
Assert.That(entitySystemManager.TryGetEntitySystem<InteractionSystem>(out var interactionSystem));
|
||||
Assert.That(entitySystemManager.TryGetEntitySystem<TestInteractionSystem>(out var testInteractionSystem));
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var attack = false;
|
||||
@@ -73,53 +370,53 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
|
||||
var interactHand = false;
|
||||
server.Assert(() =>
|
||||
{
|
||||
Assert.That(container.Insert(origin));
|
||||
Assert.That(origin.Transform.Parent!.Owner, Is.EqualTo(containerEntity));
|
||||
Assert.That(container.Insert(user));
|
||||
Assert.That(user.Transform.Parent.Owner, Is.EqualTo(containerEntity));
|
||||
|
||||
var entitySystemManager = IoCManager.Resolve<IEntitySystemManager>();
|
||||
Assert.That(entitySystemManager.TryGetEntitySystem<InteractionSystem>(out var interactionSystem));
|
||||
testInteractionSystem.AttackEvent = (_, _, ev) => { Assert.That(ev.Target, Is.EqualTo(containerEntity.Uid)); attack = true; };
|
||||
testInteractionSystem.InteractUsingEvent = (ev) => { Assert.That(ev.Target, Is.EqualTo(containerEntity)); interactUsing = true; };
|
||||
testInteractionSystem.InteractHandEvent = (ev) => { Assert.That(ev.Target, Is.EqualTo(containerEntity)); interactHand = true; };
|
||||
|
||||
Assert.That(entitySystemManager.TryGetEntitySystem<TestAttackEntitySystem>(out var testAttackEntitySystem));
|
||||
testAttackEntitySystem.ClickAttackEvent = (ev) =>
|
||||
{
|
||||
Assert.That(ev.Target, Is.EqualTo(containerEntity.Uid));
|
||||
attack = true;
|
||||
};
|
||||
testAttackEntitySystem.InteractUsingEvent = (ev) =>
|
||||
{
|
||||
Assert.That(ev.Target, Is.EqualTo(containerEntity));
|
||||
interactUsing = true;
|
||||
};
|
||||
testAttackEntitySystem.InteractHandEvent = (ev) =>
|
||||
{
|
||||
Assert.That(ev.Target, Is.EqualTo(containerEntity));
|
||||
interactHand = true;
|
||||
};
|
||||
|
||||
interactionSystem.DoAttack(origin, other.Transform.Coordinates, false, other.Uid);
|
||||
interactionSystem.UserInteraction(origin, other.Transform.Coordinates, other.Uid);
|
||||
interactionSystem.DoAttack(user, target.Transform.Coordinates, false, target.Uid);
|
||||
interactionSystem.UserInteraction(user, target.Transform.Coordinates, target.Uid);
|
||||
Assert.That(attack, Is.False);
|
||||
Assert.That(interactUsing, Is.False);
|
||||
Assert.That(interactHand, Is.False);
|
||||
|
||||
interactionSystem.DoAttack(origin, containerEntity.Transform.Coordinates, false, containerEntity.Uid);
|
||||
interactionSystem.UserInteraction(origin, containerEntity.Transform.Coordinates, containerEntity.Uid);
|
||||
interactionSystem.DoAttack(user, containerEntity.Transform.Coordinates, false, containerEntity.Uid);
|
||||
interactionSystem.UserInteraction(user, containerEntity.Transform.Coordinates, containerEntity.Uid);
|
||||
Assert.That(attack);
|
||||
Assert.That(interactUsing, Is.False);
|
||||
Assert.That(interactHand);
|
||||
|
||||
var itemEntity = entityManager.SpawnEntity(null, origin.Transform.Coordinates);
|
||||
var item = itemEntity.EnsureComponent<ItemComponent>();
|
||||
Assert.That(user.TryGetComponent<HandsComponent>(out var hands));
|
||||
Assert.That(hands.PutInHand(item.GetComponent<ItemComponent>()));
|
||||
|
||||
Assert.That(origin.TryGetComponent<HandsComponent>(out var hands));
|
||||
hands.PutInHand(item);
|
||||
|
||||
interactionSystem.UserInteraction(origin, other.Transform.Coordinates, other.Uid);
|
||||
interactionSystem.UserInteraction(user, target.Transform.Coordinates, target.Uid);
|
||||
Assert.That(interactUsing, Is.False);
|
||||
|
||||
interactionSystem.UserInteraction(origin, containerEntity.Transform.Coordinates, containerEntity.Uid);
|
||||
interactionSystem.UserInteraction(user, containerEntity.Transform.Coordinates, containerEntity.Uid);
|
||||
Assert.That(interactUsing, Is.True);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private class TestInteractionSystem : EntitySystem
|
||||
{
|
||||
public ComponentEventHandler<HandsComponent, ClickAttackEvent> AttackEvent;
|
||||
public EntityEventHandler<InteractUsingEvent> InteractUsingEvent;
|
||||
public EntityEventHandler<InteractHandEvent> InteractHandEvent;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<HandsComponent, ClickAttackEvent>((u, c, e) => AttackEvent?.Invoke(u, c, e));
|
||||
SubscribeLocalEvent<InteractUsingEvent>((e) => InteractUsingEvent?.Invoke(e));
|
||||
SubscribeLocalEvent<InteractHandEvent>((e) => InteractHandEvent?.Invoke(e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace Content.Server.AI.Operators.Combat.Melee
|
||||
|
||||
var interactionSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<InteractionSystem>();
|
||||
|
||||
interactionSystem.UseItemInHand(_owner, _target.Transform.Coordinates, _target.Uid);
|
||||
interactionSystem.AiUseInteraction(_owner, _target.Transform.Coordinates, _target.Uid);
|
||||
_elapsedTime += frameTime;
|
||||
return Outcome.Continuing;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Content.Server.AI.Operators.Combat.Melee
|
||||
}
|
||||
|
||||
var interactionSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<InteractionSystem>();
|
||||
interactionSystem.UseItemInHand(_owner, _target.Transform.Coordinates, _target.Uid);
|
||||
interactionSystem.AiUseInteraction(_owner, _target.Transform.Coordinates, _target.Uid);
|
||||
_elapsedTime += frameTime;
|
||||
return Outcome.Continuing;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Content.Server.AI.Operators.Inventory
|
||||
|
||||
// Click on da thing
|
||||
var interactionSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<InteractionSystem>();
|
||||
interactionSystem.UseItemInHand(_owner, _useTarget.Transform.Coordinates, _useTarget.Uid);
|
||||
interactionSystem.AiUseInteraction(_owner, _useTarget.Transform.Coordinates, _useTarget.Uid);
|
||||
|
||||
return Outcome.Success;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Content.Server.AI.Operators.Inventory
|
||||
}
|
||||
|
||||
var interactionSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<InteractionSystem>();
|
||||
interactionSystem.Interaction(_owner, _target);
|
||||
interactionSystem.InteractHand(_owner, _target);
|
||||
return Outcome.Success;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Content.Server.Actions
|
||||
var player = actor.PlayerSession;
|
||||
var coordinates = args.Target.Transform.Coordinates;
|
||||
var target = args.Target.Uid;
|
||||
EntitySystem.Get<InteractionSystem>().HandleClientUseItemInHand(player, coordinates, target);
|
||||
EntitySystem.Get<InteractionSystem>().HandleUseInteraction(player, coordinates, target);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -161,6 +161,8 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
if (Owner.Transform.Anchored)
|
||||
{
|
||||
if (!Owner.Transform.GridID.IsValid())
|
||||
return;
|
||||
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
|
||||
UpdatePosition(Owner.Transform.GridID, grid.TileIndicesFor(Owner.Transform.Coordinates));
|
||||
}
|
||||
|
||||
@@ -664,13 +664,13 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
var interactionSystem = _entitySystemManager.GetEntitySystem<InteractionSystem>();
|
||||
if (used != null)
|
||||
{
|
||||
await interactionSystem.Interaction(Owner, used, hand.Entity,
|
||||
await interactionSystem.InteractUsing(Owner, used, hand.Entity,
|
||||
EntityCoordinates.Invalid);
|
||||
}
|
||||
else
|
||||
{
|
||||
var entity = hand.Entity;
|
||||
interactionSystem.Interaction(Owner, entity);
|
||||
interactionSystem.InteractHand(Owner, entity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -539,7 +539,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
{
|
||||
if (activeHand != null)
|
||||
{
|
||||
await interactionSystem.Interaction(Owner, activeHand.Owner, itemContainedInSlot.Owner,
|
||||
await interactionSystem.InteractUsing(Owner, activeHand.Owner, itemContainedInSlot.Owner,
|
||||
new EntityCoordinates());
|
||||
}
|
||||
else if (Unequip(msg.Inventoryslot))
|
||||
|
||||
@@ -31,10 +31,10 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
SubscribeLocalEvent<BuckleComponent, EntRemovedFromContainerMessage>(ContainerModifiedBuckle);
|
||||
SubscribeLocalEvent<StrapComponent, EntRemovedFromContainerMessage>(ContainerModifiedStrap);
|
||||
|
||||
SubscribeLocalEvent<BuckleComponent, AttackHandEvent>(HandleAttackHand);
|
||||
SubscribeLocalEvent<BuckleComponent, InteractHandEvent>(HandleAttackHand);
|
||||
}
|
||||
|
||||
private void HandleAttackHand(EntityUid uid, BuckleComponent component, AttackHandEvent args)
|
||||
private void HandleAttackHand(EntityUid uid, BuckleComponent component, InteractHandEvent args)
|
||||
{
|
||||
args.Handled = component.TryUnbuckle(args.User);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
@@ -29,6 +30,8 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Localization;
|
||||
using Content.Shared.Interfaces;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
{
|
||||
@@ -43,16 +46,17 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeNetworkEvent<DragDropRequestEvent>(HandleDragDropMessage);
|
||||
SubscribeNetworkEvent<DragDropRequestEvent>(HandleDragDropRequestEvent);
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(EngineKeyFunctions.Use,
|
||||
new PointerInputCmdHandler(HandleClientUseItemInHand))
|
||||
new PointerInputCmdHandler(HandleUseInteraction))
|
||||
.Bind(ContentKeyFunctions.WideAttack,
|
||||
new PointerInputCmdHandler(HandleWideAttack))
|
||||
.Bind(ContentKeyFunctions.ActivateItemInWorld,
|
||||
new PointerInputCmdHandler(HandleActivateItemInWorld))
|
||||
.Bind(ContentKeyFunctions.TryPullObject, new PointerInputCmdHandler(HandleTryPullObject))
|
||||
.Bind(ContentKeyFunctions.TryPullObject,
|
||||
new PointerInputCmdHandler(HandleTryPullObject))
|
||||
.Register<InteractionSystem>();
|
||||
}
|
||||
|
||||
@@ -62,19 +66,57 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private void HandleDragDropMessage(DragDropRequestEvent msg, EntitySessionEventArgs args)
|
||||
#region Client Input Validation
|
||||
private bool ValidateClientInput(ICommonSession? session, EntityCoordinates coords, EntityUid uid, [NotNullWhen(true)] out IEntity? userEntity)
|
||||
{
|
||||
var performer = args.SenderSession.AttachedEntity;
|
||||
userEntity = null;
|
||||
|
||||
if (performer == null) return;
|
||||
if (!EntityManager.TryGetEntity(msg.Dropped, out var dropped)) return;
|
||||
if (!EntityManager.TryGetEntity(msg.Target, out var target)) return;
|
||||
if (!coords.IsValid(_entityManager))
|
||||
{
|
||||
Logger.InfoS("system.interaction", $"Invalid Coordinates: client={session}, coords={coords}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var interactionArgs = new DragDropEvent(performer, msg.DropLocation, dropped, target);
|
||||
if (uid.IsClientSide())
|
||||
{
|
||||
Logger.WarningS("system.interaction",
|
||||
$"Client sent interaction with client-side entity. Session={session}, Uid={uid}");
|
||||
return false;
|
||||
}
|
||||
|
||||
userEntity = ((IPlayerSession?) session)?.AttachedEntity;
|
||||
|
||||
if (userEntity == null || !userEntity.IsValid())
|
||||
{
|
||||
Logger.WarningS("system.interaction",
|
||||
$"Client sent interaction with no attached entity. Session={session}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Drag drop
|
||||
private void HandleDragDropRequestEvent(DragDropRequestEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (!ValidateClientInput(args.SenderSession, msg.DropLocation, msg.Target, out var userEntity))
|
||||
{
|
||||
Logger.InfoS("system.interaction", $"DragDropRequestEvent input validation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetEntity(msg.Dropped, out var dropped))
|
||||
return;
|
||||
if (!EntityManager.TryGetEntity(msg.Target, out var target))
|
||||
return;
|
||||
|
||||
var interactionArgs = new DragDropEvent(userEntity, msg.DropLocation, dropped, target);
|
||||
|
||||
// must be in range of both the target and the object they are drag / dropping
|
||||
// Client also does this check but ya know we gotta validate it.
|
||||
if (!interactionArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return;
|
||||
if (!interactionArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
|
||||
return;
|
||||
|
||||
// trigger dragdrops on the dropped entity
|
||||
RaiseLocalEvent(dropped.Uid, interactionArgs);
|
||||
@@ -98,25 +140,21 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ActivateItemInWorld
|
||||
private bool HandleActivateItemInWorld(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
if (!ValidateClientInput(session, coords, uid, out var user))
|
||||
{
|
||||
Logger.InfoS("system.interaction", $"ActivateItemInWorld input validation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetEntity(uid, out var used))
|
||||
return false;
|
||||
|
||||
var playerEnt = ((IPlayerSession?) session)?.AttachedEntity;
|
||||
|
||||
if (playerEnt == null || !playerEnt.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!playerEnt.Transform.Coordinates.InRange(EntityManager, used.Transform.Coordinates, InteractionRange))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
InteractionActivate(playerEnt, used);
|
||||
InteractionActivate(user, used);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -126,61 +164,45 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
/// </summary>
|
||||
public void TryInteractionActivate(IEntity? user, IEntity? used)
|
||||
{
|
||||
if (user != null && used != null && ActionBlockerSystem.CanUse(user))
|
||||
{
|
||||
InteractionActivate(user, used);
|
||||
}
|
||||
if (user == null || used == null)
|
||||
return;
|
||||
|
||||
InteractionActivate(user, used);
|
||||
}
|
||||
|
||||
private void InteractionActivate(IEntity user, IEntity used)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(user) || ! ActionBlockerSystem.CanUse(user))
|
||||
return;
|
||||
|
||||
// all activates should only fire when in range / unbostructed
|
||||
if (!InRangeUnobstructed(user, used, ignoreInsideBlocker: true, popup: true))
|
||||
return;
|
||||
|
||||
var activateMsg = new ActivateInWorldEvent(user, used);
|
||||
RaiseLocalEvent(used.Uid, activateMsg);
|
||||
if (activateMsg.Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!used.TryGetComponent(out IActivate? activateComp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// all activates should only fire when in range / unbostructed
|
||||
var activateEventArgs = new ActivateEventArgs(user, used);
|
||||
if (activateEventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
|
||||
{
|
||||
activateComp.Activate(activateEventArgs);
|
||||
}
|
||||
activateComp.Activate(activateEventArgs);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private bool HandleWideAttack(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
// client sanitization
|
||||
if (!coords.IsValid(_entityManager))
|
||||
{
|
||||
Logger.InfoS("system.interaction", $"Invalid Coordinates: client={session}, coords={coords}");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uid.IsClientSide())
|
||||
{
|
||||
Logger.WarningS("system.interaction",
|
||||
$"Client sent attack with client-side entity. Session={session}, Uid={uid}");
|
||||
return true;
|
||||
}
|
||||
|
||||
var userEntity = ((IPlayerSession?) session)?.AttachedEntity;
|
||||
|
||||
if (userEntity == null || !userEntity.IsValid())
|
||||
if (!ValidateClientInput(session, coords, uid, out var userEntity))
|
||||
{
|
||||
Logger.InfoS("system.interaction", $"WideAttack input validation failed");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (userEntity.TryGetComponent(out CombatModeComponent? combatMode) && combatMode.IsInCombatMode)
|
||||
{
|
||||
DoAttack(userEntity, coords, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -192,222 +214,144 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="coords"></param>
|
||||
/// <param name="uid"></param>
|
||||
internal void UseItemInHand(IEntity entity, EntityCoordinates coords, EntityUid uid)
|
||||
internal void AiUseInteraction(IEntity entity, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
if (entity.HasComponent<ActorComponent>())
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out CombatModeComponent? combatMode) && combatMode.IsInCombatMode)
|
||||
{
|
||||
DoAttack(entity, coords, false, uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
UserInteraction(entity, coords, uid);
|
||||
}
|
||||
UserInteraction(entity, coords, uid);
|
||||
}
|
||||
|
||||
public bool HandleClientUseItemInHand(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
public bool HandleUseInteraction(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
// client sanitization
|
||||
if (!coords.IsValid(_entityManager))
|
||||
if (!ValidateClientInput(session, coords, uid, out var userEntity))
|
||||
{
|
||||
Logger.InfoS("system.interaction", $"Invalid Coordinates: client={session}, coords={coords}");
|
||||
Logger.InfoS("system.interaction", $"Use input validation failed");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uid.IsClientSide())
|
||||
{
|
||||
Logger.WarningS("system.interaction",
|
||||
$"Client sent interaction with client-side entity. Session={session}, Uid={uid}");
|
||||
return true;
|
||||
}
|
||||
|
||||
var userEntity = ((IPlayerSession?) session)?.AttachedEntity;
|
||||
|
||||
if (userEntity == null || !userEntity.IsValid())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (userEntity.TryGetComponent(out CombatModeComponent? combat) && combat.IsInCombatMode)
|
||||
DoAttack(userEntity, coords, false, uid);
|
||||
else
|
||||
UserInteraction(userEntity, coords, uid);
|
||||
UserInteraction(userEntity, coords, uid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleTryPullObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
// client sanitization
|
||||
if (!coords.IsValid(_entityManager))
|
||||
if (!ValidateClientInput(session, coords, uid, out var userEntity))
|
||||
{
|
||||
Logger.InfoS("system.interaction", $"Invalid Coordinates for pulling: client={session}, coords={coords}");
|
||||
return false;
|
||||
Logger.InfoS("system.interaction", $"TryPullObject input validation failed");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uid.IsClientSide())
|
||||
{
|
||||
Logger.WarningS("system.interaction",
|
||||
$"Client sent pull interaction with client-side entity. Session={session}, Uid={uid}");
|
||||
if (userEntity.Uid == uid)
|
||||
return false;
|
||||
}
|
||||
|
||||
var player = session?.AttachedEntity;
|
||||
|
||||
if (player == null)
|
||||
{
|
||||
Logger.WarningS("system.interaction",
|
||||
$"Client sent pulling interaction with no attached entity. Session={session}, Uid={uid}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetEntity(uid, out var pulledObject))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (player == pulledObject)
|
||||
{
|
||||
if (!InRangeUnobstructed(userEntity, pulledObject, popup: true))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pulledObject.TryGetComponent(out PullableComponent? pull))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var dist = player.Transform.Coordinates.Position - pulledObject.Transform.Coordinates.Position;
|
||||
if (dist.LengthSquared > InteractionRangeSquared)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return pull.TogglePull(player);
|
||||
return pull.TogglePull(userEntity);
|
||||
}
|
||||
|
||||
public async void UserInteraction(IEntity player, EntityCoordinates coordinates, EntityUid clickedUid)
|
||||
public async void UserInteraction(IEntity user, EntityCoordinates coordinates, EntityUid clickedUid)
|
||||
{
|
||||
// Get entity clicked upon from UID if valid UID, if not assume no entity clicked upon and null
|
||||
if (!EntityManager.TryGetEntity(clickedUid, out var attacked))
|
||||
{
|
||||
attacked = null;
|
||||
}
|
||||
|
||||
// Verify player has a transform component
|
||||
if (!player.TryGetComponent<ITransformComponent>(out var playerTransform))
|
||||
if (user.TryGetComponent(out CombatModeComponent? combatMode) && combatMode.IsInCombatMode)
|
||||
{
|
||||
DoAttack(user, coordinates, false, clickedUid);
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify player is on the same map as the entity he clicked on
|
||||
if (coordinates.GetMapId(_entityManager) != playerTransform.MapID)
|
||||
if (!ValidateInteractAndFace(user, coordinates))
|
||||
return;
|
||||
|
||||
if (!ActionBlockerSystem.CanInteract(user))
|
||||
return;
|
||||
|
||||
// Get entity clicked upon from UID if valid UID, if not assume no entity clicked upon and null
|
||||
EntityManager.TryGetEntity(clickedUid, out var target);
|
||||
|
||||
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
|
||||
if (target != null && !user.IsInSameOrParentContainer(target))
|
||||
{
|
||||
Logger.WarningS("system.interaction",
|
||||
$"Player named {player.Name} clicked on a map he isn't located on");
|
||||
$"User entity named {user.Name} clicked on object {target.Name} that isn't the parent, child, or in the same container");
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify player has a hand, and find what object he is currently holding in his active hand
|
||||
if (!player.TryGetComponent<IHandsComponent>(out var hands))
|
||||
{
|
||||
// Verify user has a hand, and find what object he is currently holding in his active hand
|
||||
if (!user.TryGetComponent<IHandsComponent>(out var hands))
|
||||
return;
|
||||
}
|
||||
|
||||
var item = hands.GetActiveHand?.Owner;
|
||||
|
||||
ClickFace(player, coordinates);
|
||||
|
||||
if (!ActionBlockerSystem.CanInteract(player))
|
||||
// TODO: Replace with body interaction range when we get something like arm length or telekinesis or something.
|
||||
var inRangeUnobstructed = user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true);
|
||||
if (target == null || !inRangeUnobstructed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// In a container where the attacked entity is not the container's owner
|
||||
if (player.TryGetContainer(out var playerContainer) &&
|
||||
attacked != playerContainer.Owner)
|
||||
{
|
||||
// Either the attacked entity is null, not contained or in a different container
|
||||
if (attacked == null ||
|
||||
!attacked.TryGetContainer(out var attackedContainer) ||
|
||||
attackedContainer != playerContainer)
|
||||
{
|
||||
if (item == null)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check if client should be able to see that object to click on it in the first place
|
||||
|
||||
// Clicked on empty space behavior, try using ranged attack
|
||||
if (attacked == null)
|
||||
{
|
||||
if (item != null)
|
||||
if (!await InteractUsingRanged(user, item, target, coordinates, inRangeUnobstructed) &&
|
||||
!inRangeUnobstructed)
|
||||
{
|
||||
// After attack: Check if we clicked on an empty location, if so the only interaction we can do is AfterInteract
|
||||
var distSqrt = (playerTransform.WorldPosition - coordinates.ToMapPos(EntityManager)).LengthSquared;
|
||||
InteractAfter(player, item, coordinates, distSqrt <= InteractionRangeSquared);
|
||||
var message = Loc.GetString("You can't reach there!");
|
||||
user.PopupMessage(message);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify attacked object is on the map if we managed to click on it somehow
|
||||
if (!attacked.Transform.IsMapTransform)
|
||||
{
|
||||
Logger.WarningS("system.interaction",
|
||||
$"Player named {player.Name} clicked on object {attacked.Name} that isn't currently on the map somehow");
|
||||
return;
|
||||
}
|
||||
|
||||
// RangedInteract/AfterInteract: Check distance between user and clicked item, if too large parse it in the ranged function
|
||||
// TODO: have range based upon the item being used? or base it upon some variables of the player himself?
|
||||
var distance = (playerTransform.WorldPosition - attacked.Transform.WorldPosition).LengthSquared;
|
||||
if (distance > InteractionRangeSquared)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
RangedInteraction(player, item, attacked, coordinates);
|
||||
return;
|
||||
}
|
||||
|
||||
return; // Add some form of ranged InteractHand here if you need it someday, or perhaps just ways to modify the range of InteractHand
|
||||
}
|
||||
|
||||
// We are close to the nearby object and the object isn't contained in our active hand
|
||||
// InteractUsing/AfterInteract: We will either use the item on the nearby object
|
||||
if (item != null)
|
||||
{
|
||||
await Interaction(player, item, attacked, coordinates);
|
||||
}
|
||||
// InteractHand/Activate: Since our hand is empty we will use InteractHand/Activate
|
||||
else
|
||||
{
|
||||
Interaction(player, attacked);
|
||||
// We are close to the nearby object and the object isn't contained in our active hand
|
||||
// InteractUsing/AfterInteract: We will either use the item on the nearby object
|
||||
if (item != null)
|
||||
await InteractUsing(user, item, target, coordinates);
|
||||
// InteractHand/Activate: Since our hand is empty we will use InteractHand/Activate
|
||||
else
|
||||
InteractHand(user, target);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClickFace(IEntity player, EntityCoordinates coordinates)
|
||||
private bool ValidateInteractAndFace(IEntity user, EntityCoordinates coordinates)
|
||||
{
|
||||
var diff = coordinates.ToMapPos(EntityManager) - player.Transform.MapPosition.Position;
|
||||
// Verify user is on the same map as the entity he clicked on
|
||||
if (coordinates.GetMapId(_entityManager) != user.Transform.MapID)
|
||||
{
|
||||
Logger.WarningS("system.interaction",
|
||||
$"User entity named {user.Name} clicked on a map he isn't located on");
|
||||
return false;
|
||||
}
|
||||
|
||||
FaceClickCoordinates(user, coordinates);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void FaceClickCoordinates(IEntity user, EntityCoordinates coordinates)
|
||||
{
|
||||
var diff = coordinates.ToMapPos(EntityManager) - user.Transform.MapPosition.Position;
|
||||
if (diff.LengthSquared <= 0.01f)
|
||||
return;
|
||||
var diffAngle = Angle.FromWorldVec(diff);
|
||||
if (ActionBlockerSystem.CanChangeDirection(player))
|
||||
if (ActionBlockerSystem.CanChangeDirection(user))
|
||||
{
|
||||
player.Transform.LocalRotation = diffAngle;
|
||||
user.Transform.WorldRotation = diffAngle;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (player.TryGetComponent(out BuckleComponent? buckle) && (buckle.BuckledTo != null))
|
||||
if (user.TryGetComponent(out BuckleComponent? buckle) && (buckle.BuckledTo != null))
|
||||
{
|
||||
// We're buckled to another object. Is that object rotatable?
|
||||
if (buckle.BuckledTo!.Owner.TryGetComponent(out SharedRotatableComponent? rotatable) && rotatable.RotateWhileAnchored)
|
||||
{
|
||||
// Note the assumption that even if unanchored, player can only do spinnychair with an "independent wheel".
|
||||
// (Since the player being buckled to it holds it down with their weight.)
|
||||
// Note the assumption that even if unanchored, user can only do spinnychair with an "independent wheel".
|
||||
// (Since the user being buckled to it holds it down with their weight.)
|
||||
// This is logically equivalent to RotateWhileAnchored.
|
||||
// Barstools and office chairs have independent wheels, while regular chairs don't.
|
||||
rotatable.Owner.Transform.LocalRotation = diffAngle;
|
||||
@@ -419,90 +363,87 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
/// <summary>
|
||||
/// We didn't click on any entity, try doing an AfterInteract on the click location
|
||||
/// </summary>
|
||||
private async void InteractAfter(IEntity user, IEntity weapon, EntityCoordinates clickLocation, bool canReach)
|
||||
private async Task<bool> InteractDoAfter(IEntity user, IEntity used, IEntity? target, EntityCoordinates clickLocation, bool canReach)
|
||||
{
|
||||
var message = new AfterInteractEvent(user, weapon, null, clickLocation, canReach);
|
||||
RaiseLocalEvent(weapon.Uid, message);
|
||||
if (message.Handled)
|
||||
var afterInteractEvent = new AfterInteractEvent(user, used, target, clickLocation, canReach);
|
||||
RaiseLocalEvent(used.Uid, afterInteractEvent, false);
|
||||
if (afterInteractEvent.Handled)
|
||||
return true;
|
||||
|
||||
var afterInteractEventArgs = new AfterInteractEventArgs(user, clickLocation, target, canReach);
|
||||
var afterInteracts = used.GetAllComponents<IAfterInteract>().OrderByDescending(x => x.Priority).ToList();
|
||||
|
||||
foreach (var afterInteract in afterInteracts)
|
||||
{
|
||||
return;
|
||||
if (await afterInteract.AfterInteract(afterInteractEventArgs))
|
||||
return true;
|
||||
}
|
||||
|
||||
var afterInteractEventArgs = new AfterInteractEventArgs(user, clickLocation, null, canReach);
|
||||
await DoAfterInteract(weapon, afterInteractEventArgs);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses a weapon/object on an entity
|
||||
/// Uses a item/object on an entity
|
||||
/// Finds components with the InteractUsing interface and calls their function
|
||||
/// NOTE: Does not have an InRangeUnobstructed check
|
||||
/// </summary>
|
||||
public async Task Interaction(IEntity user, IEntity weapon, IEntity attacked, EntityCoordinates clickLocation)
|
||||
public async Task InteractUsing(IEntity user, IEntity used, IEntity target, EntityCoordinates clickLocation)
|
||||
{
|
||||
var attackMsg = new InteractUsingEvent(user, weapon, attacked, clickLocation);
|
||||
RaiseLocalEvent(attacked.Uid, attackMsg);
|
||||
if (attackMsg.Handled)
|
||||
if (!ActionBlockerSystem.CanInteract(user))
|
||||
return;
|
||||
|
||||
var attackBys = attacked.GetAllComponents<IInteractUsing>().OrderByDescending(x => x.Priority);
|
||||
var attackByEventArgs = new InteractUsingEventArgs(user, clickLocation, weapon, attacked);
|
||||
|
||||
// all AttackBys should only happen when in range / unobstructed, so no range check is needed
|
||||
if (attackByEventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
|
||||
{
|
||||
foreach (var attackBy in attackBys)
|
||||
{
|
||||
if (await attackBy.InteractUsing(attackByEventArgs))
|
||||
{
|
||||
// If an InteractUsing returns a status completion we finish our attack
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var afterAtkMsg = new AfterInteractEvent(user, weapon, attacked, clickLocation, true);
|
||||
RaiseLocalEvent(weapon.Uid, afterAtkMsg, false);
|
||||
if (afterAtkMsg.Handled)
|
||||
{
|
||||
// all interactions should only happen when in range / unobstructed, so no range check is needed
|
||||
var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation);
|
||||
RaiseLocalEvent(target.Uid, interactUsingEvent);
|
||||
if (interactUsingEvent.Handled)
|
||||
return;
|
||||
|
||||
var interactUsingEventArgs = new InteractUsingEventArgs(user, clickLocation, used, target);
|
||||
|
||||
var interactUsings = target.GetAllComponents<IInteractUsing>().OrderByDescending(x => x.Priority);
|
||||
foreach (var interactUsing in interactUsings)
|
||||
{
|
||||
// If an InteractUsing returns a status completion we finish our interaction
|
||||
if (await interactUsing.InteractUsing(interactUsingEventArgs))
|
||||
return;
|
||||
}
|
||||
|
||||
// If we aren't directly attacking the nearby object, lets see if our item has an after attack we can do
|
||||
var afterAttackEventArgs = new AfterInteractEventArgs(user, clickLocation, attacked, canReach: true);
|
||||
|
||||
await DoAfterInteract(weapon, afterAttackEventArgs);
|
||||
// If we aren't directly interacting with the nearby object, lets see if our item has an after interact we can do
|
||||
await InteractDoAfter(user, used, target, clickLocation, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses an empty hand on an entity
|
||||
/// Finds components with the InteractHand interface and calls their function
|
||||
/// NOTE: Does not have an InRangeUnobstructed check
|
||||
/// </summary>
|
||||
public void Interaction(IEntity user, IEntity attacked)
|
||||
public void InteractHand(IEntity user, IEntity target)
|
||||
{
|
||||
var message = new AttackHandEvent(user, attacked);
|
||||
RaiseLocalEvent(attacked.Uid, message);
|
||||
if (!ActionBlockerSystem.CanInteract(user))
|
||||
return;
|
||||
|
||||
// all interactions should only happen when in range / unobstructed, so no range check is needed
|
||||
var message = new InteractHandEvent(user, target);
|
||||
RaiseLocalEvent(target.Uid, message);
|
||||
if (message.Handled)
|
||||
return;
|
||||
|
||||
var attackHandEventArgs = new InteractHandEventArgs(user, attacked);
|
||||
var interactHandEventArgs = new InteractHandEventArgs(user, target);
|
||||
|
||||
// all attackHands should only fire when in range / unobstructed
|
||||
if (attackHandEventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
|
||||
var interactHandComps = target.GetAllComponents<IInteractHand>().ToList();
|
||||
foreach (var interactHandComp in interactHandComps)
|
||||
{
|
||||
var attackHands = attacked.GetAllComponents<IInteractHand>().ToList();
|
||||
foreach (var attackHand in attackHands)
|
||||
{
|
||||
if (attackHand.InteractHand(attackHandEventArgs))
|
||||
{
|
||||
// If an InteractHand returns a status completion we finish our attack
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If an InteractHand returns a status completion we finish our interaction
|
||||
if (interactHandComp.InteractHand(interactHandEventArgs))
|
||||
return;
|
||||
}
|
||||
|
||||
// Else we run Activate.
|
||||
InteractionActivate(user, attacked);
|
||||
InteractionActivate(user, target);
|
||||
}
|
||||
|
||||
#region Hands
|
||||
#region Use
|
||||
/// <summary>
|
||||
/// Activates the IUse behaviors of an entity
|
||||
/// Verifies that the user is capable of doing the use interaction first
|
||||
@@ -534,23 +475,21 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
var useMsg = new UseInHandEvent(user, used);
|
||||
RaiseLocalEvent(used.Uid, useMsg);
|
||||
if (useMsg.Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var uses = used.GetAllComponents<IUse>().ToList();
|
||||
|
||||
// Try to use item on any components which have the interface
|
||||
foreach (var use in uses)
|
||||
{
|
||||
// If a Use returns a status completion we finish our interaction
|
||||
if (use.UseEntity(new UseEntityEventArgs(user)))
|
||||
{
|
||||
// If a Use returns a status completion we finish our attack
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Throw
|
||||
/// <summary>
|
||||
/// Activates the Throw behavior of an object
|
||||
/// Verifies that the user is capable of doing the throw interaction first
|
||||
@@ -572,9 +511,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
var throwMsg = new ThrownEvent(user, thrown);
|
||||
RaiseLocalEvent(thrown.Uid, throwMsg);
|
||||
if (throwMsg.Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var comps = thrown.GetAllComponents<IThrown>().ToList();
|
||||
var args = new ThrownEventArgs(user);
|
||||
@@ -585,7 +522,9 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
comp.Thrown(args);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Equip
|
||||
/// <summary>
|
||||
/// Calls Equipped on all components that implement the IEquipped interface
|
||||
/// on an entity that has been equipped.
|
||||
@@ -595,9 +534,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
var equipMsg = new EquippedEvent(user, equipped, slot);
|
||||
RaiseLocalEvent(equipped.Uid, equipMsg);
|
||||
if (equipMsg.Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var comps = equipped.GetAllComponents<IEquipped>().ToList();
|
||||
|
||||
@@ -617,9 +554,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
var unequipMsg = new UnequippedEvent(user, equipped, slot);
|
||||
RaiseLocalEvent(equipped.Uid, unequipMsg);
|
||||
if (unequipMsg.Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var comps = equipped.GetAllComponents<IUnequipped>().ToList();
|
||||
|
||||
@@ -630,6 +565,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
}
|
||||
}
|
||||
|
||||
#region Equip Hand
|
||||
/// <summary>
|
||||
/// Calls EquippedHand on all components that implement the IEquippedHand interface
|
||||
/// on an item.
|
||||
@@ -639,9 +575,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
var equippedHandMessage = new EquippedHandEvent(user, item, hand);
|
||||
RaiseLocalEvent(item.Uid, equippedHandMessage);
|
||||
if (equippedHandMessage.Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var comps = item.GetAllComponents<IEquippedHand>().ToList();
|
||||
|
||||
@@ -660,9 +594,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
var unequippedHandMessage = new UnequippedHandEvent(user, item, hand);
|
||||
RaiseLocalEvent(item.Uid, unequippedHandMessage);
|
||||
if (unequippedHandMessage.Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var comps = item.GetAllComponents<IUnequippedHand>().ToList();
|
||||
|
||||
@@ -671,7 +603,10 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
comp.UnequippedHand(new UnequippedHandEventArgs(user, hand));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Drop
|
||||
/// <summary>
|
||||
/// Activates the Dropped behavior of an object
|
||||
/// Verifies that the user is capable of doing the drop interaction first
|
||||
@@ -693,9 +628,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
var dropMsg = new DroppedEvent(user, item, intentional);
|
||||
RaiseLocalEvent(item.Uid, dropMsg);
|
||||
if (dropMsg.Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
item.Transform.LocalRotation = intentional ? Angle.Zero : (_random.Next(0, 100) / 100f) * MathHelper.TwoPi;
|
||||
|
||||
@@ -707,7 +640,9 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
comp.Dropped(new DroppedEventArgs(user, intentional));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Hand Selected
|
||||
/// <summary>
|
||||
/// Calls HandSelected on all components that implement the IHandSelected interface
|
||||
/// on an item entity on a hand that has just been selected.
|
||||
@@ -717,9 +652,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
var handSelectedMsg = new HandSelectedEvent(user, item);
|
||||
RaiseLocalEvent(item.Uid, handSelectedMsg);
|
||||
if (handSelectedMsg.Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var comps = item.GetAllComponents<IHandSelected>().ToList();
|
||||
|
||||
@@ -739,9 +672,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
var handDeselectedMsg = new HandDeselectedEvent(user, item);
|
||||
RaiseLocalEvent(item.Uid, handDeselectedMsg);
|
||||
if (handDeselectedMsg.Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var comps = item.GetAllComponents<IHandDeselected>().ToList();
|
||||
|
||||
@@ -751,88 +682,70 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
comp.HandDeselected(new HandDeselectedEventArgs(user));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Will have two behaviors, either "uses" the weapon at range on the entity if it is capable of accepting that action
|
||||
/// Or it will use the weapon itself on the position clicked, regardless of what was there
|
||||
/// Will have two behaviors, either "uses" the used entity at range on the target entity if it is capable of accepting that action
|
||||
/// Or it will use the used entity itself on the position clicked, regardless of what was there
|
||||
/// </summary>
|
||||
public async void RangedInteraction(IEntity user, IEntity weapon, IEntity attacked, EntityCoordinates clickLocation)
|
||||
public async Task<bool> InteractUsingRanged(IEntity user, IEntity used, IEntity? target, EntityCoordinates clickLocation, bool inRangeUnobstructed)
|
||||
{
|
||||
var rangedMsg = new RangedInteractEvent(user, weapon, attacked, clickLocation);
|
||||
RaiseLocalEvent(attacked.Uid, rangedMsg);
|
||||
if (rangedMsg.Handled)
|
||||
return;
|
||||
|
||||
var rangedAttackBys = attacked.GetAllComponents<IRangedInteract>().ToList();
|
||||
var rangedAttackByEventArgs = new RangedInteractEventArgs(user, weapon, clickLocation);
|
||||
|
||||
// See if we have a ranged attack interaction
|
||||
foreach (var t in rangedAttackBys)
|
||||
if (target != null)
|
||||
{
|
||||
if (t.RangedInteract(rangedAttackByEventArgs))
|
||||
var rangedMsg = new RangedInteractEvent(user, used, target, clickLocation);
|
||||
RaiseLocalEvent(target.Uid, rangedMsg);
|
||||
if (rangedMsg.Handled)
|
||||
return true;
|
||||
|
||||
var rangedInteractions = target.GetAllComponents<IRangedInteract>().ToList();
|
||||
var rangedInteractionEventArgs = new RangedInteractEventArgs(user, used, clickLocation);
|
||||
|
||||
// See if we have a ranged interaction
|
||||
foreach (var t in rangedInteractions)
|
||||
{
|
||||
// If an InteractUsing returns a status completion we finish our attack
|
||||
return;
|
||||
// If an InteractUsingRanged returns a status completion we finish our interaction
|
||||
if (t.RangedInteract(rangedInteractionEventArgs))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var afterAtkMsg = new AfterInteractEvent(user, weapon, attacked, clickLocation, false);
|
||||
RaiseLocalEvent(weapon.Uid, afterAtkMsg);
|
||||
if (afterAtkMsg.Handled)
|
||||
return;
|
||||
|
||||
// See if we have a ranged attack interaction
|
||||
var afterAttackEventArgs = new AfterInteractEventArgs(user, clickLocation, attacked, canReach: false);
|
||||
await DoAfterInteract(weapon, afterAttackEventArgs);
|
||||
if (inRangeUnobstructed)
|
||||
return await InteractDoAfter(user, used, target, clickLocation, false);
|
||||
else
|
||||
return await InteractDoAfter(user, used, null, clickLocation, false);
|
||||
}
|
||||
|
||||
private static async Task DoAfterInteract(IEntity weapon, AfterInteractEventArgs afterAttackEventArgs)
|
||||
public void DoAttack(IEntity user, EntityCoordinates coordinates, bool wideAttack, EntityUid targetUid = default)
|
||||
{
|
||||
var afterAttacks = weapon.GetAllComponents<IAfterInteract>().OrderByDescending(x => x.Priority).ToList();
|
||||
if (!ValidateInteractAndFace(user, coordinates))
|
||||
return;
|
||||
|
||||
foreach (var afterAttack in afterAttacks)
|
||||
if (!ActionBlockerSystem.CanAttack(user))
|
||||
return;
|
||||
|
||||
IEntity? targetEnt = null;
|
||||
|
||||
if (!wideAttack)
|
||||
{
|
||||
if (await afterAttack.AfterInteract(afterAttackEventArgs))
|
||||
// Get entity clicked upon from UID if valid UID, if not assume no entity clicked upon and null
|
||||
EntityManager.TryGetEntity(targetUid, out targetEnt);
|
||||
|
||||
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
|
||||
if (targetEnt != null && !user.IsInSameOrParentContainer(targetEnt))
|
||||
{
|
||||
Logger.WarningS("system.interaction",
|
||||
$"User entity named {user.Name} clicked on object {targetEnt.Name} that isn't the parent, child, or in the same container");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DoAttack(IEntity player, EntityCoordinates coordinates, bool wideAttack, EntityUid targetUid = default)
|
||||
{
|
||||
// Verify player is on the same map as the entity he clicked on
|
||||
if (coordinates.GetMapId(EntityManager) != player.Transform.MapID)
|
||||
{
|
||||
Logger.WarningS("system.interaction",
|
||||
$"Player named {player.Name} clicked on a map he isn't located on");
|
||||
return;
|
||||
}
|
||||
|
||||
ClickFace(player, coordinates);
|
||||
|
||||
if (!ActionBlockerSystem.CanAttack(player) ||
|
||||
(!wideAttack && !player.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// In a container where the target entity is not the container's owner
|
||||
if (player.TryGetContainer(out var playerContainer) &&
|
||||
(!EntityManager.TryGetEntity(targetUid, out var target) ||
|
||||
target != playerContainer.Owner))
|
||||
{
|
||||
// Either the target entity is null, not contained or in a different container
|
||||
if (target == null ||
|
||||
!target.TryGetContainer(out var attackedContainer) ||
|
||||
attackedContainer != playerContainer)
|
||||
{
|
||||
// TODO: Replace with body attack range when we get something like arm length or telekinesis or something.
|
||||
if (!user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify player has a hand, and find what object he is currently holding in his active hand
|
||||
if (player.TryGetComponent<IHandsComponent>(out var hands))
|
||||
// Verify user has a hand, and find what object he is currently holding in his active hand
|
||||
if (user.TryGetComponent<IHandsComponent>(out var hands))
|
||||
{
|
||||
var item = hands.GetActiveHand?.Owner;
|
||||
|
||||
@@ -840,7 +753,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
{
|
||||
if (wideAttack)
|
||||
{
|
||||
var ev = new WideAttackEvent(item, player, coordinates);
|
||||
var ev = new WideAttackEvent(item, user, coordinates);
|
||||
RaiseLocalEvent(item.Uid, ev, false);
|
||||
|
||||
if(ev.Handled)
|
||||
@@ -848,30 +761,29 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
}
|
||||
else
|
||||
{
|
||||
var ev = new ClickAttackEvent(item, player, coordinates, targetUid);
|
||||
var ev = new ClickAttackEvent(item, user, coordinates, targetUid);
|
||||
RaiseLocalEvent(item.Uid, ev, false);
|
||||
|
||||
if(ev.Handled)
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (!wideAttack &&
|
||||
(targetEnt != null || EntityManager.TryGetEntity(targetUid, out targetEnt)) &&
|
||||
targetEnt.HasComponent<ItemComponent>())
|
||||
{
|
||||
// We pick up items if our hand is empty, even if we're in combat mode.
|
||||
if (EntityManager.TryGetEntity(targetUid, out var targetEnt) && targetEnt.HasComponent<ItemComponent>())
|
||||
{
|
||||
Interaction(player, targetEnt);
|
||||
return;
|
||||
}
|
||||
InteractHand(user, targetEnt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make this saner?
|
||||
// Attempt to do unarmed combat. We don't check for handled just because at this point it doesn't matter.
|
||||
if(wideAttack)
|
||||
RaiseLocalEvent(player.Uid, new WideAttackEvent(player, player, coordinates), false);
|
||||
RaiseLocalEvent(user.Uid, new WideAttackEvent(user, user, coordinates), false);
|
||||
else
|
||||
RaiseLocalEvent(player.Uid, new ClickAttackEvent(player, player, coordinates, targetUid), false);
|
||||
RaiseLocalEvent(user.Uid, new ClickAttackEvent(user, user, coordinates, targetUid), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,19 +188,8 @@ namespace Content.Shared.GameObjects.EntitySystems
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
var originPosition = origin.Transform.MapPosition;
|
||||
var otherPosition = other.Transform.MapPosition;
|
||||
predicate ??= e => e == origin || e == other;
|
||||
|
||||
var inRange = InRangeUnobstructed(originPosition, otherPosition, range, collisionMask, predicate, ignoreInsideBlocker);
|
||||
|
||||
if (!inRange && popup)
|
||||
{
|
||||
var message = Loc.GetString("You can't reach there!");
|
||||
origin.PopupMessage(message);
|
||||
}
|
||||
|
||||
return inRange;
|
||||
return InRangeUnobstructed(origin, other.Transform.MapPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -244,19 +233,7 @@ namespace Content.Shared.GameObjects.EntitySystems
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
var originPosition = origin.Transform.MapPosition;
|
||||
var otherPosition = other.Owner.Transform.MapPosition;
|
||||
predicate ??= e => e == origin || e == other.Owner;
|
||||
|
||||
var inRange = InRangeUnobstructed(originPosition, otherPosition, range, collisionMask, predicate, ignoreInsideBlocker);
|
||||
|
||||
if (!inRange && popup)
|
||||
{
|
||||
var message = Loc.GetString("You can't reach there!");
|
||||
origin.PopupMessage(message);
|
||||
}
|
||||
|
||||
return inRange;
|
||||
return InRangeUnobstructed(origin, other.Owner, range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -300,19 +277,7 @@ namespace Content.Shared.GameObjects.EntitySystems
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
var originPosition = origin.Transform.MapPosition;
|
||||
var otherPosition = other.ToMap(EntityManager);
|
||||
predicate ??= e => e == origin;
|
||||
|
||||
var inRange = InRangeUnobstructed(originPosition, otherPosition, range, collisionMask, predicate, ignoreInsideBlocker);
|
||||
|
||||
if (!inRange && popup)
|
||||
{
|
||||
var message = Loc.GetString("You can't reach there!");
|
||||
origin.PopupMessage(message);
|
||||
}
|
||||
|
||||
return inRange;
|
||||
return InRangeUnobstructed(origin, other.ToMap(EntityManager), range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -369,235 +334,5 @@ namespace Content.Shared.GameObjects.EntitySystems
|
||||
|
||||
return inRange;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that the user and target of a
|
||||
/// <see cref="ITargetedInteractEventArgs"/> are within a certain
|
||||
/// distance without any entity that matches the collision mask
|
||||
/// obstructing them.
|
||||
/// If the <paramref name="range"/> is zero or negative,
|
||||
/// this method will only check if nothing obstructs the entity and component.
|
||||
/// </summary>
|
||||
/// <param name="args">The event args to use.</param>
|
||||
/// <param name="range">
|
||||
/// Maximum distance between the two entity and set of map coordinates.
|
||||
/// </param>
|
||||
/// <param name="collisionMask">The mask to check for collisions.</param>
|
||||
/// <param name="predicate">
|
||||
/// A predicate to check whether to ignore an entity or not.
|
||||
/// If it returns true, it will be ignored.
|
||||
/// </param>
|
||||
/// <param name="ignoreInsideBlocker">
|
||||
/// If true and both the user and target are inside
|
||||
/// the obstruction, ignores the obstruction and considers the interaction
|
||||
/// unobstructed.
|
||||
/// Therefore, setting this to true makes this check more permissive,
|
||||
/// such as allowing an interaction to occur inside something impassable
|
||||
/// (like a wall). The default, false, makes the check more restrictive.
|
||||
/// </param>
|
||||
/// <param name="popup">
|
||||
/// Whether or not to popup a feedback message on the user entity for
|
||||
/// it to see.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the two points are within a given range without being obstructed.
|
||||
/// </returns>
|
||||
public bool InRangeUnobstructed(
|
||||
ITargetedInteractEventArgs args,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = CollisionGroup.Impassable,
|
||||
Ignored? predicate = null,
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
var origin = args.User;
|
||||
var other = args.Target;
|
||||
|
||||
return InRangeUnobstructed(origin, other, range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that the user of a <see cref="DragDropEvent"/> is within a
|
||||
/// certain distance of the target and dropped entities without any entity
|
||||
/// that matches the collision mask obstructing them.
|
||||
/// If the <paramref name="range"/> is zero or negative,
|
||||
/// this method will only check if nothing obstructs the entity and component.
|
||||
/// </summary>
|
||||
/// <param name="args">The event args to use.</param>
|
||||
/// <param name="range">
|
||||
/// Maximum distance between the two entity and set of map coordinates.
|
||||
/// </param>
|
||||
/// <param name="collisionMask">The mask to check for collisions.</param>
|
||||
/// <param name="predicate">
|
||||
/// A predicate to check whether to ignore an entity or not.
|
||||
/// If it returns true, it will be ignored.
|
||||
/// </param>
|
||||
/// <param name="ignoreInsideBlocker">
|
||||
/// If true and both the user and target are inside
|
||||
/// the obstruction, ignores the obstruction and considers the interaction
|
||||
/// unobstructed.
|
||||
/// Therefore, setting this to true makes this check more permissive,
|
||||
/// such as allowing an interaction to occur inside something impassable
|
||||
/// (like a wall). The default, false, makes the check more restrictive.
|
||||
/// </param>
|
||||
/// <param name="popup">
|
||||
/// Whether or not to popup a feedback message on the user entity for
|
||||
/// it to see.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the two points are within a given range without being obstructed.
|
||||
/// </returns>
|
||||
public bool InRangeUnobstructed(
|
||||
DragDropEvent args,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = CollisionGroup.Impassable,
|
||||
Ignored? predicate = null,
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
var user = args.User;
|
||||
var dropped = args.Dragged;
|
||||
var target = args.Target;
|
||||
|
||||
if (!InRangeUnobstructed(user, target, range, collisionMask, predicate, ignoreInsideBlocker))
|
||||
{
|
||||
if (popup)
|
||||
{
|
||||
var message = Loc.GetString("You can't reach there!");
|
||||
target.PopupMessage(user, message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!InRangeUnobstructed(user, dropped, range, collisionMask, predicate, ignoreInsideBlocker))
|
||||
{
|
||||
if (popup)
|
||||
{
|
||||
var message = Loc.GetString("You can't reach there!");
|
||||
dropped.PopupMessage(user, message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that the user and target of a
|
||||
/// <see cref="AfterInteractEventArgs"/> are within a certain distance
|
||||
/// without any entity that matches the collision mask obstructing them.
|
||||
/// If the <paramref name="range"/> is zero or negative,
|
||||
/// this method will only check if nothing obstructs the entity and component.
|
||||
/// </summary>
|
||||
/// <param name="args">The event args to use.</param>
|
||||
/// <param name="range">
|
||||
/// Maximum distance between the two entity and set of map coordinates.
|
||||
/// </param>
|
||||
/// <param name="collisionMask">The mask to check for collisions.</param>
|
||||
/// <param name="predicate">
|
||||
/// A predicate to check whether to ignore an entity or not.
|
||||
/// If it returns true, it will be ignored.
|
||||
/// </param>
|
||||
/// <param name="ignoreInsideBlocker">
|
||||
/// If true and both the user and target are inside
|
||||
/// the obstruction, ignores the obstruction and considers the interaction
|
||||
/// unobstructed.
|
||||
/// Therefore, setting this to true makes this check more permissive,
|
||||
/// such as allowing an interaction to occur inside something impassable
|
||||
/// (like a wall). The default, false, makes the check more restrictive.
|
||||
/// </param>
|
||||
/// <param name="popup">
|
||||
/// Whether or not to popup a feedback message on the user entity for
|
||||
/// it to see.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the two points are within a given range without being obstructed.
|
||||
/// </returns>
|
||||
public bool InRangeUnobstructed(
|
||||
AfterInteractEventArgs args,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = CollisionGroup.Impassable,
|
||||
Ignored? predicate = null,
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
var user = args.User;
|
||||
var target = args.Target;
|
||||
predicate ??= e => e == user;
|
||||
|
||||
MapCoordinates otherPosition;
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
otherPosition = args.ClickLocation.ToMap(EntityManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
otherPosition = target.Transform.MapPosition;
|
||||
predicate += e => e == target;
|
||||
}
|
||||
|
||||
return InRangeUnobstructed(user, otherPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that the user and target of a
|
||||
/// <see cref="AfterInteractEvent"/> are within a certain distance
|
||||
/// without any entity that matches the collision mask obstructing them.
|
||||
/// If the <paramref name="range"/> is zero or negative,
|
||||
/// this method will only check if nothing obstructs the entity and component.
|
||||
/// </summary>
|
||||
/// <param name="args">The event args to use.</param>
|
||||
/// <param name="range">
|
||||
/// Maximum distance between the two entity and set of map coordinates.
|
||||
/// </param>
|
||||
/// <param name="collisionMask">The mask to check for collisions.</param>
|
||||
/// <param name="predicate">
|
||||
/// A predicate to check whether to ignore an entity or not.
|
||||
/// If it returns true, it will be ignored.
|
||||
/// </param>
|
||||
/// <param name="ignoreInsideBlocker">
|
||||
/// If true and both the user and target are inside
|
||||
/// the obstruction, ignores the obstruction and considers the interaction
|
||||
/// unobstructed.
|
||||
/// Therefore, setting this to true makes this check more permissive,
|
||||
/// such as allowing an interaction to occur inside something impassable
|
||||
/// (like a wall). The default, false, makes the check more restrictive.
|
||||
/// </param>
|
||||
/// <param name="popup">
|
||||
/// Whether or not to popup a feedback message on the user entity for
|
||||
/// it to see.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the two points are within a given range without being obstructed.
|
||||
/// </returns>
|
||||
public bool InRangeUnobstructed(
|
||||
AfterInteractEvent args,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = CollisionGroup.Impassable,
|
||||
Ignored? predicate = null,
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
var user = args.User;
|
||||
var target = args.Target;
|
||||
predicate ??= e => e == user;
|
||||
|
||||
MapCoordinates otherPosition;
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
otherPosition = args.ClickLocation.ToMap(EntityManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
otherPosition = target.Transform.MapPosition;
|
||||
predicate += e => e == target;
|
||||
}
|
||||
|
||||
return InRangeUnobstructed(user, otherPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Content.Shared.Interfaces.GameObjects.Components
|
||||
/// Raised when a target entity is interacted with by a user with an empty hand.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public class AttackHandEvent : HandledEntityEventArgs
|
||||
public class InteractHandEvent : HandledEntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity that triggered the interaction.
|
||||
@@ -47,7 +47,7 @@ namespace Content.Shared.Interfaces.GameObjects.Components
|
||||
/// </summary>
|
||||
public IEntity Target { get; }
|
||||
|
||||
public AttackHandEvent(IEntity user, IEntity target)
|
||||
public InteractHandEvent(IEntity user, IEntity target)
|
||||
{
|
||||
User = user;
|
||||
Target = target;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using static Content.Shared.GameObjects.EntitySystems.SharedInteractionSystem;
|
||||
|
||||
@@ -402,20 +404,7 @@ namespace Content.Shared.Utility
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
return SharedInteractionSystem.InRangeUnobstructed(args, range, collisionMask, predicate,
|
||||
ignoreInsideBlocker, popup);
|
||||
}
|
||||
|
||||
public static bool InRangeUnobstructed(
|
||||
this DragDropEvent args,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = CollisionGroup.Impassable,
|
||||
Ignored? predicate = null,
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
return SharedInteractionSystem.InRangeUnobstructed(args, range, collisionMask, predicate,
|
||||
ignoreInsideBlocker, popup);
|
||||
return SharedInteractionSystem.InRangeUnobstructed(args.User, args.Target, range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||
}
|
||||
|
||||
public static bool InRangeUnobstructed(
|
||||
@@ -426,12 +415,39 @@ namespace Content.Shared.Utility
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
return SharedInteractionSystem.InRangeUnobstructed(args, range, collisionMask, predicate,
|
||||
ignoreInsideBlocker, popup);
|
||||
var user = args.User;
|
||||
var target = args.Target;
|
||||
|
||||
if (target == null)
|
||||
return SharedInteractionSystem.InRangeUnobstructed(user, args.ClickLocation, range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||
else
|
||||
return SharedInteractionSystem.InRangeUnobstructed(user, target, range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region EntityEventArgs
|
||||
public static bool InRangeUnobstructed(
|
||||
this DragDropEvent args,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = CollisionGroup.Impassable,
|
||||
Ignored? predicate = null,
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
var user = args.User;
|
||||
var dropped = args.Dragged;
|
||||
var target = args.Target;
|
||||
|
||||
if (!SharedInteractionSystem.InRangeUnobstructed(user, target, range, collisionMask, predicate, ignoreInsideBlocker, popup))
|
||||
return false;
|
||||
|
||||
if (!SharedInteractionSystem.InRangeUnobstructed(user, dropped, range, collisionMask, predicate, ignoreInsideBlocker, popup))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool InRangeUnobstructed(
|
||||
this AfterInteractEvent args,
|
||||
float range = InteractionRange,
|
||||
@@ -440,8 +456,13 @@ namespace Content.Shared.Utility
|
||||
bool ignoreInsideBlocker = false,
|
||||
bool popup = false)
|
||||
{
|
||||
return SharedInteractionSystem.InRangeUnobstructed(args, range, collisionMask, predicate,
|
||||
ignoreInsideBlocker, popup);
|
||||
var user = args.User;
|
||||
var target = args.Target;
|
||||
|
||||
if (target == null)
|
||||
return SharedInteractionSystem.InRangeUnobstructed(user, args.ClickLocation, range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||
else
|
||||
return SharedInteractionSystem.InRangeUnobstructed(user, target, range, collisionMask, predicate, ignoreInsideBlocker, popup);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user