diff --git a/Blocktest/BlocktestGame.cs b/Blocktest/BlocktestGame.cs index e37455a..7bd63b9 100644 --- a/Blocktest/BlocktestGame.cs +++ b/Blocktest/BlocktestGame.cs @@ -24,7 +24,7 @@ public BlocktestGame(string? newIp = null) { } public static ContentManager? ContentManager { get; private set; } - + public void SetScene(IScene newScene) { _currentScene?.EndScene(); _currentScene = newScene; @@ -40,8 +40,8 @@ protected override void Initialize() { protected override void LoadContent() { ContentManager = Content; BlockSpritesManager.LoadBlockSprites(); - - if(_ip != null) { + + if (_ip != null) { SetScene(new GameScene(this, true, _ip)); return; } diff --git a/Blocktest/Code/Block System/BlockSprites.cs b/Blocktest/Code/Block System/BlockSprites.cs index 868fafe..210eb24 100644 --- a/Blocktest/Code/Block System/BlockSprites.cs +++ b/Blocktest/Code/Block System/BlockSprites.cs @@ -20,7 +20,7 @@ public sealed class BlockSprites { public BlockSprites(BlockShared newBlockShared) { _blockShared = newBlockShared; SpriteSheet = SpriteSheet.ErrorSpriteSheet; - + string path = @"Graphics\Blocks\" + _blockShared.BlockName.ToLower().Replace(" ", ""); try { BlockSprite = diff --git a/Blocktest/Code/Block System/BlockSpritesManager.cs b/Blocktest/Code/Block System/BlockSpritesManager.cs index 15c94a1..54ec4c9 100644 --- a/Blocktest/Code/Block System/BlockSpritesManager.cs +++ b/Blocktest/Code/Block System/BlockSpritesManager.cs @@ -7,6 +7,7 @@ public sealed class BlockSpritesManager { public static Dictionary AllBlocksSprites { get; private set; } public static void LoadBlockSprites() { - AllBlocksSprites = BlockManagerShared.AllBlocks.ToDictionary(uid => uid.Key, block => new BlockSprites(block.Value)); + AllBlocksSprites = + BlockManagerShared.AllBlocks.ToDictionary(uid => uid.Key, block => new BlockSprites(block.Value)); } } \ No newline at end of file diff --git a/Blocktest/Code/Block System/RenderableTile.cs b/Blocktest/Code/Block System/RenderableTile.cs index f7e14f2..bc84d7d 100644 --- a/Blocktest/Code/Block System/RenderableTile.cs +++ b/Blocktest/Code/Block System/RenderableTile.cs @@ -5,15 +5,6 @@ namespace Blocktest.Block_System; public class RenderableTile : TileShared { public readonly Renderable Renderable; - - [Flags] - private enum DirectionalBitmask { - None = 0, - Up = 1, - Down = 2, - Right = 4, - Left = 8 - } public RenderableTile(TileShared tile, bool background) : base(tile.SourceBlock, tile.Transform.Position / GlobalsShared.GridSize) { @@ -32,7 +23,7 @@ public void UpdateAdjacencies(Vector2Int position, TilemapShared tilemap) { return; } // If the tile doesn't smooth, don't even try - var dirBitmask = DirectionalBitmask.None; + DirectionalBitmask dirBitmask = DirectionalBitmask.None; if (HasSmoothableTile(position + Vector2Int.Up, tilemap)) { dirBitmask |= DirectionalBitmask.Up; @@ -48,7 +39,7 @@ public void UpdateAdjacencies(Vector2Int position, TilemapShared tilemap) { } Renderable.Appearance = BlockSpritesManager.AllBlocksSprites[SourceBlock.BlockUid].SpriteSheet - .OrderedSprites[(int) dirBitmask]; + .OrderedSprites[(int)dirBitmask]; } /// @@ -61,7 +52,8 @@ private bool HasSmoothableTile(Vector2Int position, TilemapShared tilemap) { if (tilemap.TryGetTile(position, out TileShared? tile)) { return SourceBlock.SmoothSelf ? IsSameTileType(tile) - : tile.SourceBlock.BlockUid != "air"; // Don't smooth with air, possibly find nicer way to do this later. + : tile.SourceBlock.BlockUid != + "air"; // Don't smooth with air, possibly find nicer way to do this later. } return false; } @@ -72,4 +64,13 @@ private bool HasSmoothableTile(Vector2Int position, TilemapShared tilemap) { /// The other tile to check. /// Whether or not the other block is the same type as the current tile private bool IsSameTileType(TileShared otherTile) => otherTile.SourceBlock == SourceBlock; + + [Flags] + private enum DirectionalBitmask { + None = 0, + Up = 1, + Down = 2, + Right = 4, + Left = 8 + } } \ No newline at end of file diff --git a/Blocktest/Code/Block System/RenderableTilemap.cs b/Blocktest/Code/Block System/RenderableTilemap.cs index 66f070f..bbf8b87 100644 --- a/Blocktest/Code/Block System/RenderableTilemap.cs +++ b/Blocktest/Code/Block System/RenderableTilemap.cs @@ -4,17 +4,17 @@ namespace Blocktest.Block_System; public class RenderableTilemap { - private readonly TilemapShared _tilemap; - - private readonly RenderableTile[,] _renderables; - private readonly Camera _camera; - /// /// A list of s that specify which blocks should be refreshed when a tile is placed/destroyed. /// Defaults to the changed block and all cardinal directions. /// private static readonly List Adjacencies = new() - { Vector2Int.Zero, Vector2Int.Up, Vector2Int.Down, Vector2Int.Left, Vector2Int.Right }; + { Vector2Int.Up, Vector2Int.Down, Vector2Int.Left, Vector2Int.Right }; + + private readonly Camera _camera; + + private readonly RenderableTile[,] _renderables; + private readonly TilemapShared _tilemap; public RenderableTilemap(TilemapShared newTilemap, Camera camera) { @@ -28,7 +28,7 @@ public RenderableTilemap(TilemapShared newTilemap, Camera camera) { private void OnTilemapChanged(TileShared tile, Vector2Int location) { _camera.RenderedComponents.Remove(_renderables[location.X, location.Y].Renderable); - + foreach (Vector2Int dir in Adjacencies) { if (!_tilemap.TryGetTile(location + dir, out TileShared? adjacentTile)) { continue; @@ -36,7 +36,7 @@ private void OnTilemapChanged(TileShared tile, Vector2Int location) { _renderables[location.X + dir.X, location.Y + dir.Y].UpdateAdjacencies(location + dir, _tilemap); } - RenderableTile newTile = new RenderableTile(tile, _tilemap.Background); + RenderableTile newTile = new(tile, _tilemap.Background); _renderables[location.X, location.Y] = newTile; newTile.UpdateAdjacencies(location, _tilemap); _camera.RenderedComponents.Add(newTile.Renderable); @@ -49,7 +49,7 @@ private void UpdateRenderables() { if (!_tilemap.TryGetTile(new Vector2Int(x, y), out TileShared? tile)) { continue; } - RenderableTile newTile = new RenderableTile(tile, _tilemap.Background); + RenderableTile newTile = new(tile, _tilemap.Background); _renderables[x, y] = newTile; _camera.RenderedComponents.Add(newTile.Renderable); newTile.UpdateAdjacencies(new Vector2Int(x, y), _tilemap); diff --git a/Blocktest/Code/Networking/Client.cs b/Blocktest/Code/Networking/Client.cs index 8c32727..abb0631 100644 --- a/Blocktest/Code/Networking/Client.cs +++ b/Blocktest/Code/Networking/Client.cs @@ -1,22 +1,35 @@ +using Blocktest.Rendering; using LiteNetLib; using LiteNetLib.Utils; +using Shared.Code; +using Shared.Code.Components; using Shared.Code.Networking; using Shared.Code.Packets; namespace Blocktest.Networking; public sealed class Client { + private readonly Camera _camera; private readonly EventBasedNetListener _listener; private readonly NetManager _manager; - public NetPeer? Server { get; private set; } - public TickBuffer ClientTickBuffer = new(0); + private readonly Dictionary _playerRenderables = new(); + + private readonly WorldState _worldState; + public TickBuffer ClientTickBuffer; - public Client() { + public Client(WorldState worldState, Camera camera) { + _worldState = worldState; + _camera = camera; + ClientTickBuffer = new TickBuffer(0, _worldState); _listener = new EventBasedNetListener(); _manager = new NetManager(_listener); _listener.NetworkReceiveEvent += NetworkReceiveEvent; + _listener.PeerConnectedEvent += PeerConnected; + _listener.PeerDisconnectedEvent += PeerDisconnected; _manager.Start(); } + public NetPeer? Server { get; private set; } + public void Start(string ip, int port, string key) { _manager.Connect(ip, port, key); } @@ -29,6 +42,20 @@ public void Update() { _manager.PollEvents(); } + private void PeerConnected(NetPeer peer) { + Console.WriteLine("Connected to server"); + + Transform newTransform = new(new Vector2Int(256, 128)); + Renderable newRenderable = new(newTransform, Layer.Player, Drawable.ErrorDrawable, Color.Orange); + _worldState.PlayerPositions.Add(peer.RemoteId, newTransform); + _playerRenderables.Add(peer.RemoteId, newRenderable); + _camera.RenderedComponents.Add(newRenderable); + } + + private void PeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) { + Console.WriteLine("Disconnected from server"); + } + /// /// Recieve network events from LiteNetLib /// @@ -40,15 +67,24 @@ private void NetworkReceiveEvent(NetPeer peer, NetPacketReader packetReader, byt DeliveryMethod deliveryMethod) { if (Server == null) { Server = peer; - WorldDownload worldPacket = new(); - byte packetByte = packetReader.GetByte(); - PacketType packetType = (PacketType)packetByte; + + PacketType packetType = (PacketType)packetReader.GetByte(); if (packetType != PacketType.WorldDownload) { + Console.WriteLine("Bad packet!!!"); return; } - worldPacket.Deserialize(packetReader); - ClientTickBuffer = new TickBuffer(worldPacket.TickNum); - ClientTickBuffer.AddPacket(worldPacket); + + int sourceId = packetReader.GetInt(); + + WorldDownload worldPacket = HandlePacket(packetReader, sourceId); + worldPacket.Process(_worldState); + ClientTickBuffer = new TickBuffer(worldPacket.TickNum, _worldState); + + SendPacket(new PeerEvent { + SourceId = Server.RemoteId, + TickNum = worldPacket.TickNum, + Type = PeerEvent.PeerEventType.PlayerList + }); } else { HandlePackets(packetReader); } @@ -58,45 +94,76 @@ private void NetworkReceiveEvent(NetPeer peer, NetPacketReader packetReader, byt /// Handles packets after the first. /// /// Contains the packet sent by the server. - private void HandlePackets(NetPacketReader packetReader) { + /// ACCEPTS PacketType:SourceID:TickNum:Packet + private void HandlePackets(NetDataReader packetReader) { byte packetByte = packetReader.GetByte(); PacketType packetType = (PacketType)packetByte; + + int sourceId = packetReader.GetInt(); + switch (packetType) { case PacketType.TileChange: - HandleTileChange(packetReader); + ClientTickBuffer.AddPacket(HandlePacket(packetReader, sourceId)); break; case PacketType.BreakTile: - HandleBreakTile(packetReader); + ClientTickBuffer.AddPacket(HandlePacket(packetReader, sourceId)); + break; + case PacketType.MovePlayer: + ClientTickBuffer.AddPacket(HandlePacket(packetReader, sourceId)); + break; + case PacketType.PlayerList: + ClientTickBuffer.AddPacket(HandlePacket(packetReader, sourceId)); break; + case PacketType.PeerEvent: + PeerEvent eventPacket = new() { SourceId = sourceId, TickNum = packetReader.GetUShort() }; + eventPacket.Deserialize(packetReader); + HandleEvent(eventPacket); + break; + case PacketType.WorldDownload: default: Console.WriteLine("Bad packet!!!"); break; } } - private void HandleTileChange(NetPacketReader packetReader) { - TileChange tileChange = new(); - tileChange.Deserialize(packetReader); - ClientTickBuffer.AddPacket(tileChange); + private T HandlePacket(NetDataReader packetReader, int sourceId) where T : IPacket, new() { + T packet = new() { SourceId = sourceId, TickNum = packetReader.GetUShort() }; + packet.Deserialize(packetReader); + return packet; } - private void HandleBreakTile(NetPacketReader packetReader) { - BreakTile breakTile = new(); - breakTile.Deserialize(packetReader); - ClientTickBuffer.AddPacket(breakTile); - } + private void HandleEvent(PeerEvent peerEvent) { + switch (peerEvent.Type) { + case PeerEvent.PeerEventType.PeerDisconnect: + Console.WriteLine("Player disconnected"); - public void SendTileChange(TileChange tileChange) { - NetDataWriter writer = new(); - writer.Put((byte)PacketType.TileChange); - writer.Put(tileChange); - Server?.Send(writer, DeliveryMethod.ReliableUnordered); + _worldState.PlayerPositions.Remove(peerEvent.SourceId); + _camera.RenderedComponents.Remove(_playerRenderables[peerEvent.SourceId]); + _playerRenderables.Remove(peerEvent.SourceId); + break; + case PeerEvent.PeerEventType.PeerConnect: + Console.WriteLine("New player connected"); + + Transform newTransform = new(new Vector2Int(256, 128)); + Renderable newRenderable = new(newTransform, Layer.Player, Drawable.ErrorDrawable, Color.Orange); + _worldState.PlayerPositions.Add(peerEvent.SourceId, newTransform); + _playerRenderables.Add(peerEvent.SourceId, newRenderable); + _camera.RenderedComponents.Add(newRenderable); + break; + case PeerEvent.PeerEventType.PlayerList: + default: + throw new ArgumentOutOfRangeException(nameof(peerEvent), peerEvent, null); + } } - public void SendBreakTile(BreakTile breakTile) { + // SENDS PacketType:TickNum:Packet + public void SendPacket(IPacket packet, DeliveryMethod deliveryMethod = DeliveryMethod.ReliableUnordered) { NetDataWriter writer = new(); - writer.Put((byte)PacketType.BreakTile); - writer.Put(breakTile); - Server?.Send(writer, DeliveryMethod.ReliableUnordered); + + writer.Put((byte)packet.GetPacketType()); + writer.Put(packet.TickNum); + //ID is not needed for client to server packets + writer.Put(packet); + Server?.Send(writer, deliveryMethod); } } \ No newline at end of file diff --git a/Blocktest/Code/Rendering/Camera.cs b/Blocktest/Code/Rendering/Camera.cs index 958170f..b52beb9 100644 --- a/Blocktest/Code/Rendering/Camera.cs +++ b/Blocktest/Code/Rendering/Camera.cs @@ -1,15 +1,16 @@ namespace Blocktest.Rendering; public sealed class Camera { + private static readonly int EnumCount = Enum.GetValues(typeof(Layer)).Length; private readonly Color _backgroundColor; private readonly Vector2 _size; public readonly List RenderedComponents = new(); - - public Rectangle RenderLocation; public readonly RenderTarget2D RenderTarget; public Vector2 Position; + public Rectangle RenderLocation; + public Camera(Vector2 position, Vector2 size, GraphicsDevice graphicsDevice, Color? backgroundColor = null) { Position = position; _size = size; @@ -56,8 +57,6 @@ public void Draw(GraphicsDevice graphics, SpriteBatch spriteBatch) { graphics.SetRenderTarget(null); } - private static readonly int EnumCount = Enum.GetValues(typeof(Layer)).Length; - public Vector2 CameraToWorldPos(Vector2 mouseState) => new( (mouseState.X - RenderLocation.X) / RenderLocation.Width * RenderTarget.Width + Position.X, Position.Y + RenderTarget.Height - diff --git a/Blocktest/Code/Rendering/Drawable.cs b/Blocktest/Code/Rendering/Drawable.cs index 9c1bccc..7e29967 100644 --- a/Blocktest/Code/Rendering/Drawable.cs +++ b/Blocktest/Code/Rendering/Drawable.cs @@ -10,6 +10,6 @@ public Drawable(string fileName, Rectangle? bounds = null) { throw new Exception($"Could not load drawable {fileName}, content manager not initialized."); Bounds = bounds ?? Texture.Bounds; } - + public static Drawable ErrorDrawable { get; } = new(@"Graphics\Blocks\error"); } \ No newline at end of file diff --git a/Blocktest/Code/Rendering/Renderable.cs b/Blocktest/Code/Rendering/Renderable.cs index 223eca6..232d81a 100644 --- a/Blocktest/Code/Rendering/Renderable.cs +++ b/Blocktest/Code/Rendering/Renderable.cs @@ -12,8 +12,8 @@ public enum Layer { public sealed class Renderable { public readonly Transform Transform; public Drawable? Appearance; - public Color RenderColor; public Layer Layer; + public Color RenderColor; public Renderable(Transform transform, Layer layer = Layer.Default, Drawable? appearance = null, Color? renderColor = null) { diff --git a/Blocktest/Code/Rendering/SpriteSheet.cs b/Blocktest/Code/Rendering/SpriteSheet.cs index 5177e7c..563d19c 100644 --- a/Blocktest/Code/Rendering/SpriteSheet.cs +++ b/Blocktest/Code/Rendering/SpriteSheet.cs @@ -22,6 +22,6 @@ public SpriteSheet(string filename, int frameColumns, int frameRows, int padding OrderedSprites[frameColumn + frameRow * frameColumns] = newDrawable; } } - + public static SpriteSheet ErrorSpriteSheet { get; } = new(@"Graphics\Blocks\error", 1, 1); } \ No newline at end of file diff --git a/Blocktest/Code/Scenes/GameScene.cs b/Blocktest/Code/Scenes/GameScene.cs index 215cbf2..71462cc 100644 --- a/Blocktest/Code/Scenes/GameScene.cs +++ b/Blocktest/Code/Scenes/GameScene.cs @@ -10,23 +10,24 @@ namespace Blocktest.Scenes; public sealed class GameScene : IScene { + private readonly RenderableTilemap _backgroundTilemapSprites; + + private readonly Camera _camera; private readonly bool _connect; + private readonly RenderableTilemap _foregroundTilemapSprites; private readonly FrameCounter _frameCounter = new(); private readonly BlocktestGame _game; private readonly Client _networkingClient; private readonly SpriteBatch _spriteBatch; + + private readonly WorldState _worldState = new(); private int _blockSelected = 1; //ID of the block to place - private string[] _blockStrings; + private readonly string[] _blockStrings; - private readonly RenderableTilemap _backgroundTilemapSprites; - private readonly RenderableTilemap _foregroundTilemapSprites; - private bool _buildMode = true; //true for build, false for destroy private KeyboardState _previousKeyboardState; - private readonly Camera _camera; - public GameScene(BlocktestGame game, bool doConnect, string? ip) { _connect = doConnect; _spriteBatch = new SpriteBatch(game.GraphicsDevice); @@ -34,42 +35,69 @@ public GameScene(BlocktestGame game, bool doConnect, string? ip) { _camera = new Camera(Vector2.Zero, new Vector2(512, 256), game.GraphicsDevice); - GlobalsShared.BackgroundTilemap = new TilemapShared(GlobalsShared.MaxX, GlobalsShared.MaxY, true); - GlobalsShared.ForegroundTilemap = new TilemapShared(GlobalsShared.MaxX, GlobalsShared.MaxY, false); - _backgroundTilemapSprites = new RenderableTilemap(GlobalsShared.BackgroundTilemap, _camera); - _foregroundTilemapSprites = new RenderableTilemap(GlobalsShared.ForegroundTilemap, _camera); - _networkingClient = new Client(); - + _backgroundTilemapSprites = new RenderableTilemap(_worldState.Foreground, _camera); + _foregroundTilemapSprites = new RenderableTilemap(_worldState.Background, _camera); + _networkingClient = new Client(_worldState, _camera); + _blockStrings = BlockManagerShared.AllBlocks.Keys.ToArray(); - + if (_connect && ip != null) { _networkingClient.Start(ip, 9050, "testKey"); return; } - var testDownload = WorldDownload.Default(); - testDownload.Process(); + WorldDownload testDownload = WorldDownload.Default(); + testDownload.Process(_worldState); } public void Update(GameTime gameTime) { - MouseState currentMouseState = Mouse.GetState(); - KeyboardState currentKeyboardState = Keyboard.GetState(); - - if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || - currentKeyboardState.IsKeyDown(Keys.Escape)) { - _game.Exit(); - } - if (_connect) { _networkingClient.Update(); } - - _networkingClient.ClientTickBuffer.IncrCurrTick(); + HandleInput(); + + _networkingClient.ClientTickBuffer.IncrCurrTick(_worldState); + } + + public void Draw(GameTime gameTime, GraphicsDevice graphicsDevice) { + graphicsDevice.Clear(Color.CornflowerBlue); + _camera.Draw(graphicsDevice, _spriteBatch); + + const bool pixelPerfect = false; + + Rectangle destinationRectangle = pixelPerfect ? GetPixelPerfectRect() : GetFitRect(); + _camera.RenderLocation = destinationRectangle; + + graphicsDevice.Clear(Color.Black); + + _spriteBatch.Begin(samplerState: pixelPerfect ? SamplerState.PointClamp : null); + _spriteBatch.Draw(_camera.RenderTarget, destinationRectangle, Color.White); + + float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds; + + _frameCounter.Update(deltaTime); + + _spriteBatch.End(); + } + + public void EndScene() { + _networkingClient.Stop(); + } + + public void HandleInput() { if (!_game.IsActive) { return; } + MouseState currentMouseState = Mouse.GetState(); + KeyboardState currentKeyboardState = Keyboard.GetState(); + + if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || + currentKeyboardState.IsKeyDown(Keys.Escape)) { + _game.Exit(); + } + //press E to toggle build/destroy if (currentKeyboardState.IsKeyDown(Keys.E) && _previousKeyboardState.IsKeyUp(Keys.E)) { @@ -90,18 +118,33 @@ public void Update(GameTime gameTime) { moveValue *= 4; } + Vector2 moveVector = Vector2.Zero; if (currentKeyboardState.IsKeyDown(Keys.A)) { - _camera.Position.X -= moveValue; + moveVector.X -= moveValue; } else if (currentKeyboardState.IsKeyDown(Keys.D)) { - _camera.Position.X += moveValue; + moveVector.X += moveValue; } if (currentKeyboardState.IsKeyDown(Keys.W)) { - _camera.Position.Y += moveValue; + moveVector.Y += moveValue; } else if (currentKeyboardState.IsKeyDown(Keys.S)) { - _camera.Position.Y -= moveValue; + moveVector.Y -= moveValue; } - + + if (moveVector != Vector2.Zero) { + _camera.Position += moveVector; + + MovePlayer movementPacket = new() { + TickNum = _networkingClient.ClientTickBuffer.CurrTick, + Position = (Vector2Int)_camera.Position, + SourceId = _networkingClient.Server?.RemoteId ?? 0 + }; + _networkingClient.ClientTickBuffer.AddPacket(movementPacket); + if (_connect) { + _networkingClient.SendPacket(movementPacket); + } + } + _previousKeyboardState = currentKeyboardState; if (currentMouseState.LeftButton != ButtonState.Pressed && @@ -112,7 +155,7 @@ public void Update(GameTime gameTime) { bool foreground = currentMouseState.LeftButton == ButtonState.Pressed; Vector2 mousePos = _camera.CameraToWorldPos(currentMouseState.Position.ToVector2()); - Vector2Int tilePos = new Vector2Int( + Vector2Int tilePos = new( Math.Clamp((int)mousePos.X / GlobalsShared.GridSize.X, 0, GlobalsShared.MaxX), Math.Clamp((int)mousePos.Y / GlobalsShared.GridSize.Y, 0, GlobalsShared.MaxY)); @@ -121,49 +164,29 @@ public void Update(GameTime gameTime) { TickNum = _networkingClient.ClientTickBuffer.CurrTick, Position = tilePos, Foreground = foreground, - BlockUid = _blockStrings[_blockSelected] + BlockUid = _blockStrings[_blockSelected], + SourceId = _networkingClient.Server?.RemoteId ?? 0 }; _networkingClient.ClientTickBuffer.AddPacket(testChange); if (_connect) { - _networkingClient.SendTileChange(testChange); + _networkingClient.SendPacket(testChange); } } else { BreakTile testBreak = new() { TickNum = _networkingClient.ClientTickBuffer.CurrTick, Position = tilePos, - Foreground = foreground + Foreground = foreground, + SourceId = _networkingClient.Server?.RemoteId ?? 0 }; _networkingClient.ClientTickBuffer.AddPacket(testBreak); if (_connect) { - _networkingClient.SendBreakTile(testBreak); + _networkingClient.SendPacket(testBreak); } } } - public void Draw(GameTime gameTime, GraphicsDevice graphicsDevice) { - graphicsDevice.Clear(Color.CornflowerBlue); - - _camera.Draw(graphicsDevice, _spriteBatch); - - const bool pixelPerfect = false; - - Rectangle destinationRectangle = pixelPerfect ? GetPixelPerfectRect() : GetFitRect(); - _camera.RenderLocation = destinationRectangle; - - graphicsDevice.Clear(Color.Black); - - _spriteBatch.Begin(samplerState: pixelPerfect ? SamplerState.PointClamp : null); - _spriteBatch.Draw(_camera.RenderTarget, destinationRectangle, Color.White); - - float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds; - - _frameCounter.Update(deltaTime); - - _spriteBatch.End(); - } - private Rectangle GetPixelPerfectRect() { int multiplier = int.Min(_game.GraphicsDevice.Viewport.Height / _camera.RenderTarget.Height, _game.GraphicsDevice.Viewport.Width / _camera.RenderTarget.Width); @@ -195,8 +218,4 @@ private Rectangle GetFitRect() { return new Rectangle(x, y, width, height); } - - public void EndScene() { - _networkingClient.Stop(); - } } \ No newline at end of file diff --git a/Blocktest/Code/Scenes/MainMenuScene.cs b/Blocktest/Code/Scenes/MainMenuScene.cs index a4ac5ed..c0e4a1b 100644 --- a/Blocktest/Code/Scenes/MainMenuScene.cs +++ b/Blocktest/Code/Scenes/MainMenuScene.cs @@ -1,16 +1,13 @@ using Blocktest.UI; -using Myra; using Myra.Graphics2D; using Myra.Graphics2D.Brushes; using Myra.Graphics2D.UI; - -namespace Blocktest.Scenes; - +namespace Blocktest.Scenes; public class MainMenuScene : IScene { - private Desktop _desktop; - private VerticalStackPanel _mainMenu; - + private readonly Desktop _desktop; + private readonly VerticalStackPanel _mainMenu; + public MainMenuScene(BlocktestGame game) { _mainMenu = new VerticalStackPanel { HorizontalAlignment = HorizontalAlignment.Center, @@ -19,53 +16,47 @@ public MainMenuScene(BlocktestGame game) { Background = new SolidBrush("#404040FF"), Border = new SolidBrush("#1BA1E2FF") }; - - var titleLabel = new Label { + + Label titleLabel = new() { Text = "Blocktest", HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, Padding = new Thickness(8) }; _mainMenu.Widgets.Add(titleLabel); - - var newGameButton = new TextButton { + + TextButton newGameButton = new() { Text = "New Game", HorizontalAlignment = HorizontalAlignment.Stretch, - Padding = new Thickness(5) - }; - newGameButton.Click += (_, _) => { - game.SetScene(new GameScene(game, false, null)); + Padding = new Thickness(5) }; + newGameButton.Click += (_, _) => { game.SetScene(new GameScene(game, false, null)); }; _mainMenu.Widgets.Add(newGameButton); - - var connectButton = new TextButton { + + TextButton connectButton = new() { Text = "Connect", HorizontalAlignment = HorizontalAlignment.Stretch, Padding = new Thickness(5) }; - connectButton.Click += (_, _) => { - new ConnectionWindow(game).ShowModal(_desktop); - }; + connectButton.Click += (_, _) => { new ConnectionWindow(game).ShowModal(_desktop); }; _mainMenu.Widgets.Add(connectButton); - - var exitButton = new TextButton { + + TextButton exitButton = new() { Text = "Exit", HorizontalAlignment = HorizontalAlignment.Stretch, Padding = new Thickness(5) }; - exitButton.Click += (_, _) => { - game.Exit(); - }; + exitButton.Click += (_, _) => { game.Exit(); }; _mainMenu.Widgets.Add(exitButton); - + _desktop = new Desktop { Root = _mainMenu }; } - + public void Update(GameTime gameTime) { } public void Draw(GameTime gameTime, GraphicsDevice graphicsDevice) { graphicsDevice.Clear(Color.CadetBlue); - + _desktop.Render(); } diff --git a/Blocktest/Code/UI/ConnectionWindow.cs b/Blocktest/Code/UI/ConnectionWindow.cs index d096e9e..6071f9b 100644 --- a/Blocktest/Code/UI/ConnectionWindow.cs +++ b/Blocktest/Code/UI/ConnectionWindow.cs @@ -3,32 +3,32 @@ using Myra.Graphics2D.UI; #pragma warning disable CS0618 // Type or member is obsolete, I don't care about this warning since it works fine -namespace Blocktest.UI; +namespace Blocktest.UI; public class ConnectionWindow : Window { public ConnectionWindow(BlocktestGame game) { - var windowGrid = new Grid { + Grid windowGrid = new() { RowSpacing = 8, ColumnSpacing = 8 }; - - var label1 = new Label { + + Label label1 = new() { Text = "Enter IP:", Padding = new Thickness(5), GridColumn = 0, GridRow = 0 }; windowGrid.Widgets.Add(label1); - - var textBox = new TextBox { + + TextBox textBox = new() { Text = "127.0.0.1", Padding = new Thickness(5), GridColumn = 1, GridRow = 0 }; windowGrid.Widgets.Add(textBox); - - var button = new TextButton { + + TextButton button = new() { Text = "Connect", Padding = new Thickness(5), GridColumn = 0, @@ -36,12 +36,10 @@ public ConnectionWindow(BlocktestGame game) { GridColumnSpan = 2, HorizontalAlignment = HorizontalAlignment.Stretch }; - - button.Click += (_, _) => { - game.SetScene(new GameScene(game, true, textBox.Text)); - }; + + button.Click += (_, _) => { game.SetScene(new GameScene(game, true, textBox.Text)); }; windowGrid.Widgets.Add(button); - + Content = windowGrid; } } \ No newline at end of file diff --git a/Blocktest/Code/UI/DialogWindow.cs b/Blocktest/Code/UI/DialogWindow.cs index 8a1081b..de0dcb0 100644 --- a/Blocktest/Code/UI/DialogWindow.cs +++ b/Blocktest/Code/UI/DialogWindow.cs @@ -1,10 +1,9 @@ using Myra.Graphics2D.UI; - namespace Blocktest.UI; public sealed class DialogueWindow : Window { public DialogueWindow(string title, string text) { - var label1 = new Label { + Label label1 = new() { Text = text }; diff --git a/DedicatedServer/Code/Networking/Server.cs b/DedicatedServer/Code/Networking/Server.cs index 006c3f1..72a9842 100644 --- a/DedicatedServer/Code/Networking/Server.cs +++ b/DedicatedServer/Code/Networking/Server.cs @@ -2,6 +2,7 @@ using LiteNetLib.Utils; using Shared; using Shared.Code; +using Shared.Code.Components; using Shared.Code.Networking; using Shared.Code.Packets; namespace DedicatedServer.Code.Networking; @@ -10,9 +11,12 @@ public sealed class Server { private readonly EventBasedNetListener _listener = new(); private readonly NetManager _manager; private readonly ServerPlayerManager _playerManager; - public readonly TickBuffer ServerTickBuffer = new(0); + private readonly WorldState _worldState; + public readonly TickBuffer ServerTickBuffer; - public Server() { + public Server(WorldState worldState) { + _worldState = worldState; + ServerTickBuffer = new TickBuffer(0, _worldState); _manager = new NetManager(_listener); _playerManager = new ServerPlayerManager(GlobalsShared.MaxPlayers); @@ -46,14 +50,20 @@ private void NewConnection(ConnectionRequest request) { private void NewPeer(NetPeer peer) { Console.WriteLine("New Peer"); _playerManager.AddPlayer(peer); - NetDataWriter writer = new(); + _worldState.PlayerPositions.Add(peer.Id, new Transform(Vector2Int.Zero)); + WorldDownload worldDownload = new() { - World = BuildSystem.CurrentWorld, - TickNum = ServerTickBuffer.CurrTick + World = _worldState.CurrentWorld, + TickNum = ServerTickBuffer.CurrTick, + SourceId = -1 }; - writer.Put((byte)PacketType.WorldDownload); - writer.Put(worldDownload); - peer.Send(writer, DeliveryMethod.ReliableOrdered); + SendPacket(worldDownload, peer, DeliveryMethod.ReliableOrdered); + + SendPacketAllPeers(new PeerEvent { + SourceId = peer.Id, + TickNum = ServerTickBuffer.CurrTick, + Type = PeerEvent.PeerEventType.PeerConnect + }, peer); } /// @@ -64,6 +74,13 @@ private void NewPeer(NetPeer peer) { private void PeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) { Console.WriteLine($"Peer Disconnected: {disconnectInfo.Reason}"); _playerManager.RemovePlayer(peer); + _worldState.PlayerPositions.Remove(peer.Id); + + SendPacketAllPeers(new PeerEvent { + SourceId = peer.Id, + TickNum = ServerTickBuffer.CurrTick, + Type = PeerEvent.PeerEventType.PeerDisconnect + }, peer); } /// @@ -73,44 +90,86 @@ private void PeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) { /// Contains the packet from the client. /// /// The delivery method used to deliver this packet. + /// ACCEPTS PacketType:TickNum:Packet private void NetworkReceiveEvent(NetPeer peer, NetPacketReader packetReader, byte channelNumber, DeliveryMethod deliveryMethod) { byte packetByte = packetReader.GetByte(); PacketType packetType = (PacketType)packetByte; switch (packetType) { case PacketType.TileChange: - HandleTileChange(packetReader, peer); + HandlePacket(packetReader, peer); break; case PacketType.BreakTile: - HandleBreakTile(packetReader, peer); + HandlePacket(packetReader, peer); + break; + case PacketType.MovePlayer: + HandlePacket(packetReader, peer); + break; + case PacketType.PeerEvent: + PeerEvent eventPacket = new() { SourceId = peer.Id, TickNum = packetReader.GetUShort() }; + eventPacket.Deserialize(packetReader); + HandleEvent(eventPacket); break; case PacketType.WorldDownload: + case PacketType.PlayerList: default: Console.WriteLine("Bad packet!!!"); break; } } - private void HandleTileChange(NetPacketReader packetReader, NetPeer peer) { - TileChange tileChange = new(); - tileChange.Deserialize(packetReader); - ServerTickBuffer.AddPacket(tileChange); + private void HandlePacket(NetDataReader packetReader, NetPeer source) where T : IPacket, new() { + T packet = new() { SourceId = source.Id, TickNum = packetReader.GetUShort() }; + packet.Deserialize(packetReader); + ServerTickBuffer.AddPacket(packet); + + SendPacketAllPeers(packet, source); + } + + private void HandleEvent(PeerEvent peerEvent) { + switch (peerEvent.Type) { + case PeerEvent.PeerEventType.PlayerList: + Console.WriteLine("PlayerList requested"); + NetPeer? peer = _manager.GetPeerById(peerEvent.SourceId); + foreach (var player in _playerManager.PlayerList) { + if (player.Key == peerEvent.SourceId) { + continue; + } + SendPacket( + new PeerEvent { + SourceId = player.Key, Type = PeerEvent.PeerEventType.PeerConnect, + TickNum = ServerTickBuffer.CurrTick + }, peer); + } + + //SendPacket(new PlayerList { PlayerIds = _playerManager.PlayerList.Keys.ToArray(), TickNum = ServerTickBuffer.CurrTick }, peer); + break; + case PeerEvent.PeerEventType.PeerDisconnect: + case PeerEvent.PeerEventType.PeerConnect: + default: + throw new ArgumentOutOfRangeException(nameof(peerEvent), peerEvent, null); + } + } + // SENDS PacketType:SourceID:TickNum:Packet + private void SendPacketAllPeers(IPacket packet, NetPeer source, + DeliveryMethod deliveryMethod = DeliveryMethod.ReliableUnordered) { NetDataWriter writer = new(); - writer.Put((byte)PacketType.TileChange); - writer.Put(tileChange); - _manager.SendToAll(writer, DeliveryMethod.ReliableUnordered, - peer); // For now, just exclude the one who sent it. + writer.Put((byte)packet.GetPacketType()); + writer.Put(source.Id); + writer.Put(packet.TickNum); + writer.Put(packet); + _manager.SendToAll(writer, deliveryMethod, source); // Send to all clients except the source } - private void HandleBreakTile(NetPacketReader packetReader, NetPeer peer) { - BreakTile breakTile = new(); - breakTile.Deserialize(packetReader); - ServerTickBuffer.AddPacket(breakTile); + // SENDS PacketType:SourceID:TickNum:Packet + private void SendPacket(IPacket packet, NetPeer target, + DeliveryMethod deliveryMethod = DeliveryMethod.ReliableUnordered) { NetDataWriter writer = new(); - writer.Put((byte)PacketType.BreakTile); - writer.Put(breakTile); - _manager.SendToAll(writer, DeliveryMethod.ReliableUnordered, - peer); // For now, just exclude the one who sent it. + writer.Put((byte)packet.GetPacketType()); + writer.Put(packet.SourceId); + writer.Put(packet.TickNum); + writer.Put(packet); + target.Send(writer, deliveryMethod); } } \ No newline at end of file diff --git a/DedicatedServer/Code/Networking/ServerPlayerManager.cs b/DedicatedServer/Code/Networking/ServerPlayerManager.cs index f34686d..5aa19f0 100644 --- a/DedicatedServer/Code/Networking/ServerPlayerManager.cs +++ b/DedicatedServer/Code/Networking/ServerPlayerManager.cs @@ -3,27 +3,27 @@ namespace DedicatedServer.Code.Networking; public sealed class ServerPlayerManager : IEnumerable { - private readonly Dictionary _playerList; + public readonly Dictionary PlayerList; public int PlayerCount; public ServerPlayerManager(int maxPlayers) { - _playerList = new Dictionary(); - _playerList.EnsureCapacity(maxPlayers); + PlayerList = new Dictionary(); + PlayerList.EnsureCapacity(maxPlayers); } public IEnumerator GetEnumerator() { - return _playerList.Select(player => player.Value).GetEnumerator(); + return PlayerList.Select(player => player.Value).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public void AddPlayer(NetPeer newPlayer) { - _playerList.Add(newPlayer.Id, newPlayer); + PlayerList.Add(newPlayer.Id, newPlayer); PlayerCount++; } public void RemovePlayer(NetPeer player) { - _playerList.Remove(player.Id); + PlayerList.Remove(player.Id); PlayerCount--; } } \ No newline at end of file diff --git a/DedicatedServer/Code/WorldHandler.cs b/DedicatedServer/Code/WorldHandler.cs index 137c039..ca01a6d 100644 --- a/DedicatedServer/Code/WorldHandler.cs +++ b/DedicatedServer/Code/WorldHandler.cs @@ -21,15 +21,16 @@ internal sealed class WorldHandler { private TimeSpan _currentTime = TimeSpan.Zero; private long _previousTicks = 0; + private readonly WorldState _worldState; + public WorldHandler() { BlockManagerShared.Initialize(); - GlobalsShared.BackgroundTilemap = new TilemapShared(GlobalsShared.MaxX, GlobalsShared.MaxY, true); - GlobalsShared.ForegroundTilemap = new TilemapShared(GlobalsShared.MaxX, GlobalsShared.MaxY, false); - - var testDownload = WorldDownload.Default(); - testDownload.Process(); + _worldState = new WorldState(); + + WorldDownload testDownload = WorldDownload.Default(); + testDownload.Process(_worldState); - _server = new Server(); + _server = new Server(_worldState); _stopwatch = new Stopwatch(); } @@ -60,7 +61,7 @@ private void Tick(object? state) { previousTicks = currentTicks; counter++;*/ _server.Update(); - _server.ServerTickBuffer.IncrCurrTick(); + _server.ServerTickBuffer.IncrCurrTick(_worldState); } } diff --git a/Shared/Code/Block System/BlockManagerShared.cs b/Shared/Code/Block System/BlockManagerShared.cs index cfc3749..d7473f8 100644 --- a/Shared/Code/Block System/BlockManagerShared.cs +++ b/Shared/Code/Block System/BlockManagerShared.cs @@ -1,12 +1,13 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using YamlDotNet.Serialization; - namespace Shared.Code.Block_System; /// -/// The BlockManager contains all of the block types in a dictionary of blocks indexed by their name +/// The BlockManager contains all of the block types in +/// a dictionary of blocks indexed by their name /// public abstract class BlockManagerShared { /// Array which stores all block instances for referencing as if they were globals. @@ -17,20 +18,20 @@ public abstract class BlockManagerShared { /// Compiles all block subtypes into a dictionary of blocks indexed by their name /// public static void Initialize() { - var deserialize = new DeserializerBuilder().Build(); - var assembly = typeof(BlockManagerShared).Assembly; - var assemblyNames = assembly.GetManifestResourceNames(); + IDeserializer deserialize = new DeserializerBuilder().Build(); + Assembly assembly = typeof(BlockManagerShared).Assembly; + string[] assemblyNames = assembly.GetManifestResourceNames(); var blockNames = assemblyNames.Where(x => x.StartsWith("Shared.Content.Blocks.")); AllBlocks = new Dictionary(); foreach (string resourceName in blockNames) { - using var stream = assembly.GetManifestResourceStream(resourceName); + using Stream? stream = assembly.GetManifestResourceStream(resourceName); if (stream == null) { continue; } using StreamReader reader = new(stream); string yaml = reader.ReadToEnd(); - var block = deserialize.Deserialize(yaml); + BlockShared? block = deserialize.Deserialize(yaml); if (block == null) { continue; } diff --git a/Shared/Code/Block System/BlockShared.cs b/Shared/Code/Block System/BlockShared.cs index 1f12236..50cdea9 100644 --- a/Shared/Code/Block System/BlockShared.cs +++ b/Shared/Code/Block System/BlockShared.cs @@ -5,24 +5,24 @@ namespace Shared.Code.Block_System; /// will be used for every tile of that type. /// public sealed class BlockShared { - /// - /// The block's unique string ID, generated from the name without spaces and turned to lowercase. - /// - public string BlockUid = "error"; - /// The block's name. public string BlockName = "Error"; /// Whether or not the block supports icon smoothing. public bool BlockSmoothing = false; + /// + /// The block's unique string ID, generated from the name without spaces and turned to lowercase. + /// + public string BlockUid = "error"; + /// Whether or not a block can be placed in the background. public bool CanPlaceBackground = true; /// Whether or not a block smooths only with itself /// (Use normal 8x8 sprites to prevent overlap) public bool SmoothSelf = false; - + /* METHODS */ /// diff --git a/Shared/Code/Block System/TileShared.cs b/Shared/Code/Block System/TileShared.cs index 8261a4c..a251370 100644 --- a/Shared/Code/Block System/TileShared.cs +++ b/Shared/Code/Block System/TileShared.cs @@ -9,6 +9,8 @@ namespace Shared.Code.Block_System; /// in the correlating Block classes. /// public class TileShared : INetSerializable { + public readonly Transform Transform; + /// /// Color of the tile. /// @@ -19,8 +21,6 @@ public class TileShared : INetSerializable { /// public BlockShared SourceBlock; - public readonly Transform Transform; - /// /// Creates a . /// diff --git a/Shared/Code/Block System/TilemapShared.cs b/Shared/Code/Block System/TilemapShared.cs index 225979e..e5c8ec4 100644 --- a/Shared/Code/Block System/TilemapShared.cs +++ b/Shared/Code/Block System/TilemapShared.cs @@ -5,24 +5,22 @@ namespace Shared.Code.Block_System; /// A grid filled with s, usually representing terrain. /// public sealed class TilemapShared { + public readonly bool Background; + /// /// The size of each cell (in pixels) in the tilemap's grid. /// public readonly Vector2Int GridSize = new(8, 8); - /// - /// The size of the tilemap in tiles. - /// - public readonly Vector2Int TilemapSize; - /// /// The 2D array of all tiles in the tilemap. /// public readonly TileShared?[,] TileGrid; - public readonly bool Background; - - public event Action? OnTileChanged; + /// + /// The size of the tilemap in tiles. + /// + public readonly Vector2Int TilemapSize; /// @@ -35,31 +33,45 @@ public TilemapShared(int sizeX, int sizeY, bool background) { TileGrid = new TileShared[sizeX, sizeY]; for (int x = 0; x < sizeX; x++) { for (int y = 0; y < sizeY; y++) { - TileGrid[x, y] = new TileShared(BlockManagerShared.AllBlocks["air"], new Vector2Int(x, y)); //Fill with air + TileGrid[x, y] = + new TileShared(BlockManagerShared.AllBlocks["air"], new Vector2Int(x, y)); //Fill with air } } Background = background; } + public event Action? OnTileChanged; + /// /// Sets a Tile at the given XYZ coordinates of a cell in the tile map to a specific type. /// /// Location the new Block will be placed. /// Block type to be placed in the cell. - public TileShared SetBlock(Vector2Int location, BlockShared newBlock) => + public void SetBlock(Vector2Int location, BlockShared newBlock) => SetTile(location, new TileShared(newBlock, location)); + public void SetBlock(Vector2Int location, string blockUid) => + SetTile(location, new TileShared(BlockManagerShared.AllBlocks[blockUid], location)); + /// /// Sets a Tile at the given XYZ coordinates of a cell in the tile map to a specific type. /// /// Location the new Block will be placed. /// Block type to be placed in the cell. - public TileShared SetTile(Vector2Int location, TileShared newTile) { + public void SetTile(Vector2Int location, TileShared newTile) { + if (location.X < 0 || location.Y < 0 || location.X >= TilemapSize.X || location.Y >= TilemapSize.Y) { + return; + } + + if (TryGetTile(location, out TileShared? oldTile)) { + oldTile.SourceBlock.OnBreak(location, Background); + } + TileGrid[location.X, location.Y] = newTile; - OnTileChanged?.Invoke(newTile, location); + newTile.SourceBlock.OnPlace(location, Background); - return newTile; + OnTileChanged?.Invoke(newTile, location); } /// diff --git a/Shared/Code/BuildSystem.cs b/Shared/Code/BuildSystem.cs deleted file mode 100644 index a0102a6..0000000 --- a/Shared/Code/BuildSystem.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Shared.Code.Block_System; -namespace Shared.Code; - -public static class BuildSystem { - /// - /// An array containing an entry for every block in the world. Used for saving games. - /// - public static string[,,] CurrentWorld { get; } = new string[GlobalsShared.MaxX, GlobalsShared.MaxY, 2]; - - /// - /// The method called whenever an object is removed. - /// - /// Whether or not the block to be destroyed is in the foreground. - /// The position of the block to destroy (grid coords) - public static void BreakBlockCell(bool foreground, Vector2Int tilePosition) { - if (tilePosition.X >= GlobalsShared.MaxX || tilePosition.Y >= GlobalsShared.MaxY) { - return; - } - TilemapShared tilemap = foreground ? GlobalsShared.ForegroundTilemap : GlobalsShared.BackgroundTilemap; - - BlockShared toPlace = BlockManagerShared.AllBlocks["air"]; - TileShared newTile = new(toPlace, tilePosition); - - int z = foreground ? 1 : 0; // Convert foreground bool to int - if (tilemap.TryGetTile(tilePosition, out TileShared? tile)) { - tile.SourceBlock.OnBreak(tilePosition, foreground); - } - - tilemap.SetTile(tilePosition, newTile); - CurrentWorld[tilePosition.X, tilePosition.Y, z] = "air"; - } - - /// - /// The method called whenever a block is placed. - /// - /// The block type to place. - /// Whether or not the block should be placed in the foreground. - /// The position of the placed block. (Grid coords) - public static void PlaceBlockCell(BlockShared toPlace, bool foreground, Vector2Int tilePosition) { - if (tilePosition.X >= GlobalsShared.MaxX || tilePosition.Y >= GlobalsShared.MaxY) { - return; - } - TileShared newTile = new(toPlace, tilePosition); // TODO - remove new - toPlace.OnPlace(tilePosition, foreground); - - if (foreground) { - GlobalsShared.ForegroundTilemap.SetTile(tilePosition, newTile); - CurrentWorld[tilePosition.X, tilePosition.Y, 1] = toPlace.BlockUid; - } else if (toPlace.CanPlaceBackground) { - newTile.Color = GlobalsShared.BackgroundColor; - GlobalsShared.BackgroundTilemap.SetTile(tilePosition, newTile); - CurrentWorld[tilePosition.X, tilePosition.Y, 0] = toPlace.BlockUid; - } - } - - /// - /// Loads a new world into the tilemaps and currentWorld. - /// - /// - /// An array containing an entry for every block in the world. Used for loading games. MUST be the - /// same dimensions as currentWorld - /// - public static void LoadNewWorld(string?[,,] newWorld) { - for (int x = 0; x < newWorld.GetLength(0); x++) - for (int y = 0; y < newWorld.GetLength(1); y++) - for (int z = 0; z < 2; z++) { - Vector2Int tilePosition = new(x, y); - string? blockUid = newWorld[x, y, z]; - LoadNewBlock(blockUid, tilePosition, z); - } - } - - /// - /// Loads a new block from an int - /// - /// The blockUid - /// The position of the tile in the tilemap - /// Whether the block is in the foreground, expressed as an int - private static void LoadNewBlock(string? blockUid, Vector2Int tilePosition, int foregroundInt) { - CurrentWorld[tilePosition.X, tilePosition.Y, foregroundInt] = blockUid; - bool foreground = Convert.ToBoolean(foregroundInt); - if (blockUid is not null) { - BlockShared newBlock = BlockManagerShared.AllBlocks[blockUid]; - PlaceBlockCell(newBlock, foreground, tilePosition); - } else { - BreakBlockCell(foreground, tilePosition); - } - } -} \ No newline at end of file diff --git a/Shared/Code/Components/Transform.cs b/Shared/Code/Components/Transform.cs index 28b2cd4..2532605 100644 --- a/Shared/Code/Components/Transform.cs +++ b/Shared/Code/Components/Transform.cs @@ -3,10 +3,10 @@ namespace Shared.Code.Components; public sealed class Transform : INetSerializable { + public Vector2Int Origin; public Vector2Int Position; public float Rotation; public Vector2 Scale; - public Vector2Int Origin; public Transform(Vector2Int position, Vector2? scale = null, Vector2Int? origin = null, float rotation = 0) { Position = position; diff --git a/Shared/Code/Math.cs b/Shared/Code/Math.cs index 9c5e149..b7d2fc3 100644 --- a/Shared/Code/Math.cs +++ b/Shared/Code/Math.cs @@ -86,7 +86,7 @@ public static explicit operator Vector2Int(Vector2 vector2) => public static implicit operator Point(Vector2Int vector2Int) => new(vector2Int.X, vector2Int.Y); public static implicit operator Vector2Int(Point point) => new(point.X, point.Y); - + // Preset values /// Returns a with values (0, 0). public static Vector2Int Zero { get; } = new(0, 0); @@ -101,7 +101,7 @@ public static explicit operator Vector2Int(Vector2 vector2) => public static Vector2Int Down { get; } = new(0, -1); /// Returns a with values (-1, 0). - public static Vector2Int Left { get; } = new(-1, 0); + public static Vector2Int Left { get; } = new(-1, 0); /// Returns a with values (1, 0). public static Vector2Int Right { get; } = new(1, 0); diff --git a/Shared/Code/Networking/Tick.cs b/Shared/Code/Networking/Tick.cs index 2c31be2..2736f6c 100644 --- a/Shared/Code/Networking/Tick.cs +++ b/Shared/Code/Networking/Tick.cs @@ -23,23 +23,23 @@ public Tick(TilemapShared newForeground, TilemapShared newBackground) { /// /// Process all packets taking action on this tick. /// - public void ProcessStartTick() { - Array.Copy(_foreground.TileGrid, GlobalsShared.ForegroundTilemap.TileGrid, - GlobalsShared.MaxX * GlobalsShared.MaxY); - Array.Copy(_background.TileGrid, GlobalsShared.BackgroundTilemap.TileGrid, - GlobalsShared.MaxX * GlobalsShared.MaxY); + public void ProcessStartTick(WorldState worldState) { + //Array.Copy(_foreground.TileGrid, worldState.Foreground.TileGrid, + // GlobalsShared.MaxX * GlobalsShared.MaxY); + //Array.Copy(_background.TileGrid, worldState.Background.TileGrid, + //GlobalsShared.MaxX * GlobalsShared.MaxY); foreach (IPacket packet in Packets) { - packet.Process(); + packet.Process(worldState); } } - public void ProcessTick() { - Array.Copy(GlobalsShared.ForegroundTilemap.TileGrid, _foreground.TileGrid, - GlobalsShared.MaxX * GlobalsShared.MaxY); - Array.Copy(GlobalsShared.BackgroundTilemap.TileGrid, _background.TileGrid, - GlobalsShared.MaxX * GlobalsShared.MaxY); + public void ProcessTick(WorldState worldState) { + //Array.Copy(worldState.Foreground.TileGrid, _foreground.TileGrid, + // GlobalsShared.MaxX * GlobalsShared.MaxY); + //Array.Copy(worldState.Background.TileGrid, _background.TileGrid, + // GlobalsShared.MaxX * GlobalsShared.MaxY); foreach (IPacket packet in Packets) { - packet.Process(); + packet.Process(worldState); } } } \ No newline at end of file diff --git a/Shared/Code/Networking/TickBuffer.cs b/Shared/Code/Networking/TickBuffer.cs index 9207c58..f9e1ad8 100644 --- a/Shared/Code/Networking/TickBuffer.cs +++ b/Shared/Code/Networking/TickBuffer.cs @@ -1,17 +1,15 @@ using Shared.Code.Packets; namespace Shared.Code.Networking; -public class TickBuffer { +public sealed class TickBuffer { private readonly Tick[] _tickBuffer = new Tick[GlobalsShared.MaxTicksStored]; private int _currentDistance; private ushort _currentRecent; public ushort CurrTick; - public TickBuffer(ushort newTick) { + public TickBuffer(ushort newTick, WorldState worldState) { CurrTick = newTick; - for (int i = 0; i < GlobalsShared.MaxTicksStored; i++) { - _tickBuffer[i] = new Tick(GlobalsShared.ForegroundTilemap, GlobalsShared.BackgroundTilemap); - } + Array.Fill(_tickBuffer, new Tick(worldState.Foreground, worldState.Background)); _currentDistance = 0; _currentRecent = CurrTick; } @@ -19,10 +17,10 @@ public TickBuffer(ushort newTick) { /// /// Add additional tick /// - public void IncrCurrTick() { + public void IncrCurrTick(WorldState worldState) { //tickBuffer[currTick].ProcessStartTick(); - ProcessTicks(_currentRecent); - Tick newTick = new(GlobalsShared.ForegroundTilemap, GlobalsShared.BackgroundTilemap); + ProcessTicks(_currentRecent, worldState); + Tick newTick = new(worldState.Foreground, worldState.Foreground); CurrTick++; if (CurrTick == GlobalsShared.MaxTicksStored) { CurrTick = 0; @@ -39,30 +37,29 @@ private bool CheckFurthestTick(ushort newTickNum) { } else { newTickDistance = CurrTick - newTickNum; } - if (newTickDistance > _currentDistance) { - _currentDistance = newTickDistance; - _currentRecent = newTickNum; - return true; + if (newTickDistance <= _currentDistance) { + return false; } - return false; + _currentDistance = newTickDistance; + _currentRecent = newTickNum; + return true; } - public void ProcessTicks(ushort startTick) { + public void ProcessTicks(ushort startTick, WorldState worldState) { Tick tick = _tickBuffer[startTick]; - tick.ProcessStartTick(); + tick.ProcessStartTick(worldState); for (int i = startTick + 1; i != CurrTick + 1; i++) { if (i == GlobalsShared.MaxTicksStored) { i = 0; } tick = _tickBuffer[i]; - tick.ProcessTick(); + tick.ProcessTick(worldState); } } public void AddPacket(IPacket newPacket) { - CheckFurthestTick(newPacket.GetTickNum()); - ushort tickNum = newPacket.GetTickNum(); - _tickBuffer[tickNum] ??= new Tick(GlobalsShared.ForegroundTilemap, GlobalsShared.BackgroundTilemap); + CheckFurthestTick(newPacket.TickNum); + ushort tickNum = newPacket.TickNum; _tickBuffer[tickNum].Packets.Add(newPacket); } } \ No newline at end of file diff --git a/Shared/Code/Packets/BreakTile.cs b/Shared/Code/Packets/BreakTile.cs index 2a0087d..5a390bb 100644 --- a/Shared/Code/Packets/BreakTile.cs +++ b/Shared/Code/Packets/BreakTile.cs @@ -1,5 +1,6 @@ using LiteNetLib; using LiteNetLib.Utils; +using Shared.Code.Block_System; namespace Shared.Code.Packets; /// @@ -11,27 +12,24 @@ namespace Shared.Code.Packets; public sealed class BreakTile : IPacket { public bool Foreground; public Vector2Int Position; - public ushort TickNum; + public ushort TickNum { get; init; } - public ushort GetTickNum() => TickNum; + public PacketType GetPacketType() => PacketType.BreakTile; - public void Process() { - BuildSystem.BreakBlockCell(Foreground, Position); + public int SourceId { get; init; } + + public void Process(WorldState worldState) { + TilemapShared tilemap = Foreground ? worldState.Foreground : worldState.Background; + tilemap.DeleteTile(Position); } public void Serialize(NetDataWriter writer) { - writer.Put(TickNum); - writer.Put(Position.X); - writer.Put(Position.Y); + writer.Put(Position); writer.Put(Foreground); } public void Deserialize(NetDataReader reader) { - TickNum = reader.GetUShort(); - int x = reader.GetInt(); - int y = reader.GetInt(); + Position = reader.Get(); Foreground = reader.GetBool(); - - Position = new Vector2Int(x, y); } } \ No newline at end of file diff --git a/Shared/Code/Packets/MovePlayer.cs b/Shared/Code/Packets/MovePlayer.cs new file mode 100644 index 0000000..03cd5ba --- /dev/null +++ b/Shared/Code/Packets/MovePlayer.cs @@ -0,0 +1,23 @@ +using LiteNetLib.Utils; +namespace Shared.Code.Packets; + +public sealed class MovePlayer : IPacket { + public Vector2Int Position { get; set; } + public ushort TickNum { get; init; } + + public int SourceId { get; init; } + + public void Serialize(NetDataWriter writer) { + writer.Put(Position); + } + + public void Deserialize(NetDataReader reader) { + Position = reader.Get(); + } + + public void Process(WorldState worldState) { + worldState.PlayerPositions[SourceId].Position = Position + new Vector2Int(256, 128); + } + + public PacketType GetPacketType() => PacketType.MovePlayer; +} \ No newline at end of file diff --git a/Shared/Code/Packets/Packet.cs b/Shared/Code/Packets/Packet.cs index 8b44874..3aaf0ae 100644 --- a/Shared/Code/Packets/Packet.cs +++ b/Shared/Code/Packets/Packet.cs @@ -4,18 +4,24 @@ namespace Shared.Code.Packets; public enum PacketType : byte { WorldDownload, BreakTile, - TileChange + TileChange, + MovePlayer, + PlayerList, + PeerEvent } public interface IPacket : INetSerializable { + public int SourceId { get; init; } + + public ushort TickNum { get; init; } + /// /// Handles all processing for the packet. /// - public void Process(); + public void Process(WorldState worldState); /// - /// Returns the tick number that the simulation must rewind to. + /// Returns the type of packet. /// - /// The number of the tick this packet applies to. - public ushort GetTickNum(); + public PacketType GetPacketType(); } \ No newline at end of file diff --git a/Shared/Code/Packets/PeerEvent.cs b/Shared/Code/Packets/PeerEvent.cs new file mode 100644 index 0000000..57df73e --- /dev/null +++ b/Shared/Code/Packets/PeerEvent.cs @@ -0,0 +1,27 @@ +using LiteNetLib.Utils; +namespace Shared.Code.Packets; + +public class PeerEvent : IPacket { + public enum PeerEventType { + PeerDisconnect, + PeerConnect, + PlayerList + } + + public PeerEventType Type; + + public void Serialize(NetDataWriter writer) { + writer.Put((byte)Type); + } + + public void Deserialize(NetDataReader reader) { + Type = (PeerEventType)reader.GetByte(); + } + + public void Process(WorldState worldState) { } + + public PacketType GetPacketType() => PacketType.PeerEvent; + + public int SourceId { get; init; } + public ushort TickNum { get; init; } +} \ No newline at end of file diff --git a/Shared/Code/Packets/PlayerList.cs b/Shared/Code/Packets/PlayerList.cs new file mode 100644 index 0000000..af468c2 --- /dev/null +++ b/Shared/Code/Packets/PlayerList.cs @@ -0,0 +1,35 @@ +using System.Linq; +using LiteNetLib.Utils; +using Shared.Code.Components; +namespace Shared.Code.Packets; + +public class PlayerList : IPacket { + public int[] PlayerIds; + + public void Serialize(NetDataWriter writer) { + writer.PutArray(PlayerIds); + } + + public void Deserialize(NetDataReader reader) { + PlayerIds = reader.GetIntArray(); + } + + public void Process(WorldState worldState) { + foreach (var playerPosition in worldState.PlayerPositions) { + if (!PlayerIds.Contains(playerPosition.Key)) { + worldState.PlayerPositions.Remove(playerPosition.Key); + } + } + + foreach (int playerId in PlayerIds) { + if (!worldState.PlayerPositions.ContainsKey(playerId)) { + worldState.PlayerPositions.Add(playerId, new Transform(new Vector2Int(256, 128))); + } + } + } + + public PacketType GetPacketType() => PacketType.PlayerList; + + public int SourceId { get; init; } + public ushort TickNum { get; init; } +} \ No newline at end of file diff --git a/Shared/Code/Packets/TileChange.cs b/Shared/Code/Packets/TileChange.cs index 6187c10..6573daa 100644 --- a/Shared/Code/Packets/TileChange.cs +++ b/Shared/Code/Packets/TileChange.cs @@ -13,29 +13,27 @@ public sealed class TileChange : IPacket { public string BlockUid; public bool Foreground; public Vector2Int Position; - public ushort TickNum; - public ushort GetTickNum() => TickNum; + public ushort TickNum { get; init; } - public void Process() { - BuildSystem.PlaceBlockCell(BlockManagerShared.AllBlocks[BlockUid], Foreground, Position); + public PacketType GetPacketType() => PacketType.TileChange; + + public int SourceId { get; init; } + + public void Process(WorldState worldState) { + TilemapShared tilemap = Foreground ? worldState.Foreground : worldState.Background; + tilemap.SetBlock(Position, BlockUid); } public void Serialize(NetDataWriter writer) { - writer.Put(TickNum); - writer.Put(Position.X); - writer.Put(Position.Y); + writer.Put(Position); writer.Put(Foreground); writer.Put(BlockUid); } public void Deserialize(NetDataReader reader) { - TickNum = reader.GetUShort(); - int x = reader.GetInt(); - int y = reader.GetInt(); + Position = reader.Get(); Foreground = reader.GetBool(); BlockUid = reader.GetString(); - - Position = new Vector2Int(x, y); } } \ No newline at end of file diff --git a/Shared/Code/Packets/WorldDownload.cs b/Shared/Code/Packets/WorldDownload.cs index 51ef544..08415a8 100644 --- a/Shared/Code/Packets/WorldDownload.cs +++ b/Shared/Code/Packets/WorldDownload.cs @@ -9,28 +9,32 @@ namespace Shared.Code.Packets; /// Should be replaced when chunks are added. /// public sealed class WorldDownload : IPacket { - public ushort TickNum; public string?[,,] World = new string[GlobalsShared.MaxX, GlobalsShared.MaxY, 2]; + public ushort TickNum { get; init; } - public ushort GetTickNum() => TickNum; + public PacketType GetPacketType() => PacketType.WorldDownload; - public void Process() { - BuildSystem.LoadNewWorld(World); + public int SourceId { get; init; } + + public void Process(WorldState worldState) { + for (int x = 0; x < World.GetLength(0); x++) + for (int y = 0; y < World.GetLength(1); y++) { + worldState.Foreground.SetBlock(new Vector2Int(x, y), World[x, y, 1] ?? "air"); + worldState.Background.SetBlock(new Vector2Int(x, y), World[x, y, 0] ?? "air"); + } } public void Serialize(NetDataWriter writer) { - writer.Put(TickNum); for (int x = 0; x < GlobalsShared.MaxX; x++) { for (int y = 0; y < GlobalsShared.MaxY; y++) { for (int z = 0; z < 2; z++) { - writer.Put(World[x, y, z]); + writer.Put(World[x, y, z] ?? "air"); } } } } public void Deserialize(NetDataReader reader) { - TickNum = reader.GetUShort(); for (int x = 0; x < GlobalsShared.MaxX; x++) { for (int y = 0; y < GlobalsShared.MaxY; y++) { for (int z = 0; z < 2; z++) { @@ -42,7 +46,7 @@ public void Deserialize(NetDataReader reader) { public static WorldDownload Default() { string?[,,] newWorld = new string[GlobalsShared.MaxX, GlobalsShared.MaxY, 2]; - + for (int i = 0; i < GlobalsShared.MaxX; i++) { newWorld[i, 0, 1] = "stone"; newWorld[i, 1, 1] = "dirt"; @@ -51,7 +55,7 @@ public static WorldDownload Default() { newWorld[i, 4, 1] = "dirt"; newWorld[i, 5, 1] = "grass"; } - + return new WorldDownload { World = newWorld, TickNum = 1 diff --git a/Shared/Globals.cs b/Shared/Globals.cs index 6306993..a240545 100644 --- a/Shared/Globals.cs +++ b/Shared/Globals.cs @@ -1,6 +1,4 @@ -using Microsoft.Xna.Framework; using Shared.Code; -using Shared.Code.Block_System; namespace Shared; public static class GlobalsShared { @@ -15,14 +13,6 @@ public static class GlobalsShared { /// The maximum world size. (Height) public const int MaxY = 60; - /// Tilemap for foreground objects. - public static TilemapShared ForegroundTilemap; - - /// Tilemap for background (non-dense) objects. - public static TilemapShared BackgroundTilemap; - - public static readonly Color BackgroundColor = new(0.5f, 0.5f, 0.5f, 1f); - /// The size of the grid the game is played on. public static readonly Vector2Int GridSize = new(8, 8); } \ No newline at end of file diff --git a/Shared/Shared.csproj b/Shared/Shared.csproj index 091f9f9..33e386d 100644 --- a/Shared/Shared.csproj +++ b/Shared/Shared.csproj @@ -6,10 +6,10 @@ - + - - + + diff --git a/Shared/WorldState.cs b/Shared/WorldState.cs new file mode 100644 index 0000000..992b3d2 --- /dev/null +++ b/Shared/WorldState.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Shared.Code.Block_System; +using Shared.Code.Components; +namespace Shared; + +public class WorldState { + private const int MaxX = 100; + private const int MaxY = 60; + + public WorldState(TilemapShared newForeground, TilemapShared newBackground) { + Foreground = newForeground; + Background = newBackground; + } + + public WorldState() { } + + public TilemapShared Foreground { get; set; } = new(MaxX, MaxY, false); + public TilemapShared Background { get; set; } = new(MaxX, MaxY, true); + + public string?[,,] CurrentWorld { + get { + string?[,,] world = new string[MaxX, MaxY, 2]; + for (int x = 0; x < MaxX; x++) { + for (int y = 0; y < MaxY; y++) { + world[x, y, 0] = Foreground.TileGrid[x, y]?.SourceBlock.BlockUid; + world[x, y, 1] = Background.TileGrid[x, y]?.SourceBlock.BlockUid; + } + } + return world; + } + } + + public Dictionary PlayerPositions { get; set; } = new(); +} \ No newline at end of file diff --git a/Shared/blocks.schema.json b/Shared/blocks.schema.json index 8ad16df..a8e1ba5 100644 --- a/Shared/blocks.schema.json +++ b/Shared/blocks.schema.json @@ -1,26 +1,28 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Block", - "description": "A block for use in Blocktest", - "type": "object", - "required": ["BlockName"], - "properties": { - "BlockName": { - "type": "string", - "description": "The name of the block" - }, - "BlockSmoothing": { - "type": "boolean", - "description": "Whether the block is capable of smoothing with other blocks", - "default": false - }, - "SmoothSelf": { - "type": "boolean", - "default": false - }, - "CanPlaceBackground": { - "type": "boolean", - "default": true - } - } + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Block", + "description": "A block for use in Blocktest", + "type": "object", + "required": [ + "BlockName" + ], + "properties": { + "BlockName": { + "type": "string", + "description": "The name of the block" + }, + "BlockSmoothing": { + "type": "boolean", + "description": "Whether the block is capable of smoothing with other blocks", + "default": false + }, + "SmoothSelf": { + "type": "boolean", + "default": false + }, + "CanPlaceBackground": { + "type": "boolean", + "default": true + } + } }