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.Movement.Components;
using Content.Server.Physics.Controllers; using Content.Server.Physics.Controllers;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Conveyor;
using Content.Shared.Gravity; using Content.Shared.Gravity;
using Content.Shared.Input; using Content.Shared.Input;
using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Components;
@@ -122,6 +123,12 @@ public sealed class PullController : VirtualController
var pulled = pullerComp.Pulling; 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)) if (!_pullableQuery.TryComp(pulled, out var pullable))
return false; return false;
@@ -257,6 +264,13 @@ public sealed class PullController : VirtualController
continue; 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 movingPosition = movingTo.Position;
var ownerPosition = TransformSystem.GetWorldPosition(pullableXform); var ownerPosition = TransformSystem.GetWorldPosition(pullableXform);

View File

@@ -1,7 +1,6 @@
using Content.Server.DeviceLinking.Events; using Content.Server.DeviceLinking.Events;
using Content.Server.DeviceLinking.Systems; using Content.Server.DeviceLinking.Systems;
using Content.Server.Materials; using Content.Server.Materials;
using Content.Server.Power.Components;
using Content.Shared.Conveyor; using Content.Shared.Conveyor;
using Content.Shared.Destructible; using Content.Shared.Destructible;
using Content.Shared.Maps; using Content.Shared.Maps;
@@ -10,7 +9,6 @@ using Content.Shared.Physics.Controllers;
using Content.Shared.Power; using Content.Shared.Power;
using Robust.Shared.Physics; using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems; using Robust.Shared.Physics.Systems;
namespace Content.Server.Physics.Controllers; namespace Content.Server.Physics.Controllers;
@@ -20,7 +18,6 @@ public sealed class ConveyorController : SharedConveyorController
[Dependency] private readonly FixtureSystem _fixtures = default!; [Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly DeviceLinkSystem _signalSystem = default!; [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
[Dependency] private readonly MaterialReclaimerSystem _materialReclaimer = default!; [Dependency] private readonly MaterialReclaimerSystem _materialReclaimer = default!;
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize() public override void Initialize()
@@ -40,7 +37,7 @@ public sealed class ConveyorController : SharedConveyorController
{ {
_signalSystem.EnsureSinkPorts(uid, component.ReversePort, component.ForwardPort, component.OffPort); _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(); var shape = new PolygonShape();
shape.SetAsBox(0.55f, 0.55f); shape.SetAsBox(0.55f, 0.55f);
@@ -57,7 +54,7 @@ public sealed class ConveyorController : SharedConveyorController
if (MetaData(uid).EntityLifeStage >= EntityLifeStage.Terminating) if (MetaData(uid).EntityLifeStage >= EntityLifeStage.Terminating)
return; return;
if (!TryComp<PhysicsComponent>(uid, out var physics)) if (!PhysicsQuery.TryComp(uid, out var physics))
return; return;
_fixtures.DestroyFixture(uid, ConveyorFixture, body: physics); _fixtures.DestroyFixture(uid, ConveyorFixture, body: physics);
@@ -87,13 +84,11 @@ public sealed class ConveyorController : SharedConveyorController
else if (args.Port == component.ForwardPort) else if (args.Port == component.ForwardPort)
{ {
AwakenEntities(uid, component);
SetState(uid, ConveyorState.Forward, component); SetState(uid, ConveyorState.Forward, component);
} }
else if (args.Port == component.ReversePort) else if (args.Port == component.ReversePort)
{ {
AwakenEntities(uid, component);
SetState(uid, ConveyorState.Reverse, component); SetState(uid, ConveyorState.Reverse, component);
} }
} }
@@ -108,8 +103,10 @@ public sealed class ConveyorController : SharedConveyorController
component.State = state; component.State = state;
if (TryComp<PhysicsComponent>(uid, out var physics)) if (state != ConveyorState.Off)
_broadphase.RegenerateContacts((uid, physics)); {
WakeConveyed(uid);
}
UpdateAppearance(uid, component); UpdateAppearance(uid, component);
Dirty(uid, component); Dirty(uid, component);
@@ -117,29 +114,29 @@ public sealed class ConveyorController : SharedConveyorController
/// <summary> /// <summary>
/// Awakens sleeping entities on the conveyor belt's tile when it's turned on. /// 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> /// </summary>
private void AwakenEntities(EntityUid uid, ConveyorComponent component) protected override void AwakenConveyor(Entity<TransformComponent?> ent)
{ {
var xformQuery = GetEntityQuery<TransformComponent>(); if (!XformQuery.Resolve(ent.Owner, ref ent.Comp))
var bodyQuery = GetEntityQuery<PhysicsComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform))
return; return;
var xform = ent.Comp;
var beltTileRef = xform.Coordinates.GetTileRef(EntityManager, MapManager); var beltTileRef = xform.Coordinates.GetTileRef(EntityManager, MapManager);
if (beltTileRef != null) 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; continue;
if (physics.BodyType != BodyType.Static) 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; namespace Content.Shared.Conveyor;
/// <summary> /// <summary>
/// Indicates this entity is currently being conveyed. /// Indicates this entity is currently contacting a conveyor and will subscribe to events as appropriate.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ConveyedComponent : Component public sealed partial class ConveyedComponent : Component
{ {
[ViewVariables, AutoNetworkedField] // TODO: Delete if pulling gets fixed.
public List<EntityUid> Colliding = new(); /// <summary>
/// True if currently conveying.
/// </summary>
[DataField, AutoNetworkedField]
public bool Conveying;
} }

View File

@@ -32,7 +32,7 @@ namespace Content.Shared.Movement.Components
/// <summary> /// <summary>
/// Should our velocity be applied to our parent? /// Should our velocity be applied to our parent?
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("toParent")] [DataField]
public bool ToParent = false; public bool ToParent = false;
public GameTick LastInputTick; public GameTick LastInputTick;
@@ -43,6 +43,12 @@ namespace Content.Shared.Movement.Components
public MoveButtons HeldMoveButtons = MoveButtons.None; 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> /// <summary>
/// Entity our movement is relative to. /// Entity our movement is relative to.
/// </summary> /// </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. /// If we traverse on / off a grid then set a timer to update our relative inputs.
/// </summary> /// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan LerpTarget; public TimeSpan LerpTarget;
public const float LerpTime = 1.0f; public const float LerpTime = 1.0f;

View File

@@ -155,7 +155,6 @@ public abstract partial class SharedMoverController : VirtualController
return; return;
} }
UsedMobMovement[uid] = true; UsedMobMovement[uid] = true;
// Specifically don't use mover.Owner because that may be different to the actual physics body being moved. // 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); var weightless = _gravity.IsWeightless(physicsUid, physicsComponent, xform);
@@ -203,20 +202,21 @@ public abstract partial class SharedMoverController : VirtualController
var total = walkDir * walkSpeed + sprintDir * sprintSpeed; var total = walkDir * walkSpeed + sprintDir * sprintSpeed;
var parentRotation = GetParentGridAngle(mover); 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 friction;
float weightlessModifier; float weightlessModifier;
float accel; float accel;
var velocity = physicsComponent.LinearVelocity;
// Whether we use weightless friction or not.
if (weightless) if (weightless)
{ {
if (gridComp == null && !MapGridQuery.HasComp(xform.GridUid)) if (gridComp == null && !MapGridQuery.HasComp(xform.GridUid))
friction = moveSpeedComponent?.OffGridFriction ?? MovementSpeedModifierComponent.DefaultOffGridFriction; friction = moveSpeedComponent?.OffGridFriction ?? MovementSpeedModifierComponent.DefaultOffGridFriction;
else if (worldTotal != Vector2.Zero && touching) else if (wishDir != Vector2.Zero && touching)
friction = moveSpeedComponent?.WeightlessFriction ?? MovementSpeedModifierComponent.DefaultWeightlessFriction; friction = moveSpeedComponent?.WeightlessFriction ?? MovementSpeedModifierComponent.DefaultWeightlessFriction;
else else
friction = moveSpeedComponent?.WeightlessFrictionNoInput ?? MovementSpeedModifierComponent.DefaultWeightlessFrictionNoInput; friction = moveSpeedComponent?.WeightlessFrictionNoInput ?? MovementSpeedModifierComponent.DefaultWeightlessFrictionNoInput;
@@ -226,7 +226,7 @@ public abstract partial class SharedMoverController : VirtualController
} }
else else
{ {
if (worldTotal != Vector2.Zero || moveSpeedComponent?.FrictionNoInput == null) if (wishDir != Vector2.Zero || moveSpeedComponent?.FrictionNoInput == null)
{ {
friction = tileDef?.MobFriction ?? moveSpeedComponent?.Friction ?? MovementSpeedModifierComponent.DefaultFriction; friction = tileDef?.MobFriction ?? moveSpeedComponent?.Friction ?? MovementSpeedModifierComponent.DefaultFriction;
} }
@@ -242,14 +242,27 @@ public abstract partial class SharedMoverController : VirtualController
var minimumFrictionSpeed = moveSpeedComponent?.MinimumFrictionSpeed ?? MovementSpeedModifierComponent.DefaultMinimumFrictionSpeed; var minimumFrictionSpeed = moveSpeedComponent?.MinimumFrictionSpeed ?? MovementSpeedModifierComponent.DefaultMinimumFrictionSpeed;
Friction(minimumFrictionSpeed, frameTime, friction, ref velocity); 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)) if (!NoRotateQuery.HasComponent(uid))
{ {
// TODO apparently this results in a duplicate move event because "This should have its event run during // 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? // island solver"??. So maybe SetRotation needs an argument to avoid raising an event?
var worldRot = _transform.GetWorldRotation(xform); 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) && 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) return mover.Comp.WishDir;
Accelerate(ref velocity, in worldTotal, accel, frameTime); }
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 mover.Comp.WishDir = wishDir;
PhysicsSystem.SetAngularVelocity(physicsUid, 0, body: physicsComponent); Dirty(mover);
} }
public void LerpRotation(EntityUid uid, InputMoverComponent mover, float frameTime) 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(); var speed = velocity.Length();
@@ -338,7 +358,10 @@ public abstract partial class SharedMoverController : VirtualController
velocity *= newSpeed; 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 wishDir = velocity != Vector2.Zero ? velocity.Normalized() : Vector2.Zero;
var wishSpeed = velocity.Length(); var wishSpeed = velocity.Length();

