commit 69596ab172deba58eb367a8306a81850e8f503d7 Author: MasterGordon Date: Sat Nov 5 12:59:45 2022 +0100 first commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e2c242d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,71 @@ +[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}] +indent_style = space +indent_size = 4 +tab_width = 4 + +[*] + +# Microsoft .NET properties +csharp_new_line_before_members_in_object_initializers = false +csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion +csharp_style_var_elsewhere = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +dotnet_naming_rule.private_constants_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_constants_rule.severity = warning +dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style +dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols +dotnet_naming_rule.private_instance_fields_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_instance_fields_rule.severity = warning +dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style +dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols +dotnet_naming_rule.private_static_fields_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_static_fields_rule.severity = warning +dotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style_1 +dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols +dotnet_naming_rule.private_static_readonly_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_static_readonly_rule.severity = warning +dotnet_naming_rule.private_static_readonly_rule.style = upper_camel_case_style +dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols +dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper = True +dotnet_naming_rule.unity_serialized_field_rule.resharper_description = Unity serialized field +dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef +dotnet_naming_rule.unity_serialized_field_rule.severity = warning +dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style +dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols +dotnet_naming_style.lower_camel_case_style.capitalization = camel_case +dotnet_naming_style.lower_camel_case_style_1.capitalization = camel_case +dotnet_naming_style.lower_camel_case_style_1.required_prefix = _ +dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case +dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field +dotnet_naming_symbols.private_constants_symbols.required_modifiers = const +dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static +dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = +dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field +dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +dotnet_style_qualification_for_field = true:warning +dotnet_style_qualification_for_method = true:warning +dotnet_style_qualification_for_property = true:warning +dotnet_style_qualification_for_event = true:warning +csharp_style_unused_value_expression_statement_preference = false +dotnet_diagnostic.CA1805.severity = none diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..7beca10 --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,32 @@ +name: .NET + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Publish + run: dotnet publish -r linux-x64 + - name: Upload a Build Artifact + uses: actions/upload-artifact@v3.1.0 + with: + # Artifact name + name: Linux build + # A file, directory or wildcard pattern that describes what to upload + path: bin/Debug/net6.0/linux-x64/publish/asdlteroids diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd42ee3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin/ +obj/ diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..2c3e067 --- /dev/null +++ b/Program.cs @@ -0,0 +1,13 @@ +class Program +{ + static void Main(string[] args) + { + bool isHost = args.Contains("--host"); + // bool isHost = true; + var game = new Mine2d(isHost); + game.Run(); + // var p = new Publisher(isHost ? InteractorKind.Server : InteractorKind.Client); + // p.Dump(); + // Console.WriteLine("Hello World!"); + } +} diff --git a/assets/font.ttf b/assets/font.ttf new file mode 100644 index 0000000..39adf42 Binary files /dev/null and b/assets/font.ttf differ diff --git a/assets/stone.png b/assets/stone.png new file mode 100644 index 0000000..496c4ac Binary files /dev/null and b/assets/stone.png differ diff --git a/mine2d.csproj b/mine2d.csproj new file mode 100644 index 0000000..140033f --- /dev/null +++ b/mine2d.csproj @@ -0,0 +1,24 @@ + + + + Exe + net6.0 + mine2d + enable + true + link + true + true + disable + + + + + + + + + + + + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..f95b8f4 --- /dev/null +++ b/readme.md @@ -0,0 +1,2 @@ +# Boulder Dash SDL + diff --git a/src/Context.cs b/src/Context.cs new file mode 100644 index 0000000..7f40153 --- /dev/null +++ b/src/Context.cs @@ -0,0 +1,45 @@ +class Context +{ + public bool IsHost { get; set; } + public IBackend Backend { get; set; } + public IFrontend Frontend { get; set; } + public GameState GameState { get; set; } + public FrontendGameState FrontendGameState { get; set; } + public Window Window { get; set; } + public Renderer Renderer { get; set; } + public TileRegistry TileRegistry { get; set; } + public ResourceLoader ResourceLoader { get; set; } + public static Context instance { get; set; } + + public Context( + bool isHost, + IBackend backend, + IFrontend frontend, + GameState gameState, + FrontendGameState frontendGameState, + Renderer renderer, + Window window + ) + { + this.IsHost = isHost; + this.Backend = backend; + this.Frontend = frontend; + this.GameState = gameState; + this.FrontendGameState = frontendGameState; + this.Renderer = renderer; + this.Window = window; + this.TileRegistry = new TileRegistry(); + this.ResourceLoader = new ResourceLoader(); + Context.instance = this; + } + + public static Context Get() + { + if (Context.instance == null) + { + throw new Exception("Context not initialized"); + } + + return Context.instance; + } +} diff --git a/src/Controls.cs b/src/Controls.cs new file mode 100644 index 0000000..9ffc27a --- /dev/null +++ b/src/Controls.cs @@ -0,0 +1,35 @@ +using static SDL2.SDL; + +enum Control +{ + UP, + DOWN, + LEFT, + RIGHT, + STAY, + CONFIRM, +} + +static class ControlKeyExtension +{ + public static SDL_Keycode Key(this Control c) + { + switch (c) + { + case Control.UP: + return SDL_Keycode.SDLK_w; + case Control.DOWN: + return SDL_Keycode.SDLK_s; + case Control.LEFT: + return SDL_Keycode.SDLK_a; + case Control.RIGHT: + return SDL_Keycode.SDLK_d; + case Control.STAY: + return SDL_Keycode.SDLK_LCTRL; + case Control.CONFIRM: + return SDL_Keycode.SDLK_SPACE; + default: + throw new ArgumentException("Invalid control"); + } + } +} diff --git a/src/Import.cs b/src/Import.cs new file mode 100644 index 0000000..1dd9f3a --- /dev/null +++ b/src/Import.cs @@ -0,0 +1,4 @@ +global using System.Numerics; +global using static SDL2.SDL; +global using static SDL2.SDL_image; +global using static SDL2.SDL_ttf; diff --git a/src/Mine2d.cs b/src/Mine2d.cs new file mode 100644 index 0000000..43ab454 --- /dev/null +++ b/src/Mine2d.cs @@ -0,0 +1,46 @@ +class Mine2d : Game +{ + private Context ctx; + + public Mine2d(bool isHost) + { + var window = new Window("MultiPlayerGame" + (isHost ? " - host" : ""), 1200, 800); + if (isHost) + { + this.ctx = new Context( + isHost, + new Backend(), + new Frontend(), + new GameState(), + new FrontendGameState(), + new Renderer(window), + window + ); + } + else + { + this.ctx = new Context( + isHost, + new RemoteBackend(), + new Frontend(), + new GameState(), + new FrontendGameState(), + new Renderer(window), + window + ); + } + Bootstrapper.Bootstrap(); + ctx.Backend.Init(); + ctx.Frontend.Init(); + } + + protected override void draw() + { + ctx.Frontend.Process(); + } + + protected override void update(double dt) + { + ctx.Backend.Process(dt); + } +} diff --git a/src/backend/Backend.cs b/src/backend/Backend.cs new file mode 100644 index 0000000..400fdf0 --- /dev/null +++ b/src/backend/Backend.cs @@ -0,0 +1,87 @@ +using System.Text; +using Newtonsoft.Json; +using WatsonTcp; + +class Backend : IBackend +{ + private WatsonTcpServer server; + private Publisher publisher; + private Queue pendingPackets = new(); + private uint tick = 0; + + public void Process(double dt) + { + this.ProcessPacket(new TickPacket(this.tick++)); + while (this.pendingPackets.Count > 0) + { + var packet = this.pendingPackets.Dequeue(); + this.publisher.Publish(packet); + } + this.sendGameState(); + } + + public void ProcessPacket(ValueType packet) + { + this.pendingPackets.Enqueue(packet); + } + + public void Init() + { + Task.Run(this.Run); + this.publisher = new Publisher(InteractorKind.Hybrid); + } + + public void Run() + { + this.server = new WatsonTcpServer("127.0.0.1", 42069); + this.server.Events.ClientConnected += this.clientConnected; + this.server.Events.ClientDisconnected += this.clientDisconnected; + this.server.Events.MessageReceived += this.messageReceived; + this.server.Start(); + } + + private void clientConnected(object sender, ConnectionEventArgs args) + { + Console.WriteLine("Client connected: " + args.IpPort); + var gameState = Context.Get().GameState; + var json = JsonConvert.SerializeObject(gameState); + if (sender is WatsonTcpServer server) + { + server.Send(args.IpPort, Encoding.UTF8.GetBytes(json)); + } + } + + private void clientDisconnected(object sender, DisconnectionEventArgs args) + { + Console.WriteLine("Client disconnected: " + args.IpPort); + } + + private void messageReceived(object sender, MessageReceivedEventArgs args) + { + var time = DateTime.Now; + Console.WriteLine("Message Received: " + args.IpPort); + var packet = Converter.ParsePacket(args.Data); + Console.WriteLine("Received packet: " + packet); + if (packet != null) + { + pendingPackets.Enqueue(packet); + } + Console.WriteLine(DateTime.Now - time); + } + + private void sendGameState() + { + if (server == null) + return; + var clients = server.ListClients(); + if (clients.Count() == 0) + return; + var gameState = Context.Get().GameState; + var json = JsonConvert.SerializeObject(gameState); + var bytes = Encoding.UTF8.GetBytes(json); + foreach (var client in clients) + { + server.Send(client, bytes); + } + } +} diff --git a/src/backend/IBackend.cs b/src/backend/IBackend.cs new file mode 100644 index 0000000..4533116 --- /dev/null +++ b/src/backend/IBackend.cs @@ -0,0 +1,6 @@ +interface IBackend +{ + public void Process(double dt); + public void ProcessPacket(ValueType packet); + public void Init(); +} diff --git a/src/backend/Publisher.cs b/src/backend/Publisher.cs new file mode 100644 index 0000000..4eb482c --- /dev/null +++ b/src/backend/Publisher.cs @@ -0,0 +1,86 @@ +class Publisher +{ + private readonly Dictionary> subscribers = + new(); + private readonly InteractorKind kind; + + public Publisher(InteractorKind kind) + { + this.kind = kind; + this.scan(); + } + + private void scan() + { + var assembly = this.GetType().Assembly; + var types = assembly.GetTypes(); + foreach (var type in types) + { + var attrs = type.GetCustomAttributes(typeof(Interactor), false); + if (attrs.Length == 0) + { + continue; + } + var methods = type.GetMethods(); + foreach (var method in methods) + { + var attrs2 = method.GetCustomAttributes(typeof(Interaction), false); + if (attrs2.Length == 0) + { + continue; + } + var attr = (Interaction)attrs2[0]; + if (attr.Kind != this.kind && this.kind != InteractorKind.Hybrid) + { + continue; + } + var del = Delegate.CreateDelegate( + typeof(Action<>).MakeGenericType(method.GetParameters()[0].ParameterType), + method + ); + this.subscribe(attr.Type, del); + } + } + } + + private void subscribe(string type, Delegate callback) + { + if (!this.subscribers.ContainsKey(type)) + { + this.subscribers[type] = new HashSet(); + } + this.subscribers[type].Add(callback); + } + + public void Dump() + { + foreach (var pair in this.subscribers) + { + Console.WriteLine(pair.Key); + foreach (var del in pair.Value) + { + Console.WriteLine(del); + } + } + } + + public void Publish(ValueType packet) + { + var type = PacketUtils.GetType(packet); + if (type != "tick") + { + Console.WriteLine("Publishing packet: " + type); + } + if (this.subscribers.ContainsKey(type)) + { + if (type != "tick") + { + Console.WriteLine("Found " + this.subscribers[type].Count + " subscribers"); + } + foreach (var del in this.subscribers[type]) + { + del.DynamicInvoke(packet); + } + } + } +} diff --git a/src/backend/RemoteBackend.cs b/src/backend/RemoteBackend.cs new file mode 100644 index 0000000..0584e22 --- /dev/null +++ b/src/backend/RemoteBackend.cs @@ -0,0 +1,52 @@ +using WatsonTcp; +using Newtonsoft.Json; +using System.Text; + +class RemoteBackend : IBackend +{ + private WatsonTcpClient client; + private Publisher publisher; + private Queue pendingPackets = new Queue(); + private uint tick = 0; + + public void Process(double dt) + { + var ctx = Context.Get(); + this.ProcessPacket(new TickPacket(tick++)); + while (pendingPackets.Count > 0) + { + var packet = pendingPackets.Dequeue(); + this.ProcessPacket(packet); + } + } + + public void ProcessPacket(ValueType packet) + { + this.publisher.Publish(packet); + var json = JsonConvert.SerializeObject(packet); + var bytes = Encoding.UTF8.GetBytes(json); + client.Send(bytes); + } + + public void Init() + { + Task.Run(this.Run); + this.publisher = new Publisher(InteractorKind.Client); + } + + public void Run() + { + client = new WatsonTcpClient("127.0.0.1", 42069); + client.Events.MessageReceived += (sender, args) => + { + var ctx = Context.Get(); + var message = Encoding.UTF8.GetString(args.Data); + var packet = JsonConvert.DeserializeObject(message); + if (packet != null) + { + ctx.GameState = packet; + } + }; + client.Connect(); + } +} diff --git a/src/backend/data/Packet.cs b/src/backend/data/Packet.cs new file mode 100644 index 0000000..3d30fc7 --- /dev/null +++ b/src/backend/data/Packet.cs @@ -0,0 +1,49 @@ +using System.Numerics; + +readonly struct MovePacket +{ + readonly public string type = "move"; + readonly public string playerName; + readonly public Vector2 movement; + + public MovePacket(string playerName, Vector2 movement) + { + this.playerName = playerName; + this.movement = movement; + } +} + +readonly struct ConnectPacket +{ + public readonly string type = "connect"; + public readonly string playerName; + public readonly Guid playerGuid; + + public ConnectPacket(string playerName, Guid playerGuid) + { + this.playerName = playerName; + this.playerGuid = playerGuid; + } +} + +readonly struct TickPacket +{ + public readonly string type = "tick"; + public readonly uint tick; + + public TickPacket(uint tick) + { + this.tick = tick; + } +} + +readonly struct SelfMovedPacket +{ + public readonly string type = "selfMoved"; + public readonly Vector2 target; + + public SelfMovedPacket(Vector2 target) + { + this.target = target; + } +} diff --git a/src/backend/interactor/Connect.cs b/src/backend/interactor/Connect.cs new file mode 100644 index 0000000..a222b52 --- /dev/null +++ b/src/backend/interactor/Connect.cs @@ -0,0 +1,22 @@ +[Interactor] +class Connect +{ + [Interaction(InteractorKind.Server, "connect")] + public static void ConnectServer(ConnectPacket packet) + { + var ctx = Context.Get(); + var player = ctx.GameState.Players.Find(p => p.Name == packet.playerName); + if (player == null) + { + ctx.GameState.Players.Add( + new Player + { + Name = packet.playerName, + Guid = packet.playerGuid, + Position = new Vector2(20, 16 * 16), + Movement = new Vector2(0, 0) + } + ); + } + } +} diff --git a/src/backend/interactor/Move.cs b/src/backend/interactor/Move.cs new file mode 100644 index 0000000..9327b0a --- /dev/null +++ b/src/backend/interactor/Move.cs @@ -0,0 +1,29 @@ +[Interactor] +class Move +{ + [Interaction(InteractorKind.Hybrid, "move")] + public static void MoveHybrid(MovePacket packet) + { + var ctx = Context.Get(); + var player = ctx.GameState.Players.Find(p => p.Name == packet.playerName); + if (player != null) + { + player.Movement = packet.movement * 4; + } + } + + [Interaction(InteractorKind.Hybrid, "tick")] + public static void TickHybrid(TickPacket packet) + { + var ctx = Context.Get(); + ctx.GameState.Players.ForEach(PlayerEntity.Move); + ctx.GameState.Players.ForEach(PlayerEntity.Collide); + } + + [Interaction(InteractorKind.Client, "tick")] + public static void SelfMovedClient(TickPacket packet) + { + var camera = Context.Get().FrontendGameState.Camera; + camera.CenterOn(PlayerEntity.GetSelf().Position); + } +} diff --git a/src/core/Bootstrapper.cs b/src/core/Bootstrapper.cs new file mode 100644 index 0000000..24023ed --- /dev/null +++ b/src/core/Bootstrapper.cs @@ -0,0 +1,12 @@ +class Bootstrapper +{ + public static void Bootstrap() + { + var ctx = Context.Get(); + ctx.GameState.World = new World(); + ctx.GameState.World.AddChunk(ChunkGenerator.CreateFilledChunk(0, 1, Tiles.stone)); + ctx.GameState.World.AddChunk(ChunkGenerator.CreateFilledChunk(-1, 1, Tiles.stone)); + ctx.GameState.World.AddChunk(ChunkGenerator.CreateFilledChunk(1, 1, Tiles.stone)); + ctx.GameState.World.AddChunk(ChunkGenerator.CreateFilledChunk(1, 0, Tiles.stone)); + } +} diff --git a/src/core/Camera.cs b/src/core/Camera.cs new file mode 100644 index 0000000..70be9e5 --- /dev/null +++ b/src/core/Camera.cs @@ -0,0 +1,19 @@ +class Camera +{ + public Vector2 position; + + public Camera() + { + position = Vector2.Zero; + } + + public void CenterOn(Vector2 target) + { + Console.WriteLine("Centering camera on " + target); + var ctx = Context.Get(); + var scale = ctx.FrontendGameState.Settings.GameScale; + var windowWidth = ctx.FrontendGameState.WindowWidth; + var windowHeight = ctx.FrontendGameState.WindowHeight; + position = target - (new Vector2(windowWidth / 2, windowHeight / 2)) / scale; + } +} diff --git a/src/core/Constants.cs b/src/core/Constants.cs new file mode 100644 index 0000000..df5de5e --- /dev/null +++ b/src/core/Constants.cs @@ -0,0 +1,6 @@ +class Constants +{ + public const int ChunkSize = 32; + public const int TileSize = 16; + public static Vector2 gravity = new Vector2(0, 0.1f); +} diff --git a/src/core/PlayerEntity.cs b/src/core/PlayerEntity.cs new file mode 100644 index 0000000..097a59e --- /dev/null +++ b/src/core/PlayerEntity.cs @@ -0,0 +1,69 @@ +class PlayerEntity +{ + public static bool isSelf(Player p) + { + return p.Guid == GetSelf().Guid; + } + + public static Player GetSelf() + { + var ctx = Context.Get(); + var player = ctx.GameState.Players.FirstOrDefault( + p => p.Guid == ctx.FrontendGameState.PlayerGuid + ); + return player; + } + + public static void Move(Player p) + { + p.Movement += Constants.gravity; + p.Position += p.Movement; + } + + public static void Collide(Player p) + { + var world = Context.Get().GameState.World; + bool hasCollision; + do + { + var pL = p.Position + new Vector2(0, -8); + hasCollision = + world.HasChunkAt(pL) && world.GetChunkAt(pL).hasTileAt(pL); + if (hasCollision) + { + p.Movement = p.Movement with { X = 0 }; + p.Position += new Vector2(0.1f, 0); + } + } while (hasCollision); + do + { + var pR = p.Position + new Vector2(16, -8); + hasCollision = + world.HasChunkAt(pR) && world.GetChunkAt(pR).hasTileAt(pR); + if (hasCollision) + { + p.Movement = p.Movement with { X = 0 }; + p.Position += new Vector2(-0.1f, 0); + } + } while (hasCollision); + do + { + var pL = p.Position; + var pR = p.Position + new Vector2(16, 0); + hasCollision = + world.HasChunkAt(pL) && world.GetChunkAt(pL).hasTileAt(pL) + || world.HasChunkAt(pR) && world.GetChunkAt(pR).hasTileAt(pR); + Console.WriteLine(World.ToChunkPos(p.Position)); + if (world.HasChunkAt(p.Position)) + { + var chunk = world.GetChunkAt(p.Position); + Console.WriteLine($"Chunk: {chunk.X}, {chunk.Y}"); + } + if (hasCollision) + { + p.Movement = p.Movement with { Y = 0 }; + p.Position += new Vector2(0, -0.1f); + } + } while (hasCollision); + } +} diff --git a/src/core/data/Chunk.cs b/src/core/data/Chunk.cs new file mode 100644 index 0000000..65d5722 --- /dev/null +++ b/src/core/data/Chunk.cs @@ -0,0 +1,63 @@ +class Chunk +{ + public int[,] Tiles { get; set; } = new int[Constants.ChunkSize, Constants.ChunkSize]; + public int X { get; set; } + public int Y { get; set; } + + public Chunk(int x, int y) + { + this.X = x; + this.Y = y; + } + + public void SetTile(int x, int y, int tile) + { + this.Tiles[x, y] = tile; + } + + public int GetTile(int x, int y) + { + return this.Tiles[x, y]; + } + + public bool hasTileAt(Vector2 pos) + { + return this.hasTileAt((int)pos.X, (int)pos.Y); + } + + public bool hasTileAt(int x, int y) + { + var posInChunk = this.GetPositionInChunk(new Vector2(x, y)); + var tileX = (int)Math.Floor(posInChunk.X / Constants.TileSize); + var tileY = (int)Math.Floor(posInChunk.Y / Constants.TileSize); + return this.hasTile(tileX, tileY); + } + + public int GetTileAt(Vector2 pos) + { + return this.GetTileAt((int)pos.X, (int)pos.Y); + } + + public int GetTileAt(int x, int y) + { + var tileX = (int)Math.Floor(x / (float)Constants.TileSize); + var tileY = (int)Math.Floor(y / (float)Constants.TileSize); + return this.GetTile(tileX, tileY); + } + + public bool hasTile(int x, int y) + { + return x >= 0 && x < this.Tiles.Length && y >= 0 && y < this.Tiles.Length; + } + + public bool hasTile(Vector2 pos) + { + return this.hasTile((int)pos.X, (int)pos.Y); + } + + public Vector2 GetPositionInChunk(Vector2 pos) + { + return pos - new Vector2(this.X * Constants.ChunkSize * Constants.TileSize, + this.Y * Constants.ChunkSize * Constants.TileSize); + } +} diff --git a/src/core/data/World.cs b/src/core/data/World.cs new file mode 100644 index 0000000..0465f38 --- /dev/null +++ b/src/core/data/World.cs @@ -0,0 +1,71 @@ +class World +{ + public Dictionary Chunks { get; set; } = new Dictionary(); + + public void AddChunk(Chunk chunk) + { + this.Chunks.Add(chunk.X + "," + chunk.Y, chunk); + } + + public Chunk GetChunk(Vector2 pos) + { + return this.GetChunk((int)pos.X, (int)pos.Y); + } + + public Chunk GetChunk(int x, int y) + { + return this.Chunks[x + "," + y]; + } + + public bool HasChunk(Vector2 pos) + { + return this.HasChunk((int)pos.X, (int)pos.Y); + } + + public bool HasChunk(int x, int y) + { + return this.Chunks.ContainsKey(x + "," + y); + } + + public Chunk GetChunkAt(Vector2 pos) + { + return this.GetChunkAt((int)pos.X, (int)pos.Y); + } + + public Chunk GetChunkAt(int x, int y) + { + var chunkPos = ToChunkPos(new Vector2(x, y)); + return this.Chunks[chunkPos.X + "," + chunkPos.Y]; + } + + public bool HasChunkAt(Vector2 pos) + { + return this.HasChunkAt((int)pos.X, (int)pos.Y); + } + + public bool HasChunkAt(int x, int y) + { + var chunkX = Math.Floor(x / (float)(Constants.ChunkSize * Constants.TileSize)); + var chunkY = Math.Floor(y / (float)(Constants.ChunkSize * Constants.TileSize)); + + return this.Chunks.ContainsKey(chunkX + "," + chunkY); + } + + public bool ChunkExists(int x, int y) + { + return this.Chunks.ContainsKey(x + "," + y); + } + + public static Vector2 ToChunkPos(Vector2 pos) + { + var chunkX = Math.Floor(pos.X / (Constants.ChunkSize * Constants.TileSize)); + var chunkY = Math.Floor(pos.Y / (Constants.ChunkSize * Constants.TileSize)); + return new Vector2((float)chunkX, (float)chunkY); + } + + public int GetTileAt(int x, int y) + { + var chunk = this.GetChunkAt(x, y); + return 0; + } +} diff --git a/src/core/tiles/Tile.cs b/src/core/tiles/Tile.cs new file mode 100644 index 0000000..6fed165 --- /dev/null +++ b/src/core/tiles/Tile.cs @@ -0,0 +1,48 @@ +class Tile +{ + public string Name { get; set; } + public IntPtr Texture { get; set; } + + public Tile(string name, string textureName) + { + this.Name = name; + + var rl = Context.Get().ResourceLoader; + var res = rl.LoadToIntPtr("assets." + textureName + ".png"); + var sdlBuffer = SDL_RWFromMem(res.ptr, res.size); + var surface = IMG_Load_RW(sdlBuffer, 1); + var texture = Context.Get().Renderer.CreateTextureFromSurface(surface); + this.Texture = texture; + SDL_FreeSurface(surface); + } + + ~Tile() + { + SDL_DestroyTexture(this.Texture); + } + + public void Render(int x, int y) + { + var renderer = Context.Get().Renderer; + var scale = Context.Get().FrontendGameState.Settings.GameScale; + var camera = Context.Get().FrontendGameState.Camera; + renderer.DrawTexture( + this.Texture, + (x - (int)camera.position.X) * scale, + (y - (int)camera.position.Y) * scale, + Constants.TileSize * scale, + Constants.TileSize * scale + ); + } + + public SDL_Rect GetCollisionRect(int x, int y) + { + return new SDL_Rect() + { + x = x, + y = y, + w = Constants.TileSize, + h = Constants.TileSize + }; + } +} diff --git a/src/core/tiles/TileRegistry.cs b/src/core/tiles/TileRegistry.cs new file mode 100644 index 0000000..61341ec --- /dev/null +++ b/src/core/tiles/TileRegistry.cs @@ -0,0 +1,19 @@ +enum Tiles : int +{ + stone = 1, +} + +class TileRegistry +{ + public Dictionary Tiles { get; set; } = new Dictionary(); + + public void RegisterTile() + { + Tiles.Add(1, new Tile("stone", "stone")); + } + + public Tile GetTile(int id) + { + return Tiles[id]; + } +} diff --git a/src/core/world/ChunkGenerator.cs b/src/core/world/ChunkGenerator.cs new file mode 100644 index 0000000..193253e --- /dev/null +++ b/src/core/world/ChunkGenerator.cs @@ -0,0 +1,20 @@ +class ChunkGenerator +{ + public static Chunk CreateFilledChunk(int x, int y, int fill) + { + var chunk = new Chunk(x, y); + for (var i = 0; i < Constants.ChunkSize; i++) + { + for (var j = 0; j < Constants.ChunkSize; j++) + { + chunk.SetTile(i, j, fill); + } + } + return chunk; + } + + public static Chunk CreateFilledChunk(int x, int y, Tiles fill) + { + return CreateFilledChunk(x, y, (int)fill); + } +} diff --git a/src/engine/AudioPlayer.cs b/src/engine/AudioPlayer.cs new file mode 100644 index 0000000..252191c --- /dev/null +++ b/src/engine/AudioPlayer.cs @@ -0,0 +1,25 @@ +enum Sound { } + +class AudioPlayer +{ + private Dictionary audioFiles = new(); + private ResourceLoader resourceLoader = new(); + + public AudioPlayer() + { + SDL2.SDL_mixer.Mix_OpenAudio(44100, SDL2.SDL_mixer.MIX_DEFAULT_FORMAT, 2, 2048); + } + + public void Register(Sound name, string path) + { + var buffer = resourceLoader.LoadBytes(path); + this.audioFiles.Add(name, buffer); + } + + public void Play(Sound name) + { + var buffer = this.audioFiles[name]; + var sound = SDL2.SDL_mixer.Mix_QuickLoad_WAV(buffer); + SDL2.SDL_mixer.Mix_PlayChannel((int)name, sound, 0); + } +} diff --git a/src/engine/FontManager.cs b/src/engine/FontManager.cs new file mode 100644 index 0000000..ec94791 --- /dev/null +++ b/src/engine/FontManager.cs @@ -0,0 +1,36 @@ +using static SDL2.SDL_ttf; +using static SDL2.SDL; + +class FontManager +{ + private Dictionary fonts = new(); + private ResourceLoader resourceLoader; + + public FontManager(ResourceLoader resourceLoader) + { + this.resourceLoader = resourceLoader; + if (TTF_Init() != 0) + { + throw new Exception("TTF_Init failed"); + } + } + + public void RegisterFont(string name, string path, int fontSize) + { + if (fonts.ContainsKey(name)) + return; + var res = resourceLoader.LoadToIntPtr(path); + var sdlBuffer = SDL_RWFromConstMem(res.ptr, res.size); + var font = TTF_OpenFontRW(sdlBuffer, 1, fontSize); + if (font == IntPtr.Zero) + { + throw new Exception("TTF_OpenFont failed"); + } + fonts.Add(name, font); + } + + public IntPtr GetFont(string name) + { + return fonts[name]; + } +} diff --git a/src/engine/Game.cs b/src/engine/Game.cs new file mode 100644 index 0000000..73963d5 --- /dev/null +++ b/src/engine/Game.cs @@ -0,0 +1,30 @@ +abstract class Game +{ + public const int TPS = 128; + private Queue fpsQueue = new Queue(); + protected abstract void update(double dt); + protected abstract void draw(); + + public void Run() + { + var tLast = DateTime.Now; + var tAcc = TimeSpan.Zero; + while (true) + { + var dt = DateTime.Now - tLast; + tLast = DateTime.Now; + tAcc += dt; + var fps = (int)(1 / dt.TotalSeconds); + fpsQueue.Enqueue(fps); + while (fpsQueue.Count > fps) + fpsQueue.Dequeue(); + while (tAcc >= TimeSpan.FromSeconds(1.0 / TPS)) + { + update(dt.TotalSeconds); + tAcc -= TimeSpan.FromSeconds(1.0 / TPS); + } + + draw(); + } + } +} diff --git a/src/engine/PacketUtils.cs b/src/engine/PacketUtils.cs new file mode 100644 index 0000000..f5dc1bf --- /dev/null +++ b/src/engine/PacketUtils.cs @@ -0,0 +1,22 @@ +class PacketUtils +{ + public static string GetType(ValueType packet) + { + var t = packet.GetType(); + foreach (var pp in t.GetProperties()) + { + Console.WriteLine(pp.Name); + } + var p = t.GetField("type"); + if (p == null) + { + throw new Exception("p undef"); + } + var v = p.GetValue(packet); + if (v == null) + { + throw new Exception("v undef"); + } + return (string)v; + } +} diff --git a/src/engine/Renderer.cs b/src/engine/Renderer.cs new file mode 100644 index 0000000..fa46b10 --- /dev/null +++ b/src/engine/Renderer.cs @@ -0,0 +1,120 @@ +class Renderer +{ + private IntPtr renderer; + private IntPtr font; + private SDL_Color color; + + public Renderer(Window window) + { + this.renderer = SDL_CreateRenderer( + window.GetWindow(), + -1, + SDL_RendererFlags.SDL_RENDERER_ACCELERATED + ); + } + + public void Clear() + { + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + } + + public void Present() + { + SDL_RenderPresent(renderer); + } + + public void DrawRect(double x, double y, int w, int h) + { + this.DrawRect((int)x, (int)y, w, h); + } + + public void DrawRect(int x, int y, int w, int h) + { + SDL_Rect rect = new SDL_Rect(); + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + + SDL_RenderFillRect(renderer, ref rect); + } + + public void DrawLines(double[][] points) + { + SDL_Point[] sdlPoints = new SDL_Point[points.Length]; + for (int i = 0; i < points.Length; i++) + { + sdlPoints[i].x = (int)points[i][0]; + sdlPoints[i].y = (int)points[i][1]; + } + + SDL_RenderDrawLines(renderer, sdlPoints, points.Length); + } + + public void SetColor(int r, int g, int b, int a = 255) + { + SDL_SetRenderDrawColor(renderer, (byte)r, (byte)g, (byte)b, (byte)a); + } + + public void SetFont(IntPtr font, SDL_Color color) + { + this.font = font; + this.color = color; + } + + public void SetFont(IntPtr font, Color color) + { + this.font = font; + this.color = color.toSDLColor(); + } + + public void DrawText(string text, int x, int y, bool center = false) + { + var surfaceMessage = TTF_RenderText_Solid(this.font, text, this.color); + + var texture = SDL_CreateTextureFromSurface(this.renderer, surfaceMessage); + int width; + int height; + + SDL_QueryTexture(texture, out _, out _, out width, out height); + + SDL_Rect rect = new SDL_Rect(); + rect.x = x; + rect.y = y; + rect.w = width; + rect.h = height; + + if (center) + { + rect.x -= width / 2; + rect.y -= height / 2; + } + + SDL_RenderCopy(this.renderer, texture, IntPtr.Zero, ref rect); + SDL_DestroyTexture(texture); + SDL_FreeSurface(surfaceMessage); + } + + public IntPtr GetRaw() + { + return renderer; + } + + public IntPtr CreateTextureFromSurface(IntPtr surface) + { + return SDL_CreateTextureFromSurface(renderer, surface); + } + + public void DrawTexture(IntPtr texture, int x, int y, int w, int h) + { + SDL_Rect rect = new() + { + x = x, + y = y, + w = w, + h = h + }; + SDL_RenderCopy(renderer, texture, IntPtr.Zero, ref rect); + } +} diff --git a/src/engine/ResourceLoader.cs b/src/engine/ResourceLoader.cs new file mode 100644 index 0000000..d17a3ad --- /dev/null +++ b/src/engine/ResourceLoader.cs @@ -0,0 +1,52 @@ +using System.Runtime.InteropServices; + +class ResourceLoader +{ + private string assemblyName; + + public ResourceLoader() + { + this.assemblyName = this.GetType().Assembly.GetName().Name!; + } + + public string LoadString(string resourceName) + { +#if (DEBUG) + Console.WriteLine("Loading resource: " + resourceName); + return File.ReadAllText(ToPath(resourceName)); +#endif + using var stream = this.GetType() + .Assembly.GetManifestResourceStream($"{this.assemblyName}.{resourceName}"); + using var reader = new StreamReader(stream!); + return reader.ReadToEnd(); + } + + public byte[] LoadBytes(string resourceName) + { + using var stream = this.GetType() + .Assembly.GetManifestResourceStream($"{this.assemblyName}.{resourceName}"); + using var memoryStream = new MemoryStream(); + stream!.CopyTo(memoryStream); + return memoryStream.ToArray(); + } + + public (IntPtr ptr, int size) LoadToIntPtr(string resourceName) + { + var bytes = this.LoadBytes(resourceName); + var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + var ptr = handle.AddrOfPinnedObject(); + return (ptr, bytes.Length); + } + + public void SaveString(string resourceName, string content) + { + using var stream = new StreamWriter(ToPath(resourceName)); + stream.Write(content); + } + + private static string ToPath(string resourceName) + { + var s = resourceName.Split('.'); + return String.Join('/', s[..^1]) + "." + s[^1]; + } +} diff --git a/src/engine/Shapes.cs b/src/engine/Shapes.cs new file mode 100644 index 0000000..bffb4f4 --- /dev/null +++ b/src/engine/Shapes.cs @@ -0,0 +1,11 @@ +struct Line +{ + public Vector2 start; + public Vector2 end; + + public Line(Vector2 start, Vector2 end) + { + this.start = start; + this.end = end; + } +} diff --git a/src/engine/Window.cs b/src/engine/Window.cs new file mode 100644 index 0000000..720ec85 --- /dev/null +++ b/src/engine/Window.cs @@ -0,0 +1,44 @@ +class Window +{ + IntPtr window; + + public Window(string title, int w, int h) + { + window = SDL_CreateWindow( + title, + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + w, + h, + SDL_WindowFlags.SDL_WINDOW_VULKAN | SDL_WindowFlags.SDL_WINDOW_RESIZABLE + ); + } + + public Window(string title, int x, int y, int w, int h, SDL_WindowFlags flags) + { + this.window = SDL_CreateWindow(title, x, y, w, h, flags); + } + + public void Dispose() + { + SDL_DestroyWindow(this.window); + } + + public IntPtr GetWindow() + { + return this.window; + } + + public (int width, int height) GetSize() + { + int w, + h; + SDL_GetWindowSize(this.window, out w, out h); + return (w, h); + } + + public IntPtr GetRaw() + { + return this.window; + } +} diff --git a/src/engine/system/EventPriority.cs b/src/engine/system/EventPriority.cs new file mode 100644 index 0000000..3adb334 --- /dev/null +++ b/src/engine/system/EventPriority.cs @@ -0,0 +1,9 @@ +enum EventPriority : int +{ + Lowest = 0, + Low = 1, + Normal = 2, + High = 3, + Highest = 4, + Important = 5, +} diff --git a/src/engine/system/annotations/Interactor.cs b/src/engine/system/annotations/Interactor.cs new file mode 100644 index 0000000..0aec9f7 --- /dev/null +++ b/src/engine/system/annotations/Interactor.cs @@ -0,0 +1,22 @@ +enum InteractorKind +{ + Client, + Server, + Hybrid +} + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] +class Interactor : Attribute { } + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] +class Interaction : Attribute +{ + public string Type; + public InteractorKind Kind; + + public Interaction(InteractorKind kind, string type) + { + this.Type = type; + this.Kind = kind; + } +} diff --git a/src/engine/utils/Color.cs b/src/engine/utils/Color.cs new file mode 100644 index 0000000..ab3c5fb --- /dev/null +++ b/src/engine/utils/Color.cs @@ -0,0 +1,33 @@ +class Color +{ + public int r, + g, + b, + a; + + public Color(int r, int g, int b, int a) + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + public Color(int r, int g, int b) + { + this.r = r; + this.g = g; + this.b = b; + this.a = 255; + } + + public SDL_Color toSDLColor() + { + SDL_Color color = new(); + color.r = (byte)r; + color.g = (byte)g; + color.b = (byte)b; + color.a = (byte)a; + return color; + } +} diff --git a/src/engine/utils/Point.cs b/src/engine/utils/Point.cs new file mode 100644 index 0000000..585457e --- /dev/null +++ b/src/engine/utils/Point.cs @@ -0,0 +1,7 @@ +class Point +{ + public static double Distance(double x1, double y1, double x2, double y2) + { + return Math.Sqrt(Math.Pow(x2 - x1, 2) + Math.Pow(y2 - y1, 2)); + } +} diff --git a/src/frontend/Frontend.cs b/src/frontend/Frontend.cs new file mode 100644 index 0000000..8997d03 --- /dev/null +++ b/src/frontend/Frontend.cs @@ -0,0 +1,141 @@ +class Frontend : IFrontend +{ + private string playerName = "Player"; + private bool fullscreen = false; + + public void Init() + { + var ctx = Context.Get(); + this.playerName = Context.Get().IsHost ? "Host" : "Client"; + var guid = Guid.NewGuid(); + ctx.FrontendGameState.PlayerGuid = guid; + var connectPacket = new ConnectPacket(playerName, guid); + ctx.Backend.ProcessPacket(connectPacket); + ctx.TileRegistry.RegisterTile(); + var (width, height) = ctx.Window.GetSize(); + ctx.FrontendGameState.WindowWidth = width; + ctx.FrontendGameState.WindowHeight = height; + } + + public void Process() + { + var ctx = Context.Get(); + while (SDL_PollEvent(out var e) != 0) + { + if (e.type == SDL_EventType.SDL_QUIT) + { + Environment.Exit(0); + } + if (e.type == SDL_EventType.SDL_WINDOWEVENT) + { + if (e.window.windowEvent == SDL_WindowEventID.SDL_WINDOWEVENT_RESIZED) + { + ctx.FrontendGameState.WindowWidth = e.window.data1; + ctx.FrontendGameState.WindowHeight = e.window.data2; + Console.WriteLine($"Window resized to {e.window.data1}x{e.window.data2}"); + var player = ctx.GameState.Players.Find( + p => p.Guid == ctx.FrontendGameState.PlayerGuid + ); + ctx.FrontendGameState.Camera.CenterOn(player.Position); + } + } + if (e.type == SDL_EventType.SDL_KEYDOWN && e.key.repeat == 0) + { + var movementInput = ctx.FrontendGameState.MovementInput; + if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_F11) + { + if (!fullscreen) + { + SDL_SetWindowFullscreen( + ctx.Window.GetRaw(), + (uint)SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP + ); + } + else + { + SDL_SetWindowFullscreen(ctx.Window.GetRaw(), 0); + } + fullscreen = !fullscreen; + } + if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_A) + { + movementInput.X -= 1; + } + if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_D) + { + movementInput.X += 1; + } + if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_W) + { + movementInput.Y -= 1; + } + if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_S) + { + movementInput.Y += 1; + } + ctx.FrontendGameState.MovementInput = movementInput; + } + if (e.type == SDL_EventType.SDL_KEYUP && e.key.repeat == 0) + { + var movementInput = ctx.FrontendGameState.MovementInput; + if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_A) + { + movementInput.X += 1; + } + if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_D) + { + movementInput.X -= 1; + } + if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_W) + { + movementInput.Y += 1; + } + if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_S) + { + movementInput.Y -= 1; + } + ctx.FrontendGameState.MovementInput = movementInput; + } + if ( + e.key.keysym.scancode + is SDL_Scancode.SDL_SCANCODE_A + or SDL_Scancode.SDL_SCANCODE_D + or SDL_Scancode.SDL_SCANCODE_W + or SDL_Scancode.SDL_SCANCODE_S + && e.key.repeat == 0 + && e.type is SDL_EventType.SDL_KEYDOWN or SDL_EventType.SDL_KEYUP + ) + { + if (e.key.repeat == 1) + continue; + var movement = ctx.FrontendGameState.MovementInput; + if (movement.Length() > 0) + movement = Vector2.Normalize(movement); + ctx.Backend.ProcessPacket(new MovePacket(playerName, movement)); + } + if (e.key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_ESCAPE) + { + Environment.Exit(0); + } + } + + ctx.Renderer.Clear(); + var scale = ctx.FrontendGameState.Settings.GameScale; + var camera = Context.Get().FrontendGameState.Camera; + new WorldRenderer().Render(); + ctx.GameState.Players.ForEach(player => + { + if (player.Name == playerName) + ctx.Renderer.SetColor(0, 0, 255, 255); + else + ctx.Renderer.SetColor(255, 0, 0, 255); + ctx.Renderer.DrawRect( + (player.Position.X - (int)camera.position.X) * scale, + (player.Position.Y - (int)camera.position.Y) * scale - 32 * scale, + 16 * scale, + 32 * scale + ); + }); + ctx.Renderer.Present(); + } +} diff --git a/src/frontend/IFrontend.cs b/src/frontend/IFrontend.cs new file mode 100644 index 0000000..06df2db --- /dev/null +++ b/src/frontend/IFrontend.cs @@ -0,0 +1,5 @@ +interface IFrontend +{ + public void Process(); + public void Init(); +} diff --git a/src/frontend/renderer/IRenderer.cs b/src/frontend/renderer/IRenderer.cs new file mode 100644 index 0000000..7423328 --- /dev/null +++ b/src/frontend/renderer/IRenderer.cs @@ -0,0 +1,4 @@ +interface IRenderer +{ + public void Render(); +} diff --git a/src/frontend/renderer/WorldRenderer.cs b/src/frontend/renderer/WorldRenderer.cs new file mode 100644 index 0000000..f80c9d8 --- /dev/null +++ b/src/frontend/renderer/WorldRenderer.cs @@ -0,0 +1,24 @@ +class WorldRenderer : IRenderer +{ + public void Render() + { + var ctx = Context.Get(); + var world = ctx.GameState.World; + var renderer = ctx.Renderer; + var tileRegistry = ctx.TileRegistry; + foreach (var (_, chunk) in world.Chunks) + { + for (int y = 0; y < Constants.ChunkSize; y++) + { + for (int x = 0; x < Constants.ChunkSize; x++) + { + var tileId = chunk.GetTile(x, y); + var tile = tileRegistry.GetTile(tileId); + var chunkOffsetX = chunk.X * Constants.TileSize * Constants.ChunkSize; + var chunkOffsetY = chunk.Y * Constants.TileSize * Constants.ChunkSize; + tile.Render(x * 16 + chunkOffsetX, y * 16 + chunkOffsetY); + } + } + } + } +} diff --git a/src/network/Converter.cs b/src/network/Converter.cs new file mode 100644 index 0000000..2aa6a1f --- /dev/null +++ b/src/network/Converter.cs @@ -0,0 +1,36 @@ +using System.Text; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; + +class Converter +{ + public static ValueType ParsePacket(byte[] bytes) + { + var jsonString = Encoding.UTF8.GetString(bytes); + return ParsePacket(jsonString); + } + + public static ValueType ParsePacket(string jsonString) + { + var parsedRaw = JObject.Parse(jsonString); + var type = parsedRaw.GetValue("type"); + if (type == null) + { + throw new Exception("Packet has no type"); + } + var packetType = type.Value(); + Console.WriteLine("Packet type: " + packetType); + return packetType switch + { + "move" => parsedRaw.ToObject(), + "connect" => parsedRaw.ToObject(), + _ => throw new Exception("Unknown packet type") + }; + } + + public static byte[] SerializePacket(ValueType packet) + { + var jsonString = JsonConvert.SerializeObject(packet); + return Encoding.UTF8.GetBytes(jsonString); + } +} diff --git a/src/state/GameState.cs b/src/state/GameState.cs new file mode 100644 index 0000000..de86c15 --- /dev/null +++ b/src/state/GameState.cs @@ -0,0 +1,23 @@ +class FrontendGameState +{ + public Vector2 MovementInput; + public Vector2 CameraPosition; + public int WindowWidth; + public int WindowHeight; + public Guid PlayerGuid; + public Camera Camera = new Camera(); + public Settings Settings { get; set; } = new Settings(); +} + +class Settings +{ + public int GameScale = 4; + public int UIScale = 4; + public bool ShowCollision = true; +} + +class GameState +{ + public List Players { get; set; } = new List(); + public World World { get; set; } +} diff --git a/src/state/Player.cs b/src/state/Player.cs new file mode 100644 index 0000000..8a40db7 --- /dev/null +++ b/src/state/Player.cs @@ -0,0 +1,12 @@ +class Player +{ + public string Name; + public Vector2 Position; + public Vector2 Movement; + public Guid Guid; + + public Line GetBottomCollisionLine() + { + return new Line(Position, Position + new Vector2(16, 0)); + } +}