using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using FlaxEngine; using FlaxEngine.Assertions; using FlaxEngine.Json; using FlaxEngine.Networking; namespace Game; public enum GameModeMessageType2 : byte { AcceptConnection, // AcceptConnectionMessage SpawnPlayer, PlayerInput, PlayerPosition, // world update PlayerSnapshot, // send world state delta to client since last client's acknowledged frame LastMessageType = 128, } public class PlayerFrame { public ulong frame => inputState.Frame; public Float3 position; public PlayerInputState2 inputState; public PlayerMovementState movementState; } public interface IClientInfo { public NetworkConnection Connection { get; set; } public PlayerActor PlayerActor { get; set; } public PlayerFrame[] FrameHistory { get; set; } } public class World { public bool IsServer => this is ServerWorld; public bool IsClient => this is ClientWorld; public ulong Frame { get; protected set; } public ulong ServerFrame { get; protected set; } // Last received frame from server public float GameTime { get; protected set; } // The join time public float GetFrameTime(ulong frame) => GameTime + (frame * (1.0f / Time.PhysicsFPS)); protected Scene _scene; protected Actor _worldSpawn; public static void InitClient() { WorldStore.ClientWorld = new ClientWorld(); WorldStore.ClientWorld.Init(); } public static void InitServer() { WorldStore.ServerWorld = new ServerWorld(); WorldStore.ServerWorld.Init(); Scripting.Update += WorldStore.ServerWorld.OnUpdate; Scripting.LateUpdate += WorldStore.ServerWorld.OnUpdateLate; Scripting.FixedUpdate += WorldStore.ServerWorld.OnFixedUpdate; } public static void CleanupClient() { WorldStore.ClientWorld?.Cleanup(); WorldStore.ClientWorld = null; } public static void CleanupServer() { WorldStore.ServerWorld?.Cleanup(); WorldStore.ServerWorld = null; } virtual protected void Init() { Scripting.LateFixedUpdate += OnLateUpdate; } virtual protected void Cleanup() { //Level.SceneLoaded -= OnLevelLoaded; //Scripting.LateUpdate -= OnLateUpdatePre; Scripting.Update -= OnUpdate; Scripting.LateUpdate -= OnUpdateLate; Scripting.FixedUpdate -= OnFixedUpdate; Scripting.LateFixedUpdate -= OnLateUpdate; if (_scene) { foreach (var player in _scene.GetChildren()) { FlaxEngine.Object.Destroy(player); } if (Level.UnloadScene(_scene)) throw new Exception("Failed to unload scene"); _scene = null; } } void OnUpdate() { //Console.Print("server Update"); } void OnUpdateLate() { //Console.Print("server LateUpdate"); } void OnFixedUpdate() { //Console.Print("server FixedUpdate"); } protected void CreateScene(string sceneNamePrefix, string sceneGuid) { string physicsSceneName = $"{sceneNamePrefix}PhysicsScene"; PhysicsScene localPhysicsScene = Physics.FindOrCreateScene(physicsSceneName); Guid guid = JsonSerializer.ParseID(sceneGuid); string guidStr = JsonSerializer.GetStringID(guid); string sceneData = $@" {{ ""ID"": ""{guidStr}"", ""TypeName"": ""FlaxEngine.SceneAsset"", ""EngineBuild"": 65046, ""Data"": [ {{ ""ID"": ""{guidStr}"", ""TypeName"": ""FlaxEngine.Scene"", ""LightmapSettings"": {{ ""IndirectLightingIntensity"": 1.0, ""GlobalObjectsScale"": 1.0, ""ChartsPadding"": 3, ""AtlasSize"": 1024, ""BounceCount"": 1, ""CompressLightmaps"": true, ""UseGeometryWithNoMaterials"": true, ""Quality"": 10 }} }} ] }}"; { var onSceneLoaded = (Scene loadedScene, Guid id) => { if (guid == id) { loadedScene.PhysicsScene = localPhysicsScene; //scene = loadedScene; } }; try { Level.SceneLoaded += onSceneLoaded; //Level.LoadScene(new SceneReference(guid)); _scene = Level.LoadSceneFromBytes(Encoding.ASCII.GetBytes(sceneData)); } finally { Level.SceneLoaded -= onSceneLoaded; } } Assert.IsTrue(_scene); _scene.Name = $"{sceneNamePrefix}Scene"; //Level.SceneLoaded += OnLevelLoaded; var importer = FlaxEngine.Object.New(); importer.mapPath = @"C:\dev\GoakeFlax\Assets\Maps\aerowalk.map"; importer.LoadCollidersOnly = sceneNamePrefix != "Client"; // FIXME importer.Parent = _scene; //importer.Enabled = true; _worldSpawn = _scene.FindActor("WorldSpawn");// ?? Level.FindActor("WorldSpawn"); Assert.IsTrue(_worldSpawn); } virtual protected void OnLateUpdate() { Frame++; } virtual protected IClientInfo GetClient(uint playerId) => null; virtual protected PlayerActor SpawnPlayer(uint playerId, Float3 position, Vector3 eulerAngles) { string prefabPath = Path.Combine(AssetManager.ContentPath, "Common"); var playerPrefab = Content.Load(Path.Combine(prefabPath, "PlayerPrefab.prefab")); if (playerPrefab == null) Console.PrintError("GameModeManager: Failed to find PlayerPrefab"); PlayerActor playerActor = SpawnActor(playerPrefab); playerActor.Initialize(playerId, position, eulerAngles); //playerActor.Teleport(position, eulerAngles); IClientInfo player = GetClient(playerId); player.PlayerActor = playerActor; IPlayerInput playerInput = playerActor.GetScript().Input; playerInput.SetFrame(Frame); /*if (IsServer) { serverWorldState.actors.Add(playerActor); if (!playerLastReceivedFrames.ContainsKey(playerId)) playerLastReceivedFrames.Add(playerId, 0); }*/ return playerActor; } protected T SpawnActor(Prefab prefab) where T : Actor { T actor = PrefabManager.SpawnPrefab(prefab, _scene).As(); actor.PhysicsScene = _scene.PhysicsScene; return actor; } virtual public bool IsLocalPlayer(uint playerId) => false; virtual public ulong GetLastProcessedFrame(uint playerId) => 0; virtual public ulong GetLastReceivedFrame(uint playerId) => 0; public PlayerFrame GetPlayerFrame(uint playerId, ulong frame) { IClientInfo player = GetClient(playerId); PlayerFrame playerFrame = player.FrameHistory[frame % 120]; if (playerFrame.inputState.Frame == frame || playerFrame.movementState.frame == frame) return playerFrame; return null; } public bool HasPlayerFrame(uint playerId, ulong frame) => GetPlayerFrame(playerId, frame) != null; public bool GetPlayerInputState(uint playerId, ulong frame, out PlayerInputState2 inputState) { PlayerFrame playerFrame = GetPlayerFrame(playerId, frame); inputState = playerFrame?.inputState ?? default; return playerFrame != null; } virtual public void UpdatePlayerInputState(uint playerId, ulong frame, PlayerInputState2 inputState, PlayerMovementState movementState) { IClientInfo player = GetClient(playerId); PlayerFrame playerFrame = player.FrameHistory[frame % 120]; /*if (playerFrame.frame == frame) { var old = playerFrame.inputState with { Frame = inputState.Frame }; playerFrame.inputState = inputState with { ViewDelta = playerFrame.inputState.ViewDelta + inputState.ViewDelta, MoveForward = MathF.MaxMagnitude(playerFrame.inputState.MoveForward, inputState.MoveForward), MoveRight = MathF.MaxMagnitude(playerFrame.inputState.MoveRight, inputState.MoveRight), Attack = playerFrame.inputState.Attack || inputState.Attack, Jump = playerFrame.inputState.Jump || inputState.Jump, }; if (old != playerFrame.inputState) old = old; } else*/ playerFrame.inputState = inputState; playerFrame.inputState.Frame = frame; //playerFrame.movementState = movementState; //playerFrame.frame = frame; } virtual public void UpdatePlayerState(uint playerId, ulong frame, PlayerMovementState movementState) { IClientInfo player = GetClient(playerId); PlayerFrame playerFrame = player.FrameHistory[frame % 120]; playerFrame.movementState = movementState; playerFrame.movementState.frame = frame; } } file class ServerWorld : World { protected class ClientInfo : IClientInfo { public NetworkConnection Connection { get; set; } public PlayerActor PlayerActor { get; set; } public PlayerFrame[] FrameHistory { get; set; } = new PlayerFrame[120]; public ulong LastReceivedFrame = 0; public ulong LastSentDeltaFrame = 0; // TODO: Accumulate deltas since this frame public ulong LastProcessedFrame = 0; // In case player is lagged behind, these frames should be caught up during next simulation public int SendRate = 0; // How many updates the client wants to receive per second public ClientInfo() { for (int i = 0; i < FrameHistory.Length; i++) { FrameHistory[i] = new PlayerFrame(); FrameHistory[i].inputState.Frame = ulong.MaxValue; FrameHistory[i].movementState.frame = ulong.MaxValue; } } } protected Dictionary _clients = new(); protected override IClientInfo GetClient(uint playerId) => _clients[playerId]; protected override void Init() { NetworkManager.RegisterServerCallbacks(OnMessage, OnClientConnecting, OnClientConnected); GameTime = Time.GameTime; CreateScene("Server", "59dd37cc444d5d7015759389c6153c4c"); base.Init(); } protected override void Cleanup() { NetworkManager.UnregisterServerCallbacks(OnMessage, OnClientConnecting, OnClientConnected); base.Cleanup(); } protected override void OnLateUpdate() { //Console.Print("server LateFixedUpdate"); base.OnLateUpdate(); foreach (var client in _clients.Values) { // We assume all the scripts have processed the frames during this cycle client.LastProcessedFrame = client.LastReceivedFrame; } } public bool OnMessage(ref NetworkEvent networkEvent) { byte messageTypeByte = networkEvent.Message.ReadByte(); if (!Enum.IsDefined(typeof(GameModeMessageType2), messageTypeByte)) { //Console.PrintError($"GameModeManager: Unsupported message type received from client: {messageTypeByte}"); return false; } GameModeMessageType2 messageType = (GameModeMessageType2)messageTypeByte; NetworkManager.DebugLastHandledMessage = messageType.ToString(); //Console.Print($"GameModeManager: {messageType}"); switch (messageType) { case GameModeMessageType2.AcceptConnection: // Client { break; } case GameModeMessageType2.PlayerPosition: // FIXME: Should be the delta frame { //uint playerId = networkEvent.Sender.ConnectionId; PlayerInputState2 inputState; //? PlayerActorState actorState; ulong receivedFrame = networkEvent.Message.ReadUInt64(); uint reportedPlayerId = networkEvent.Message.ReadUInt32(); actorState.position.X = networkEvent.Message.ReadSingle(); actorState.position.Y = networkEvent.Message.ReadSingle(); actorState.position.Z = networkEvent.Message.ReadSingle(); actorState.velocity.X = networkEvent.Message.ReadSingle(); actorState.velocity.Y = networkEvent.Message.ReadSingle(); actorState.velocity.Z = networkEvent.Message.ReadSingle(); /*actorState.orientation.X = networkEvent.Message.ReadSingle(); actorState.orientation.Y = networkEvent.Message.ReadSingle(); actorState.orientation.Z = networkEvent.Message.ReadSingle(); actorState.orientation.W = networkEvent.Message.ReadSingle();*/ actorState.viewAngles.X = networkEvent.Message.ReadSingle(); actorState.viewAngles.Y = networkEvent.Message.ReadSingle(); actorState.viewAngles.Z = networkEvent.Message.ReadSingle(); actorState.lastJumpTime = networkEvent.Message.ReadSingle(); actorState.numJumps = networkEvent.Message.ReadInt32(); actorState.jumped = networkEvent.Message.ReadBoolean(); //inputState.frame = receivedFrame; inputState.ViewDelta.X = networkEvent.Message.ReadSingle(); inputState.ViewDelta.Y = networkEvent.Message.ReadSingle(); inputState.MoveForward = networkEvent.Message.ReadSingle(); inputState.MoveRight = networkEvent.Message.ReadSingle(); inputState.Attack = networkEvent.Message.ReadBoolean(); inputState.Jump = networkEvent.Message.ReadBoolean(); ServerFrame = receivedFrame; break; } case GameModeMessageType2.PlayerInput: { PlayerInputState2 inputState; //? ulong receivedFrame = inputState.Frame = networkEvent.Message.ReadUInt64(); uint playerId = networkEvent.Message.ReadUInt32(); float time = networkEvent.Message.ReadSingle(); float delay = Time.TimeSinceStartup - time; inputState.ViewDelta.X = networkEvent.Message.ReadSingle(); inputState.ViewDelta.Y = networkEvent.Message.ReadSingle(); inputState.MoveForward = networkEvent.Message.ReadSingle(); inputState.MoveRight = networkEvent.Message.ReadSingle(); inputState.Attack = networkEvent.Message.ReadBoolean(); inputState.Jump = networkEvent.Message.ReadBoolean(); //Console.Print("recv: " + receivedFrame.ToString()); ClientInfo player = GetClient(playerId) as ClientInfo; if (receivedFrame <= player.LastReceivedFrame) { if (receivedFrame < player.LastProcessedFrame) { Console.PrintWarning($"Dropping late frame from client: {receivedFrame}, last processed: {player.LastProcessedFrame}"); break; } else { //Console.PrintWarning($"Out-of-order frame received from client: {receivedFrame}, latest: {player.LastReceivedFrame}"); } } player.LastReceivedFrame = Math.Max(receivedFrame, player.LastReceivedFrame); //if (player.LastProcessedFrame == 0) // player.LastProcessedFrame = receivedFrame - 1; //Console.Print($"server receive client frame {receivedFrame}, current server frame {Frame}"); var asdf = receivedFrame; //receivedFrame = Frame; // TODO: receive all frames from players, simulate all unprocessed frames in next simulation cycle? if (inputState.MoveForward != 0) receivedFrame = receivedFrame; //Console.Print($"delay: {delay * 1000}ms"); // Sanity check if (GetPlayerInputState(playerId, receivedFrame, out var prevInputState)) { Console.PrintWarning($"Duplicate frame received from client: {asdf}, delay: {delay*1000}ms"); //break; } UpdatePlayerInputState(playerId, receivedFrame, inputState, default); break; } default: break; } return true; } public bool OnClientConnecting(NetworkConnection connection) { AcceptConnectionMessage message = new AcceptConnectionMessage(); message.Frame = Frame; message.GameTime = GameTime; message.Players = new AcceptConnectionMessage.PlayerInfo[_clients.Count]; int playerIndex = 0; foreach (var player in _clients.Values) { ref AcceptConnectionMessage.PlayerInfo playerInfo = ref message.Players[playerIndex]; playerInfo.PlayerId = player.PlayerActor.GetScript().PlayerId; playerInfo.PlayerPosition = player.PlayerActor.Position; playerIndex++; } NetworkMessage networkMessage = NetworkManager.ServerBeginSendMessage(); message.Write(ref networkMessage); NetworkManager.ServerEndSendMessage(ref networkMessage, connection); return true; } public void OnClientConnected(NetworkConnection connection) { uint playerId = connection.ConnectionId; _clients.Add(playerId, new ClientInfo() { Connection = connection }); var spawns = _worldSpawn.GetChildren().Where(x => x.Name.StartsWith("PlayerSpawn_")).ToArray(); Console.Print($"found {spawns.Length} spawns"); var randomSpawn = spawns.First(); Float3 position = randomSpawn.Position + new Float3(0f, 4.1f, 0f); Float3 eulerAngles = randomSpawn.Orientation.EulerAngles; SpawnPlayer(playerId, position, eulerAngles); { NetworkMessage message = NetworkManager.ServerBeginSendMessage(); message.WriteByte((byte)GameModeMessageType.SpawnPlayer); message.WriteUInt32(playerId); message.WriteUInt64(Frame); message.WriteSingle(position.X); message.WriteSingle(position.Y); message.WriteSingle(position.Z); message.WriteSingle(eulerAngles.X); message.WriteSingle(eulerAngles.Y); message.WriteSingle(eulerAngles.Z); NetworkManager.ServerEndSendMessage(ref message, connection); } } public override ulong GetLastProcessedFrame(uint playerId) { ClientInfo player = GetClient(playerId) as ClientInfo; return player.LastProcessedFrame; } public override ulong GetLastReceivedFrame(uint playerId) { ClientInfo player = GetClient(playerId) as ClientInfo; return player.LastReceivedFrame; } public override void UpdatePlayerInputState(uint playerId, ulong frame, PlayerInputState2 inputState, PlayerMovementState movementState) { base.UpdatePlayerInputState(playerId, frame, inputState, movementState); //ClientInfo player = GetClient(playerId) as ClientInfo; //player.LastReceivedFrame = frame; // Dropped frames should be ignored? } protected override PlayerActor SpawnPlayer(uint playerId, Float3 position, Vector3 eulerAngles) { var playerActor = base.SpawnPlayer(playerId, position, eulerAngles); playerActor.Input = new PlayerInputNetwork2(playerId, this); return playerActor; } } file class ClientWorld : World { protected class ClientInfo : IClientInfo { public NetworkConnection Connection { get; set; } public PlayerActor PlayerActor { get; set; } public PlayerFrame[] FrameHistory { get; set; } = new PlayerFrame[120]; public ClientInfo() { for (int i = 0; i < FrameHistory.Length; i++) FrameHistory[i] = new PlayerFrame(); } } private uint _localPlayerId; protected Dictionary _clients = new(); protected override IClientInfo GetClient(uint playerId) => _clients[playerId]; protected override void Init() { NetworkManager.RegisterClientCallbacks(OnMessage, null, OnClientConnected); CreateScene("Client", "c095f9ac4989a46afd7fe3821f086e2e"); base.Init(); } protected override void Cleanup() { NetworkManager.UnregisterClientCallbacks(OnMessage, null, OnClientConnected); base.Cleanup(); } override protected void OnLateUpdate() { foreach (var player in _scene.GetChildren()) { if (player.PlayerId != _localPlayerId) continue; ClientInfo playerClient = GetClient(player.PlayerId) as ClientInfo; PlayerInputState2 inputState = player.Input.GetState(); PlayerMovementState movementState = player.GetScript().movementState; if (inputState.Frame == 0) inputState = inputState; /*public Float2 ViewDelta; public float MoveForward; public float MoveRight; public bool Attack; public bool Jump;*/ //Console.Print($"client send {Frame}"); UpdatePlayerInputState(player.PlayerId, Frame, inputState, movementState); //Console.Print($"cframe: {Frame}"); NetworkMessage message = NetworkManager.ClientBeginSendMessage(); message.WriteByte((byte)GameModeMessageType2.PlayerInput); message.WriteUInt64(Frame); message.WriteUInt32(player.PlayerId); message.WriteSingle(Time.TimeSinceStartup); message.WriteVector2(inputState.ViewDelta); message.WriteSingle(inputState.MoveForward); message.WriteSingle(inputState.MoveRight); message.WriteBoolean(inputState.Attack); message.WriteBoolean(inputState.Jump); NetworkManager.ClientEndSendMessage(ref message); //Console.Print("send: " + Frame.ToString()); } base.OnLateUpdate(); } public bool OnMessage(ref NetworkEvent networkEvent) { byte messageTypeByte = networkEvent.Message.ReadByte(); if (!Enum.IsDefined(typeof(GameModeMessageType2), messageTypeByte)) { //Console.PrintError($"GameModeManager: Unsupported message type received from client: {messageTypeByte}"); return false; } GameModeMessageType2 messageType = (GameModeMessageType2)messageTypeByte; NetworkManager.DebugLastHandledMessage = messageType.ToString(); //Console.Print($"GameModeManager: {messageType}"); switch (messageType) { case GameModeMessageType2.AcceptConnection: { // Setup some initial state for newly connected client AcceptConnectionMessage welcomePlayer = AcceptConnectionMessage.Read(ref networkEvent.Message); ServerFrame = welcomePlayer.Frame; Frame += welcomePlayer.Frame; GameTime = welcomePlayer.GameTime; foreach (var playerInfo in welcomePlayer.Players) { _clients.Add(playerInfo.PlayerId, new ClientInfo()); SpawnPlayer(playerInfo.PlayerId, playerInfo.PlayerPosition, new Float3(0)); } Console.Print("received welcome: frame " + ServerFrame); break; } case GameModeMessageType2.SpawnPlayer: { uint playerId = networkEvent.Message.ReadUInt32(); ulong playerFrameIndex = networkEvent.Message.ReadUInt64(); Float3 position = new Float3(networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle()); Vector3 eulerAngles = new Float3(networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle()); /*if (!clientWorldState.playerFrameHistory.ContainsKey(playerId)) { var playerFrames = new PlayerFrame[120]; for (int j = 0; j < playerFrames.Length; j++) playerFrames[j] = new PlayerFrame(); clientWorldState.playerFrameHistory.Add(playerId, playerFrames); }*/ SpawnPlayer(playerId, position, eulerAngles); //if (NetworkManager.IsClient) //players[playerId].GetScript().Input.SetFrame(ClientFrame); break; } #if false case GameModeMessageType2.PlayerPosition: { //uint playerId = networkEvent.Sender.ConnectionId; PlayerInputState2 inputState; //? PlayerActorState actorState; ulong receivedFrame = networkEvent.Message.ReadUInt64(); uint reportedPlayerId = networkEvent.Message.ReadUInt32(); actorState.position.X = networkEvent.Message.ReadSingle(); actorState.position.Y = networkEvent.Message.ReadSingle(); actorState.position.Z = networkEvent.Message.ReadSingle(); actorState.velocity.X = networkEvent.Message.ReadSingle(); actorState.velocity.Y = networkEvent.Message.ReadSingle(); actorState.velocity.Z = networkEvent.Message.ReadSingle(); /*actorState.orientation.X = networkEvent.Message.ReadSingle(); actorState.orientation.Y = networkEvent.Message.ReadSingle(); actorState.orientation.Z = networkEvent.Message.ReadSingle(); actorState.orientation.W = networkEvent.Message.ReadSingle();*/ actorState.viewAngles.X = networkEvent.Message.ReadSingle(); actorState.viewAngles.Y = networkEvent.Message.ReadSingle(); actorState.viewAngles.Z = networkEvent.Message.ReadSingle(); actorState.lastJumpTime = networkEvent.Message.ReadSingle(); actorState.numJumps = networkEvent.Message.ReadInt32(); actorState.jumped = networkEvent.Message.ReadBoolean(); //inputState.frame = receivedFrame; inputState.ViewDelta.X = networkEvent.Message.ReadSingle(); inputState.ViewDelta.Y = networkEvent.Message.ReadSingle(); inputState.MoveForward = networkEvent.Message.ReadSingle(); inputState.MoveRight = networkEvent.Message.ReadSingle(); inputState.Attack = networkEvent.Message.ReadBoolean(); inputState.Jump = networkEvent.Message.ReadBoolean(); ServerFrame = receivedFrame; break; } #endif default: break; } return true; } public override bool IsLocalPlayer(uint playerId) => playerId == _localPlayerId; public override ulong GetLastProcessedFrame(uint playerId) { //ClientInfo player = GetClient(playerId) as ClientInfo; return 0; } public override ulong GetLastReceivedFrame(uint playerId) { //ClientInfo player = GetClient(playerId) as ClientInfo; return 0; } public void OnClientConnected(NetworkConnection connection) { uint playerId = connection.ConnectionId; _clients.Add(playerId, new ClientInfo() { Connection = connection }); _localPlayerId = playerId; Console.Print($"ClientWorld: Connected, playerId: {_localPlayerId}"); } protected override PlayerActor SpawnPlayer(uint playerId, Float3 position, Vector3 eulerAngles) { var playerActor = base.SpawnPlayer(playerId, position, eulerAngles); if (playerId == _localPlayerId) playerActor.Input = new PlayerInput2(); else playerActor.Input = new PlayerInputNetwork2(playerId, this); return playerActor; } } public static class WorldExtensions { public static World GetWorld(this Script script) => IsServerScene(script.Scene) ? WorldStore.ServerWorld : WorldStore.ClientWorld; public static World GetWorld(this Actor actor) => IsServerScene(actor.Scene) ? WorldStore.ServerWorld : WorldStore.ClientWorld; private static bool IsServerScene(Scene scene) => scene.Name == "ServerScene"; } file static class WorldStore { public static ServerWorld ServerWorld; public static ClientWorld ClientWorld; } /*file class WorldPlugin : GamePlugin { public WorldPlugin() { } public override void Initialize() { WorldStore.ClientWorld = new World(isServer: false); } public override void Deinitialize() { WorldStore.ServerWorld = null; WorldStore.ClientWorld = null; } }*/