View File

@@ -2,124 +2,211 @@
using Content.Shared.Conveyor; using Content.Shared.Conveyor;
using Content.Shared.Gravity; using Content.Shared.Gravity;
using Content.Shared.Magic; using Content.Shared.Magic;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems; using Content.Shared.Movement.Systems;
using Robust.Shared.Collections; using Robust.Shared.Collections;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics; using Robust.Shared.Physics;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Controllers; using Robust.Shared.Physics.Controllers;
using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Events;
using Robust.Shared.Physics.Systems; using Robust.Shared.Physics.Systems;
using Robust.Shared.Utility; using Robust.Shared.Threading;
namespace Content.Shared.Physics.Controllers; namespace Content.Shared.Physics.Controllers;
public abstract class SharedConveyorController : VirtualController public abstract class SharedConveyorController : VirtualController
{ {
[Dependency] protected readonly IMapManager MapManager = default!; [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] protected readonly EntityLookupSystem Lookup = default!;
[Dependency] private readonly SharedMapSystem _maps = default!; [Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] protected readonly SharedPhysicsSystem Physics = default!; [Dependency] private readonly SharedGravitySystem _gravity = default!;
[Dependency] private readonly SharedGravitySystem _gravity = default!; [Dependency] private readonly SharedMoverController _mover = default!;
protected const string ConveyorFixture = "conveyor"; protected const string ConveyorFixture = "conveyor";
private EntityQuery<MapGridComponent> _gridQuery; private ConveyorJob _job;
private EntityQuery<TransformComponent> _xformQuery;
private ValueList<EntityUid> _ents = new(); private EntityQuery<ConveyorComponent> _conveyorQuery;
private HashSet<Entity<ConveyorComponent>> _conveyors = new(); private EntityQuery<ConveyedComponent> _conveyedQuery;
protected EntityQuery<PhysicsComponent> PhysicsQuery;
protected EntityQuery<TransformComponent> XformQuery;
protected HashSet<EntityUid> Intersecting = new();
public override void Initialize() public override void Initialize()
{ {
_gridQuery = GetEntityQuery<MapGridComponent>(); _job = new ConveyorJob(this);
_xformQuery = GetEntityQuery<TransformComponent>(); _conveyorQuery = GetEntityQuery<ConveyorComponent>();
_conveyedQuery = GetEntityQuery<ConveyedComponent>();
PhysicsQuery = GetEntityQuery<PhysicsComponent>();
XformQuery = GetEntityQuery<TransformComponent>();
UpdatesAfter.Add(typeof(SharedMoverController)); UpdatesAfter.Add(typeof(SharedMoverController));
SubscribeLocalEvent<ConveyedComponent, TileFrictionEvent>(OnConveyedFriction);
SubscribeLocalEvent<ConveyedComponent, ComponentStartup>(OnConveyedStartup);
SubscribeLocalEvent<ConveyedComponent, ComponentShutdown>(OnConveyedShutdown);
SubscribeLocalEvent<ConveyorComponent, StartCollideEvent>(OnConveyorStartCollide); SubscribeLocalEvent<ConveyorComponent, StartCollideEvent>(OnConveyorStartCollide);
SubscribeLocalEvent<ConveyorComponent, EndCollideEvent>(OnConveyorEndCollide); SubscribeLocalEvent<ConveyorComponent, ComponentStartup>(OnConveyorStartup);
base.Initialize(); 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; 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; return;
var conveyed = EnsureComp<ConveyedComponent>(otherUid); 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);
} }
public override void UpdateBeforeSolve(bool prediction, float frameTime) public override void UpdateBeforeSolve(bool prediction, float frameTime)
{ {
base.UpdateBeforeSolve(prediction, frameTime); base.UpdateBeforeSolve(prediction, frameTime);
var query = EntityQueryEnumerator<ConveyedComponent, TransformComponent, PhysicsComponent>(); _job.Prediction = prediction;
_ents.Clear(); _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));
continue;
_ents.Add(uid);
} }
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; if (conveyed.Conveying == value)
var xform = entity.Comp3; return;
var contacting = entity.Comp1.Colliding.Count > 0;
if (!contacting) conveyed.Conveying = value;
return false; 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 // Client moment
if (!physics.Predict && prediction) if (!physics.Predict && prediction)
return true; return true;
if (physics.BodyType == BodyType.Static) if (xform.GridUid == null)
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)
return true; return true;
if (physics.BodyStatus == BodyStatus.InAir || if (physics.BodyStatus == BodyStatus.InAir ||
@@ -130,48 +217,93 @@ public abstract class SharedConveyorController : VirtualController
Entity<ConveyorComponent> bestConveyor = default; Entity<ConveyorComponent> bestConveyor = default;
var bestSpeed = 0f; 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; bestSpeed = conveyor.Speed;
bestConveyor = conveyor; 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) if (bestSpeed == 0f || bestConveyor == default)
return true; return true;
var comp = bestConveyor.Comp!; var comp = bestConveyor.Comp!;
var conveyorXform = _xformQuery.GetComponent(bestConveyor.Owner); var conveyorXform = XformQuery.GetComponent(bestConveyor.Owner);
var conveyorPos = conveyorXform.LocalPosition; var (conveyorPos, conveyorRot) = TransformSystem.GetWorldPositionRotation(conveyorXform);
var conveyorRot = conveyorXform.LocalRotation;
conveyorRot += bestConveyor.Comp!.Angle; conveyorRot += bestConveyor.Comp!.Angle;
if (comp.State == ConveyorState.Reverse) if (comp.State == ConveyorState.Reverse)
conveyorRot += MathF.PI; conveyorRot += MathF.PI;
var direction = conveyorRot.ToWorldVec(); var conveyorDirection = conveyorRot.ToWorldVec();
direction = conveyorDirection;
var localPos = xform.LocalPosition; var itemRelative = conveyorPos - transform.Position;
var itemRelative = conveyorPos - localPos; 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. var other = contact.OtherEnt(entity.Owner);
Physics.SetAwake((entity, physics), true); var otherBody = contact.OtherBody(entity.Owner);
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; return true;
} }
private static Vector2 Convey(Vector2 direction, float speed, Vector2 itemRelative)
private static Vector2 Convey(Vector2 direction, float speed, float frameTime, Vector2 itemRelative)
{ {
if (speed == 0 || direction.Length() == 0) if (speed == 0 || direction.LengthSquared() == 0)
return Vector2.Zero; return Vector2.Zero;
/* /*
@@ -190,15 +322,15 @@ public abstract class SharedConveyorController : VirtualController
if (r.Length() < 0.1) if (r.Length() < 0.1)
{ {
var velocity = direction * speed; var velocity = direction * speed;
return velocity * frameTime; return velocity;
} }
else else
{ {
// Give a slight nudge in the direction of the conveyor to prevent // Give a slight nudge in the direction of the conveyor to prevent
// to collidable objects (e.g. crates) on the locker from getting stuck // to collidable objects (e.g. crates) on the locker from getting stuck
// pushing each other when rounding a corner. // pushing each other when rounding a corner.
var velocity = (r + direction*0.2f).Normalized() * speed; var velocity = (r + direction).Normalized() * speed;
return velocity * frameTime; return velocity;
} }
} }
@@ -206,4 +338,55 @@ public abstract class SharedConveyorController : VirtualController
{ {
return component.State != ConveyorState.Off && component.Powered; 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: meta:
format: 7 format: 7
category: Map category: Map
engineVersion: 249.0.0 engineVersion: 250.0.0
forkId: "" forkId: ""
forkVersion: "" forkVersion: ""
time: 03/23/2025 08:00:27 time: 03/27/2025 06:48:27
entityCount: 25710 entityCount: 25710
maps: maps:
- 943 - 943
@@ -75883,6 +75883,9 @@ entities:
- type: Transform - type: Transform
pos: -14.52124,-43.447884 pos: -14.52124,-43.447884
parent: 60 parent: 60
- type: CollisionWake
enabled: False
- type: Conveyed
- proto: FoodBoxDonkpocketPizza - proto: FoodBoxDonkpocketPizza
entities: entities:
- uid: 15714 - uid: 15714

View File

@@ -13,7 +13,7 @@
anchored: true anchored: true
- type: Sprite - type: Sprite
sprite: Structures/conveyor.rsi sprite: Structures/conveyor.rsi
state: conveyor_started_cw state: conveyor_stopped_cw
drawdepth: HighFloorObjects drawdepth: HighFloorObjects
- type: ApcPowerReceiver - type: ApcPowerReceiver
- type: ExtensionCableReceiver - type: ExtensionCableReceiver
@@ -24,10 +24,10 @@
conveyor: conveyor:
shape: !type:PolygonShape shape: !type:PolygonShape
vertices: vertices:
- -0.49,-0.49 - -0.50,-0.50
- 0.49,-0.49 - 0.50,-0.50
- 0.49,0.49 - 0.50,0.50
- -0.49,0.49 - -0.50,0.50
layer: layer:
- Impassable - Impassable
- MidImpassable - MidImpassable