Files
GoakeFlax/Source/Game/GameMode/World.cs
2025-03-28 15:24:44 +02:00

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;
}
}*/