]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add some tests and fix some miscellaneous bugs (#22836)
authorLeon Friedrich <60421075+ElectroJr@users.noreply.github.com>
Wed, 27 Dec 2023 23:05:20 +0000 (18:05 -0500)
committerGitHub <noreply@github.com>
Wed, 27 Dec 2023 23:05:20 +0000 (10:05 +1100)
* Add some tests and fix some bugs

* Add more helper methods

* remove submodule

* fix merge

* also fix DirtyAll()

* poke

14 files changed:
Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
Content.IntegrationTests/Pair/TestPair.cs
Content.IntegrationTests/PoolTestLogHandler.cs
Content.IntegrationTests/Tests/DoAfter/DoAfterCancellationTests.cs
Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
Content.IntegrationTests/Tests/Networking/PvsCommandTest.cs [new file with mode: 0644]
Content.IntegrationTests/Tests/Physics/ItemThrowingTest.cs [new file with mode: 0644]
Content.Server/Administration/Commands/DirtyCommand.cs
Content.Server/Hands/Systems/HandsSystem.cs
Content.Server/Spreader/SpreaderSystem.cs
Content.Shared/Damage/Components/DamageOnHighSpeedImpactComponent.cs
Content.Shared/Damage/Systems/DamageOnHighSpeedImpactSystem.cs
Content.Shared/Throwing/ThrowingSystem.cs

index aae06965ef92cf2581b161b8430420eb92347cc4..0a73943923e8f1f7ea9b04bd460ea74db69eb8e5 100644 (file)
@@ -773,9 +773,10 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
         if (_actionsSystem == null)
             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());
index 2672b4db56e454de1dbffe093697daaf1b4233a8..ba2faacb7e07995b04f6f4a6e620508d25f1a39f 100644 (file)
@@ -28,6 +28,14 @@ public sealed partial class TestPair
     public RobustIntegrationTest.ServerIntegrationInstance Server { 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 ContentPlayerData? PlayerData => Player?.Data.ContentData();
 
@@ -78,6 +86,8 @@ public sealed partial class TestPair
     public void Kill()
     {
         State = PairState.Dead;
+        ServerLogHandler.ShuttingDown = true;
+        ClientLogHandler.ShuttingDown = true;
         Server.Dispose();
         Client.Dispose();
     }
index efa185e148c6148b45a01fe52f5f8f0c39884f68..909bee9785a0accd7437512396d4953308561c7f 100644 (file)
@@ -36,8 +36,15 @@ public sealed class PoolTestLogHandler : ILogHandler
         _prefix = prefix != null ? $"{prefix}: " : "";
     }
 
+    public bool ShuttingDown;
+
     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 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");
         }
 
-        var level = message.Level.ToRobust();
         var name = LogMessage.LogLevelToName(level);
         var seconds = _stopwatch.Elapsed.TotalSeconds;
         var rendered = message.RenderMessage();
index 5522ce8c54b8d66dd236edc1a7def6e9ffaa2ed2..d47eb13273f998b0137def379ef4c4bb7c47252d 100644 (file)
@@ -38,10 +38,10 @@ public sealed class DoAfterCancellationTests : InteractionTest
         AssertAnchored(false);
 
         // Repeat for screwdriver interaction.
-        AssertDeleted(false);
+        AssertExists();
         await Interact(Screw, awaitDoAfters: false);
         await CancelDoAfters();
-        AssertDeleted(false);
+        AssertExists();
         await Interact(Screw);
         AssertDeleted();
     }
index 695de4461dbf49b05e936675fee6d595a0c4ae9a..9aa0157c8aff2ab624c96fcf56f4967ab1d6c1b7 100644 (file)
@@ -134,9 +134,9 @@ public abstract partial class InteractionTest
     /// <remarks>
     /// Automatically enables welders.
     /// </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>
@@ -145,7 +145,7 @@ public abstract partial class InteractionTest
     /// <remarks>
     /// Automatically enables welders.
     /// </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)
         {
@@ -153,15 +153,9 @@ public abstract partial class InteractionTest
             return default;
         }
 
+        Assert.That(!string.IsNullOrWhiteSpace(entity.Prototype));
         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
         var item = await SpawnEntity(entity, SEntMan.GetCoordinates(PlayerCoords));
         ItemToggleComponent? itemToggle = null;
@@ -184,7 +178,7 @@ public abstract partial class InteractionTest
         if (enableWelder && itemToggle != null)
             Assert.That(itemToggle.Activated);
 
-        return item;
+        return SEntMan.GetNetEntity(item);
     }
 
     /// <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
 
     /// <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;
         if (target == null)
