Files
RobustToolbox/Robust.Client/BaseClient.cs
T
f2625bf5c5 Removes IMapManager (#6584)
* Move MapManager queries to SharedMapSystem
Moves all variants of FindGridsIntersecting/TryFindGridAt to SharedMapSystem
Hollows out the MapManager methods and converts them into relays to SharedMapSystem

* Move CreateGrid to SharedMapSystem
Moves the functionality for CreateGrid and its variants to SharedMapSystem
Hollows out the MapManager methods and converts them into relays for the SharedMapSystem methods
Obsoletes them too
Also moves over the GetAllMapGrids and GetAllGrids methods

* Move RaiseOnTileChanged to SharedMapSystem
Also moves the SuppressOnTileChanged flag member to SharedMapSystem
Hollows out and obsoletes the MapManager versions

* Move default value constants to SharedMapSystem

* Converts map pausing events into LocalizedEntityCommands

* Move MapManager related delegates/structs to SharedMapSystem
Moves the GridCreationOptions struct to the same namespace as SharedMapSystem and converts it into a record struct
Move the GridCallback delegates to the same namespace as SharedMapSystem

* Move CullDeletionHistory to SharedMapSystem
Well, that was less painful than I thought it would be

* Actually obsolete the NetworkedMapManager method

* Rename file

* Doc comments for new SharedMapSystem methods

* Doc comment

* Doc comments

* Fix access

* Purge all references to IMapManagerInternal

* Cull easy IMapManager references

* The rest

* Purge IMapManager

* Private internal FindGridsIntersecting method

* please die

* 41

* notes

---------

Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
2026-07-01 19:32:17 -07:00

407 lines
13 KiB
C#

using System;
using System.Net;
using Robust.Client.Configuration;
using Robust.Client.GameObjects;
using Robust.Client.GameStates;
using Robust.Client.Player;
using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client
{
/// <inheritdoc />
public sealed partial class BaseClient : IBaseClient, IPostInjectInit
{
[Dependency] private IClientNetManager _net = default!;
[Dependency] private IPlayerManager _playMan = default!;
[Dependency] private IClientNetConfigurationManager _configManager = default!;
[Dependency] private IClientEntityManager _entityManager = default!;
[Dependency] private IDiscordRichPresence _discord = default!;
[Dependency] private IGameTiming _timing = default!;
[Dependency] private IClientGameStateManager _gameStates = default!;
[Dependency] private ILogManager _logMan = default!;
/// <inheritdoc />
public ushort DefaultPort { get; } = 1212;
/// <inheritdoc />
public ClientRunLevel RunLevel { get; private set; }
/// <inheritdoc />
public ServerInfo? GameInfo { get; private set; }
/// <inheritdoc />
public string? PlayerNameOverride { get; set; }
public string? LastDisconnectReason { get; private set; }
private (TimeSpan, GameTick) _timeBase;
private ISawmill _logger = default!;
/// <inheritdoc />
public void Initialize()
{
_net.Connected += OnConnected;
_net.ConnectFailed += OnConnectFailed;
_net.Disconnect += OnNetDisconnect;
_net.RegisterNetMessage<MsgSyncTimeBase>(
SyncTimeBase,
NetMessageAccept.Handshake | NetMessageAccept.Client);
_configManager.OnValueChanged(CVars.NetTickrate, TickRateChanged, invokeImmediately: true);
_configManager.OnValueChanged(CVars.GameTimeScale, TimeScaleChanged, invokeImmediately: true);
_playMan.Initialize(0);
_playMan.PlayerListUpdated += OnPlayerListUpdated;
Reset();
}
private void OnPlayerListUpdated()
{
var serverPlayers = _playMan.PlayerCount;
if (_net.ServerChannel != null && GameInfo != null && _net.IsConnected)
_discord.Update(GameInfo.ServerName, _net.ServerChannel.UserName, GameInfo.ServerMaxPlayers.ToString(), serverPlayers.ToString());
}
private void SyncTimeBase(MsgSyncTimeBase message)
{
_logger.Debug($"Synchronized time base: {message.Tick}: {message.Time}");
if (RunLevel >= ClientRunLevel.Connected)
_timing.TimeBase = (message.Time, message.Tick);
else
_timeBase = (message.Time, message.Tick);
}
private void TickRateChanged(int tickrate, in CVarChangeInfo info)
{
if (GameInfo != null)
{
GameInfo.TickRate = (ushort) tickrate;
}
_timing.SetTickRateAt((ushort) tickrate, info.TickChanged);
_logger.Info($"Tickrate changed to: {tickrate} on tick {_timing.CurTick}");
}
private void TimeScaleChanged(float timeScale, in CVarChangeInfo info)
{
if (!GameTiming.IsTimescaleValid(timeScale))
{
_logger.Error($"Invalid time scale set: {timeScale}, ignoring");
return;
}
_timing.TimeScale = timeScale;
_logger.Info($"Tickrate changed to: {timeScale} on tick {_timing.CurTick}");
}
/// <inheritdoc />
public void ConnectToServer(DnsEndPoint endPoint)
{
if (RunLevel == ClientRunLevel.Connecting)
{
_net.Reset("Client mashing that connect button.");
Reset();
}
DebugTools.Assert(RunLevel < ClientRunLevel.Connecting);
DebugTools.Assert(!_net.IsConnected);
OnRunLevelChanged(ClientRunLevel.Connecting);
_net.ClientConnect(endPoint.Host, endPoint.Port,
PlayerNameOverride ?? _configManager.GetCVar(CVars.PlayerName));
}
/// <inheritdoc />
public void DisconnectFromServer(string reason)
{
_net.ClientDisconnect(reason);
}
/// <inheritdoc />
public void StartSinglePlayer()
{
DebugTools.Assert(RunLevel < ClientRunLevel.Connecting);
DebugTools.Assert(!_net.IsConnected);
var name = PlayerNameOverride ?? _configManager.GetCVar(CVars.PlayerName);
_playMan.SetupSinglePlayer(name);
OnRunLevelChanged(ClientRunLevel.SinglePlayerGame);
_playMan.JoinGame(_playMan.LocalSession!);
GameStartedSetup();
}
/// <inheritdoc />
public void StopSinglePlayer()
{
DebugTools.Assert(RunLevel == ClientRunLevel.SinglePlayerGame);
DebugTools.Assert(!_net.IsConnected);
GameStoppedReset();
}
/// <inheritdoc />
public event EventHandler<RunLevelChangedEventArgs>? RunLevelChanged;
public event EventHandler<PlayerEventArgs>? PlayerJoinedServer;
public event EventHandler<PlayerEventArgs>? PlayerJoinedGame;
public event EventHandler<PlayerEventArgs>? PlayerLeaveServer;
private void OnConnected(object? sender, NetChannelArgs args)
{
_configManager.SyncWithServer();
_configManager.ReceivedInitialNwVars += OnReceivedClientData;
}
private void OnReceivedClientData(object? sender, EventArgs e)
{
_configManager.ReceivedInitialNwVars -= OnReceivedClientData;
var info = GameInfo;
var serverName = _configManager.GetCVar<string>("game.hostname");
if (info == null)
{
GameInfo = info = new ServerInfo(serverName);
}
else
{
info.ServerName = serverName;
}
var channel = _net.ServerChannel!;
// start up player management
_playMan.SetupMultiplayer(channel);
_playMan.PlayerStatusChanged += OnStatusChanged;
var serverPlayers = _playMan.PlayerCount;
_discord.Update(info.ServerName, channel.UserName, info.ServerMaxPlayers.ToString(), serverPlayers.ToString());
}
/// <summary>
/// Player session is fully built, player is an active member of the server. Player is prepared to start
/// receiving states when they join the lobby.
/// </summary>
/// <param name="session">Session of the player.</param>
private void OnPlayerJoinedServer(ICommonSession session)
{
DebugTools.Assert(RunLevel < ClientRunLevel.Connected);
OnRunLevelChanged(ClientRunLevel.Connected);
GameStartedSetup();
PlayerJoinedServer?.Invoke(this, new PlayerEventArgs(session));
}
/// <summary>
/// Player is joining the game
/// </summary>
/// <param name="session">Session of the player.</param>
private void OnPlayerJoinedGame(ICommonSession session)
{
DebugTools.Assert(RunLevel >= ClientRunLevel.Connected);
OnRunLevelChanged(ClientRunLevel.InGame);
PlayerJoinedGame?.Invoke(this, new PlayerEventArgs(session));
}
private void Reset()
{
_configManager.ReceivedInitialNwVars -= OnReceivedClientData;
_playMan.PlayerStatusChanged -= OnStatusChanged;
_configManager.ClearReceivedInitialNwVars();
OnRunLevelChanged(ClientRunLevel.Initialize);
}
private void OnConnectFailed(object? sender, NetConnectFailArgs args)
{
DebugTools.Assert(RunLevel == ClientRunLevel.Connecting);
Reset();
}
private void OnNetDisconnect(object? sender, NetDisconnectedArgs args)
{
DebugTools.Assert(RunLevel > ClientRunLevel.Initialize);
// Don't invoke PlayerLeaveServer if PlayerJoinedServer & GameStartedSetup hasn't been called yet.
if (RunLevel > ClientRunLevel.Connecting)
PlayerLeaveServer?.Invoke(this, new PlayerEventArgs(_playMan.LocalSession));
LastDisconnectReason = args.Reason;
GameStoppedReset();
}
private void GameStartedSetup()
{
_entityManager.Startup();
_timing.ResetSimTime(_timeBase);
_timing.Paused = false;
}
private void GameStoppedReset()
{
_configManager.FlushMessages();
_gameStates.Reset();
_playMan.Shutdown();
_entityManager.Shutdown();
_discord.ClearPresence();
Reset();
}
private void OnStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.Session != _playMan.LocalSession)
return;
// player finished fully connecting to the server.
// OldStatus is used here because it can go from connecting-> connected or connecting-> ingame
if (e.OldStatus == SessionStatus.Connecting)
OnPlayerJoinedServer(e.Session);
else if (e.NewStatus == SessionStatus.InGame)
OnPlayerJoinedGame(e.Session);
}
private void OnRunLevelChanged(ClientRunLevel newRunLevel)
{
_logger.Debug($"Runlevel changed to: {newRunLevel}");
var args = new RunLevelChangedEventArgs(RunLevel, newRunLevel);
RunLevel = newRunLevel;
RunLevelChanged?.Invoke(this, args);
}
void IPostInjectInit.PostInject()
{
_logger = _logMan.GetSawmill("client");
}
}
/// <summary>
/// Enumeration of the run levels of the BaseClient.
/// </summary>
/// <seealso cref="ClientRunLevelExt"/>
public enum ClientRunLevel : byte
{
Error = 0,
/// <summary>
/// The client has not started connecting to a server (on main menu).
/// </summary>
Initialize,
/// <summary>
/// The client started connecting to the server, and is in the process of building the session.
/// </summary>
Connecting,
/// <summary>
/// The client has successfully finished connecting to the server.
/// </summary>
Connected,
/// <summary>
/// The client is now in the game, moving around.
/// </summary>
InGame,
/// <summary>
/// The client is now in singleplayer mode, in-game.
/// </summary>
SinglePlayerGame,
}
/// <summary>
/// Helper functions for working with <see cref="ClientRunLevel"/>.
/// </summary>
public static class ClientRunLevelExt
{
/// <summary>
/// Check if a <see cref="ClientRunLevel"/> is <see cref="ClientRunLevel.InGame"/>
/// or <see cref="ClientRunLevel.SinglePlayerGame"/>.
/// </summary>
public static bool IsInGameLike(this ClientRunLevel runLevel)
{
return runLevel is ClientRunLevel.InGame or ClientRunLevel.SinglePlayerGame;
}
}
/// <summary>
/// Event arguments for when something changed with the player.
/// </summary>
public sealed class PlayerEventArgs : EventArgs
{
/// <summary>
/// The session that triggered the event.
/// </summary>
private ICommonSession? Session { get; }
/// <summary>
/// Constructs a new instance of the class.
/// </summary>
public PlayerEventArgs(ICommonSession? session)
{
Session = session;
}
}
/// <summary>
/// Event arguments for when the RunLevel has changed in the BaseClient.
/// </summary>
public sealed class RunLevelChangedEventArgs : EventArgs
{
/// <summary>
/// RunLevel that the BaseClient switched from.
/// </summary>
public ClientRunLevel OldLevel { get; }
/// <summary>
/// RunLevel that the BaseClient switched to.
/// </summary>
public ClientRunLevel NewLevel { get; }
/// <summary>
/// Constructs a new instance of the class.
/// </summary>
public RunLevelChangedEventArgs(ClientRunLevel oldLevel, ClientRunLevel newLevel)
{
OldLevel = oldLevel;
NewLevel = newLevel;
}
}
/// <summary>
/// Info about the server and player that is sent to the client while connecting.
/// </summary>
public sealed class ServerInfo
{
public ServerInfo(string serverName)
{
ServerName = serverName;
}
/// <summary>
/// Current name of the server.
/// </summary>
public string ServerName { get; set; }
/// <summary>
/// Max number of players that are allowed in the server at one time.
/// </summary>
public int ServerMaxPlayers { get; set; }
public uint TickRate { get; internal set; }
}
}