diff --git a/Assets/Maps/aerowalk.map b/Assets/Maps/aerowalk.map index b759350..2e53573 100644 --- a/Assets/Maps/aerowalk.map +++ b/Assets/Maps/aerowalk.map @@ -4965,3 +4965,9 @@ "origin" "160 40 504" "targetname" "light_target1" } +// entity 74 +{ +"classname" "info_player_deathmatch" +"origin" "-496 -304 152" +"angle" "180" +} diff --git a/Content/Common/PlayerPrefab.prefab b/Content/Common/PlayerPrefab.prefab index 5b4efe4..de631c2 100644 --- a/Content/Common/PlayerPrefab.prefab +++ b/Content/Common/PlayerPrefab.prefab @@ -9,19 +9,6 @@ "V": {}, "IsActive": false, "Name": "PlayerPrefab", - "Transform": { - "Translation": { - "X": 73.19660949707031, - "Y": -33.01502227783203, - "Z": -139.43125915527345 - }, - "Orientation": { - "X": 0.0, - "Y": 1.0, - "Z": 0.0, - "W": -4.371138828673793e-8 - } - }, "StaticFlags": 0, "OverrideMass": true, "Mass": 10.0 diff --git a/Content/GameSettings.json b/Content/GameSettings.json index fda699b..58c228d 100644 --- a/Content/GameSettings.json +++ b/Content/GameSettings.json @@ -1,7 +1,7 @@ { "ID": "3c7bc3854d42f9b1b0fea9ba0d7fa8e9", "TypeName": "FlaxEditor.Content.Settings.GameSettings", - "EngineBuild": 6335, + "EngineBuild": 6340, "Data": { "ProductName": "Goake", "CompanyName": "GoaLitiuM", diff --git a/Content/SceneData/MainScene/Terrain/92de37984e46b6e605d9928cc985c1b6_ 0_ 0_Heightfield.flax b/Content/SceneData/MainScene/Terrain/92de37984e46b6e605d9928cc985c1b6_ 0_ 0_Heightfield.flax deleted file mode 100644 index dc491ce..0000000 Binary files a/Content/SceneData/MainScene/Terrain/92de37984e46b6e605d9928cc985c1b6_ 0_ 0_Heightfield.flax and /dev/null differ diff --git a/Content/SceneData/MainScene/Terrain/92de37984e46b6e605d9928cc985c1b6_ 0_ 0_Heightmap.flax b/Content/SceneData/MainScene/Terrain/92de37984e46b6e605d9928cc985c1b6_ 0_ 0_Heightmap.flax deleted file mode 100644 index 0e8d175..0000000 Binary files a/Content/SceneData/MainScene/Terrain/92de37984e46b6e605d9928cc985c1b6_ 0_ 0_Heightmap.flax and /dev/null differ diff --git a/Content/Scenes/MainScene.scene b/Content/Scenes/MainScene.scene index 0d82452..76de44d 100644 --- a/Content/Scenes/MainScene.scene +++ b/Content/Scenes/MainScene.scene @@ -49,13 +49,13 @@ "ParentID": "ff6b6db54b5aa08e7286ef86246149ef", "Transform": { "Translation": { - "X": 1841.0, - "Y": 972.0, + "X": 1355.0, + "Y": 791.0, "Z": 0.0 } }, "Data": { - "Text": "1302 tris\n 131 drawcalls\n434fps2\n1921fps" + "Text": "13022 tris\n 102 drawcalls\n238fps2\n240fps" } }, { diff --git a/Content/Settings/EngineSettings/BuildSettings.json b/Content/Settings/EngineSettings/BuildSettings.json index 15c6e57..cc55101 100644 --- a/Content/Settings/EngineSettings/BuildSettings.json +++ b/Content/Settings/EngineSettings/BuildSettings.json @@ -1,7 +1,7 @@ { "ID": "af2e52554f7faed7b4937181dd22d166", "TypeName": "FlaxEditor.Content.Settings.BuildSettings", - "EngineBuild": 6332, + "EngineBuild": 6340, "Data": { "MaxAssetsPerPackage": 4096, "MaxPackageSizeMB": 1024, @@ -9,6 +9,7 @@ "ForDistribution": false, "SkipPackaging": false, "AdditionalAssets": [], + "AdditionalScenes": null, "AdditionalAssetFolders": [ "Content/Materials", "Content/Textures", @@ -17,6 +18,7 @@ ], "ShadersNoOptimize": false, "ShadersGenerateDebugData": false, + "SkipDotnetPackaging": true, "Presets": [ { "Name": "Preset 3", diff --git a/Content/Textures/sky/sky.flax b/Content/Textures/TexturesUnused/sky/sky.flax similarity index 99% rename from Content/Textures/sky/sky.flax rename to Content/Textures/TexturesUnused/sky/sky.flax index dfd99ac..1baeb6e 100644 Binary files a/Content/Textures/sky/sky.flax and b/Content/Textures/TexturesUnused/sky/sky.flax differ diff --git a/Content/Textures/dev/dev_128_black.flax b/Content/Textures/dev/dev_128_black.flax index ee85e60..de17f55 100644 Binary files a/Content/Textures/dev/dev_128_black.flax and b/Content/Textures/dev/dev_128_black.flax differ diff --git a/Content/Textures/dev/dev_128_gray.flax b/Content/Textures/dev/dev_128_gray.flax index 6abe860..c5e95f1 100644 Binary files a/Content/Textures/dev/dev_128_gray.flax and b/Content/Textures/dev/dev_128_gray.flax differ diff --git a/Content/Textures/dev/dev_128_gray_norm.flax b/Content/Textures/dev/dev_128_gray_norm.flax index 2b0fe5a..74555b5 100644 Binary files a/Content/Textures/dev/dev_128_gray_norm.flax and b/Content/Textures/dev/dev_128_gray_norm.flax differ diff --git a/Source/Game.Gen.cpp b/Source/Game.Gen.cpp index 6e5a42a..ab24fb0 100644 --- a/Source/Game.Gen.cpp +++ b/Source/Game.Gen.cpp @@ -7,6 +7,6 @@ StaticallyLinkedBinaryModuleInitializer StaticallyLinkedBinaryModuleGame(GetBina extern "C" BinaryModule* GetBinaryModuleGame() { - static NativeBinaryModule module("Game", MAssemblyOptions()); + static NativeBinaryModule module("Game"); return &module; } diff --git a/Source/Game/Console/Console.cs b/Source/Game/Console/Console.cs index c1fc67a..1047d5c 100644 --- a/Source/Game/Console/Console.cs +++ b/Source/Game/Console/Console.cs @@ -3,12 +3,13 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using FlaxEditor; using FlaxEngine; namespace Game { - public class ConsoleLine + public struct ConsoleLine { public string content; @@ -75,7 +76,7 @@ namespace Game public static string LinePrefix => instance.LinePrefix; - public static IReadOnlyCollection Lines => instance.Lines; + public static ReadOnlySpan Lines => instance.Lines; public static void Init() { @@ -220,7 +221,7 @@ namespace Game public int DebugVerbosity { get; set; } = 1; public string LinePrefix { get; internal set; } = "]"; - public IReadOnlyCollection Lines => consoleLines.AsReadOnly(); + public ReadOnlySpan Lines => CollectionsMarshal.AsSpan(consoleLines); public void Dispose() { @@ -380,23 +381,14 @@ namespace Game { debugLastLine = text; - foreach (string line in text.Split('\n')) - { - ConsoleLine lineEntry = new ConsoleLine(line); - consoleLines.Add(lineEntry); - OnPrint?.Invoke(text); - } + PrintLine(text); } + // Echoes warning text to Console public void PrintWarning(string text) { - foreach (string line in text.Split('\n')) - { - ConsoleLine lineEntry = new ConsoleLine(line); - consoleLines.Add(lineEntry); - OnPrint?.Invoke(text); - } + PrintLine(text); } // Echoes error text to Console @@ -404,12 +396,7 @@ namespace Game [DebuggerHidden] public void PrintError(string text) { - foreach (string line in text.Split('\n')) - { - ConsoleLine lineEntry = new ConsoleLine(line); - consoleLines.Add(lineEntry); - OnPrint?.Invoke(text); - } + PrintLine(text); /*if (Debugger.IsAttached) Debugger.Break(); @@ -417,6 +404,26 @@ namespace Game throw new Exception(text); } + private void PrintLine(string text) + { + if (text.IndexOf('\n') != -1) + { + // Avoid generating extra garbage in single-line cases + foreach (string line in text.Split('\n')) + { + ConsoleLine lineEntry = new ConsoleLine(line); + consoleLines.Add(lineEntry); + OnPrint?.Invoke(text); + } + } + else + { + ConsoleLine lineEntry = new ConsoleLine(text); + consoleLines.Add(lineEntry); + OnPrint?.Invoke(text); + } + } + public void PrintDebug(int verbosity, bool noRepeat, string text) { if (DebugVerbosity < verbosity) @@ -428,12 +435,7 @@ namespace Game debugLastLine = text; - foreach (string line in text.Split('\n')) - { - ConsoleLine lineEntry = new ConsoleLine(line); - consoleLines.Add(lineEntry); - OnPrint?.Invoke(text); - } + PrintLine(text); } // Opens the Console diff --git a/Source/Game/Console/ConsoleContentTextBox.cs b/Source/Game/Console/ConsoleContentTextBox.cs index c3ad826..1d6eece 100644 --- a/Source/Game/Console/ConsoleContentTextBox.cs +++ b/Source/Game/Console/ConsoleContentTextBox.cs @@ -127,7 +127,7 @@ namespace Game } - private void CalculateVisibleLines(IReadOnlyCollection lines, out int firstVisibleLine, + private void CalculateVisibleLines(ReadOnlySpan lines, out int firstVisibleLine, out int lastVisibleLine, out LineInfo[] wrappedLines) { wrappedLines = null; @@ -142,18 +142,21 @@ namespace Game int lineMaxChars = (int)(Width / fontWidth); int lineMaxLines = GetHeightInLines(); int numLines = 0; - int lineIndex = lines.Count; var lineInfos = new List(lineMaxLines + 1); int linesSkipped = 0; - foreach (string line in lines.Reverse()) + //foreach (string line in lines.Reverse()) + int lineIndex = lines.Length - 1; + for (; lineIndex >= 0; lineIndex--) { - lineIndex--; + //lineIndex--; if (linesSkipped < ScrollOffset) { linesSkipped++; continue; } + string line = lines[lineIndex]; + int numChars = 0; int startIndex = lineInfos.Count; while (numChars < line.Length) @@ -207,7 +210,7 @@ namespace Game Profiler.BeginEvent("ConsoleContentTextBoxDraw_FetchLines"); var lines = Console.Lines; Profiler.EndEvent(); - if (lines.Count > 0) + if (lines.Length > 0) { Profiler.BeginEvent("ConsoleContentTextBoxDraw_Lines"); @@ -253,7 +256,7 @@ namespace Game foreach (LineInfo li in wrappedLines) { int lineIndex = li.lineIndex; - string fullLine = lines.ElementAt(lineIndex); + string fullLine = lines[lineIndex]; string line = fullLine.Substring(li.lineOffset, li.lineLength); int leftChar = selectionLeftChar; @@ -330,7 +333,7 @@ namespace Game foreach (LineInfo li in wrappedLines) { int lineIndex = li.lineIndex; - string line = lines.ElementAt(lineIndex).content.Substring(li.lineOffset, li.lineLength); + string line = lines[lineIndex].content.Substring(li.lineOffset, li.lineLength); Render2D.DrawText(font, line, TextColor, ref layout); layout.Bounds.Y += lineHeight; } @@ -405,7 +408,7 @@ namespace Game return false; hitLine = wrappedLines[hitWrappedLine].lineIndex; - string line = lines.ElementAt(hitLine).content.Substring(wrappedLines[hitWrappedLine].lineOffset, + string line = lines[hitLine].content.Substring(wrappedLines[hitWrappedLine].lineOffset, wrappedLines[hitWrappedLine].lineLength); layout.Bounds.Y = top + hitWrappedLine * lineHeight; @@ -447,7 +450,7 @@ namespace Game ScrollOffset += GetHeightInLines() / 2; // should count the wrapped line count here over Console.Lines.Count //var maxOffset = Console.Lines.Count - GetHeightInLines(); - int maxOffset = Console.Lines.Count - 1; + int maxOffset = Console.Lines.Length - 1; if (ScrollOffset > maxOffset) ScrollOffset = maxOffset; } @@ -477,7 +480,7 @@ namespace Game else if (delta > 0) { ScrollOffset += ScrollMouseLines; - int maxOffset = Console.Lines.Count - GetHeightInLines(); + int maxOffset = Console.Lines.Length - GetHeightInLines(); if (ScrollOffset > maxOffset) ScrollOffset = maxOffset; } @@ -497,7 +500,7 @@ namespace Game ret = true; } - if (button == MouseButton.Left && Console.Lines.Count > 0) + if (button == MouseButton.Left && Console.Lines.Length > 0) { bool selectionStarted = !selectionActive; Focus(); @@ -614,7 +617,7 @@ namespace Game selectedText.AppendLine(); lastLineIndex = lineIndex; - string fullLine = lines.ElementAt(lineIndex); + string fullLine = lines[lineIndex]; string line = fullLine.Substring(li.lineOffset, li.lineLength); int leftChar = selectionLeftChar; diff --git a/Source/Game/Console/ConsolePlugin.cs b/Source/Game/Console/ConsolePlugin.cs index 3bc2580..1b8b874 100644 --- a/Source/Game/Console/ConsolePlugin.cs +++ b/Source/Game/Console/ConsolePlugin.cs @@ -25,6 +25,7 @@ namespace Game //AssetManager.Init(); // TODO: move these elsewhere #if !FLAX_EDITOR Level.SceneLoading += OnSceneLoading; + Level.SceneLoaded += OnSceneLoaded; #endif } @@ -32,6 +33,7 @@ namespace Game { #if !FLAX_EDITOR Level.SceneLoading -= OnSceneLoading; + Level.SceneLoaded -= OnSceneLoaded; #endif } @@ -41,6 +43,19 @@ namespace Game LoadConfig(); } + private void OnSceneLoaded(Scene scene, Guid guid) + { + Level.SceneLoaded -= OnSceneLoaded; +#if !FLAX_EDITOR + + //GameMode.Connect(); + //GameMode.StartServer(true); + //NetworkManager.StartServer(); + GameModeManager.Init(); + NetworkManager.ConnectServer(); +#endif + } + private void LoadConfig() { Console.Print("Loading config file (GamePlugin)"); @@ -101,12 +116,16 @@ namespace Game { //FlaxEditor.Editor.Instance.PlayModeBegin -= Instance_PlayModeBegin; LoadConfig(); - GameMode.StartServer(true); + //GameMode.Connect(); + GameModeManager.Init(); + NetworkManager.StartServer(); + //GameMode.StartServer(true); } private void OnPlayModeEnd() { - GameMode.StopServer(); + //GameMode.StopServer(); + NetworkManager.Cleanup(); } public override void Deinitialize() diff --git a/Source/Game/Console/ConsoleScript.cs b/Source/Game/Console/ConsoleScript.cs index a6ef4f0..2a21d20 100644 --- a/Source/Game/Console/ConsoleScript.cs +++ b/Source/Game/Console/ConsoleScript.cs @@ -30,6 +30,8 @@ namespace Game private UIControl rootControl; + private int fontHeight; + public override void OnStart() { consoleInputEvent = new InputEvent("Console"); @@ -37,8 +39,7 @@ namespace Game FontReference fontReference = new FontReference(ConsoleFont, ConsoleFontSize); Font fontRaw = fontReference.GetFont(); - int fontHeight = (int)(fontRaw.Height / Platform.DpiScale); - + fontHeight = (int)(fontRaw.Height / Platform.DpiScale); // root actor which holds all the elements //var rootContainerControl = new ContainerControl(new Rectangle(0, 0, screenSize.X, screenSize.Y)); ContainerControl rootContainerControl = new ContainerControl(new Rectangle()); @@ -222,6 +223,7 @@ namespace Game Debug.Logger.LogHandler.SendLog += OnSendLog; Debug.Logger.LogHandler.SendExceptionLog += OnSendExceptionLog; + Console.OnOpen += OnConsoleOpen; Console.OnClose += OnConsoleClose; Console.OnPrint += OnPrint; @@ -254,7 +256,7 @@ namespace Game } else { - Console.Print("[EXCEP] " + exception.Message); + Console.Print("[EXCEP] " + exception.ToString()); } } @@ -374,7 +376,6 @@ namespace Game } else if (!Console.IsOpen) { - int fontHeight = (int)(consoleNotifyBox.FontHeight / Platform.DpiScale); if (location.Y < -conHeight * ConsoleHeight + fontHeight) { consoleNotifyBox.Visible = true; @@ -386,8 +387,7 @@ namespace Game public void OnPrint(string text) { - int fontHeight = (int)(consoleNotifyBox.FontHeight / Platform.DpiScale); - consoleNotifyBox.Height = Math.Min(ConsoleNotifyLines, Console.Lines.Count) * fontHeight; + consoleNotifyBox.Height = Math.Min(ConsoleNotifyLines, Console.Lines.Length) * fontHeight; } public void SetInput(string text) diff --git a/Source/Game/Console/EngineSubsystem.cs b/Source/Game/Console/EngineSubsystem.cs index 58346f9..f65657f 100644 --- a/Source/Game/Console/EngineSubsystem.cs +++ b/Source/Game/Console/EngineSubsystem.cs @@ -7,6 +7,7 @@ using NVIDIA; #endif using FlaxEditor.Content.Settings; using FlaxEngine; +using FlaxEngine.Networking; namespace Game { @@ -353,11 +354,12 @@ namespace Game /*aoSettings.OverrideFlags = (aoSettings.OverrideFlags & ~AmbientOcclusionSettingsOverride.Enabled) | (boolValue ? AmbientOcclusionSettingsOverride.Enabled - : 0 & AmbientOcclusionSettingsOverride.Enabled); - */ + : 0 & AmbientOcclusionSettingsOverride.Enabled);*/ + aoSettings.Enabled = boolValue; postProcessSettings.AmbientOcclusion = aoSettings; + Graphics.PostProcessSettings = postProcessSettings; } } @@ -608,17 +610,45 @@ namespace Game Scripting.Update += TimeDemoOnUpdate; } + [ConsoleVariable("net_fakelag")] + public static string NetFakeLag + { + get + { + var driver = NetworkManager.server != null ? (NetworkManager.ServerNetworkDriver as NetworkLagDriver) : (NetworkManager.ClientNetworkDriver as NetworkLagDriver); + if (driver == null) + return 0.ToString(); + return ((int)driver.Lag).ToString(); + } + set + { + var driver = NetworkManager.server != null ? (NetworkManager.ServerNetworkDriver as NetworkLagDriver) : (NetworkManager.ClientNetworkDriver as NetworkLagDriver); + if (driver == null) + return; + + int intValue = 0; + if (int.TryParse(value, out intValue)) + { } + else if (float.TryParse(value, out float valueFloat)) + intValue = (int)valueFloat; + intValue = Math.Clamp(intValue, 0, 2000); + + driver.Lag = intValue; + } + } + [ConsoleCommand("map")] public static void MapCommand() { //NetworkManager.StartServer(true); - GameMode.StartServer(true); + NetworkManager.StartServer(); } [ConsoleCommand("connect")] public static void ConnectCommand() { - GameMode.Connect(); + //GameMode.Connect(); + NetworkManager.ConnectServer(); } [ConsoleSubsystemInitializer] diff --git a/Source/Game/Game.Build.cs b/Source/Game/Game.Build.cs index 618ba9b..85935c6 100644 --- a/Source/Game/Game.Build.cs +++ b/Source/Game/Game.Build.cs @@ -35,8 +35,8 @@ public class Game : GameModule options.PublicDependencies.Add("Networking"); options.PrivateDependencies.Add("FidelityFXFSR"); - - //options.ScriptingAPI.FileReferences.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "DotNet", "Newtonsoft.Json.dll")); + + //options.ScriptingAPI.FileReferences.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "DotNet", "Newtonsoft.Json.dll")); #if COMPILE_WITH_DLSS DLSS.ConditionalImport(options, options.PrivateDependencies); diff --git a/Source/Game/GameMode/GameModeManager_old.cs b/Source/Game/GameMode/GameModeManager_old.cs index dc2d146..7df4e5f 100644 --- a/Source/Game/GameMode/GameModeManager_old.cs +++ b/Source/Game/GameMode/GameModeManager_old.cs @@ -1,9 +1,10 @@ -#if false +#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; @@ -14,7 +15,8 @@ namespace Game WelcomePlayer, SpawnPlayer, PlayerInput, - PlayerPosition, // debug + PlayerPosition, // world update + PlayerSnapshot, // send world state delta to client since last client's acknowledged frame } @@ -43,32 +45,42 @@ namespace Game private static Dictionary players; private static Dictionary playerConnections; - private static bool spawned = false; + private static bool welcomed = false; - private static WorldState worldState; - private static PlayerFrame[] localPlayerFrameHistory; + 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 void Init() { + welcomed = false; + lastReceivedServerFrame = 0; + players = new Dictionary(); playerConnections = new Dictionary(); - localPlayerFrameHistory = new PlayerFrame[120]; - worldState = new WorldState(); + serverWorldState = new WorldState(); + clientWorldState = new WorldState(); NetworkManager.OnMessage += OnMessage; Level.SceneLoaded += OnLevelLoaded; - Scripting.LateUpdate += OnLateUpdatePre; + //Scripting.LateUpdate += OnLateUpdatePre; + Scripting.LateFixedUpdate += OnLateUpdatePre; } public static void Cleanup() { NetworkManager.OnMessage -= OnMessage; Level.SceneLoaded -= OnLevelLoaded; - Scripting.LateUpdate -= OnLateUpdatePre; + //Scripting.LateUpdate -= OnLateUpdatePre; + Scripting.LateFixedUpdate -= OnLateUpdatePre; } 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]; @@ -80,7 +92,7 @@ namespace Game public static void OnLevelLoaded(Scene scene, Guid assetGuid) { - worldState.frame = 0; + serverWorldState.frame = 0; Console.Print("level loaded"); } @@ -90,12 +102,14 @@ namespace Game { NetworkManager.IsServer = NetworkManager.server != null; NetworkManager.IsClient = NetworkManager.client != null && NetworkManager.server == null; + NetworkManager.IsLocalClient = NetworkManager.client != null && NetworkManager.server != null; OnLateUpdate(); } finally { NetworkManager.IsServer = false; NetworkManager.IsClient = false; + NetworkManager.IsLocalClient = false; } } @@ -108,10 +122,10 @@ namespace Game var playerId = kv.Key; var playerActor = kv.Value; - var playerFrames = worldState.playerFrameHistory[playerId]; - var playerFrame = playerFrames[worldState.frame % 120]; + var playerFrames = serverWorldState.playerFrameHistory[playerId]; + var playerFrame = playerFrames[serverWorldState.frame % 120]; - playerFrame.frame = worldState.frame; + playerFrame.frame = serverWorldState.frame; playerFrame.position = playerActor.Position; } @@ -120,41 +134,79 @@ namespace Game var playerId = kv.Key; foreach (KeyValuePair kv2 in players) { - if (kv2.Key == playerId) - continue; + //if (kv2.Key == playerId) + // continue; var otherPlayerActor = kv2.Value; // TODO: relevancy checks here etc. + PlayerMovement playerMovement = otherPlayerActor.GetScript(); + PlayerActorState actorState = playerMovement.input.GetCurrentActorState(); + PlayerInputState inputState = playerMovement.input.GetCurrentInputState(); { NetworkMessage message = NetworkManager.ServerBeginSendMessage(); message.WriteByte((byte)GameModeMessageType.PlayerPosition); - message.WriteUInt64(worldState.frame); + message.WriteUInt64(serverWorldState.frame); message.WriteUInt32(kv2.Key); - message.WriteSingle(otherPlayerActor.Position.X); - message.WriteSingle(otherPlayerActor.Position.Y); - message.WriteSingle(otherPlayerActor.Position.Z); + 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); + + //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[playerId]); } } } } - if (NetworkManager.IsClient) + if (NetworkManager.IsClient || NetworkManager.IsLocalClient) { - if (!spawned) - return; + //if (!welcomed) + // return; - PlayerActor playerActor = Level.GetActors().First(x => + if (welcomed) + foreach (PlayerActor playerActor in Level.GetActors()) + { + var playerId = playerActor.PlayerId; + var playerFrameHistory = clientWorldState.playerFrameHistory[playerId]; + var playerFrame = playerFrameHistory[clientWorldState.frame % 120]; + + playerFrame.frame = clientWorldState.frame; + playerFrame.position = playerActor.Position; + } + + clientWorldState.frame++; + + /*PlayerActor playerActor = Level.GetActors().FirstOrDefault(x => x.GetScript().PlayerId == NetworkManager.LocalPlayerClientId); - var playerFrame = localPlayerFrameHistory[worldState.frame % 120]; + if (playerActor == null) + return;*/ - playerFrame.frame = worldState.frame; - playerFrame.position = playerActor.Position; + //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); @@ -163,10 +215,14 @@ namespace Game message.WriteSingle(playerActor.Position.Y); message.WriteSingle(playerActor.Position.Z); NetworkManager.ClientEndSendMessage(ref message); - } + }*/ + } + else if (NetworkManager.IsLocalClient) + { + } - worldState.frame++; + serverWorldState.frame++; /*foreach (KeyValuePair kv in players) { @@ -205,11 +261,12 @@ namespace Game { case GameModeMessageType.WelcomePlayer: { - if (NetworkManager.IsClient) + welcomed = true; + if (NetworkManager.IsClient || NetworkManager.IsLocalClient) { - worldState.frame = networkEvent.Message.ReadUInt64(); + var serverFrame = networkEvent.Message.ReadUInt64(); int numActors = (int)networkEvent.Message.ReadUInt32(); - for (int i=0; i().input.frame = clientWorldState.frame; break; } case GameModeMessageType.PlayerInput: @@ -257,21 +335,49 @@ namespace Game case GameModeMessageType.PlayerPosition: { uint playerId = networkEvent.Sender.ConnectionId; - Float3 reportedPosition; + PlayerInputState inputState = default; //? + PlayerActorState actorState; ulong reportedFrame = networkEvent.Message.ReadUInt64(); uint reportedPlayerId = networkEvent.Message.ReadUInt32(); - reportedPosition.X = networkEvent.Message.ReadSingle(); - reportedPosition.Y = networkEvent.Message.ReadSingle(); - reportedPosition.Z = networkEvent.Message.ReadSingle(); + 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(); - if (NetworkManager.IsLocalClient && !NetworkManager.IsServer) + 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(); + + //Assert.IsTrue(reportedFrame >= lastReceivedServerFrame); + if (reportedFrame < lastReceivedServerFrame) + { + Console.Print($"packet wrong order, received {lastReceivedServerFrame}, new {reportedFrame}"); + break; + } + + + /*if (NetworkManager.IsLocalClient && !NetworkManager.IsServer) { } - else if (NetworkManager.IsServer) + else*/ if (NetworkManager.IsServer) { - PlayerFrame playerFrame = GetPlayerFrame(playerId, reportedFrame); + Assert.Fail(); + /*PlayerFrame playerFrame = GetPlayerFrame(playerId, reportedFrame); PlayerActor playerActor = players[playerId]; if (playerFrame == null) Console.Print("frame is in the past, unable to verify frame"); @@ -285,12 +391,8 @@ namespace Game { NetworkMessage message = NetworkManager.ServerBeginSendMessage(); message.WriteByte((byte)GameModeMessageType.PlayerPosition); - /*message.WriteUInt64(reportedFrame); - message.WriteUInt32(playerId); - message.WriteSingle(playerFramePosition.X); - message.WriteSingle(playerFramePosition.Y); - message.WriteSingle(playerFramePosition.Z);*/ - message.WriteUInt64(worldState.frame); + + message.WriteUInt64(serverWorldState.frame); message.WriteUInt32(playerId); message.WriteSingle(playerActor.Position.X); message.WriteSingle(playerActor.Position.Y); @@ -298,16 +400,26 @@ namespace Game NetworkManager.ServerEndSendMessage(ref message, playerConnections[playerId]); } } - } + }*/ } else if (NetworkManager.IsClient) { - Console.Print($"we drifted, corrected. client frame: {worldState.frame}, server frame: {reportedFrame}"); + 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) - playerActor.SetPosition(reportedPosition); + { + PlayerInput playerInput = playerActor.GetScript().input; + + playerInput.SetState(reportedFrame, ref inputState, ref actorState); + { + + } + //playerActor.SetPosition(reportedPosition); + } } break; @@ -322,14 +434,14 @@ namespace Game public static bool OnClientConnecting(NetworkConnection connection) { - if (connection.ConnectionId != NetworkManager.LocalPlayerClientId) + //if (connection.ConnectionId != NetworkManager.LocalPlayerClientId) { - Console.Print("sending welcome: frame " + worldState.frame); + Console.Print("sending welcome: frame " + serverWorldState.frame); NetworkMessage message = NetworkManager.ServerBeginSendMessage(); message.WriteByte((byte)GameModeMessageType.WelcomePlayer); - message.WriteUInt64(worldState.frame); - message.WriteUInt32((uint)worldState.actors.Count); - foreach (PlayerActor playerActor in worldState.actors) + message.WriteUInt64(serverWorldState.frame); + message.WriteUInt32((uint)serverWorldState.actors.Count); + foreach (PlayerActor playerActor in serverWorldState.actors) { message.WriteUInt32(playerActor.GetScript().PlayerId); message.WriteSingle(playerActor.Position.X); @@ -343,12 +455,16 @@ namespace Game 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(); - uint playerId = connection.ConnectionId; + Float3 position = randomSpawn.Position + new Float3(0f, 4.1f, 0f); Float3 eulerAngles = randomSpawn.Orientation.EulerAngles; @@ -357,7 +473,7 @@ namespace Game var playerFrames = new PlayerFrame[120]; for (int i = 0; i < playerFrames.Length; i++) playerFrames[i] = new PlayerFrame(); - worldState.playerFrameHistory.Add(playerId, playerFrames); + serverWorldState.playerFrameHistory.Add(playerId, playerFrames); SpawnPlayer(playerId, position, eulerAngles); @@ -365,7 +481,7 @@ namespace Game NetworkMessage message = NetworkManager.ServerBeginSendMessage(); message.WriteByte((byte)GameModeMessageType.SpawnPlayer); message.WriteUInt32(playerId); - message.WriteUInt64(worldState.frame); + message.WriteUInt64(serverWorldState.frame); message.WriteSingle(position.X); message.WriteSingle(position.Y); message.WriteSingle(position.Z); @@ -383,7 +499,7 @@ namespace Game if (NetworkManager.IsLocalClient) return; // Handled by listenserver - spawned = true; + //spawned = true; string prefabPath = Path.Combine(AssetManager.ContentPath, "Common"); var playerPrefab = Content.Load(Path.Combine(prefabPath, "PlayerPrefab.prefab")); @@ -391,15 +507,16 @@ namespace Game Console.PrintError("GameModeManager: Failed to find PlayerPrefab"); PlayerActor playerActor = PrefabManager.SpawnPrefab(playerPrefab).As(); - playerActor.Initialize(playerId); - playerActor.Teleport(position, eulerAngles); + 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; if (NetworkManager.IsServer) - worldState.actors.Add(playerActor); + serverWorldState.actors.Add(playerActor); } private static void UpdatePlayerInput(uint playerId, PlayerInputState inputState) diff --git a/Source/Game/GameMode/GameMode.cs b/Source/Game/GameMode/GameMode_highlevel.cs similarity index 84% rename from Source/Game/GameMode/GameMode.cs rename to Source/Game/GameMode/GameMode_highlevel.cs index e6824fb..51c04b5 100644 --- a/Source/Game/GameMode/GameMode.cs +++ b/Source/Game/GameMode/GameMode_highlevel.cs @@ -1,4 +1,5 @@ -using FlaxEditor.Content.Settings; +#if false +using FlaxEditor.Content.Settings; using FlaxEngine; using FlaxEngine.Networking; using System; @@ -88,6 +89,8 @@ namespace Game currentGameMode.Start(); + Console.Open(); + return true; } @@ -122,9 +125,23 @@ namespace Game private static void NetworkManager_ClientConnected(NetworkClient networkClient) { - Console.Print("new client connected"); + try + { + Console.Print("new client connected"); - currentGameMode.OnPlayerSpawn(networkClient.ClientId); + foreach (var (playerId, playerActor) in currentGameMode.players) + { + NetworkReplicator.SpawnObject(playerActor, new[] { networkClient.ClientId }); + playerActor.Initialize(playerId, playerActor.Position, playerActor.GetRotation()); + } + + currentGameMode.OnPlayerSpawn(networkClient.ClientId); + } + catch (Exception e) + { + Console.Print(e.ToString()); + throw; + } } private static void NetworkManager_ClientConnecting(ref NetworkClientConnectionData arg0) @@ -184,11 +201,18 @@ namespace Game public void OnPlayerSpawn(uint clientId) { - // Get random spawn + // Get a random spawn var spawns = Level.GetActors().Where(x => x.Name.StartsWith("PlayerSpawn_")).ToArray(); - Console.Print($"found {spawns.Length} spawns"); - var randomSpawn = spawns.First(); + var randomSpawn = spawns.FirstOrDefault(); + if (players.Count > 0) + randomSpawn = spawns.LastOrDefault(); + if (randomSpawn == null) + { + Console.Print("No spawns found for player"); + return; + } + Console.Print($"found {spawns.Length} spawns"); Float3 spawnPosition = randomSpawn.Position + new Float3(0f, 4.1f, 0f); Float3 spawnAngles = randomSpawn.Orientation.EulerAngles; @@ -201,12 +225,13 @@ namespace Game PlayerActor playerActor = PrefabManager.SpawnPrefab(playerPrefab).As(); NetworkReplicator.AddObject(playerActor); - playerActor.Initialize(clientId); + playerActor.Initialize(clientId, spawnPosition, spawnAngles); - playerActor.Teleport(spawnPosition, spawnAngles); + //playerActor.Teleport(spawnPosition, spawnAngles); //NetworkReplicator.SetObjectOwnership(playerActor, clientId); NetworkReplicator.SpawnObject(playerActor); + players.Add(clientId, playerActor); //playerActor.Initialize(clientId); //playerActor.hai = 345; @@ -232,4 +257,5 @@ namespace Game return Float3.Zero; } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Source/Game/GameMode/NetworkManager.cs b/Source/Game/GameMode/NetworkManager.cs new file mode 100644 index 0000000..92d6738 --- /dev/null +++ b/Source/Game/GameMode/NetworkManager.cs @@ -0,0 +1,150 @@ +using System; +using System.Linq; +using FlaxEditor; +using FlaxEngine; +using FlaxEngine.Networking; +using Console = Game.Console; + +namespace Game +{ + [AttributeUsage(AttributeTargets.Class)] + public class NetworkPredictedAttribute : Attribute + { + } + + // TODO: insert code to update variables with this attribute? + // rename to NetworkReplicatedAttribute? + [AttributeUsage(AttributeTargets.Class)] + public class NetworkedAttribute : Attribute + { + } + + // NetworkMulticastAttribute: calls methods marked with this in all clients + + public enum NetworkMessageType : byte + { + Handshake = 1, + Message + } + + + public static partial class NetworkManager + { + public delegate bool OnMessageDecl(ref NetworkEvent networkEvent); + + private static bool initialized; + + public static NetworkPeer server; + public static NetworkPeer client; + + private static readonly ushort ServerPort = 59183; + private static string ServerAddress; + private static readonly ushort MTU = 1500; + private static readonly ushort MaximumClients = 32; + public static OnMessageDecl OnMessage; + + public static bool IsServer = false; + public static bool IsClient = false; + public static bool IsLocalClient = false; // Context dependant, true when message is handled by local client + + public static void Init() + { + if (initialized) + return; + + /*if (Engine.CommandLine.Contains("-server")) + { + StartServer(); + ServerAddress = "localhost"; + ConnectServer(); + } + else if (Engine.CommandLine.Contains("-client")) + { + ServerAddress = "localhost"; + ConnectServer(); + } +//#if FLAX_EDITOR + else + { + StartServer(); + ServerAddress = "localhost"; + ConnectServer(); + }*/ +//#endif + + initialized = true; +#if FLAX_EDITOR + Editor.Instance.PlayModeEnd += Cleanup; +#endif + + GameModeManager.Init(); // FIXME + } + + public static void Cleanup() + { + if (server != null) + { + Scripting.FixedUpdate -= OnServerUpdate; + Scripting.Exit -= Cleanup; + Level.ActorSpawned -= OnServerActorSpawned; + NetworkPeer.ShutdownPeer(server); + server = null; + } + + if (client != null) + { + Scripting.FixedUpdate -= OnClientUpdate; + Scripting.Exit -= Cleanup; + Level.ActorSpawned -= OnClientActorSpawned; + NetworkPeer.ShutdownPeer(client); + client = null; + } + LocalPlayerClientId = 0; + +#if FLAX_EDITOR + Editor.Instance.PlayModeEnd -= Cleanup; + GameModeManager.Cleanup(); // FIXME +#endif + + initialized = false; + } + + private static void OnNetworkMessage(ref NetworkEvent networkEvent) + { + byte messageTypeByte = networkEvent.Message.ReadByte(); + if (!Enum.IsDefined(typeof(NetworkMessageType), messageTypeByte)) + { + Console.PrintError($"Unsupported message type received from client: {messageTypeByte}"); + return; + } + + NetworkMessageType messageType = (NetworkMessageType)messageTypeByte; + + switch (messageType) + { + case NetworkMessageType.Handshake: + { + string message = networkEvent.Message.ReadString(); + Console.Print($"Received handshake from {networkEvent.Sender.ConnectionId}, msg: " + message); + break; + } + case NetworkMessageType.Message: + { + if (OnMessage != null) + foreach (OnMessageDecl func in OnMessage.GetInvocationList() + .Cast().ToArray()) + { + bool ret = func.Invoke(ref networkEvent); + if (ret) + break; + } + + break; + } + default: + Console.PrintError($"Unsupported message type received from client: {messageTypeByte}"); + break; + } + } + } +} \ No newline at end of file diff --git a/Source/Game/GameMode/NetworkManager_Client.cs b/Source/Game/GameMode/NetworkManager_Client.cs new file mode 100644 index 0000000..b929147 --- /dev/null +++ b/Source/Game/GameMode/NetworkManager_Client.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; +using FlaxEngine; +using FlaxEngine.Networking; +using Console = Game.Console; +using Object = FlaxEngine.Object; + +namespace Game +{ + public static partial class NetworkManager + { + public static uint LocalPlayerClientId { get; /*private*/ set; } + + public static INetworkDriver ClientNetworkDriver { get; set; } + + public static bool ConnectServer(string serverAddress = "localhost", bool listenServer = false) + { + if (!listenServer) + Cleanup(); + ServerAddress = serverAddress; + + //var driver = Object.New(typeof(ENetDriver)); + //ClientNetworkDriver = null; + NetworkLagDriver driver = Object.New(); + driver.Lag = 0f; + ClientNetworkDriver = driver; + + + client = NetworkPeer.CreatePeer(new NetworkConfig + { + NetworkDriver = driver, + ConnectionsLimit = MaximumClients, + MessagePoolSize = 2048, + MessageSize = MTU, + Address = ServerAddress == "localhost" ? "127.0.0.1" : ServerAddress, + Port = ServerPort + }); + if (client == null) + { + Console.Print("Failed to create NetworkPeer."); + return false; + } + if (!client.Connect()) + { + Console.Print("Failed to connect to the server."); + return false; + } + + Scripting.FixedUpdate += OnClientUpdate; + if (!listenServer) + { + Scripting.Exit += Cleanup; + Level.ActorSpawned += OnClientActorSpawned; + } + return true; + } + + private static void OnClientUpdate() + { + using Utilities.ScopeProfiler _ = Utilities.ProfileScope("NetworkManager_OnClientUpdate"); + + while (client.PopEvent(out NetworkEvent networkEvent)) + switch (networkEvent.EventType) + { + case NetworkEventType.Connected: + { + LocalPlayerClientId = networkEvent.Sender.ConnectionId; + Console.Print("Connected to server, ConnectionId: " + networkEvent.Sender.ConnectionId); + break; + } + case NetworkEventType.Disconnected: + { + Console.Print("Disconnected from server, timeout."); + LocalPlayerClientId = 0; + break; + } + case NetworkEventType.Timeout: + { + Console.Print("Disconnected from server, connection closed."); + LocalPlayerClientId = 0; + break; + } + case NetworkEventType.Message: + { + try + { + IsLocalClient = server != null; + IsClient = true; + OnNetworkMessage(ref networkEvent); + + if (networkEvent.Message.Position > 0 && + networkEvent.Message.Position < networkEvent.Message.Length) + { + string err = + $"Network message was not fully read: {networkEvent.Message.Position} / {networkEvent.Message.Length}."; + + networkEvent.Message.Position = 0; + byte[] messageBytes = new byte[networkEvent.Message.Length]; + unsafe + { + fixed (byte* messageBytePtr = &messageBytes[0]) + networkEvent.Message.ReadBytes(messageBytePtr, (int)networkEvent.Message.Length); + } + + string messageBytesStr = string.Join(", ", + messageBytes.Select(x => "0x" + ((int)x).ToString("X2"))); + + Console.PrintError(err + $"Message dump: {messageBytesStr}"); + } + } + finally + { + IsLocalClient = false; + IsClient = false; + client.RecycleMessage(networkEvent.Message); + } + break; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private static void OnClientActorSpawned(Actor actor) + { + } + } +} \ No newline at end of file diff --git a/Source/Game/GameMode/NetworkManager_Server.cs b/Source/Game/GameMode/NetworkManager_Server.cs new file mode 100644 index 0000000..9706438 --- /dev/null +++ b/Source/Game/GameMode/NetworkManager_Server.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using FlaxEngine; +using FlaxEngine.Networking; +using Console = Game.Console; +using Object = FlaxEngine.Object; + +namespace Game +{ + public static partial class NetworkManager + { + private static List ConnectedClients; + + private static List NetworkedTypes; + + public static INetworkDriver ServerNetworkDriver { get; set; } + + public static bool StartServer(bool listenServer = true) + { + ConnectedClients = new List(MaximumClients); + + + //var driver = Object.New(typeof(ENetDriver)); + //ServerNetworkDriver = null; + NetworkLagDriver driver = Object.New(); + driver.Lag = 200f; + ServerNetworkDriver = driver; + + server = NetworkPeer.CreatePeer(new NetworkConfig + { + NetworkDriver = driver, + ConnectionsLimit = MaximumClients, + MessagePoolSize = 2048, + MessageSize = MTU, + Address = "any", + Port = ServerPort + }); + if (!server.Listen()) + { + Console.PrintError("Failed to start the server."); + return false; + } + + Scripting.FixedUpdate += OnServerUpdate; + Scripting.Exit += Cleanup; + Level.ActorSpawned += OnServerActorSpawned; + + NetworkedTypes = new List(); + +#if false + var assemblies = Utils.GetAssemblies(); + + foreach (Assembly assembly in assemblies) + { + // Skip common assemblies + string assemblyName = assembly.GetName().Name; + if (assemblyName == "System" || + assemblyName.StartsWith("System.") || + assemblyName.StartsWith("Mono.") || + assemblyName == "mscorlib" || + assemblyName == "Newtonsoft.Json" || + assemblyName == "Snippets" || + assemblyName == "netstandard" || + assemblyName == "Anonymously Hosted DynamicMethods Assembly" || + assemblyName.StartsWith("FlaxEngine.") || + assemblyName.StartsWith("JetBrains.") || + assemblyName.StartsWith("Microsoft.") || + assemblyName.StartsWith("nunit.")) + continue; + + foreach (Type type in assembly.GetTypes()) + if (type.GetCustomAttributes().Any(x => x is NetworkedAttribute)) + NetworkedTypes.Add(type); + } + + foreach (Type type in NetworkedTypes) + Console.Print("tracking networked type: " + type.Name); +#endif + + if (listenServer) + return ConnectServer(listenServer: true); + return true; + } + + public static NetworkMessage ServerBeginSendMessage() + { + NetworkMessage message = server.BeginSendMessage(); + message.WriteByte((byte)NetworkMessageType.Message); + return message; + } + + public static void ServerEndSendMessage(ref NetworkMessage message, NetworkConnection connection, NetworkChannelType channelType = NetworkChannelType.Reliable) + { + server.EndSendMessage(channelType, message, connection); + } + + public static NetworkMessage ClientBeginSendMessage() + { + NetworkMessage message = client.BeginSendMessage(); + message.WriteByte((byte)NetworkMessageType.Message); + return message; + } + + public static void ClientEndSendMessage(ref NetworkMessage message, NetworkChannelType channelType = NetworkChannelType.Reliable) + { + client.EndSendMessage(channelType, message); + } + + private static void OnServerUpdate() + { + using Utilities.ScopeProfiler _ = Utilities.ProfileScope("NetworkManager_OnServerUpdate"); + + while (server.PopEvent(out NetworkEvent networkEvent)) + switch (networkEvent.EventType) + { + case NetworkEventType.Connected: + { + Console.Print($"Client({networkEvent.Sender.ConnectionId}) is trying to connect"); + + try + { + IsServer = true; + if (GameModeManager.OnClientConnecting(networkEvent.Sender)) + { + ConnectedClients.Add(networkEvent.Sender); + Console.Print( + $"Client({networkEvent.Sender.ConnectionId}) connected. Total clients: {ConnectedClients.Count}"); + + GameModeManager.OnClientConnected(networkEvent.Sender); + } + else + Console.Print($"Client({networkEvent.Sender.ConnectionId}) connection refused"); + } + finally + { + IsServer = false; + } + + break; + } + case NetworkEventType.Disconnected: + case NetworkEventType.Timeout: + { + Console.Print($"Client({networkEvent.Sender.ConnectionId}) disconnected!"); + + ConnectedClients.Remove(networkEvent.Sender); + Console.Print("Connected clients: " + ConnectedClients.Count); + break; + } + case NetworkEventType.Message: + { + try + { + IsServer = true; + OnNetworkMessage(ref networkEvent); + + if (networkEvent.Message.Position > 0 && + networkEvent.Message.Position < networkEvent.Message.Length) + { + string err = + $"Network message was not fully read: {networkEvent.Message.Position} / {networkEvent.Message.Length}."; + + networkEvent.Message.Position = 0; + byte[] messageBytes = new byte[networkEvent.Message.Length]; + unsafe + { + fixed (byte* messageBytePtr = &messageBytes[0]) + networkEvent.Message.ReadBytes(messageBytePtr, (int)networkEvent.Message.Length); + } + + string messageBytesStr = string.Join(", ", + messageBytes.Select(x => "0x" + ((int)x).ToString("X2"))); + + Console.PrintError(err + $"Message dump: {messageBytesStr}"); + } + } + finally + { + IsServer = false; + server.RecycleMessage(networkEvent.Message); + } + break; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private static void OnServerActorSpawned(Actor actor) + { + //Console.Print($"actor spawned: {actor.Name} ({actor.TypeName})"); + } + } +} \ No newline at end of file diff --git a/Source/Game/GameMode/NetworkWorld.cs b/Source/Game/GameMode/NetworkWorld.cs new file mode 100644 index 0000000..179f2ff --- /dev/null +++ b/Source/Game/GameMode/NetworkWorld.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using FlaxEngine; +using FlaxEngine.Assertions; + +namespace Game; + +public static class NetworkWorld +{ + private class WorldState + { + public ulong frame; + public List actors; + public Dictionary playerFrameHistory; + + public WorldState() + { + actors = new List(); + playerFrameHistory = new Dictionary(); + } + } + + private struct PlayerLagCompState + { + public Float3 position; + } + + private class PlayerLagCompStates + { + public ulong oldestFrame; + private PlayerLagCompState[] states; + + public PlayerLagCompStates(int frames) + { + states = new PlayerLagCompState[frames]; + } + + public PlayerLagCompState GetState(ulong frame) + { + Assert.IsTrue(frame >= oldestFrame); + ulong index = frame % (ulong)states.Length; + return states[index]; + } + + public void SetState(PlayerLagCompState state, ulong frame) + { + ulong index = frame % (ulong)states.Length; + states[index] = state; + } + } +} \ No newline at end of file diff --git a/Source/Game/Level/Q3MapImporter.cs b/Source/Game/Level/Q3MapImporter.cs index 0dca542..b20231d 100644 --- a/Source/Game/Level/Q3MapImporter.cs +++ b/Source/Game/Level/Q3MapImporter.cs @@ -135,7 +135,7 @@ namespace Game outVertices = finalPoints.ToArray(); //verts = new QuickHull().QuickHull2(points); - //outVertices = verts.ToArray(); + //outVertices = verts.ToArray(); frf f } private MapEntity root; diff --git a/Source/Game/Player/PlayerActor.cs b/Source/Game/Player/PlayerActor.cs index 814a97c..a35e6e6 100644 --- a/Source/Game/Player/PlayerActor.cs +++ b/Source/Game/Player/PlayerActor.cs @@ -35,12 +35,16 @@ namespace Game } #endif - public class PlayerActor : RigidBody + public class SomeActor : EmptyActor + { + + } + + public class PlayerActor : RigidBody//, INetworkSerializable { private PlayerMovement playerMovement; - private RigidBody playerRigidBody; - [NetworkReplicated] + //[NetworkReplicated] public uint PlayerId = uint.MaxValue; /*public PlayerActor() @@ -65,12 +69,17 @@ namespace Game base.OnBeginPlay(); playerMovement = FindScript(); - playerRigidBody = FindActor(); + //playerRigidBody = FindActor(); + + + + //Console.Print("OnBeginPlay playerid: " + PlayerId.ToString()); + //playerMovement.input = new PlayerInputNetwork(); } public override void OnEnable() { - // Trigger OnEnable manually, does not seem 8to propagate when parent gets enabled/disabled + // Trigger OnEnable manually, does not seem to propagate when parent gets enabled/disabled playerMovement.Enabled = true; //NetworkReplicator.AddObject(this); } @@ -81,29 +90,37 @@ namespace Game //NetworkReplicator.RemoveObject(this); } - [NetworkRpc(client: true)] - public void Initialize(uint playerId) + //[NetworkRpc(client: true)] + public void Initialize(uint playerId, Float3 newPosition, Float3 eulerAngles) { + if (PlayerId == playerId) // FIXME + return; + + FindActor("PlayerModel").IsActive = true; + IsActive = true; + PlayerId = playerId; playerMovement.SetInput(playerId); - if (playerId == NetworkManager.LocalClientId) + if (playerId == NetworkManager.LocalPlayerClientId) { FindActor("CameraHolder").IsActive = true; //FindActor("ViewModelHolder").IsActive = true; FindActor("PlayerModel").IsActive = false; } - else - FindActor("PlayerModel").IsActive = true; - IsActive = true; + SetPosition(newPosition); + SetRotation(eulerAngles); + //else + // FindActor("PlayerModel").IsActive = true; + //IsActive = true; } - [NetworkRpc(server: true)] - public void UpdateNetworkInput(ulong frame, Float4 viewDeltaXYMoveForwardRight, bool attacking, bool jumping) + //[NetworkRpc(server: true)] + public void UpdateNetworkInput(PlayerInputState inputState/*, Float4 viewDeltaXYMoveForwardRight, bool attacking, bool jumping*/) { if (playerMovement.input is not PlayerInputNetwork playerInputNetwork) return; - PlayerInputState inputState = new PlayerInputState(frame, viewDeltaXYMoveForwardRight.X, viewDeltaXYMoveForwardRight.Y, viewDeltaXYMoveForwardRight.Z, viewDeltaXYMoveForwardRight.W, attacking, jumping); + //PlayerInputState inputState = new PlayerInputState(frame, viewDeltaXYMoveForwardRight.X, viewDeltaXYMoveForwardRight.Y, viewDeltaXYMoveForwardRight.Z, viewDeltaXYMoveForwardRight.W, attacking, jumping); playerInputNetwork.currentState.input = inputState; } @@ -117,7 +134,12 @@ namespace Game playerMovement.ResetRotation(eulerAngles); } - [NetworkRpc(client: true)] + public Float3 GetRotation() + { + return playerMovement.viewAngles; + } + + //[NetworkRpc(client: true)] public void Teleport(Float3 newPosition, Float3 eulerAngles) { SetPosition(newPosition); diff --git a/Source/Game/Player/PlayerInput.cs b/Source/Game/Player/PlayerInput.cs index e4583cc..bc081ce 100644 --- a/Source/Game/Player/PlayerInput.cs +++ b/Source/Game/Player/PlayerInput.cs @@ -87,6 +87,11 @@ namespace Game public const byte DemoVer = 1; public PlayerState currentState; public ulong frame; + //public ulong oldestFrame; + + private PlayerState[] states = new PlayerState[120]; + + public virtual bool Predict => false; public virtual void OnUpdate() { @@ -98,12 +103,43 @@ namespace Game public virtual void OnEndFrame() { + states[frame % 120] = currentState; + + /*ulong oldest = ulong.MaxValue; + for (int i = 0; i < 120; i++) + oldest = states[i].input.frame < oldest ? states[i].input.frame : oldest; + oldestFrame = oldest;*/ + + frame++; } public virtual void RecordCurrentActorState(PlayerActorState actorState) { } + public bool GetState(ulong frame, out PlayerInputState inputState, out PlayerActorState actorState) + { + int frameIndex = (int)frame % 120; + if (states[frameIndex].input.frame != frame) + { + inputState = default; + actorState = default; + return false; + } + + inputState = states[frameIndex].input; + actorState = states[frameIndex].actor; + return true; + } + + public void SetState(ulong frame, ref PlayerInputState inputState, ref PlayerActorState actorState) + { + int frameIndex = (int)frame % 120; + states[frameIndex].input = inputState; + states[frameIndex].input.frame = frame; + states[frameIndex].actor = actorState; + } + public PlayerInputState GetCurrentInputState() { return currentState.input; diff --git a/Source/Game/Player/PlayerInputDemo.cs b/Source/Game/Player/PlayerInputDemo.cs index 2f95433..190ed6c 100644 --- a/Source/Game/Player/PlayerInputDemo.cs +++ b/Source/Game/Player/PlayerInputDemo.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Console = Game.Console; @@ -26,49 +28,46 @@ namespace Game if (!File.Exists(demoPath)) return; - int expectedPlayerInputStateSize = Marshal.SizeOf(typeof(PlayerInputState)); + int expectedPlayerInputStateSize = Unsafe.SizeOf(); - FileStream fileStream = File.OpenRead(demoPath); - GZipStream stream = new GZipStream(fileStream, CompressionMode.Decompress); + Stopwatch sw = Stopwatch.StartNew(); + + using FileStream fileStream = File.OpenRead(demoPath); + using GZipStream stream = new GZipStream(fileStream, CompressionMode.Decompress); int ver = stream.ReadByte(); int inputStateSize = stream.ReadByte(); - if (ver != DemoVer && inputStateSize != expectedPlayerInputStateSize) + if (ver != DemoVer || inputStateSize != expectedPlayerInputStateSize) { Console.Print("demover mismatch: version " + ver + " != " + DemoVer + ", inputStateSize " + - inputStateSize + " != " + Marshal.SizeOf(typeof(PlayerInputState))); + inputStateSize + " != " + Unsafe.SizeOf()); stream.Close(); return; } - // TODO: bench and compare to unsafe ptr version of this: - // https://stackoverflow.com/questions/17549123/c-sharp-performance-using-unsafe-pointers-instead-of-intptr-and-marshal/29836312#29836312 - T RawDeserialize(byte[] rawData, int position) - { - int rawsize = Marshal.SizeOf(typeof(T)); - if (rawsize > rawData.Length - position) - throw new ArgumentException("Not enough data to fill struct. Array length from position: " + - (rawData.Length - position) + ", Struct length: " + rawsize); - IntPtr buffer = Marshal.AllocHGlobal(rawsize); - Marshal.Copy(rawData, position, buffer, rawsize); - T retobj = (T)Marshal.PtrToStructure(buffer, typeof(T)); - Marshal.FreeHGlobal(buffer); - return retobj; - } - + Span b = stackalloc byte[expectedPlayerInputStateSize]; while (true) { - byte[] b = new byte[expectedPlayerInputStateSize]; - int readBytes = stream.Read(b, 0, b.Length); - if (readBytes < expectedPlayerInputStateSize) - break; + int bytesLeftInBuffer = expectedPlayerInputStateSize; + do + { + int readBytes = stream.Read(b.Slice(expectedPlayerInputStateSize - bytesLeftInBuffer, bytesLeftInBuffer)); + if (readBytes == 0) + break; + bytesLeftInBuffer -= readBytes; + } while (bytesLeftInBuffer > 0); - buffer.Add(RawDeserialize(b, 0)); + if (bytesLeftInBuffer > 0) + break; // EOF; + + buffer.Add(MemoryMarshal.Read(b)); } + sw.Stop(); + bufferEnumerable = buffer.GetEnumerator(); - Console.Print("demo numstates: " + buffer.Count); + Console.Print($"Demo parse time {sw.Elapsed.TotalMilliseconds}ms, frames: {buffer.Count} "); OnEndFrame(); // advances to first frame } diff --git a/Source/Game/Player/PlayerInputLocal.cs b/Source/Game/Player/PlayerInputLocal.cs index b2465f1..4e288ba 100644 --- a/Source/Game/Player/PlayerInputLocal.cs +++ b/Source/Game/Player/PlayerInputLocal.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.IO.Compression; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using FlaxEngine; using FlaxEngine.Networking; @@ -12,16 +14,19 @@ namespace Game public class PlayerInputLocal : PlayerInput { protected List buffer = new List(); - protected GZipStream demoFileStream; - protected FileStream demoFileStream2; + protected GZipStream demoStream; + protected FileStream demoFileStream; private PlayerActor playerActor; - //public bool IsNetworked => NetworkManager.client != null; + public bool IsNetworked => NetworkManager.client != null; + private long flushedFrames = 0; /*public PlayerInputLocal() { }*/ + public override bool Predict => true; + public PlayerInputLocal(PlayerActor playerActor, string demoPath) { this.playerActor = playerActor; @@ -29,15 +34,13 @@ namespace Game if (!demoFolder.Exists) Directory.CreateDirectory(demoFolder.FullName); - demoFileStream2 = File.Open(demoPath, FileMode.Create, FileAccess.Write); - demoFileStream = new GZipStream(demoFileStream2, CompressionMode.Compress); - //stream.Position = 0; - //stream.SetLength(0); - demoFileStream.WriteByte(DemoVer); - demoFileStream.WriteByte((byte)Marshal.SizeOf(typeof(PlayerInputState))); + demoFileStream = File.Open(demoPath, FileMode.Create, FileAccess.Write); + demoStream = new GZipStream(demoFileStream, CompressionMode.Compress); + demoStream.WriteByte(DemoVer); + demoStream.WriteByte((byte)Unsafe.SizeOf()); } - public bool IsRecording => demoFileStream != null; + public bool IsRecording => demoStream != null; public override void OnUpdate() { @@ -82,11 +85,12 @@ namespace Game if (playerActor != null) { - playerActor.UpdateNetworkInput(currentState.input.frame, new Float4(currentState.input.viewDeltaX, currentState.input.viewDeltaY, currentState.input.moveForward, currentState.input.moveRight), currentState.input.attacking, currentState.input.jumping); + //playerActor.UpdateNetworkInput(currentState.input.frame, new Float4(currentState.input.viewDeltaX, currentState.input.viewDeltaY, currentState.input.moveForward, currentState.input.moveRight), currentState.input.attacking, currentState.input.jumping); + } //playerActor.UpdateNetworkInput(currentState.input.frame, currentState.input.viewDeltaX, currentState.input.viewDeltaY, currentState.input.moveForward, currentState.input.moveRight, currentState.input.attacking, currentState.input.jumping, currentState.input.verificationPosition, currentState.input.verificationVelocity, currentState.input.verificationViewAngles, currentState.input.verificationOrientation); - /*if (IsNetworked) + if (IsNetworked) { var message = NetworkManager.ClientBeginSendMessage(); message.WriteByte((byte)GameModeMessageType.PlayerInput); @@ -98,13 +102,13 @@ namespace Game message.WriteBoolean(currentState.input.attacking); message.WriteBoolean(currentState.input.jumping); NetworkManager.ClientEndSendMessage(ref message); - }*/ + } // Reset anything accumulatable here currentState.input.viewDeltaX = 0; currentState.input.viewDeltaY = 0; - frame++; + base.OnEndFrame(); } public override void RecordCurrentActorState(PlayerActorState actorState) @@ -122,24 +126,21 @@ namespace Game if (!IsRecording) return; - byte[] RawSerialize(object anything) + Stopwatch sw = Stopwatch.StartNew(); + + Span bytes = stackalloc byte[Unsafe.SizeOf()]; + foreach (ref PlayerInputState state in CollectionsMarshal.AsSpan(buffer)) { - int rawSize = Marshal.SizeOf(anything); - IntPtr buffer = Marshal.AllocHGlobal(rawSize); - Marshal.StructureToPtr(anything, buffer, false); - byte[] rawDatas = new byte[rawSize]; - Marshal.Copy(buffer, rawDatas, 0, rawSize); - Marshal.FreeHGlobal(buffer); - return rawDatas; - } - - foreach (PlayerInputState state in buffer) - { - byte[] bytes = RawSerialize(state); - demoFileStream.Write(bytes, 0, bytes.Length * sizeof(byte)); + MemoryMarshal.Write(bytes, ref state); + demoStream.Write(bytes); } + sw.Stop(); + + flushedFrames += buffer.Count; buffer.Clear(); + + FlaxEngine.Debug.Write(LogType.Info, $"Wrote demo in {sw.Elapsed.TotalMilliseconds}ms, frames: {flushedFrames}"); } public void StopRecording() @@ -148,10 +149,10 @@ namespace Game return; FlushDemo(); + demoStream.Close(); + demoStream = null; demoFileStream.Close(); demoFileStream = null; - - Debug.Write(LogType.Info, "demo, wrote states: " + buffer.Count); } } } \ No newline at end of file diff --git a/Source/Game/Player/PlayerInputNetwork.cs b/Source/Game/Player/PlayerInputNetwork.cs index c3744a1..b18e5f1 100644 --- a/Source/Game/Player/PlayerInputNetwork.cs +++ b/Source/Game/Player/PlayerInputNetwork.cs @@ -2,5 +2,12 @@ { public class PlayerInputNetwork : PlayerInput { + public override bool Predict => true; + + public override void OnEndFrame() + { + currentState.input.frame = frame; + base.OnEndFrame(); + } } } \ No newline at end of file diff --git a/Source/Game/Player/PlayerMovement.cs b/Source/Game/Player/PlayerMovement.cs index 9e1c5e0..ba18a5c 100644 --- a/Source/Game/Player/PlayerMovement.cs +++ b/Source/Game/Player/PlayerMovement.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using FlaxEngine; using FlaxEngine.Assertions; using FlaxEngine.Networking; @@ -79,11 +80,11 @@ namespace Game private readonly InputEvent onExit = new InputEvent("Exit"); - private readonly bool predicting = false; + private bool predicting = false; private readonly List touchingActors = new List(); - public int currentInputFrame; - private int currentInputFrame2; + //public int currentInputFrame; + //private int currentInputFrame2; private Float3 currentVelocity; public PlayerInput input; @@ -92,7 +93,7 @@ namespace Game private bool jumped; - private int lastInputFrame; + //private int lastInputFrame; private float lastJumped = -1f; private float lastLanded = -1f; private int numJumps; @@ -105,7 +106,7 @@ namespace Game private Actor rootActor; private float startupTime; - private Float3 viewAngles; + public Float3 viewAngles; private Float3 viewAnglesLastFrame; [Limit(0, 9000)] @@ -114,7 +115,7 @@ namespace Game private static Float3 Gravity { get; } = new Float3(0, -800.0f, 0f); - //private Float3 safePosition; + //private Float3 safePosition;444 rg [NetworkReplicated] public uint PlayerId = 0; @@ -173,7 +174,7 @@ namespace Game Assert.IsTrue(playerId != uint.MaxValue); PlayerId = playerId; - if (PlayerId == NetworkManager.LocalClientId)//if (NetworkReplicator.GetObjectRole(this.Parent) == NetworkObjectRole.OwnedAuthoritative)// if (playerId == NetworkManager.LocalPlayerClientId) + if (PlayerId == NetworkManager.LocalPlayerClientId)//if (NetworkReplicator.GetObjectRole(this.Parent) == NetworkObjectRole.OwnedAuthoritative)// if (playerId == NetworkManager.LocalPlayerClientId) { Console.Print("local player?: " + playerId.ToString()); string demoPath = System.IO.Path.Combine(AssetManager.DemoPath, $"{DateTimeOffset.Now.UtcTicks}.gdem"); @@ -290,24 +291,26 @@ namespace Game viewPitch = viewPitch, viewRoll = viewRoll });*/ - currentInputFrame2++; + //currentInputFrame2++; } + private ulong lastPredictedFrame = 0; public override void OnFixedUpdate() { - if (input is PlayerInputDemo) - input.OnUpdate(); + PlayerInputDemo demoInput = input as PlayerInputDemo; + if (demoInput != null) + demoInput.OnUpdate(); - float deltadif = Time.DeltaTime - 1.0f / Time.PhysicsFPS; - //if (Math.Abs(deltadif) > 0.0001f) - // Console.Print("drift: " + deltadif); + float timeDeltaDiff = Time.DeltaTime - 1.0f / Time.PhysicsFPS; + if (Math.Abs(timeDeltaDiff) > 0.0001f) + Console.Print("Time.DeltaTime is not stable: " + timeDeltaDiff); input.OnFixedUpdate(); PlayerInputState inputState = input.GetCurrentInputState(); - if (input is PlayerInputDemo) + if (demoInput != null && demoInput.IsPlaying) { - ApplyInputToCamera(inputState, false); + ApplyInputToCamera(inputState, true); // Verify view angles first if (demoDeltasVerify) @@ -316,43 +319,75 @@ namespace Game float viewAnglesDelta = (viewAngles - verifAngles).Length; if (viewAnglesDelta > 0.00001) { - Console.PrintError($"Demo verification failed, view angles delta: {viewAnglesDelta}, viewAngles:{viewAngles}, verif:{verifAngles}"); + Console.Print($"Demo verification failed, view angles delta: {viewAnglesDelta}, viewAngles:{viewAngles}, verif:{verifAngles}"); if (demoDeltasCorrect) SetCameraEulerAngles(verifAngles, false); } } - } - SimulatePlayerMovement(inputState); + SimulatePlayerMovement(inputState); - if (input is PlayerInputDemo && demoDeltasVerify) - { - // verify - float positionDelta = (Actor.Position - inputState.verificationPosition).Length; - if (positionDelta > 0.00001) - Console.PrintError("Demo verification failed, position delta: " + positionDelta); - - float velocityDelta = (currentVelocity - inputState.verificationVelocity).Length; - if (velocityDelta > 0.00001) - Console.PrintError("Demo verification failed, velocity delta: " + velocityDelta); - - float orientationDelta = (rootActor.Orientation - inputState.verificationOrientation).Length; - if (orientationDelta > 0.00001) + if (demoDeltasVerify) { - Console.PrintError("Demo verification failed, orientation delta: " + orientationDelta); - if (demoDeltasCorrect) + // verify + float positionDelta = (Actor.Position - inputState.verificationPosition).Length; + if (positionDelta > 0.00001) + Console.Print("Demo verification failed, position delta: " + positionDelta); + + float velocityDelta = (currentVelocity - inputState.verificationVelocity).Length; + if (velocityDelta > 0.00001) + Console.Print("Demo verification failed, velocity delta: " + velocityDelta); + + float orientationDelta = (rootActor.Orientation - inputState.verificationOrientation).Length; + if (orientationDelta > 0.00001) { + Console.Print("Demo verification failed, orientation delta: " + orientationDelta); + if (demoDeltasCorrect) + { + } } + + + //if (currentInputFrame == 0) + /*{ + //Console.Print("repos: " + inputState.verificationPosition); + Actor.Position = inputState.verificationPosition; + currentVelocity = inputState.verificationVelocity; + rootActor.Orientation = inputState.verificationOrientation; + }*/ + } + } + else + { + if (input.Predict && GameModeManager.ClientFrame > 0 && GameModeManager.ServerFrame > 0) + { + predicting = true; + for (ulong currentFrame = GameModeManager.ServerFrame; currentFrame < GameModeManager.ClientFrame - 1; currentFrame++) + { + if (!input.GetState(currentFrame, out var pastInputState, out var pastActorState)) + { + Console.Print($"predict failure: {currentFrame}"); + break; + } + + if (currentFrame == GameModeManager.ServerFrame) + { + Actor.Position = pastActorState.position; + currentVelocity = pastActorState.velocity; + rootActor.Orientation = pastActorState.orientation; + viewAngles = new Float3(pastActorState.viewAngles.Y, pastActorState.viewAngles.X, pastActorState.viewAngles.Z); + } + SimulatePlayerMovement(pastInputState); + + //Console.Print($"predicted: {currentFrame}"); + } + predicting = false; + //Console.Print($"current: {inputState.frame}"); + //if (GameModeManager.ClientFrame - GameModeManager.ServerFrame > 0) + // Console.Print($"current diff: {GameModeManager.ClientFrame - GameModeManager.ServerFrame}"); } - - //if (currentInputFrame == 0) - /*{ - //Console.Print("repos: " + inputState.verificationPosition); - Actor.Position = inputState.verificationPosition; - currentVelocity = inputState.verificationVelocity; - rootActor.Orientation = inputState.verificationOrientation; - }*/ + SimulatePlayerMovement(inputState); } input.RecordCurrentActorState(new PlayerActorState @@ -365,8 +400,8 @@ namespace Game input.OnEndFrame(); - lastInputFrame = currentInputFrame; - currentInputFrame++; + //lastInputFrame = currentInputFrame; + //currentInputFrame++; viewAnglesLastFrame = viewAngles; } diff --git a/Source/GameTarget.Build.cs b/Source/GameTarget.Build.cs index 377050a..9d18ffd 100644 --- a/Source/GameTarget.Build.cs +++ b/Source/GameTarget.Build.cs @@ -28,6 +28,13 @@ public class GameTarget : GameProjectTarget Modules.Add("Game"); } + public override void SetupTargetEnvironment(BuildOptions options) + { + base.SetupTargetEnvironment(options); + + options.LinkEnv.UseFastPDBLinking = true; + } + public override string GetOutputFilePath(BuildOptions options, TargetOutputType? outputType = null) { if (!Environment.CommandLine.Contains("Cooker")) // Hacky way to detect if this is run during cooking