From 7228c51dd76dc25471a8cdb573c0d52642ed297d Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 6 Apr 2023 21:12:04 +0300 Subject: [PATCH] networking progress --- Assets/Maps/aerowalk.map | 6 + Content/Common/PlayerPrefab.prefab | 13 - Content/GameSettings.json | 2 +- ...6e605d9928cc985c1b6_ 0_ 0_Heightfield.flax | Bin 1036884 -> 0 bytes ...6b6e605d9928cc985c1b6_ 0_ 0_Heightmap.flax | Bin 1398627 -> 0 bytes Content/Scenes/MainScene.scene | 6 +- .../EngineSettings/BuildSettings.json | 4 +- .../{ => TexturesUnused}/sky/sky.flax | Bin 4195334 -> 4195343 bytes Content/Textures/dev/dev_128_black.flax | Bin 1399070 -> 175727 bytes Content/Textures/dev/dev_128_gray.flax | Bin 700045 -> 700026 bytes Content/Textures/dev/dev_128_gray_norm.flax | Bin 1399107 -> 1399089 bytes Source/Game.Gen.cpp | 2 +- Source/Game/Console/Console.cs | 56 ++-- Source/Game/Console/ConsoleContentTextBox.cs | 27 +- Source/Game/Console/ConsolePlugin.cs | 23 +- Source/Game/Console/ConsoleScript.cs | 12 +- Source/Game/Console/EngineSubsystem.cs | 38 ++- Source/Game/Game.Build.cs | 4 +- Source/Game/GameMode/GameModeManager_old.cs | 247 +++++++++++++----- .../{GameMode.cs => GameMode_highlevel.cs} | 44 +++- Source/Game/GameMode/NetworkManager.cs | 150 +++++++++++ Source/Game/GameMode/NetworkManager_Client.cs | 128 +++++++++ Source/Game/GameMode/NetworkManager_Server.cs | 196 ++++++++++++++ Source/Game/GameMode/NetworkWorld.cs | 50 ++++ Source/Game/Level/Q3MapImporter.cs | 2 +- Source/Game/Player/PlayerActor.cs | 52 ++-- Source/Game/Player/PlayerInput.cs | 36 +++ Source/Game/Player/PlayerInputDemo.cs | 51 ++-- Source/Game/Player/PlayerInputLocal.cs | 61 ++--- Source/Game/Player/PlayerInputNetwork.cs | 7 + Source/Game/Player/PlayerMovement.cs | 121 ++++++--- Source/GameTarget.Build.cs | 7 + 32 files changed, 1084 insertions(+), 261 deletions(-) delete mode 100644 Content/SceneData/MainScene/Terrain/92de37984e46b6e605d9928cc985c1b6_ 0_ 0_Heightfield.flax delete mode 100644 Content/SceneData/MainScene/Terrain/92de37984e46b6e605d9928cc985c1b6_ 0_ 0_Heightmap.flax rename Content/Textures/{ => TexturesUnused}/sky/sky.flax (99%) rename Source/Game/GameMode/{GameMode.cs => GameMode_highlevel.cs} (84%) create mode 100644 Source/Game/GameMode/NetworkManager.cs create mode 100644 Source/Game/GameMode/NetworkManager_Client.cs create mode 100644 Source/Game/GameMode/NetworkManager_Server.cs create mode 100644 Source/Game/GameMode/NetworkWorld.cs 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 dc491ce6be1df9707bc2a457a87b2604b9f20358..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1036884 zcmeIuK`R7d7y#f;T(*>he=uk54{#t2gII~gNln_=+Fe>BO0xMI%F)%;jh`UDML8|L zvAa1qa*&>Pdf)lx{pOo_d(vz>tAy_{5?c?Kx39Iy@ztA^z3NPJcRvfwkcYFd5ZYls zG-9oXYS@Uq(=Z=v5pyma$EzOKzaNkBljU5`v0Ob(eE#6C%~;M?#`_PVJb!opd|i$A z^JKYobl54@^Wq?xPLip?^VV^_)6QG<*jqfzR~tp+q?TNiC;QQzUEA3lS<05Oa-v== zWrl7q?dMxMcrl6&XC8J!81BBMW3gFz8U5O_B0zuu0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N c0t5&UAV7cs0RjXF5FkK+009C72>b(q4@Yu-yZ`_I 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 0e8d175a3745e3888936c7f4c625d28f940f8ea7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1398627 zcmeIwF>4e-6ae6fM#0WPJF8Q$i6IFJno2#r5HTSn5hJ3qUM9)GB;<3Je$g!_~n%D z9#2!Z9rKg))ZK{rN%}u`zDZffCpZ(OH^*#Z=c0fA!*(rlQ?t40(VF{ewqA+n$;~v; zlwN=OSiFkByVml4IjrinqTXvQwpufj_a~!V4T}97v87@0xL5baE3vrwri{>hWR33C z{bJNw+$s8_Jkwgu2f5m)t6c1##@fUEvdW`Ks;hD2EDyGGnPV8#?Y&|!$o-%DN9#-X zPd6^*L9U8Auk^~5qCDN->G#TKr%CI1)O#DnObS?wKjrEm-|LrqMSB?46uZA!UC%qY z%7bpC&CTA5!{x(zT;-)|I4)a@H{z5V-J;)|cXn>3wNe~z9RKk4{GEkZiuqP7%`MzK zd5Ov9FoO*55dcRby#(KB0wO(Uw<5b)n#NDCbA}JvS!6Aa6ZVH0^7z(-{ z!B64jhj4JL|1>T5!tcCK&Uwh~{k8sp$9G>z!DNC%C>iWnGw0cjNA8P4d^E0w`Xq;S zp?=6=U8p)atP7>vt-A1~-KvY1fB8GiPdV-DFcthd(!`3Q&Xm}i#?hSL!t1lo_my#U zQE^=F_{=*uEWeIixU;1uS9-%UHoG){usYb!3pm26EWM W7Phg2UF^ZaKJqv~0mYgl?b;vXzjAf} delta 482 zcmYk!IZFdU7{>8;cjB3N-^Oz_8jVLZo@-137NQ{78w)|fMwAdztVIMt?G!})2G#*( zYQgUyr1C2WHiCuzH*3Iw-@MPvJk0ggnR$Vm=e$+<+7zj^sKW)wHW?$>MBzTe+o`p2baI@@ZtrCeE!$eN)9L+#+|!mBG|nA$=_msx-0VXIDp7@M)SwoA z1W<>1G@ubpXhsWK(S~+(pc7r_Mi4#dMIZVxfI(Oo!Z1P@!6?EQ!#Ej5M|0x< diff --git a/Content/Textures/dev/dev_128_black.flax b/Content/Textures/dev/dev_128_black.flax index ee85e60ab7bf77d215733ddcdc34477378d8ec1b..de17f559cbcebd32e220f627298c305f6b34b07f 100644 GIT binary patch literal 175727 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 BezgDq literal 1399070 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 Q12G2>b8hcp;(E6l0Mf!V9{>OV diff --git a/Content/Textures/dev/dev_128_gray_norm.flax b/Content/Textures/dev/dev_128_gray_norm.flax index 2b0fe5afed62d99114b37b0b9b0afdf6967e4534..74555b5e7b028fa121cdf8931b5ebe0be12d5ddf 100644 GIT binary patch delta 250 zcmX@SDRkqe&B_VfdGhLILHj5S)gnYAk7G6i!v}cfSC*joq=qKa)yJ!KsH1< z!@+PM8={=yV1y`4IRj&)C`>tnAQD>*k2*eGr- K$1;gN3j+Ymo-8{6 delta 268 zcmdnEDfIBB&tnG7{Su!rpAm$jYcX8K|*Z$uqYgzo;Z2u_QyuO3B$O zCMG4dOy4~}F+0^QC$U1m*_)}|n~4#KK{PWEvj8zG5VHX>I}mdKF((jn0Wmia^8hg~ c5c2^sKM)H5u^ 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