#if true using System; using System.Collections.Generic; using System.IO; using System.Linq; using FlaxEngine; using FlaxEngine.Assertions; using FlaxEngine.Networking; using Console = Game.Console; namespace Game { public enum GameModeMessageType : byte { WelcomePlayer, SpawnPlayer, PlayerInput, PlayerPosition, // world update PlayerSnapshot, // send world state delta to client since last client's acknowledged frame } public static class GameModeManager { private class WorldState { public ulong frame; public List actors; public Dictionary playerFrameHistory; public WorldState() { actors = new List(); playerFrameHistory = new Dictionary(); } } private class PlayerFrame { public ulong frame; public Float3 position; } private static Dictionary players; private static Dictionary playerConnections; public static Dictionary playerLastReceivedFrames; private static Dictionary playerLastFrame; private static bool welcomed = false; private static WorldState serverWorldState; private static WorldState clientWorldState; private static ulong lastReceivedServerFrame = 0; public static ulong ServerFrame => /*NetworkManager.server != null ? serverWorldState.frame :*/ lastReceivedServerFrame; public static ulong ClientFrame => clientWorldState.frame; public static float ServerTime = 0f; public static float ClientTime = 0f; public static void Init() { welcomed = false; lastReceivedServerFrame = 0; ServerTime = Time.GameTime; ClientTime = 0f; players = new Dictionary(); playerConnections = new Dictionary(); playerLastReceivedFrames = new Dictionary(); playerLastFrame = new Dictionary(); serverWorldState = new WorldState(); clientWorldState = new WorldState(); NetworkManager.OnMessage += OnMessage; Level.SceneLoaded += OnLevelLoaded; //Scripting.LateUpdate += OnLateUpdatePre; Scripting.LateFixedUpdate += OnLateUpdatePre; } public static void Cleanup() { NetworkManager.OnMessage -= OnMessage; Level.SceneLoaded -= OnLevelLoaded; //Scripting.LateUpdate -= OnLateUpdatePre; Scripting.LateFixedUpdate -= OnLateUpdatePre; foreach (var player in Level.GetActors()) { FlaxEngine.Object.Destroy(player); } } private static PlayerFrame GetPlayerFrame(uint playerIndex, ulong playerFrameIndex) { WorldState worldState = NetworkManager.server != null ? serverWorldState : clientWorldState; PlayerFrame[] playerFrames = worldState.playerFrameHistory[playerIndex]; PlayerFrame playerFrame = playerFrames[playerFrameIndex % 120]; if (playerFrame.frame != playerFrameIndex) return null; return playerFrame; } public static void OnLevelLoaded(Scene scene, Guid assetGuid) { serverWorldState.frame = 0; Console.Print("level loaded"); } public static void OnLateUpdatePre() { try { NetworkManager.IsServer = NetworkManager.server != null; NetworkManager.IsClient = (NetworkManager.client != null || NetworkManager.IsDemoPlaying) && NetworkManager.server == null; NetworkManager.IsLocalClient = (NetworkManager.client != null || NetworkManager.IsDemoPlaying) && NetworkManager.server != null; OnLateUpdate(); } finally { NetworkManager.IsServer = false; NetworkManager.IsClient = false; NetworkManager.IsLocalClient = false; } } public static void OnLateUpdate() { if (NetworkManager.IsServer) { foreach (KeyValuePair kv in players) { var playerId = kv.Key; var playerActor = kv.Value; var playerFrames = serverWorldState.playerFrameHistory[playerId]; var playerFrame = playerFrames[serverWorldState.frame % 120]; playerFrame.frame = serverWorldState.frame; //playerFrame.position = playerActor.Position; PlayerMovement playerMovement = playerActor.GetScript(); playerMovement.input.GetState(serverWorldState.frame, out var inputState, out var actorState); playerFrame.position = actorState.position; } foreach (KeyValuePair kv in players) { var otherPlayerId = kv.Key; foreach (KeyValuePair kv2 in players) { //if (kv2.Key == playerId) // continue; var playerActor = kv2.Value; var playerId = kv2.Key; // TODO: relevancy checks here etc. PlayerMovement playerMovement = playerActor.GetScript(); //PlayerActorState actorState = playerMovement.input.GetCurrentActorState(); //PlayerInputState inputState = playerMovement.input.GetCurrentInputState(); var playerFrame = playerLastFrame[playerId]; if (!playerMovement.input.GetState(playerFrame, out var inputState, out var actorState)) Console.Print("send input failure to client"); { NetworkMessage message = NetworkManager.ServerBeginSendMessage(); message.WriteByte((byte)GameModeMessageType.PlayerPosition); message.WriteUInt64(playerFrame); message.WriteUInt32(kv2.Key); message.WriteSingle(actorState.position.X); message.WriteSingle(actorState.position.Y); message.WriteSingle(actorState.position.Z); message.WriteSingle(actorState.velocity.X); message.WriteSingle(actorState.velocity.Y); message.WriteSingle(actorState.velocity.Z); message.WriteSingle(actorState.orientation.X); message.WriteSingle(actorState.orientation.Y); message.WriteSingle(actorState.orientation.Z); message.WriteSingle(actorState.orientation.W); message.WriteSingle(actorState.viewAngles.X); message.WriteSingle(actorState.viewAngles.Y); message.WriteSingle(actorState.viewAngles.Z); message.WriteSingle(actorState.lastJumpTime); message.WriteInt32(actorState.numJumps); message.WriteBoolean(actorState.jumped); //inputState.frame message.WriteSingle(inputState.viewDeltaX); message.WriteSingle(inputState.viewDeltaY); message.WriteSingle(inputState.moveForward); message.WriteSingle(inputState.moveRight); message.WriteBoolean(inputState.attacking); message.WriteBoolean(inputState.jumping); NetworkManager.ServerEndSendMessage(ref message, playerConnections[otherPlayerId]); } } } } if (NetworkManager.IsClient || NetworkManager.IsLocalClient) { //if (!welcomed) // return; if (welcomed) foreach (PlayerActor playerActor in Level.GetActors()) { var playerId = playerActor.PlayerId; 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); } var playerFrameHistory = clientWorldState.playerFrameHistory[playerId]; var playerFrame = playerFrameHistory[ClientFrame % 120]; playerFrame.frame = ClientFrame; playerFrame.position = playerActor.Position; } clientWorldState.frame++; /*PlayerActor playerActor = Level.GetActors().FirstOrDefault(x => x.GetScript().PlayerId == NetworkManager.LocalPlayerClientId); if (playerActor == null) return;*/ //var playerFrame = localPlayerFrameHistory[serverWorldState.frame % 120]; //playerFrame.frame = serverWorldState.frame; //playerFrame.position = playerActor.Position; /*{ NetworkMessage message = NetworkManager.ClientBeginSendMessage(); message.WriteByte((byte)GameModeMessageType.PlayerPosition); message.WriteUInt64(worldState.frame); message.WriteUInt32(NetworkManager.LocalPlayerClientId); message.WriteSingle(playerActor.Position.X); message.WriteSingle(playerActor.Position.Y); message.WriteSingle(playerActor.Position.Z); NetworkManager.ClientEndSendMessage(ref message); }*/ } else if (NetworkManager.IsLocalClient) { } serverWorldState.frame++; /*foreach (KeyValuePair kv in players) { var playerId = kv.Key; var playerActor = kv.Value; var playerConnection = playerConnections[playerId]; if (NetworkManager.IsLocalClient && playerId == NetworkManager.LocalPlayerClientId) continue; { NetworkMessage message = NetworkManager.ServerBeginSendMessage(); message.WriteByte((byte)GameModeMessageType.PlayerPosition); message.WriteUInt64(0); message.WriteSingle(playerActor.Position.X); message.WriteSingle(playerActor.Position.Y); message.WriteSingle(playerActor.Position.Z); NetworkManager.ServerEndSendMessage(ref message, playerConnections[playerId]); } }*/ } public static bool OnMessage(ref NetworkEvent networkEvent) { byte messageTypeByte = networkEvent.Message.ReadByte(); if (!Enum.IsDefined(typeof(GameModeMessageType), messageTypeByte)) { //Console.PrintError($"GameModeManager: Unsupported message type received from client: {messageTypeByte}"); return false; } GameModeMessageType messageType = (GameModeMessageType)messageTypeByte; //Console.Print($"GameModeManager: {messageType}"); switch (messageType) { case GameModeMessageType.WelcomePlayer: { welcomed = true; if (NetworkManager.IsClient || NetworkManager.IsLocalClient) { var serverFrame = networkEvent.Message.ReadUInt64(); if (!NetworkManager.IsLocalClient) serverWorldState.frame = serverFrame; //lastReceivedServerFrame = serverFrame; clientWorldState.frame += serverFrame; ClientTime = networkEvent.Message.ReadSingle(); int numActors = (int)networkEvent.Message.ReadUInt32(); for (int i = 0; i < numActors; i++) { uint playerId = networkEvent.Message.ReadUInt32(); Float3 playerPosition; playerPosition.X = networkEvent.Message.ReadSingle(); playerPosition.Y = networkEvent.Message.ReadSingle(); playerPosition.Z = networkEvent.Message.ReadSingle(); SpawnPlayer(playerId, playerPosition, new Float3(0)); var playerFrames = new PlayerFrame[120]; for (int j = 0; j < playerFrames.Length; j++) playerFrames[j] = new PlayerFrame(); clientWorldState.playerFrameHistory.Add(playerId, playerFrames); } Console.Print("received welcome: frame " + serverWorldState.frame); if (!players.ContainsKey(NetworkManager.LocalPlayerClientId)) // listen server players.Add(NetworkManager.LocalPlayerClientId, null); //playerConnections.Add(NetworkManager.LocalPlayerClientId, connection); } break; } case GameModeMessageType.SpawnPlayer: { uint playerId = networkEvent.Message.ReadUInt32(); ulong playerFrameIndex = networkEvent.Message.ReadUInt64(); 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, new Float3(networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle()), new Float3(networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle())); //if (NetworkManager.IsClient) players[playerId].GetScript().input.frame = ClientFrame; break; } case GameModeMessageType.PlayerInput: { uint playerId = networkEvent.Sender.ConnectionId; PlayerInputState inputState = new PlayerInputState(); inputState.frame = networkEvent.Message.ReadUInt64(); inputState.viewDeltaX = networkEvent.Message.ReadSingle(); inputState.viewDeltaY = networkEvent.Message.ReadSingle(); inputState.moveForward = networkEvent.Message.ReadSingle(); inputState.moveRight = networkEvent.Message.ReadSingle(); inputState.attacking = networkEvent.Message.ReadBoolean(); inputState.jumping = networkEvent.Message.ReadBoolean(); UpdatePlayerInput(playerId, inputState); playerLastReceivedFrames[playerId] = inputState.frame; break; } case GameModeMessageType.PlayerPosition: { //uint playerId = networkEvent.Sender.ConnectionId; PlayerInputState inputState = default; //? PlayerActorState actorState; ulong reportedFrame = 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 = reportedFrame; inputState.viewDeltaX = networkEvent.Message.ReadSingle(); inputState.viewDeltaY = networkEvent.Message.ReadSingle(); inputState.moveForward = networkEvent.Message.ReadSingle(); inputState.moveRight = networkEvent.Message.ReadSingle(); inputState.attacking = networkEvent.Message.ReadBoolean(); inputState.jumping = networkEvent.Message.ReadBoolean(); //if (actorState.viewAngles != new Float3(90f, 0f, 0f)) // Console.Print($"{reportedFrame} has viewangles: {actorState.viewAngles}"); //Assert.IsTrue(reportedFrame >= lastReceivedServerFrame); if (reportedPlayerId == NetworkManager.LocalPlayerClientId && reportedFrame < lastReceivedServerFrame) { //Console.Print($"packet wrong order, last received: {lastReceivedServerFrame}, new: {reportedFrame}"); break; } if (NetworkManager.IsClient) { if (reportedPlayerId == NetworkManager.LocalPlayerClientId) lastReceivedServerFrame = reportedFrame; //Console.Print($"we drifted, corrected. client frame: {serverWorldState.frame}, server frame: {reportedFrame}"); PlayerActor playerActor = Level.GetActors().FirstOrDefault(x => x.GetScript().PlayerId == reportedPlayerId); if (playerActor != null) { PlayerInput playerInput = playerActor.GetScript().input; if (NetworkManager.IsLocalClient && reportedPlayerId == NetworkManager.LocalPlayerClientId) { } else playerInput.SetState(reportedFrame, inputState, actorState); if (!NetworkManager.IsLocalClient && playerInput is PlayerInputNetwork) { playerActor.Position = actorState.position; playerActor.GetScript().movementState.currentVelocity = actorState.velocity; playerActor.GetScript().movementState.lastJumped = actorState.lastJumpTime; playerActor.GetScript().movementState.numJumps = actorState.numJumps; playerActor.GetScript().movementState.jumped = actorState.jumped; playerActor.GetScript().SetCameraEulerAngles(actorState.viewAngles, true); } //playerActor.SetPosition(reportedPosition); } } break; } default: Console.PrintError($"GameModeManager: Unhandled message type: {messageType}"); return false; } return true; } public static bool OnClientConnecting(NetworkConnection connection) { //if (connection.ConnectionId != NetworkManager.LocalPlayerClientId) { Console.Print("sending welcome: frame " + serverWorldState.frame); NetworkMessage message = NetworkManager.ServerBeginSendMessage(); message.WriteByte((byte)GameModeMessageType.WelcomePlayer); message.WriteUInt64(serverWorldState.frame); message.WriteSingle(ServerTime); message.WriteUInt32((uint)serverWorldState.actors.Count); foreach (PlayerActor playerActor in serverWorldState.actors) { message.WriteUInt32(playerActor.GetScript().PlayerId); message.WriteSingle(playerActor.Position.X); message.WriteSingle(playerActor.Position.Y); message.WriteSingle(playerActor.Position.Z); playerActor.GetScript().input.SetState(serverWorldState.frame, new PlayerInputState(), new PlayerActorState() { position = playerActor.Position, velocity = playerActor.GetScript().movementState.currentVelocity, orientation = playerActor.GetScript().rootActor.Orientation, viewAngles = playerActor.GetScript().viewAngles, lastJumpTime = playerActor.GetScript().movementState.lastJumped, numJumps = playerActor.GetScript().movementState.numJumps, jumped = playerActor.GetScript().movementState.jumped, //onGround = playerActor.GetScript().movementState.onGround, }); } NetworkManager.ServerEndSendMessage(ref message, connection); } return true; } public static bool OnClientConnected(NetworkConnection connection) { uint playerId = connection.ConnectionId; if (NetworkManager.LocalPlayerClientId == 0) NetworkManager.LocalPlayerClientId = playerId; var spawns = Level.GetActors().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; players.Add(playerId, null); playerConnections.Add(playerId, connection); var playerFrames = new PlayerFrame[120]; for (int i = 0; i < playerFrames.Length; i++) playerFrames[i] = new PlayerFrame(); serverWorldState.playerFrameHistory.Add(playerId, playerFrames); SpawnPlayer(playerId, position, eulerAngles); { NetworkMessage message = NetworkManager.ServerBeginSendMessage(); message.WriteByte((byte)GameModeMessageType.SpawnPlayer); message.WriteUInt32(playerId); message.WriteUInt64(serverWorldState.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); } return true; } private static void SpawnPlayer(uint playerId, Float3 position, Vector3 eulerAngles) { if (NetworkManager.IsServer && !playerLastFrame.ContainsKey(playerId)) playerLastFrame.Add(playerId, serverWorldState.frame); if (NetworkManager.IsLocalClient && playerId == NetworkManager.LocalPlayerClientId) return; // Handled by listenserver //spawned = true; 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 = PrefabManager.SpawnPrefab(playerPrefab).As(); playerActor.Initialize(playerId, position, eulerAngles); //playerActor.Teleport(position, eulerAngles); if (!players.ContainsKey(playerId)) players.Add(playerId, null); players[playerId] = playerActor; PlayerInput playerInput = playerActor.GetScript().input; playerInput.frame = (NetworkManager.IsServer) ? serverWorldState.frame : ClientFrame; if (NetworkManager.IsServer) { serverWorldState.actors.Add(playerActor); if (!playerLastReceivedFrames.ContainsKey(playerId)) playerLastReceivedFrames.Add(playerId, 0); } } private static void UpdatePlayerInput(uint playerId, PlayerInputState inputState) { if (playerId == NetworkManager.LocalPlayerClientId) { playerLastFrame[playerId] = inputState.frame; return; } PlayerActor playerActor = players[playerId]; PlayerMovement playerMovement = playerActor.GetScript(); playerActor.UpdateNetworkInput(inputState); ulong startFrame = playerLastFrame[playerId]; if (startFrame >= inputState.frame) return; // dropped frame, ignore // simulate at least one or more frame when receiving input frames from player. missing frames use inputs from previous frames var simframs = 0; ulong frame = startFrame+1; PlayerInputState prevInputState; playerMovement.input.GetState(frame - 1, out prevInputState, out var _); for (; frame <= inputState.frame; frame++) { if (!playerMovement.input.GetState(frame, out var lastInputState, out var lastActorState)) { // dropped frame, use previous input lastInputState = prevInputState; lastInputState.frame = frame; } playerMovement.ApplyInputToCamera(lastInputState, true); playerMovement.SimulatePlayerMovement(lastInputState); playerMovement.input.SetState(frame, lastInputState, new PlayerActorState() { position = playerActor.Position, velocity = playerMovement.movementState.currentVelocity, orientation = playerMovement.rootActor.Orientation, viewAngles = playerMovement.viewAngles, lastJumpTime = playerMovement.movementState.lastJumped, numJumps = playerMovement.movementState.numJumps, jumped = playerMovement.movementState.jumped, //onGround = playerMovement.movementState.onGround, }); simframs++; prevInputState = lastInputState; } if (playerActor.Position.Length < 1.0f) simframs = simframs; playerLastFrame[playerId] = inputState.frame;//frame; if (simframs > 1) Console.Print($"simulated {simframs} frames"); } } } #endif