first commit
This commit is contained in:
commit
69596ab172
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
bin/
|
||||
obj/
|
||||
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 454 B |
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>mine2d</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<TrimmerDefaultAction>link</TrimmerDefaultAction>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="ppy.SDL2-CS" Version="1.0.596-alpha" />
|
||||
<PackageReference Include="WatsonTcp" Version="4.8.14.14" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="assets/*" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using WatsonTcp;
|
||||
|
||||
class Backend : IBackend
|
||||
{
|
||||
private WatsonTcpServer server;
|
||||
private Publisher publisher;
|
||||
private Queue<ValueType> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
interface IBackend
|
||||
{
|
||||
public void Process(double dt);
|
||||
public void ProcessPacket(ValueType packet);
|
||||
public void Init();
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
class Publisher
|
||||
{
|
||||
private readonly Dictionary<string, HashSet<Delegate>> 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<Delegate>();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
using WatsonTcp;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text;
|
||||
|
||||
class RemoteBackend : IBackend
|
||||
{
|
||||
private WatsonTcpClient client;
|
||||
private Publisher publisher;
|
||||
private Queue<ValueType> pendingPackets = new Queue<ValueType>();
|
||||
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<GameState>(message);
|
||||
if (packet != null)
|
||||
{
|
||||
ctx.GameState = packet;
|
||||
}
|
||||
};
|
||||
client.Connect();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
class World
|
||||
{
|
||||
public Dictionary<string, Chunk> Chunks { get; set; } = new Dictionary<string, Chunk>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
enum Tiles : int
|
||||
{
|
||||
stone = 1,
|
||||
}
|
||||
|
||||
class TileRegistry
|
||||
{
|
||||
public Dictionary<int, Tile> Tiles { get; set; } = new Dictionary<int, Tile>();
|
||||
|
||||
public void RegisterTile()
|
||||
{
|
||||
Tiles.Add(1, new Tile("stone", "stone"));
|
||||
}
|
||||
|
||||
public Tile GetTile(int id)
|
||||
{
|
||||
return Tiles[id];
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
enum Sound { }
|
||||
|
||||
class AudioPlayer
|
||||
{
|
||||
private Dictionary<Sound, byte[]> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using static SDL2.SDL_ttf;
|
||||
using static SDL2.SDL;
|
||||
|
||||
class FontManager
|
||||
{
|
||||
private Dictionary<string, IntPtr> 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];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
abstract class Game
|
||||
{
|
||||
public const int TPS = 128;
|
||||
private Queue<int> fpsQueue = new Queue<int>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
struct Line
|
||||
{
|
||||
public Vector2 start;
|
||||
public Vector2 end;
|
||||
|
||||
public Line(Vector2 start, Vector2 end)
|
||||
{
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
enum EventPriority : int
|
||||
{
|
||||
Lowest = 0,
|
||||
Low = 1,
|
||||
Normal = 2,
|
||||
High = 3,
|
||||
Highest = 4,
|
||||
Important = 5,
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
interface IFrontend
|
||||
{
|
||||
public void Process();
|
||||
public void Init();
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
interface IRenderer
|
||||
{
|
||||
public void Render();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<string>();
|
||||
Console.WriteLine("Packet type: " + packetType);
|
||||
return packetType switch
|
||||
{
|
||||
"move" => parsedRaw.ToObject<MovePacket>(),
|
||||
"connect" => parsedRaw.ToObject<ConnectPacket>(),
|
||||
_ => throw new Exception("Unknown packet type")
|
||||
};
|
||||
}
|
||||
|
||||
public static byte[] SerializePacket(ValueType packet)
|
||||
{
|
||||
var jsonString = JsonConvert.SerializeObject(packet);
|
||||
return Encoding.UTF8.GetBytes(jsonString);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Player> Players { get; set; } = new List<Player>();
|
||||
public World World { get; set; }
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue