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.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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
_parallel.ProcessNow(_job, _job.Conveyed.Count);
|
||||||
|
|
||||||
|
foreach (var ent in _job.Conveyed)
|
||||||
|
{
|
||||||
|
if (!ent.Entity.Comp3.Predict && prediction)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
_ents.Add(uid);
|
var physics = ent.Entity.Comp3;
|
||||||
}
|
var velocity = physics.LinearVelocity;
|
||||||
|
var targetDir = ent.Direction;
|
||||||
|
|
||||||
foreach (var ent in _ents)
|
// If mob is moving with the conveyor then combine the directions.
|
||||||
|
var wishDir = _mover.GetWishDir(ent.Entity.Owner);
|
||||||
|
|
||||||
|
if (Vector2.Dot(wishDir, targetDir) > 0f)
|
||||||
{
|
{
|
||||||
RemComp<ConveyedComponent>(ent);
|
targetDir += wishDir;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryConvey(Entity<ConveyedComponent, PhysicsComponent, TransformComponent> entity, bool prediction, float frameTime)
|
if (ent.Result)
|
||||||
{
|
{
|
||||||
var physics = entity.Comp2;
|
SetConveying(ent.Entity.Owner, ent.Entity.Comp1, targetDir.LengthSquared() > 0f);
|
||||||
var xform = entity.Comp3;
|
|
||||||
var contacting = entity.Comp1.Colliding.Count > 0;
|
|
||||||
|
|
||||||
if (!contacting)
|
// We apply friction here so when we push items towards the center of the conveyor they don't go overspeed.
|
||||||
return false;
|
// We also don't want this to apply to mobs as they apply their own friction and otherwise
|
||||||
|
// they'll go too slow.
|
||||||
|
if (!_mover.UsedMobMovement.TryGetValue(ent.Entity.Owner, out var usedMob) || !usedMob)
|
||||||
|
{
|
||||||
|
_mover.Friction(0f, frameTime: frameTime, friction: 5f, ref velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedMoverController.Accelerate(ref velocity, targetDir, 20f, frameTime);
|
||||||
|
}
|
||||||
|
else if (!_mover.UsedMobMovement.TryGetValue(ent.Entity.Owner, out var usedMob) || !usedMob)
|
||||||
|
{
|
||||||
|
// Need friction to outweigh the movement as it will bounce a bit against the wall.
|
||||||
|
// This facilitates being able to sleep entities colliding into walls.
|
||||||
|
_mover.Friction(0f, frameTime: frameTime, friction: 40f, ref velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsSystem.SetLinearVelocity(ent.Entity.Owner, velocity, wakeBody: false);
|
||||||
|
|
||||||
|
if (!IsConveyed((ent.Entity.Owner, ent.Entity.Comp2)))
|
||||||
|
{
|
||||||
|
RemComp<ConveyedComponent>(ent.Entity.Owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetConveying(EntityUid uid, ConveyedComponent conveyed, bool value)
|
||||||
|
{
|
||||||
|
if (conveyed.Conveying == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
conveyed.Conveying = value;
|
||||||
|
Dirty(uid, conveyed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the conveying direction for an entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>False if we should no longer be considered actively conveyed.</returns>
|
||||||
|
private bool TryConvey(Entity<ConveyedComponent, FixturesComponent, PhysicsComponent, TransformComponent> entity,
|
||||||
|
bool prediction,
|
||||||
|
out Vector2 direction)
|
||||||
|
{
|
||||||
|
direction = Vector2.Zero;
|
||||||
|
var fixtures = entity.Comp2;
|
||||||
|
var physics = entity.Comp3;
|
||||||
|
var xform = entity.Comp4;
|
||||||
|
|
||||||
|
if (!physics.Awake)
|
||||||
|
return true;
|
||||||
|
|
||||||
// Client moment
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user