Multi-threaded mob movement (#12611)

This commit is contained in:
metalgearsloth
2022-11-30 09:41:26 +11:00
committed by GitHub
parent e923b15e27
commit d8bc7e1cb7
5 changed files with 242 additions and 105 deletions

View File

@@ -1,11 +1,14 @@
using Content.Shared.Inventory;
using Content.Shared.Movement.Components; using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems; using Content.Shared.Movement.Systems;
using Content.Shared.Pulling.Components; using Content.Shared.Pulling.Components;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Shared.Containers;
using Robust.Shared.Physics; using Robust.Shared.Physics;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.Physics.Controllers namespace Content.Client.Physics.Controllers
{ {
@@ -134,8 +137,30 @@ namespace Content.Client.Physics.Controllers
} }
} }
var mobQuery = GetEntityQuery<MobMoverComponent>();
var inventoryQuery = GetEntityQuery<InventoryComponent>();
var containerQuery = GetEntityQuery<ContainerManagerComponent>();
var footQuery = GetEntityQuery<FootstepModifierComponent>();
DebugTools.Assert(!UsedMobMovement.ContainsKey(mover.Owner));
// Server-side should just be handled on its own so we'll just do this shizznit // Server-side should just be handled on its own so we'll just do this shizznit
HandleMobMovement(mover, body, xformMover, frameTime, xformQuery); HandleMobMovement(mover, body, xformMover, frameTime, xformQuery, mobQuery, inventoryQuery, containerQuery, footQuery, out var dirtyMover, out var linearVelocity, out var sound, out var audio);
MetaDataComponent? metadata = null;
if (dirtyMover)
{
Dirty(mover, metadata);
}
if (linearVelocity != null)
{
PhysicsSystem.SetLinearVelocity(body, linearVelocity.Value, false);
PhysicsSystem.SetAngularVelocity(body, 0f, false);
Dirty(body, metadata);
}
Audio.PlayPredicted(sound, mover.Owner, mover.Owner, audio);
} }
protected override bool CanSound() protected override bool CanSound()

View File

