this repo has no description
at master 407 lines 18 kB view raw
1using System.Drawing; 2using System.Numerics; 3using Arch.Core.Extensions; 4using GlmSharp; 5using Kestrel.Framework.Client.Graphics; 6using Kestrel.Framework.Client.Graphics.Buffers; 7using Kestrel.Framework.Client.Graphics.Shaders; 8using Kestrel.Framework.Entity.Components; 9using Kestrel.Framework.Networking; 10using Kestrel.Framework.Networking.Packets; 11using Kestrel.Framework.Networking.Packets.C2S; 12using Kestrel.Framework.Networking.Packets.S2C; 13using Kestrel.Framework.Utils; 14using LiteNetLib; 15using LiteNetLib.Utils; 16using Silk.NET.Core.Contexts; 17using Silk.NET.GLFW; 18using Silk.NET.Input; 19using Silk.NET.Maths; 20using Silk.NET.OpenGL; 21using Silk.NET.Windowing; 22using ArchEntity = Arch.Core.Entity; 23using ArchWorld = Arch.Core.World; 24 25namespace Kestrel.Framework.Platform; 26 27public class Client 28{ 29#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. 30 private IWindow _window; 31 private IInputContext _input; 32 public ClientState clientState; 33 private GL _gl; 34 private ShaderProgram _shaderProgram; 35 private QuadMesh quad; 36 NetManager networkClient; 37#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. 38 private DateTime lastRequestedChunks = DateTime.Now; 39 40 public void Run() 41 { 42 WindowOptions options = WindowOptions.Default with 43 { 44 Size = new Vector2D<int>(800, 600), 45 Title = "My first Silk.NET application!", 46 }; 47 48 _window = Silk.NET.Windowing.Window.Create(options); 49 _window.Load += OnLoad; 50 _window.Update += OnUpdate; 51 _window.Render += OnRender; 52 _window.Run(); 53 } 54 55 private unsafe void OnLoad() 56 { 57 _input = _window.CreateInput(); 58 for (int i = 0; i < _input.Keyboards.Count; i++) 59 _input.Keyboards[i].KeyDown += KeyDown; 60 61 _gl = _window.CreateOpenGL(); 62 _gl.ClearColor(Color.FromArgb(1, 121, 184)); 63 _gl.Viewport(0, 0, (uint)_window.Size.X, (uint)_window.Size.Y); 64 65 if (Environment.GetCommandLineArgs().Length != 2) 66 { 67 Console.WriteLine("No name provided"); 68 return; 69 } 70 71 clientState = new(_gl, _window) 72 { 73 PlayerName = Environment.GetCommandLineArgs()[1], 74 World = new(), 75 Entities = ArchWorld.Create() 76 }; 77 78 for (int i = 0; i < _input.Keyboards.Count; i++) 79 { 80 _input.Mice[i].Cursor.CursorMode = CursorMode.Raw; 81 _input.Mice[i].MouseMove += clientState.Camera.OnMouseMove; 82 } 83 84 VertexShader vs = new(_gl, "./shaders/simple.vs"); 85 FragmentShader fs = new(_gl, "./shaders/simple.fs"); 86 87 _shaderProgram = new(_gl, vs, fs); 88 89 quad = new QuadMesh(clientState, _shaderProgram); 90 91 _gl.Enable(EnableCap.Blend); 92 _gl.Enable(EnableCap.DepthTest); 93 _gl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); 94 // _gl.PolygonMode(GLEnum.FrontAndBack, GLEnum.Line); 95 // clientState.Window.GL.Enable(GLEnum.CullFace); 96 // clientState.Window.GL.CullFace(GLEnum.Back); 97 98 // Tell GL which triangle winding is considered "front" 99 // clientState.Window.GL.FrontFace(GLEnum.Ccw); 100 101 // Networking 102 103 ComponentRegistry.RegisterComponents(); 104 105 EventBasedNetListener listener = new(); 106 networkClient = new(listener); 107 networkClient.Start(); 108 networkClient.Connect("localhost" /* host IP or name */, 9050 /* port */, "SomeConnectionKey" /* text key or NetDataWriter */); 109 listener.NetworkReceiveEvent += (server, dataReader, deliveryMethod, channel) => 110 { 111 var packetId = (Packet)dataReader.GetByte(); 112 Console.WriteLine("Recieved network packet: {0}", packetId.ToString()); 113 switch (packetId) 114 { 115 case Packet.S2CPlayerLoginSuccess: 116 { 117 var packet = new S2CPlayerLoginSuccess(); 118 packet.Deserialize(dataReader); 119 120 Console.WriteLine("Found {0} Entities", packet.EntityCount); 121 122 bool foundPlayer = false; 123 foreach (var entity in packet.Entities) 124 { 125 Console.WriteLine("Parsing Entity"); 126 ArchEntity archEntity = clientState.Entities.Create(new ServerId(entity.Key)); 127 128 // entity.Key is the server ID so we add it to the dictionary 129 clientState.ServerIdToEntity.TryAdd(entity.Key, archEntity); 130 131 foreach (var component in entity.Value) 132 { 133 Console.WriteLine("Component Type: {0} {1}", component.PacketId, component.GetType()); 134 if (component is Player player && player.Name == clientState.PlayerName) 135 { 136 clientState.Player = archEntity; 137 foundPlayer = true; 138 } 139 switch (component) 140 { 141 case Player p: clientState.Entities.Add(archEntity, p); break; 142 case Location l: clientState.Entities.Add(archEntity, l); break; 143 case Nametag n: clientState.Entities.Add(archEntity, n); break; 144 case Velocity v: clientState.Entities.Add(archEntity, v); break; 145 default: 146 Console.WriteLine($"Unknown component {component.GetType().Name}"); 147 break; 148 } 149 } 150 } 151 152 if (!foundPlayer) 153 { 154 Console.WriteLine("No player was sent from the server, exiting."); 155 Environment.Exit(0); 156 return; 157 } 158 159 Console.WriteLine("Player has {0} components.", clientState.Entities.GetAllComponents(clientState.Player).Length); 160 161 clientState.status = ClientStatus.Connected; 162 163 Location position = clientState.Player.Get<Location>(); 164 165 clientState.World.WorldToChunk((int)position.X, (int)position.Y, (int)position.Z, out var chunkPos, out _); 166 position.LastFrameChunkPos = chunkPos; 167 168 foreach (var (x, y, z) in LocationUtil.CoordsNearestFirst(clientState.RenderDistance, chunkPos.X, chunkPos.Y, chunkPos.Z)) 169 { 170 clientState.RequestChunk(new(x, y, z)); 171 } 172 } 173 break; 174 case Packet.S2CBroadcastEntitySpawn: 175 { 176 var packet = new S2CBroadcastEntitySpawn(); 177 packet.Deserialize(dataReader); 178 179 var playerServerId = clientState.Player.Get<ServerId>().Id; 180 if (packet.ServerId == playerServerId) 181 { 182 Console.WriteLine("Ignoring player spawn"); 183 return; 184 } 185 186 if (!clientState.ServerIdToEntity.ContainsKey(packet.ServerId)) 187 { 188 Console.WriteLine("adding entity"); 189 ArchEntity archEntity = clientState.Entities.Create(new ServerId(packet.ServerId)); 190 191 // entity.Key is the server ID so we add it to the dictionary 192 clientState.ServerIdToEntity.TryAdd(packet.ServerId, archEntity); 193 194 foreach (var component in packet.Components) 195 { 196 // Should be unreachable but you never know 197 if (component is Player player && player.Name == clientState.PlayerName) 198 { 199 clientState.Player = archEntity; 200 } 201 202 switch (component) 203 { 204 case Location location: clientState.Entities.Add(archEntity, location); break; 205 case Nametag nametag: clientState.Entities.Add(archEntity, nametag); break; 206 case Player playerC: clientState.Entities.Add(archEntity, playerC); break; 207 case Velocity velocity: clientState.Entities.Add(archEntity, velocity); break; 208 case Physics physics: clientState.Entities.Add(archEntity, physics); break; 209 case Collider collider: clientState.Entities.Add(archEntity, collider); break; 210 } 211 } 212 } 213 else 214 { 215 Console.WriteLine("Entity already exists"); 216 // If the entity already exists we just ignore it for now we might want to change this later 217 // to check for new components etc etc 218 } 219 } 220 break; 221 case Packet.S2CBroadcastEntityMove: 222 { 223 var packet = new S2CBroadcastEntityMove(); 224 packet.Deserialize(dataReader); 225 226 // Don't update the position of the player, might want to change this later but yeah 227 var playerServerId = clientState.Player.Get<ServerId>().Id; 228 if (packet.ServerId == playerServerId) 229 { 230 return; 231 } 232 233 ArchEntity entity = clientState.ServerIdToEntity[packet.ServerId]; 234 entity.Get<Location>().Position = new Vector3(packet.Position.X, packet.Position.Y, packet.Position.Z); 235 } 236 break; 237 case Packet.S2CChunkResponse: 238 { 239 var packet = new S2CChunkResponse(); 240 packet.Deserialize(dataReader); 241 242 foreach (var packetChunk in packet.Chunks) 243 { 244 var chunk = new World.Chunk(clientState.World, packetChunk.ChunkX, packetChunk.ChunkY, packetChunk.ChunkZ) { Blocks = packetChunk.Blocks, IsEmpty = packetChunk.IsEmpty }; 245 clientState.World.SetChunk(packetChunk.ChunkX, packetChunk.ChunkY, packetChunk.ChunkZ, chunk); 246 247 var key = new Vector3I(packetChunk.ChunkX, packetChunk.ChunkY, packetChunk.ChunkZ); 248 clientState.ChunkMeshes.Remove(key, out var _); 249 250 var mesh = new ChunkMesh(clientState, chunk); 251 clientState.ChunkMeshManager.QueueGeneration(mesh); 252 clientState.ChunkMeshes.TryAdd(key, mesh); 253 254 if (clientState.ChunkMeshes.TryGetValue(new Vector3I(packetChunk.ChunkX, packetChunk.ChunkY + 1, packetChunk.ChunkZ), out var topMesh)) clientState.ChunkMeshManager.QueueGeneration(topMesh); 255 if (clientState.ChunkMeshes.TryGetValue(new Vector3I(packetChunk.ChunkX, packetChunk.ChunkY - 1, packetChunk.ChunkZ), out var bottomMesh)) clientState.ChunkMeshManager.QueueGeneration(bottomMesh); 256 if (clientState.ChunkMeshes.TryGetValue(new Vector3I(packetChunk.ChunkX, packetChunk.ChunkY, packetChunk.ChunkZ + 1), out var northMesh)) clientState.ChunkMeshManager.QueueGeneration(northMesh); 257 if (clientState.ChunkMeshes.TryGetValue(new Vector3I(packetChunk.ChunkX, packetChunk.ChunkY, packetChunk.ChunkZ - 1), out var southMesh)) clientState.ChunkMeshManager.QueueGeneration(southMesh); 258 if (clientState.ChunkMeshes.TryGetValue(new Vector3I(packetChunk.ChunkX - 1, packetChunk.ChunkY, packetChunk.ChunkZ), out var westMesh)) clientState.ChunkMeshManager.QueueGeneration(westMesh); 259 if (clientState.ChunkMeshes.TryGetValue(new Vector3I(packetChunk.ChunkX + 1, packetChunk.ChunkY, packetChunk.ChunkZ), out var eastMesh)) clientState.ChunkMeshManager.QueueGeneration(eastMesh); 260 } 261 } 262 break; 263 } 264 265 dataReader.Recycle(); 266 }; 267 268 listener.PeerConnectedEvent += peer => 269 { 270 Console.WriteLine("Connected to server!"); 271 clientState.NetServer = peer; 272 273 C2SPlayerLoginRequest loginRequest = new(clientState.PlayerName); 274 275 clientState.NetServer.Send(IPacket.Serialize(loginRequest), DeliveryMethod.ReliableOrdered); 276 }; 277 } 278 279 private void OnUpdate(double deltaTime) 280 { 281 clientState.Profiler.Tick += 1; 282 283 networkClient.PollEvents(); 284 285 if (clientState.status != ClientStatus.Connected) 286 return; 287 288 var _keyboard = _input.Keyboards[0]; 289 float cameraSpeed = 150.0f * (float)deltaTime; 290 291 clientState.Entities.Query(new Arch.Core.QueryDescription().WithAll<Location, Player>(), (ArchEntity entity, ref Location location, ref Player player) => 292 { 293 if (player.Name != clientState.PlayerName) 294 return; 295 296 bool playerMoved = false; 297 298 var actualCameraSpeed = cameraSpeed; 299 if (_keyboard.IsKeyPressed(Key.ShiftLeft)) 300 actualCameraSpeed *= 0.2f; 301 302 if (_keyboard.IsKeyPressed(Key.W)) 303 { 304 playerMoved = true; 305 location.Position += actualCameraSpeed * clientState.Camera.front.ToVector3(); 306 } 307 if (_keyboard.IsKeyPressed(Key.S)) 308 { 309 playerMoved = true; 310 location.Position -= actualCameraSpeed * clientState.Camera.front.ToVector3(); 311 } 312 if (_keyboard.IsKeyPressed(Key.A)) 313 { 314 playerMoved = true; 315 316 location.Position -= glm.Normalized(glm.Cross(clientState.Camera.front, clientState.Camera.up)).ToVector3() * actualCameraSpeed; 317 } 318 if (_keyboard.IsKeyPressed(Key.D)) 319 { 320 location.Position += glm.Normalized(glm.Cross(clientState.Camera.front, clientState.Camera.up)).ToVector3() * actualCameraSpeed; 321 playerMoved = true; 322 } 323 324 clientState.World.WorldToChunk((int)location.X, (int)location.Y, (int)location.Z, out var chunkPos, out _); 325 bool hasMovedBetweenChunks = !location.LastFrameChunkPos.Equals(chunkPos); 326 327 if (playerMoved && clientState.NetServer != null && hasMovedBetweenChunks) 328 { 329 clientState.Profiler.Start("Requested chunks distance culling", () => 330 { 331 List<Vector3I> _requestedChunksCache = [.. clientState.RequestedChunksQueue]; 332 foreach (var targetChunk in _requestedChunksCache) 333 { 334 bool condition = LocationUtil.Distance(chunkPos.ToVector3(), targetChunk.ToVector3()) > clientState.RenderDistance * 1.5; 335 if (condition) 336 { 337 clientState.RequestedChunks.Remove(targetChunk); 338 clientState.RequestedChunksQueue.Remove(targetChunk); 339 } 340 } 341 }); 342 343 344 // List<KeyValuePair<Vector3I, ChunkMesh>> _chunkMeshes = clientState.ChunkMeshes.ToList(); 345 // foreach (var targetChunk in _chunkMeshes) 346 // { 347 // bool condition = LocationUtil.Distance(chunkPos.ToVector3(), targetChunk.Key.ToVector3()) > clientState.RenderDistance * 1.5; 348 // if (condition) 349 // { 350 // clientState.ChunkMeshes.Remove(targetChunk.Key); 351 // } 352 // } 353 354 location.LastFrameChunkPos = chunkPos; 355 356 foreach (var (x, y, z) in LocationUtil.CoordsNearestFirst(clientState.RenderDistance, chunkPos.X, chunkPos.Y, chunkPos.Z)) 357 { 358 clientState.RequestChunk(new(x, y, z)); 359 } 360 361 Console.WriteLine("Sending player move packet"); 362 clientState.NetServer.Send(IPacket.Serialize(new C2SPlayerMove(location.Position)), DeliveryMethod.ReliableUnordered); 363 } 364 }); 365 366 clientState.Profiler.Start("Request chunks from queue", () => 367 { 368 TimeSpan elapsed = DateTime.Now - lastRequestedChunks; 369 if (elapsed.TotalMilliseconds > 500) 370 { 371 lastRequestedChunks = DateTime.Now; 372 clientState.RequestChunksFromQueue(); 373 } 374 }); 375 376 clientState.Profiler.Start("Generate chunks meshes under limit", () => 377 { 378 clientState.ChunkMeshManager.GenerateFromQueueUnderTimeLimit(8); 379 }); 380 } 381 382 private unsafe void OnRender(double deltaTime) 383 { 384 _gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 385 386 if (clientState.status != ClientStatus.Connected) 387 return; 388 389 clientState.Profiler.Start("Rendering", () => 390 { 391 quad.Draw(); 392 }); 393 } 394 395 private void KeyDown(IKeyboard keyboard, Key key, int keyCode) 396 { 397 if (key == Key.Escape) 398 { 399 networkClient.Stop(); 400 _window.Close(); 401 clientState.Profiler.Build(); 402 } 403 404 if (key == Key.F11) 405 _window.WindowState = WindowState.Maximized; 406 } 407}