From a6dea8efbcb70c7a2f7bcf8b493018abaabeb9aa Mon Sep 17 00:00:00 2001 From: CommonLoon102 <321850+CommonLoon102@users.noreply.github.com> Date: Mon, 27 Jan 2020 12:23:00 +0000 Subject: [PATCH] Mods and home page (#3) * add some mods * refactor * user friendly home page added * refactor * update readme * update readme --- .gitignore | 5 +- Common/Common.csproj | 4 + Common/Constants.cs | 11 +- Common/NBloodServerStartInfo.cs | 7 +- Model/Mod.cs | 21 +++ Model/Server.cs | 1 + README.md | 49 +++++- Supervisor/PublicServerManager.cs | 51 +++--- WebInterface/CommandLineUtils.cs | 17 ++ WebInterface/Controllers/HomeController.cs | 26 +++ WebInterface/Controllers/NBloodController.cs | 81 +++------- .../ListServersResponse.cs | 0 WebInterface/{ViewModel => Models}/Player.cs | 0 WebInterface/{ViewModel => Models}/Server.cs | 1 + .../{ViewModel => Models}/ServerParameters.cs | 1 + .../StartServerResponse.cs | 0 WebInterface/Services/IListServersService.cs | 12 ++ WebInterface/Services/ListServersService.cs | 64 ++++++++ WebInterface/Startup.cs | 3 + WebInterface/Views/Home/Index.cshtml | 153 ++++++++++++++++++ WebInterface/appsettings.json | 2 +- 21 files changed, 416 insertions(+), 93 deletions(-) create mode 100644 Model/Mod.cs create mode 100644 WebInterface/CommandLineUtils.cs create mode 100644 WebInterface/Controllers/HomeController.cs rename WebInterface/{ViewModel => Models}/ListServersResponse.cs (100%) rename WebInterface/{ViewModel => Models}/Player.cs (100%) rename WebInterface/{ViewModel => Models}/Server.cs (93%) rename WebInterface/{ViewModel => Models}/ServerParameters.cs (85%) rename WebInterface/{ViewModel => Models}/StartServerResponse.cs (100%) create mode 100644 WebInterface/Services/IListServersService.cs create mode 100644 WebInterface/Services/ListServersService.cs create mode 100644 WebInterface/Views/Home/Index.cshtml diff --git a/.gitignore b/.gitignore index 4ce6fdd..7e324c3 100644 --- a/.gitignore +++ b/.gitignore @@ -337,4 +337,7 @@ ASALocalRun/ .localhistory/ # BeatPulse healthcheck temp database -healthchecksdb \ No newline at end of file +healthchecksdb +/WebInterface/.config/dotnet-tools.json +/output +/publish diff --git a/Common/Common.csproj b/Common/Common.csproj index 9f5c4f4..c720296 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -4,4 +4,8 @@ netstandard2.0 + + + + diff --git a/Common/Constants.cs b/Common/Constants.cs index dd66f5b..3c89c45 100644 --- a/Common/Constants.cs +++ b/Common/Constants.cs @@ -1,4 +1,5 @@ -using System; +using Model; +using System; using System.Collections.Generic; using System.Text; @@ -7,5 +8,13 @@ namespace Common public class Constants { public const string NBloodExecutable = "nblood_server"; + public static readonly IReadOnlyDictionary SupportedMods = new Dictionary() + { + { "BLOOD", new Mod("BLOOD", "Blood", "") }, + { "CRYPTIC", new Mod("CRYPTIC", "Cryptic Passage", "-ini CRYPTIC.INI") }, + { "DW", new Mod("DW", "Death Wish", "-ini dw.ini") }, + { "FO", new Mod("FO", "Fleshed Out", "-ini fo.ini") }, + { "TWOIRA", new Mod("TWOIRA", "The Way of Ira", "-ini TWOIRA/twoira.ini -j=TWOIRA") }, + }; } } diff --git a/Common/NBloodServerStartInfo.cs b/Common/NBloodServerStartInfo.cs index c6878ba..9e8c0b0 100644 --- a/Common/NBloodServerStartInfo.cs +++ b/Common/NBloodServerStartInfo.cs @@ -1,4 +1,5 @@ -using System; +using Model; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -12,9 +13,9 @@ namespace Common { private static readonly string workingDir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "blood"); - public static ProcessStartInfo Get(int maxPlayers, int port) + public static ProcessStartInfo Get(int maxPlayers, int port, Mod mod) { - var psi = new ProcessStartInfo(GetExecutable(), $"-server {maxPlayers} -port {port} -pname Server") + var psi = new ProcessStartInfo(GetExecutable(), $"-server {maxPlayers} -port {port} -pname Server {mod.CommandLine}") { UseShellExecute = true, WorkingDirectory = workingDir diff --git a/Model/Mod.cs b/Model/Mod.cs new file mode 100644 index 0000000..a2b6108 --- /dev/null +++ b/Model/Mod.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Model +{ + [Serializable] + public class Mod + { + public string Name { get; } + public string FriendlyName { get; } + public string CommandLine { get; } + + public Mod(string name, string friendlyName, string cmdLine) + { + Name = name; + FriendlyName = friendlyName; + CommandLine = cmdLine; + } + } +} diff --git a/Model/Server.cs b/Model/Server.cs index 273602a..244fa09 100644 --- a/Model/Server.cs +++ b/Model/Server.cs @@ -16,6 +16,7 @@ namespace Model public int CurrentPlayers { get; set; } public int MaximumPlayers { get; set; } public string GameType { get; set; } + public Mod Mod { get; set; } public IList Players { get; set; } = new List(); } } diff --git a/README.md b/README.md index d5a0806..0a24150 100644 --- a/README.md +++ b/README.md @@ -25,21 +25,52 @@ After you start the container, the following will happen: 6. Start `WebInterface.exe` 7. Attach the debugger to `WebInterface.exe` and/or `Supervisor.exe` 8. You can call the following URLs with your web browser or Postman: +- http://localhost:5000/nblood/home - http://localhost:5000/nblood/api/listservers -- http://localhost:5000/nblood/api/startserver?players=3&ApiKey=CHANGEME +- http://localhost:5000/nblood/api/startserver?players=3&modName=cryptic&apiKey=CHANGEME ## Deploy the server onto GNU/Linux 1. Install Docker and wget (if you don't have already), for example like this: `sudo snap install docker && sudo apt install wget -y` 2. Download the Dockerfile: `wget https://raw.githubusercontent.com/CommonLoon102/NBloodServerSupervisor/master/Dockerfile --directory-prefix=supervisor` 3. Build the Docker image: `sudo docker build -t nblood-supervisor:latest supervisor` -4. Navigate to your Blood 1.21 directory where you have these files: +4. Navigate to your Blood 1.21 directory where you have the below files. +The files are from stock Blood 1.21, Cryptic Passage, Death Wish 1.6.10, The Way of Ira 1.0.1, Fleshed Out 1.3 - BLOOD.INI - BLOOD.RFF +- CP01.MAP-CP09.MAP +- CPART07.AR_ (Fresh Supply owners need to copy tiles007.ART from `\addons\Cryptic Passage` and rename it) +- CPART15.AR_ (Fresh Supply owners need to copy tiles015.ART from `\addons\Cryptic Passage` and rename it) +- CPBB01.MAP-CPBB04.MAP +- CPSL.MAP +- CRYPTIC.INI +- dw.ini +- DWBB1.MAP-DWBB3.MAP +- DWE1M1.MAP-DWE1M12.MAP +- DWE2M1.MAP-DWE2M12.MAP +- DWE3M1.MAP-DWE3M12.MAP +- fo.INI +- fo1m1.MAP-fo1m8.MAP - GUI.RFF - SOUNDS.RFF - SURFACE.DAT - TILES000.ART-TILES017.ART +- TWOIRA (folder, see below) - VOXEL.DAT + +You need a folder in your Blood folder, named `TWOIRA`, and inside that, these files: +- IRA01.MAP +- IRA02_A.MAP +- IRA02_B.MAP +- IRA03.MAP +- IRA04.MAP +- IRA05.MAP +- IRA06.MAP +- IRA07.MAP +- IRA08.MAP +- SURFACE.DAT +- TILES18.ART +- twoira.ini + 5. Run a Docker container from there: `sudo docker run --volume "$PWD":/supervisor/publish/blood --network=host --detach nblood-supervisor` 6. Optional: You can see the ApiKey here: - `sudo docker run -it nblood-supervisor /bin/bash` @@ -47,12 +78,14 @@ After you start the container, the following will happen: - `exit` ## Usage -You can list the currently running public servers via this URL: +User friendly homepage: +http://your.ip.goes.here:23580/nblood/home +You can list the currently running public servers via this API: http://your.ip.goes.here:23580/nblood/api/listservers -You can start new private servers via this URL: - -http://your.ip.goes.here:23580/nblood/api/startserver?players=3&ApiKey=the_actual_apikey_here - -The number of players must be at least 3 and maximum 8. The servers started with this URL won't be visible publicly via the `listservers` URL. You can see the port and the command line command to join in the response. +You can start new private servers via this API: +http://your.ip.goes.here:23580/nblood/api/startserver?players=3&modName=cryptic&apiKey=the_actual_apikey_here +The number of players must be at least 3 and maximum 8. The servers started with this URL won't be visible publicly via the `listservers` URL. +The modName parameter can be `cryptic`, `dw`, `fo`, `twoira` or it can be missing. +You can see the port and the command line command to join in the response. diff --git a/Supervisor/PublicServerManager.cs b/Supervisor/PublicServerManager.cs index cbb11c8..63a03cb 100644 --- a/Supervisor/PublicServerManager.cs +++ b/Supervisor/PublicServerManager.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; namespace Supervisor { - class PublicServerManager + static class PublicServerManager { public static void Start() { @@ -39,36 +39,43 @@ namespace Supervisor private static void LaunchNewServersWhenNeeded() { - const int maxPlayers = 8; - - for (int i = 3; i <= maxPlayers; i++) + foreach (Mod mod in Constants.SupportedMods.Values) { - if (IsNewServerNeeded(i)) + const int maxPlayers = 8; + for (int i = 3; i <= maxPlayers; i++) { - int port = PortUtils.GetPort(); - var process = Process.Start(NBloodServerStartInfo.Get(i, port)); - Program.State.Servers.AddOrUpdate(port, new Server() + if (IsNewServerNeeded(i, mod)) { - Port = port, - ProcessId = process.Id, - MaximumPlayers = i, - CurrentPlayers = 1, - }, - (prt, server) => - { - server.ProcessId = process.Id; - return server; - }); + int port = PortUtils.GetPort(); + var process = Process.Start(NBloodServerStartInfo.Get(i, port, mod)); + Program.State.Servers.AddOrUpdate(port, new Server() + { + Port = port, + ProcessId = process.Id, + MaximumPlayers = i, + CurrentPlayers = 1, + Mod = mod, + }, + (prt, server) => + { + server.ProcessId = process.Id; + server.MaximumPlayers = i; + server.CurrentPlayers = 1; + server.Mod = mod; + return server; + }); + } } - - Thread.Sleep(TimeSpan.FromSeconds(2)); } } - private static bool IsNewServerNeeded(int i) + private static bool IsNewServerNeeded(int i, Mod mod) { return !Program.State.Servers.Values.Any(s => - !s.IsPrivate && s.MaximumPlayers == i && s.CurrentPlayers < s.MaximumPlayers); + !s.IsPrivate + && s.Mod.Name == mod.Name + && s.MaximumPlayers == i + && s.CurrentPlayers < s.MaximumPlayers); } } } diff --git a/WebInterface/CommandLineUtils.cs b/WebInterface/CommandLineUtils.cs new file mode 100644 index 0000000..a5f85b5 --- /dev/null +++ b/WebInterface/CommandLineUtils.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Http; +using Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WebInterface +{ + public static class CommandLineUtils + { + public static string GetLaunchCommand(string host, int port, Mod mod) + { + return $"nblood -client {host} -port {port} {mod.CommandLine}"; + } + } +} diff --git a/WebInterface/Controllers/HomeController.cs b/WebInterface/Controllers/HomeController.cs new file mode 100644 index 0000000..5c749c7 --- /dev/null +++ b/WebInterface/Controllers/HomeController.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using WebInterface.Services; + +namespace WebInterface.Controllers +{ + public class HomeController : Controller + { + IListServersService _serversList; + + public HomeController(IListServersService serversList) + { + _serversList = serversList; + } + + [Route("nblood/home")] + public IActionResult Index() + { + var viewModel = _serversList.ListServers(HttpContext.Request.Host.Host).Servers; + return View(viewModel); + } + } +} \ No newline at end of file diff --git a/WebInterface/Controllers/NBloodController.cs b/WebInterface/Controllers/NBloodController.cs index 392a88a..1ea2607 100644 --- a/WebInterface/Controllers/NBloodController.cs +++ b/WebInterface/Controllers/NBloodController.cs @@ -1,20 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Runtime.Serialization.Formatters.Binary; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Common; +using Common; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Model; +using System; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using WebInterface.Services; namespace WebInterface.Controllers { @@ -24,22 +21,19 @@ namespace WebInterface.Controllers private static bool _isBusy = false; private static DateTime _lastRefresh = DateTime.MinValue; private static ListServersResponse _lastServerList = null; - private static readonly object _locker = new object(); private readonly ILogger _logger; private readonly IConfiguration _config; - - private const int listenPort = 11029; - private static readonly IPEndPoint remoteIP = new IPEndPoint(IPAddress.Loopback, listenPort); - private static readonly UdpClient udpClient = new UdpClient(remoteIP); + private readonly IListServersService _listServersService; private static readonly Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); private static readonly IPEndPoint webApiListenerEndPoint = new IPEndPoint(IPAddress.Loopback, 11028); - public NBloodController(ILogger logger, IConfiguration config) + public NBloodController(ILogger logger, IConfiguration config, IListServersService listServersService) { _logger = logger; _config = config; + _listServersService = listServersService; } [HttpGet] @@ -69,9 +63,10 @@ namespace WebInterface.Controllers if (serversRunning >= _config.GetValue("MaximumServers")) return new StartServerResponse("The maximum number of servers are already running."); + Mod mod = GetMod(parameters.ModName); int port = PortUtils.GetPort(); - var process = Process.Start(NBloodServerStartInfo.Get(parameters.Players, port)); + var process = Process.Start(NBloodServerStartInfo.Get(parameters.Players, port, mod)); byte[] payload = Encoding.ASCII.GetBytes($"B{port}\t{process.Id}\0"); socket.SendTo(payload, webApiListenerEndPoint); @@ -79,7 +74,7 @@ namespace WebInterface.Controllers parameters.Players, port); Thread.Sleep(TimeSpan.FromSeconds(2)); - return new StartServerResponse(port) { CommandLine = GetCommandLine(port) }; + return new StartServerResponse(port) { CommandLine = CommandLineUtils.GetLaunchCommand(HttpContext.Request.Host.Host, port, mod) }; } catch (Exception ex) { @@ -98,32 +93,10 @@ namespace WebInterface.Controllers { try { - if (DateTime.UtcNow - _lastRefresh > TimeSpan.FromSeconds(5) + if (DateTime.UtcNow - _lastRefresh > TimeSpan.FromSeconds(1) || _lastServerList == null) { - byte[] payload = Encoding.ASCII.GetBytes($"A"); - byte[] response; - lock (_locker) - { - socket.SendTo(payload, webApiListenerEndPoint); - response = udpClient.ReceiveAsync().Result.Buffer; - } - - StateResponse stateResponse = (StateResponse)ByteArrayToObject(response); - var webResponse = new ListServersResponse(); - webResponse.Servers = stateResponse.Servers.Where(s => !s.IsPrivate).Select(s => new Server() - { - Port = s.Port, - IsStarted = s.IsStarted, - CommandLine = s.CurrentPlayers == s.MaximumPlayers ? "Sorry, the game is already started." : GetCommandLine(s.Port), - GameType = s.GameType, - CurrentPlayers = s.CurrentPlayers, - MaximumPlayers = s.MaximumPlayers, - Players = s.Players.Select(p => new Player() { Name = p.Name, Score = p.Score }).ToList(), - SpawnedAtUtc = s.SpawnedAtUtc - }).OrderBy(s => s.MaximumPlayers).ToList(); - - _lastServerList = webResponse; + _lastServerList = _listServersService.ListServers(HttpContext.Request.Host.Host); _lastRefresh = DateTime.UtcNow; } @@ -136,21 +109,15 @@ namespace WebInterface.Controllers } } - private static object ByteArrayToObject(byte[] arrBytes) + private Mod GetMod(string modName) { - using (var memStream = new MemoryStream()) - { - var binForm = new BinaryFormatter(); - memStream.Write(arrBytes, 0, arrBytes.Length); - memStream.Seek(0, SeekOrigin.Begin); - var obj = binForm.Deserialize(memStream); - return obj; - } - } + if (string.IsNullOrWhiteSpace(modName)) + return Constants.SupportedMods["BLOOD"]; - private string GetCommandLine(int port) - { - return $"nblood -client {HttpContext.Request.Host.Host} -port {port}"; + if (!Constants.SupportedMods.ContainsKey(modName.ToUpper())) + throw new Exception("This mod is not supported: " + modName); + + return Constants.SupportedMods[modName.ToUpper()]; } } } \ No newline at end of file diff --git a/WebInterface/ViewModel/ListServersResponse.cs b/WebInterface/Models/ListServersResponse.cs similarity index 100% rename from WebInterface/ViewModel/ListServersResponse.cs rename to WebInterface/Models/ListServersResponse.cs diff --git a/WebInterface/ViewModel/Player.cs b/WebInterface/Models/Player.cs similarity index 100% rename from WebInterface/ViewModel/Player.cs rename to WebInterface/Models/Player.cs diff --git a/WebInterface/ViewModel/Server.cs b/WebInterface/Models/Server.cs similarity index 93% rename from WebInterface/ViewModel/Server.cs rename to WebInterface/Models/Server.cs index 0131232..d725b6d 100644 --- a/WebInterface/ViewModel/Server.cs +++ b/WebInterface/Models/Server.cs @@ -14,6 +14,7 @@ namespace WebInterface public int CurrentPlayers { get; set; } public int MaximumPlayers { get; set; } public string GameType { get; set; } + public string Mod { get; set; } public IList Players { get; set; } = new List(); } } diff --git a/WebInterface/ViewModel/ServerParameters.cs b/WebInterface/Models/ServerParameters.cs similarity index 85% rename from WebInterface/ViewModel/ServerParameters.cs rename to WebInterface/Models/ServerParameters.cs index e38c370..28e046f 100644 --- a/WebInterface/ViewModel/ServerParameters.cs +++ b/WebInterface/Models/ServerParameters.cs @@ -9,5 +9,6 @@ namespace WebInterface { public string ApiKey { get; set; } public int Players { get; set; } + public string ModName { get; set; } } } diff --git a/WebInterface/ViewModel/StartServerResponse.cs b/WebInterface/Models/StartServerResponse.cs similarity index 100% rename from WebInterface/ViewModel/StartServerResponse.cs rename to WebInterface/Models/StartServerResponse.cs diff --git a/WebInterface/Services/IListServersService.cs b/WebInterface/Services/IListServersService.cs new file mode 100644 index 0000000..881873a --- /dev/null +++ b/WebInterface/Services/IListServersService.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WebInterface.Services +{ + public interface IListServersService + { + ListServersResponse ListServers(string host); + } +} diff --git a/WebInterface/Services/ListServersService.cs b/WebInterface/Services/ListServersService.cs new file mode 100644 index 0000000..4c95c95 --- /dev/null +++ b/WebInterface/Services/ListServersService.cs @@ -0,0 +1,64 @@ +using Model; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; + +namespace WebInterface.Services +{ + public class ListServersService : IListServersService + { + private const int listenPort = 11029; + + private static readonly object _locker = new object(); + private static readonly IPEndPoint remoteIP = new IPEndPoint(IPAddress.Loopback, listenPort); + private static readonly UdpClient udpClient = new UdpClient(remoteIP); + + private static readonly Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + private static readonly IPEndPoint webApiListenerEndPoint = new IPEndPoint(IPAddress.Loopback, 11028); + + public ListServersResponse ListServers(string host) + { + byte[] payload = Encoding.ASCII.GetBytes($"A"); + byte[] response; + lock (_locker) + { + socket.SendTo(payload, webApiListenerEndPoint); + response = udpClient.ReceiveAsync().Result.Buffer; + } + + StateResponse stateResponse = (StateResponse)ByteArrayToObject(response); + var serversResponse = new ListServersResponse + { + Servers = stateResponse.Servers.Where(s => !s.IsPrivate).Select(s => new Server() + { + Port = s.Port, + IsStarted = s.IsStarted, + CommandLine = s.CurrentPlayers == s.MaximumPlayers ? "Sorry, the game is already started." : CommandLineUtils.GetLaunchCommand(host, s.Port, s.Mod), + GameType = s.GameType, + Mod = s.Mod.FriendlyName, + CurrentPlayers = s.CurrentPlayers, + MaximumPlayers = s.MaximumPlayers, + Players = s.Players.Select(p => new Player() { Name = p.Name, Score = p.Score }).ToList(), + SpawnedAtUtc = s.SpawnedAtUtc + }).OrderBy(s => s.MaximumPlayers).ToList() + }; + + return serversResponse; + } + + private static object ByteArrayToObject(byte[] arrBytes) + { + using (var memStream = new MemoryStream()) + { + var binForm = new BinaryFormatter(); + memStream.Write(arrBytes, 0, arrBytes.Length); + memStream.Seek(0, SeekOrigin.Begin); + var obj = binForm.Deserialize(memStream); + return obj; + } + } + } +} diff --git a/WebInterface/Startup.cs b/WebInterface/Startup.cs index d8a0cc3..30a51a6 100644 --- a/WebInterface/Startup.cs +++ b/WebInterface/Startup.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using WebInterface.Services; namespace WebInterface { @@ -37,6 +38,8 @@ namespace WebInterface public void ConfigureServices(IServiceCollection services) { services.AddControllers(); + services.AddControllersWithViews(); + services.Add(new ServiceDescriptor(typeof(IListServersService), typeof(ListServersService), ServiceLifetime.Singleton)); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/WebInterface/Views/Home/Index.cshtml b/WebInterface/Views/Home/Index.cshtml new file mode 100644 index 0000000..0975292 --- /dev/null +++ b/WebInterface/Views/Home/Index.cshtml @@ -0,0 +1,153 @@ +@model IEnumerable + +@{ + Layout = null; + string b = "Blood"; + string cp = "Cryptic Passage"; + string dw = "Death Wish"; + string fo = "Fleshed Out"; + string twoira = "The Way of Ira"; + + Func + ListServers = @
+ @foreach (var server in Model.Where(s => s.Mod == item).OrderBy(s => s.MaximumPlayers)) + { +
+

Players: @(server.CurrentPlayers - 1)/@(server.MaximumPlayers - 1)

+
+ @if (server.IsStarted) + { +

Game Type: @server.GameType

+
+ @foreach (var player in server.Players.Skip(1)) + { +

@player.Name: @player.Score

+ } +
+ } + else + { + @if (server.CurrentPlayers < server.MaximumPlayers) + { +

Command to join: @server.CommandLine

+ } + } +
+
+ } +
; +} + + + + + + + Blood servers + + + +