@@ -1,20 +1,28 @@
using System.Buffers;
using System.Threading.Tasks;
using Content.Server.Cargo.Components; using Content.Server.Cargo.Components;
using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Systems; using Content.Server.Shuttles.Systems;
using Content.Shared.Inventory;
using Content.Shared.Movement.Components; using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems; using Content.Shared.Movement.Systems;
using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Components;
using Content.Shared.Shuttles.Systems; using Content.Shared.Shuttles.Systems;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Threading;
using Robust.Shared.Utility;
namespace Content.Server.Physics.Controllers namespace Content.Server.Physics.Controllers
{ {
public sealed class MoverController : SharedMoverController public sealed class MoverController : SharedMoverController
{ {
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IParallelManager _parallel = default!;
[Dependency] private readonly ThrusterSystem _thruster = default!; [Dependency] private readonly ThrusterSystem _thruster = default!;
private Dictionary<ShuttleComponent, List<(PilotComponent, InputMoverComponent, TransformComponent)>> _shuttlePilots = new(); private Dictionary<ShuttleComponent, List<(PilotComponent, InputMoverComponent, TransformComponent)>> _shuttlePilots = new();
@@ -64,7 +72,12 @@ namespace Content.Server.Physics.Controllers
var xformQuery = GetEntityQuery<TransformComponent>(); var xformQuery = GetEntityQuery<TransformComponent>();
var moverQuery = GetEntityQuery<InputMoverComponent>(); var moverQuery = GetEntityQuery<InputMoverComponent>();
foreach (var mover in EntityQuery<InputMoverComponent>(true)) var movers = AllEntityQuery<InputMoverComponent>();
var totalCount = EntityManager.Count<InputMoverComponent>();
var moveInput = ArrayPool<(InputMoverComponent Mover, TransformComponent Transform, PhysicsComponent Physics)>.Shared.Rent(totalCount);
var count = 0;
while (movers.MoveNext(out var mover))
{ {
if (relayQuery.TryGetComponent(mover.Owner, out var relayed) && relayed.RelayEntity != null) if (relayQuery.TryGetComponent(mover.Owner, out var relayed) && relayed.RelayEntity != null)
{ {
@@ -83,13 +96,11 @@ namespace Content.Server.Physics.Controllers
continue; continue;
} }
PhysicsComponent? body = null; PhysicsComponent? body;
TransformComponent? xformMover = xform;
if (mover.ToParent && relayQuery.HasComponent(xform.ParentUid)) if (mover.ToParent && relayQuery.HasComponent(xform.ParentUid))
{ {
if (!bodyQuery.TryGetComponent(xform.ParentUid, out body) || if (!bodyQuery.TryGetComponent(xform.ParentUid, out body) ||
!TryComp(xform.ParentUid, out xformMover)) !xformQuery.HasComponent(xform.ParentUid))
{ {
continue; continue;
} }
@@ -99,9 +110,64 @@ namespace Content.Server.Physics.Controllers
continue; continue;
} }
HandleMobMovement(mover, body, xformMover, frameTime, xformQuery); DebugTools.Assert(!UsedMobMovement.ContainsKey(mover.Owner));
// To avoid threading issues on adding dictionary entries later.
UsedMobMovement[mover.Owner] = false;
moveInput[count++] = (mover, xform, body);
} }
var moveResults = ArrayPool<(bool DirtyMover, Vector2? LinearVelocity, SoundSpecifier? sound, AudioParams audio)>.Shared.Rent(count);
var mobQuery = GetEntityQuery<MobMoverComponent>();
var inventoryQuery = GetEntityQuery<InventoryComponent>();
var containerQuery = GetEntityQuery<ContainerManagerComponent>();
var footQuery = GetEntityQuery<FootstepModifierComponent>();
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = _parallel.ParallelProcessCount,
};
Parallel.For(0, count, options, i =>
{
var (mover, xform, body) = moveInput[i];
HandleMobMovement(mover, body, xform, frameTime, xformQuery, mobQuery, inventoryQuery, containerQuery, footQuery, out var dirtyMover, out var linearVelocity, out var sound, out var audio);
moveResults[i] = (dirtyMover, linearVelocity, sound, audio);
});
var metaQuery = GetEntityQuery<MetaDataComponent>();
for (var i = 0; i < count; i++)
{
var results = moveResults[i];
var input = moveInput[i];
MetaDataComponent? metadata = null;
// Calling dirty isn't thread-safe sadly.
if (results.DirtyMover)
{
metadata ??= metaQuery.GetComponent(input.Mover.Owner);
Dirty(input.Mover, metadata);
}
if (results.LinearVelocity != null)
{
metadata ??= metaQuery.GetComponent(input.Physics.Owner);
PhysicsSystem.SetLinearVelocity(input.Physics, results.LinearVelocity.Value, false);
PhysicsSystem.SetAngularVelocity(input.Physics, 0f, false);
Dirty(input.Physics, metadata);
}
if (results.sound != null)
{
Audio.PlayPredicted(results.sound, input.Mover.Owner, input.Mover.Owner, results.audio);
}
moveInput[i] = default;
}
ArrayPool<(bool DirtyMover, Vector2? LinearVelocity, SoundSpecifier? Sound, AudioParams Audio)>.Shared.Return(moveResults);
ArrayPool<(InputMoverComponent, TransformComponent, PhysicsComponent)>.Shared.Return(moveInput);
HandleShuttleMovement(frameTime); HandleShuttleMovement(frameTime);
} }

View File

@@ -1,5 +1,6 @@
using Content.Shared.Movement.Systems; using Content.Shared.Movement.Systems;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Timing; using Robust.Shared.Timing;
namespace Content.Shared.Movement.Components namespace Content.Shared.Movement.Components
@@ -61,8 +62,8 @@ namespace Content.Shared.Movement.Components
/// <summary> /// <summary>
/// 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>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite), DataField("lerpTarget", customTypeSerializer: typeof(TimeOffsetSerializer))]
public float LerpAccumulator; public TimeSpan LerpTarget;
public const float LerpTime = 1.0f; public const float LerpTime = 1.0f;

View File

@@ -80,7 +80,7 @@ namespace Content.Shared.Movement.Systems
component.RelativeRotation = state.RelativeRotation; component.RelativeRotation = state.RelativeRotation;
component.TargetRelativeRotation = state.TargetRelativeRotation; component.TargetRelativeRotation = state.TargetRelativeRotation;
component.RelativeEntity = state.RelativeEntity; component.RelativeEntity = state.RelativeEntity;
component.LerpAccumulator = state.LerpAccumulator; component.LerpTarget = state.LerpTarget;
} }
private void OnInputGetState(EntityUid uid, InputMoverComponent component, ref ComponentGetState args) private void OnInputGetState(EntityUid uid, InputMoverComponent component, ref ComponentGetState args)
@@ -91,7 +91,7 @@ namespace Content.Shared.Movement.Systems
component.RelativeRotation, component.RelativeRotation,
component.TargetRelativeRotation, component.TargetRelativeRotation,
component.RelativeEntity, component.RelativeEntity,
component.LerpAccumulator); component.LerpTarget);
} }
private void ShutdownInput() private void ShutdownInput()
@@ -118,13 +118,73 @@ namespace Content.Shared.Movement.Systems
public void ResetCamera(EntityUid uid) public void ResetCamera(EntityUid uid)
{ {
if (CameraRotationLocked || !TryComp<InputMoverComponent>(uid, out var mover) || mover.TargetRelativeRotation.Equals(Angle.Zero)) if (CameraRotationLocked ||
!TryComp<InputMoverComponent>(uid, out var mover))
{
return;
}
// If we updated parent then cancel the accumulator and force it now.
var xformQuery = GetEntityQuery<TransformComponent>();
if (!TryUpdateRelative(mover, xformQuery.GetComponent(uid), xformQuery) && mover.TargetRelativeRotation.Equals(Angle.Zero))
return; return;
mover.LerpTarget = TimeSpan.Zero;
mover.TargetRelativeRotation = Angle.Zero; mover.TargetRelativeRotation = Angle.Zero;
Dirty(mover); Dirty(mover);
} }
private bool TryUpdateRelative(InputMoverComponent mover, TransformComponent xform, EntityQuery<TransformComponent> xformQuery)
{
var relative = xform.GridUid;
relative ??= xform.MapUid;
// So essentially what we want:
// 1. If we go from grid to map then preserve our rotation and continue as usual
// 2. If we go from grid -> grid then (after lerp time) snap to nearest cardinal (probably imperceptible)
// 3. If we go from map -> grid then (after lerp time) snap to nearest cardinal
if (mover.RelativeEntity.Equals(relative))
return false;
// Okay need to get our old relative rotation with respect to our new relative rotation
// e.g. if we were right side up on our current grid need to get what that is on our new grid.
var currentRotation = Angle.Zero;
var targetRotation = Angle.Zero;
// Get our current relative rotation
if (xformQuery.TryGetComponent(mover.RelativeEntity, out var oldRelativeXform))
{
currentRotation = _transform.GetWorldRotation(oldRelativeXform, xformQuery) + mover.RelativeRotation;
}
if (xformQuery.TryGetComponent(relative, out var relativeXform))
{
// This is our current rotation relative to our new parent.
mover.RelativeRotation = (currentRotation - _transform.GetWorldRotation(relativeXform, xformQuery)).FlipPositive();
}
// If we went from grid -> map we'll preserve our worldrotation
if (relative != null && _mapManager.IsMap(relative.Value))
{
targetRotation = currentRotation.FlipPositive().Reduced();
}
// If we went from grid -> grid OR grid -> map then snap the target to cardinal and lerp there.
// OR just rotate to zero (depending on cvar)
else if (relative != null && _mapManager.IsGrid(relative.Value))
{
if (CameraRotationLocked)
targetRotation = Angle.Zero;
else
targetRotation = mover.RelativeRotation.GetCardinalDir().ToAngle().Reduced();
}
mover.RelativeEntity = relative;
mover.TargetRelativeRotation = targetRotation;
return true;
}
public Angle GetParentGridAngle(InputMoverComponent mover, EntityQuery<TransformComponent> xformQuery) public Angle GetParentGridAngle(InputMoverComponent mover, EntityQuery<TransformComponent> xformQuery)
{ {
var rotation = mover.RelativeRotation; var rotation = mover.RelativeRotation;
@@ -161,16 +221,16 @@ namespace Content.Shared.Movement.Systems
// If we go on a grid and back off then just reset the accumulator. // If we go on a grid and back off then just reset the accumulator.
if (relative == component.RelativeEntity) if (relative == component.RelativeEntity)
{ {
if (component.LerpAccumulator != 0f) if (component.LerpTarget >= Timing.CurTime)
{ {
component.LerpAccumulator = 0f; component.LerpTarget = TimeSpan.Zero;
Dirty(component); Dirty(component);
} }
return; return;
} }
component.LerpAccumulator = InputMoverComponent.LerpTime; component.LerpTarget = TimeSpan.FromSeconds(InputMoverComponent.LerpTime) + Timing.CurTime;
Dirty(component); Dirty(component);
} }
@@ -499,16 +559,16 @@ namespace Content.Shared.Movement.Systems
/// </summary> /// </summary>
public Angle TargetRelativeRotation; public Angle TargetRelativeRotation;
public EntityUid? RelativeEntity; public EntityUid? RelativeEntity;
public float LerpAccumulator = 0f; public TimeSpan LerpTarget;
public InputMoverComponentState(MoveButtons buttons, bool canMove, Angle relativeRotation, Angle targetRelativeRotation, EntityUid? relativeEntity, float lerpAccumulator) public InputMoverComponentState(MoveButtons buttons, bool canMove, Angle relativeRotation, Angle targetRelativeRotation, EntityUid? relativeEntity, TimeSpan lerpTarget)
{ {
Buttons = buttons; Buttons = buttons;
CanMove = canMove; CanMove = canMove;
RelativeRotation = relativeRotation; RelativeRotation = relativeRotation;
TargetRelativeRotation = targetRelativeRotation; TargetRelativeRotation = targetRelativeRotation;
RelativeEntity = relativeEntity; RelativeEntity = relativeEntity;
LerpAccumulator = lerpAccumulator; LerpTarget = lerpTarget;
} }
} }

