Add some tests and fix some miscellaneous bugs (#22836)
* Add some tests and fix some bugs * Add more helper methods * remove submodule * fix merge * also fix DirtyAll() * poke
This commit is contained in:
@@ -773,9 +773,10 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
if (_actionsSystem == null)
|
if (_actionsSystem == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (var i = 0; i < assignments.Count; i++)
|
_actions.Clear();
|
||||||
|
foreach (var assign in assignments)
|
||||||
{
|
{
|
||||||
_actions[i] = assignments[i].ActionId;
|
_actions.Add(assign.ActionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_container?.SetActionData(_actionsSystem, _actions.ToArray());
|
_container?.SetActionData(_actionsSystem, _actions.ToArray());
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ public sealed partial class TestPair
|
|||||||
public RobustIntegrationTest.ServerIntegrationInstance Server { get; private set; } = default!;
|
public RobustIntegrationTest.ServerIntegrationInstance Server { get; private set; } = default!;
|
||||||
public RobustIntegrationTest.ClientIntegrationInstance Client { get; private set; } = default!;
|
public RobustIntegrationTest.ClientIntegrationInstance Client { get; private set; } = default!;
|
||||||
|
|
||||||
|
public void Deconstruct(
|
||||||
|
out RobustIntegrationTest.ServerIntegrationInstance server,
|
||||||
|
out RobustIntegrationTest.ClientIntegrationInstance client)
|
||||||
|
{
|
||||||
|
server = Server;
|
||||||
|
client = Client;
|
||||||
|
}
|
||||||
|
|
||||||
public ICommonSession? Player => Server.PlayerMan.Sessions.FirstOrDefault();
|
public ICommonSession? Player => Server.PlayerMan.Sessions.FirstOrDefault();
|
||||||
public ContentPlayerData? PlayerData => Player?.Data.ContentData();
|
public ContentPlayerData? PlayerData => Player?.Data.ContentData();
|
||||||
|
|
||||||
@@ -78,6 +86,8 @@ public sealed partial class TestPair
|
|||||||
public void Kill()
|
public void Kill()
|
||||||
{
|
{
|
||||||
State = PairState.Dead;
|
State = PairState.Dead;
|
||||||
|
ServerLogHandler.ShuttingDown = true;
|
||||||
|
ClientLogHandler.ShuttingDown = true;
|
||||||
Server.Dispose();
|
Server.Dispose();
|
||||||
Client.Dispose();
|
Client.Dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,15 @@ public sealed class PoolTestLogHandler : ILogHandler
|
|||||||
_prefix = prefix != null ? $"{prefix}: " : "";
|
_prefix = prefix != null ? $"{prefix}: " : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ShuttingDown;
|
||||||
|
|
||||||
public void Log(string sawmillName, LogEvent message)
|
public void Log(string sawmillName, LogEvent message)
|
||||||
{
|
{
|
||||||
|
var level = message.Level.ToRobust();
|
||||||
|
|
||||||
|
if (ShuttingDown && (FailureLevel == null || level < FailureLevel))
|
||||||
|
return;
|
||||||
|
|
||||||
if (ActiveContext is not { } testContext)
|
if (ActiveContext is not { } testContext)
|
||||||
{
|
{
|
||||||
// If this gets hit it means something is logging to this instance while it's "between" tests.
|
// If this gets hit it means something is logging to this instance while it's "between" tests.
|
||||||
@@ -45,7 +52,6 @@ public sealed class PoolTestLogHandler : ILogHandler
|
|||||||
throw new InvalidOperationException("Log to pool test log handler without active test context");
|
throw new InvalidOperationException("Log to pool test log handler without active test context");
|
||||||
}
|
}
|
||||||
|
|
||||||
var level = message.Level.ToRobust();
|
|
||||||
var name = LogMessage.LogLevelToName(level);
|
var name = LogMessage.LogLevelToName(level);
|
||||||
var seconds = _stopwatch.Elapsed.TotalSeconds;
|
var seconds = _stopwatch.Elapsed.TotalSeconds;
|
||||||
var rendered = message.RenderMessage();
|
var rendered = message.RenderMessage();
|
||||||
|
|||||||
@@ -38,10 +38,10 @@ public sealed class DoAfterCancellationTests : InteractionTest
|
|||||||
AssertAnchored(false);
|
AssertAnchored(false);
|
||||||
|
|
||||||
// Repeat for screwdriver interaction.
|
// Repeat for screwdriver interaction.
|
||||||
AssertDeleted(false);
|
AssertExists();
|
||||||
await Interact(Screw, awaitDoAfters: false);
|
await Interact(Screw, awaitDoAfters: false);
|
||||||
await CancelDoAfters();
|
await CancelDoAfters();
|
||||||
AssertDeleted(false);
|
AssertExists();
|
||||||
await Interact(Screw);
|
await Interact(Screw);
|
||||||
AssertDeleted();
|
AssertDeleted();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,9 +134,9 @@ public abstract partial class InteractionTest
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Automatically enables welders.
|
/// Automatically enables welders.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected async Task<EntityUid?> PlaceInHands(string? id, int quantity = 1, bool enableWelder = true)
|
protected async Task<NetEntity> PlaceInHands(string id, int quantity = 1, bool enableWelder = true)
|
||||||
{
|
{
|
||||||
return await PlaceInHands(id == null ? null : (id, quantity), enableWelder);
|
return await PlaceInHands((id, quantity), enableWelder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -145,7 +145,7 @@ public abstract partial class InteractionTest
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Automatically enables welders.
|
/// Automatically enables welders.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected async Task<EntityUid?> PlaceInHands(EntitySpecifier? entity, bool enableWelder = true)
|
protected async Task<NetEntity> PlaceInHands(EntitySpecifier entity, bool enableWelder = true)
|
||||||
{
|
{
|
||||||
if (Hands.ActiveHand == null)
|
if (Hands.ActiveHand == null)
|
||||||
{
|
{
|
||||||
@@ -153,15 +153,9 @@ public abstract partial class InteractionTest
|
|||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assert.That(!string.IsNullOrWhiteSpace(entity.Prototype));
|
||||||
await DeleteHeldEntity();
|
await DeleteHeldEntity();
|
||||||
|
|
||||||
if (entity == null || string.IsNullOrWhiteSpace(entity.Prototype))
|
|
||||||
{
|
|
||||||
await RunTicks(1);
|
|
||||||
Assert.That(Hands.ActiveHandEntity, Is.Null);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// spawn and pick up the new item
|
// spawn and pick up the new item
|
||||||
var item = await SpawnEntity(entity, SEntMan.GetCoordinates(PlayerCoords));
|
var item = await SpawnEntity(entity, SEntMan.GetCoordinates(PlayerCoords));
|
||||||
ItemToggleComponent? itemToggle = null;
|
ItemToggleComponent? itemToggle = null;
|
||||||
@@ -184,7 +178,7 @@ public abstract partial class InteractionTest
|
|||||||
if (enableWelder && itemToggle != null)
|
if (enableWelder && itemToggle != null)
|
||||||
Assert.That(itemToggle.Activated);
|
Assert.That(itemToggle.Activated);
|
||||||
|
|
||||||
return item;
|
return SEntMan.GetNetEntity(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -330,6 +324,17 @@ public abstract partial class InteractionTest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throw the currently held entity. Defaults to targeting the current <see cref="TargetCoords"/>
|
||||||
|
/// </summary>
|
||||||
|
protected async Task<bool> ThrowItem(NetCoordinates? target = null, float minDistance = 4)
|
||||||
|
{
|
||||||
|
var actualTarget = SEntMan.GetCoordinates(target ?? TargetCoords);
|
||||||
|
var result = false;
|
||||||
|
await Server.WaitPost(() => result = HandSys.ThrowHeldItem(SEntMan.GetEntity(Player), actualTarget, minDistance));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -483,7 +488,7 @@ public abstract partial class InteractionTest
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void AssertDeleted(bool deleted = true, NetEntity? target = null)
|
protected void AssertDeleted(NetEntity? target = null)
|
||||||
{
|
{
|
||||||
target ??= Target;
|
target ??= Target;
|
||||||
if (target == null)
|
if (target == null)
|
||||||
@@ -494,8 +499,24 @@ public abstract partial class InteractionTest
|
|||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(SEntMan.Deleted(SEntMan.GetEntity(target)), Is.EqualTo(deleted));
|
Assert.That(SEntMan.Deleted(SEntMan.GetEntity(target)));
|
||||||
Assert.That(CEntMan.Deleted(CEntMan.GetEntity(target)), Is.EqualTo(deleted));
|
Assert.That(CEntMan.Deleted(CEntMan.GetEntity(target)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void AssertExists(NetEntity? target = null)
|
||||||
|
{
|
||||||
|
target ??= Target;
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
Assert.Fail("No target specified");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(SEntMan.EntityExists(SEntMan.GetEntity(target)));
|
||||||
|
Assert.That(CEntMan.EntityExists(CEntMan.GetEntity(target)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,6 +754,11 @@ public abstract partial class InteractionTest
|
|||||||
await RunTicks(5);
|
await RunTicks(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Task Delete(NetEntity nuid)
|
||||||
|
{
|
||||||
|
return Delete(SEntMan.GetEntity(nuid));
|
||||||
|
}
|
||||||
|
|
||||||
#region Time/Tick managment
|
#region Time/Tick managment
|
||||||
|
|
||||||
protected async Task RunTicks(int ticks)
|
protected async Task RunTicks(int ticks)
|
||||||
@@ -1064,4 +1090,35 @@ public abstract partial class InteractionTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Networking
|
||||||
|
|
||||||
|
protected EntityUid ToServer(NetEntity nent) => SEntMan.GetEntity(nent);
|
||||||
|
protected EntityUid ToClient(NetEntity nent) => CEntMan.GetEntity(nent);
|
||||||
|
protected EntityUid? ToServer(NetEntity? nent) => SEntMan.GetEntity(nent);
|
||||||
|
protected EntityUid? ToClient(NetEntity? nent) => CEntMan.GetEntity(nent);
|
||||||
|
protected EntityUid ToServer(EntityUid cuid) => SEntMan.GetEntity(CEntMan.GetNetEntity(cuid));
|
||||||
|
protected EntityUid ToClient(EntityUid cuid) => CEntMan.GetEntity(SEntMan.GetNetEntity(cuid));
|
||||||
|
protected EntityUid? ToServer(EntityUid? cuid) => SEntMan.GetEntity(CEntMan.GetNetEntity(cuid));
|
||||||
|
protected EntityUid? ToClient(EntityUid? cuid) => CEntMan.GetEntity(SEntMan.GetNetEntity(cuid));
|
||||||
|
|
||||||
|
protected EntityCoordinates ToServer(NetCoordinates coords) => SEntMan.GetCoordinates(coords);
|
||||||
|
protected EntityCoordinates ToClient(NetCoordinates coords) => CEntMan.GetCoordinates(coords);
|
||||||
|
protected EntityCoordinates? ToServer(NetCoordinates? coords) => SEntMan.GetCoordinates(coords);
|
||||||
|
protected EntityCoordinates? ToClient(NetCoordinates? coords) => CEntMan.GetCoordinates(coords);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Metadata & Transforms
|
||||||
|
|
||||||
|
protected MetaDataComponent Meta(NetEntity uid) => Meta(ToServer(uid));
|
||||||
|
protected MetaDataComponent Meta(EntityUid uid) => SEntMan.GetComponent<MetaDataComponent>(uid);
|
||||||
|
|
||||||
|
protected TransformComponent Xform(NetEntity uid) => Xform(ToServer(uid));
|
||||||
|
protected TransformComponent Xform(EntityUid uid) => SEntMan.GetComponent<TransformComponent>(uid);
|
||||||
|
|
||||||
|
protected EntityCoordinates Position(NetEntity uid) => Position(ToServer(uid));
|
||||||
|
protected EntityCoordinates Position(EntityUid uid) => Xform(uid).Coordinates;
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ using Content.Client.Construction;
|
|||||||
using Content.Client.Examine;
|
using Content.Client.Examine;
|
||||||
using Content.IntegrationTests.Pair;
|
using Content.IntegrationTests.Pair;
|
||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
|
using Content.Server.Hands.Systems;
|
||||||
using Content.Server.Stack;
|
using Content.Server.Stack;
|
||||||
using Content.Server.Tools;
|
using Content.Server.Tools;
|
||||||
using Content.Shared.Body.Part;
|
using Content.Shared.Body.Part;
|
||||||
using Content.Shared.DoAfter;
|
using Content.Shared.DoAfter;
|
||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
using Content.Shared.Hands.EntitySystems;
|
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Server.Item;
|
using Content.Server.Item;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
@@ -66,6 +66,9 @@ public abstract partial class InteractionTest
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected NetEntity Player;
|
protected NetEntity Player;
|
||||||
|
|
||||||
|
protected EntityUid SPlayer => ToServer(Player);
|
||||||
|
protected EntityUid CPlayer => ToClient(Player);
|
||||||
|
|
||||||
protected ICommonSession ClientSession = default!;
|
protected ICommonSession ClientSession = default!;
|
||||||
protected ICommonSession ServerSession = default!;
|
protected ICommonSession ServerSession = default!;
|
||||||
|
|
||||||
@@ -81,6 +84,9 @@ public abstract partial class InteractionTest
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected NetEntity? Target;
|
protected NetEntity? Target;
|
||||||
|
|
||||||
|
protected EntityUid? STarget => ToServer(Target);
|
||||||
|
protected EntityUid? CTarget => ToClient(Target);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When attempting to start construction, this is the client-side ID of the construction ghost.
|
/// When attempting to start construction, this is the client-side ID of the construction ghost.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -93,7 +99,7 @@ public abstract partial class InteractionTest
|
|||||||
protected IPrototypeManager ProtoMan = default!;
|
protected IPrototypeManager ProtoMan = default!;
|
||||||
protected IGameTiming STiming = default!;
|
protected IGameTiming STiming = default!;
|
||||||
protected IComponentFactory Factory = default!;
|
protected IComponentFactory Factory = default!;
|
||||||
protected SharedHandsSystem HandSys = default!;
|
protected HandsSystem HandSys = default!;
|
||||||
protected StackSystem Stack = default!;
|
protected StackSystem Stack = default!;
|
||||||
protected SharedInteractionSystem InteractSys = default!;
|
protected SharedInteractionSystem InteractSys = default!;
|
||||||
protected Content.Server.Construction.ConstructionSystem SConstruction = default!;
|
protected Content.Server.Construction.ConstructionSystem SConstruction = default!;
|
||||||
@@ -152,7 +158,7 @@ public abstract partial class InteractionTest
|
|||||||
ProtoMan = Server.ResolveDependency<IPrototypeManager>();
|
ProtoMan = Server.ResolveDependency<IPrototypeManager>();
|
||||||
Factory = Server.ResolveDependency<IComponentFactory>();
|
Factory = Server.ResolveDependency<IComponentFactory>();
|
||||||
STiming = Server.ResolveDependency<IGameTiming>();
|
STiming = Server.ResolveDependency<IGameTiming>();
|
||||||
HandSys = SEntMan.System<SharedHandsSystem>();
|
HandSys = SEntMan.System<HandsSystem>();
|
||||||
InteractSys = SEntMan.System<SharedInteractionSystem>();
|
InteractSys = SEntMan.System<SharedInteractionSystem>();
|
||||||
ToolSys = SEntMan.System<ToolSystem>();
|
ToolSys = SEntMan.System<ToolSystem>();
|
||||||
ItemToggleSys = SEntMan.System<SharedItemToggleSystem>();
|
ItemToggleSys = SEntMan.System<SharedItemToggleSystem>();
|
||||||
|
|||||||
51
Content.IntegrationTests/Tests/Networking/PvsCommandTest.cs
Normal file
51
Content.IntegrationTests/Tests/Networking/PvsCommandTest.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests.Networking;
|
||||||
|
|
||||||
|
[TestFixture]
|
||||||
|
public sealed class PvsCommandTest
|
||||||
|
{
|
||||||
|
public static EntProtoId TestEnt = "MobHuman";
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TestPvsCommands()
|
||||||
|
{
|
||||||
|
await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, DummyTicker = false});
|
||||||
|
var (server, client) = pair;
|
||||||
|
await pair.RunTicksSync(5);
|
||||||
|
|
||||||
|
// Spawn a complex entity.
|
||||||
|
EntityUid entity = default;
|
||||||
|
await server.WaitPost(() => entity = server.EntMan.Spawn(TestEnt));
|
||||||
|
await pair.RunTicksSync(5);
|
||||||
|
|
||||||
|
// Check that the client has a variety pf entities.
|
||||||
|
Assert.That(client.EntMan.EntityCount, Is.GreaterThan(0));
|
||||||
|
Assert.That(client.EntMan.Count<MapComponent>, Is.GreaterThan(0));
|
||||||
|
Assert.That(client.EntMan.Count<MapGridComponent>, Is.GreaterThan(0));
|
||||||
|
|
||||||
|
var meta = client.MetaData(pair.ToClientUid(entity));
|
||||||
|
var lastApplied = meta.LastStateApplied;
|
||||||
|
|
||||||
|
// Dirty all entities
|
||||||
|
await server.ExecuteCommand("dirty");
|
||||||
|
await pair.RunTicksSync(5);
|
||||||
|
Assert.That(meta.LastStateApplied, Is.GreaterThan(lastApplied));
|
||||||
|
await pair.RunTicksSync(5);
|
||||||
|
|
||||||
|
// Do a client-side full state reset
|
||||||
|
await client.ExecuteCommand("resetallents");
|
||||||
|
await pair.RunTicksSync(5);
|
||||||
|
|
||||||
|
// Request a full server state.
|
||||||
|
lastApplied = meta.LastStateApplied;
|
||||||
|
await client.ExecuteCommand("fullstatereset");
|
||||||
|
await pair.RunTicksSync(10);
|
||||||
|
Assert.That(meta.LastStateApplied, Is.GreaterThan(lastApplied));
|
||||||
|
|
||||||
|
await server.WaitPost(() => server.EntMan.DeleteEntity(entity));
|
||||||
|
await pair.CleanReturnAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
111
Content.IntegrationTests/Tests/Physics/ItemThrowingTest.cs
Normal file
111
Content.IntegrationTests/Tests/Physics/ItemThrowingTest.cs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
using Content.IntegrationTests.Tests.Interaction;
|
||||||
|
using Content.Shared.Damage.Components;
|
||||||
|
using Content.Shared.Throwing;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Physics.Components;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests.Physics;
|
||||||
|
|
||||||
|
public sealed class ItemThrowingTest : InteractionTest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Check that an egg breaks when thrown at a wall.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
[TestOf(typeof(ThrownItemComponent))]
|
||||||
|
[TestOf(typeof(DamageOnHighSpeedImpactComponent))]
|
||||||
|
public async Task TestThrownEggBreaks()
|
||||||
|
{
|
||||||
|
// Setup entities
|
||||||
|
var egg = await PlaceInHands("FoodEgg");
|
||||||
|
await SpawnTarget("WallSolid");
|
||||||
|
await RunTicks(5);
|
||||||
|
AssertExists(egg);
|
||||||
|
|
||||||
|
// Currently not a "thrown" item.
|
||||||
|
AssertComp<ThrownItemComponent>(hasComp: false, egg);
|
||||||
|
Assert.That(Comp<PhysicsComponent>(egg).BodyStatus, Is.Not.EqualTo(BodyStatus.InAir));
|
||||||
|
|
||||||
|
// Throw it.
|
||||||
|
await ThrowItem();
|
||||||
|
await RunTicks(1);
|
||||||
|
AssertExists(egg);
|
||||||
|
AssertComp<ThrownItemComponent>(hasComp: true, egg);
|
||||||
|
Assert.That(Comp<PhysicsComponent>(egg).BodyStatus, Is.EqualTo(BodyStatus.InAir));
|
||||||
|
|
||||||
|
// Splat
|
||||||
|
await RunTicks(30);
|
||||||
|
AssertDeleted(egg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check that an egg thrown into space continues to be an egg.
|
||||||
|
/// I.e., verify that the deletions that happen in the other two tests aren't coincidental.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
//[TestOf(typeof(Egg))]
|
||||||
|
public async Task TestEggIsEgg()
|
||||||
|
{
|
||||||
|
// Setup entities
|
||||||
|
var egg = await PlaceInHands("FoodEgg");
|
||||||
|
await RunTicks(5);
|
||||||
|
AssertExists(egg);
|
||||||
|
|
||||||
|
// Currently not a "thrown" item.
|
||||||
|
AssertComp<ThrownItemComponent>(hasComp: false, egg);
|
||||||
|
Assert.That(Comp<PhysicsComponent>(egg).BodyStatus, Is.Not.EqualTo(BodyStatus.InAir));
|
||||||
|
|
||||||
|
// Throw it
|
||||||
|
await ThrowItem();
|
||||||
|
await RunTicks(5);
|
||||||
|
AssertExists(egg);
|
||||||
|
AssertComp<ThrownItemComponent>(hasComp: true, egg);
|
||||||
|
Assert.That(Comp<PhysicsComponent>(egg).BodyStatus, Is.EqualTo(BodyStatus.InAir));
|
||||||
|
|
||||||
|
// Wait a while
|
||||||
|
await RunTicks(60);
|
||||||
|
|
||||||
|
// Egg is egg
|
||||||
|
AssertExists(egg);
|
||||||
|
AssertPrototype("FoodEgg", egg);
|
||||||
|
AssertComp<ThrownItemComponent>(hasComp: false, egg);
|
||||||
|
Assert.That(Comp<PhysicsComponent>(egg).BodyStatus, Is.Not.EqualTo(BodyStatus.InAir));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check that a physics can handle deleting a thrown entity. As to why this exists, see
|
||||||
|
/// https://github.com/space-wizards/RobustToolbox/pull/4746
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
[TestOf(typeof(ThrownItemComponent))]
|
||||||
|
[TestOf(typeof(PhysicsComponent))]
|
||||||
|
public async Task TestDeleteThrownItem()
|
||||||
|
{
|
||||||
|
// Setup entities
|
||||||
|
var pen = await PlaceInHands("Pen");
|
||||||
|
var physics = Comp<PhysicsComponent>(pen);
|
||||||
|
await RunTicks(5);
|
||||||
|
AssertExists(pen);
|
||||||
|
|
||||||
|
// Currently not a "thrown" item.
|
||||||
|
AssertComp<ThrownItemComponent>(hasComp: false, pen);
|
||||||
|
Assert.That(physics.BodyStatus, Is.Not.EqualTo(BodyStatus.InAir));
|
||||||
|
|
||||||
|
// Throw it
|
||||||
|
await ThrowItem();
|
||||||
|
await RunTicks(5);
|
||||||
|
AssertExists(pen);
|
||||||
|
AssertComp<ThrownItemComponent>(hasComp: true, pen);
|
||||||
|
Assert.That(physics.BodyStatus, Is.EqualTo(BodyStatus.InAir));
|
||||||
|
Assert.That(physics.CanCollide);
|
||||||
|
|
||||||
|
// Attempt to make it sleep mid-air. This happens automatically due to the sleep timer, but we just do it manually.
|
||||||
|
await Server.WaitPost(() => Server.System<PhysicsSystem>().SetAwake((ToServer(pen), physics), false));
|
||||||
|
|
||||||
|
// Then try and delete it
|
||||||
|
await Delete(pen);
|
||||||
|
await RunTicks(5);
|
||||||
|
AssertDeleted(pen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -38,9 +38,9 @@ public sealed class DirtyCommand : IConsoleCommand
|
|||||||
|
|
||||||
private static void DirtyAll(IEntityManager manager, EntityUid entityUid)
|
private static void DirtyAll(IEntityManager manager, EntityUid entityUid)
|
||||||
{
|
{
|
||||||
foreach (var component in manager.GetComponents(entityUid))
|
foreach (var component in manager.GetNetComponents(entityUid))
|
||||||
{
|
{
|
||||||
manager.Dirty((Component)component);
|
manager.Dirty(entityUid, component.component);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace Content.Server.Hands.Systems
|
|||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<HandsComponent, DisarmedEvent>(OnDisarmed, before: new[] { typeof(StunSystem) });
|
SubscribeLocalEvent<HandsComponent, DisarmedEvent>(OnDisarmed, before: new[] {typeof(StunSystem)});
|
||||||
|
|
||||||
SubscribeLocalEvent<HandsComponent, PullStartedMessage>(HandlePullStarted);
|
SubscribeLocalEvent<HandsComponent, PullStartedMessage>(HandlePullStarted);
|
||||||
SubscribeLocalEvent<HandsComponent, PullStoppedMessage>(HandlePullStopped);
|
SubscribeLocalEvent<HandsComponent, PullStoppedMessage>(HandlePullStopped);
|
||||||
@@ -67,7 +67,7 @@ namespace Content.Server.Hands.Systems
|
|||||||
{
|
{
|
||||||
foreach (var hand in ent.Comp.Hands.Values)
|
foreach (var hand in ent.Comp.Hands.Values)
|
||||||
{
|
{
|
||||||
if (hand.HeldEntity is {} uid)
|
if (hand.HeldEntity is { } uid)
|
||||||
args.Contents.Add(uid);
|
args.Contents.Add(uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,8 @@ namespace Content.Server.Hands.Systems
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Break any pulls
|
// Break any pulls
|
||||||
if (TryComp(uid, out SharedPullerComponent? puller) && puller.Pulling is EntityUid pulled && TryComp(pulled, out SharedPullableComponent? pullable))
|
if (TryComp(uid, out SharedPullerComponent? puller) && puller.Pulling is EntityUid pulled &&
|
||||||
|
TryComp(pulled, out SharedPullableComponent? pullable))
|
||||||
_pullingSystem.TryStopPull(pullable);
|
_pullingSystem.TryStopPull(pullable);
|
||||||
|
|
||||||
if (!_handsSystem.TryDrop(uid, component.ActiveHand!, null, checkActionBlocker: false))
|
if (!_handsSystem.TryDrop(uid, component.ActiveHand!, null, checkActionBlocker: false))
|
||||||
@@ -114,6 +115,7 @@ namespace Content.Server.Hands.Systems
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region pulling
|
#region pulling
|
||||||
|
|
||||||
private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args)
|
private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args)
|
||||||
{
|
{
|
||||||
if (args.Puller.Owner != uid)
|
if (args.Puller.Owner != uid)
|
||||||
@@ -146,17 +148,25 @@ namespace Content.Server.Hands.Systems
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region interactions
|
#region interactions
|
||||||
|
|
||||||
private bool HandleThrowItem(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity)
|
private bool HandleThrowItem(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity)
|
||||||
{
|
{
|
||||||
if (playerSession == null)
|
if (playerSession?.AttachedEntity is not {Valid: true} player || !Exists(player))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (playerSession.AttachedEntity is not {Valid: true} player ||
|
return ThrowHeldItem(player, coordinates);
|
||||||
!Exists(player) ||
|
}
|
||||||
ContainerSystem.IsEntityInContainer(player) ||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throw the player's currently held item.
|
||||||
|
/// </summary>
|
||||||
|
public bool ThrowHeldItem(EntityUid player, EntityCoordinates coordinates, float minDistance = 0.1f)
|
||||||
|
{
|
||||||
|
if (ContainerSystem.IsEntityInContainer(player) ||
|
||||||
!TryComp(player, out HandsComponent? hands) ||
|
!TryComp(player, out HandsComponent? hands) ||
|
||||||
hands.ActiveHandEntity is not { } throwEnt ||
|
hands.ActiveHandEntity is not { } throwEnt ||
|
||||||
!_actionBlockerSystem.CanThrow(player, throwEnt))
|
!_actionBlockerSystem.CanThrow(player, throwEnt))
|
||||||
@@ -176,7 +186,9 @@ namespace Content.Server.Hands.Systems
|
|||||||
if (direction == Vector2.Zero)
|
if (direction == Vector2.Zero)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
direction = direction.Normalized() * Math.Min(direction.Length(), hands.ThrowRange);
|
var length = direction.Length();
|
||||||
|
var distance = Math.Clamp(length, minDistance, hands.ThrowRange);
|
||||||
|
direction *= distance/length;
|
||||||
|
|
||||||
var throwStrength = hands.ThrowForceMultiplier;
|
var throwStrength = hands.ThrowForceMultiplier;
|
||||||
|
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ public sealed class SpreaderSystem : EntitySystem
|
|||||||
if (position == null)
|
if (position == null)
|
||||||
{
|
{
|
||||||
var transform = Transform(uid);
|
var transform = Transform(uid);
|
||||||
if (!_mapManager.TryGetGrid(transform.GridUid, out grid))
|
if (!_mapManager.TryGetGrid(transform.GridUid, out grid) || TerminatingOrDeleted(transform.GridUid.Value))
|
||||||
return neighbors;
|
return neighbors;
|
||||||
tile = grid.TileIndicesFor(transform.Coordinates);
|
tile = grid.TileIndicesFor(transform.Coordinates);
|
||||||
}
|
}
|
||||||
@@ -337,7 +337,7 @@ public sealed class SpreaderSystem : EntitySystem
|
|||||||
while (directionEnumerator.MoveNext(out var ent))
|
while (directionEnumerator.MoveNext(out var ent))
|
||||||
{
|
{
|
||||||
DebugTools.Assert(Transform(ent.Value).Anchored);
|
DebugTools.Assert(Transform(ent.Value).Anchored);
|
||||||
if (spreaderQuery.HasComponent(ent))
|
if (spreaderQuery.HasComponent(ent) && !TerminatingOrDeleted(ent.Value))
|
||||||
neighbors.Add(ent.Value);
|
neighbors.Add(ent.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public sealed partial class DamageOnHighSpeedImpactComponent : Component
|
|||||||
public float DamageCooldown = 2f;
|
public float DamageCooldown = 2f;
|
||||||
|
|
||||||
[DataField("lastHit", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
[DataField("lastHit", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
||||||
public TimeSpan LastHit = TimeSpan.Zero;
|
public TimeSpan? LastHit;
|
||||||
|
|
||||||
[DataField("damage", required: true), ViewVariables(VVAccess.ReadWrite)]
|
[DataField("damage", required: true), ViewVariables(VVAccess.ReadWrite)]
|
||||||
public DamageSpecifier Damage = default!;
|
public DamageSpecifier Damage = default!;
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ public sealed class DamageOnHighSpeedImpactSystem : EntitySystem
|
|||||||
if (speed < component.MinimumSpeed)
|
if (speed < component.MinimumSpeed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ((_gameTiming.CurTime - component.LastHit).TotalSeconds < component.DamageCooldown)
|
if (component.LastHit != null
|
||||||
|
&& (_gameTiming.CurTime - component.LastHit.Value).TotalSeconds < component.DamageCooldown)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
component.LastHit = _gameTiming.CurTime;
|
component.LastHit = _gameTiming.CurTime;
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ public sealed class ThrowingSystem : EntitySystem
|
|||||||
var impulseVector = direction.Normalized() * strength * physics.Mass;
|
var impulseVector = direction.Normalized() * strength * physics.Mass;
|
||||||
_physics.ApplyLinearImpulse(uid, impulseVector, body: physics);
|
_physics.ApplyLinearImpulse(uid, impulseVector, body: physics);
|
||||||
|
|
||||||
if (comp.LandTime <= TimeSpan.Zero)
|
if (comp.LandTime == null || comp.LandTime <= TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
_thrownSystem.LandComponent(uid, comp, physics, playSound);
|
_thrownSystem.LandComponent(uid, comp, physics, playSound);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user