The client EXE

+

Use this exe to connect: https://lerppu.net/wannabethesis/nblood/20200113-2073/

+ +

@b 1.21

+
+ Show @b version 1.21 servers +

The below files must be in your Blood directory.

+
    +
  • BLOOD.INI
  • +
  • BLOOD.RFF
  • +
  • GUI.RFF
  • +
  • SOUNDS.RFF
  • +
  • SURFACE.DAT
  • +
  • TILES000.ART-TILES017.ART
  • +
  • VOXEL.DAT
  • +
+ @ListServers(@b) +
+
+

@cp

+
+ Show @cp servers +

The below files must be in your Blood directory.

+
    +
  • CP01-CP09.MAP
  • +
  • CPART07.AR_ (Fresh Supply owners need to copy tiles007.ART from \addons\Cryptic Passage and rename it
  • +
  • CPART15.AR_ (Fresh Supply owners need to copy tiles015.ART from \addons\Cryptic Passage and rename it
  • +
  • CPBB01.MAP-CPBB04.MAP
  • +
  • CPSL.MAP
  • +
  • CRYPTIC.INI
  • +
+

Don't forget to send back the ferry every time at the end of the last map, otherwise you cannot go back if the boss kills you!

+ @ListServers(@cp) +
+
+

@dw 1.6.10

+
+ Show @dw version 1.6.10 servers +

The below files must be in your Blood directory.

+
    +
  • dw.ini
  • +
  • DWBB1.MAP-DWBB3.MAP
  • +
  • DWE1M1.MAP-DWE1M12.MAP
  • +
  • DWE2M1.MAP-DWE2M12.MAP
  • +
  • DWE3M1.MAP-DWE3M12.MAP
  • +
+ @ListServers(@dw) +
+
+

@twoira 1.0.1

+
+ Show @twoira version 1.0.1 servers +

@twoira

+

The below folder (TWOIRA) must be in your Blood directory, and inside that the other additional files.

+
    +
  • TWOIRA
  • +
  • +
      +
    • IRA01.MAP
    • +
    • IRA02_A.MAP
    • +
    • IRA02_B.MAP
    • +
    • IRA03.MAP
    • +
    • IRA04.MAP
    • +
    • IRA05.MAP
    • +
    • IRA06.MAP
    • +
    • IRA07.MAP
    • +
    • IRA08.MAP
    • +
    • SURFACE.DAT
    • +
    • TILES18.ART
    • +
    • twoira.ini
    • +
    +
  • +
+ @ListServers(twoira) +
+

@fo 1.3

+
+ Show @fo version 1.3 servers +

The below files must be in your Blood directory.

+
    +
  • fo.INI
  • +
  • fo1m1.MAP-fo1m8.MAP
  • +
+

Not tested in co-op! But I hope it works! :)

+ @ListServers(@fo) +
+
+
+

Do you think this page is ugly? You are right! If you want to make it better, PRs are welcomed (but please, don't use any JavaScript, thanks): https://github.com/CommonLoon102/NBloodServerSupervisor

+ + diff --git a/WebInterface/appsettings.json b/WebInterface/appsettings.json index e10280b..e708deb 100644 --- a/WebInterface/appsettings.json +++ b/WebInterface/appsettings.json @@ -7,6 +7,6 @@ } }, "AllowedHosts": "*", - "MaximumServers": 20, + "MaximumServers": 80, "ApiKey": "CHANGEME" }