using UnityEngine; using Unity.Entities; using UnityEngine.Profiling; using SQP; using Unity.Animation; using Unity.Collections; using Unity.Jobs; using Unity.Networking.Transport; using Unity.Scenes; using Unity.NetCode; using Unity.Sample.Core; [UpdateInGroup(typeof(ServerSimulationSystemGroup))] [UpdateBefore(typeof(GhostSimulationSystemGroup))] [AlwaysUpdateSystem] [AlwaysSynchronizeSystem] public class BeforeServerPredictionSystem : JobComponentSystem { public ServerGameWorld GameWorld; protected override JobHandle OnUpdate(JobHandle inputDeps) { var gameWorld = GameWorld; var PostUpdateCommands = new EntityCommandBuffer(Allocator.TempJob); Entities .WithNone() .WithAll() .WithNativeDisableContainerSafetyRestriction(PostUpdateCommands) .WithoutBurst() // Captures managed data .ForEach((Entity entity) => { if (gameWorld != null) gameWorld.HandleClientDisconnect(PostUpdateCommands,entity); PostUpdateCommands.RemoveComponent(entity); }).Run(); PostUpdateCommands.Playback(EntityManager); PostUpdateCommands.Dispose(); if (GameWorld != null) GameWorld.BeforePredictionUpdate(); return default; } } [UpdateInGroup(typeof(ServerSimulationSystemGroup))] [UpdateAfter(typeof(GhostSimulationSystemGroup))] [UpdateBefore(typeof(AnimationSystemGroup))] [AlwaysSynchronizeSystem] public class AfterServerPredictionSystem : JobComponentSystem { public ServerGameWorld GameWorld; protected override JobHandle OnUpdate(JobHandle inputDeps) { if (GameWorld != null) GameWorld.AfterPredictionUpdate(); return default; } } [UpdateInGroup(typeof(GhostPredictionSystemGroup))] [AlwaysSynchronizeSystem] public class ServerPredictionSystem : JobComponentSystem { public ServerGameWorld GameWorld; protected override JobHandle OnUpdate(JobHandle inputDeps) { if (GameWorld != null) GameWorld.PredictionUpdate(); return default; } } struct AcceptedConnectionStateComponent : ISystemStateComponentData { public Entity playerEntity; public bool isReady; } public class ServerGameWorld { public ServerGameWorld(World world) { m_GameWorld = world; m_PlayerModule = new PlayerModuleServer(m_GameWorld); #pragma warning disable 618 // we're keeping World.Active until we can properly remove them all var defaultWorld = World.Active; try { m_GameModeSystem = m_GameWorld.CreateSystem(m_GameWorld, m_GameWorld.GetExistingSystem()); m_HandleDamageGroup = m_GameWorld.CreateSystem(); m_TeleporterSystem = m_GameWorld.CreateSystem(); m_DamageAreaSystem = m_GameWorld.CreateSystem(); m_HandleControlledEntityChangedGroup = m_GameWorld.CreateSystem(); m_HandleControlledEntityChangedGroup.AddSystemToUpdateList(m_GameWorld.CreateSystem()); m_PredictedUpdateGroup = m_GameWorld.CreateSystem(); m_PredictedUpdateGroup.AddSystemToUpdateList(CharacterModule.CreateServerUpdateSystemGroup(world)); m_PredictedUpdateGroup.AddSystemToUpdateList(world.CreateSystem()); m_AfterPredictionUpdateGroup = m_GameWorld.CreateSystem(); m_AfterPredictionUpdateGroup.AddSystemToUpdateList(CharacterModule.CreateServerPresentationSystemGroup(world)); m_AfterPredictionUpdateGroup.AddSystemToUpdateList(m_GameWorld.GetOrCreateSystem(typeof(PartSystemUpdateGroup))); } finally { World.Active = defaultWorld; } #pragma warning restore 618 m_MoveableSystem = new MovableSystemServer(m_GameWorld); m_CameraSystem = new ServerCameraSystem(); } public void Shutdown(bool isDestroyingWorld) { m_PlayerModule.Shutdown(); // When destroying the world all systems will be torn down - so no need to do it manually if (!isDestroyingWorld) { m_HandleDamageGroup.DestroyGroup(); m_GameWorld.DestroySystem(m_TeleporterSystem); m_GameWorld.DestroySystem(m_HandleControlledEntityChangedGroup); m_GameWorld.DestroySystem(m_PredictedUpdateGroup); m_GameWorld.DestroySystem(m_AfterPredictionUpdateGroup); m_GameWorld.DestroySystem(m_DamageAreaSystem); } m_CameraSystem.Shutdown(); m_MoveableSystem.Shutdown(); AnimationGraphHelper.Shutdown(m_GameWorld); m_GameWorld = null; } public void RespawnPlayer(Entity playerEntity) { var playerState = m_GameWorld.EntityManager.GetComponentData(playerEntity); if (playerState.controlledEntity == Entity.Null) return; if (m_GameWorld.EntityManager.HasComponent(playerState.controlledEntity)) CharacterDespawnRequest.Create(m_GameWorld, playerState.controlledEntity); playerState.controlledEntity = Entity.Null; m_GameWorld.EntityManager.SetComponentData(playerEntity, playerState); } char[] _msgBuf = new char[256]; public void HandlePlayerSetupEvent(Entity playerEntity, PlayerSettings settings, ChatSystemServer chatSystem) { var playerState = m_GameWorld.EntityManager.GetComponentData(playerEntity); if (playerState.playerName.ToString() != settings.playerName) { int l = 0; if (playerState.playerName.ToString() == "") l = StringFormatter.Write(ref _msgBuf, 0, "{0} joined", settings.playerName); else l = StringFormatter.Write(ref _msgBuf, 0, "{0} is now known as {1}", playerState.playerName.ToString(), settings.playerName); chatSystem.SendChatAnnouncement(new CharBufView(_msgBuf, l)); playerState.playerName = new NativeString64(settings.playerName); m_GameWorld.EntityManager.SetComponentData(playerEntity, playerState); } var charControl = m_GameWorld.EntityManager.GetComponentData(playerEntity); charControl.requestedCharacterType = settings.characterType; m_GameWorld.EntityManager.SetComponentData(playerEntity,charControl); } public void HandleClientCommands() { var connectionQuery = m_GameWorld.EntityManager.CreateEntityQuery( ComponentType.ReadWrite(), ComponentType.ReadWrite()); var commandTargets = connectionQuery.ToComponentDataArray(Allocator.TempJob); for (int i = 0; i < commandTargets.Length; ++i) { var targetEntity = commandTargets[i].targetEntity; if (targetEntity == Entity.Null) continue; m_GameWorld.EntityManager.GetBuffer(targetEntity) .GetDataAtTick(m_GameWorld.GetExistingSystem().ServerTick, out var latestCommand); // Pass on command to controlled entity var playerState = m_GameWorld.EntityManager.GetComponentData(targetEntity); if (playerState.controlledEntity != Entity.Null) { var userCommand = m_GameWorld.EntityManager.GetComponentData( playerState.controlledEntity); userCommand.prevCommand = userCommand.command; userCommand.command = latestCommand; m_GameWorld.EntityManager.SetComponentData(playerState.controlledEntity, userCommand); } } commandTargets.Dispose(); } public bool HandleClientCommand(Entity client, string v) { if (v == "nextchar") { GameDebug.Log("nextchar for client " + m_GameWorld.EntityManager.GetComponentData(client).Value); m_GameModeSystem.RequestNextChar(m_GameWorld.EntityManager.GetComponentData(client).targetEntity); } else { return false; } return true; } public void BeforePredictionUpdate() { var gameTimeSystem = m_GameWorld.GetExistingSystem(); var time = gameTimeSystem.GetWorldTime(); time.tick = (int)m_GameWorld.GetExistingSystem().ServerTick; time.tickDuration = time.tickInterval; gameTimeSystem.SetWorldTime(time); gameTimeSystem.frameDuration = time.tickInterval; Profiler.BeginSample("HandleClientCommands"); // This call backs into ProcessCommand HandleClientCommands(); Profiler.EndSample(); GameTime gameTime = new GameTime(gameTimeSystem.GetWorldTime().tickRate); gameTime.SetTime(gameTimeSystem.GetWorldTime().tick, gameTimeSystem.GetWorldTime().tickInterval); // Handle controlled entity changed m_HandleControlledEntityChangedGroup.Update(); // Start movement of scene objects. Scene objects that player movement // depends on should finish movement in this phase m_MoveableSystem.Update(); m_CameraSystem.Update(); // Update movement of player controlled units m_TeleporterSystem.Update(); } public void PredictionUpdate() { m_PredictedUpdateGroup.Update(); } public void AfterPredictionUpdate() { m_DamageAreaSystem.Update(); // Handle damage m_HandleDamageGroup.Update(); // TODO (mogensh) for now we upadte this AFTER CharacterModule as we depend on AnimSourceCtrl to run before bodypart. Sort this out m_AfterPredictionUpdateGroup.Update(); // Update gamemode. Run last to allow picking up deaths etc. m_GameModeSystem.Update(); } public void HandleClientConnect(Entity client) { var entityManager = m_GameWorld.EntityManager; bool isReady = entityManager.GetComponentData(client).isReady; var playerEntity = m_PlayerModule.CreatePlayerEntity(m_GameWorld, entityManager.GetComponentData(client).Value, 0, "", isReady); entityManager.AddBuffer(playerEntity); entityManager.SetComponentData(client, new CommandTargetComponent{targetEntity = playerEntity}); entityManager.SetComponentData(client, new AcceptedConnectionStateComponent {playerEntity = playerEntity, isReady = isReady}); } public void HandleClientDisconnect(EntityCommandBuffer ecb, Entity client) { var entityManager = m_GameWorld.EntityManager; var playerEntity = entityManager.GetComponentData(client).playerEntity; if (playerEntity == Entity.Null) return; CharacterModule.ServerCleanupPlayer(m_GameWorld, ecb, playerEntity); m_PlayerModule.CleanupPlayer(playerEntity); } // Internal systems World m_GameWorld; readonly PlayerModuleServer m_PlayerModule; readonly ServerCameraSystem m_CameraSystem; readonly GameModeSystemServer m_GameModeSystem; readonly ManualComponentSystemGroup m_HandleControlledEntityChangedGroup; readonly ManualComponentSystemGroup m_PredictedUpdateGroup; readonly ManualComponentSystemGroup m_AfterPredictionUpdateGroup; readonly DamageAreaSystemServer m_DamageAreaSystem; readonly TeleporterSystemServer m_TeleporterSystem; readonly HandleDamageSystemGroup m_HandleDamageGroup; readonly MovableSystemServer m_MoveableSystem; } public class ServerGameLoop : Game.IGameLoop { [ConfigVar(Name = "server.maxclients", DefaultValue = "100", Description = "Maximum allowed clients")] public static ConfigVar serverMaxClients; [ConfigVar(Name = "server.disconnecttimeout", DefaultValue = "30000", Description = "Timeout in ms. Server will kick clients after this interval if nothing has been heard.")] public static ConfigVar serverDisconnectTimeout; public static string[] CurrentArgs; private World m_World; public bool Init(string[] args) { // TODO (timj) create singleton for parameters instead CurrentArgs = args; m_World = ClientServerBootstrap.CreateServerWorld(GameBootStrap.DefaultWorld, "ServerWorld"); CurrentArgs = null; return true; } public void Shutdown() { // TODO (timj) world shutdown order - the ecs worlds are already torn down when application quit is called if (m_World == null) return; m_World.GetExistingSystem().Shutdown(); m_World.Dispose(); } public void Update() { } public void FixedUpdate() { } public void LateUpdate() { } } [UpdateInGroup(typeof(ServerInitializationSystemGroup))] [AlwaysSynchronizeSystem] public class ServerGameLoopSystem : JobComponentSystem { [ConfigVar(Name = "server.printstatus", DefaultValue = "0", Description = "Print status line every ticks")] public static ConfigVar serverPrintStatus; public void Shutdown() { m_StateMachine.SwitchTo(null, ServerState.Idle); } protected override void OnCreate() { var args = ServerGameLoop.CurrentArgs; if (args == null) args = new string[0]; World.GetOrCreateSystem().BuildSettingsGUID = new Unity.Entities.Hash128("9635cffb5d7da422c922505e40219752"); var tickRate = EntityManager.CreateEntity(); EntityManager.AddComponentData(tickRate, new ClientServerTickRate { MaxSimulationStepsPerFrame = 4, // Hardcoded for now, should be a setting NetworkTickRate = Game.serverTickRate.IntValue / 3, SimulationTickRate = Game.serverTickRate.IntValue, TargetFrameRateMode = Game.IsHeadless() ? ClientServerTickRate.FrameRateMode.Sleep : ClientServerTickRate.FrameRateMode.BusyWait }); m_ClientsQuery = EntityManager.CreateEntityQuery(ComponentType.ReadWrite(), ComponentType.ReadWrite()); // Set up statemachine for ServerGame m_StateMachine = new StateMachine(); m_StateMachine.Add(ServerState.Idle, null, UpdateIdleState, null); m_StateMachine.Add(ServerState.Loading, null, UpdateLoadingState, null); m_StateMachine.Add(ServerState.WaitSubscene, null, UpdateWaitSubscene, null); m_StateMachine.Add(ServerState.Active, EnterActiveState, UpdateActiveState, LeaveActiveState); m_StateMachine.SwitchTo(null,ServerState.Idle); var ep = NetworkEndPoint.AnyIpv4; ep.Port = (ushort) (NetworkConfig.serverPort.IntValue); World.GetOrCreateSystem().Listen(ep); var listenAddresses = NetworkUtils.GetLocalInterfaceAddresses(); if (listenAddresses.Count > 0) Console.SetPrompt(listenAddresses[0] + ":" + NetworkConfig.serverPort.Value + "> "); GameDebug.Log("Listening on " + string.Join(", ", NetworkUtils.GetLocalInterfaceAddresses()) + " on port " + NetworkConfig.serverPort.IntValue); #if UNITY_EDITOR if (Game.game.clientFrontend != null && (!GameBootStrap.IsSingleLevelPlaymode || ClientServerBootstrap.RequestedPlayType != ClientServerBootstrap.PlayType.ClientAndServer)) #else if (Game.game.clientFrontend != null) #endif { var serverPanel = Game.game.clientFrontend.serverPanel; serverPanel.SetPanelActive(true); serverPanel.serverInfo.text += "Listening on:\n"; foreach (var a in NetworkUtils.GetLocalInterfaceAddresses()) { serverPanel.serverInfo.text += a + ":" + NetworkConfig.serverPort.IntValue + "\n"; } } if (serverServerName.Value == "") serverServerName.Value = MakeServername(); //m_ServerQueryProtocolServer = new SQP.SQPServer(NetworkConfig.serverSQPPort.IntValue > 0 ? NetworkConfig.serverSQPPort.IntValue : NetworkConfig.serverPort.IntValue + NetworkConfig.sqpPortOffset); m_GameWorld = World; World.CreateSystem(); GameDebug.Log("Network server initialized"); Console.AddCommand("load", CmdLoad, "Load a named scene", this.GetHashCode()); Console.AddCommand("unload", CmdUnload, "Unload current scene", this.GetHashCode()); Console.AddCommand("s_respawn", CmdRespawn, "Respawn character (usage : respawn playername|playerId)", this.GetHashCode()); Console.AddCommand("servername", CmdSetServerName, "Set name of the server", this.GetHashCode()); Console.AddCommand("list", CmdList, "List clients", this.GetHashCode()); #if UNITY_EDITOR if (GameBootStrap.IsSingleLevelPlaymode) m_StateMachine.SwitchTo(World, ServerState.Loading); else #endif CmdLoad(args); InputSystem.SetMousePointerLock(false); m_ServerStartTime = (float)Time.ElapsedTime; GameDebug.Log("Server initialized"); Console.SetOpen(false); } protected override void OnDestroy() { m_isDestroyingWorld = true; GameDebug.Log("ServerGameState shutdown"); Console.RemoveCommandsWithTag(this.GetHashCode()); m_StateMachine.Shutdown(); #if UNITY_EDITOR if (!GameBootStrap.IsSingleLevelPlaymode) #endif Game.game.levelManager.UnloadLevel(); PrefabAssetManager.Shutdown(); m_GameWorld = null; } protected override JobHandle OnUpdate(JobHandle inputDeps) { int clientCount = m_ClientsQuery.CalculateEntityCount(); if (serverRecycleInterval.FloatValue > 0.0f) { // Recycle server if time is up and no clients connected if (clientCount == 0 && (float)Time.ElapsedTime > m_ServerStartTime + serverRecycleInterval.FloatValue) { GameDebug.Log("Server exiting because recycle timeout was hit."); Console.EnqueueCommandNoHistory("quit"); } } if (clientCount > m_MaxClients) m_MaxClients = clientCount; if (serverQuitWhenEmpty.IntValue > 0 && m_MaxClients > 0 && clientCount == 0) { GameDebug.Log("Server exiting because last client disconnected"); Console.EnqueueCommandNoHistory("quit"); } UpdateNetwork(); m_StateMachine.Update(); if (showGameLoopInfo.IntValue > 0) OnDebugDrawGameloopInfo(); return default; } public ServerGameWorld GetServerGameWorld() { return m_serverGameWorld; } public void OnConnect(Entity client) { // TODO (timj) disconnect if server is full m_GameWorld.EntityManager.AddComponent(client); if (m_serverGameWorld != null) m_serverGameWorld.HandleClientConnect(client); } void UpdateNetwork() { Profiler.BeginSample("ServerGameLoop.UpdateNetwork"); if(serverPrintStatus.IntValue > 0) { if ((Game.frameCount % serverPrintStatus.IntValue) == 0) { var playerCount = m_ClientsQuery.CalculateEntityCount(); GameDebug.Log("ServerStatus: Map: {0} Players: {1}/{2} (max: {3})", Game.game.levelManager.currentLevel.name, playerCount, ServerGameLoop.serverMaxClients.IntValue, m_MaxClients); } } /* // Update SQP data with current values var sid = m_ServerQueryProtocolServer.ServerInfoData; sid.BuildId = Game.game.buildId; sid.Port = (ushort)NetworkConfig.serverPort.IntValue; sid.CurrentPlayers = (ushort)m_ClientsQuery.CalculateEntityCount(); sid.GameType = GameModeSystemServer.modeName.Value; sid.Map = Game.game.levelManager.currentLevel.name; sid.MaxPlayers = (ushort)ServerGameLoop.serverMaxClients.IntValue; sid.ServerName = serverServerName.Value; m_ServerQueryProtocolServer.Update(); */ Profiler.EndSample(); } /// /// Idle state, no level is loaded /// void UpdateIdleState() { } /// /// Loading state, load in progress /// void UpdateLoadingState() { if (Game.game.levelManager.IsCurrentLevelLoaded()) m_StateMachine.SwitchTo(m_GameWorld,ServerState.WaitSubscene); } void UpdateWaitSubscene() { // TODO (mogensh) we should find a better way to make sure subscene is loaded (this uses knowledge of what is in subscene) var query = m_GameWorld.EntityManager.CreateEntityQuery(typeof(HeroRegistry.RegistryEntity)); var ready = query.CalculateEntityCount() > 0; query.Dispose(); if(ready) m_StateMachine.SwitchTo(m_GameWorld,ServerState.Active); } /// /// Active state, level loaded /// void EnterActiveState() { GameDebug.Assert(m_serverGameWorld == null); m_serverGameWorld = new ServerGameWorld(m_GameWorld); var clients = m_ClientsQuery.ToEntityArray(Allocator.TempJob); for (int i = 0; i < clients.Length; ++i) { m_serverGameWorld.HandleClientConnect(clients[i]); } clients.Dispose(); var entity = m_GameWorld.EntityManager.CreateEntity();// Game state entity m_GameWorld.EntityManager.AddComponentData(entity, new ActiveStateComponentData{MapName = new NativeString64(Game.game.levelManager.currentLevel.name)}); m_GameWorld.GetExistingSystem().GameWorld = m_serverGameWorld; m_GameWorld.GetExistingSystem().GameWorld = m_serverGameWorld; m_GameWorld.GetExistingSystem().GameWorld = m_serverGameWorld; } void UpdateActiveState() { } void LeaveActiveState() { if (Unity.Entities.World.AllWorlds.Contains(World)) { m_GameWorld.GetExistingSystem().GameWorld = null; m_GameWorld.GetExistingSystem().GameWorld = null; m_GameWorld.GetExistingSystem().GameWorld = null; } m_serverGameWorld.Shutdown(m_isDestroyingWorld); m_serverGameWorld = null; } void LoadLevel(string levelname, string gamemode = "deathmatch") { bool levelCanBeLoaded = Game.game.levelManager.CanLoadLevel(levelname); GameDebug.Assert(levelCanBeLoaded, "FATAL : Cannot load level : " + levelname); m_RequestedGameMode = gamemode; Game.game.levelManager.LoadLevel(levelname); m_StateMachine.SwitchTo(null,ServerState.Loading); } void UnloadLevel() { // TODO } void CmdSetServerName(string[] args) { if (args.Length > 0) { // TODO (petera) fix or remove this? } else Console.Write("Invalid argument to servername (usage : servername name)"); } void CmdLoad(string[] args) { if (args.Length == 1) LoadLevel(args[0]); else if (args.Length == 2) LoadLevel(args[0], args[1]); } void CmdUnload(string[] args) { UnloadLevel(); } void CmdRespawn(string[] args) { if (args.Length != 1) { Console.Write("Invalid argument for respawn command (usage : respawn playername|playerId)"); return; } var playerId = -1; var playerName = args[0]; var usePlayerId = int.TryParse(args[0], out playerId); var entityManager = m_GameWorld.EntityManager; var clients = m_ClientsQuery.ToEntityArray(Allocator.TempJob); bool found = false; for (int i = 0; i < clients.Length; ++i) { var playerEntity = entityManager.GetComponentData(clients[i]).playerEntity; if (playerEntity == Entity.Null) continue; var clientId = entityManager.GetComponentData(clients[i]).Value; if (usePlayerId && clientId != playerId) continue; var playerState = m_GameWorld.EntityManager.GetComponentData(playerEntity); if (!usePlayerId && playerState.playerName.ToString() != playerName) continue; m_serverGameWorld.RespawnPlayer(playerEntity); found = true; break; } clients.Dispose(); if(!found) GameDebug.Log("Could not find character. Unknown player, invalid character id or player doesn't have a character: " + args[0]); } void CmdList(string[] args) { Console.Write("Players on server:"); Console.Write("-------------------"); Console.Write(string.Format(" {0,2} {1,-15}", "ID", "PlayerName")); Console.Write("-------------------"); var entityManager = m_GameWorld.EntityManager; var clients = m_ClientsQuery.ToEntityArray(Allocator.TempJob); for (int i = 0; i < clients.Length; ++i) { var clientId = entityManager.GetComponentData(clients[i]).Value; var playerEntity = entityManager.GetComponentData(clients[i]).playerEntity; var playerName = ""; var playerState = m_GameWorld.EntityManager.GetComponentData(playerEntity); playerName = playerState.playerName.ToString(); Console.Write(string.Format(" {0:00} {1,-15} score: {2}", clientId, playerName, playerState.score)); } Console.Write("-------------------"); Console.Write(string.Format("Total: {0}/{0} players connected", clients.Length, ServerGameLoop.serverMaxClients.IntValue)); clients.Dispose(); } string MakeServername() { var f = new string[] { "Ultimate", "Furry", "Quick", "Laggy", "Hot", "Curious", "Flappy", "Sneaky", "Nested", "Deep", "Blue", "Hipster", "Artificial" }; var l = new string[] { "Speedrun", "Fragfest", "Win", "Exception", "Prefab", "Scene", "Garbage", "System", "Souls", "Whitespace", "Dolphin" }; return f[Random.Range(0, f.Length)] + " " + l[Random.Range(0, l.Length)]; } void OnDebugDrawGameloopInfo() { //DebugOverlay.Write(2,2,"Server Gameloop Info:"); //var y = 3; //DebugOverlay.Write(2, y++, " Simulation time average : {0}", m_NetworkServer.simStats.simTime); //DebugOverlay.Write(2, y++, " Simulation time stdev : {0}", m_NetworkServer.simStats.simTimeStdDev); //DebugOverlay.Write(2, y++, " Simulation time peek : {0}", m_NetworkServer.simStats.simTimeMax); //y++; //DebugOverlay.Write(2, y++, " Delta time average : {0}", m_NetworkServer.simStats.deltaTime); //DebugOverlay.Write(2, y++, " Delta time stdev : {0}", m_NetworkServer.simStats.deltaTimeStdDev); //DebugOverlay.Write(2, y++, " Delta time peek : {0}", m_NetworkServer.simStats.deltaTimeMax); //y += 2; //foreach (var clientId in m_NetworkServer.clients) //{ // var info = m_NetworkServer.GetClientConnectionInfo(clientId); // DebugOverlay.Write(2, y++, " addr: {0} port: {1} rtt: {2} ms", info.address, info.port, info.rtt); //} } // Statemachine enum ServerState { Idle, Loading, WaitSubscene, Active, } StateMachine m_StateMachine; World m_GameWorld; ServerGameWorld m_serverGameWorld; public double m_nextTickTime = 0; string m_RequestedGameMode = "deathmatch"; //SQPServer m_ServerQueryProtocolServer; #pragma warning disable 649 [ConfigVar(Name = "show.gameloopinfo", DefaultValue = "0", Description = "Show gameloop info")] static ConfigVar showGameLoopInfo; [ConfigVar(Name = "server.quitwhenempty", DefaultValue = "0", Description = "If enabled, quit when last client disconnects.")] static ConfigVar serverQuitWhenEmpty; [ConfigVar(Name = "server.recycleinterval", DefaultValue = "0", Description = "Exit when N seconds old AND when 0 players. 0 means never.")] static ConfigVar serverRecycleInterval; [ConfigVar(Name = "debug.servertickstats", DefaultValue = "0", Description = "Show stats about how many ticks we run per Unity update (headless only)")] static ConfigVar debugServerTickStats; [ConfigVar(Name = "server.servername", DefaultValue = "", Description = "Servername")] static ConfigVar serverServerName; #pragma warning restore 649 float m_ServerStartTime; int m_MaxClients; EntityQuery m_ClientsQuery; private bool m_isDestroyingWorld; }