mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Robust.UnitTesting was both ALL tests for RT, and also API surface for content tests. Tests are now split into separate projects as appropriate, and the API side has also been split off.
520 lines
23 KiB
C#
520 lines
23 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Threading.Tasks;
|
|
using NUnit.Framework;
|
|
using Robust.Client.Physics;
|
|
using Robust.Shared;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.GameStates;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Physics.Collision.Shapes;
|
|
using Robust.Shared.Physics.Components;
|
|
using Robust.Shared.Physics.Dynamics;
|
|
using Robust.Shared.Physics.Events;
|
|
using Robust.Shared.Physics.Systems;
|
|
using Robust.Shared.Serialization;
|
|
using Robust.Shared.Timing;
|
|
|
|
namespace Robust.UnitTesting.Shared.Physics;
|
|
|
|
/// <summary>
|
|
/// This test is meant to check that collision start & stop events are raised correctly by the client.
|
|
/// The expectation is that start & stop events are only raised if the client predicts that two entities will move into
|
|
/// contact. They do not get raised as a result of applying component states received from the server.
|
|
/// I.e., the assumption is that if a collision results in changes to data on a component, then that data will already
|
|
/// have been sent to clients in the component's state, so we don't want to "double count" collisions.
|
|
/// </summary>
|
|
internal sealed class CollisionPredictionTest : RobustIntegrationTest
|
|
{
|
|
private static readonly string Prototypes = @"
|
|
- type: entity
|
|
id: CollisionTest1
|
|
components:
|
|
- type: CollisionPredictionTest
|
|
- type: Physics
|
|
bodyType: Dynamic
|
|
sleepingAllowed: false
|
|
|
|
- type: entity
|
|
id: CollisionTest2
|
|
components:
|
|
- type: Physics
|
|
bodyType: Dynamic
|
|
sleepingAllowed: false
|
|
";
|
|
|
|
[Test]
|
|
[TestCase(true, true)]
|
|
[TestCase(true, false)]
|
|
[TestCase(false, true)]
|
|
[TestCase(false, false)]
|
|
public async Task TestCollisionPrediction(bool hard1, bool hard2)
|
|
{
|
|
var serverOpts = new ServerIntegrationOptions { Pool = false, ExtraPrototypes = Prototypes };
|
|
var clientOpts = new ClientIntegrationOptions { Pool = false, ExtraPrototypes = Prototypes };
|
|
var server = StartServer(serverOpts);
|
|
var client = StartClient(clientOpts);
|
|
|
|
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
|
|
var netMan = client.ResolveDependency<IClientNetManager>();
|
|
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
|
|
await server.WaitPost(() => server.CfgMan.SetCVar(CVars.NetPVS, false));
|
|
await client.WaitPost(() => netMan.ClientConnect(null!, 0, null!));
|
|
|
|
var sFix = server.System<FixtureSystem>();
|
|
var sPhys = server.System<SharedPhysicsSystem>();
|
|
var sSys = server.System<CollisionPredictionTestSystem>();
|
|
|
|
// Set up entities
|
|
EntityUid map = default;
|
|
EntityUid sEntity1 = default;
|
|
EntityUid sEntity2 = default;
|
|
MapCoordinates coords1 = default;
|
|
MapCoordinates coords2 = default;
|
|
await server.WaitPost(() =>
|
|
{
|
|
var radius = 0.25f;
|
|
map = server.System<SharedMapSystem>().CreateMap(out var mapId);
|
|
coords1 = new(default, mapId);
|
|
coords2 = new(Vector2.One, mapId);
|
|
sEntity1 = server.EntMan.Spawn("CollisionTest1", coords1);
|
|
sEntity2 = server.EntMan.Spawn("CollisionTest2", new MapCoordinates(coords2.Position + new Vector2(0, radius), mapId));
|
|
sFix.CreateFixture(sEntity1, "a", new Fixture(new PhysShapeCircle(radius), 1, 1, hard1));
|
|
sFix.CreateFixture(sEntity2, "a", new Fixture(new PhysShapeCircle(radius), 1, 1, hard2));
|
|
sPhys.SetCanCollide(sEntity1, true);
|
|
sPhys.SetCanCollide(sEntity2, true);
|
|
sPhys.SetAwake((sEntity1, server.EntMan.GetComponent<PhysicsComponent>(sEntity1)), true);
|
|
sPhys.SetAwake((sEntity2, server.EntMan.GetComponent<PhysicsComponent>(sEntity2)), true);
|
|
});
|
|
|
|
for (var i = 0; i < 10; i++)
|
|
{
|
|
await server.WaitRunTicks(1);
|
|
await client.WaitRunTicks(1);
|
|
}
|
|
|
|
await server.WaitPost(() => server.PlayerMan.JoinGame(server.PlayerMan.Sessions.First()));
|
|
|
|
for (var i = 0; i < 10; i++)
|
|
{
|
|
await server.WaitRunTicks(1);
|
|
await client.WaitRunTicks(1);
|
|
}
|
|
|
|
// Ensure client & server ticks are synced.
|
|
// Client runs 2 tick ahead
|
|
{
|
|
var targetDelta = 2;
|
|
var sTick = (int)server.Timing.CurTick.Value;
|
|
var cTick = (int)client.Timing.CurTick.Value;
|
|
var delta = cTick - sTick;
|
|
|
|
if (delta > targetDelta)
|
|
await server.WaitRunTicks(delta - targetDelta);
|
|
else if (delta < targetDelta)
|
|
await client.WaitRunTicks(targetDelta - delta);
|
|
|
|
sTick = (int)server.Timing.CurTick.Value;
|
|
cTick = (int)client.Timing.CurTick.Value;
|
|
delta = cTick - sTick;
|
|
Assert.That(delta, Is.EqualTo(targetDelta));
|
|
}
|
|
|
|
var cPhys = client.System<SharedPhysicsSystem>();
|
|
var cSys = client.System<CollisionPredictionTestSystem>();
|
|
|
|
void ResetSystem()
|
|
{
|
|
sSys.CollisionEnded = false;
|
|
sSys.CollisionStarted = false;
|
|
|
|
cSys.CollisionEnded = false;
|
|
cSys.CollisionStarted = false;
|
|
}
|
|
|
|
async Task Tick()
|
|
{
|
|
ResetSystem();
|
|
await server.WaitRunTicks(1);
|
|
await client.WaitRunTicks(1);
|
|
}
|
|
|
|
var nEntity1 = server.EntMan.GetNetEntity(sEntity1);
|
|
var nEntity2 = server.EntMan.GetNetEntity(sEntity2);
|
|
|
|
var cEntity1 = client.EntMan.GetEntity(nEntity1);
|
|
var cEntity2 = client.EntMan.GetEntity(nEntity2);
|
|
|
|
var sComp = server.EntMan.GetComponent<CollisionPredictionTestComponent>(sEntity1);
|
|
var cComp = client.EntMan.GetComponent<CollisionPredictionTestComponent>(cEntity1);
|
|
|
|
cPhys.UpdateIsPredicted(cEntity1);
|
|
|
|
// Initially, the objects are not colliding.
|
|
{
|
|
Assert.That(sComp.IsTouching, Is.False);
|
|
Assert.That(cComp.IsTouching, Is.False);
|
|
Assert.That(cComp.WasTouching, Is.False);
|
|
Assert.That(cComp.LastState, Is.False);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity1), Is.Empty);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity2), Is.Empty);
|
|
Assert.That(cPhys.GetContactingEntities(cEntity1), Is.Empty);
|
|
Assert.That(cPhys.GetContactingEntities(cEntity2), Is.Empty);
|
|
Assert.That(sSys.CollisionStarted, Is.False);
|
|
Assert.That(cSys.CollisionStarted, Is.False);
|
|
Assert.That(sSys.CollisionEnded, Is.False);
|
|
Assert.That(cSys.CollisionEnded, Is.False);
|
|
}
|
|
|
|
// We now simulate a predictive event that gets raised due to some client-side input, causing the entities to
|
|
// move and start colliding. Instead of setting up a proper input / keybind handler, The predictive event will
|
|
// just be raised in the system update method, which updates before the physics system does.
|
|
{
|
|
cSys.Ev = new CollisionTestMoveEvent(nEntity1, coords2);
|
|
await Tick();
|
|
Assert.That(sComp.IsTouching, Is.False);
|
|
Assert.That(cComp.IsTouching, Is.True);
|
|
Assert.That(cComp.WasTouching, Is.False);
|
|
Assert.That(cComp.LastState, Is.False);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity1), Is.Empty);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity2), Is.Empty);
|
|
Assert.That(cPhys.GetContactingEntities(cEntity1), Is.EquivalentTo(new []{ cEntity2 }));
|
|
Assert.That(cPhys.GetContactingEntities(cEntity2), Is.EquivalentTo(new []{ cEntity1 }));
|
|
Assert.That(sSys.CollisionStarted, Is.False);
|
|
Assert.That(cSys.CollisionStarted, Is.True);
|
|
Assert.That(sSys.CollisionEnded, Is.False);
|
|
Assert.That(cSys.CollisionEnded, Is.False);
|
|
}
|
|
|
|
// Run another tick. Client should reset states, and re-predict the event.
|
|
{
|
|
await Tick();
|
|
Assert.That(sComp.IsTouching, Is.False);
|
|
Assert.That(cComp.IsTouching, Is.True);
|
|
Assert.That(cComp.WasTouching, Is.True);
|
|
Assert.That(cComp.LastState, Is.False);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity1), Is.Empty);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity2), Is.Empty);
|
|
Assert.That(cPhys.GetContactingEntities(cEntity1), Is.EquivalentTo(new []{ cEntity2 }));
|
|
Assert.That(cPhys.GetContactingEntities(cEntity2), Is.EquivalentTo(new []{ cEntity1 }));
|
|
Assert.That(sSys.CollisionStarted, Is.False);
|
|
Assert.That(cSys.CollisionStarted, Is.True);
|
|
Assert.That(sSys.CollisionEnded, Is.False);
|
|
Assert.That(cSys.CollisionEnded, Is.False);
|
|
}
|
|
|
|
// Next tick the server should raise the event received from the client, which will raise a serve-side
|
|
// collide-start event.
|
|
{
|
|
await Tick();
|
|
Assert.That(sComp.IsTouching, Is.True);
|
|
Assert.That(cComp.IsTouching, Is.True);
|
|
Assert.That(cComp.StartTick, Is.EqualTo(sComp.StartTick));
|
|
Assert.That(cComp.WasTouching, Is.True);
|
|
Assert.That(cComp.LastState, Is.False);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity1), Is.EquivalentTo(new []{ sEntity2 }));
|
|
Assert.That(sPhys.GetContactingEntities(sEntity2), Is.EquivalentTo(new []{ sEntity1 }));
|
|
Assert.That(cPhys.GetContactingEntities(cEntity1), Is.EquivalentTo(new []{ cEntity2 }));
|
|
Assert.That(cPhys.GetContactingEntities(cEntity2), Is.EquivalentTo(new []{ cEntity1 }));
|
|
Assert.That(sSys.CollisionStarted, Is.True);
|
|
Assert.That(cSys.CollisionStarted, Is.True);
|
|
Assert.That(sSys.CollisionEnded, Is.False);
|
|
Assert.That(cSys.CollisionEnded, Is.False);
|
|
}
|
|
|
|
// The client will have received the server-state, but will take some time for it to leave the state buffer.
|
|
// In the meantime, the client will keep predicting that the collision will "starts"
|
|
for (var i = 0; i < 2; i ++)
|
|
{
|
|
await Tick();
|
|
Assert.That(sComp.IsTouching, Is.True);
|
|
Assert.That(cComp.IsTouching, Is.True);
|
|
Assert.That(cComp.StartTick, Is.EqualTo(sComp.StartTick));
|
|
Assert.That(cComp.WasTouching, Is.True);
|
|
Assert.That(cComp.LastState, Is.False);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity1), Is.EquivalentTo(new []{ sEntity2 }));
|
|
Assert.That(sPhys.GetContactingEntities(sEntity2), Is.EquivalentTo(new []{ sEntity1 }));
|
|
Assert.That(cPhys.GetContactingEntities(cEntity1), Is.EquivalentTo(new []{ cEntity2 }));
|
|
Assert.That(cPhys.GetContactingEntities(cEntity2), Is.EquivalentTo(new []{ cEntity1 }));
|
|
Assert.That(sSys.CollisionStarted, Is.False);
|
|
Assert.That(cSys.CollisionStarted, Is.True);
|
|
Assert.That(sSys.CollisionEnded, Is.False);
|
|
Assert.That(cSys.CollisionEnded, Is.False);
|
|
}
|
|
|
|
// Then in the next tick the client should apply the new server state, wherein the contacts were already touching.
|
|
// I.e., the contact start event never actually gets raised.
|
|
{
|
|
await Tick();
|
|
Assert.That(sComp.IsTouching, Is.True);
|
|
Assert.That(cComp.IsTouching, Is.True);
|
|
Assert.That(cComp.StartTick, Is.EqualTo(sComp.StartTick));
|
|
Assert.That(cComp.WasTouching, Is.False); // IsTouching gets resets to false before server state is applied
|
|
Assert.That(cComp.LastState, Is.True);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity1), Is.EquivalentTo(new []{ sEntity2 }));
|
|
Assert.That(sPhys.GetContactingEntities(sEntity2), Is.EquivalentTo(new []{ sEntity1 }));
|
|
Assert.That(cPhys.GetContactingEntities(cEntity1), Is.EquivalentTo(new []{ cEntity2 }));
|
|
Assert.That(cPhys.GetContactingEntities(cEntity2), Is.EquivalentTo(new []{ cEntity1 }));
|
|
Assert.That(sSys.CollisionStarted, Is.False);
|
|
Assert.That(cSys.CollisionStarted, Is.False);
|
|
Assert.That(sSys.CollisionEnded, Is.False);
|
|
Assert.That(cSys.CollisionEnded, Is.False);
|
|
}
|
|
|
|
// for the next few ticks, nothing should change
|
|
for (var i = 0; i < 10; i ++)
|
|
{
|
|
await Tick();
|
|
Assert.That(sComp.IsTouching, Is.True);
|
|
Assert.That(cComp.IsTouching, Is.True);
|
|
Assert.That(cComp.StartTick, Is.EqualTo(sComp.StartTick));
|
|
Assert.That(cComp.WasTouching, Is.False);
|
|
Assert.That(cComp.LastState, Is.True);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity1), Is.EquivalentTo(new []{ sEntity2 }));
|
|
Assert.That(sPhys.GetContactingEntities(sEntity2), Is.EquivalentTo(new []{ sEntity1 }));
|
|
Assert.That(cPhys.GetContactingEntities(cEntity1), Is.EquivalentTo(new []{ cEntity2 }));
|
|
Assert.That(cPhys.GetContactingEntities(cEntity2), Is.EquivalentTo(new []{ cEntity1 }));
|
|
Assert.That(sSys.CollisionStarted, Is.False);
|
|
Assert.That(cSys.CollisionStarted, Is.False);
|
|
Assert.That(sSys.CollisionEnded, Is.False);
|
|
Assert.That(cSys.CollisionEnded, Is.False);
|
|
}
|
|
|
|
// Next we move the entity away again, so the contact should stop
|
|
{
|
|
cSys.Ev = new CollisionTestMoveEvent(nEntity1, coords1);
|
|
await Tick();
|
|
Assert.That(sComp.IsTouching, Is.True);
|
|
Assert.That(cComp.IsTouching, Is.False);
|
|
Assert.That(cComp.StartTick, Is.EqualTo(sComp.StartTick));
|
|
Assert.That(cComp.WasTouching, Is.False);
|
|
Assert.That(cComp.LastState, Is.True);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity1), Is.EquivalentTo(new []{ sEntity2 }));
|
|
Assert.That(sPhys.GetContactingEntities(sEntity2), Is.EquivalentTo(new []{ sEntity1 }));
|
|
Assert.That(cPhys.GetContactingEntities(cEntity1), Is.Empty);
|
|
Assert.That(cPhys.GetContactingEntities(cEntity2), Is.Empty);
|
|
Assert.That(sSys.CollisionStarted, Is.False);
|
|
Assert.That(cSys.CollisionStarted, Is.False);
|
|
Assert.That(sSys.CollisionEnded, Is.False);
|
|
Assert.That(cSys.CollisionEnded, Is.True);
|
|
}
|
|
|
|
// Next tick, the client should reset to a state where the entities were touching, and then re-predict the stop-collide events
|
|
{
|
|
await Tick();
|
|
Assert.That(sComp.IsTouching, Is.True);
|
|
Assert.That(cComp.IsTouching, Is.False);
|
|
Assert.That(cComp.StartTick, Is.EqualTo(sComp.StartTick));
|
|
Assert.That(cComp.WasTouching, Is.False);
|
|
Assert.That(cComp.LastState, Is.True);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity1), Is.EquivalentTo(new []{ sEntity2 }));
|
|
Assert.That(sPhys.GetContactingEntities(sEntity2), Is.EquivalentTo(new []{ sEntity1 }));
|
|
Assert.That(cPhys.GetContactingEntities(cEntity1), Is.Empty);
|
|
Assert.That(cPhys.GetContactingEntities(cEntity2), Is.Empty);
|
|
Assert.That(sSys.CollisionStarted, Is.False);
|
|
Assert.That(cSys.CollisionStarted, Is.False);
|
|
Assert.That(sSys.CollisionEnded, Is.False);
|
|
Assert.That(cSys.CollisionEnded, Is.True);
|
|
}
|
|
|
|
// Next, the server should receive the networked event
|
|
{
|
|
await Tick();
|
|
Assert.That(sComp.IsTouching, Is.False);
|
|
Assert.That(cComp.IsTouching, Is.False);
|
|
Assert.That(cComp.StartTick, Is.EqualTo(sComp.StartTick));
|
|
Assert.That(cComp.StopTick, Is.EqualTo(sComp.StopTick));
|
|
Assert.That(cComp.WasTouching, Is.False);
|
|
Assert.That(cComp.LastState, Is.True);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity1), Is.Empty);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity2), Is.Empty);
|
|
Assert.That(cPhys.GetContactingEntities(cEntity1), Is.Empty);
|
|
Assert.That(cPhys.GetContactingEntities(cEntity2), Is.Empty);
|
|
Assert.That(sSys.CollisionStarted, Is.False);
|
|
Assert.That(cSys.CollisionStarted, Is.False);
|
|
Assert.That(sSys.CollisionEnded, Is.True);
|
|
Assert.That(cSys.CollisionEnded, Is.True);
|
|
}
|
|
|
|
// nothing changes while waiting for the client to apply the new server state
|
|
for (var i = 0; i < 2; i ++)
|
|
{
|
|
await Tick();
|
|
Assert.That(sComp.IsTouching, Is.False);
|
|
Assert.That(cComp.IsTouching, Is.False);
|
|
Assert.That(cComp.StartTick, Is.EqualTo(sComp.StartTick));
|
|
Assert.That(cComp.StopTick, Is.EqualTo(sComp.StopTick));
|
|
Assert.That(cComp.WasTouching, Is.False);
|
|
Assert.That(cComp.LastState, Is.True);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity1), Is.Empty);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity2), Is.Empty);
|
|
Assert.That(cPhys.GetContactingEntities(cEntity1), Is.Empty);
|
|
Assert.That(cPhys.GetContactingEntities(cEntity2), Is.Empty);
|
|
Assert.That(sSys.CollisionStarted, Is.False);
|
|
Assert.That(cSys.CollisionStarted, Is.False);
|
|
Assert.That(sSys.CollisionEnded, Is.False);
|
|
Assert.That(cSys.CollisionEnded, Is.True);
|
|
}
|
|
|
|
// And then the client should apply the new server state
|
|
{
|
|
await Tick();
|
|
Assert.That(sComp.IsTouching, Is.False);
|
|
Assert.That(cComp.IsTouching, Is.False);
|
|
Assert.That(cComp.StartTick, Is.EqualTo(sComp.StartTick));
|
|
Assert.That(cComp.StopTick, Is.EqualTo(sComp.StopTick));
|
|
Assert.That(cComp.WasTouching, Is.True);
|
|
Assert.That(cComp.LastState, Is.False);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity1), Is.Empty);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity2), Is.Empty);
|
|
Assert.That(cPhys.GetContactingEntities(cEntity1), Is.Empty);
|
|
Assert.That(cPhys.GetContactingEntities(cEntity2), Is.Empty);
|
|
Assert.That(sSys.CollisionStarted, Is.False);
|
|
Assert.That(cSys.CollisionStarted, Is.False);
|
|
Assert.That(sSys.CollisionEnded, Is.False);
|
|
Assert.That(cSys.CollisionEnded, Is.False);
|
|
}
|
|
|
|
// Nothing should change in the next few ticks
|
|
for (var i = 0; i < 10; i ++)
|
|
{
|
|
await Tick();
|
|
Assert.That(sComp.IsTouching, Is.False);
|
|
Assert.That(cComp.IsTouching, Is.False);
|
|
Assert.That(cComp.StartTick, Is.EqualTo(sComp.StartTick));
|
|
Assert.That(cComp.StopTick, Is.EqualTo(sComp.StopTick));
|
|
Assert.That(cComp.WasTouching, Is.True);
|
|
Assert.That(cComp.LastState, Is.False);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity1), Is.Empty);
|
|
Assert.That(sPhys.GetContactingEntities(sEntity2), Is.Empty);
|
|
Assert.That(cPhys.GetContactingEntities(cEntity1), Is.Empty);
|
|
Assert.That(cPhys.GetContactingEntities(cEntity2), Is.Empty);
|
|
Assert.That(sSys.CollisionStarted, Is.False);
|
|
Assert.That(cSys.CollisionStarted, Is.False);
|
|
Assert.That(sSys.CollisionEnded, Is.False);
|
|
Assert.That(cSys.CollisionEnded, Is.False);
|
|
}
|
|
|
|
await client.WaitPost(() => netMan.ClientDisconnect(""));
|
|
await server.WaitRunTicks(5);
|
|
await client.WaitRunTicks(5);
|
|
}
|
|
}
|
|
|
|
[RegisterComponent, NetworkedComponent]
|
|
internal sealed partial class CollisionPredictionTestComponent : Component
|
|
{
|
|
public bool IsTouching;
|
|
public bool WasTouching;
|
|
public bool LastState;
|
|
public GameTick StartTick;
|
|
public GameTick StopTick;
|
|
|
|
[Serializable, NetSerializable]
|
|
internal sealed class State(bool isTouching) : ComponentState
|
|
{
|
|
public bool IsTouching = isTouching;
|
|
}
|
|
}
|
|
|
|
[Serializable, NetSerializable]
|
|
internal sealed class CollisionTestMoveEvent(NetEntity ent, MapCoordinates coords) : EntityEventArgs
|
|
{
|
|
public NetEntity Ent = ent;
|
|
public MapCoordinates Coords = coords;
|
|
}
|
|
|
|
internal sealed class CollisionPredictionTestSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
|
[Dependency] private readonly IGameTiming _timing = default!;
|
|
|
|
public bool CollisionStarted;
|
|
public bool CollisionEnded;
|
|
|
|
public override void Initialize()
|
|
{
|
|
SubscribeLocalEvent<CollisionPredictionTestComponent, ComponentHandleState>(OnHandleState);
|
|
SubscribeLocalEvent<CollisionPredictionTestComponent, ComponentGetState>(OnGetState);
|
|
SubscribeLocalEvent<CollisionPredictionTestComponent, StartCollideEvent>(OnStartCollide);
|
|
SubscribeLocalEvent<CollisionPredictionTestComponent, EndCollideEvent>(OnEndCollide);
|
|
SubscribeLocalEvent<CollisionPredictionTestComponent, UpdateIsPredictedEvent>(OnIsPredicted);
|
|
SubscribeAllEvent<CollisionTestMoveEvent>(OnMove);
|
|
|
|
// Updates before physics to simulate input events.
|
|
// inputs are processed before systems update, but I CBF setting up a proper input / keybinding.
|
|
UpdatesBefore.Add(typeof(SharedPhysicsSystem));
|
|
}
|
|
|
|
public CollisionTestMoveEvent? Ev;
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
if (Ev == null || !_timing.IsFirstTimePredicted)
|
|
return;
|
|
|
|
RaisePredictiveEvent(Ev);
|
|
Ev = null;
|
|
}
|
|
|
|
private void OnIsPredicted(Entity<CollisionPredictionTestComponent> ent, ref UpdateIsPredictedEvent args)
|
|
{
|
|
args.IsPredicted = true;
|
|
}
|
|
|
|
private void OnMove(CollisionTestMoveEvent ev)
|
|
{
|
|
_xform.SetMapCoordinates(GetEntity(ev.Ent), ev.Coords);
|
|
}
|
|
|
|
private void OnEndCollide(Entity<CollisionPredictionTestComponent> ent, ref EndCollideEvent args)
|
|
{
|
|
// TODO PHYSICS Collision Mispredicts
|
|
// Currently the client will raise collision start/stop events multiple times for each collision
|
|
// If this ever gets fixed, re-add the assert:
|
|
// Assert.That(ent.Comp.IsTouching, Is.True);
|
|
if (!ent.Comp.IsTouching)
|
|
return;
|
|
|
|
Assert.That(CollisionEnded, Is.False);
|
|
ent.Comp.StopTick = _timing.CurTick;
|
|
ent.Comp.IsTouching = false;
|
|
CollisionEnded = true;
|
|
Dirty(ent);
|
|
}
|
|
|
|
private void OnStartCollide(Entity<CollisionPredictionTestComponent> ent, ref StartCollideEvent args)
|
|
{
|
|
// TODO PHYSICS Collision Mispredicts
|
|
// Currently the client will raise collision start/stop events multiple times for each collision
|
|
// If this ever gets fixed, re-add the assert:
|
|
// Assert.That(ent.Comp.IsTouching, Is.False);
|
|
if (ent.Comp.IsTouching)
|
|
return;
|
|
|
|
Assert.That(CollisionStarted, Is.False);
|
|
ent.Comp.StartTick = _timing.CurTick;
|
|
ent.Comp.IsTouching = true;
|
|
CollisionStarted = true;
|
|
Dirty(ent);
|
|
}
|
|
|
|
private void OnGetState(Entity<CollisionPredictionTestComponent> ent, ref ComponentGetState args)
|
|
{
|
|
args.State = new CollisionPredictionTestComponent.State(ent.Comp.IsTouching);
|
|
}
|
|
|
|
private void OnHandleState(Entity<CollisionPredictionTestComponent> ent, ref ComponentHandleState args)
|
|
{
|
|
if (args.Current is not CollisionPredictionTestComponent.State state)
|
|
return;
|
|
|
|
ent.Comp.WasTouching = ent.Comp.IsTouching;
|
|
ent.Comp.LastState = state.IsTouching;
|
|
ent.Comp.IsTouching = state.IsTouching;
|
|
}
|
|
}
|