diff --git a/Content.Server/Movement/Systems/PullController.cs b/Content.Server/Movement/Systems/PullController.cs index 4bd4b60371..f28ea952c8 100644 --- a/Content.Server/Movement/Systems/PullController.cs +++ b/Content.Server/Movement/Systems/PullController.cs @@ -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(pullableEnt); + continue; + } + var movingPosition = movingTo.Position; var ownerPosition = TransformSystem.GetWorldPosition(pullableXform); diff --git a/Content.Server/Physics/Controllers/ConveyorController.cs b/Content.Server/Physics/Controllers/ConveyorController.cs index bf6abff158..494bc8c6f0 100644 --- a/Content.Server/Physics/Controllers/ConveyorController.cs +++ b/Content.Server/Physics/Controllers/ConveyorController.cs @@ -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(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(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(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 /// /// 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. /// - private void AwakenEntities(EntityUid uid, ConveyorComponent component) + protected override void AwakenConveyor(Entity ent) { - var xformQuery = GetEntityQuery(); - var bodyQuery = GetEntityQuery(); - - 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); } } } diff --git a/Content.Shared/Conveyor/ConveyedComponent.cs b/Content.Shared/Conveyor/ConveyedComponent.cs index 25189d2182..212c4dc23f 100644 --- a/Content.Shared/Conveyor/ConveyedComponent.cs +++ b/Content.Shared/Conveyor/ConveyedComponent.cs @@ -3,11 +3,15 @@ using Robust.Shared.GameStates; namespace Content.Shared.Conveyor; /// -/// Indicates this entity is currently being conveyed. +/// Indicates this entity is currently contacting a conveyor and will subscribe to events as appropriate. /// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class ConveyedComponent : Component { - [ViewVariables, AutoNetworkedField] - public List Colliding = new(); + // TODO: Delete if pulling gets fixed. + /// + /// True if currently conveying. + /// + [DataField, AutoNetworkedField] + public bool Conveying; } diff --git a/Content.Shared/Movement/Components/InputMoverComponent.cs b/Content.Shared/Movement/Components/InputMoverComponent.cs index f1e34c90df..7c3b8b431a 100644 --- a/Content.Shared/Movement/Components/InputMoverComponent.cs +++ b/Content.Shared/Movement/Components/InputMoverComponent.cs @@ -32,7 +32,7 @@ namespace Content.Shared.Movement.Components /// /// Should our velocity be applied to our parent? /// - [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. + /// + /// Direction to move this tick. + /// + public Vector2 WishDir; + /// /// Entity our movement is relative to. /// @@ -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. /// [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] - [ViewVariables(VVAccess.ReadWrite)] public TimeSpan LerpTarget; public const float LerpTime = 1.0f; diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs index 5de74d7294..6456444080 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.cs @@ -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 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 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) + /// + /// Adjusts the current velocity to the target velocity based on the specified acceleration. + /// + 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(); diff --git a/Content.Shared/Physics/Controllers/SharedConveyorController.cs b/Content.Shared/Physics/Controllers/SharedConveyorController.cs index abcd2bc4a2..07bf6c7332 100644 --- a/Content.Shared/Physics/Controllers/SharedConveyorController.cs +++ b/Content.Shared/Physics/Controllers/SharedConveyorController.cs @@ -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 _gridQuery; - private EntityQuery _xformQuery; + private ConveyorJob _job; - private ValueList _ents = new(); - private HashSet> _conveyors = new(); + private EntityQuery _conveyorQuery; + private EntityQuery _conveyedQuery; + protected EntityQuery PhysicsQuery; + protected EntityQuery XformQuery; + + protected HashSet Intersecting = new(); public override void Initialize() { - _gridQuery = GetEntityQuery(); - _xformQuery = GetEntityQuery(); + _job = new ConveyorJob(this); + _conveyorQuery = GetEntityQuery(); + _conveyedQuery = GetEntityQuery(); + PhysicsQuery = GetEntityQuery(); + XformQuery = GetEntityQuery(); UpdatesAfter.Add(typeof(SharedMoverController)); + SubscribeLocalEvent(OnConveyedFriction); + SubscribeLocalEvent(OnConveyedStartup); + SubscribeLocalEvent(OnConveyedShutdown); + SubscribeLocalEvent(OnConveyorStartCollide); - SubscribeLocalEvent(OnConveyorEndCollide); + SubscribeLocalEvent(OnConveyorStartup); base.Initialize(); } - private void OnConveyorStartCollide(EntityUid uid, ConveyorComponent component, ref StartCollideEvent args) + private void OnConveyedFriction(Entity 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 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 ent, ref ComponentShutdown args) + { + _wake.SetEnabled(ent.Owner, true); + } + + private void OnConveyorStartup(Entity ent, ref ComponentStartup args) + { + AwakenConveyor(ent.Owner); + } + + /// + /// Forcefully awakens all entities near the conveyor. + /// + protected virtual void AwakenConveyor(Entity ent) + { + } + + /// + /// Wakes all conveyed entities contacting this conveyor. + /// + 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 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(otherUid); - - if (conveyed.Colliding.Contains(uid)) - return; - - conveyed.Colliding.Add(uid); - Dirty(otherUid, conveyed); - } - - private void OnConveyorEndCollide(Entity ent, ref EndCollideEvent args) - { - if (!TryComp(args.OtherEntity, out ConveyedComponent? conveyed)) - return; - - if (!conveyed.Colliding.Remove(ent.Owner)) - return; - - Dirty(args.OtherEntity, conveyed); + EnsureComp(otherUid); } public override void UpdateBeforeSolve(bool prediction, float frameTime) { base.UpdateBeforeSolve(prediction, frameTime); - var query = EntityQueryEnumerator(); - _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(); + + 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(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(ent.Entity.Owner); + } } } - private bool TryConvey(Entity 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); + } + + /// + /// Gets the conveying direction for an entity. + /// + /// False if we should no longer be considered actively conveyed. + private bool TryConvey(Entity 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 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 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); + } + } + + /// + /// Checks an entity's contacts to see if it's still being conveyed. + /// + private bool IsConveyed(Entity 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; + } } diff --git a/Resources/Maps/bagel.yml b/Resources/Maps/bagel.yml index 0cc097fd8b..2ac538e80f 100644 --- a/Resources/Maps/bagel.yml +++ b/Resources/Maps/bagel.yml @@ -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 diff --git a/Resources/Prototypes/Entities/Structures/conveyor.yml b/Resources/Prototypes/Entities/Structures/conveyor.yml index 17c8f2e434..6722181b26 100644 --- a/Resources/Prototypes/Entities/Structures/conveyor.yml +++ b/Resources/Prototypes/Entities/Structures/conveyor.yml @@ -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