private bool VerifyVersionData(ServerCommandData data) { CoreLog.Log(CoreLogLevel.Debug, $"Received {data.Command} while waiting for server announcement."); if (data.Command == ServerCommand.Announce) { var protocolVersion = ValueConverter.ToUInt32(data.Data, 0); var pluginVersion = ValueConverter.ToVersion(data.Data, 4); if (protocolVersion != CoreVersion.ProtocolVersion) { var updatee = protocolVersion > CoreVersion.ProtocolVersion ? "game mode" : "server"; CoreLog.Log(CoreLogLevel.Error, $"Protocol version mismatch! The server is running protocol {protocolVersion} but the game mode is running {CoreVersion.ProtocolVersion}"); CoreLog.Log(CoreLogLevel.Error, $"Please update your {updatee}."); return(false); } CoreLog.Log(CoreLogLevel.Info, $"Connected to version {pluginVersion.ToString(3)} via protocol {protocolVersion}"); ServerPath = ValueConverter.ToString(data.Data, 8, Encoding.ASCII); return(true); } CoreLog.Log(CoreLogLevel.Error, $"Received command {data.Command.ToString().ToLower()} instead of announce."); return(false); }
/// <summary> /// Waits for the next command sent by the server. /// </summary> /// <returns>The command sent by the server.</returns> public virtual async Task<ServerCommandData> ReceiveAsync() { AssertNotDisposed(); while (true) { if (_buffer.TryPop(out var command)) return command; try { var task = _stream?.ReadAsync(_readBuffer, 0, _readBuffer.Length, _source.Token); if (task == null) throw new StreamCommunicationClientClosedException(); var len = await task; if (_stream == null) throw new StreamCommunicationClientClosedException(); if (_readBuffer.Length == len) { CoreLog.Log(CoreLogLevel.Error, "Network buffer overflow detected!"); } _buffer.Push(_readBuffer, 0, len); } catch (TaskCanceledException) { throw new StreamCommunicationClientClosedException(); } } }
private async Task NetworkingRoutine() { try { while (_running) { var data = await CommunicationClient.ReceiveAsync(); if (!_running) { return; } _commandWaitQueue.Release(data); } } catch (StreamCommunicationClientClosedException) { CoreLog.Log(CoreLogLevel.Warning, "Network routine ended because the communication with the SA:MP server was closed."); } catch (Exception e) { CoreLog.Log(CoreLogLevel.Error, "Network routine died! " + e); } }
/// <inheritdoc /> public void Tick() { if (!_didInitialize || _timers.Count == 0) { return; } var timestamp = Stopwatch.GetTimestamp(); // Don't user foreach for performance reasons // ReSharper disable once ForCanBeConvertedToForeach for (var i = 0; i < _timers.Count; i++) { var timer = _timers[i]; while ((timer.NextTick > _lastTick || timestamp < _lastTick) && timer.NextTick <= timestamp) { try { timer.Invoke(); } catch (Exception e) { CoreLog.Log(CoreLogLevel.Error, $"Timer threw an exception: {e}"); } timer.NextTick += timer.IntervalTicks; } } _lastTick = timestamp; }
/// <summary> /// Runs the game mode of this runner. /// </summary> /// <returns>true if shut down by the game mode, false otherwise.</returns> /// <exception cref="Exception">Thrown if a game mode is already running.</exception> public bool Run() { InternalStorage.RunningClient = this; // Prepare the syncronization context _messageQueue = new NoWaitMessageQueue(); _synchronizationContext = new SampSharpSynchronizationContext(_messageQueue); SynchronizationContext.SetSynchronizationContext(_synchronizationContext); _mainThread = Thread.CurrentThread.ManagedThreadId; _running = true; CoreLog.Log(CoreLogLevel.Initialisation, "SampSharp GameMode Client"); CoreLog.Log(CoreLogLevel.Initialisation, "-------------------------"); CoreLog.Log(CoreLogLevel.Initialisation, $"v{CoreVersion.Version.ToString(3)}, (C)2014-2020 Tim Potze"); CoreLog.Log(CoreLogLevel.Initialisation, "Hosted run mode is active."); CoreLog.Log(CoreLogLevel.Initialisation, ""); // TODO: Verify plugin version _gameModeProvider.Initialize(this); return(true); }
private async void Initialize() { _mainThread = Thread.CurrentThread.ManagedThreadId; CoreLog.Log(CoreLogLevel.Initialisation, "SampSharp GameMode Client"); CoreLog.Log(CoreLogLevel.Initialisation, "-------------------------"); CoreLog.Log(CoreLogLevel.Initialisation, $"v{CoreVersion.Version.ToString(3)}, (C)2014-2020 Tim Potze"); CoreLog.Log(CoreLogLevel.Initialisation, "Multi-process run mode is active. FOR DEVELOPMENT PURPOSES ONLY!"); CoreLog.Log(CoreLogLevel.Initialisation, "Run your server in hosted run mode for production environments. See https://sampsharp.net/running-in-production for more information."); CoreLog.Log(CoreLogLevel.Initialisation, ""); AppDomain.CurrentDomain.ProcessExit += (sender, args) => { CoreLog.Log(CoreLogLevel.Info, "Shutdown signal received"); ShutDown(); if (_mainRoutine != null && !_mainRoutine.IsCompleted) { _mainRoutine.Wait(); } if (_networkingRoutine != null && !_networkingRoutine.IsCompleted) { _networkingRoutine.Wait(); } }; CoreLog.Log(CoreLogLevel.Info, $"Connecting to the server via {CommunicationClient}..."); await CommunicationClient.Connect(); _running = true; CoreLog.Log(CoreLogLevel.Info, "Set up networking routine..."); StartNetworkingRoutine(); CoreLog.Log(CoreLogLevel.Info, "Connected! Waiting for server announcement..."); ServerCommandData data; do { data = await _commandWaitQueue.WaitAsync(); // Could receive ticks if reconnecting. } while (data.Command == ServerCommand.Tick); if (!VerifyVersionData(data)) { return; } CoreLog.Log(CoreLogLevel.Info, "Initializing game mode provider..."); _gameModeProvider.Initialize(this); CoreLog.Log(CoreLogLevel.Info, "Sending start signal to server..."); Send(ServerCommand.Start, new[] { (byte)_startBehaviour }); CoreLog.Log(CoreLogLevel.Info, "Set up main routine..."); _mainRoutine = MainRoutine(); }
/// <summary> /// Registers the specified command. /// </summary> /// <param name="command">The command.</param> public virtual void Register(ICommand command) { if (command == null) { throw new ArgumentNullException(nameof(command)); } CoreLog.Log(CoreLogLevel.Debug, $"Registering command {command}"); _commands.Add(command); }
/// <summary> /// Invokes the native with the specified arguments. /// </summary> /// <param name="arguments">The arguments.</param> /// <returns>The return value of the native.</returns> public int Invoke(params object[] arguments) { if (arguments == null) { throw new ArgumentNullException(nameof(arguments)); } if (Parameters.Length != arguments.Length) { throw new ArgumentOutOfRangeException(nameof(arguments), "Invalid argument count"); } IEnumerable <byte> data = ValueConverter.GetBytes(Handle); if (CoreLog.DoesLog(CoreLogLevel.Verbose)) { CoreLog.LogVerbose("Invoking {0}({1})", Name, string.Join(", ", arguments)); } int length; for (var i = 0; i < Parameters.Length; i++) { length = GetLength(i, arguments); data = data .Concat(new[] { (byte)Parameters[i].ArgumentType }) .Concat(Parameters[i].GetBytes(arguments[i], length, _gameModeClient)); } var response = _gameModeClient.InvokeNative(data); if (response.Length < 4) { CoreLog.Log(CoreLogLevel.Warning, "Native call returned no response, execution probably failed."); return(0); } var result = ValueConverter.ToInt32(response, 0); var respPos = 4; for (var i = 0; i < Parameters.Length; i++) { length = GetLength(i, arguments); var value = Parameters[i].GetReferenceArgument(response, ref respPos, length, result, _gameModeClient); if (value != null) { arguments[i] = value; } } return(result); }
/// <summary> /// Autoloads the controllers in the specified assembly. /// </summary> /// <param name="assembly">The assembly.</param> public void AutoloadControllersForAssembly(Assembly assembly) { foreach (var type in assembly.GetExportedTypes() .Where(t => t.GetTypeInfo().IsClass&& typeof(IController).GetTypeInfo().IsAssignableFrom(t) && t.GetTypeInfo().GetCustomAttribute <ControllerAttribute>() != null)) { CoreLog.Log(CoreLogLevel.Debug, $"Autoloading type {type}..."); _controllers.Override(Activator.CreateInstance(type) as IController); } }
private void AutoloadPoolTypes() { var types = new List <Type>(); foreach (var poolType in new[] { typeof(BaseMode), GetType() }.Concat(_extensions.Select(e => e.GetType())) .Select(t => t.GetTypeInfo().Assembly) .Distinct() .SelectMany(a => a.GetTypes()) .Where(t => t.GetTypeInfo().IsClass&& t.GetTypeInfo().GetCustomAttribute <PooledTypeAttribute>() != null) .Distinct()) { // If poolType or subclass of poolType is already in types, continue. if (types.Any(t => t == poolType || poolType.GetTypeInfo().IsAssignableFrom(t))) { CoreLog.Log(CoreLogLevel.Debug, $"Pool of type {poolType} is not autoloaded because a subclass of it will already be loaded."); continue; } // Remove all types in types where type is supertype of poolType. foreach (var t in types.Where(t => t.GetTypeInfo().IsAssignableFrom(poolType)).ToArray()) { CoreLog.Log(CoreLogLevel.Debug, $"No longer autoloading type {poolType} because a subclass of it is going to be loaded."); types.Remove(t); } CoreLog.Log(CoreLogLevel.Debug, $"Autoloading pool of type {poolType}."); types.Add(poolType); } var poolTypes = new[] { typeof(IdentifiedPool <>), typeof(IdentifiedOwnedPool <,>) }; foreach (var type in types) { var pool = type; do { pool = pool.GetTypeInfo().BaseType; } while (pool != null && (!pool.GetTypeInfo().IsGenericType || !poolTypes.Contains(pool.GetGenericTypeDefinition()))); if (pool == null) { CoreLog.Log(CoreLogLevel.Debug, $"Skipped autoloading pool of type {type} because it's not a subtype of a pool."); continue; } pool.GetTypeInfo().GetMethod("Register", new[] { typeof(Type) }).Invoke(null, new object[] { type }); } }
private void CreateTimersFromAssemblies(long tick) { // Find methods with TimerAttribute in any ISystem in any assembly. var events = new AssemblyScanner() .IncludeAllAssemblies() .IncludeNonPublicMembers() .Implements <ISystem>() .ScanMethods <TimerAttribute>(); // Create timer invokers and store timer info in registry. foreach (var(method, attribute) in events) { CoreLog.LogDebug("Adding timer on {0}.{1}.", method.DeclaringType, method.Name); if (!IsValidInterval(attribute.IntervalTimeSpan)) { CoreLog.Log(CoreLogLevel.Error, $"Timer {method} could not be registered the interval {attribute.IntervalTimeSpan} is invalid."); continue; } var service = _serviceProvider.GetService(method.DeclaringType); if (service == null) { CoreLog.Log(CoreLogLevel.Debug, "Skipping timer registration because service could not be loaded."); continue; } var parameterInfos = method.GetParameters() .Select(info => new MethodParameterSource(info) { IsService = true }) .ToArray(); var compiled = MethodInvokerFactory.Compile(method, parameterInfos); if (attribute.IntervalTimeSpan < LowInterval) { CoreLog.Log(CoreLogLevel.Warning, $"Timer {method.DeclaringType}.{method.Name} has a low interval of {attribute.IntervalTimeSpan}."); } var timer = new TimerInfo { IsActive = true, Invoke = () => compiled(service, null, _serviceProvider, null), IntervalTicks = attribute.IntervalTimeSpan.Ticks, NextTick = tick + attribute.IntervalTimeSpan.Ticks }; _timers.Add(timer); } }
private void LoadExtensions() { var load = new List <Assembly>(); // Create a dependency-ordered list of extensions. var loading = new List <Assembly>(); foreach ( var assembly in GetType() .GetTypeInfo() .Assembly.GetReferencedAssemblies() .Select(Assembly.Load) .Concat(new[] { GetType().GetTypeInfo().Assembly }) .Distinct() .Where(a => a.GetCustomAttributes <SampSharpExtensionAttribute>().Any())) { AddExtensionToLoadList(assembly, load, loading); } // Load extensions according to dependency list. foreach (var assembly in load) { var attributes = assembly.GetCustomAttributes <SampSharpExtensionAttribute>(); foreach (var extensionType in attributes.Select(attribute => attribute.Type)) { if (extensionType == null) { continue; } if (!typeof(IExtension).GetTypeInfo().IsAssignableFrom(extensionType)) { CoreLog.Log(CoreLogLevel.Warning, $"The extension from {assembly} could not be loaded. The specified extension type does not inherit from IExtension."); continue; } if (!extensionType.GetTypeInfo().Assembly.Equals(assembly)) { CoreLog.Log(CoreLogLevel.Warning, $"The extension from {assembly} could not be loaded. The specified extension type is not part of the assembly."); continue; } if (_extensions.Any(e => e.GetType() == extensionType)) { CoreLog.Log(CoreLogLevel.Warning, $"The extension from {assembly} could not be loaded. The specified extension type was already loaded."); continue; } // Register the extension to the plugin. var extension = (IExtension)Activator.CreateInstance(extensionType); RegisterExtension(extension); } } }
/// <summary> /// Registers the type to use when initializing new instances. /// </summary> /// <param name="type">The type.</param> /// <exception cref="System.ArgumentNullException">Thrown if type is null</exception> /// <exception cref="System.ArgumentException">type must be of type TInstance;type</exception> public static void Register(Type type) { if (type == null) { throw new ArgumentNullException(nameof(type)); } if (!typeof(TInstance).GetTypeInfo().IsAssignableFrom(type)) { throw new ArgumentException("type must be of type " + typeof(TInstance), nameof(type)); } CoreLog.Log(CoreLogLevel.Debug, $"Type {type} registered to pool."); InstanceType = type; }
/// <summary> /// Adds a <see cref="IController" /> to this collection and remove controllers it overrides. /// </summary> /// <param name="controller">The <see cref="IController" /> to add to this collection.</param> public void Override(IController controller) { var overrides = this.Where(c => c.GetType().GetTypeInfo().IsInstanceOfType(controller)).ToArray(); if (overrides.Any()) { CoreLog.Log(CoreLogLevel.Debug, $"{controller} overrides {string.Join(", ", (object[]) overrides)}"); } foreach (var c in overrides) { Remove(c); } _controllers.Add(controller); }
/// <summary> /// Invokes the middleware. /// </summary> public object Invoke(EventContext context, IRconCommandService commandService) { var result = _next(context); if (EventHelper.IsSuccessResponse(result)) { return(result); } if (context.Arguments[0] is string text) { return(commandService.Invoke(context.EventServices, text)); } CoreLog.Log(CoreLogLevel.Error, "Invalid command middleware input argument types!"); return(null); }
/// <summary> /// Waits for the next command sent by the server. /// </summary> /// <returns>The command sent by the server.</returns> public virtual async Task <ServerCommandData> ReceiveAsync() { AssertNotDisposed(); while (true) { if (_buffer.TryPop(out var command)) { return(command); } try { var task = _stream?.ReadAsync(_readBuffer, 0, _readBuffer.Length, _source.Token); if (task == null) { throw new StreamCommunicationClientClosedException(); } var len = await task; if (_stream == null) { throw new StreamCommunicationClientClosedException(); } if (_readBuffer.Length == len) { CoreLog.Log(CoreLogLevel.Error, "Network buffer overflow detected!"); // TODO: Could try and detect first valid command, but that could mean some vital commands were lost. } _buffer.Push(_readBuffer, 0, len); } catch (TaskCanceledException) { throw new StreamCommunicationClientClosedException(); } } }
/// <inheritdoc /> public void Initialize(IGameModeClient client) { client.UnhandledException += (sender, args) => { CoreLog.Log(CoreLogLevel.Error, "Unhandled exception: " + args.Exception); }; var services = new ServiceCollection(); services.AddSingleton(client); services.AddSingleton(client.NativeLoader); Configure(services); _serviceProvider = services.BuildServiceProvider(); _systemRegistry = _serviceProvider.GetRequiredService <ISystemRegistry>(); Configure(); AddWrappedSystemTypes(); }
private async void Initialize() { CoreLog.Log(CoreLogLevel.Initialisation, "SampSharp GameMode Client"); CoreLog.Log(CoreLogLevel.Initialisation, "-------------------------"); CoreLog.Log(CoreLogLevel.Initialisation, $"v{CoreVersion.Version.ToString(3)}, (C)2014-2019 Tim Potze"); CoreLog.Log(CoreLogLevel.Initialisation, ""); _mainThread = Thread.CurrentThread.ManagedThreadId; _running = true; CoreLog.Log(CoreLogLevel.Info, $"Connecting to the server via {CommunicationClient}..."); await CommunicationClient.Connect(); CoreLog.Log(CoreLogLevel.Info, "Set up networking routine..."); StartNetworkingRoutine(); CoreLog.Log(CoreLogLevel.Info, "Connected! Waiting for server announcement..."); ServerCommandData data; do { data = await _commandWaitQueue.WaitAsync(); // Could receive ticks if reconnecting. } while (data.Command == ServerCommand.Tick); if (!VerifyVersionData(data)) { return; } CoreLog.Log(CoreLogLevel.Info, "Initializing game mode provider..."); _gameModeProvider.Initialize(this); CoreLog.Log(CoreLogLevel.Info, "Sending start signal to server..."); Send(ServerCommand.Start, new[] { (byte)_startBehaviour }); CoreLog.Log(CoreLogLevel.Info, "Set up main routine..."); MainRoutine(); }
private InvokerInfo CreateInvoker(MethodInfo method, MethodParameterSource[] parameterInfos, int callbackParamCount) { var compiled = MethodInvokerFactory.Compile(method, parameterInfos); return(new InvokerInfo { TargetType = method.DeclaringType, Invoke = (instance, eventContext) => { var args = eventContext.Arguments; if (callbackParamCount == args.Length) { return compiled(instance, args, eventContext.EventServices, _entityManager); } CoreLog.Log(CoreLogLevel.Error, $"Callback parameter count mismatch {callbackParamCount} != {args.Length}"); return null; } }); }
private void ProcessCommand(ServerCommandData data) { switch (data.Command) { case ServerCommand.Nop: break; case ServerCommand.Tick: // The server expects at least a message every 5 seconds or else the debug pause // detector kicks in. Send one at least every 3 to be safe. if (DateTime.UtcNow - _lastSend > TimeSpan.FromSeconds(3)) { Send(ServerCommand.Alive, null); } if (!_canTick) { break; } try { _gameModeProvider.Tick(); } catch (Exception e) { OnUnhandledException(new UnhandledExceptionEventArgs("Tick", e)); } break; case ServerCommand.Pong: if (_pongs.Count == 0) { CoreLog.Log(CoreLogLevel.Error, "Received a random pong"); CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace); } else { _pongs.Dequeue().Pong(); } break; case ServerCommand.Announce: CoreLog.Log(CoreLogLevel.Error, "Received a random server announcement"); CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace); break; case ServerCommand.PublicCall: var name = ValueConverter.ToString(data.Data, 0, Encoding); var isInit = name == "OnGameModeInit"; if (CoreLog.DoesLog(CoreLogLevel.Verbose)) { CoreLog.LogVerbose("Incoming public call: {0}", name); } if ((_startBehaviour == GameModeStartBehaviour.Gmx || _startBehaviour == GameModeStartBehaviour.FakeGmx) && !_initReceived && !isInit) { CoreLog.Log(CoreLogLevel.Debug, $"Skipping callback {name} because OnGameModeInit has not yet been called"); Send(ServerCommand.Response, AZero); break; } var isFirstInit = isInit && !_initReceived; if (isFirstInit) { _initReceived = true; } if (_callbacks.TryGetValue(name, out var callback)) { int?result = null; try { result = callback.Invoke(data.Data, name.Length + 1); CoreLog.LogVerbose("Public call response for {0}: {1}", name, result); } catch (Exception e) { OnUnhandledException(new UnhandledExceptionEventArgs(name, e)); } Send(ServerCommand.Response, result != null ? AOne.Concat(ValueConverter.GetBytes(result.Value)) : AZero); } else { CoreLog.Log(CoreLogLevel.Error, "Received unknown callback " + name); CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace); } if (isFirstInit) { if (_startBehaviour == GameModeStartBehaviour.FakeGmx) { FakeGmxRotate(); } _canTick = true; } else if (_initReceived && name == "OnGameModeExit") { CoreLog.Log(CoreLogLevel.Info, "OnGameModeExit received, sending reconnect signal..."); Send(ServerCommand.Reconnect, null); // Give the server time to receive the reconnect signal. Thread.Sleep(100); CleanUp(); } break; default: CoreLog.Log(CoreLogLevel.Error, $"Unknown command {data.Command} received with {data.Data?.Length.ToString() ?? "NULL"} data"); CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace); break; } }
private void ProcessCommand(ServerCommandData data) { switch (data.Command) { case ServerCommand.Tick: if (!_canTick) { break; } _gameModeProvider.Tick(); // The server expects at least a message every 5 seconds or else the debug pause // detector kicks in. Send one at least every 3 to be safe. if (DateTime.UtcNow - _lastSend > TimeSpan.FromSeconds(3)) { Send(ServerCommand.Alive, null); } break; case ServerCommand.Pong: if (_pongs.Count == 0) { CoreLog.Log(CoreLogLevel.Error, "Received a random pong"); CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace); } else { _pongs.Dequeue().Pong(); } break; case ServerCommand.Announce: CoreLog.Log(CoreLogLevel.Error, "Received a random server announcement"); CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace); break; case ServerCommand.PublicCall: var name = ValueConverter.ToString(data.Data, 0, Encoding); var isInit = name == "OnGameModeInit"; if (CoreLog.DoesLog(CoreLogLevel.Verbose)) { CoreLog.LogVerbose("Incoming public call: {0}", name); } if ((_startBehaviour == GameModeStartBehaviour.Gmx || _startBehaviour == GameModeStartBehaviour.FakeGmx) && !_initReceived && !isInit) { CoreLog.Log(CoreLogLevel.Debug, $"Skipping callback {name} because OnGameModeInit has not yet been called"); Send(ServerCommand.Response, AZero); break; } var isFirstInit = isInit && !_initReceived; if (isFirstInit) { _initReceived = true; } if (_callbacks.TryGetValue(name, out var callback)) { int?result = null; try { result = callback.Invoke(data.Data, name.Length + 1); CoreLog.LogVerbose("Public call response for {0}: {1}", name, result); } catch (Exception e) { OnUnhandledException(new UnhandledExceptionEventArgs(e)); } Send(ServerCommand.Response, result != null ? AOne.Concat(ValueConverter.GetBytes(result.Value)) : AZero); } else { CoreLog.Log(CoreLogLevel.Error, "Received unknown callback " + name); CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace); } if (isFirstInit) { if (_startBehaviour == GameModeStartBehaviour.FakeGmx) { _callbacks.TryGetValue("OnIncomingConnection", out var onIncomingConnection); _callbacks.TryGetValue("OnPlayerConnect", out var onPlayerConnect); _callbacks.TryGetValue("OnPlayerRequestClass", out var onPlayerRequestClass); var natIsPlayerConnected = NativeLoader.Load("IsPlayerConnected", new[] { NativeParameterInfo.ForType(typeof(int)) }); var natGetPlayerPoolSize = NativeLoader.Load("GetPlayerPoolSize", new NativeParameterInfo[0]); var natForceClassSelection = NativeLoader.Load("ForceClassSelection", new[] { NativeParameterInfo.ForType(typeof(int)) }); var natTogglePlayerSpectating = NativeLoader.Load("TogglePlayerSpectating", new[] { NativeParameterInfo.ForType(typeof(int)), NativeParameterInfo.ForType(typeof(int)) }); var natGetPlayerIp = NativeLoader.Load("GetPlayerIp", new[] { NativeParameterInfo.ForType(typeof(int)), new NativeParameterInfo(NativeParameterType.StringReference, 2), NativeParameterInfo.ForType(typeof(int)), }); var poolSize = natGetPlayerPoolSize.Invoke(); for (var i = 0; i <= poolSize; i++) { var isConnected = natIsPlayerConnected.Invoke(i); if (isConnected == 0) { continue; } var args = new object[] { i, null, 16 }; natGetPlayerIp.Invoke(args); if (args[1] is string ip) { onIncomingConnection?.Invoke( ValueConverter.GetBytes(i) .Concat(ValueConverter.GetBytes(ip, Encoding.ASCII)) .Concat(ValueConverter.GetBytes(9999999)) .ToArray(), 0); } onPlayerConnect?.Invoke(ValueConverter.GetBytes(i), 0); natForceClassSelection.Invoke(i); natTogglePlayerSpectating.Invoke(i, 1); natTogglePlayerSpectating.Invoke(i, 0); onPlayerRequestClass?.Invoke( ValueConverter.GetBytes(i) .Concat(ValueConverter.GetBytes(0)) .ToArray(), 0); } } _canTick = true; } else if (_initReceived && name == "OnGameModeExit") { CoreLog.Log(CoreLogLevel.Info, "OnGameModeExit received, sending reconnect signal..."); Send(ServerCommand.Reconnect, null); // Give the server time to receive the reconnect signal. // TODO: This is an ugly freeze/comms-deadlock fix. Thread.Sleep(100); CleanUp(); } break; default: CoreLog.Log(CoreLogLevel.Error, $"Unknown command {data.Command} recieved with {data.Data?.Length.ToString() ?? "NULL"} data"); CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace); break; } }