Files
GoakeFlax/Source/Game/GameMode/World.cs

774 lines
29 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 PlayerFrame
{
public ulong frame => inputState.Frame;
public Float3 position;
public PlayerInputState2 inputState;
public PlayerMovementState movementState;
}
public interface IClientInfo
{
public NetworkConnection Connection { get; set; }
public PlayerActor PlayerActor { get; set; }
public PlayerFrame[] FrameHistory { get; set; }
}
public class World
{
public bool IsServer => this is ServerWorld;
public bool IsClient => this is ClientWorld;
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;
public static void InitClient()
{
WorldStore.ClientWorld = new ClientWorld();
WorldStore.ClientWorld.Init();
}
public static void InitServer()
{
WorldStore.ServerWorld = new ServerWorld();
WorldStore.ServerWorld.Init();
Scripting.Update += WorldStore.ServerWorld.OnUpdate;
Scripting.LateUpdate += WorldStore.ServerWorld.OnUpdateLate;
Scripting.FixedUpdate += WorldStore.ServerWorld.OnFixedUpdate;
}
public static void CleanupClient()
{
WorldStore.ClientWorld?.Cleanup();
WorldStore.ClientWorld = null;
}
public static void CleanupServer()
{
WorldStore.ServerWorld?.Cleanup();
WorldStore.ServerWorld = null;
}
virtual protected void Init()
{
Scripting.LateFixedUpdate += OnLateUpdate;
}
virtual protected void Cleanup()
{
//Level.SceneLoaded -= OnLevelLoaded;
//Scripting.LateUpdate -= OnLateUpdatePre;
Scripting.Update -= OnUpdate;
Scripting.LateUpdate -= OnUpdateLate;
Scripting.FixedUpdate -= OnFixedUpdate;
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;
}
}
void OnUpdate()
{
//Console.Print("server Update");
}
void OnUpdateLate()
{
//Console.Print("server LateUpdate");
}
void OnFixedUpdate()
{
//Console.Print("server FixedUpdate");
}
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;
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);
}
virtual protected void OnLateUpdate()
{
Frame++;
}
virtual protected IClientInfo GetClient(uint playerId) => null;
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);
IClientInfo player = GetClient(playerId);
player.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;
virtual public ulong GetLastProcessedFrame(uint playerId) => 0;
virtual public ulong GetLastReceivedFrame(uint playerId) => 0;
public PlayerFrame GetPlayerFrame(uint playerId, ulong frame)
{
IClientInfo player = GetClient(playerId);
PlayerFrame playerFrame = player.FrameHistory[frame % 120];
if (playerFrame.inputState.Frame == frame || playerFrame.movementState.frame == frame)
return playerFrame;
return null;
}
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;
}
virtual public void UpdatePlayerInputState(uint playerId, ulong frame, PlayerInputState2 inputState, PlayerMovementState movementState)
{
IClientInfo player = GetClient(playerId);
PlayerFrame playerFrame = player.FrameHistory[frame % 120];
/*if (playerFrame.frame == frame)
{
var old = playerFrame.inputState with { Frame = inputState.Frame };
playerFrame.inputState = inputState with
{
ViewDelta = playerFrame.inputState.ViewDelta + inputState.ViewDelta,
MoveForward = MathF.MaxMagnitude(playerFrame.inputState.MoveForward, inputState.MoveForward),
MoveRight = MathF.MaxMagnitude(playerFrame.inputState.MoveRight, inputState.MoveRight),
Attack = playerFrame.inputState.Attack || inputState.Attack,
Jump = playerFrame.inputState.Jump || inputState.Jump,
};
if (old != playerFrame.inputState)
old = old;
}
else*/
playerFrame.inputState = inputState;
playerFrame.inputState.Frame = frame;
//playerFrame.movementState = movementState;
//playerFrame.frame = frame;
}
virtual public void UpdatePlayerState(uint playerId, ulong frame, PlayerMovementState movementState)
{
IClientInfo player = GetClient(playerId);
PlayerFrame playerFrame = player.FrameHistory[frame % 120];
playerFrame.movementState = movementState;
playerFrame.movementState.frame = frame;
}
}
file class ServerWorld : World
{
protected class ClientInfo : IClientInfo
{
public NetworkConnection Connection { get; set; }
public PlayerActor PlayerActor { get; set; }
public PlayerFrame[] FrameHistory { get; set; } = new PlayerFrame[120];
public ulong LastReceivedFrame = 0;
public ulong LastSentDeltaFrame = 0; // TODO: Accumulate deltas since this frame
public ulong LastProcessedFrame = 0; // In case player is lagged behind, these frames should be caught up during next simulation
public int SendRate = 0; // How many updates the client wants to receive per second
public ClientInfo()
{
for (int i = 0; i < FrameHistory.Length; i++)
{
FrameHistory[i] = new PlayerFrame();
FrameHistory[i].inputState.Frame = ulong.MaxValue;
FrameHistory[i].movementState.frame = ulong.MaxValue;
}
}
}
protected Dictionary<uint, ClientInfo> _clients = new();
protected override IClientInfo GetClient(uint playerId) => _clients[playerId];
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();
}
protected override void OnLateUpdate()
{
//Console.Print("server LateFixedUpdate");
base.OnLateUpdate();
foreach (var client in _clients.Values)
{
// We assume all the scripts have processed the frames during this cycle
client.LastProcessedFrame = client.LastReceivedFrame;
}
}
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
{
break;
}
case GameModeMessageType2.PlayerPosition: // FIXME: Should be the delta frame
{
//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;
}
case GameModeMessageType2.PlayerInput:
{
PlayerInputState2 inputState; //?
ulong receivedFrame = inputState.Frame = networkEvent.Message.ReadUInt64();
uint playerId = networkEvent.Message.ReadUInt32();
float time = networkEvent.Message.ReadSingle();
float delay = Time.TimeSinceStartup - time;
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();
//Console.Print("recv: " + receivedFrame.ToString());
ClientInfo player = GetClient(playerId) as ClientInfo;
if (receivedFrame <= player.LastReceivedFrame)
{
if (receivedFrame < player.LastProcessedFrame)
{
Console.PrintWarning($"Dropping late frame from client: {receivedFrame}, last processed: {player.LastProcessedFrame}");
break;
}
else
{
//Console.PrintWarning($"Out-of-order frame received from client: {receivedFrame}, latest: {player.LastReceivedFrame}");
}
}
player.LastReceivedFrame = Math.Max(receivedFrame, player.LastReceivedFrame);
//if (player.LastProcessedFrame == 0)
// player.LastProcessedFrame = receivedFrame - 1;
//Console.Print($"server receive client frame {receivedFrame}, current server frame {Frame}");
var asdf = receivedFrame;
//receivedFrame = Frame; // TODO: receive all frames from players, simulate all unprocessed frames in next simulation cycle?
if (inputState.MoveForward != 0)
receivedFrame = receivedFrame;
//Console.Print($"delay: {delay * 1000}ms");
// Sanity check
if (GetPlayerInputState(playerId, receivedFrame, out var prevInputState))
{
Console.PrintWarning($"Duplicate frame received from client: {asdf}, delay: {delay*1000}ms");
//break;
}
UpdatePlayerInputState(playerId, receivedFrame, inputState, default);
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 void OnClientConnected(NetworkConnection connection)
{
uint playerId = connection.ConnectionId;
_clients.Add(playerId, new ClientInfo() { Connection = connection });
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);
}
}
public override ulong GetLastProcessedFrame(uint playerId)
{
ClientInfo player = GetClient(playerId) as ClientInfo;
return player.LastProcessedFrame;
}
public override ulong GetLastReceivedFrame(uint playerId)
{
ClientInfo player = GetClient(playerId) as ClientInfo;
return player.LastReceivedFrame;
}
public override void UpdatePlayerInputState(uint playerId, ulong frame, PlayerInputState2 inputState, PlayerMovementState movementState)
{
base.UpdatePlayerInputState(playerId, frame, inputState, movementState);
//ClientInfo player = GetClient(playerId) as ClientInfo;
//player.LastReceivedFrame = frame; // Dropped frames should be ignored?
}
protected override PlayerActor SpawnPlayer(uint playerId, Float3 position, Vector3 eulerAngles)
{
var playerActor = base.SpawnPlayer(playerId, position, eulerAngles);
playerActor.Input = new PlayerInputNetwork2(playerId, this);
return playerActor;
}
}
file class ClientWorld : World
{
protected class ClientInfo : IClientInfo
{
public NetworkConnection Connection { get; set; }
public PlayerActor PlayerActor { get; set; }
public PlayerFrame[] FrameHistory { get; set; } = new PlayerFrame[120];
public ClientInfo()
{
for (int i = 0; i < FrameHistory.Length; i++)
FrameHistory[i] = new PlayerFrame();
}
}
private uint _localPlayerId;
protected Dictionary<uint, ClientInfo> _clients = new();
protected override IClientInfo GetClient(uint playerId) => _clients[playerId];
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();
}
override protected void OnLateUpdate()
{
foreach (var player in _scene.GetChildren<PlayerActor>())
{
if (player.PlayerId != _localPlayerId)
continue;
ClientInfo playerClient = GetClient(player.PlayerId) as ClientInfo;
PlayerInputState2 inputState = player.Input.GetState();
PlayerMovementState movementState = player.GetScript<PlayerMovement>().movementState;
if (inputState.Frame == 0)
inputState = inputState;
/*public Float2 ViewDelta;
public float MoveForward;
public float MoveRight;
public bool Attack;
public bool Jump;*/
//Console.Print($"client send {Frame}");
UpdatePlayerInputState(player.PlayerId, Frame, inputState, movementState);
//Console.Print($"cframe: {Frame}");
NetworkMessage message = NetworkManager.ClientBeginSendMessage();
message.WriteByte((byte)GameModeMessageType2.PlayerInput);
message.WriteUInt64(Frame);
message.WriteUInt32(player.PlayerId);
message.WriteSingle(Time.TimeSinceStartup);
message.WriteVector2(inputState.ViewDelta);
message.WriteSingle(inputState.MoveForward);
message.WriteSingle(inputState.MoveRight);
message.WriteBoolean(inputState.Attack);
message.WriteBoolean(inputState.Jump);
NetworkManager.ClientEndSendMessage(ref message);
//Console.Print("send: " + Frame.ToString());
}
base.OnLateUpdate();
}
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 override ulong GetLastProcessedFrame(uint playerId)
{
//ClientInfo player = GetClient(playerId) as ClientInfo;
return 0;
}
public override ulong GetLastReceivedFrame(uint playerId)
{
//ClientInfo player = GetClient(playerId) as ClientInfo;
return 0;
}
public void OnClientConnected(NetworkConnection connection)
{
uint playerId = connection.ConnectionId;
_clients.Add(playerId, new ClientInfo() { Connection = connection });
_localPlayerId = playerId;
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;
}
}*/