Skip to content

Commit

Permalink
Merge pull request #16 from spess-empyrean/tile_movement
Browse files Browse the repository at this point in the history
Tile Movement AI Support, adds Tile Movement by default to all mobs
  • Loading branch information
spess-empyrean authored Jan 15, 2025
2 parents a1c56b5 + 0ff5b8a commit 1777096
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 15 deletions.
3 changes: 2 additions & 1 deletion Content.Server/NPC/Systems/NPCCombatSystem.Ranged.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ private void UpdateRanged(float frameTime)
var goalRotation = (targetSpot - worldPos).ToWorldAngle();
var rotationSpeed = comp.RotationSpeed;

if (!_rotate.TryRotateTo(uid, goalRotation, frameTime, comp.AccuracyThreshold, rotationSpeed?.Theta ?? double.MaxValue, xform))
if (Double.IsNaN(goalRotation.Theta) ||
!_rotate.TryRotateTo(uid, goalRotation, frameTime, comp.AccuracyThreshold, rotationSpeed?.Theta ?? double.MaxValue, xform))
{
continue;
}
Expand Down
41 changes: 40 additions & 1 deletion Content.Server/NPC/Systems/NPCSteeringSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
using Content.Server.Administration.Managers;
using Content.Server.Atmos;
using Content.Server.DoAfter;
using Content.Server.NPC.Components;
using Content.Server.NPC.Events;
Expand All @@ -28,7 +29,10 @@
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Content.Shared.Prying.Systems;
using Content.Shared.TileMovement;
using Microsoft.Extensions.ObjectPool;
using Robust.Server.GameObjects;


namespace Content.Server.NPC.Systems;

Expand Down Expand Up @@ -61,12 +65,15 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] protected readonly SharedPhysicsSystem PhysicsSystem = default!;

private EntityQuery<FixturesComponent> _fixturesQuery;
private EntityQuery<MovementSpeedModifierComponent> _modifierQuery;
private EntityQuery<NpcFactionMemberComponent> _factionQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<TransformComponent> _xformQuery;
private EntityQuery<TileMovementComponent> _tileMovementQuery;

private ObjectPool<HashSet<EntityUid>> _entSetPool =
new DefaultObjectPool<HashSet<EntityUid>>(new SetPolicy<EntityUid>());
Expand All @@ -86,6 +93,8 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem

private object _obstacles = new();

private TimeSpan CurrentTime => PhysicsSystem.EffectiveCurTime ?? Timing.CurTime;

