Smooth docking traversal (#10822)
This commit is contained in:
@@ -105,6 +105,11 @@ namespace Content.Client.EscapeMenu.UI.Tabs
|
|||||||
AddButton(EngineKeyFunctions.MoveRight);
|
AddButton(EngineKeyFunctions.MoveRight);
|
||||||
AddButton(EngineKeyFunctions.Walk);
|
AddButton(EngineKeyFunctions.Walk);
|
||||||
|
|
||||||
|
AddHeader("ui-options-header-camera");
|
||||||
|
AddButton(EngineKeyFunctions.CameraRotateLeft);
|
||||||
|
AddButton(EngineKeyFunctions.CameraRotateRight);
|
||||||
|
AddButton(EngineKeyFunctions.CameraReset);
|
||||||
|
|
||||||
AddHeader("ui-options-header-interaction-basic");
|
AddHeader("ui-options-header-interaction-basic");
|
||||||
AddButton(EngineKeyFunctions.Use);
|
AddButton(EngineKeyFunctions.Use);
|
||||||
AddButton(ContentKeyFunctions.UseItemInHand);
|
AddButton(ContentKeyFunctions.UseItemInHand);
|
||||||
|
|||||||
@@ -1,41 +1,31 @@
|
|||||||
using System;
|
|
||||||
using Content.Shared.Movement.Components;
|
using Content.Shared.Movement.Components;
|
||||||
|
using Content.Shared.Movement.Systems;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.Physics;
|
using Robust.Client.Physics;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.Collections;
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Client.Eye;
|
namespace Content.Client.Eye;
|
||||||
|
|
||||||
public sealed class EyeLerpingSystem : EntitySystem
|
public sealed class EyeLerpingSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Dependency] private readonly SharedMoverController _mover = default!;
|
||||||
// How fast the camera rotates in radians / s
|
|
||||||
private const float CameraRotateSpeed = MathF.PI;
|
|
||||||
|
|
||||||
// Safety override
|
|
||||||
private const float LerpTimeMax = 1.5f;
|
|
||||||
|
|
||||||
// Lerping information for the player's active eye.
|
|
||||||
private readonly EyeLerpInformation _playerActiveEye = new();
|
|
||||||
|
|
||||||
// Eyes other than the primary eye that are currently active.
|
// Eyes other than the primary eye that are currently active.
|
||||||
private readonly Dictionary<EntityUid, EyeLerpInformation> _activeEyes = new();
|
private readonly Dictionary<EntityUid, EyeLerpInformation> _activeEyes = new();
|
||||||
private readonly List<EntityUid> _toRemove = new();
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<EyeComponent, ComponentStartup>(OnEyeStartup);
|
||||||
SubscribeLocalEvent<EyeComponent, ComponentShutdown>(OnEyeShutdown);
|
SubscribeLocalEvent<EyeComponent, ComponentShutdown>(OnEyeShutdown);
|
||||||
|
|
||||||
UpdatesAfter.Add(typeof(TransformSystem));
|
UpdatesAfter.Add(typeof(TransformSystem));
|
||||||
@@ -43,6 +33,27 @@ public sealed class EyeLerpingSystem : EntitySystem
|
|||||||
UpdatesBefore.Add(typeof(EyeUpdateSystem));
|
UpdatesBefore.Add(typeof(EyeUpdateSystem));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnEyeStartup(EntityUid uid, EyeComponent component, ComponentStartup args)
|
||||||
|
{
|
||||||
|
if (component.Eye == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If the eye starts up then don't lerp at all.
|
||||||
|
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
TryComp<InputMoverComponent>(uid, out var mover);
|
||||||
|
xformQuery.TryGetComponent(uid, out var xform);
|
||||||
|
var lerpInfo = _activeEyes.GetOrNew(uid);
|
||||||
|
lerpInfo.TargetRotation = GetRotation(xformQuery, mover, xform);
|
||||||
|
lerpInfo.LastRotation = lerpInfo.TargetRotation;
|
||||||
|
|
||||||
|
if (xform != null)
|
||||||
|
{
|
||||||
|
lerpInfo.MapId = xform.MapID;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.Eye.Rotation = lerpInfo.TargetRotation;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnEyeShutdown(EntityUid uid, EyeComponent component, ComponentShutdown args)
|
private void OnEyeShutdown(EntityUid uid, EyeComponent component, ComponentShutdown args)
|
||||||
{
|
{
|
||||||
RemoveEye(uid);
|
RemoveEye(uid);
|
||||||
@@ -50,145 +61,131 @@ public sealed class EyeLerpingSystem : EntitySystem
|
|||||||
|
|
||||||
public void AddEye(EntityUid uid)
|
public void AddEye(EntityUid uid)
|
||||||
{
|
{
|
||||||
if (!_activeEyes.ContainsKey(uid))
|
_activeEyes.TryAdd(uid, new EyeLerpInformation());
|
||||||
{
|
|
||||||
_activeEyes.Add(uid, new());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveEye(EntityUid uid)
|
public void RemoveEye(EntityUid uid)
|
||||||
{
|
{
|
||||||
if (_activeEyes.ContainsKey(uid))
|
_activeEyes.Remove(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
if (!_gameTiming.IsFirstTimePredicted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var moverQuery = GetEntityQuery<InputMoverComponent>();
|
||||||
|
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
var foundEyes = new ValueList<EntityUid>(1);
|
||||||
|
|
||||||
|
// Set all of our eye rotations to the relevant values.
|
||||||
|
foreach (var (eye, entity) in GetEyes())
|
||||||
{
|
{
|
||||||
_activeEyes.Remove(uid);
|
var lerpInfo = _activeEyes.GetOrNew(entity);
|
||||||
|
foundEyes.Add(entity);
|
||||||
|
moverQuery.TryGetComponent(entity, out var mover);
|
||||||
|
xformQuery.TryGetComponent(entity, out var xform);
|
||||||
|
lerpInfo.LastRotation = eye.Rotation;
|
||||||
|
lerpInfo.TargetRotation = GetRotation(xformQuery, mover, xform);
|
||||||
|
|
||||||
|
if (xform != null)
|
||||||
|
{
|
||||||
|
// If we traverse maps then don't lerp.
|
||||||
|
if (xform.MapID != lerpInfo.MapId)
|
||||||
|
{
|
||||||
|
lerpInfo.LastRotation = lerpInfo.TargetRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var eye in foundEyes)
|
||||||
|
{
|
||||||
|
if (!_activeEyes.ContainsKey(eye))
|
||||||
|
{
|
||||||
|
_activeEyes.Remove(eye);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Angle GetRotation(EntityQuery<TransformComponent> xformQuery, InputMoverComponent? mover = null, TransformComponent? xform = null)
|
||||||
|
{
|
||||||
|
// If we can move then tie our eye to our inputs (these also get lerped so it should be fine).
|
||||||
|
if (mover != null)
|
||||||
|
{
|
||||||
|
return -_mover.GetParentGridAngle(mover);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not tied to a mover then lock it to map / grid
|
||||||
|
if (xform != null)
|
||||||
|
{
|
||||||
|
var relative = xform.GridUid;
|
||||||
|
relative ??= xform.MapUid;
|
||||||
|
|
||||||
|
if (xformQuery.TryGetComponent(relative, out var relativeXform))
|
||||||
|
{
|
||||||
|
return relativeXform.WorldRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Angle.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<(IEye Eye, EntityUid Entity)> GetEyes()
|
||||||
|
{
|
||||||
|
if (_playerManager.LocalPlayer?.ControlledEntity is { } player && !Deleted(player))
|
||||||
|
{
|
||||||
|
yield return (_eyeManager.CurrentEye, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_activeEyes.Count == 0)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
var eyeQuery = GetEntityQuery<EyeComponent>();
|
||||||
|
|
||||||
|
foreach (var (ent, info) in _activeEyes)
|
||||||
|
{
|
||||||
|
if (!eyeQuery.TryGetComponent(ent, out var eyeComp) ||
|
||||||
|
eyeComp.Eye == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return (eyeComp.Eye, ent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void FrameUpdate(float frameTime)
|
public override void FrameUpdate(float frameTime)
|
||||||
{
|
{
|
||||||
if (!_gameTiming.IsFirstTimePredicted)
|
var tickFraction = (float) _gameTiming.TickFraction / ushort.MaxValue;
|
||||||
return;
|
var lerpMinimum = 0.01;
|
||||||
|
|
||||||
// Always do this one.
|
foreach (var (eye, entity) in GetEyes())
|
||||||
LerpPlayerEye(frameTime);
|
|
||||||
|
|
||||||
foreach (var (entity, info) in _activeEyes)
|
|
||||||
{
|
{
|
||||||
LerpEntityEye(entity, info, frameTime);
|
if (!_activeEyes.TryGetValue(entity, out var lerpInfo))
|
||||||
}
|
continue;
|
||||||
|
|
||||||
if (_toRemove.Count != 0)
|
var shortest = Angle.ShortestDistance(lerpInfo.LastRotation, lerpInfo.TargetRotation);
|
||||||
{
|
|
||||||
foreach (var entity in _toRemove)
|
if (Math.Abs(shortest.Theta) < lerpMinimum)
|
||||||
{
|
{
|
||||||
RemoveEye(entity);
|
eye.Rotation = lerpInfo.TargetRotation;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_toRemove.Clear();
|
eye.Rotation = shortest * tickFraction + lerpInfo.LastRotation;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LerpPlayerEye(float frameTime)
|
|
||||||
{
|
|
||||||
if (_playerManager.LocalPlayer?.ControlledEntity is not {} mob || Deleted(mob))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// We can't lerp if the mob can't move!
|
|
||||||
if (!TryComp(mob, out InputMoverComponent? mover))
|
|
||||||
return;
|
|
||||||
|
|
||||||
LerpEye(_eyeManager.CurrentEye, frameTime, mover.LastGridAngle, _playerActiveEye);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LerpEntityEye(EntityUid uid, EyeLerpInformation info, float frameTime)
|
|
||||||
{
|
|
||||||
if (!TryComp(uid, out TransformComponent? transform)
|
|
||||||
|| !TryComp(uid, out EyeComponent? eye)
|
|
||||||
|| eye.Eye == null
|
|
||||||
|| !_mapManager.TryGetGrid(transform.GridUid, out var grid))
|
|
||||||
{
|
|
||||||
_toRemove.Add(uid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LerpEye(eye.Eye, frameTime, grid.WorldRotation, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LerpEye(IEye eye, float frameTime, Angle lastAngle, EyeLerpInformation lerpInfo)
|
|
||||||
{
|
|
||||||
|
|
||||||
// Let's not turn the camera into a washing machine when the game starts.
|
|
||||||
if (lerpInfo.LastGridAngle == null)
|
|
||||||
{
|
|
||||||
lerpInfo.LastGridAngle = lastAngle;
|
|
||||||
eye.Rotation = -lastAngle;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the last lerp grid angle we have is not the same as the last mover grid angle...
|
|
||||||
if (!lerpInfo.LastGridAngle.Value.EqualsApprox(lastAngle))
|
|
||||||
{
|
|
||||||
// And now, we start lerping.
|
|
||||||
lerpInfo.LerpTo = lastAngle;
|
|
||||||
lerpInfo.LastGridAngle = lastAngle;
|
|
||||||
lerpInfo.LerpStartRotation = eye.Rotation;
|
|
||||||
lerpInfo.Accumulator = 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lerpInfo.LerpTo != null)
|
|
||||||
{
|
|
||||||
lerpInfo.Accumulator += frameTime;
|
|
||||||
|
|
||||||
var lerpRot = -lerpInfo.LerpTo.Value.FlipPositive().Reduced();
|
|
||||||
var startRot = lerpInfo.LerpStartRotation.FlipPositive().Reduced();
|
|
||||||
|
|
||||||
var changeNeeded = Angle.ShortestDistance(startRot, lerpRot);
|
|
||||||
|
|
||||||
if (changeNeeded.EqualsApprox(Angle.Zero))
|
|
||||||
{
|
|
||||||
// Nothing to do here!
|
|
||||||
lerpInfo.Cleanup(eye);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get how much the camera should have moved by now. Make it faster depending on the change needed.
|
|
||||||
var changeRot = (CameraRotateSpeed * Math.Max(1f, Math.Abs(changeNeeded) * 0.75f)) * lerpInfo.Accumulator * Math.Sign(changeNeeded);
|
|
||||||
|
|
||||||
// How close is this from reaching the end?
|
|
||||||
var percentage = (float)Math.Abs(changeRot / changeNeeded);
|
|
||||||
|
|
||||||
eye.Rotation = Angle.Lerp(startRot, lerpRot, percentage);
|
|
||||||
|
|
||||||
// Either we have overshot, or we have taken way too long on this, emergency reset time
|
|
||||||
if (percentage >= 1.0f || lerpInfo.Accumulator >= LerpTimeMax)
|
|
||||||
{
|
|
||||||
lerpInfo.Cleanup(eye);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// This makes it so rotating the camera manually is impossible...
|
|
||||||
// However, it is needed. Why? Because of a funny (hilarious, even) race condition involving
|
|
||||||
// ghosting, this system listening for attached mob changes, and the eye rotation being reset after our
|
|
||||||
// changes back to zero because of an EyeComponent state coming from the server being applied.
|
|
||||||
// At some point we'll need to come up with a solution for that. But for now, I just want to fix this.
|
|
||||||
eye.Rotation = -lastAngle;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class EyeLerpInformation
|
private sealed class EyeLerpInformation
|
||||||
{
|
{
|
||||||
public Angle? LastGridAngle { get; set; }
|
public Angle LastRotation;
|
||||||
public Angle? LerpTo { get; set; }
|
public Angle TargetRotation;
|
||||||
public Angle LerpStartRotation { get; set; }
|
|
||||||
public float Accumulator { get; set; }
|
|
||||||
|
|
||||||
public void Cleanup(IEye eye)
|
/// <summary>
|
||||||
{
|
/// If we go to a new map then don't lerp and snap instantly.
|
||||||
eye.Rotation = -LerpTo ?? Angle.Zero;
|
/// </summary>
|
||||||
LerpStartRotation = eye.Rotation;
|
public MapId MapId;
|
||||||
LerpTo = null;
|
|
||||||
Accumulator = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using Content.Shared.Movement.Systems;
|
|||||||
using Content.Shared.Pulling.Components;
|
using Content.Shared.Pulling.Components;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Client.Physics.Controllers
|
namespace Content.Client.Physics.Controllers
|
||||||
@@ -23,7 +22,17 @@ namespace Content.Client.Physics.Controllers
|
|||||||
if (TryComp<RelayInputMoverComponent>(player, out var relayMover))
|
if (TryComp<RelayInputMoverComponent>(player, out var relayMover))
|
||||||
{
|
{
|
||||||
if (relayMover.RelayEntity != null)
|
if (relayMover.RelayEntity != null)
|
||||||
|
{
|
||||||
|
if (TryComp<InputMoverComponent>(player, out var mover) &&
|
||||||
|
TryComp<InputMoverComponent>(relayMover.RelayEntity, out var relayed))
|
||||||
|
{
|
||||||
|
relayed.RelativeEntity = mover.RelativeEntity;
|
||||||
|
relayed.RelativeRotation = mover.RelativeRotation;
|
||||||
|
relayed.TargetRelativeRotation = mover.RelativeRotation;
|
||||||
|
}
|
||||||
|
|
||||||
HandleClientsideMovement(relayMover.RelayEntity.Value, frameTime);
|
HandleClientsideMovement(relayMover.RelayEntity.Value, frameTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleClientsideMovement(player, frameTime);
|
HandleClientsideMovement(player, frameTime);
|
||||||
@@ -31,8 +40,10 @@ namespace Content.Client.Physics.Controllers
|
|||||||
|
|
||||||
private void HandleClientsideMovement(EntityUid player, float frameTime)
|
private void HandleClientsideMovement(EntityUid player, float frameTime)
|
||||||
{
|
{
|
||||||
|
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
|
||||||
if (!TryComp(player, out InputMoverComponent? mover) ||
|
if (!TryComp(player, out InputMoverComponent? mover) ||
|
||||||
!TryComp(player, out TransformComponent? xform))
|
!xformQuery.TryGetComponent(player, out var xform))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -47,20 +58,12 @@ namespace Content.Client.Physics.Controllers
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryComp<InputMoverComponent>(xform.ParentUid, out var parentMover))
|
|
||||||
{
|
|
||||||
mover.LastGridAngle = parentMover.LastGridAngle;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (!TryComp(player, out body))
|
else if (!TryComp(player, out body))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xform.GridUid != null)
|
|
||||||
mover.LastGridAngle = GetParentGridAngle(xform, mover);
|
|
||||||
|
|
||||||
// Essentially we only want to set our mob to predicted so every other entity we just interpolate
|
// Essentially we only want to set our mob to predicted so every other entity we just interpolate
|
||||||
// (i.e. only see what the server has sent us).
|
// (i.e. only see what the server has sent us).
|
||||||
// The exception to this is joints.
|
// The exception to this is joints.
|
||||||
@@ -98,7 +101,7 @@ namespace Content.Client.Physics.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
HandleMobMovement(mover, body, xformMover, frameTime, xformQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CanSound()
|
protected override bool CanSound()
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Content.Server.Administration.Commands
|
|||||||
var coordinates = player.AttachedEntity != null
|
var coordinates = player.AttachedEntity != null
|
||||||
? _entities.GetComponent<TransformComponent>(player.AttachedEntity.Value).Coordinates
|
? _entities.GetComponent<TransformComponent>(player.AttachedEntity.Value).Coordinates
|
||||||
: EntitySystem.Get<GameTicker>().GetObserverSpawnPoint();
|
: EntitySystem.Get<GameTicker>().GetObserverSpawnPoint();
|
||||||
var ghost = _entities.SpawnEntity("AdminObserver", coordinates.ToMap(_entities));
|
var ghost = _entities.SpawnEntity("AdminObserver", coordinates);
|
||||||
|
|
||||||
if (canReturn)
|
if (canReturn)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -121,10 +121,9 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
var (entities, gridIds) = _mapLoader.LoadMap(targetMapId, ev.GameMap.MapPath.ToString(), ev.Options);
|
var (entities, gridIds) = _mapLoader.LoadMap(targetMapId, ev.GameMap.MapPath.ToString(), ev.Options);
|
||||||
|
|
||||||
var gridUids = gridIds.Select(g => (EntityUid)g).ToList();
|
var gridUids = gridIds.Select(g => g).ToList();
|
||||||
RaiseLocalEvent(new PostGameMapLoad(map, targetMapId, entities, gridUids, stationName));
|
RaiseLocalEvent(new PostGameMapLoad(map, targetMapId, entities, gridUids, stationName));
|
||||||
|
|
||||||
_spawnPoint = _mapManager.GetGrid(gridIds[0]).ToCoordinates();
|
|
||||||
return (entities, gridUids);
|
return (entities, gridUids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,9 +25,6 @@ namespace Content.Server.GameTicking
|
|||||||
{
|
{
|
||||||
private const string ObserverPrototypeName = "MobObserver";
|
private const string ObserverPrototypeName = "MobObserver";
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite), Obsolete("Due for removal when observer spawning is refactored.")] // See also: MindComponent's OnShutdown shitcode
|
|
||||||
private EntityCoordinates _spawnPoint;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How many players have joined the round through normal methods.
|
/// How many players have joined the round through normal methods.
|
||||||
/// Useful for game rules to look at. Doesn't count observers, people in lobby, etc.
|
/// Useful for game rules to look at. Doesn't count observers, people in lobby, etc.
|
||||||
@@ -280,24 +277,72 @@ namespace Content.Server.GameTicking
|
|||||||
#region Spawn Points
|
#region Spawn Points
|
||||||
public EntityCoordinates GetObserverSpawnPoint()
|
public EntityCoordinates GetObserverSpawnPoint()
|
||||||
{
|
{
|
||||||
// TODO rename this to TryGetObserverSpawnPoint to make it clear that the result might be invalid. Or at
|
|
||||||
// least try try more fallback values, like randomly spawning them in any available map or just creating a
|
|
||||||
// "we fucked up" map. Its better than dumping them into the void.
|
|
||||||
|
|
||||||
var location = _spawnPoint.IsValid(EntityManager) ? _spawnPoint : EntityCoordinates.Invalid;
|
|
||||||
|
|
||||||
_possiblePositions.Clear();
|
_possiblePositions.Clear();
|
||||||
|
|
||||||
foreach (var (point, transform) in EntityManager.EntityQuery<SpawnPointComponent, TransformComponent>(true))
|
foreach (var (point, transform) in EntityManager.EntityQuery<SpawnPointComponent, TransformComponent>(true))
|
||||||
{
|
{
|
||||||
if (point.SpawnType == SpawnPointType.Observer)
|
if (point.SpawnType != SpawnPointType.Observer)
|
||||||
_possiblePositions.Add(transform.Coordinates);
|
continue;
|
||||||
|
|
||||||
|
_possiblePositions.Add(transform.Coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
var metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||||
|
|
||||||
|
// Fallback to a random grid.
|
||||||
|
if (_possiblePositions.Count == 0)
|
||||||
|
{
|
||||||
|
foreach (var grid in _mapManager.GetAllGrids())
|
||||||
|
{
|
||||||
|
if (!metaQuery.TryGetComponent(grid.GridEntityId, out var meta) ||
|
||||||
|
meta.EntityPaused)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_possiblePositions.Add(new EntityCoordinates(grid.GridEntityId, Vector2.Zero));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_possiblePositions.Count != 0)
|
if (_possiblePositions.Count != 0)
|
||||||
location = _robustRandom.Pick(_possiblePositions);
|
{
|
||||||
|
// TODO: This is just here for the eye lerping.
|
||||||
|
// Ideally engine would just spawn them on grid directly I guess? Right now grid traversal is handling it during
|
||||||
|
// update which means we need to add a hack somewhere around it.
|
||||||
|
var spawn = _robustRandom.Pick(_possiblePositions);
|
||||||
|
var toMap = spawn.ToMap(EntityManager);
|
||||||
|
|
||||||
return location;
|
if (_mapManager.TryFindGridAt(toMap, out var foundGrid))
|
||||||
|
{
|
||||||
|
return new EntityCoordinates(foundGrid.GridEntityId,
|
||||||
|
foundGrid.InvWorldMatrix.Transform(toMap.Position));
|
||||||
|
}
|
||||||
|
|
||||||
|
return spawn;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_mapManager.MapExists(DefaultMap))
|
||||||
|
{
|
||||||
|
return new EntityCoordinates(_mapManager.GetMapEntityId(DefaultMap), Vector2.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just pick a point at this point I guess.
|
||||||
|
foreach (var map in _mapManager.GetAllMapIds())
|
||||||
|
{
|
||||||
|
var mapUid = _mapManager.GetMapEntityId(map);
|
||||||
|
|
||||||
|
if (!metaQuery.TryGetComponent(mapUid, out var meta) ||
|
||||||
|
meta.EntityPaused)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EntityCoordinates(mapUid, Vector2.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AAAAAAAAAAAAA
|
||||||
|
_sawmill.Error("Found no observer spawn points!");
|
||||||
|
return EntityCoordinates.Invalid;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,9 +75,7 @@ namespace Content.Server.Medical.CrewMonitoring
|
|||||||
// the monitor. But in the special case where the monitor IS a player (i.e., admin ghost), we base it off
|
// the monitor. But in the special case where the monitor IS a player (i.e., admin ghost), we base it off
|
||||||
// the players eye rotation. We don't know what that is for sure, but we know their last grid angle, which
|
// the players eye rotation. We don't know what that is for sure, but we know their last grid angle, which
|
||||||
// should work well enough?
|
// should work well enough?
|
||||||
if (TryComp(uid, out InputMoverComponent? mover))
|
if (_mapManager.TryGetGrid(xform.GridUid, out var grid))
|
||||||
worldRot = mover.LastGridAngle;
|
|
||||||
else if (_mapManager.TryGetGrid(xform.GridUid, out var grid))
|
|
||||||
worldRot = grid.WorldRotation;
|
worldRot = grid.WorldRotation;
|
||||||
|
|
||||||
// update all sensors info
|
// update all sensors info
|
||||||
|
|||||||
@@ -28,10 +28,23 @@ namespace Content.Server.Physics.Controllers
|
|||||||
|
|
||||||
var bodyQuery = GetEntityQuery<PhysicsComponent>();
|
var bodyQuery = GetEntityQuery<PhysicsComponent>();
|
||||||
var relayQuery = GetEntityQuery<RelayInputMoverComponent>();
|
var relayQuery = GetEntityQuery<RelayInputMoverComponent>();
|
||||||
|
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
var moverQuery = GetEntityQuery<InputMoverComponent>();
|
||||||
|
|
||||||
foreach (var (mover, xform) in EntityQuery<InputMoverComponent, TransformComponent>(true))
|
foreach (var mover in EntityQuery<InputMoverComponent>(true))
|
||||||
{
|
{
|
||||||
if (relayQuery.TryGetComponent(mover.Owner, out var relayed) && relayed.RelayEntity != null)
|
if (relayQuery.TryGetComponent(mover.Owner, out var relayed) && relayed.RelayEntity != null)
|
||||||
|
{
|
||||||
|
if (moverQuery.TryGetComponent(relayed.RelayEntity, out var relayMover))
|
||||||
|
{
|
||||||
|
relayMover.RelativeEntity = mover.RelativeEntity;
|
||||||
|
relayMover.RelativeRotation = mover.RelativeRotation;
|
||||||
|
relayMover.TargetRelativeRotation = mover.TargetRelativeRotation;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!xformQuery.TryGetComponent(mover.Owner, out var xform))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -46,18 +59,13 @@ namespace Content.Server.Physics.Controllers
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryComp<InputMoverComponent>(xform.ParentUid, out var parentMover))
|
|
||||||
{
|
|
||||||
mover.LastGridAngle = parentMover.LastGridAngle;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (!bodyQuery.TryGetComponent(mover.Owner, out body))
|
else if (!bodyQuery.TryGetComponent(mover.Owner, out body))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleMobMovement(mover, body, xformMover, frameTime);
|
HandleMobMovement(mover, body, xformMover, frameTime, xformQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleShuttleMovement(frameTime);
|
HandleShuttleMovement(frameTime);
|
||||||
|
|||||||
@@ -908,6 +908,16 @@ namespace Content.Shared.CCVar
|
|||||||
* Shuttles
|
* Shuttles
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Look this is technically eye behavior but its main impact is shuttles so I just dumped it here.
|
||||||
|
/// <summary>
|
||||||
|
/// If true then the camera will match the grid / map and is unchangeable.
|
||||||
|
/// - When traversing grids it will snap to 0 degrees rotation.
|
||||||
|
/// False means the player has control over the camera rotation.
|
||||||
|
/// - When traversing grids it will snap to the nearest cardinal which will generally be imperceptible.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly CVarDef<bool> CameraRotationLocked =
|
||||||
|
CVarDef.Create("shuttle.camera_rotation_locked", true, CVar.REPLICATED);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether cargo shuttles are enabled.
|
/// Whether cargo shuttles are enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -40,8 +40,30 @@ namespace Content.Shared.Movement.Components
|
|||||||
|
|
||||||
public MoveButtons HeldMoveButtons = MoveButtons.None;
|
public MoveButtons HeldMoveButtons = MoveButtons.None;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entity our movement is relative to.
|
||||||
|
/// </summary>
|
||||||
|
public EntityUid? RelativeEntity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Although our movement might be relative to a particular entity we may have an additional relative rotation
|
||||||
|
/// e.g. if we've snapped to a different cardinal direction
|
||||||
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public Angle LastGridAngle { get; set; } = new(0);
|
public Angle TargetRelativeRotation = Angle.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current relative rotation. This will lerp towards the <see cref="TargetRelativeRotation"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables] public Angle RelativeRotation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If we traverse on / off a grid then set a timer to update our relative inputs.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float LerpAccumulator;
|
||||||
|
|
||||||
|
public const float LerpTime = 1.0f;
|
||||||
|
|
||||||
public bool Sprinting => (HeldMoveButtons & MoveButtons.Walk) == 0x0;
|
public bool Sprinting => (HeldMoveButtons & MoveButtons.Walk) == 0x0;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using Content.Shared.CCVar;
|
|||||||
using Content.Shared.Input;
|
using Content.Shared.Input;
|
||||||
using Content.Shared.Movement.Components;
|
using Content.Shared.Movement.Components;
|
||||||
using Content.Shared.Movement.Events;
|
using Content.Shared.Movement.Events;
|
||||||
using Content.Shared.Shuttles.Components;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Input;
|
using Robust.Shared.Input;
|
||||||
using Robust.Shared.Input.Binding;
|
using Robust.Shared.Input.Binding;
|
||||||
@@ -17,6 +17,8 @@ namespace Content.Shared.Movement.Systems
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class SharedMoverController
|
public abstract partial class SharedMoverController
|
||||||
{
|
{
|
||||||
|
public bool CameraRotationLocked { get; private set; }
|
||||||
|
|
||||||
private void InitializeInput()
|
private void InitializeInput()
|
||||||
{
|
{
|
||||||
var moveUpCmdHandler = new MoverDirInputCmdHandler(this, Direction.North);
|
var moveUpCmdHandler = new MoverDirInputCmdHandler(this, Direction.North);
|
||||||
@@ -30,6 +32,9 @@ namespace Content.Shared.Movement.Systems
|
|||||||
.Bind(EngineKeyFunctions.MoveRight, moveRightCmdHandler)
|
.Bind(EngineKeyFunctions.MoveRight, moveRightCmdHandler)
|
||||||
.Bind(EngineKeyFunctions.MoveDown, moveDownCmdHandler)
|
.Bind(EngineKeyFunctions.MoveDown, moveDownCmdHandler)
|
||||||
.Bind(EngineKeyFunctions.Walk, new WalkInputCmdHandler(this))
|
.Bind(EngineKeyFunctions.Walk, new WalkInputCmdHandler(this))
|
||||||
|
.Bind(EngineKeyFunctions.CameraRotateLeft, new CameraRotateInputCmdHandler(this, Direction.West))
|
||||||
|
.Bind(EngineKeyFunctions.CameraRotateRight, new CameraRotateInputCmdHandler(this, Direction.East))
|
||||||
|
.Bind(EngineKeyFunctions.CameraReset, new CameraResetInputCmdHandler(this))
|
||||||
// TODO: Relay
|
// TODO: Relay
|
||||||
// Shuttle
|
// Shuttle
|
||||||
.Bind(ContentKeyFunctions.ShuttleStrafeUp, new ShuttleInputCmdHandler(this, ShuttleButtons.StrafeUp))
|
.Bind(ContentKeyFunctions.ShuttleStrafeUp, new ShuttleInputCmdHandler(this, ShuttleButtons.StrafeUp))
|
||||||
@@ -44,6 +49,14 @@ namespace Content.Shared.Movement.Systems
|
|||||||
SubscribeLocalEvent<InputMoverComponent, ComponentInit>(OnInputInit);
|
SubscribeLocalEvent<InputMoverComponent, ComponentInit>(OnInputInit);
|
||||||
SubscribeLocalEvent<InputMoverComponent, ComponentGetState>(OnInputGetState);
|
SubscribeLocalEvent<InputMoverComponent, ComponentGetState>(OnInputGetState);
|
||||||
SubscribeLocalEvent<InputMoverComponent, ComponentHandleState>(OnInputHandleState);
|
SubscribeLocalEvent<InputMoverComponent, ComponentHandleState>(OnInputHandleState);
|
||||||
|
SubscribeLocalEvent<InputMoverComponent, EntParentChangedMessage>(OnInputParentChange);
|
||||||
|
|
||||||
|
_configManager.OnValueChanged(CCVars.CameraRotationLocked, SetCameraRotationLocked, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetCameraRotationLocked(bool obj)
|
||||||
|
{
|
||||||
|
CameraRotationLocked = obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetMoveInput(InputMoverComponent component, MoveButtons buttons)
|
private void SetMoveInput(InputMoverComponent component, MoveButtons buttons)
|
||||||
@@ -55,27 +68,98 @@ namespace Content.Shared.Movement.Systems
|
|||||||
|
|
||||||
private void OnInputHandleState(EntityUid uid, InputMoverComponent component, ref ComponentHandleState args)
|
private void OnInputHandleState(EntityUid uid, InputMoverComponent component, ref ComponentHandleState args)
|
||||||
{
|
{
|
||||||
if (args.Current is not InputMoverComponentState state) return;
|
if (args.Current is not InputMoverComponentState state)
|
||||||
|
return;
|
||||||
|
|
||||||
component.HeldMoveButtons = state.Buttons;
|
component.HeldMoveButtons = state.Buttons;
|
||||||
component.LastInputTick = GameTick.Zero;
|
component.LastInputTick = GameTick.Zero;
|
||||||
component.LastInputSubTick = 0;
|
component.LastInputSubTick = 0;
|
||||||
component.CanMove = state.CanMove;
|
component.CanMove = state.CanMove;
|
||||||
|
|
||||||
|
component.RelativeRotation = state.RelativeRotation;
|
||||||
|
component.TargetRelativeRotation = state.TargetRelativeRotation;
|
||||||
|
component.RelativeEntity = state.RelativeEntity;
|
||||||
|
component.LerpAccumulator = state.LerpAccumulator;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnInputGetState(EntityUid uid, InputMoverComponent component, ref ComponentGetState args)
|
private void OnInputGetState(EntityUid uid, InputMoverComponent component, ref ComponentGetState args)
|
||||||
{
|
{
|
||||||
args.State = new InputMoverComponentState(component.HeldMoveButtons, component.CanMove);
|
args.State = new InputMoverComponentState(
|
||||||
|
component.HeldMoveButtons,
|
||||||
|
component.CanMove,
|
||||||
|
component.RelativeRotation,
|
||||||
|
component.TargetRelativeRotation,
|
||||||
|
component.RelativeEntity,
|
||||||
|
component.LerpAccumulator);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShutdownInput()
|
private void ShutdownInput()
|
||||||
{
|
{
|
||||||
CommandBinds.Unregister<SharedMoverController>();
|
CommandBinds.Unregister<SharedMoverController>();
|
||||||
|
_configManager.UnsubValueChanged(CCVars.CameraRotationLocked, SetCameraRotationLocked);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DiagonalMovementEnabled => _configManager.GetCVar(CCVars.GameDiagonalMovement);
|
public bool DiagonalMovementEnabled => _configManager.GetCVar(CCVars.GameDiagonalMovement);
|
||||||
|
|
||||||
protected virtual void HandleShuttleInput(EntityUid uid, ShuttleButtons button, ushort subTick, bool state) {}
|
protected virtual void HandleShuttleInput(EntityUid uid, ShuttleButtons button, ushort subTick, bool state) {}
|
||||||
|
|
||||||
|
public void RotateCamera(EntityUid uid, Angle angle)
|
||||||
|
{
|
||||||
|
if (CameraRotationLocked || !TryComp<InputMoverComponent>(uid, out var mover))
|
||||||
|
return;
|
||||||
|
|
||||||
|
mover.TargetRelativeRotation += angle;
|
||||||
|
Dirty(mover);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetCamera(EntityUid uid)
|
||||||
|
{
|
||||||
|
if (CameraRotationLocked || !TryComp<InputMoverComponent>(uid, out var mover) || mover.TargetRelativeRotation.Equals(Angle.Zero))
|
||||||
|
return;
|
||||||
|
|
||||||
|
mover.TargetRelativeRotation = Angle.Zero;
|
||||||
|
Dirty(mover);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Angle GetParentGridAngle(InputMoverComponent mover)
|
||||||
|
{
|
||||||
|
var rotation = mover.RelativeRotation;
|
||||||
|
|
||||||
|
if (TryComp<TransformComponent>(mover.RelativeEntity, out var relativeXform))
|
||||||
|
return (relativeXform.WorldRotation + rotation);
|
||||||
|
|
||||||
|
return rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInputParentChange(EntityUid uid, InputMoverComponent component, ref EntParentChangedMessage args)
|
||||||
|
{
|
||||||
|
// If we change our grid / map then delay updating our LastGridAngle.
|
||||||
|
var relative = args.Transform.GridUid;
|
||||||
|
relative ??= args.Transform.MapUid;
|
||||||
|
|
||||||
|
if (component.LifeStage < ComponentLifeStage.Running)
|
||||||
|
{
|
||||||
|
component.RelativeEntity = relative;
|
||||||
|
Dirty(component);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we go on a grid and back off then just reset the accumulator.
|
||||||
|
if (relative == component.RelativeEntity)
|
||||||
|
{
|
||||||
|
if (component.LerpAccumulator != 0f)
|
||||||
|
{
|
||||||
|
component.LerpAccumulator = 0f;
|
||||||
|
Dirty(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.LerpAccumulator = InputMoverComponent.LerpTime;
|
||||||
|
Dirty(component);
|
||||||
|
}
|
||||||
|
|
||||||
private void HandleDirChange(EntityUid entity, Direction dir, ushort subTick, bool state)
|
private void HandleDirChange(EntityUid entity, Direction dir, ushort subTick, bool state)
|
||||||
{
|
{
|
||||||
// Relayed movement just uses the same keybinds given we're moving the relayed entity
|
// Relayed movement just uses the same keybinds given we're moving the relayed entity
|
||||||
@@ -124,9 +208,11 @@ namespace Content.Shared.Movement.Systems
|
|||||||
{
|
{
|
||||||
var xform = Transform(uid);
|
var xform = Transform(uid);
|
||||||
|
|
||||||
if (!xform.ParentUid.IsValid()) return;
|
if (!xform.ParentUid.IsValid())
|
||||||
|
return;
|
||||||
|
|
||||||
component.LastGridAngle = Transform(xform.ParentUid).WorldRotation;
|
component.RelativeEntity = xform.GridUid ?? xform.MapUid;
|
||||||
|
component.TargetRelativeRotation = Angle.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleRunChange(EntityUid uid, ushort subTick, bool walking)
|
private void HandleRunChange(EntityUid uid, ushort subTick, bool walking)
|
||||||
@@ -300,9 +386,53 @@ namespace Content.Shared.Movement.Systems
|
|||||||
return (buttons & flag) == flag;
|
return (buttons & flag) == flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class CameraRotateInputCmdHandler : InputCmdHandler
|
||||||
|
{
|
||||||
|
private readonly SharedMoverController _controller;
|
||||||
|
private readonly Angle _angle;
|
||||||
|
|
||||||
|
public CameraRotateInputCmdHandler(SharedMoverController controller, Direction direction)
|
||||||
|
{
|
||||||
|
_controller = controller;
|
||||||
|
_angle = direction.ToAngle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool HandleCmdMessage(ICommonSession? session, InputCmdMessage message)
|
||||||
|
{
|
||||||
|
if (message is not FullInputCmdMessage full || session?.AttachedEntity == null) return false;
|
||||||
|
|
||||||
|
if (full.State != BoundKeyState.Up)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_controller.RotateCamera(session.AttachedEntity.Value, _angle);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class CameraResetInputCmdHandler : InputCmdHandler
|
||||||
|
{
|
||||||
|
private readonly SharedMoverController _controller;
|
||||||
|
|
||||||
|
public CameraResetInputCmdHandler(SharedMoverController controller)
|
||||||
|
{
|
||||||
|
_controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool HandleCmdMessage(ICommonSession? session, InputCmdMessage message)
|
||||||
|
{
|
||||||
|
if (message is not FullInputCmdMessage full || session?.AttachedEntity == null) return false;
|
||||||
|
|
||||||
|
if (full.State != BoundKeyState.Up)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_controller.ResetCamera(session.AttachedEntity.Value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class MoverDirInputCmdHandler : InputCmdHandler
|
private sealed class MoverDirInputCmdHandler : InputCmdHandler
|
||||||
{
|
{
|
||||||
private SharedMoverController _controller;
|
private readonly SharedMoverController _controller;
|
||||||
private readonly Direction _dir;
|
private readonly Direction _dir;
|
||||||
|
|
||||||
public MoverDirInputCmdHandler(SharedMoverController controller, Direction dir)
|
public MoverDirInputCmdHandler(SharedMoverController controller, Direction dir)
|
||||||
@@ -344,17 +474,33 @@ namespace Content.Shared.Movement.Systems
|
|||||||
public MoveButtons Buttons { get; }
|
public MoveButtons Buttons { get; }
|
||||||
public readonly bool CanMove;
|
public readonly bool CanMove;
|
||||||
|
|
||||||
public InputMoverComponentState(MoveButtons buttons, bool canMove)
|
/// <summary>
|
||||||
|
/// Our current rotation for movement purposes. This is lerping towards <see cref="TargetRelativeRotation"/>
|
||||||
|
/// </summary>
|
||||||
|
public Angle RelativeRotation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Target rotation relative to the <see cref="RelativeEntity"/>. Typically 0
|
||||||
|
/// </summary>
|
||||||
|
public Angle TargetRelativeRotation;
|
||||||
|
public EntityUid? RelativeEntity;
|
||||||
|
public float LerpAccumulator = 0f;
|
||||||
|
|
||||||
|
public InputMoverComponentState(MoveButtons buttons, bool canMove, Angle relativeRotation, Angle targetRelativeRotation, EntityUid? relativeEntity, float lerpAccumulator)
|
||||||
{
|
{
|
||||||
Buttons = buttons;
|
Buttons = buttons;
|
||||||
CanMove = canMove;
|
CanMove = canMove;
|
||||||
|
RelativeRotation = relativeRotation;
|
||||||
|
TargetRelativeRotation = targetRelativeRotation;
|
||||||
|
RelativeEntity = relativeEntity;
|
||||||
|
LerpAccumulator = lerpAccumulator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class ShuttleInputCmdHandler : InputCmdHandler
|
private sealed class ShuttleInputCmdHandler : InputCmdHandler
|
||||||
{
|
{
|
||||||
private SharedMoverController _controller;
|
private readonly SharedMoverController _controller;
|
||||||
private ShuttleButtons _button;
|
private readonly ShuttleButtons _button;
|
||||||
|
|
||||||
public ShuttleInputCmdHandler(SharedMoverController controller, ShuttleButtons button)
|
public ShuttleInputCmdHandler(SharedMoverController controller, ShuttleButtons button)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ namespace Content.Shared.Movement.Systems
|
|||||||
private const float FootstepVolume = 3f;
|
private const float FootstepVolume = 3f;
|
||||||
private const float FootstepWalkingAddedVolumeMultiplier = 0f;
|
private const float FootstepWalkingAddedVolumeMultiplier = 0f;
|
||||||
|
|
||||||
|
protected ISawmill Sawmill = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="CCVars.StopSpeed"/>
|
/// <see cref="CCVars.StopSpeed"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -59,6 +61,7 @@ namespace Content.Shared.Movement.Systems
|
|||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
Sawmill = Logger.GetSawmill("mover");
|
||||||
InitializeFootsteps();
|
InitializeFootsteps();
|
||||||
InitializeInput();
|
InitializeInput();
|
||||||
InitializeMob();
|
InitializeMob();
|
||||||
@@ -87,14 +90,6 @@ namespace Content.Shared.Movement.Systems
|
|||||||
UsedMobMovement.Clear();
|
UsedMobMovement.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Angle GetParentGridAngle(TransformComponent xform, InputMoverComponent mover)
|
|
||||||
{
|
|
||||||
if (!_mapManager.TryGetGrid(xform.GridUid, out var grid))
|
|
||||||
return mover.LastGridAngle;
|
|
||||||
|
|
||||||
return grid.WorldRotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Movement while considering actionblockers, weightlessness, etc.
|
/// Movement while considering actionblockers, weightlessness, etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -102,7 +97,8 @@ namespace Content.Shared.Movement.Systems
|
|||||||
InputMoverComponent mover,
|
InputMoverComponent mover,
|
||||||
PhysicsComponent physicsComponent,
|
PhysicsComponent physicsComponent,
|
||||||
TransformComponent xform,
|
TransformComponent xform,
|
||||||
float frameTime)
|
float frameTime,
|
||||||
|
EntityQuery<TransformComponent> xformQuery)
|
||||||
{
|
{
|
||||||
DebugTools.Assert(!UsedMobMovement.ContainsKey(mover.Owner));
|
DebugTools.Assert(!UsedMobMovement.ContainsKey(mover.Owner));
|
||||||
|
|
||||||
@@ -134,12 +130,6 @@ namespace Content.Shared.Movement.Systems
|
|||||||
if (!touching && TryComp<MobMoverComponent>(xform.Owner, out var mobMover))
|
if (!touching && TryComp<MobMoverComponent>(xform.Owner, out var mobMover))
|
||||||
touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, physicsComponent);
|
touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, physicsComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!touching)
|
|
||||||
{
|
|
||||||
if (xform.GridUid != null)
|
|
||||||
mover.LastGridAngle = GetParentGridAngle(xform, mover);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regular movement.
|
// Regular movement.
|
||||||
@@ -152,7 +142,92 @@ namespace Content.Shared.Movement.Systems
|
|||||||
|
|
||||||
var total = walkDir * walkSpeed + sprintDir * sprintSpeed;
|
var total = walkDir * walkSpeed + sprintDir * sprintSpeed;
|
||||||
|
|
||||||
var parentRotation = GetParentGridAngle(xform, mover);
|
// Update relative movement
|
||||||
|
if (mover.LerpAccumulator > 0f)
|
||||||
|
{
|
||||||
|
Dirty(mover);
|
||||||
|
mover.LerpAccumulator -= frameTime;
|
||||||
|
|
||||||
|
if (mover.LerpAccumulator <= 0f)
|
||||||
|
{
|
||||||
|
mover.LerpAccumulator = 0f;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.005))
|
||||||
|
{
|
||||||
|
var adjustment = angleDiff * 5f * frameTime;
|
||||||
|
var minAdjustment = 0.005 * 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;
|
||||||
|
Dirty(mover);
|
||||||
|
}
|
||||||
|
else if (!angleDiff.Equals(Angle.Zero))
|
||||||
|
{
|
||||||
|
mover.RelativeRotation = mover.TargetRelativeRotation;
|
||||||
|
Dirty(mover);
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentRotation = GetParentGridAngle(mover);
|
||||||
var worldTotal = _relativeMovement ? parentRotation.RotateVec(total) : total;
|
var worldTotal = _relativeMovement ? parentRotation.RotateVec(total) : total;
|
||||||
|
|
||||||
DebugTools.Assert(MathHelper.CloseToPercent(total.Length, worldTotal.Length));
|
DebugTools.Assert(MathHelper.CloseToPercent(total.Length, worldTotal.Length));
|
||||||
@@ -190,17 +265,11 @@ namespace Content.Shared.Movement.Systems
|
|||||||
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 (xform.GridUid != EntityUid.Invalid)
|
|
||||||
mover.LastGridAngle = parentRotation;
|
|
||||||
|
|
||||||
if (worldTotal != Vector2.Zero)
|
if (worldTotal != Vector2.Zero)
|
||||||
{
|
{
|
||||||
// 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();
|
||||||
xform.LocalRotation = xform.GridUid != null
|
|
||||||
? total.ToWorldAngle()
|
|
||||||
: worldTotal.ToWorldAngle();
|
|
||||||
xform.DeferUpdates = false;
|
xform.DeferUpdates = false;
|
||||||
|
|
||||||
if (!weightless && TryComp<MobMoverComponent>(mover.Owner, out var mobMover) &&
|
if (!weightless && TryComp<MobMoverComponent>(mover.Owner, out var mobMover) &&
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ ui-options-bind-reset = Reset
|
|||||||
ui-options-key-prompt = Press a key...
|
ui-options-key-prompt = Press a key...
|
||||||
|
|
||||||
ui-options-header-movement = Movement
|
ui-options-header-movement = Movement
|
||||||
|
ui-options-header-camera = Camera
|
||||||
ui-options-header-interaction-basic = Basic Interaction
|
ui-options-header-interaction-basic = Basic Interaction
|
||||||
ui-options-header-interaction-adv = Advanced Interaction
|
ui-options-header-interaction-adv = Advanced Interaction
|
||||||
ui-options-header-ui = User Interface
|
ui-options-header-ui = User Interface
|
||||||
@@ -84,6 +85,10 @@ ui-options-function-move-down = Move Down
|
|||||||
ui-options-function-move-right = Move Right
|
ui-options-function-move-right = Move Right
|
||||||
ui-options-function-walk = Walk
|
ui-options-function-walk = Walk
|
||||||
|
|
||||||
|
ui-options-function-camera-rotate-left = Rotate left
|
||||||
|
ui-options-function-camera-rotate-right = Rotate right
|
||||||
|
ui-options-function-camera-reset = Reset
|
||||||
|
|
||||||
ui-options-function-use = Use
|
ui-options-function-use = Use
|
||||||
ui-options-function-wide-attack = Wide attack
|
ui-options-function-wide-attack = Wide attack
|
||||||
ui-options-function-activate-item-in-hand = Activate item in hand
|
ui-options-function-activate-item-in-hand = Activate item in hand
|
||||||
|
|||||||
@@ -61,13 +61,17 @@ binds:
|
|||||||
- function: ShuttleBrake
|
- function: ShuttleBrake
|
||||||
type: State
|
type: State
|
||||||
key: Space
|
key: Space
|
||||||
|
# Camera
|
||||||
- function: CameraRotateLeft
|
- function: CameraRotateLeft
|
||||||
type: State
|
type: State
|
||||||
key: NumpadNum7
|
key: NumpadNum7
|
||||||
- function: CameraRotateRight
|
- function: CameraRotateRight
|
||||||
type: State
|
type: State
|
||||||
key: NumpadNum9
|
key: NumpadNum9
|
||||||
|
- function: CameraReset
|
||||||
|
type: State
|
||||||
|
key: NumpadNum8
|
||||||
|
# Misc
|
||||||
- function: ShowEscapeMenu
|
- function: ShowEscapeMenu
|
||||||
type: State
|
type: State
|
||||||
key: Escape
|
key: Escape
|
||||||
|
|||||||
Reference in New Issue
Block a user