@@ -494,8 +499,24 @@ public abstract partial class InteractionTest
 
         Assert.Multiple(() =>
         {
-            Assert.That(SEntMan.Deleted(SEntMan.GetEntity(target)), Is.EqualTo(deleted));
-            Assert.That(CEntMan.Deleted(CEntMan.GetEntity(target)), Is.EqualTo(deleted));
+            Assert.That(SEntMan.Deleted(SEntMan.GetEntity(target)));
+            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);
     }
 
+    protected Task Delete(NetEntity nuid)
+    {
+        return Delete(SEntMan.GetEntity(nuid));
+    }
+
     #region Time/Tick managment
 
     protected async Task RunTicks(int ticks)
@@ -1064,4 +1090,35 @@ public abstract partial class InteractionTest
     }
 
     #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
 }
index f54d772881bf47b76ef82a97b6f40427dfca1581..45a880fe867a80d85a41dfd6fc9dd9bfaeab0045 100644 (file)
@@ -5,12 +5,12 @@ using Content.Client.Construction;
 using Content.Client.Examine;
 using Content.IntegrationTests.Pair;
 using Content.Server.Body.Systems;
+using Content.Server.Hands.Systems;
 using Content.Server.Stack;
 using Content.Server.Tools;
 using Content.Shared.Body.Part;
 using Content.Shared.DoAfter;
 using Content.Shared.Hands.Components;
-using Content.Shared.Hands.EntitySystems;
 using Content.Shared.Interaction;
 using Content.Server.Item;
 using Content.Shared.Mind;
@@ -66,6 +66,9 @@ public abstract partial class InteractionTest
     /// </summary>
     protected NetEntity Player;
 
+    protected EntityUid SPlayer => ToServer(Player);
+    protected EntityUid CPlayer => ToClient(Player);
+
     protected ICommonSession ClientSession = default!;
     protected ICommonSession ServerSession = default!;
 
@@ -81,6 +84,9 @@ public abstract partial class InteractionTest
     /// </remarks>
     protected NetEntity? Target;
 
+    protected EntityUid? STarget => ToServer(Target);
+    protected EntityUid? CTarget => ToClient(Target);
+
     /// <summary>
     /// When attempting to start construction, this is the client-side ID of the construction ghost.
     /// </summary>
@@ -93,7 +99,7 @@ public abstract partial class InteractionTest
     protected IPrototypeManager ProtoMan = default!;
     protected IGameTiming STiming = default!;
     protected IComponentFactory Factory = default!;
-    protected SharedHandsSystem HandSys = default!;
+    protected HandsSystem HandSys = default!;
     protected StackSystem Stack = default!;
     protected SharedInteractionSystem InteractSys = default!;
     protected Content.Server.Construction.ConstructionSystem SConstruction = default!;
@@ -152,7 +158,7 @@ public abstract partial class InteractionTest
         ProtoMan = Server.ResolveDependency<IPrototypeManager>();
         Factory = Server.ResolveDependency<IComponentFactory>();
         STiming = Server.ResolveDependency<IGameTiming>();
-        HandSys = SEntMan.System<SharedHandsSystem>();
+        HandSys = SEntMan.System<HandsSystem>();
         InteractSys = SEntMan.System<SharedInteractionSystem>();
         ToolSys = SEntMan.System<ToolSystem>();
         ItemToggleSys = SEntMan.System<SharedItemToggleSystem>();
diff --git a/Content.IntegrationTests/Tests/Networking/PvsCommandTest.cs b/Content.IntegrationTests/Tests/Networking/PvsCommandTest.cs
new file mode 100644 (file)
index 0000000..4783d21
--- /dev/null
@@ -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();
+    }
+}
diff --git a/Content.IntegrationTests/Tests/Physics/ItemThrowingTest.cs b/Content.IntegrationTests/Tests/Physics/ItemThrowingTest.cs
new file mode 100644 (file)
index 0000000..4b762d1
--- /dev/null
@@ -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);
+    }
+}
+
index 0ed8689d7fcddcad76dc467fff4d7f2e9e9ca208..cc0b0fcec67da5c4633fef9f084875162067d05c 100644 (file)
@@ -38,9 +38,9 @@ public sealed class DirtyCommand : IConsoleCommand
 
     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);
         }
     }
 }
