]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Mob movement rewrite (#35931)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Thu, 27 Mar 2025 22:29:02 +0000 (09:29 +1100)
committerGitHub <noreply@github.com>
Thu, 27 Mar 2025 22:29:02 +0000 (09:29 +1100)
* Conveyor optimisations

- Optimise movement for moving stuff. Better flags + less resolves + slapped parallelrobustjob on it.
- Sleeping for entities getting conveyed into walls.

* Blocker version

* Finish

* Final

* Fix conveyor power mispredict

* Bagel save

* Revert "Bagel save"

This reverts commit 1b93fda81fb852d89b89b0beae0b80f8a61165f2.

* Conveyor resave

* Fix prediction

* Mob movement rewrite

* Bandaid

* Working version

* Tentatively working

* Friction to fix cornering

* More fixes

* Revert bagel

* Revert this

* a

* Reviewed

* Funky re-save

* Fix velocity

* Table fix

* Review

* a

Content.Server/Movement/Systems/PullController.cs
Content.Server/Physics/Controllers/ConveyorController.cs
Content.Shared/Conveyor/ConveyedComponent.cs
Content.Shared/Movement/Components/InputMoverComponent.cs
Content.Shared/Movement/Systems/SharedMoverController.cs
Content.Shared/Physics/Controllers/SharedConveyorController.cs
Resources/Maps/bagel.yml
Resources/Prototypes/Entities/Structures/conveyor.yml

index 4bd4b603714f4f54f3bfd7676b3325e8a089b4a9..f28ea952c8960f3df7c41ca855f6e5d036e97873 100644 (file)
@@ -2,6 +2,7 @@ using System.Numerics;
 using Content.Server.Movement.Components;
 using Content.Server.Physics.Controllers;
 using Content.Shared.ActionBlocker;
+using Content.Shared.Conveyor;
 using Content.Shared.Gravity;
 using Content.Shared.Input;
 using Content.Shared.Movement.Pulling.Components;
@@ -122,6 +123,12 @@ public sealed class PullController : VirtualController
 
         var pulled = pullerComp.Pulling;
 
+        // See update statement; this thing overwrites so many systems, DOESN'T EVEN LERP PROPERLY.
+        // We had a throwing version but it occasionally had issues.
+        // We really need the throwing version back.
+        if (TryComp(pulled, out ConveyedComponent? conveyed) && conveyed.Conveying)
+            return false;
+
         if (!_pullableQuery.TryComp(pulled, out var pullable))
             return false;
 
@@ -134,7 +141,7 @@ public sealed class PullController : VirtualController
         var range = 2f;
         var fromUserCoords = coords.WithEntityId(player, EntityManager);
         var userCoords = new EntityCoordinates(player, Vector2.Zero);
-        
+
         if (!_transformSystem.InRange(coords, userCoords, range))
         {
             var direction = fromUserCoords.Position - userCoords.Position;
@@ -257,6 +264,13 @@ public sealed class PullController : VirtualController
                 continue;
             }
 
+            // TODO: This whole thing is slop and really needs to be throwing again
+            if (TryComp(pullableEnt, out ConveyedComponent? conveyed) && conveyed.Conveying)
+            {
+                RemCompDeferred<PullMovingComponent>(pullableEnt);
+                continue;
+            }
+
             var movingPosition = movingTo.Position;
             var ownerPosition = TransformSystem.GetWorldPosition(pullableXform);
 
index bf6abff158019e61de8d6294c93dc9f79de57c18..494bc8c6f028d8bf3ebd552fda4bd095202d2bb5 100644 (file)
@@ -1,7 +1,6 @@
 using Content.Server.DeviceLinking.Events;
 using Content.Server.DeviceLinking.Systems;
 using Content.Server.Materials;
-using Content.Server.Power.Components;
 using Content.Shared.Conveyor;
 using Content.Shared.Destructible;
 using Content.Shared.Maps;
@@ -10,7 +9,6 @@ using Content.Shared.Physics.Controllers;
 using Content.Shared.Power;
 using Robust.Shared.Physics;
 using Robust.Shared.Physics.Collision.Shapes;
-using Robust.Shared.Physics.Components;
 using Robust.Shared.Physics.Systems;
 
 namespace Content.Server.Physics.Controllers;
@@ -20,7 +18,6 @@ public sealed class ConveyorController : SharedConveyorController
     [Dependency] private readonly FixtureSystem _fixtures = default!;
     [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
     [Dependency] private readonly MaterialReclaimerSystem _materialReclaimer = default!;
-    [Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
     [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
 
     public override void Initialize()
@@ -40,7 +37,7 @@ public sealed class ConveyorController : SharedConveyorController
     {
         _signalSystem.EnsureSinkPorts(uid, component.ReversePort, component.ForwardPort, component.OffPort);
 
-        if (TryComp<PhysicsComponent>(uid, out var physics))
+        if (PhysicsQuery.TryComp(uid, out var physics))
         {
             var shape = new PolygonShape();
             shape.SetAsBox(0.55f, 0.55f);
@@ -57,7 +54,7 @@ public sealed class ConveyorController : SharedConveyorController
         if (MetaData(uid).EntityLifeStage >= EntityLifeStage.Terminating)
             return;
 
-        if (!TryComp<PhysicsComponent>(uid, out var physics))
+        if (!PhysicsQuery.TryComp(uid, out var physics))
             return;
 
         _fixtures.DestroyFixture(uid, ConveyorFixture, body: physics);
@@ -87,13 +84,11 @@ public sealed class ConveyorController : SharedConveyorController
 
         else if (args.Port == component.ForwardPort)
         {
-            AwakenEntities(uid, component);
             SetState(uid, ConveyorState.Forward, component);
         }
 
         else if (args.Port == component.ReversePort)
         {
-            AwakenEntities(uid, component);
             SetState(uid, ConveyorState.Reverse, component);
         }
     }
@@ -108,8 +103,10 @@ public sealed class ConveyorController : SharedConveyorController
 
         component.State = state;
 
-        if (TryComp<PhysicsComponent>(uid, out var physics))
-            _broadphase.RegenerateContacts((uid, physics));
+        if (state != ConveyorState.Off)
+        {
+            WakeConveyed(uid);
+        }
 
         UpdateAppearance(uid, component);
         Dirty(uid, component);
@@ -117,29 +114,29 @@ public sealed class ConveyorController : SharedConveyorController
 
     /// <summary>
     /// Awakens sleeping entities on the conveyor belt's tile when it's turned on.
-    /// Fixes an issue where non-hard/sleeping entities refuse to wake up + collide if a belt is turned off and on again.
+    /// Need this as we might activate under CollisionWake entities and need to forcefully check them.
     /// </summary>
-    private void AwakenEntities(EntityUid uid, ConveyorComponent component)
+    protected override void AwakenConveyor(Entity<TransformComponent?> ent)
     {
-        var xformQuery = GetEntityQuery<TransformComponent>();
-        var bodyQuery = GetEntityQuery<PhysicsComponent>();
-
-        if (!xformQuery.TryGetComponent(uid, out var xform))
+        if (!XformQuery.Resolve(ent.Owner, ref ent.Comp))
             return;
 
+        var xform = ent.Comp;
+
         var beltTileRef = xform.Coordinates.GetTileRef(EntityManager, MapManager);
 
         if (beltTileRef != null)
         {
-            var intersecting = Lookup.GetLocalEntitiesIntersecting(beltTileRef.Value, 0f);
+            Intersecting.Clear();
+            Lookup.GetLocalEntitiesIntersecting(beltTileRef.Value.GridUid, beltTileRef.Value.GridIndices, Intersecting, 0f, flags: LookupFlags.Dynamic | LookupFlags.Sundries | LookupFlags.Approximate);
 
-            foreach (var entity in intersecting)
+            foreach (var entity in Intersecting)
             {
-                if (!bodyQuery.TryGetComponent(entity, out var physics))
+                if (!PhysicsQuery.TryGetComponent(entity, out var physics))
                     continue;
 
                 if (physics.BodyType != BodyType.Static)
-                    Physics.WakeBody(entity, body: physics);
+                    PhysicsSystem.WakeBody(entity, body: physics);
             }
         }
     }
index 25189d2182a5918eccfa46f9e085149abeee6eb5..212c4dc23ff03e8530cd95f625a0a84448a90ab2 100644 (file)
@@ -3,11 +3,15 @@ using Robust.Shared.GameStates;
 namespace Content.Shared.Conveyor;
 
 /// <summary>
-/// Indicates this entity is currently being conveyed.
+/// Indicates this entity is currently contacting a conveyor and will subscribe to events as appropriate.
 /// </summary>
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
 public sealed partial class ConveyedComponent : Component
 {
-    [ViewVariables, AutoNetworkedField]
-    public List<EntityUid> Colliding = new();
+    // TODO: Delete if pulling gets fixed.
+    /// <summary>
+    /// True if currently conveying.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Conveying;
 }
index f1e34c90df47e5bda1de3faae5c17ab21b900071..7c3b8b431a37fb7a7d622f317fc007cb71fcdfc3 100644 (file)
@@ -32,7 +32,7 @@ namespace Content.Shared.Movement.Components
         /// <summary>
         /// Should our velocity be applied to our parent?
         /// </summary>
-        [ViewVariables(VVAccess.ReadWrite), DataField("toParent")]
+        [DataField]
         public bool ToParent = false;
 
         public GameTick LastInputTick;
@@ -43,6 +43,12 @@ namespace Content.Shared.Movement.Components
 
         public MoveButtons HeldMoveButtons = MoveButtons.None;
 
+        // I don't know if we even need this networked? It's mostly so conveyors can calculate properly.
+        /// <summary>
+        /// Direction to move this tick.
+        /// </summary>
+        public Vector2 WishDir;
+
         /// <summary>
         /// Entity our movement is relative to.
         /// </summary>
@@ -65,7 +71,6 @@ namespace Content.Shared.Movement.Components
         /// If we traverse on / off a grid then set a timer to update our relative inputs.
         /// </summary>
         [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
-        [ViewVariables(VVAccess.ReadWrite)]
         public TimeSpan LerpTarget;
 
         public const float LerpTime = 1.0f;
index 5de74d72940802731ac2c77439cc0796abdff810..6456444080380b6299d6e09e996d90c98216198e 100644 (file)
@@ -155,7 +155,6 @@ public abstract partial class SharedMoverController : VirtualController
             return;
         }
 
-
         UsedMobMovement[uid] = true;
         // Specifically don't use mover.Owner because that may be different to the actual physics body being moved.
         var weightless = _gravity.IsWeightless(physicsUid, physicsComponent, xform);
@@ -203,20 +202,21 @@ public abstract partial class SharedMoverController : VirtualController
         var total = walkDir * walkSpeed + sprintDir * sprintSpeed;
 
         var parentRotation = GetParentGridAngle(mover);
-        var worldTotal = _relativeMovement ? parentRotation.RotateVec(total) : total;
+        var wishDir = _relativeMovement ? parentRotation.RotateVec(total) : total;
 
-        DebugTools.Assert(MathHelper.CloseToPercent(total.Length(), worldTotal.Length()));
+        DebugTools.Assert(MathHelper.CloseToPercent(total.Length(), wishDir.Length()));
 
-        var velocity = physicsComponent.LinearVelocity;
         float friction;
         float weightlessModifier;
         float accel;
+        var velocity = physicsComponent.LinearVelocity;
 
+        // Whether we use weightless friction or not.
         if (weightless)
         {
             if (gridComp == null && !MapGridQuery.HasComp(xform.GridUid))
                 friction = moveSpeedComponent?.OffGridFriction ?? MovementSpeedModifierComponent.DefaultOffGridFriction;
-            else if (worldTotal != Vector2.Zero && touching)
+            else if (wishDir != Vector2.Zero && touching)
                 friction = moveSpeedComponent?.WeightlessFriction ?? MovementSpeedModifierComponent.DefaultWeightlessFriction;
             else
                 friction = moveSpeedComponent?.WeightlessFrictionNoInput ?? MovementSpeedModifierComponent.DefaultWeightlessFrictionNoInput;
@@ -226,7 +226,7 @@ public abstract partial class SharedMoverController : VirtualController
         }
         else
         {
-            if (worldTotal != Vector2.Zero || moveSpeedComponent?.FrictionNoInput == null)
+            if (wishDir != Vector2.Zero || moveSpeedComponent?.FrictionNoInput == null)
             {
                 friction = tileDef?.MobFriction ?? moveSpeedComponent?.Friction ?? MovementSpeedModifierComponent.DefaultFriction;
             }
@@ -242,14 +242,27 @@ public abstract partial class SharedMoverController : VirtualController
         var minimumFrictionSpeed = moveSpeedComponent?.MinimumFrictionSpeed ?? MovementSpeedModifierComponent.DefaultMinimumFrictionSpeed;
         Friction(minimumFrictionSpeed, frameTime, friction, ref velocity);
 
-        if (worldTotal != Vector2.Zero)
+        wishDir *= weightlessModifier;
+
+        if (!weightless || touching)
+            Accelerate(ref velocity, in wishDir, accel, frameTime);
+
+        SetWishDir((uid, mover), wishDir);
+
+        PhysicsSystem.SetLinearVelocity(physicsUid, velocity, body: physicsComponent);
+
+        // Ensures that players do not spiiiiiiin
+        PhysicsSystem.SetAngularVelocity(physicsUid, 0, body: physicsComponent);
+
+        // Handle footsteps at the end
+        if (total != Vector2.Zero)
         {
             if (!NoRotateQuery.HasComponent(uid))
             {
                 // TODO apparently this results in a duplicate move event because "This should have its event run during
                 // island solver"??. So maybe SetRotation needs an argument to avoid raising an event?
                 var worldRot = _transform.GetWorldRotation(xform);
-                _transform.SetLocalRotation(xform, xform.LocalRotation + worldTotal.ToWorldAngle() - worldRot);
+                _transform.SetLocalRotation(xform, xform.LocalRotation + wishDir.ToWorldAngle() - worldRot);
             }
 
             if (!weightless && MobMoverQuery.TryGetComponent(uid, out var mobMover) &&
@@ -272,16 +285,23 @@ public abstract partial class SharedMoverController : VirtualController
                 }
             }
         }
+    }
 
-        worldTotal *= weightlessModifier;
+    public Vector2 GetWishDir(Entity<InputMoverComponent?> mover)
+    {
+        if (!MoverQuery.Resolve(mover.Owner, ref mover.Comp, false))
+            return Vector2.Zero;
 
-        if (!weightless || touching)
-            Accelerate(ref velocity, in worldTotal, accel, frameTime);
+        return mover.Comp.WishDir;
+    }
 
-        PhysicsSystem.SetLinearVelocity(physicsUid, velocity, body: physicsComponent);
+    public void SetWishDir(Entity<InputMoverComponent> mover, Vector2 wishDir)
+    {
+        if (mover.Comp.WishDir.Equals(wishDir))
+            return;
 
-        // Ensures that players do not spiiiiiiin
-        PhysicsSystem.SetAngularVelocity(physicsUid, 0, body: physicsComponent);
+        mover.Comp.WishDir = wishDir;
+        Dirty(mover);
     }
 
     public void LerpRotation(EntityUid uid, InputMoverComponent mover, float frameTime)
@@ -317,7 +337,7 @@ public abstract partial class SharedMoverController : VirtualController
         }
     }
 
-    private void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref Vector2 velocity)
+    public void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref Vector2 velocity)
     {
         var speed = velocity.Length();
 
@@ -338,7 +358,10 @@ public abstract partial class SharedMoverController : VirtualController
         velocity *= newSpeed;
     }
 