View File

@@ -36,7 +36,7 @@ namespace Content.Shared.Movement.Systems
[Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedGravitySystem _gravity = default!; [Dependency] private readonly SharedGravitySystem _gravity = default!;
[Dependency] private readonly SharedMobStateSystem _mobState = default!; [Dependency] private readonly SharedMobStateSystem _mobState = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] protected readonly SharedAudioSystem Audio = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly TagSystem _tags = default!; [Dependency] private readonly TagSystem _tags = default!;
@@ -96,69 +96,35 @@ namespace Content.Shared.Movement.Systems
/// <summary> /// <summary>
/// Movement while considering actionblockers, weightlessness, etc. /// Movement while considering actionblockers, weightlessness, etc.
/// </summary> /// </summary>
/// <remarks>
/// Yes this signature is massive but this is also called a lot.
/// </remarks>
protected void HandleMobMovement( protected void HandleMobMovement(
InputMoverComponent mover, InputMoverComponent mover,
PhysicsComponent physicsComponent, PhysicsComponent physicsComponent,
TransformComponent xform, TransformComponent xform,
float frameTime, float frameTime,
EntityQuery<TransformComponent> xformQuery) EntityQuery<TransformComponent> xformQuery,
EntityQuery<MobMoverComponent> mobQuery,
EntityQuery<InventoryComponent> inventoryQuery,
EntityQuery<ContainerManagerComponent> containerQuery,
EntityQuery<FootstepModifierComponent> footQuery,
out bool dirtyMover,
out Vector2? linearVelocity,
out SoundSpecifier? sound,
out AudioParams audio)
{ {
DebugTools.Assert(!UsedMobMovement.ContainsKey(mover.Owner)); dirtyMover = false;
linearVelocity = Vector2.Zero;
sound = null;
audio = default;
// Update relative movement // Update relative movement
if (mover.LerpAccumulator > 0f) if (mover.LerpTarget < Timing.CurTime)
{ {
Dirty(mover); if (TryUpdateRelative(mover, xform, xformQuery))
mover.LerpAccumulator -= frameTime;
if (mover.LerpAccumulator <= 0f)
{ {
mover.LerpAccumulator = 0f; dirtyMover = true;
var relative = xform.GridUid;
relative ??= xform.MapUid;
// So essentially what we want:
// 1. If we go from grid to map then preserve our rotation and continue as usual
// 2. If we go from grid -> grid then (after lerp time) snap to nearest cardinal (probably imperceptible)
// 3. If we go from map -> grid then (after lerp time) snap to nearest cardinal
if (!mover.RelativeEntity.Equals(relative))
{
// Okay need to get our old relative rotation with respect to our new relative rotation
// e.g. if we were right side up on our current grid need to get what that is on our new grid.
var currentRotation = Angle.Zero;
var targetRotation = Angle.Zero;
// Get our current relative rotation
if (xformQuery.TryGetComponent(mover.RelativeEntity, out var oldRelativeXform))
{
currentRotation = oldRelativeXform.WorldRotation + mover.RelativeRotation;
}
if (xformQuery.TryGetComponent(relative, out var relativeXform))
{
// This is our current rotation relative to our new parent.
mover.RelativeRotation = (currentRotation - relativeXform.WorldRotation).FlipPositive();
}
// If we went from grid -> map we'll preserve our worldrotation
if (relative != null && _mapManager.IsMap(relative.Value))
{
targetRotation = currentRotation.FlipPositive().Reduced();
}
// If we went from grid -> grid OR grid -> map then snap the target to cardinal and lerp there.
// OR just rotate to zero (depending on cvar)
else if (relative != null && _mapManager.IsGrid(relative.Value))
{
if (CameraRotationLocked)
targetRotation = Angle.Zero;
else
targetRotation = mover.RelativeRotation.GetCardinalDir().ToAngle().Reduced();
}
mover.RelativeEntity = relative;
mover.TargetRelativeRotation = targetRotation;
}
} }
} }
@@ -183,13 +149,13 @@ namespace Content.Shared.Movement.Systems
mover.RelativeRotation += adjustment; mover.RelativeRotation += adjustment;
mover.RelativeRotation.FlipPositive(); mover.RelativeRotation.FlipPositive();
Dirty(mover); dirtyMover = true;
} }
else if (!angleDiff.Equals(Angle.Zero)) else if (!angleDiff.Equals(Angle.Zero))
{ {
mover.TargetRelativeRotation.FlipPositive(); mover.TargetRelativeRotation.FlipPositive();
mover.RelativeRotation = mover.TargetRelativeRotation; mover.RelativeRotation = mover.TargetRelativeRotation;
Dirty(mover); dirtyMover = true;
} }
if (!UseMobMovement(mover, physicsComponent)) if (!UseMobMovement(mover, physicsComponent))
@@ -217,7 +183,7 @@ namespace Content.Shared.Movement.Systems
// No gravity: is our entity touching anything? // No gravity: is our entity touching anything?
touching = ev.CanMove; touching = ev.CanMove;
if (!touching && TryComp<MobMoverComponent>(xform.Owner, out var mobMover)) if (!touching && mobQuery.TryGetComponent(xform.Owner, out var mobMover))
touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, physicsComponent); touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, physicsComponent);
} }
} }
@@ -274,19 +240,20 @@ namespace Content.Shared.Movement.Systems
{ {
// This should have its event run during island solver soooo // This should have its event run during island solver soooo
xform.DeferUpdates = true; xform.DeferUpdates = true;
xform.WorldRotation = worldTotal.ToWorldAngle(); _transform.SetWorldRotation(xform, worldTotal.ToWorldAngle(), xformQuery);
xform.DeferUpdates = false; xform.DeferUpdates = false;
if (!weightless && TryComp<MobMoverComponent>(mover.Owner, out var mobMover) && if (!weightless && TryComp<MobMoverComponent>(mover.Owner, out var mobMover) &&
TryGetSound(weightless, mover, mobMover, xform, out var sound)) TryGetSound(weightless, mover, mobMover, xform, inventoryQuery, containerQuery, footQuery, out var soundSpec))
{ {
var soundModifier = mover.Sprinting ? 1.0f : FootstepWalkingAddedVolumeMultiplier; var soundModifier = mover.Sprinting ? 1.0f : FootstepWalkingAddedVolumeMultiplier;
var audioParams = sound.Params var audioParams = soundSpec.Params
.WithVolume(FootstepVolume * soundModifier) .WithVolume(FootstepVolume * soundModifier)
.WithVariation(sound.Params.Variation ?? FootstepVariation); .WithVariation(soundSpec.Params.Variation ?? FootstepVariation);
_audio.PlayPredicted(sound, mover.Owner, mover.Owner, audioParams); sound = soundSpec;
audio = audioParams;
} }
} }
@@ -295,10 +262,13 @@ namespace Content.Shared.Movement.Systems
if (!weightless || touching) if (!weightless || touching)
Accelerate(ref velocity, in worldTotal, accel, frameTime); Accelerate(ref velocity, in worldTotal, accel, frameTime);
PhysicsSystem.SetLinearVelocity(physicsComponent, velocity); if (physicsComponent.LinearVelocity.EqualsApprox(velocity, 0.0001f) &&
MathHelper.CloseTo(physicsComponent.AngularVelocity, 0f))
{
return;
}
// Ensures that players do not spiiiiiiin linearVelocity = velocity;
PhysicsSystem.SetAngularVelocity(physicsComponent, 0);
} }
private void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref Vector2 velocity) private void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref Vector2 velocity)
@@ -379,7 +349,16 @@ namespace Content.Shared.Movement.Systems
protected abstract bool CanSound(); protected abstract bool CanSound();
private bool TryGetSound(bool weightless, InputMoverComponent mover, MobMoverComponent mobMover, TransformComponent xform, [NotNullWhen(true)] out SoundSpecifier? sound) private bool TryGetSound(
bool weightless,
InputMoverComponent mover,
MobMoverComponent mobMover,
TransformComponent xform,
EntityQuery<InventoryComponent> inventoryQuery,
EntityQuery<ContainerManagerComponent> containerQuery,
EntityQuery<FootstepModifierComponent> footQuery,
[NotNullWhen(true)] out SoundSpecifier? sound)
{ {
sound = null; sound = null;
@@ -413,18 +392,21 @@ namespace Content.Shared.Movement.Systems
if (mobMover.StepSoundDistance < distanceNeeded) return false; if (mobMover.StepSoundDistance < distanceNeeded) return false;
mobMover.StepSoundDistance -= distanceNeeded; mobMover.StepSoundDistance -= distanceNeeded;
EntityUid? shoes = null;
if (_inventory.TryGetSlotEntity(mover.Owner, "shoes", out var shoes) && if (inventoryQuery.TryGetComponent(mover.Owner, out var inventory) &&
EntityManager.TryGetComponent<FootstepModifierComponent>(shoes, out var modifier)) containerQuery.TryGetComponent(mover.Owner, out var containerManager) &&
_inventory.TryGetSlotEntity(mover.Owner, "shoes", out shoes, inventory, containerManager) &&
footQuery.TryGetComponent(shoes, out var modifier))
{ {
sound = modifier.Sound; sound = modifier.Sound;
return true; return true;
} }
return TryGetFootstepSound(coordinates, shoes != null, out sound); return TryGetFootstepSound(coordinates, shoes != null, footQuery, out sound);
} }
private bool TryGetFootstepSound(EntityCoordinates coordinates, bool haveShoes, [NotNullWhen(true)] out SoundSpecifier? sound) private bool TryGetFootstepSound(EntityCoordinates coordinates, bool haveShoes, EntityQuery<FootstepModifierComponent> footQuery, [NotNullWhen(true)] out SoundSpecifier? sound)
{ {
sound = null; sound = null;
var gridUid = coordinates.GetGridUid(EntityManager); var gridUid = coordinates.GetGridUid(EntityManager);
@@ -432,7 +414,7 @@ namespace Content.Shared.Movement.Systems
// Fallback to the map // Fallback to the map
if (gridUid == null) if (gridUid == null)
{ {
if (TryComp<FootstepModifierComponent>(coordinates.GetMapUid(EntityManager), out var modifier)) if (footQuery.TryGetComponent(coordinates.GetMapUid(EntityManager), out var modifier))
{ {
sound = modifier.Sound; sound = modifier.Sound;
return true; return true;
@@ -444,18 +426,21 @@ namespace Content.Shared.Movement.Systems
var grid = _mapManager.GetGrid(gridUid.Value); var grid = _mapManager.GetGrid(gridUid.Value);
var tile = grid.GetTileRef(coordinates); var tile = grid.GetTileRef(coordinates);
if (tile.IsSpace(_tileDefinitionManager)) return false; if (tile.IsSpace(_tileDefinitionManager))
return false;
// If the coordinates have a FootstepModifier component // If the coordinates have a FootstepModifier component
// i.e. component that emit sound on footsteps emit that sound // i.e. component that emit sound on footsteps emit that sound
foreach (var maybeFootstep in grid.GetAnchoredEntities(tile.GridIndices)) var anchoredEnumerator = grid.GetAnchoredEntitiesEnumerator(tile.GridIndices);
{
if (EntityManager.TryGetComponent(maybeFootstep, out FootstepModifierComponent? footstep)) while (anchoredEnumerator.MoveNext(out var maybeFootstep))
{ {
if (!footQuery.TryGetComponent(maybeFootstep, out var footstep))
continue;
sound = footstep.Sound; sound = footstep.Sound;
return true; return true;
} }
}
// Walking on a tile. // Walking on a tile.
var def = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId]; var def = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId];