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