public override void Initialize()
{
base.Initialize();
Expand All @@ -96,6 +105,7 @@ public override void Initialize()
_factionQuery = GetEntityQuery<NpcFactionMemberComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_tileMovementQuery = GetEntityQuery<TileMovementComponent>();

for (var i = 0; i < InterestDirections; i++)
{
Expand Down Expand Up @@ -391,7 +401,36 @@ private void Steer(

steering.LastSteerDirection = resultDirection;
DebugTools.Assert(!float.IsNaN(resultDirection.X));
SetDirection(mover, steering, resultDirection, false);
if (_tileMovementQuery.TryGetComponent(uid, out var tileMovement))
{
SetTileMovementDirection(xform, tileMovement, resultDirection);
}
else
{
SetDirection(mover, steering, resultDirection, false);
}
}

/// <summary>
/// Shitcode solution to get tile movement to work with AI pathing. Essentially converts the direction vector into a
/// target tile and manipulates values on the TileMovementComponent as to start a movement towards that location.
/// In an optimal world I would tear all of this movement code down and try something more modular.
/// </summary>
private void SetTileMovementDirection(
TransformComponent transform,
TileMovementComponent tileMovement,
Vector2 direction)
{
if (tileMovement.SlideActive || direction == Vector2.Zero)
return;

var targetLocation = transform.LocalPosition + (direction.Normalized() * 0.97f);

tileMovement.SlideActive = true;
tileMovement.Origin = new EntityCoordinates(transform.ParentUid, transform.LocalPosition);
tileMovement.Destination = SharedMoverController.SnapCoordinatesToTile(targetLocation);
tileMovement.MovementKeyInitialDownTime = CurrentTime;
tileMovement.CurrentSlideMoveButtons = MoveButtons.None;
}

private EntityCoordinates GetCoordinates(PathPoly poly)
Expand Down
32 changes: 19 additions & 13 deletions Content.Shared/Movement/Systems/SharedMoverController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Content.Shared.Tag;
using Content.Shared.Traits.Assorted.Components;
using Content.Shared.TileMovement;
using Microsoft.Extensions.Logging;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
Expand Down Expand Up @@ -95,7 +96,7 @@ public override void Initialize()
XformQuery = GetEntityQuery<TransformComponent>();
NoRotateQuery = GetEntityQuery<NoRotateOnMoveComponent>();
CanMoveInAirQuery = GetEntityQuery<CanMoveInAirComponent>();
TileMovementQuery = GetEntityQuery<TileMovement.TileMovementComponent>();
TileMovementQuery = GetEntityQuery<TileMovementComponent>();

InitializeInput();
InitializeRelay();
Expand Down Expand Up @@ -596,6 +597,9 @@ private bool TryGetFootstepSound(
/// pixel movement and the rigidity of tiles. Works surprisingly well.
/// Note: the code is intentionally separated here from everything else to make it easier to port and
/// to reduce the risk of merge conflicts.
/// However, I would also NOT recommend porting it right now unless you're okay with continually updating it.
/// For one, a shapecast-based implementation rather than a true physics implementation is in the cards for
/// the future. For another, it's not terribly clean and is not integrated too well into existing movement code.

/// <summary>
/// Runs one tick of tile-based movement on the given inputs.
Expand All @@ -607,7 +611,7 @@ private bool TryGetFootstepSound(
/// <param name="targetTransform">TransformComponent on the entity doing the move.</param>
/// <param name="inputMover">InputMoverComponent on the entity doing the move.</param>
/// <param name="tileDef">ContentTileDefinition of the tile underneath the entity doing the move, if there is one.</param>
/// <param name="relayTarget"></param>
/// <param name="relayTarget">MovementRelayTargetComponent on the relay target, if any.</param>
/// <param name="frameTime">Time in seconds since the last tick of the physics system.</param>
/// <returns></returns>
public bool HandleTileMovement(
Expand Down Expand Up @@ -769,6 +773,7 @@ float movementSpeed
// account for diagonals being sqrt(2) length as well. Max of 10 seconds just in case.
var distanceToDestination = (tileMovement.Destination - tileMovement.Origin.Position).Length();
var minPressedTime = Math.Min((1.05f / movementSpeed) * distanceToDestination, 20);

// We need to stop the move once we are close enough. This isn't perfect, since it technically ends the move
// 1 tick early in some cases. This is because there's a fundamental issue where because this is a physics-based
// tile movement system, we sometimes find scenarios where on each tick of the physics system, the player is moved
Expand All @@ -781,7 +786,8 @@ float movementSpeed
var stoppedPressing = pressedButtons != tileMovement.CurrentSlideMoveButtons;
var minDurationPassed = CurrentTime - tileMovement.MovementKeyInitialDownTime >= TimeSpan.FromSeconds(minPressedTime);
var noProgress = tileMovement.LastTickLocalCoordinates != null && transform.LocalPosition.EqualsApprox(tileMovement.LastTickLocalCoordinates.Value, destinationTolerance/3);
return reachedDestination || (stoppedPressing && (minDurationPassed || noProgress));
var hardDurationLimitPassed = CurrentTime - tileMovement.MovementKeyInitialDownTime >= TimeSpan.FromSeconds(minPressedTime) * 3;
return reachedDestination || (stoppedPressing && (minDurationPassed || noProgress)) || hardDurationLimitPassed;
}


Expand Down Expand Up @@ -900,16 +906,6 @@ private void EndSlide(EntityUid uid, TileMovement.TileMovementComponent tileMove
PhysicsSystem.SetAngularVelocity(uid, 0, body: physicsComponent);
}

/// <summary>
/// Returns the given local coordinates snapped to the center of the tile it is currently on.
/// </summary>
/// <param name="input">Given coordinates to snap.</param>
/// <returns>The closest tile center to the input.<returns>
private Vector2 SnapCoordinatesToTile(Vector2 input)
{
return new Vector2((int) Math.Floor(input.X) + 0.5f, (int) Math.Floor(input.Y) + 0.5f);
}

/// <summary>
/// Instantly snaps/teleports an entity to the center of the tile it is currently standing on based on the
/// given grid. Does not trigger collisions on the way there, but does trigger collisions after the snap.
Expand Down Expand Up @@ -976,5 +972,15 @@ private MoveButtons StripWalk(MoveButtons input)
{
return input & ~MoveButtons.Walk;
}

/// <summary>
/// Returns the given local coordinates snapped to the center of the tile it is currently on.
/// </summary>
/// <param name="input">Given coordinates to snap.</param>
/// <returns>The closest tile center to the input.<returns>
public static Vector2 SnapCoordinatesToTile(Vector2 input)
{
return new Vector2((int) Math.Floor(input.X) + 0.5f, (int) Math.Floor(input.Y) + 0.5f);
}
}
}
1 change: 1 addition & 0 deletions Resources/Prototypes/Entities/Mobs/base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
- type: Clickable
- type: InteractionOutline
- type: InputMover
- type: TileMovement
- type: Input
context: "human"
- type: LagCompensation
Expand Down

0 comments on commit 1777096

Please sign in to comment.