| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527 |
- using System.Diagnostics.CodeAnalysis;
- using System.Numerics;
- using Content.Shared.Bed.Sleep;
- using Content.Shared.CCVar;
- using Content.Shared.Friction;
- using Content.Shared.Gravity;
- using Content.Shared.Inventory;
- using Content.Shared.Maps;
- using Content.Shared.Mobs.Systems;
- using Content.Shared.Movement.Components;
- using Content.Shared.Movement.Events;
- using Content.Shared.Tag;
- using Robust.Shared.Audio;
- using Robust.Shared.Audio.Systems;
- using Robust.Shared.Configuration;
- using Robust.Shared.Containers;
- 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.Systems;
- using Robust.Shared.Timing;
- using Robust.Shared.Utility;
- using PullableComponent = Content.Shared.Movement.Pulling.Components.PullableComponent;
- using Robust.Shared.Maths; // Shitmed Change
- namespace Content.Shared.Movement.Systems;
- /// <summary>
- /// Handles player and NPC mob movement.
- /// NPCs are handled server-side only.
- /// </summary>
- public abstract partial class SharedMoverController : VirtualController
- {
- [Dependency] private readonly IConfigurationManager _configManager = default!;
- [Dependency] protected readonly IGameTiming Timing = default!;
- [Dependency] private readonly IMapManager _mapManager = default!;
- [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
- [Dependency] private readonly EntityLookupSystem _lookup = default!;
- [Dependency] private readonly InventorySystem _inventory = default!;
- [Dependency] private readonly MobStateSystem _mobState = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedContainerSystem _container = default!;
- [Dependency] private readonly SharedMapSystem _mapSystem = default!;
- [Dependency] private readonly SharedGravitySystem _gravity = default!;
- [Dependency] protected readonly SharedPhysicsSystem Physics = default!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly TagSystem _tags = default!;
- protected EntityQuery<InputMoverComponent> MoverQuery;
- protected EntityQuery<MobMoverComponent> MobMoverQuery;
- protected EntityQuery<MovementRelayTargetComponent> RelayTargetQuery;
- protected EntityQuery<MovementSpeedModifierComponent> ModifierQuery;
- protected EntityQuery<PhysicsComponent> PhysicsQuery;
- protected EntityQuery<RelayInputMoverComponent> RelayQuery;
- protected EntityQuery<PullableComponent> PullableQuery;
- protected EntityQuery<TransformComponent> XformQuery;
- protected EntityQuery<CanMoveInAirComponent> CanMoveInAirQuery;
- protected EntityQuery<NoRotateOnMoveComponent> NoRotateQuery;
- protected EntityQuery<FootstepModifierComponent> FootstepModifierQuery;
- protected EntityQuery<MapGridComponent> MapGridQuery;
- /// <summary>
- /// <see cref="CCVars.StopSpeed"/>
- /// </summary>
- private float _stopSpeed;
- private bool _relativeMovement;
- /// <summary>
- /// Cache the mob movement calculation to re-use elsewhere.
- /// </summary>
- public Dictionary<EntityUid, bool> UsedMobMovement = new();
- public override void Initialize()
- {
- base.Initialize();
- MoverQuery = GetEntityQuery<InputMoverComponent>();
- MobMoverQuery = GetEntityQuery<MobMoverComponent>();
- ModifierQuery = GetEntityQuery<MovementSpeedModifierComponent>();
- RelayTargetQuery = GetEntityQuery<MovementRelayTargetComponent>();
- PhysicsQuery = GetEntityQuery<PhysicsComponent>();
- RelayQuery = GetEntityQuery<RelayInputMoverComponent>();
- PullableQuery = GetEntityQuery<PullableComponent>();
- XformQuery = GetEntityQuery<TransformComponent>();
- NoRotateQuery = GetEntityQuery<NoRotateOnMoveComponent>();
- CanMoveInAirQuery = GetEntityQuery<CanMoveInAirComponent>();
- FootstepModifierQuery = GetEntityQuery<FootstepModifierComponent>();
- MapGridQuery = GetEntityQuery<MapGridComponent>();
- InitializeInput();
- InitializeRelay();
- Subs.CVar(_configManager, CCVars.RelativeMovement, value => _relativeMovement = value, true);
- Subs.CVar(_configManager, CCVars.StopSpeed, value => _stopSpeed = value, true);
- UpdatesBefore.Add(typeof(TileFrictionController));
- }
- public override void Shutdown()
- {
- base.Shutdown();
- ShutdownInput();
- }
- public override void UpdateAfterSolve(bool prediction, float frameTime)
- {
- base.UpdateAfterSolve(prediction, frameTime);
- UsedMobMovement.Clear();
- }
- /// <summary>
- /// Movement while considering actionblockers, weightlessness, etc.
- /// </summary>
- protected void HandleMobMovement(
- EntityUid uid,
- InputMoverComponent mover,
- EntityUid physicsUid,
- PhysicsComponent physicsComponent,
- TransformComponent xform,
- float frameTime)
- {
- var canMove = mover.CanMove;
- if (RelayTargetQuery.TryGetComponent(uid, out var relayTarget))
- {
- if (_mobState.IsIncapacitated(relayTarget.Source) ||
- TryComp<SleepingComponent>(relayTarget.Source, out _) ||
- !PhysicsQuery.TryGetComponent(relayTarget.Source, out var relayedPhysicsComponent) ||
- !MoverQuery.TryGetComponent(relayTarget.Source, out var relayedMover) ||
- !XformQuery.TryGetComponent(relayTarget.Source, out var relayedXform))
- {
- canMove = false;
- }
- else
- {
- mover.RelativeEntity = relayedMover.RelativeEntity;
- mover.RelativeRotation = relayedMover.RelativeRotation;
- mover.TargetRelativeRotation = relayedMover.TargetRelativeRotation;
- }
- }
- // Update relative movement
- // Shitmed Change Start
- else
- {
- if (mover.LerpTarget < Timing.CurTime)
- {
- if (TryComp(uid, out RelayInputMoverComponent? relay)
- && TryComp(relay.RelayEntity, out TransformComponent? relayXform))
- {
- if (TryUpdateRelative(mover, relayXform))
- Dirty(uid, mover);
- }
- else
- {
- if (TryUpdateRelative(mover, xform))
- Dirty(uid, mover);
- }
- }
- LerpRotation(uid, mover, frameTime);
- }
- // Shitmed Change End
- if (!canMove
- || physicsComponent.BodyStatus != BodyStatus.OnGround && !CanMoveInAirQuery.HasComponent(uid)
- || PullableQuery.TryGetComponent(uid, out var pullable) && pullable.BeingPulled)
- {
- UsedMobMovement[uid] = false;
- return;
- }
- UsedMobMovement[uid] = true;
- // Specifically don't use mover.Owner because that may be different to the actual physics body being moved.
- var weightless = _gravity.IsWeightless(physicsUid, physicsComponent, xform);
- var (walkDir, sprintDir) = GetVelocityInput(mover);
- var touching = false;
- // Handle wall-pushes.
- if (weightless)
- {
- if (xform.GridUid != null)
- touching = true;
- if (!touching)
- {
- var ev = new CanWeightlessMoveEvent(uid);
- RaiseLocalEvent(uid, ref ev, true);
- // No gravity: is our entity touching anything?
- touching = ev.CanMove;
- if (!touching && TryComp<MobMoverComponent>(uid, out var mobMover))
- touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, physicsUid, physicsComponent);
- }
- }
- // Get current tile def for things like speed/friction mods
- ContentTileDefinition? tileDef = null;
- // Don't bother getting the tiledef here if we're weightless or in-air
- // since no tile-based modifiers should be applying in that situation
- if (MapGridQuery.TryComp(xform.GridUid, out var gridComp)
- && _mapSystem.TryGetTileRef(xform.GridUid.Value, gridComp, xform.Coordinates, out var tile)
- && !(weightless || physicsComponent.BodyStatus == BodyStatus.InAir))
- {
- tileDef = (ContentTileDefinition)_tileDefinitionManager[tile.Tile.TypeId];
- }
- // Regular movement.
- // Target velocity.
- // This is relative to the map / grid we're on.
- var moveSpeedComponent = ModifierQuery.CompOrNull(uid);
- var walkSpeed = moveSpeedComponent?.CurrentWalkSpeed ?? MovementSpeedModifierComponent.DefaultBaseWalkSpeed;
- var sprintSpeed = moveSpeedComponent?.CurrentSprintSpeed ?? MovementSpeedModifierComponent.DefaultBaseSprintSpeed;
- var total = walkDir * walkSpeed + sprintDir * sprintSpeed;
- var parentRotation = GetParentGridAngle(mover);
- var worldTotal = _relativeMovement ? parentRotation.RotateVec(total) : total;
- DebugTools.Assert(MathHelper.CloseToPercent(total.Length(), worldTotal.Length()));
- var velocity = physicsComponent.LinearVelocity;
- float friction;
- float weightlessModifier;
- float accel;
- if (weightless)
- {
- if (gridComp == null && !MapGridQuery.HasComp(xform.GridUid))
- friction = moveSpeedComponent?.OffGridFriction ?? MovementSpeedModifierComponent.DefaultOffGridFriction;
- else if (worldTotal != Vector2.Zero && touching)
- friction = moveSpeedComponent?.WeightlessFriction ?? MovementSpeedModifierComponent.DefaultWeightlessFriction;
- else
- friction = moveSpeedComponent?.WeightlessFrictionNoInput ?? MovementSpeedModifierComponent.DefaultWeightlessFrictionNoInput;
- weightlessModifier = moveSpeedComponent?.WeightlessModifier ?? MovementSpeedModifierComponent.DefaultWeightlessModifier;
- accel = moveSpeedComponent?.WeightlessAcceleration ?? MovementSpeedModifierComponent.DefaultWeightlessAcceleration;
- }
- else
- {
- if (worldTotal != Vector2.Zero || moveSpeedComponent?.FrictionNoInput == null)
- {
- friction = tileDef?.MobFriction ?? moveSpeedComponent?.Friction ?? MovementSpeedModifierComponent.DefaultFriction;
- }
- else
- {
- friction = tileDef?.MobFrictionNoInput ?? moveSpeedComponent.FrictionNoInput ?? MovementSpeedModifierComponent.DefaultFrictionNoInput;
- }
- weightlessModifier = 1f;
- accel = tileDef?.MobAcceleration ?? moveSpeedComponent?.Acceleration ?? MovementSpeedModifierComponent.DefaultAcceleration;
- }
- var minimumFrictionSpeed = moveSpeedComponent?.MinimumFrictionSpeed ?? MovementSpeedModifierComponent.DefaultMinimumFrictionSpeed;
- Friction(minimumFrictionSpeed, frameTime, friction, ref velocity);
- if (worldTotal != 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);
- }
- if (!weightless && MobMoverQuery.TryGetComponent(uid, out var mobMover) &&
- TryGetSound(weightless, uid, mover, mobMover, xform, out var sound, tileDef: tileDef))
- {
- var soundModifier = mover.Sprinting ? 3.5f : 1.5f;
- var audioParams = sound.Params
- .WithVolume(sound.Params.Volume + soundModifier)
- .WithVariation(sound.Params.Variation ?? mobMover.FootstepVariation);
- // If we're a relay target then predict the sound for all relays.
- if (relayTarget != null)
- {
- _audio.PlayPredicted(sound, uid, relayTarget.Source, audioParams);
- }
- else
- {
- _audio.PlayPredicted(sound, uid, uid, audioParams);
- }
- }
- }
- worldTotal *= weightlessModifier;
- if (!weightless || touching)
- Accelerate(ref velocity, in worldTotal, accel, frameTime);
- PhysicsSystem.SetLinearVelocity(physicsUid, velocity, body: physicsComponent);
- // Ensures that players do not spiiiiiiin
- PhysicsSystem.SetAngularVelocity(physicsUid, 0, body: physicsComponent);
- }
- public void LerpRotation(EntityUid uid, InputMoverComponent mover, float frameTime)
- {
- var angleDiff = Angle.ShortestDistance(mover.RelativeRotation, mover.TargetRelativeRotation);
- // if we've just traversed then lerp to our target rotation.
- if (!angleDiff.EqualsApprox(Angle.Zero, 0.001))
- {
- var adjustment = angleDiff * 5f * frameTime;
- var minAdjustment = 0.01 * frameTime;
- if (angleDiff < 0)
- {
- adjustment = Math.Min(adjustment, -minAdjustment);
- adjustment = Math.Clamp(adjustment, angleDiff, -angleDiff);
- }
- else
- {
- adjustment = Math.Max(adjustment, minAdjustment);
- adjustment = Math.Clamp(adjustment, -angleDiff, angleDiff);
- }
- mover.RelativeRotation += adjustment;
- mover.RelativeRotation.FlipPositive();
- Dirty(uid, mover);
- }
- else if (!angleDiff.Equals(Angle.Zero))
- {
- mover.TargetRelativeRotation.FlipPositive();
- mover.RelativeRotation = mover.TargetRelativeRotation;
- Dirty(uid, mover);
- }
- }
- private void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref Vector2 velocity)
- {
- var speed = velocity.Length();
- if (speed < minimumFrictionSpeed)
- return;
- var drop = 0f;
- var control = MathF.Max(_stopSpeed, speed);
- drop += control * friction * frameTime;
- var newSpeed = MathF.Max(0f, speed - drop);
- if (newSpeed.Equals(speed))
- return;
- newSpeed /= speed;
- velocity *= newSpeed;
- }
- private 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();
- var currentSpeed = Vector2.Dot(currentVelocity, wishDir);
- var addSpeed = wishSpeed - currentSpeed;
- if (addSpeed <= 0f)
- return;
- var accelSpeed = accel * frameTime * wishSpeed;
- accelSpeed = MathF.Min(accelSpeed, addSpeed);
- currentVelocity += wishDir * accelSpeed;
- }
- public bool UseMobMovement(EntityUid uid)
- {
- return UsedMobMovement.TryGetValue(uid, out var used) && used;
- }
- /// <summary>
- /// Used for weightlessness to determine if we are near a wall.
- /// </summary>
- private bool IsAroundCollider(SharedPhysicsSystem broadPhaseSystem, TransformComponent transform, MobMoverComponent mover, EntityUid physicsUid, PhysicsComponent collider)
- {
- var enlargedAABB = _lookup.GetWorldAABB(physicsUid, transform).Enlarged(mover.GrabRangeVV);
- foreach (var otherCollider in broadPhaseSystem.GetCollidingEntities(transform.MapID, enlargedAABB))
- {
- if (otherCollider == collider)
- continue; // Don't try to push off of yourself!
- // Only allow pushing off of anchored things that have collision.
- if (otherCollider.BodyType != BodyType.Static ||
- !otherCollider.CanCollide ||
- ((collider.CollisionMask & otherCollider.CollisionLayer) == 0 &&
- (otherCollider.CollisionMask & collider.CollisionLayer) == 0) ||
- (TryComp(otherCollider.Owner, out PullableComponent? pullable) && pullable.BeingPulled))
- {
- continue;
- }
- return true;
- }
- return false;
- }
- protected abstract bool CanSound();
- private bool TryGetSound(
- bool weightless,
- EntityUid uid,
- InputMoverComponent mover,
- MobMoverComponent mobMover,
- TransformComponent xform,
- [NotNullWhen(true)] out SoundSpecifier? sound,
- ContentTileDefinition? tileDef = null)
- {
- sound = null;
- if (!CanSound() || !_tags.HasTag(uid, "FootstepSound"))
- return false;
- var coordinates = xform.Coordinates;
- var distanceNeeded = mover.Sprinting
- ? mobMover.StepSoundMoveDistanceRunning
- : mobMover.StepSoundMoveDistanceWalking;
- // Handle footsteps.
- if (!weightless)
- {
- // Can happen when teleporting between grids.
- if (!coordinates.TryDistance(EntityManager, mobMover.LastPosition, out var distance) ||
- distance > distanceNeeded)
- {
- mobMover.StepSoundDistance = distanceNeeded;
- }
- else
- {
- mobMover.StepSoundDistance += distance;
- }
- }
- else
- {
- // In space no one can hear you squeak
- return false;
- }
- mobMover.LastPosition = coordinates;
- if (mobMover.StepSoundDistance < distanceNeeded)
- return false;
- mobMover.StepSoundDistance -= distanceNeeded;
- if (FootstepModifierQuery.TryComp(uid, out var moverModifier))
- {
- sound = moverModifier.FootstepSoundCollection;
- return sound != null;
- }
- if (_inventory.TryGetSlotEntity(uid, "shoes", out var shoes) &&
- FootstepModifierQuery.TryComp(shoes, out var modifier))
- {
- sound = modifier.FootstepSoundCollection;
- return sound != null;
- }
- return TryGetFootstepSound(uid, xform, shoes != null, out sound, tileDef: tileDef);
- }
- private bool TryGetFootstepSound(
- EntityUid uid,
- TransformComponent xform,
- bool haveShoes,
- [NotNullWhen(true)] out SoundSpecifier? sound,
- ContentTileDefinition? tileDef = null)
- {
- sound = null;
- // Fallback to the map?
- if (!MapGridQuery.TryComp(xform.GridUid, out var grid))
- {
- if (FootstepModifierQuery.TryComp(xform.MapUid, out var modifier))
- {
- sound = modifier.FootstepSoundCollection;
- }
- return sound != null;
- }
- var position = grid.LocalToTile(xform.Coordinates);
- var soundEv = new GetFootstepSoundEvent(uid);
- // If the coordinates have a FootstepModifier component
- // i.e. component that emit sound on footsteps emit that sound
- var anchored = grid.GetAnchoredEntitiesEnumerator(position);
- while (anchored.MoveNext(out var maybeFootstep))
- {
- RaiseLocalEvent(maybeFootstep.Value, ref soundEv);
- if (soundEv.Sound != null)
- {
- sound = soundEv.Sound;
- return true;
- }
- if (FootstepModifierQuery.TryComp(maybeFootstep, out var footstep))
- {
- sound = footstep.FootstepSoundCollection;
- return sound != null;
- }
- }
- // Walking on a tile.
- // Tile def might have been passed in already from previous methods, so use that
- // if we have it
- if (tileDef == null && grid.TryGetTileRef(position, out var tileRef))
- {
- tileDef = (ContentTileDefinition)_tileDefinitionManager[tileRef.Tile.TypeId];
- }
- if (tileDef == null)
- return false;
- sound = haveShoes ? tileDef.FootstepSounds : tileDef.BarestepSounds;
- return sound != null;
- }
- }
|