index f41298e98dec1ecdd8ffaebecb70102f85a45424..ae5a99bf28cf210dd92fc8933defec27a78f2ab8 100644 (file)
@@ -34,7 +34,7 @@ namespace Content.Server.Hands.Systems
         {
             base.Initialize();
 
-            SubscribeLocalEvent<HandsComponent, DisarmedEvent>(OnDisarmed, before: new[] { typeof(StunSystem) });
+            SubscribeLocalEvent<HandsComponent, DisarmedEvent>(OnDisarmed, before: new[] {typeof(StunSystem)});
 
             SubscribeLocalEvent<HandsComponent, PullStartedMessage>(HandlePullStarted);
             SubscribeLocalEvent<HandsComponent, PullStoppedMessage>(HandlePullStopped);
@@ -67,7 +67,7 @@ namespace Content.Server.Hands.Systems
         {
             foreach (var hand in ent.Comp.Hands.Values)
             {
-                if (hand.HeldEntity is {} uid)
+                if (hand.HeldEntity is { } uid)
                     args.Contents.Add(uid);
             }
         }
@@ -78,7 +78,8 @@ namespace Content.Server.Hands.Systems
                 return;
 
             // 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);
 
             if (!_handsSystem.TryDrop(uid, component.ActiveHand!, null, checkActionBlocker: false))
@@ -114,6 +115,7 @@ namespace Content.Server.Hands.Systems
         }
 
         #region pulling
+
         private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args)
         {
             if (args.Puller.Owner != uid)
@@ -146,17 +148,25 @@ namespace Content.Server.Hands.Systems
                 break;
             }
         }
+
         #endregion
 
         #region interactions
+
         private bool HandleThrowItem(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity)
         {
-            if (playerSession == null)
+            if (playerSession?.AttachedEntity is not {Valid: true} player || !Exists(player))
                 return false;
 
-            if (playerSession.AttachedEntity is not {Valid: true} player ||
-                !Exists(player) ||
-                ContainerSystem.IsEntityInContainer(player) ||
+            return ThrowHeldItem(player, coordinates);
+        }
+
+        /// <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) ||
                 hands.ActiveHandEntity is not { } throwEnt ||
                 !_actionBlockerSystem.CanThrow(player, throwEnt))
@@ -176,7 +186,9 @@ namespace Content.Server.Hands.Systems
             if (direction == Vector2.Zero)
                 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;
 
index 8afc7e6bd543a94003aa1168c5d4fb8e29fa793b..545d2cf3f12dd446b44aa813fb073db51e42a813 100644 (file)
@@ -312,7 +312,7 @@ public sealed class SpreaderSystem : EntitySystem
         if (position == null)
         {
             var transform = Transform(uid);
-            if (!_mapManager.TryGetGrid(transform.GridUid, out grid))
+            if (!_mapManager.TryGetGrid(transform.GridUid, out grid) || TerminatingOrDeleted(transform.GridUid.Value))
                 return neighbors;
             tile = grid.TileIndicesFor(transform.Coordinates);
         }
@@ -337,7 +337,7 @@ public sealed class SpreaderSystem : EntitySystem
             while (directionEnumerator.MoveNext(out var ent))
             {
                 DebugTools.Assert(Transform(ent.Value).Anchored);
-                if (spreaderQuery.HasComponent(ent))
+                if (spreaderQuery.HasComponent(ent) && !TerminatingOrDeleted(ent.Value))
                     neighbors.Add(ent.Value);
             }
         }
index 3eef1d70a4b46569feac77193e5a0a72550492f3..c56ed6537ca0934a1e0e2ac40abfb733f87a4251 100644 (file)
@@ -32,7 +32,7 @@ public sealed partial class DamageOnHighSpeedImpactComponent : Component
     public float DamageCooldown = 2f;
 
     [DataField("lastHit", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
-    public TimeSpan LastHit = TimeSpan.Zero;
+    public TimeSpan? LastHit;
 
     [DataField("damage", required: true), ViewVariables(VVAccess.ReadWrite)]
     public DamageSpecifier Damage = default!;
index 12bca5227471c2043e32a9d4aadc23738c82e5f9..6768048defe16e3071aef04976e4022d41734335 100644 (file)
@@ -38,7 +38,8 @@ public sealed class DamageOnHighSpeedImpactSystem : EntitySystem
         if (speed < component.MinimumSpeed)
             return;
 
-        if ((_gameTiming.CurTime - component.LastHit).TotalSeconds < component.DamageCooldown)
+        if (component.LastHit != null
+            && (_gameTiming.CurTime - component.LastHit.Value).TotalSeconds < component.DamageCooldown)
             return;
 
         component.LastHit = _gameTiming.CurTime;
index 229d8a72b2495201ef4d9ff6d00718259bb7f81a..a4c95ec5e36bd62516340fdb7fe9c07c9de64098 100644 (file)
@@ -145,7 +145,7 @@ public sealed class ThrowingSystem : EntitySystem
         var impulseVector = direction.Normalized() * strength * physics.Mass;
         _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);
         }