Compare commits

...

3 commits

Author SHA1 Message Date
Vinzenz Schroeter
4666eb31ec update README.md 2025-09-29 18:56:38 +02:00
Vinzenz Schroeter
dfa7cb5cb1 server starts now, add app 2025-09-29 18:43:25 +02:00
Vinzenz Schroeter
a5a3f2cc81 add assets and frontend to backend package 2025-09-29 18:34:14 +02:00
9 changed files with 125 additions and 53 deletions

View file

@ -2,7 +2,56 @@
<!-- TODO: image --> <!-- TODO: image -->
### Backend ## Building and running
### With Nix
Using the power of nix, you can just `nix run git+https://git.berlin.ccc.de/vinzenz/servicepoint-tanks`!
To build from local source:
```bash
# no submodules required
git clone https://github.com/kaesaecracker/servicepoint-tanks.git
cd servicepoint-tanks
nix build .#
result/bin/TanksServer
```
You can also use the provided devShells (`nix develop .#`) and follow the manual steps.
For proper IDE suggestions, you may need to initialize the submodules. They are not used for building by Nix though.
### The hard way
```bash
# initialize the submodules
git clone https://github.com/kaesaecracker/servicepoint-tanks.git
cd servicepoint-tanks
git submodule update --init --recursive
# build
cd tank-frontent
npm install
npm run build
cd ../tanks-backend
dotnet build
```
The Docker builds are currently broken and need some updates for the new library repo structure.
<!-- TODO currently broken
# build with docker/podman - probably broken rifht
cd tanks-backend
docker build .
cd ../tank-frontend
docker build .
cd ..
docker build .
```
-->
## Backend
<!-- TODO: image --> <!-- TODO: image -->
@ -18,7 +67,7 @@
- some values (like tank speed) can be configured but are fixed at run time - some values (like tank speed) can be configured but are fixed at run time
- By default, the backend also hosts the frontend - By default, the backend also hosts the frontend
### Frontend ## Frontend
<!-- TODO: image --> <!-- TODO: image -->
@ -28,9 +77,9 @@
- Sends user input to server - Sends user input to server
- real time communication via WebSockets, HTTP for the REST - real time communication via WebSockets, HTTP for the REST
### Binary formats ## Binary formats
#### Controls WebSocket ### Controls WebSocket
- Client sends 2 byte messages. - Client sends 2 byte messages.
- on or off: `0x01` or `0x02` - on or off: `0x01` or `0x02`
@ -55,31 +104,3 @@
- 10: bullet - 10: bullet
- 11: (reserved) - 11: (reserved)
- client responds with empty message to request the next frame - client responds with empty message to request the next frame
# Building
```bash
# checkout repo and submodules
git clone https://github.com/kaesaecracker/servicepoint-tanks.git
cd servicepoint-tanks
git submodule update --init
cd tanks-backend/servicepoint
git submodule update --init
cd ../..
# build with nix-shell
nix-shell
cd tank-frontent
npm install
npm run build
cd ../tanks-backend
dotnet build
# build with docker/podman
cd tanks-backend
docker build .
cd ../tank-frontend
docker build .
cd ..
docker build .
```

View file

@ -5,13 +5,17 @@
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-25.05"; nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-25.05";
binding = { binding = {
url = "git+https://git.berlin.ccc.de/servicepoint/servicepoint-binding-csharp.git"; url = "git+https://git.berlin.ccc.de/servicepoint/servicepoint-binding-csharp.git";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
}; };
outputs = outputs =
{ self, nixpkgs, binding }: {
self,
nixpkgs,
binding,
}:
let let
supported-systems = [ supported-systems = [
"x86_64-linux" "x86_64-linux"
@ -31,6 +35,20 @@
); );
in in
{ {
apps = forAllSystems (
{
pkgs,
lib,
selfPkgs,
...
}:
{
default = {
type = "app";
program = "${lib.getBin selfPkgs.servicepoint-tanks}/bin/TanksServer";
};
}
);
devShells = forAllSystems ( devShells = forAllSystems (
{ {
pkgs, pkgs,
@ -47,7 +65,7 @@
]; ];
}; };
backend-set = { backend-set = {
inputsFrom = [ selfPkgs.servicepoint-tanks-backend ]; inputsFrom = [ selfPkgs.servicepoint-tanks ];
packages = with pkgs; [ packages = with pkgs; [
nuget-to-json nuget-to-json
cargo-tarpaulin cargo-tarpaulin
@ -71,7 +89,7 @@
}: }:
{ {
servicepoint-tanks-frontend = pkgs.buildNpmPackage (finalAttrs: { servicepoint-tanks-frontend = pkgs.buildNpmPackage (finalAttrs: {
pname = "tank-frontend"; pname = "servicepoint-tanks-frontend";
version = "0.0.0"; version = "0.0.0";
src = ./tank-frontend; src = ./tank-frontend;
@ -82,20 +100,29 @@
''; '';
}); });
servicepoint-tanks-backend = pkgs.buildDotnetModule { servicepoint-tanks-assets = ./tanks-backend/TanksServer/assets;
pname = "servicepoint-tanks-backend";
servicepoint-tanks = pkgs.buildDotnetModule {
pname = "servicepoint-tanks";
version = "0.0.0"; version = "0.0.0";
dotnet-sdk = pkgs.dotnetCorePackages.sdk_8_0; dotnet-sdk = pkgs.dotnetCorePackages.sdk_8_0;
dotnet-runtime = pkgs.dotnetCorePackages.runtime_8_0; dotnet-runtime = pkgs.dotnetCorePackages.runtime_8_0;
src = ./tanks-backend; src = ./tanks-backend;
projectFile = "TanksServer/TanksServer.csproj"; projectFile = "TanksServer/TanksServer.csproj";
nugetDeps = ./tanks-backend/deps.json; nugetDeps = ./tanks-backend/deps.json;
selfContainedBuild = true; selfContainedBuild = true;
buildInputs = [ bindingPkgs.servicepoint-binding-csharp ]; buildInputs = [ bindingPkgs.servicepoint-binding-csharp ];
runtimeDeps = [ bindingPkgs.servicepoint-binding-uniffi ];
makeWrapperArgs = [
"--set-default TANKSSERVER_CLIENT ${selfPkgs.servicepoint-tanks-frontend}"
"--set-default TANKSSERVER_ASSETS ${selfPkgs.servicepoint-tanks-assets}"
];
}; };
} }
); );

View file

@ -4,7 +4,7 @@ namespace TanksServer.GameLogic;
internal sealed class CollideBullets : ITickStep internal sealed class CollideBullets : ITickStep
{ {
private readonly Sprite _explosiveSprite = Sprite.FromImageFile("assets/explosion.png"); private readonly Sprite _explosiveSprite = Sprite.FromImageFile(Path.Combine(Program.AssetsDir, "explosion.png"));
private readonly Predicate<Bullet> _removeBulletsPredicate; private readonly Predicate<Bullet> _removeBulletsPredicate;
private readonly MapEntityManager _entityManager; private readonly MapEntityManager _entityManager;
private readonly MapService _map; private readonly MapService _map;

View file

@ -22,9 +22,10 @@ internal sealed class MapService
public MapService() public MapService()
{ {
foreach (var file in Directory.EnumerateFiles("./assets/maps/", "*.txt")) var dir = Path.Combine(Program.AssetsDir, "maps");
foreach (var file in Directory.EnumerateFiles(dir, "*.txt"))
LoadMapString(file); LoadMapString(file);
foreach (var file in Directory.EnumerateFiles("./assets/maps/", "*.png")) foreach (var file in Directory.EnumerateFiles(dir, "*.png"))
LoadMapPng(file); LoadMapPng(file);
Current = GetRandomMap(); Current = GetRandomMap();
} }

View file

@ -1,6 +1,7 @@
global using System; global using System;
global using System.Collections.Concurrent; global using System.Collections.Concurrent;
global using System.Collections.Generic; global using System.Collections.Generic;
global using System.IO;
global using System.Linq; global using System.Linq;
global using System.Threading; global using System.Threading;
global using System.Threading.Tasks; global using System.Threading.Tasks;

View file

@ -4,11 +4,11 @@ namespace TanksServer.Graphics;
internal sealed class DrawPowerUpsStep(MapEntityManager entityManager) : IDrawStep internal sealed class DrawPowerUpsStep(MapEntityManager entityManager) : IDrawStep
{ {
private readonly Sprite _genericSprite = Sprite.FromImageFile("assets/powerup_generic.png"); private readonly Sprite _genericSprite = Sprite.FromImageFile(Path.Combine(Program.AssetsDir, "powerup_generic.png"));
private readonly Sprite _smartSprite = Sprite.FromImageFile("assets/powerup_smart.png"); private readonly Sprite _smartSprite = Sprite.FromImageFile(Path.Combine(Program.AssetsDir, "powerup_smart.png"));
private readonly Sprite _magazineSprite = Sprite.FromImageFile("assets/powerup_magazine.png"); private readonly Sprite _magazineSprite = Sprite.FromImageFile(Path.Combine(Program.AssetsDir, "powerup_magazine.png"));
private readonly Sprite _explosiveSprite = Sprite.FromImageFile("assets/powerup_explosive.png"); private readonly Sprite _explosiveSprite = Sprite.FromImageFile(Path.Combine(Program.AssetsDir, "powerup_explosive.png"));
private readonly Sprite _fastSprite = Sprite.FromImageFile("assets/powerup_fastbullet.png"); private readonly Sprite _fastSprite = Sprite.FromImageFile(Path.Combine(Program.AssetsDir, "powerup_fastbullet.png"));
public void Draw(GamePixelGrid pixels) public void Draw(GamePixelGrid pixels)
{ {

View file

@ -5,7 +5,7 @@ namespace TanksServer.Graphics;
internal sealed class DrawTanksStep(MapEntityManager entityManager) : IDrawStep internal sealed class DrawTanksStep(MapEntityManager entityManager) : IDrawStep
{ {
private readonly SpriteSheet _tankSprites = private readonly SpriteSheet _tankSprites =
SpriteSheet.FromImageFile("assets/tank.png", (int)MapService.TileSize, (int)MapService.TileSize); SpriteSheet.FromImageFile(Path.Combine(Program.AssetsDir, "tank.png"), (int)MapService.TileSize, (int)MapService.TileSize);
public void Draw(GamePixelGrid pixels) public void Draw(GamePixelGrid pixels)
{ {

View file

@ -11,22 +11,44 @@ namespace TanksServer;
public static class Program public static class Program
{ {
internal static string AssetsDir = (Environment.GetEnvironmentVariable("TANKSSERVER_ASSETS") ?? "./assets") + "/";
[RequiresUnreferencedCode("Calls Endpoints.Map")] [RequiresUnreferencedCode("Calls Endpoints.Map")]
[RequiresDynamicCode("Calls Endpoints.Map")] [RequiresDynamicCode("Calls Endpoints.Map")]
public static async Task Main(string[] args) public static async Task Main(string[] args)
{ {
var app = Configure(args); var app = Configure(args);
var clientFileProvider = new PhysicalFileProvider(Path.Combine(app.Environment.ContentRootPath, "client")); app.Logger.LogInformation("Running in {}", app.Environment.ContentRootPath);
app.UseDefaultFiles(new DefaultFilesOptions { FileProvider = clientFileProvider }); AddStaticClientHost(app);
app.UseStaticFiles(new StaticFileOptions { FileProvider = clientFileProvider });
app.Services.GetRequiredService<Endpoints>().Map(app); app.Services.GetRequiredService<Endpoints>().Map(app);
await app.RunAsync(); await app.RunAsync();
} }
private static void AddStaticClientHost(WebApplication app)
{
var clientDir = Environment.GetEnvironmentVariable("TANKSSERVER_CLIENT");
bool required = clientDir != null;
clientDir ??= Path.Combine(app.Environment.ContentRootPath, "client");
bool available = Directory.Exists(clientDir);
if (!available)
{
if (required)
throw new InvalidOperationException($"The environment variable TANKSSERVER_CLIENT is set, but the specified directory {clientDir} does not exist.");
app.Logger.LogError("Not providing static file host for client because {} does not exist", clientDir);
return;
}
var clientFileProvider = new PhysicalFileProvider(clientDir);
app.UseDefaultFiles(new DefaultFilesOptions { FileProvider = clientFileProvider });
app.UseStaticFiles(new StaticFileOptions { FileProvider = clientFileProvider });
}
[RequiresUnreferencedCode("Calls Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure<TOptions>(IConfiguration)")] [RequiresUnreferencedCode("Calls Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure<TOptions>(IConfiguration)")]
[RequiresDynamicCode("Calls Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure<TOptions>(IConfiguration)")] [RequiresDynamicCode("Calls Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure<TOptions>(IConfiguration)")]
private static WebApplication Configure(string[] args) private static WebApplication Configure(string[] args)

View file

@ -26,7 +26,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="./assets/**" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="Always"/> <None Include="./assets/**" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="Always" Condition="'$(ContinuousIntegrationBuild)'!='true'" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>