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;
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;
[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()
{
_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);
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);
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);
}
}
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);
/// <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);
}
}
}
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);
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;
}
else
{
- if (worldTotal != Vector2.Zero || moveSpeedComponent?.FrictionNoInput == null)
+ if (wishDir != Vector2.Zero || moveSpeedComponent?.FrictionNoInput == null)
{
friction = tileDef?.MobFriction ?? moveSpeedComponent?.Friction ?? MovementSpeedModifierComponent.DefaultFriction;
}
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) &&
}
}
}
+ }
- 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)
}
}
- 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();
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();
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 ||
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;
/*
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;
}
}
{
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;
+ }
}