Mob movement rewrite (#35931)

* 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
This commit is contained in:
metalgearsloth
2025-03-28 09:29:02 +11:00
committed by GitHub
parent e98df217e2
commit 948588399c
8 changed files with 360 additions and 131 deletions

View 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;
@@ -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);

View 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);
}
}
}

View 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;
}

View 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;

View 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();

View File

@@ -2,124 +2,211 @@
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 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)
{
// Conveyed entities don't get friction, they just get wishdir applied so will inherently slowdown anyway.
args.Modifier = 0f;
}
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);
}
private void OnConveyedShutdown(Entity<ConveyedComponent> ent, ref ComponentShutdown args)
{
_wake.SetEnabled(ent.Owner, true);
}
private void OnConveyorStartup(Entity<ConveyorComponent> ent, ref ComponentStartup args)
{
AwakenConveyor(ent.Owner);
}
/// <summary>
/// Forcefully awakens all entities near the conveyor.
/// </summary>
protected virtual void AwakenConveyor(Entity<TransformComponent?> ent)
{
}
/// <summary>
/// Wakes all conveyed entities contacting this conveyor.
/// </summary>
protected void WakeConveyed(EntityUid conveyorUid)
{
var contacts = PhysicsSystem.GetContacts(conveyorUid);
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 || component.State == ConveyorState.Off)
if (!args.OtherFixture.Hard || args.OtherBody.BodyType == BodyType.Static)
return;
var conveyed = EnsureComp<ConveyedComponent>(otherUid);
if (conveyed.Colliding.Contains(uid))
return;
conveyed.Colliding.Add(uid);
Dirty(otherUid, conveyed);
}
private void OnConveyorEndCollide(Entity<ConveyorComponent> ent, ref EndCollideEvent args)
{
if (!TryComp(args.OtherEntity, out ConveyedComponent? conveyed))
return;
if (!conveyed.Colliding.Remove(ent.Owner))
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))
var query = EntityQueryEnumerator<ConveyedComponent, FixturesComponent, PhysicsComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var comp, out var fixtures, out var physics, out var xform))
{
if (TryConvey((uid, comp, physics, xform), prediction, frameTime))
_job.Conveyed.Add(((uid, comp, fixtures, physics, xform), Vector2.Zero, false));
}
_parallel.ProcessNow(_job, _job.Conveyed.Count);
foreach (var ent in _job.Conveyed)
{
if (!ent.Entity.Comp3.Predict && prediction)
continue;
_ents.Add(uid);
}
var physics = ent.Entity.Comp3;
var velocity = physics.LinearVelocity;
var targetDir = ent.Direction;
foreach (var ent in _ents)
// If mob is moving with the conveyor then combine the directions.
var wishDir = _mover.GetWishDir(ent.Entity.Owner);
if (Vector2.Dot(wishDir, targetDir) > 0f)
{
RemComp<ConveyedComponent>(ent);
}
targetDir += wishDir;
}
private bool TryConvey(Entity<ConveyedComponent, PhysicsComponent, TransformComponent> entity, bool prediction, float frameTime)
if (ent.Result)
{
var physics = entity.Comp2;
var xform = entity.Comp3;
var contacting = entity.Comp1.Colliding.Count > 0;
SetConveying(ent.Entity.Owner, ent.Entity.Comp1, targetDir.LengthSquared() > 0f);
if (!contacting)
return false;
// 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 void SetConveying(EntityUid uid, ConveyedComponent conveyed, bool value)
{
if (conveyed.Conveying == value)
return;
conveyed.Conveying = value;
Dirty(uid, conveyed);
}
/// <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 (!physics.Awake)
return true;
// Client moment
if (!physics.Predict && prediction)
return true;
if (physics.BodyType == BodyType.Static)
return false;
if (!_gridQuery.TryComp(xform.GridUid, out var grid))
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);
// 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;
// Force it awake for collisionwake reasons.
Physics.SetAwake((entity, physics), true);
Physics.SetSleepTime(physics, 0f);
var other = contact.OtherEnt(entity.Owner);
var otherBody = contact.OtherBody(entity.Owner);
// 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;
}
}

View 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

View 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
@@ -24,10 +24,10 @@
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