-    private void Accelerate(ref Vector2 currentVelocity, in Vector2 velocity, float accel, float frameTime)
+    /// <summary>
+    /// Adjusts the current velocity to the target velocity based on the specified acceleration.
+    /// </summary>
+    public static void Accelerate(ref Vector2 currentVelocity, in Vector2 velocity, float accel, float frameTime)
     {
         var wishDir = velocity != Vector2.Zero ? velocity.Normalized() : Vector2.Zero;
         var wishSpeed = velocity.Length();
index abcd2bc4a2112ef5da61c8a3ea0f459a9fbb941f..07bf6c7332c7dbcdbe24ac397b3edfa098b8ea80 100644 (file)
 using Content.Shared.Conveyor;
 using Content.Shared.Gravity;
 using Content.Shared.Magic;
+using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Events;
 using Content.Shared.Movement.Systems;
 using Robust.Shared.Collections;
 using Robust.Shared.Map;
-using Robust.Shared.Map.Components;
 using Robust.Shared.Physics;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Physics.Controllers;
 using Robust.Shared.Physics.Events;
 using Robust.Shared.Physics.Systems;
-using Robust.Shared.Utility;
+using Robust.Shared.Threading;
 
 namespace Content.Shared.Physics.Controllers;
 
 public abstract class SharedConveyorController : VirtualController
 {
     [Dependency] protected readonly IMapManager MapManager = default!;
+    [Dependency] private   readonly IParallelManager _parallel = default!;
+    [Dependency] private   readonly CollisionWakeSystem _wake = default!;
     [Dependency] protected readonly EntityLookupSystem Lookup = default!;
-    [Dependency] private readonly SharedMapSystem _maps = default!;
-    [Dependency] protected readonly SharedPhysicsSystem Physics = default!;
-    [Dependency] private readonly SharedGravitySystem _gravity = default!;
+    [Dependency] private   readonly FixtureSystem _fixtures = default!;
+    [Dependency] private   readonly SharedGravitySystem _gravity = default!;
+    [Dependency] private   readonly SharedMoverController _mover = default!;
 
     protected const string ConveyorFixture = "conveyor";
 
-    private EntityQuery<MapGridComponent> _gridQuery;
-    private EntityQuery<TransformComponent> _xformQuery;
+    private ConveyorJob _job;
 
-    private ValueList<EntityUid> _ents = new();
-    private HashSet<Entity<ConveyorComponent>> _conveyors = new();
+    private EntityQuery<ConveyorComponent> _conveyorQuery;
+    private EntityQuery<ConveyedComponent> _conveyedQuery;
+    protected EntityQuery<PhysicsComponent> PhysicsQuery;
+    protected EntityQuery<TransformComponent> XformQuery;
+
+    protected HashSet<EntityUid> Intersecting = new();
 
     public override void Initialize()
     {
-        _gridQuery = GetEntityQuery<MapGridComponent>();
-        _xformQuery = GetEntityQuery<TransformComponent>();
+        _job = new ConveyorJob(this);
+        _conveyorQuery = GetEntityQuery<ConveyorComponent>();
+        _conveyedQuery = GetEntityQuery<ConveyedComponent>();
+        PhysicsQuery = GetEntityQuery<PhysicsComponent>();
+        XformQuery = GetEntityQuery<TransformComponent>();
 
         UpdatesAfter.Add(typeof(SharedMoverController));
 
+        SubscribeLocalEvent<ConveyedComponent, TileFrictionEvent>(OnConveyedFriction);
+        SubscribeLocalEvent<ConveyedComponent, ComponentStartup>(OnConveyedStartup);
+        SubscribeLocalEvent<ConveyedComponent, ComponentShutdown>(OnConveyedShutdown);
+
         SubscribeLocalEvent<ConveyorComponent, StartCollideEvent>(OnConveyorStartCollide);
-        SubscribeLocalEvent<ConveyorComponent, EndCollideEvent>(OnConveyorEndCollide);
+        SubscribeLocalEvent<ConveyorComponent, ComponentStartup>(OnConveyorStartup);
 
         base.Initialize();
     }
 
-    private void OnConveyorStartCollide(EntityUid uid, ConveyorComponent component, ref StartCollideEvent args)
+    private void OnConveyedFriction(Entity<ConveyedComponent> ent, ref TileFrictionEvent args)
     {
-        var otherUid = args.OtherEntity;
+        // Conveyed entities don't get friction, they just get wishdir applied so will inherently slowdown anyway.
+        args.Modifier = 0f;
+    }
 
-        if (!args.OtherFixture.Hard || args.OtherBody.BodyType == BodyType.Static || component.State == ConveyorState.Off)
-            return;
+    private void OnConveyedStartup(Entity<ConveyedComponent> ent, ref ComponentStartup args)
+    {
+        // We need waking / sleeping to work and don't want collisionwake interfering with us.
+        _wake.SetEnabled(ent.Owner, false);
+    }
 
-        var conveyed = EnsureComp<ConveyedComponent>(otherUid);
+    private void OnConveyedShutdown(Entity<ConveyedComponent> ent, ref ComponentShutdown args)
+    {
+        _wake.SetEnabled(ent.Owner, true);
+    }
 
-        if (conveyed.Colliding.Contains(uid))
-            return;
+    private void OnConveyorStartup(Entity<ConveyorComponent> ent, ref ComponentStartup args)
+    {
+        AwakenConveyor(ent.Owner);
+    }
 
-        conveyed.Colliding.Add(uid);
-        Dirty(otherUid, conveyed);
+    /// <summary>
+    /// Forcefully awakens all entities near the conveyor.
+    /// </summary>
+    protected virtual void AwakenConveyor(Entity<TransformComponent?> ent)
+    {
     }
 
-    private void OnConveyorEndCollide(Entity<ConveyorComponent> ent, ref EndCollideEvent args)
+    /// <summary>
+    /// Wakes all conveyed entities contacting this conveyor.
+    /// </summary>
+    protected void WakeConveyed(EntityUid conveyorUid)
     {
-        if (!TryComp(args.OtherEntity, out ConveyedComponent? conveyed))
-            return;
+        var contacts = PhysicsSystem.GetContacts(conveyorUid);
 
-        if (!conveyed.Colliding.Remove(ent.Owner))
+        while (contacts.MoveNext(out var contact))
+        {
+            var other = contact.OtherEnt(conveyorUid);
+
+            if (_conveyedQuery.HasComp(other))
+            {
+                PhysicsSystem.WakeBody(other);
+            }
+        }
+    }
+
+    private void OnConveyorStartCollide(Entity<ConveyorComponent> conveyor, ref StartCollideEvent args)
+    {
+        var otherUid = args.OtherEntity;
+
+        if (!args.OtherFixture.Hard || args.OtherBody.BodyType == BodyType.Static)
             return;
 
-        Dirty(args.OtherEntity, conveyed);
+        EnsureComp<ConveyedComponent>(otherUid);
     }
 
     public override void UpdateBeforeSolve(bool prediction, float frameTime)
     {
         base.UpdateBeforeSolve(prediction, frameTime);
 
-        var query = EntityQueryEnumerator<ConveyedComponent, TransformComponent, PhysicsComponent>();
-        _ents.Clear();
+        _job.Prediction = prediction;
+        _job.Conveyed.Clear();
 
-        while (query.MoveNext(out var uid, out var comp, out var xform, out var physics))
-        {
-            if (TryConvey((uid, comp, physics, xform), prediction, frameTime))
-                continue;
+        var query = EntityQueryEnumerator<ConveyedComponent, FixturesComponent, PhysicsComponent, TransformComponent>();
 
-            _ents.Add(uid);
+        while (query.MoveNext(out var uid, out var comp, out var fixtures, out var physics, out var xform))
+        {
+            _job.Conveyed.Add(((uid, comp, fixtures, physics, xform), Vector2.Zero, false));
         }
 
-        foreach (var ent in _ents)
+        _parallel.ProcessNow(_job, _job.Conveyed.Count);
+
+        foreach (var ent in _job.Conveyed)
         {
-            RemComp<ConveyedComponent>(ent);
+            if (!ent.Entity.Comp3.Predict && prediction)
+                continue;
+
+            var physics = ent.Entity.Comp3;
+            var velocity = physics.LinearVelocity;
+            var targetDir = ent.Direction;
+
+            // If mob is moving with the conveyor then combine the directions.
+            var wishDir = _mover.GetWishDir(ent.Entity.Owner);
+
+            if (Vector2.Dot(wishDir, targetDir) > 0f)
+            {
+                targetDir += wishDir;
+            }
+
+            if (ent.Result)
+            {
+                SetConveying(ent.Entity.Owner, ent.Entity.Comp1, targetDir.LengthSquared() > 0f);
+
+                // We apply friction here so when we push items towards the center of the conveyor they don't go overspeed.
+                // We also don't want this to apply to mobs as they apply their own friction and otherwise
+                // they'll go too slow.
+                if (!_mover.UsedMobMovement.TryGetValue(ent.Entity.Owner, out var usedMob) || !usedMob)
+                {
+                    _mover.Friction(0f, frameTime: frameTime, friction: 5f, ref velocity);
+                }
+
+                SharedMoverController.Accelerate(ref velocity, targetDir, 20f, frameTime);
+            }
+            else if (!_mover.UsedMobMovement.TryGetValue(ent.Entity.Owner, out var usedMob) || !usedMob)
+            {
+                // Need friction to outweigh the movement as it will bounce a bit against the wall.
+                // This facilitates being able to sleep entities colliding into walls.
+                _mover.Friction(0f, frameTime: frameTime, friction: 40f, ref velocity);
+            }
+
+            PhysicsSystem.SetLinearVelocity(ent.Entity.Owner, velocity, wakeBody: false);
+
+            if (!IsConveyed((ent.Entity.Owner, ent.Entity.Comp2)))
+            {
+                RemComp<ConveyedComponent>(ent.Entity.Owner);
+            }
         }
     }
 
-    private bool TryConvey(Entity<ConveyedComponent, PhysicsComponent, TransformComponent> entity, bool prediction, float frameTime)
+    private void SetConveying(EntityUid uid, ConveyedComponent conveyed, bool value)
     {
-        var physics = entity.Comp2;
-        var xform = entity.Comp3;
-        var contacting = entity.Comp1.Colliding.Count > 0;
-
-        if (!contacting)
-            return false;
+        if (conveyed.Conveying == value)
+            return;
 
-        // Client moment
-        if (!physics.Predict && prediction)
-            return true;
+        conveyed.Conveying = value;
+        Dirty(uid, conveyed);
+    }
 
-        if (physics.BodyType == BodyType.Static)
-            return false;
+    /// <summary>
+    /// Gets the conveying direction for an entity.
+    /// </summary>
+    /// <returns>False if we should no longer be considered actively conveyed.</returns>
+    private bool TryConvey(Entity<ConveyedComponent, FixturesComponent, PhysicsComponent, TransformComponent> entity,
+        bool prediction,
+        out Vector2 direction)
+    {
+        direction = Vector2.Zero;
+        var fixtures = entity.Comp2;
+        var physics = entity.Comp3;
+        var xform = entity.Comp4;
 
-        if (!_gridQuery.TryComp(xform.GridUid, out var grid))
+        if (!physics.Awake)
             return true;
 
-        var gridTile = _maps.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
-        _conveyors.Clear();
-
-        // Check for any conveyors on the attached tile.
-        Lookup.GetLocalEntitiesIntersecting(xform.GridUid.Value, gridTile, _conveyors);
-        DebugTools.Assert(_conveyors.Count <= 1);
+        // Client moment
+        if (!physics.Predict && prediction)
+            return true;
 
-        // No more conveyors.
-        if (_conveyors.Count == 0)
+        if (xform.GridUid == null)
             return true;
 
         if (physics.BodyStatus == BodyStatus.InAir ||
@@ -130,48 +217,93 @@ public abstract class SharedConveyorController : VirtualController
 
         Entity<ConveyorComponent> bestConveyor = default;
         var bestSpeed = 0f;
+        var contacts = PhysicsSystem.GetContacts((entity.Owner, fixtures));
+        var transform = PhysicsSystem.GetPhysicsTransform(entity.Owner);
+        var anyConveyors = false;
 
-        foreach (var conveyor in _conveyors)
+        while (contacts.MoveNext(out var contact))
         {
-            if (conveyor.Comp.Speed > bestSpeed && CanRun(conveyor))
+            if (!contact.IsTouching)
+                continue;
+
+            // Check if our center is over their fixture otherwise ignore it.
+            var other = contact.OtherEnt(entity.Owner);
+
+            // Check for blocked, if so then we can't convey at all and just try to sleep
+            // Otherwise we may just keep pushing it into the wall
+
+            if (!_conveyorQuery.TryComp(other, out var conveyor))
+                continue;
+
+            anyConveyors = true;
+            var otherFixture = contact.OtherFixture(entity.Owner);
+            var otherTransform = PhysicsSystem.GetPhysicsTransform(other);
+
+            // Check if our center is over the conveyor, otherwise ignore it.
+            if (!_fixtures.TestPoint(otherFixture.Item2.Shape, otherTransform, transform.Position))
+                continue;
+
+            if (conveyor.Speed > bestSpeed && CanRun(conveyor))
             {
-                bestSpeed = conveyor.Comp.Speed;
-                bestConveyor = conveyor;
+                bestSpeed = conveyor.Speed;
+                bestConveyor = (other, conveyor);
             }
         }
 
+        // If we have no touching contacts we shouldn't be using conveyed anyway so nuke it.
+        if (!anyConveyors)
+            return true;
+
         if (bestSpeed == 0f || bestConveyor == default)
             return true;
 
         var comp = bestConveyor.Comp!;
-        var conveyorXform = _xformQuery.GetComponent(bestConveyor.Owner);
-        var conveyorPos = conveyorXform.LocalPosition;
-        var conveyorRot = conveyorXform.LocalRotation;
+        var conveyorXform = XformQuery.GetComponent(bestConveyor.Owner);
+        var (conveyorPos, conveyorRot) = TransformSystem.GetWorldPositionRotation(conveyorXform);
 
         conveyorRot += bestConveyor.Comp!.Angle;
 
         if (comp.State == ConveyorState.Reverse)
             conveyorRot += MathF.PI;
 
-        var direction = conveyorRot.ToWorldVec();
+        var conveyorDirection = conveyorRot.ToWorldVec();
+        direction = conveyorDirection;
 
-        var localPos = xform.LocalPosition;
-        var itemRelative = conveyorPos - localPos;
+        var itemRelative = conveyorPos - transform.Position;
+        direction = Convey(direction, bestSpeed, itemRelative);
 
-        localPos += Convey(direction, bestSpeed, frameTime, itemRelative);
+        // Do a final check for hard contacts so if we're conveying into a wall then NOOP.
+        contacts = PhysicsSystem.GetContacts((entity.Owner, fixtures));
 
-        TransformSystem.SetLocalPosition(entity, localPos, xform);
+        while (contacts.MoveNext(out var contact))
+        {
+            if (!contact.Hard || !contact.IsTouching)
+                continue;
+
+            var other = contact.OtherEnt(entity.Owner);
+            var otherBody = contact.OtherBody(entity.Owner);
 
-        // Force it awake for collisionwake reasons.
-        Physics.SetAwake((entity, physics), true);
-        Physics.SetSleepTime(physics, 0f);
+            // If the blocking body is dynamic then don't ignore it for this.
+            if (otherBody.BodyType != BodyType.Static)
+                continue;
+
+            var otherTransform = PhysicsSystem.GetPhysicsTransform(other);
+            var dotProduct = Vector2.Dot(otherTransform.Position - transform.Position, direction);
+
+            // TODO: This should probably be based on conveyor speed, this is mainly so we don't
+            // go to sleep when conveying and colliding with tables perpendicular to the conveyance direction.
+            if (dotProduct > 1.5f)
+            {
+                direction = Vector2.Zero;
+                return false;
+            }
+        }
 
         return true;
     }
-
-    private static Vector2 Convey(Vector2 direction, float speed, float frameTime, Vector2 itemRelative)
+    private static Vector2 Convey(Vector2 direction, float speed, Vector2 itemRelative)
     {
-        if (speed == 0 || direction.Length() == 0)
+        if (speed == 0 || direction.LengthSquared() == 0)
             return Vector2.Zero;
 
         /*
@@ -190,15 +322,15 @@ public abstract class SharedConveyorController : VirtualController
         if (r.Length() < 0.1)
         {
             var velocity = direction * speed;
-            return velocity * frameTime;
+            return velocity;
         }
         else
         {
             // Give a slight nudge in the direction of the conveyor to prevent
             // to collidable objects (e.g. crates) on the locker from getting stuck
             // pushing each other when rounding a corner.
-            var velocity = (r + direction*0.2f).Normalized() * speed;
-            return velocity * frameTime;
+            var velocity = (r + direction).Normalized() * speed;
+            return velocity;
         }
     }
 
@@ -206,4 +338,55 @@ public abstract class SharedConveyorController : VirtualController
     {
         return component.State != ConveyorState.Off && component.Powered;
     }
+
+    private record struct ConveyorJob : IParallelRobustJob
+    {
+        public int BatchSize => 16;
+
+        public List<(Entity<ConveyedComponent, FixturesComponent, PhysicsComponent, TransformComponent> Entity, Vector2 Direction, bool Result)> Conveyed = new();
+
+        public SharedConveyorController System;
+
+        public bool Prediction;
+
+        public ConveyorJob(SharedConveyorController controller)
+        {
+            System = controller;
+        }
+
+        public void Execute(int index)
+        {
+            var convey = Conveyed[index];
+
+            var result = System.TryConvey(
+                (convey.Entity.Owner, convey.Entity.Comp1, convey.Entity.Comp2, convey.Entity.Comp3, convey.Entity.Comp4),
+                Prediction, out var direction);
+
+            Conveyed[index] = (convey.Entity, direction, result);
+        }
+    }
+
+    /// <summary>
+    /// Checks an entity's contacts to see if it's still being conveyed.
+    /// </summary>
+    private bool IsConveyed(Entity<FixturesComponent?> ent)
+    {
+        if (!Resolve(ent.Owner, ref ent.Comp))
+            return false;
+
+        var contacts = PhysicsSystem.GetContacts(ent.Owner);
+
+        while (contacts.MoveNext(out var contact))
+        {
+            if (!contact.IsTouching)
+                continue;
+
+            var other = contact.OtherEnt(ent.Owner);
+
+            if (_conveyorQuery.HasComp(other))
+                return true;
+        }
+
+        return false;
+    }
 }
index 0cc097fd8b8d82586c4785cf4f093ded887fa401..2ac538e80f37a6c69f0a472b9987a19da4b71217 100644 (file)
@@ -1,10 +1,10 @@
 meta:
   format: 7
   category: Map
-  engineVersion: 249.0.0
+  engineVersion: 250.0.0
   forkId: ""
   forkVersion: ""
-  time: 03/23/2025 08:00:27
+  time: 03/27/2025 06:48:27
   entityCount: 25710
 maps:
 - 943
@@ -75883,6 +75883,9 @@ entities:
     - type: Transform
       pos: -14.52124,-43.447884
       parent: 60
+    - type: CollisionWake
+      enabled: False
+    - type: Conveyed
 - proto: FoodBoxDonkpocketPizza
   entities:
   - uid: 15714
index 17c8f2e434936d75099e9b3ba6a8314294c0158b..6722181b269edc0cdd604e64009a96be1859e02f 100644 (file)
@@ -13,7 +13,7 @@
     anchored: true
   - type: Sprite
     sprite: Structures/conveyor.rsi
-    state: conveyor_started_cw
+    state: conveyor_stopped_cw
     drawdepth: HighFloorObjects
   - type: ApcPowerReceiver
   - type: ExtensionCableReceiver
       conveyor:
         shape: !type:PolygonShape
           vertices:
-          - -0.49,-0.49
-          - 0.49,-0.49
-          - 0.49,0.49
-          - -0.49,0.49
+          - -0.50,-0.50
+          - 0.50,-0.50
+          - 0.50,0.50
+          - -0.50,0.50
         layer:
         - Impassable
         - MidImpassable