Fix some tabletop prediction jank (#12758)
This commit is contained in:
@@ -16,7 +16,6 @@ using Robust.Shared.Input.Binding;
|
|||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using static Robust.Shared.Input.Binding.PointerInputCmdHandler;
|
using static Robust.Shared.Input.Binding.PointerInputCmdHandler;
|
||||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
|
||||||
|
|
||||||
namespace Content.Client.Tabletop
|
namespace Content.Client.Tabletop
|
||||||
{
|
{
|
||||||
@@ -27,7 +26,6 @@ namespace Content.Client.Tabletop
|
|||||||
[Dependency] private readonly IUserInterfaceManager _uiManger = default!;
|
[Dependency] private readonly IUserInterfaceManager _uiManger = 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 SharedAppearanceSystem _appearance = default!;
|
|
||||||
|
|
||||||
// Time in seconds to wait until sending the location of a dragged entity to the server again
|
// Time in seconds to wait until sending the location of a dragged entity to the server again
|
||||||
private const float Delay = 1f / 10; // 10 Hz
|
private const float Delay = 1f / 10; // 10 Hz
|
||||||
@@ -40,10 +38,11 @@ namespace Content.Client.Tabletop
|
|||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
|
base.Initialize();
|
||||||
UpdatesOutsidePrediction = true;
|
UpdatesOutsidePrediction = true;
|
||||||
|
|
||||||
CommandBinds.Builder
|
CommandBinds.Builder
|
||||||
.Bind(EngineKeyFunctions.Use, new PointerInputCmdHandler(OnUse, false))
|
.Bind(EngineKeyFunctions.Use, new PointerInputCmdHandler(OnUse, false, true))
|
||||||
.Register<TabletopSystem>();
|
.Register<TabletopSystem>();
|
||||||
|
|
||||||
SubscribeNetworkEvent<TabletopPlayEvent>(OnTabletopPlay);
|
SubscribeNetworkEvent<TabletopPlayEvent>(OnTabletopPlay);
|
||||||
@@ -57,12 +56,8 @@ namespace Content.Client.Tabletop
|
|||||||
StopDragging(false);
|
StopDragging(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void FrameUpdate(float frameTime)
|
||||||
{
|
{
|
||||||
// don't send network messages when doing prediction.
|
|
||||||
if (!_gameTiming.IsFirstTimePredicted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_window == null)
|
if (_window == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -110,7 +105,7 @@ namespace Content.Client.Tabletop
|
|||||||
// Only send new position to server when Delay is reached
|
// Only send new position to server when Delay is reached
|
||||||
if (_timePassed >= Delay && _table != null)
|
if (_timePassed >= Delay && _table != null)
|
||||||
{
|
{
|
||||||
RaiseNetworkEvent(new TabletopMoveEvent(_draggedEntity.Value, clampedCoords, _table.Value));
|
RaisePredictiveEvent(new TabletopMoveEvent(_draggedEntity.Value, clampedCoords, _table.Value));
|
||||||
_timePassed -= Delay;
|
_timePassed -= Delay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,6 +164,9 @@ namespace Content.Client.Tabletop
|
|||||||
|
|
||||||
private bool OnUse(in PointerInputCmdArgs args)
|
private bool OnUse(in PointerInputCmdArgs args)
|
||||||
{
|
{
|
||||||
|
if (!_gameTiming.IsFirstTimePredicted)
|
||||||
|
return false;
|
||||||
|
|
||||||
return args.State switch
|
return args.State switch
|
||||||
{
|
{
|
||||||
BoundKeyState.Down => OnMouseDown(args),
|
BoundKeyState.Down => OnMouseDown(args),
|
||||||
@@ -216,13 +214,7 @@ namespace Content.Client.Tabletop
|
|||||||
/// <param name="viewport">The viewport in which we are dragging.</param>
|
/// <param name="viewport">The viewport in which we are dragging.</param>
|
||||||
private void StartDragging(EntityUid draggedEntity, ScalingViewport viewport)
|
private void StartDragging(EntityUid draggedEntity, ScalingViewport viewport)
|
||||||
{
|
{
|
||||||
RaiseNetworkEvent(new TabletopDraggingPlayerChangedEvent(draggedEntity, true));
|
RaisePredictiveEvent(new TabletopDraggingPlayerChangedEvent(draggedEntity, true));
|
||||||
|
|
||||||
if (EntityManager.TryGetComponent<AppearanceComponent>(draggedEntity, out var appearance))
|
|
||||||
{
|
|
||||||
_appearance.SetData(draggedEntity, TabletopItemVisuals.Scale, new Vector2(1.25f, 1.25f), appearance);
|
|
||||||
_appearance.SetData(draggedEntity, TabletopItemVisuals.DrawDepth, (int) DrawDepth.Items + 1, appearance);
|
|
||||||
}
|
|
||||||
|
|
||||||
_draggedEntity = draggedEntity;
|
_draggedEntity = draggedEntity;
|
||||||
_viewport = viewport;
|
_viewport = viewport;
|
||||||
@@ -237,7 +229,8 @@ namespace Content.Client.Tabletop
|
|||||||
// Set the dragging player on the component to noone
|
// Set the dragging player on the component to noone
|
||||||
if (broadcast && _draggedEntity != null && EntityManager.HasComponent<TabletopDraggableComponent>(_draggedEntity.Value))
|
if (broadcast && _draggedEntity != null && EntityManager.HasComponent<TabletopDraggableComponent>(_draggedEntity.Value))
|
||||||
{
|
{
|
||||||
RaiseNetworkEvent(new TabletopDraggingPlayerChangedEvent(_draggedEntity.Value, false));
|
RaisePredictiveEvent(new TabletopMoveEvent(_draggedEntity.Value, Transform(_draggedEntity.Value).MapPosition, _table!.Value));
|
||||||
|
RaisePredictiveEvent(new TabletopDraggingPlayerChangedEvent(_draggedEntity.Value, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
_draggedEntity = null;
|
_draggedEntity = null;
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
using Content.Server.Tabletop.Components;
|
|
||||||
using Content.Shared.Tabletop;
|
|
||||||
using Content.Shared.Tabletop.Components;
|
|
||||||
using Content.Shared.Tabletop.Events;
|
|
||||||
using Robust.Server.Player;
|
|
||||||
using Robust.Shared.GameStates;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
|
||||||
|
|
||||||
namespace Content.Server.Tabletop
|
|
||||||
{
|
|
||||||
public sealed partial class TabletopSystem
|
|
||||||
{
|
|
||||||
public void InitializeDraggable()
|
|
||||||
{
|
|
||||||
SubscribeNetworkEvent<TabletopMoveEvent>(OnTabletopMove);
|
|
||||||
SubscribeNetworkEvent<TabletopDraggingPlayerChangedEvent>(OnDraggingPlayerChanged);
|
|
||||||
SubscribeLocalEvent<TabletopDraggableComponent, ComponentGetState>(GetDraggableState);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Move an entity which is dragged by the user, but check if they are allowed to do so and to these coordinates
|
|
||||||
/// </summary>
|
|
||||||
private void OnTabletopMove(TabletopMoveEvent msg, EntitySessionEventArgs args)
|
|
||||||
{
|
|
||||||
if (args.SenderSession as IPlayerSession is not { AttachedEntity: { } playerEntity } playerSession)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!EntityManager.TryGetComponent(msg.TableUid, out TabletopGameComponent? tabletop) || tabletop.Session is not {} session)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Check if player is actually playing at this table
|
|
||||||
if (!session.Players.ContainsKey(playerSession))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!CanSeeTable(playerEntity, msg.TableUid) || !CanDrag(playerEntity, msg.MovedEntityUid, out _))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// TODO: some permission system, disallow movement if you're not permitted to move the item
|
|
||||||
|
|
||||||
// Move the entity and dirty it (we use the map ID from the entity so noone can try to be funny and move the item to another map)
|
|
||||||
var transform = EntityManager.GetComponent<TransformComponent>(msg.MovedEntityUid);
|
|
||||||
var entityCoordinates = new EntityCoordinates(_mapManager.GetMapEntityId(transform.MapID), msg.Coordinates.Position);
|
|
||||||
transform.Coordinates = entityCoordinates;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDraggingPlayerChanged(TabletopDraggingPlayerChangedEvent msg, EntitySessionEventArgs args)
|
|
||||||
{
|
|
||||||
var dragged = msg.DraggedEntityUid;
|
|
||||||
|
|
||||||
if (!EntityManager.TryGetComponent<TabletopDraggableComponent?>(dragged, out var draggableComponent)) return;
|
|
||||||
|
|
||||||
draggableComponent.DraggingPlayer = msg.IsDragging ? args.SenderSession.UserId : null;
|
|
||||||
Dirty(draggableComponent);
|
|
||||||
|
|
||||||
if (!EntityManager.TryGetComponent<AppearanceComponent?>(dragged, out var appearance)) return;
|
|
||||||
|
|
||||||
if (draggableComponent.DraggingPlayer != null)
|
|
||||||
{
|
|
||||||
appearance.SetData(TabletopItemVisuals.Scale, new Vector2(1.25f, 1.25f));
|
|
||||||
appearance.SetData(TabletopItemVisuals.DrawDepth, (int) DrawDepth.Items + 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
appearance.SetData(TabletopItemVisuals.Scale, Vector2.One);
|
|
||||||
appearance.SetData(TabletopItemVisuals.DrawDepth, (int) DrawDepth.Items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GetDraggableState(EntityUid uid, TabletopDraggableComponent component, ref ComponentGetState args)
|
|
||||||
{
|
|
||||||
args.State = new TabletopDraggableComponentState(component.DraggingPlayer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,6 +19,7 @@ namespace Content.Server.Tabletop
|
|||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
|
base.Initialize();
|
||||||
SubscribeNetworkEvent<TabletopStopPlayingEvent>(OnStopPlaying);
|
SubscribeNetworkEvent<TabletopStopPlayingEvent>(OnStopPlaying);
|
||||||
SubscribeLocalEvent<TabletopGameComponent, ActivateInWorldEvent>(OnTabletopActivate);
|
SubscribeLocalEvent<TabletopGameComponent, ActivateInWorldEvent>(OnTabletopActivate);
|
||||||
SubscribeLocalEvent<TabletopGameComponent, ComponentShutdown>(OnGameShutdown);
|
SubscribeLocalEvent<TabletopGameComponent, ComponentShutdown>(OnGameShutdown);
|
||||||
@@ -27,7 +28,21 @@ namespace Content.Server.Tabletop
|
|||||||
SubscribeLocalEvent<TabletopGameComponent, GetVerbsEvent<ActivationVerb>>(AddPlayGameVerb);
|
SubscribeLocalEvent<TabletopGameComponent, GetVerbsEvent<ActivationVerb>>(AddPlayGameVerb);
|
||||||
|
|
||||||
InitializeMap();
|
InitializeMap();
|
||||||
InitializeDraggable();
|
}
|
||||||
|
|
||||||
|
protected override void OnTabletopMove(TabletopMoveEvent msg, EntitySessionEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.SenderSession is not IPlayerSession playerSession)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp(msg.TableUid, out TabletopGameComponent? tabletop) || tabletop.Session is not { } session)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check if player is actually playing at this table
|
||||||
|
if (!session.Players.ContainsKey(playerSession))
|
||||||
|
return;
|
||||||
|
|
||||||
|
base.OnTabletopMove(msg, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ using Content.Shared.ActionBlocker;
|
|||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Tabletop.Components;
|
using Content.Shared.Tabletop.Components;
|
||||||
|
using Content.Shared.Tabletop.Events;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
@@ -10,8 +14,66 @@ namespace Content.Shared.Tabletop
|
|||||||
{
|
{
|
||||||
public abstract class SharedTabletopSystem : EntitySystem
|
public abstract class SharedTabletopSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] protected readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
[Dependency] protected readonly ActionBlockerSystem ActionBlockerSystem = default!;
|
||||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||||
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transforms = default!;
|
||||||
|
[Dependency] private readonly IMapManager _mapMan = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<TabletopDraggableComponent, ComponentGetState>(GetDraggableState);
|
||||||
|
SubscribeAllEvent<TabletopDraggingPlayerChangedEvent>(OnDraggingPlayerChanged);
|
||||||
|
SubscribeAllEvent<TabletopMoveEvent>(OnTabletopMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Move an entity which is dragged by the user, but check if they are allowed to do so and to these coordinates
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnTabletopMove(TabletopMoveEvent msg, EntitySessionEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.SenderSession is not { AttachedEntity: { } playerEntity } playerSession)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!CanSeeTable(playerEntity, msg.TableUid) || !CanDrag(playerEntity, msg.MovedEntityUid, out _))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Move the entity and dirty it (we use the map ID from the entity so noone can try to be funny and move the item to another map)
|
||||||
|
var transform = EntityManager.GetComponent<TransformComponent>(msg.MovedEntityUid);
|
||||||
|
_transforms.SetParent(transform, _mapMan.GetMapEntityId(transform.MapID));
|
||||||
|
_transforms.SetLocalPositionNoLerp(transform, msg.Coordinates.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetDraggableState(EntityUid uid, TabletopDraggableComponent component, ref ComponentGetState args)
|
||||||
|
{
|
||||||
|
args.State = new TabletopDraggableComponentState(component.DraggingPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDraggingPlayerChanged(TabletopDraggingPlayerChangedEvent msg, EntitySessionEventArgs args)
|
||||||
|
{
|
||||||
|
var dragged = msg.DraggedEntityUid;
|
||||||
|
|
||||||
|
if (!TryComp(dragged, out TabletopDraggableComponent? draggableComponent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
draggableComponent.DraggingPlayer = msg.IsDragging ? args.SenderSession.UserId : null;
|
||||||
|
Dirty(draggableComponent);
|
||||||
|
|
||||||
|
if (!TryComp(dragged, out AppearanceComponent? appearance))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (draggableComponent.DraggingPlayer != null)
|
||||||
|
{
|
||||||
|
_appearance.SetData(dragged, TabletopItemVisuals.Scale, new Vector2(1.25f, 1.25f), appearance);
|
||||||
|
_appearance.SetData(dragged, TabletopItemVisuals.DrawDepth, (int) DrawDepth.DrawDepth.Items + 1, appearance);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_appearance.SetData(dragged, TabletopItemVisuals.Scale, Vector2.One, appearance);
|
||||||
|
_appearance.SetData(dragged, TabletopItemVisuals.DrawDepth, (int) DrawDepth.DrawDepth.Items, appearance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class TabletopDraggableComponentState : ComponentState
|
public sealed class TabletopDraggableComponentState : ComponentState
|
||||||
@@ -41,7 +103,7 @@ namespace Content.Shared.Tabletop
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _interactionSystem.InRangeUnobstructed(playerEntity, table.Value) && _actionBlockerSystem.CanInteract(playerEntity, table);
|
return _interactionSystem.InRangeUnobstructed(playerEntity, table.Value) && ActionBlockerSystem.CanInteract(playerEntity, table);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool CanDrag(EntityUid playerEntity, EntityUid target, [NotNullWhen(true)] out TabletopDraggableComponent? draggable)
|
protected bool CanDrag(EntityUid playerEntity, EntityUid target, [NotNullWhen(true)] out TabletopDraggableComponent? draggable)
|
||||||
@@ -51,7 +113,7 @@ namespace Content.Shared.Tabletop
|
|||||||
|
|
||||||
// CanSeeTable checks interaction action blockers. So no need to check them here.
|
// CanSeeTable checks interaction action blockers. So no need to check them here.
|
||||||
// If this ever changes, so that ghosts can spectate games, then the check needs to be moved here.
|
// If this ever changes, so that ghosts can spectate games, then the check needs to be moved here.
|
||||||
|
|
||||||
return TryComp(playerEntity, out SharedHandsComponent? hands) && hands.Hands.Count > 0;
|
return TryComp(playerEntity, out SharedHandsComponent? hands) && hands.Hands.Count > 0;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
Reference in New Issue
Block a user