mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-02 01:57:07 +02:00
* 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>
829 lines
31 KiB
C#
829 lines
31 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Runtime;
|
|
using System.Threading.Tasks;
|
|
using Robust.Client.Audio;
|
|
using Robust.Client.Audio.Midi;
|
|
using Robust.Client.Console;
|
|
using Robust.Client.GameObjects;
|
|
using Robust.Client.GameStates;
|
|
using Robust.Client.Graphics;
|
|
using Robust.Client.Input;
|
|
using Robust.Client.Network.Transfer;
|
|
using Robust.Client.Placement;
|
|
using Robust.Client.Replays.Loading;
|
|
using Robust.Client.Replays.Playback;
|
|
using Robust.Client.ResourceManagement;
|
|
using Robust.Client.State;
|
|
using Robust.Client.Upload;
|
|
using Robust.Client.UserInterface;
|
|
using Robust.Client.UserInterface.RichText;
|
|
using Robust.Client.UserInterface.XAML.Proxy;
|
|
using Robust.Client.Utility;
|
|
using Robust.Client.ViewVariables;
|
|
using Robust.Client.WebViewHook;
|
|
using Robust.LoaderApi;
|
|
using Robust.Shared;
|
|
using Robust.Shared.Asynchronous;
|
|
using Robust.Shared.Audio;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.ContentPack;
|
|
using Robust.Shared.Exceptions;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Localization;
|
|
using Robust.Shared.Log;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Network.Transfer;
|
|
using Robust.Shared.Profiling;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Reflection;
|
|
using Robust.Shared.Replays;
|
|
using Robust.Shared.Serialization;
|
|
using Robust.Shared.Serialization.Manager;
|
|
using Robust.Shared.Threading;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Upload;
|
|
using Robust.Shared.Utility;
|
|
using YamlDotNet.RepresentationModel;
|
|
|
|
namespace Robust.Client
|
|
{
|
|
internal sealed partial class GameController : IGameControllerInternal
|
|
{
|
|
[Dependency] private INetConfigurationManagerInternal _configurationManager = default!;
|
|
[Dependency] private IResourceCacheInternal _resourceCache = default!;
|
|
[Dependency] private IResourceManagerInternal _resManager = default!;
|
|
[Dependency] private IRobustSerializer _serializer = default!;
|
|
[Dependency] private IXamlProxyManager _xamlProxyManager = default!;
|
|
[Dependency] private IXamlHotReloadManager _xamlHotReloadManager = default!;
|
|
[Dependency] private IPrototypeManager _prototypeManager = default!;
|
|
[Dependency] private IClientNetManager _networkManager = default!;
|
|
[Dependency] private IStateManager _stateManager = default!;
|
|
[Dependency] private IUserInterfaceManagerInternal _userInterfaceManager = default!;
|
|
[Dependency] private IBaseClient _client = default!;
|
|
[Dependency] private IInputManager _inputManager = default!;
|
|
[Dependency] private IClientConsoleHost _console = default!;
|
|
[Dependency] private ITimerManager _timerManager = default!;
|
|
[Dependency] private IClientEntityManager _entityManager = default!;
|
|
[Dependency] private IPlacementManager _placementManager = default!;
|
|
[Dependency] private IClientGameStateManager _gameStateManager = default!;
|
|
[Dependency] private IOverlayManagerInternal _overlayManager = default!;
|
|
[Dependency] private ILogManager _logManager = default!;
|
|
[Dependency] private ITaskManager _taskManager = default!;
|
|
[Dependency] private IClientViewVariablesManagerInternal _viewVariablesManager = default!;
|
|
[Dependency] private IDiscordRichPresence _discord = default!;
|
|
[Dependency] private IClydeInternal _clyde = default!;
|
|
[Dependency] private IAudioInternal _audio = default!;
|
|
[Dependency] private IFontManagerInternal _fontManager = default!;
|
|
[Dependency] private IModLoaderInternal _modLoader = default!;
|
|
[Dependency] private IScriptClient _scriptClient = default!;
|
|
[Dependency] private IRobustMappedStringSerializer _stringSerializer = default!;
|
|
[Dependency] private IAuthManager _authManager = default!;
|
|
[Dependency] private IMidiManager _midiManager = default!;
|
|
[Dependency] private IEyeManager _eyeManager = default!;
|
|
[Dependency] private IParallelManagerInternal _parallelMgr = default!;
|
|
[Dependency] private ProfManager _prof = default!;
|
|
[Dependency] private IRuntimeLog _runtimeLog = default!;
|
|
[Dependency] private ISerializationManager _serializationManager = default!;
|
|
[Dependency] private MarkupTagManager _tagManager = default!;
|
|
[Dependency] private IGamePrototypeLoadManager _protoLoadMan = default!;
|
|
[Dependency] private NetworkResourceManager _netResMan = default!;
|
|
[Dependency] private IReplayLoadManager _replayLoader = default!;
|
|
[Dependency] private IReplayPlaybackManager _replayPlayback = default!;
|
|
[Dependency] private IReplayRecordingManagerInternal _replayRecording = default!;
|
|
[Dependency] private IReflectionManager _reflectionManager = default!;
|
|
[Dependency] private IReloadManager _reload = default!;
|
|
[Dependency] private ILocalizationManager _loc = default!;
|
|
[Dependency] private ISystemFontManagerInternal _systemFontManager = default!;
|
|
[Dependency] private LoadingScreenManager _loadscr = default!;
|
|
[Dependency] private ITransferManager _transfer = default!;
|
|
[Dependency] private ClientTransferTestManager _transferTest = default!;
|
|
|
|
private IWebViewManagerHook? _webViewHook;
|
|
|
|
private CommandLineArgs? _commandLineArgs;
|
|
|
|
// Arguments for loader-load. Not used otherwise.
|
|
private IMainArgs? _loaderArgs;
|
|
|
|
public bool ContentStart { get; set; } = false;
|
|
public StartType StartTypeValue { get; private set; }
|
|
public GameControllerOptions Options { get; private set; } = new();
|
|
public InitialLaunchState LaunchState { get; private set; } = default!;
|
|
|
|
private ResourceManifestData? _resourceManifest;
|
|
|
|
private DisplayMode _displayMode;
|
|
|
|
public void SetCommandLineArgs(CommandLineArgs args)
|
|
{
|
|
_commandLineArgs = args;
|
|
}
|
|
|
|
public string GameTitle()
|
|
{
|
|
return Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox";
|
|
}
|
|
|
|
public string WindowIconSet()
|
|
{
|
|
return Options.WindowIconSet?.ToString() ?? _resourceManifest!.WindowIconSet ?? "";
|
|
}
|
|
|
|
public string SplashLogo()
|
|
{
|
|
return Options.SplashLogo?.ToString() ?? _resourceManifest!.SplashLogo ?? "";
|
|
}
|
|
|
|
public bool ShowLoadingBar()
|
|
{
|
|
return _resourceManifest!.ShowLoadingBar ?? _configurationManager.GetCVar(CVars.LoadingShowBar);
|
|
}
|
|
|
|
internal bool StartupContinue(DisplayMode displayMode)
|
|
{
|
|
DebugTools.AssertNotNull(_resourceManifest);
|
|
|
|
_loadscr.Initialize(41);
|
|
|
|
_loadscr.BeginLoadingSection("Init graphics", dontRender: true);
|
|
_clyde.InitializePostWindowing();
|
|
_clyde.SetWindowTitle(GameTitle());
|
|
_loadscr.EndLoadingSection();
|
|
|
|
_loadscr.LoadingStep(_audio.InitializePostWindowing, "Init audio");
|
|
|
|
_loadscr.LoadingStep(_taskManager.Initialize, _taskManager);
|
|
_loadscr.LoadingStep(_parallelMgr.Initialize, _parallelMgr);
|
|
_fontManager.SetFontDpi((uint)_configurationManager.GetCVar(CVars.DisplayFontDpi));
|
|
|
|
_loadscr.LoadingStep(_systemFontManager.Initialize, "System fonts");
|
|
|
|
// Load optional Robust modules.
|
|
_loadscr.LoadingStep(() => LoadOptionalRobustModules(displayMode, _resourceManifest!), "Robust Modules");
|
|
|
|
_loadscr.BeginLoadingSection(_modLoader);
|
|
// Disable load context usage on content start.
|
|
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
|
|
_modLoader.SetUseLoadContext(!ContentStart);
|
|
var disableSandbox = Environment.GetEnvironmentVariable("ROBUST_DISABLE_SANDBOX") == "1";
|
|
_modLoader.SetEnableSandboxing(!disableSandbox && Options.Sandboxing);
|
|
if (!LoadModules())
|
|
return false;
|
|
|
|
foreach (var loadedModule in _modLoader.LoadedModules)
|
|
{
|
|
_configurationManager.LoadCVarsFromAssembly(loadedModule);
|
|
}
|
|
|
|
_loadscr.EndLoadingSection();
|
|
|
|
_loadscr.LoadingStep(_serializationManager.Initialize, _serializationManager);
|
|
_loadscr.LoadingStep(_loc.Initialize, _loc);
|
|
|
|
// Call Init in game assemblies.
|
|
_loadscr.LoadingStep(() => _modLoader.BroadcastRunLevel(ModRunLevel.PreInit), "Content PreInit");
|
|
|
|
_loadscr.LoadingStep(() =>
|
|
{
|
|
// Finish initialization of WebView if loaded.
|
|
_webViewHook?.Initialize();
|
|
},
|
|
"WebView init");
|
|
|
|
_loadscr.LoadingStep(() => _modLoader.BroadcastRunLevel(ModRunLevel.Init), "Content Init");
|
|
|
|
// Start bad file extensions check after content init,
|
|
// in case content screws with the VFS.
|
|
var checkBadExtensions = ProgramShared.CheckBadFileExtensions(
|
|
_resManager,
|
|
_configurationManager,
|
|
_logManager.GetSawmill("res"));
|
|
|
|
_loadscr.LoadingStep(_resourceCache.PreloadTextures, "Texture preload");
|
|
_loadscr.LoadingStep(() =>
|
|
{
|
|
_networkManager.Initialize(false);
|
|
_transfer.Initialize();
|
|
_transferTest.Initialize();
|
|
},
|
|
_networkManager);
|
|
|
|
_loadscr.LoadingStep(_configurationManager.SetupNetworking, _configurationManager);
|
|
_loadscr.LoadingStep(_serializer.Initialize, _serializer);
|
|
_loadscr.LoadingStep(_inputManager.Initialize, _inputManager);
|
|
_loadscr.LoadingStep(_console.Initialize, _console);
|
|
|
|
// Make sure this is done before we try to load prototypes,
|
|
// avoid any possibility of race conditions causing the check to not finish
|
|
// before prototype load.
|
|
_loadscr.LoadingStep(
|
|
() => ProgramShared.FinishCheckBadFileExtensions(checkBadExtensions),
|
|
"Check bad file extensions");
|
|
|
|
_loadscr.LoadingStep(_reload.Initialize, _reload);
|
|
_loadscr.LoadingStep(_reflectionManager.Initialize, _reflectionManager);
|
|
_loadscr.LoadingStep(_xamlProxyManager.Initialize, _xamlProxyManager);
|
|
_loadscr.LoadingStep(_xamlHotReloadManager.Initialize, _xamlHotReloadManager);
|
|
_loadscr.BeginLoadingSection(_prototypeManager);
|
|
_prototypeManager.Initialize();
|
|
_prototypeManager.LoadDefaultPrototypes();
|
|
_loadscr.EndLoadingSection();
|
|
_loadscr.LoadingStep(_userInterfaceManager.Initialize, "UI init");
|
|
_loadscr.LoadingStep(_eyeManager.Initialize, _eyeManager);
|
|
_loadscr.LoadingStep(_entityManager.Initialize, _entityManager);
|
|
_loadscr.LoadingStep(_gameStateManager.Initialize, _gameStateManager);
|
|
_loadscr.LoadingStep(_placementManager.Initialize, _placementManager);
|
|
_loadscr.LoadingStep(_viewVariablesManager.Initialize, _viewVariablesManager);
|
|
_loadscr.LoadingStep(_scriptClient.Initialize, _scriptClient);
|
|
_loadscr.LoadingStep(_client.Initialize, _client);
|
|
_loadscr.LoadingStep(_discord.Initialize, _discord);
|
|
_loadscr.LoadingStep(_tagManager.Initialize, _tagManager);
|
|
_loadscr.LoadingStep(_protoLoadMan.Initialize, _protoLoadMan);
|
|
_loadscr.LoadingStep(_netResMan.Initialize, _netResMan);
|
|
_loadscr.LoadingStep(_replayLoader.Initialize, _replayLoader);
|
|
_loadscr.LoadingStep(_replayPlayback.Initialize, _replayPlayback);
|
|
_loadscr.LoadingStep(_replayRecording.Initialize, _replayRecording);
|
|
_loadscr.LoadingStep(_userInterfaceManager.PostInitialize, "UI postinit");
|
|
|
|
// Init stuff before this if at all possible.
|
|
|
|
_loadscr.LoadingStep(() => _modLoader.BroadcastRunLevel(ModRunLevel.PostInit), "Content PostInit");
|
|
|
|
_loadscr.Finish();
|
|
|
|
if (_commandLineArgs?.Username != null)
|
|
{
|
|
_client.PlayerNameOverride = _commandLineArgs.Username;
|
|
}
|
|
|
|
_authManager.LoadFromEnv();
|
|
|
|
if (_configurationManager.GetCVar(CVars.SysGCCollectStart))
|
|
{
|
|
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
|
GC.Collect();
|
|
}
|
|
|
|
// Setup main loop
|
|
if (_mainLoop == null)
|
|
{
|
|
_mainLoop = new GameLoop(
|
|
_gameTiming,
|
|
_runtimeLog,
|
|
_prof,
|
|
_logManager.GetSawmill("eng"),
|
|
GameLoopOptions.FromCVars(_configurationManager))
|
|
{
|
|
SleepMode = displayMode == DisplayMode.Headless ? SleepMode.Delay : SleepMode.None
|
|
};
|
|
}
|
|
|
|
_mainLoop.Tick += (sender, args) =>
|
|
{
|
|
if (_mainLoop.Running)
|
|
{
|
|
Tick(args);
|
|
}
|
|
};
|
|
|
|
_mainLoop.Render += (sender, args) =>
|
|
{
|
|
if (_mainLoop.Running)
|
|
{
|
|
_gameTiming.CurFrame++;
|
|
_clyde.Render();
|
|
}
|
|
};
|
|
_mainLoop.Input += (sender, args) =>
|
|
{
|
|
if (_mainLoop.Running)
|
|
{
|
|
Input(args);
|
|
}
|
|
};
|
|
|
|
_mainLoop.Update += (sender, args) =>
|
|
{
|
|
if (_mainLoop.Running)
|
|
{
|
|
Update(args);
|
|
}
|
|
};
|
|
|
|
_configurationManager.OnValueChanged(CVars.DisplayMaxFPS, _ => UpdateVsyncConfig());
|
|
_configurationManager.OnValueChanged(CVars.DisplayVSync, _ => UpdateVsyncConfig(), invokeImmediately: true);
|
|
|
|
_clyde.Ready();
|
|
|
|
if (_resourceManifest!.AutoConnect &&
|
|
(_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
|
|
&& LaunchState.ConnectEndpoint != null)
|
|
{
|
|
_client.ConnectToServer(LaunchState.ConnectEndpoint);
|
|
}
|
|
|
|
ProgramShared.RunExecCommands(_console, _commandLineArgs?.ExecCommands);
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool LoadModules()
|
|
{
|
|
DebugTools.Assert(_resourceManifest != null);
|
|
|
|
var assemblyPrefix = Options.ContentModulePrefix ?? _resourceManifest!.AssemblyPrefix ?? "Content.";
|
|
var assemblyDir = Options.AssemblyDirectory;
|
|
|
|
bool result;
|
|
if (_resourceManifest.ClientAssemblies is { } clientAssemblies)
|
|
{
|
|
// We have client assemblies. Load only the assemblies listed in the content manifest.
|
|
var paths = clientAssemblies.Select(p => assemblyDir / $"{p}.dll");
|
|
result = _modLoader.TryLoadModules(paths);
|
|
}
|
|
else
|
|
{
|
|
result = _modLoader.TryLoadModulesFrom(assemblyDir, assemblyPrefix);
|
|
}
|
|
|
|
if (!result)
|
|
{
|
|
_logger.Fatal("Errors while loading content assemblies.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal bool StartupSystemSplash(
|
|
GameControllerOptions options,
|
|
Func<ILogHandler>? logHandlerFactory,
|
|
bool globalExceptionLog = false)
|
|
{
|
|
Options = options;
|
|
ReadInitialLaunchState();
|
|
|
|
SetupLogging(_logManager, logHandlerFactory ?? (() => new ConsoleLogHandler()), globalExceptionLog);
|
|
|
|
if (_commandLineArgs != null)
|
|
{
|
|
foreach (var (sawmill, level) in _commandLineArgs.LogLevels)
|
|
{
|
|
LogLevel? logLevel;
|
|
if (level == "null")
|
|
logLevel = null;
|
|
else
|
|
{
|
|
if (!Enum.TryParse<LogLevel>(level, out var result))
|
|
{
|
|
System.Console.WriteLine($"LogLevel {level} does not exist!");
|
|
continue;
|
|
}
|
|
|
|
logLevel = result;
|
|
}
|
|
|
|
_logManager.GetSawmill(sawmill).Level = logLevel;
|
|
}
|
|
}
|
|
|
|
ProgramShared.PrintRuntimeInfo(_logManager.RootSawmill);
|
|
|
|
// Figure out user data directory.
|
|
var userDataDir = GetUserDataDir();
|
|
|
|
_configurationManager.Initialize(false);
|
|
_configurationManager.LoadCVarsFromAssembly(typeof(GameController).Assembly); // Client
|
|
_configurationManager.LoadCVarsFromAssembly(typeof(IConfigurationManager).Assembly); // Shared
|
|
|
|
CVarDefaultOverrides.OverrideClient(_configurationManager);
|
|
|
|
if (Options.LoadConfigAndUserData)
|
|
{
|
|
var configFile = Path.Combine(userDataDir, Options.ConfigFileName);
|
|
if (File.Exists(configFile))
|
|
{
|
|
// Load config from user data if available.
|
|
_configurationManager.LoadFromFile(configFile);
|
|
}
|
|
else
|
|
{
|
|
// Else we just use code-defined defaults and let it save to file when the user changes things.
|
|
_configurationManager.SetSaveFile(configFile);
|
|
}
|
|
}
|
|
|
|
_configurationManager.OverrideConVars(EnvironmentVariables.GetEnvironmentCVars());
|
|
|
|
if (_commandLineArgs != null)
|
|
{
|
|
_configurationManager.OverrideConVars(_commandLineArgs.CVars);
|
|
}
|
|
|
|
ProfileOptSetup.Setup(_configurationManager);
|
|
|
|
_prof.Initialize();
|
|
|
|
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
|
|
|
|
var mountOptions = _commandLineArgs != null
|
|
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
|
: Options.MountOptions;
|
|
|
|
StartTypeValue = ContentStart ? StartType.Content : StartType.Engine;
|
|
#if FULL_RELEASE
|
|
if (_loaderArgs != null || Options.ResourceMountDisabled)
|
|
StartTypeValue = StartType.Loader;
|
|
#else
|
|
if (StartTypeValue == StartType.Content && Path.GetFileName(PathHelpers.GetExecutableDirectory()) == "MacOS")
|
|
StartTypeValue = StartType.ContentAppBundle;
|
|
#endif
|
|
|
|
ProgramShared.DoMounts(_resManager, mountOptions, Options.ContentBuildDirectory,
|
|
Options.AssemblyDirectory,
|
|
Options.LoadContentResources, StartTypeValue);
|
|
|
|
if (_loaderArgs != null)
|
|
{
|
|
if (_loaderArgs.ApiMounts is { } mounts)
|
|
{
|
|
foreach (var (api, prefix) in mounts)
|
|
{
|
|
_resourceCache.MountLoaderApi(_resManager, api, "", new(prefix));
|
|
}
|
|
}
|
|
|
|
_stringSerializer.EnableCaching = false;
|
|
_resourceCache.MountLoaderApi(_resManager, _loaderArgs.FileApi, "Resources/");
|
|
_modLoader.VerifierExtraLoadHandler = VerifierExtraLoadHandler;
|
|
}
|
|
|
|
_resourceManifest = ResourceManifestData.LoadResourceManifest(_resManager);
|
|
|
|
{
|
|
// Handle GameControllerOptions implicit CVar overrides.
|
|
_configurationManager.OverrideConVars(new[]
|
|
{
|
|
(CVars.DisplayWindowIconSet.Name, WindowIconSet()),
|
|
(CVars.DisplaySplashLogo.Name, SplashLogo()),
|
|
(CVars.LoadingShowBar.Name, ShowLoadingBar().ToString()),
|
|
});
|
|
}
|
|
|
|
_clyde.TextEntered += TextEntered;
|
|
_clyde.TextEditing += TextEditing;
|
|
_clyde.MouseMove += MouseMove;
|
|
_clyde.KeyUp += KeyUp;
|
|
_clyde.KeyDown += KeyDown;
|
|
_clyde.MouseWheel += MouseWheel;
|
|
_clyde.CloseWindow += args =>
|
|
{
|
|
if (args.Window == _clyde.MainWindow)
|
|
{
|
|
Shutdown("Main window closed");
|
|
}
|
|
};
|
|
|
|
// Bring display up as soon as resources are mounted.
|
|
return _clyde.InitializePreWindowing();
|
|
}
|
|
|
|
private Stream? VerifierExtraLoadHandler(string arg)
|
|
{
|
|
DebugTools.AssertNotNull(_loaderArgs);
|
|
|
|
if (_loaderArgs!.FileApi.TryOpen(arg, out var stream))
|
|
{
|
|
return stream;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void ReadInitialLaunchState()
|
|
{
|
|
if (_commandLineArgs == null)
|
|
{
|
|
LaunchState = new InitialLaunchState(false, null, null, null);
|
|
}
|
|
else
|
|
{
|
|
var addr = _commandLineArgs.ConnectAddress;
|
|
if (!addr.Contains("://"))
|
|
{
|
|
addr = "udp://" + addr;
|
|
}
|
|
|
|
var uri = new Uri(addr);
|
|
|
|
if (uri.Scheme != "udp")
|
|
{
|
|
_logger.Warning($"connect-address '{uri}' does not have URI scheme of udp://..");
|
|
}
|
|
|
|
LaunchState = new InitialLaunchState(
|
|
_commandLineArgs.Launcher,
|
|
_commandLineArgs.ConnectAddress,
|
|
_commandLineArgs.Ss14Address,
|
|
new DnsEndPoint(uri.Host, uri.IsDefaultPort ? 1212 : uri.Port));
|
|
}
|
|
}
|
|
|
|
public void Shutdown(string? reason = null)
|
|
{
|
|
if (_mainLoop == null)
|
|
{
|
|
if (!_dontStart)
|
|
{
|
|
_logger.Info($"Shutdown called before client init completed: {reason ?? "No reason provided"}");
|
|
_dontStart = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Already got shut down I assume,
|
|
if (!_mainLoop.Running)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (reason != null)
|
|
{
|
|
_logger.Info($"Shutting down! Reason: {reason}");
|
|
}
|
|
else
|
|
{
|
|
_logger.Info("Shutting down!");
|
|
}
|
|
|
|
_mainLoop.Running = false;
|
|
}
|
|
|
|
private void Input(FrameEventArgs frameEventArgs)
|
|
{
|
|
using (_prof.Group("Input Events"))
|
|
{
|
|
_clyde.ProcessInput(frameEventArgs);
|
|
}
|
|
|
|
using (_prof.Group("Network"))
|
|
{
|
|
_networkManager.ProcessPackets();
|
|
}
|
|
|
|
using (_prof.Group("Async"))
|
|
{
|
|
_taskManager.ProcessPendingTasks(); // tasks like connect
|
|
}
|
|
|
|
using (_prof.Group("Content post engine"))
|
|
{
|
|
_modLoader.BroadcastUpdate(ModUpdateLevel.InputPostEngine, frameEventArgs);
|
|
}
|
|
}
|
|
|
|
private void Tick(FrameEventArgs frameEventArgs)
|
|
{
|
|
using (_prof.Group("Content pre engine"))
|
|
{
|
|
_modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs);
|
|
}
|
|
|
|
using (_prof.Group("Console"))
|
|
{
|
|
_console.CommandBufferExecute();
|
|
}
|
|
|
|
using (_prof.Group("Timers"))
|
|
{
|
|
_timerManager.UpdateTimers(frameEventArgs);
|
|
}
|
|
|
|
using (_prof.Group("Async"))
|
|
{
|
|
_taskManager.ProcessPendingTasks();
|
|
}
|
|
|
|
// GameStateManager is in full control of the simulation update in multiplayer.
|
|
if (_client.RunLevel == ClientRunLevel.InGame || _client.RunLevel == ClientRunLevel.Connected)
|
|
{
|
|
using (_prof.Group("Game state"))
|
|
{
|
|
_gameStateManager.ApplyGameState();
|
|
}
|
|
}
|
|
|
|
// In singleplayer, however, we're in full control instead.
|
|
else if (_client.RunLevel == ClientRunLevel.SinglePlayerGame)
|
|
{
|
|
using (_prof.Group("Entity"))
|
|
{
|
|
if (TickUpdateOverride != null)
|
|
{
|
|
TickUpdateOverride.Invoke(frameEventArgs);
|
|
}
|
|
else
|
|
{
|
|
// The last real tick is the current tick! This way we won't be in "prediction" mode.
|
|
_gameTiming.LastRealTick = _gameTiming.LastProcessedTick = _gameTiming.CurTick;
|
|
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false);
|
|
}
|
|
}
|
|
}
|
|
|
|
using (_prof.Group("Content post engine"))
|
|
{
|
|
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
|
|
}
|
|
}
|
|
|
|
private void Update(FrameEventArgs frameEventArgs)
|
|
{
|
|
if (_webViewHook != null)
|
|
{
|
|
using (_prof.Group("WebView"))
|
|
{
|
|
_webViewHook?.Update();
|
|
}
|
|
}
|
|
|
|
using (_prof.Group("Clyde"))
|
|
{
|
|
_clyde.FrameProcess(frameEventArgs);
|
|
}
|
|
|
|
using (_prof.Group("Content Pre Engine"))
|
|
{
|
|
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePreEngine, frameEventArgs);
|
|
}
|
|
|
|
using (_prof.Group("State"))
|
|
{
|
|
_stateManager.FrameUpdate(frameEventArgs);
|
|
}
|
|
|
|
if (_client.RunLevel >= ClientRunLevel.Connected)
|
|
{
|
|
using (_prof.Group("Placement"))
|
|
{
|
|
_placementManager.FrameUpdate(frameEventArgs);
|
|
}
|
|
|
|
using (_prof.Group("Entity"))
|
|
{
|
|
_entityManager.FrameUpdate(frameEventArgs.DeltaSeconds);
|
|
}
|
|
}
|
|
|
|
using (_prof.Group("Overlay"))
|
|
{
|
|
_overlayManager.FrameUpdate(frameEventArgs);
|
|
}
|
|
|
|
using (_prof.Group("UI"))
|
|
{
|
|
_userInterfaceManager.FrameUpdate(frameEventArgs);
|
|
}
|
|
|
|
using (_prof.Group("Content Post Engine"))
|
|
{
|
|
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs);
|
|
}
|
|
|
|
using (_prof.Group("Transfer"))
|
|
{
|
|
_transfer.FrameUpdate();
|
|
}
|
|
|
|
_audio.FlushALDisposeQueues();
|
|
}
|
|
|
|
internal static void SetupLogging(
|
|
ILogManager logManager,
|
|
Func<ILogHandler> logHandlerFactory,
|
|
bool globalExceptionLog)
|
|
{
|
|
logManager.RootSawmill.AddHandler(logHandlerFactory());
|
|
|
|
//logManager.GetSawmill("res.typecheck").Level = LogLevel.Info;
|
|
logManager.GetSawmill("res.tex").Level = LogLevel.Info;
|
|
logManager.GetSawmill("console").Level = LogLevel.Info;
|
|
logManager.GetSawmill("go.sys").Level = LogLevel.Info;
|
|
logManager.GetSawmill("ogl.debug.performance").Level = LogLevel.Fatal;
|
|
// Stupid nvidia driver spams buffer info on DebugTypeOther every time you re-allocate a buffer.
|
|
logManager.GetSawmill("ogl.debug.other").Level = LogLevel.Warning;
|
|
logManager.GetSawmill("gdparse").Level = LogLevel.Error;
|
|
logManager.GetSawmill("discord").Level = LogLevel.Warning;
|
|
logManager.GetSawmill("szr").Level = LogLevel.Info;
|
|
logManager.GetSawmill("loc").Level = LogLevel.Warning;
|
|
|
|
if (globalExceptionLog)
|
|
{
|
|
#if DEBUG_ONLY_FCE_INFO
|
|
#if DEBUG_ONLY_FCE_LOG
|
|
var fce = logManager.GetSawmill("fce");
|
|
#endif
|
|
AppDomain.CurrentDomain.FirstChanceException += (sender, args) =>
|
|
{
|
|
// TODO: record FCE stats
|
|
#if DEBUG_ONLY_FCE_LOG
|
|
fce.Fatal(message);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
var uh = logManager.GetSawmill("unhandled");
|
|
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
|
|
{
|
|
var message = ((Exception)args.ExceptionObject).ToString();
|
|
uh.Log(args.IsTerminating ? LogLevel.Fatal : LogLevel.Error, message);
|
|
};
|
|
|
|
var uo = logManager.GetSawmill("unobserved");
|
|
TaskScheduler.UnobservedTaskException += (sender, args) =>
|
|
{
|
|
uo.Error(args.Exception!.ToString());
|
|
#if EXCEPTION_TOLERANCE
|
|
args.SetObserved(); // don't crash
|
|
#endif
|
|
};
|
|
}
|
|
}
|
|
|
|
private string GetUserDataDir()
|
|
{
|
|
if (_commandLineArgs?.SelfContained == true)
|
|
{
|
|
// Self contained mode. Data is stored in a directory called user_data next to Robust.Client.exe.
|
|
var exeDir = typeof(GameController).Assembly.Location;
|
|
if (string.IsNullOrEmpty(exeDir))
|
|
{
|
|
throw new Exception("Unable to locate client exe");
|
|
}
|
|
|
|
exeDir = Path.GetDirectoryName(exeDir);
|
|
return Path.Combine(exeDir ?? throw new InvalidOperationException(), "user_data");
|
|
}
|
|
|
|
return UserDataDir.GetUserDataDir(this);
|
|
}
|
|
|
|
|
|
private void UpdateVsyncConfig()
|
|
{
|
|
if (_displayMode == DisplayMode.Headless)
|
|
return;
|
|
|
|
var vsync = _configurationManager.GetCVar(CVars.DisplayVSync);
|
|
var maxFps = Math.Clamp(_configurationManager.GetCVar(CVars.DisplayMaxFPS), 0, 10_000);
|
|
|
|
_clyde.VsyncEnabled = vsync;
|
|
|
|
if (_mainLoop == null)
|
|
return;
|
|
|
|
if (vsync || maxFps == 0)
|
|
{
|
|
_mainLoop.SleepMode = SleepMode.None;
|
|
}
|
|
else
|
|
{
|
|
_mainLoop.SleepMode = SleepMode.Limit;
|
|
_mainLoop.LimitMinFrameTime = TimeSpan.FromSeconds(1.0 / maxFps);
|
|
}
|
|
}
|
|
|
|
internal enum DisplayMode : byte
|
|
{
|
|
Headless,
|
|
Clyde,
|
|
}
|
|
|
|
internal void CleanupGameThread()
|
|
{
|
|
_replayRecording.Shutdown();
|
|
|
|
_modLoader.Shutdown();
|
|
|
|
// CEF specifically makes a massive silent stink of it if we don't shut it down from the correct thread.
|
|
_webViewHook?.Shutdown();
|
|
|
|
_networkManager.Shutdown("Client shutting down");
|
|
_midiManager.Shutdown();
|
|
_entityManager.Shutdown();
|
|
}
|
|
|
|
internal void CleanupWindowThread()
|
|
{
|
|
_clyde.Shutdown();
|
|
_audio.Shutdown();
|
|
}
|
|
|
|
public event Action<FrameEventArgs>? TickUpdateOverride;
|
|
}
|
|
}
|