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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 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)
|
||||
{
|
||||
// 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))
|
||||
continue;
|
||||
|
||||
_ents.Add(uid);
|
||||
_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 (conveyed.Conveying == value)
|
||||
return;
|
||||
|
||||
if (!contacting)
|
||||
return false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user