using System;
using Robust.Client.GameStates;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Players;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
///
/// Client-side processing of all input commands through the simulation.
///
public class InputSystem : SharedInputSystem
{
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientGameStateManager _stateManager = default!;
private readonly IPlayerCommandStates _cmdStates = new PlayerCommandStates();
///
/// Current states for all of the keyFunctions.
///
public IPlayerCommandStates CmdStates => _cmdStates;
///
/// If the input system is currently predicting input.
///
public bool Predicted { get; private set; }
///
/// Inserts an Input Command into the simulation.
///
/// Player session that raised the command. On client, this is always the LocalPlayer session.
/// Function that is being changed.
/// Arguments for this event.
/// if true, current cmd state will not be checked or updated - use this for "replaying" an
/// old input that was saved or buffered until further processing could be done
public bool HandleInputCommand(ICommonSession? session, BoundKeyFunction function, FullInputCmdMessage message, bool replay = false)
{
#if DEBUG
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(function);
DebugTools.Assert(funcId == message.InputFunctionId, "Function ID in message does not match function.");
#endif
if (!replay)
{
// set state, state change is updated regardless if it is locally bound
if (_cmdStates.GetState(function) == message.State)
{
return false;
}
_cmdStates.SetState(function, message.State);
}
// handle local binds before sending off
foreach (var handler in BindRegistry.GetHandlers(function))
{
// local handlers can block sending over the network.
if (handler.HandleCmdMessage(session, message))
{
return true;
}
}
// send it off to the server
DispatchInputCommand(message);
return false;
}
///
/// Handle a predicted input command.
///
/// Input command to handle as predicted.
public void PredictInputCommand(FullInputCmdMessage inputCmd)
{
DebugTools.AssertNotNull(_playerManager.LocalPlayer);
var keyFunc = _inputManager.NetworkBindMap.KeyFunctionName(inputCmd.InputFunctionId);
Predicted = true;
var session = _playerManager.LocalPlayer!.Session;
foreach (var handler in BindRegistry.GetHandlers(keyFunc))
{
if (handler.HandleCmdMessage(session, inputCmd)) break;
}
Predicted = false;
}
private void DispatchInputCommand(FullInputCmdMessage message)
{
_stateManager.InputCommandDispatched(message);
EntityNetworkManager.SendSystemNetworkMessage(message, message.InputSequence);
}
public override void Initialize()
{
SubscribeLocalEvent(OnAttachedEntityChanged);
}
private void OnAttachedEntityChanged(PlayerAttachSysMessage message)
{
if (message.AttachedEntity != null) // attach
{
SetEntityContextActive(_inputManager, message.AttachedEntity);
}
else // detach
{
_inputManager.Contexts.SetActiveContext(InputContextContainer.DefaultContextName);
}
}
private static void SetEntityContextActive(IInputManager inputMan, IEntity entity)
{
if(entity == null || !entity.IsValid())
throw new ArgumentNullException(nameof(entity));
if (!entity.TryGetComponent(out InputComponent? inputComp))
{
Logger.DebugS("input.context", $"AttachedEnt has no InputComponent: entId={entity.Uid}, entProto={entity.Prototype}. Setting default \"{InputContextContainer.DefaultContextName}\" context...");
inputMan.Contexts.SetActiveContext(InputContextContainer.DefaultContextName);
return;
}
if (inputMan.Contexts.Exists(inputComp.ContextName))
{
inputMan.Contexts.SetActiveContext(inputComp.ContextName);
}
else
{
Logger.ErrorS("input.context", $"Unknown context: entId={entity.Uid}, entProto={entity.Prototype}, context={inputComp.ContextName}. . Setting default \"{InputContextContainer.DefaultContextName}\" context...");
inputMan.Contexts.SetActiveContext(InputContextContainer.DefaultContextName);
}
}
///
/// Sets the active context to the defined context on the attached entity.
///
public void SetEntityContextActive()
{
if (_playerManager.LocalPlayer?.ControlledEntity == null)
{
return;
}
SetEntityContextActive(_inputManager, _playerManager.LocalPlayer.ControlledEntity);
}
}
///
/// Entity system message that is raised when the player changes attached entities.
///
public class PlayerAttachSysMessage : EntitySystemMessage
{
///
/// New entity the player is attached to.
///
public IEntity? AttachedEntity { get; }
///
/// Creates a new instance of .
///
/// New entity the player is attached to.
public PlayerAttachSysMessage(IEntity? attachedEntity)
{
AttachedEntity = attachedEntity;
}
}
}