mirror of
https://github.com/CommonLoon102/NBloodServerSupervisor.git
synced 2024-12-22 10:42:54 +01:00
add option to request private servers (#7)
This commit is contained in:
parent
c1b385c41d
commit
90ed231088
@ -1,10 +1,9 @@
|
|||||||
using Model;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Common
|
namespace Model
|
||||||
{
|
{
|
||||||
public class SpawnedServerInfo
|
public class SpawnedServerInfo
|
||||||
{
|
{
|
@ -16,7 +16,7 @@ namespace WebInterface.Controllers
|
|||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("nblood/home")]
|
[Route("nblood/home", Name = "Home")]
|
||||||
public IActionResult Index()
|
public IActionResult Index()
|
||||||
{
|
{
|
||||||
var servers = _stateService.ListServers(HttpContext.Request.Host.Host).Servers;
|
var servers = _stateService.ListServers(HttpContext.Request.Host.Host).Servers;
|
||||||
|
@ -24,16 +24,18 @@ namespace WebInterface.Controllers
|
|||||||
|
|
||||||
private readonly ILogger<NBloodController> _logger;
|
private readonly ILogger<NBloodController> _logger;
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
private readonly IStateService _listServersService;
|
private readonly IStateService _stateService;
|
||||||
|
private readonly IPrivateServerService _privateServerService;
|
||||||
|
|
||||||
private static readonly Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
public NBloodController(ILogger<NBloodController> logger,
|
||||||
private static readonly IPEndPoint webApiListenerEndPoint = new IPEndPoint(IPAddress.Loopback, 11028);
|
IConfiguration config,
|
||||||
|
IStateService stateService,
|
||||||
public NBloodController(ILogger<NBloodController> logger, IConfiguration config, IStateService listServersService)
|
IPrivateServerService privateServerService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_config = config;
|
_config = config;
|
||||||
_listServersService = listServersService;
|
_stateService = stateService;
|
||||||
|
_privateServerService = privateServerService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -42,6 +44,9 @@ namespace WebInterface.Controllers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (parameters.ApiKey != _config.GetValue<string>("ApiKey"))
|
||||||
|
return new StartServerResponse("Invalid ApiKey.");
|
||||||
|
|
||||||
Stopwatch sw = Stopwatch.StartNew();
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
while (_isBusy)
|
while (_isBusy)
|
||||||
{
|
{
|
||||||
@ -52,26 +57,16 @@ namespace WebInterface.Controllers
|
|||||||
|
|
||||||
_isBusy = true;
|
_isBusy = true;
|
||||||
|
|
||||||
if (parameters.Players < 3)
|
var serverProcess = _privateServerService.SpawnNewPrivateServer(parameters.Players, parameters.ModName);
|
||||||
parameters.Players = 3;
|
|
||||||
|
|
||||||
if (parameters.ApiKey != _config.GetValue<string>("ApiKey"))
|
|
||||||
return new StartServerResponse("Invalid ApiKey.");
|
|
||||||
|
|
||||||
SpawnedServerInfo serverProcess = ProcessSpawner.SpawnServer(parameters.Players, parameters.ModName);
|
|
||||||
byte[] payload = Encoding.ASCII.GetBytes($"B{serverProcess.Port}\t{serverProcess.Process.Id}\0");
|
|
||||||
socket.SendTo(payload, webApiListenerEndPoint);
|
|
||||||
|
|
||||||
_logger.LogInformation("Server started waiting for {0} players on port {1}.",
|
_logger.LogInformation("Server started waiting for {0} players on port {1}.",
|
||||||
parameters.Players, serverProcess.Port);
|
parameters.Players, serverProcess.Port);
|
||||||
|
|
||||||
|
string commandLine = CommandLineUtils.GetClientLaunchCommand(HttpContext.Request.Host.Host,
|
||||||
|
serverProcess.Port,
|
||||||
|
serverProcess.Mod.CommandLine);
|
||||||
|
|
||||||
Thread.Sleep(TimeSpan.FromSeconds(2));
|
Thread.Sleep(TimeSpan.FromSeconds(2));
|
||||||
return new StartServerResponse(serverProcess.Port)
|
return new StartServerResponse(serverProcess.Port, commandLine);
|
||||||
{
|
|
||||||
CommandLine = CommandLineUtils.GetClientLaunchCommand(HttpContext.Request.Host.Host,
|
|
||||||
serverProcess.Port,
|
|
||||||
serverProcess.Mod.CommandLine)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -93,7 +88,7 @@ namespace WebInterface.Controllers
|
|||||||
if (DateTime.UtcNow - _lastRefresh > TimeSpan.FromSeconds(1)
|
if (DateTime.UtcNow - _lastRefresh > TimeSpan.FromSeconds(1)
|
||||||
|| _lastServerList == null)
|
|| _lastServerList == null)
|
||||||
{
|
{
|
||||||
_lastServerList = _listServersService.ListServers(HttpContext.Request.Host.Host);
|
_lastServerList = _stateService.ListServers(HttpContext.Request.Host.Host);
|
||||||
_lastRefresh = DateTime.UtcNow;
|
_lastRefresh = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
64
WebInterface/Controllers/PrivateController.cs
Normal file
64
WebInterface/Controllers/PrivateController.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Common;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using WebInterface.Services;
|
||||||
|
|
||||||
|
namespace WebInterface.Controllers
|
||||||
|
{
|
||||||
|
public class PrivateController : Controller
|
||||||
|
{
|
||||||
|
private readonly IPrivateServerService _privateServerService;
|
||||||
|
private readonly IRateLimiterService _rateLimiterService;
|
||||||
|
|
||||||
|
public PrivateController(IPrivateServerService privateServerService, IRateLimiterService rateLimiterService)
|
||||||
|
{
|
||||||
|
_privateServerService = privateServerService;
|
||||||
|
_rateLimiterService = rateLimiterService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("nblood/private")]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
var viewModel = new PrivateViewModel
|
||||||
|
{
|
||||||
|
ModName = Constants.SupportedMods.Values.First().Name,
|
||||||
|
Players = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
return View(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("nblood/private", Name = "RequestPrivateServer")]
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public IActionResult Index(PrivateViewModel request)
|
||||||
|
{
|
||||||
|
StartServerResponse viewModel;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
throw new Exception("Something went off the rails.");
|
||||||
|
|
||||||
|
if (!_rateLimiterService.IsRequestAllowed(HttpContext.Connection.RemoteIpAddress))
|
||||||
|
throw new Exception("Sorry, you have requested too many servers recently, you need to wait some time.");
|
||||||
|
|
||||||
|
var spawnedServer = _privateServerService.SpawnNewPrivateServer(request.Players + 1, request.ModName);
|
||||||
|
string commandLine = CommandLineUtils.GetClientLaunchCommand(HttpContext.Request.Host.Host,
|
||||||
|
spawnedServer.Port,
|
||||||
|
spawnedServer.Mod.CommandLine);
|
||||||
|
|
||||||
|
viewModel = new StartServerResponse(spawnedServer.Port, commandLine);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
viewModel = new StartServerResponse(ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return View("Result", viewModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
WebInterface/Models/PrivateViewModel.cs
Normal file
22
WebInterface/Models/PrivateViewModel.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using Common;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace WebInterface
|
||||||
|
{
|
||||||
|
public class PrivateViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[Display(Name = "Required players")]
|
||||||
|
public int Players { get; set; }
|
||||||
|
[Required]
|
||||||
|
[Display(Name = "Select mod")]
|
||||||
|
public string ModName { get; set; }
|
||||||
|
public List<SelectListItem> ModNames { get; } = Constants.SupportedMods
|
||||||
|
.Select(m => new SelectListItem(m.Value.FriendlyName, m.Value.Name))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
@ -18,10 +18,11 @@ namespace WebInterface
|
|||||||
ErrorMessage = errorMessage ?? "";
|
ErrorMessage = errorMessage ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public StartServerResponse(int port)
|
public StartServerResponse(int port, string commandLine)
|
||||||
{
|
{
|
||||||
IsSuccess = true;
|
IsSuccess = true;
|
||||||
Port = port;
|
Port = port;
|
||||||
|
CommandLine = commandLine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
WebInterface/Services/IPrivateServerService.cs
Normal file
13
WebInterface/Services/IPrivateServerService.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using Model;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WebInterface.Services
|
||||||
|
{
|
||||||
|
public interface IPrivateServerService
|
||||||
|
{
|
||||||
|
SpawnedServerInfo SpawnNewPrivateServer(int players, string modName);
|
||||||
|
}
|
||||||
|
}
|
13
WebInterface/Services/IRateLimiterService.cs
Normal file
13
WebInterface/Services/IRateLimiterService.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WebInterface.Services
|
||||||
|
{
|
||||||
|
public interface IRateLimiterService
|
||||||
|
{
|
||||||
|
bool IsRequestAllowed(IPAddress remoteIpAddress);
|
||||||
|
}
|
||||||
|
}
|
29
WebInterface/Services/PrivateServerService.cs
Normal file
29
WebInterface/Services/PrivateServerService.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using Common;
|
||||||
|
using Model;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WebInterface.Services
|
||||||
|
{
|
||||||
|
public class PrivateServerService : IPrivateServerService
|
||||||
|
{
|
||||||
|
private static readonly Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||||
|
private static readonly IPEndPoint webApiListenerEndPoint = new IPEndPoint(IPAddress.Loopback, 11028);
|
||||||
|
|
||||||
|
public SpawnedServerInfo SpawnNewPrivateServer(int players, string modName)
|
||||||
|
{
|
||||||
|
players = Math.Min(8, Math.Max(3, players));
|
||||||
|
|
||||||
|
SpawnedServerInfo serverProcess = ProcessSpawner.SpawnServer(players, modName);
|
||||||
|
byte[] payload = Encoding.ASCII.GetBytes($"B{serverProcess.Port}\t{serverProcess.Process.Id}\0");
|
||||||
|
socket.SendTo(payload, webApiListenerEndPoint);
|
||||||
|
|
||||||
|
return serverProcess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
WebInterface/Services/RateLimiterService.cs
Normal file
39
WebInterface/Services/RateLimiterService.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WebInterface.Services
|
||||||
|
{
|
||||||
|
public class RateLimiterService : IRateLimiterService
|
||||||
|
{
|
||||||
|
private static readonly ConcurrentDictionary<IPAddress, List<DateTime>> requestHistory = new ConcurrentDictionary<IPAddress, List<DateTime>>();
|
||||||
|
|
||||||
|
public bool IsRequestAllowed(IPAddress remoteIpAddress)
|
||||||
|
{
|
||||||
|
if (requestHistory.TryGetValue(remoteIpAddress, out var list))
|
||||||
|
{
|
||||||
|
bool isLimited = list.Count(e => DateTime.UtcNow - e < TimeSpan.FromHours(1)) >= 5;
|
||||||
|
if (isLimited)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestHistory.AddOrUpdate(remoteIpAddress,
|
||||||
|
(ip) =>
|
||||||
|
{
|
||||||
|
var list = new List<DateTime>();
|
||||||
|
list.Add(DateTime.UtcNow);
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
(ip, list) =>
|
||||||
|
{
|
||||||
|
list.Add(DateTime.UtcNow);
|
||||||
|
return list;
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,6 +40,8 @@ namespace WebInterface
|
|||||||
services.AddControllers();
|
services.AddControllers();
|
||||||
services.AddControllersWithViews();
|
services.AddControllersWithViews();
|
||||||
services.Add(new ServiceDescriptor(typeof(IStateService), typeof(StateService), ServiceLifetime.Singleton));
|
services.Add(new ServiceDescriptor(typeof(IStateService), typeof(StateService), ServiceLifetime.Singleton));
|
||||||
|
services.Add(new ServiceDescriptor(typeof(IPrivateServerService), typeof(PrivateServerService), ServiceLifetime.Transient));
|
||||||
|
services.Add(new ServiceDescriptor(typeof(IRateLimiterService), typeof(RateLimiterService), ServiceLifetime.Singleton));
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
@model WebInterface.HomeViewModel
|
@model HomeViewModel
|
||||||
|
|
||||||
@{
|
@{
|
||||||
Layout = null;
|
ViewData["Title"] = "Blood Servers";
|
||||||
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||||
|
|
||||||
string b = "Blood";
|
string b = "Blood";
|
||||||
string cp = "Cryptic Passage";
|
string cp = "Cryptic Passage";
|
||||||
string dw = "Death Wish";
|
string dw = "Death Wish";
|
||||||
@ -12,7 +14,7 @@
|
|||||||
ListServers = @<div>
|
ListServers = @<div>
|
||||||
@foreach (var server in Model.Servers.Where(s => s.Mod == item).OrderBy(s => s.MaximumPlayers))
|
@foreach (var server in Model.Servers.Where(s => s.Mod == item).OrderBy(s => s.MaximumPlayers))
|
||||||
{
|
{
|
||||||
<div style="border:2px solid cornflowerblue;padding-left:1em">
|
<div class="bordered">
|
||||||
<p style="font-weight:bold">Players: @(server.CurrentPlayers - 1)/@(server.MaximumPlayers - 1)</p>
|
<p style="font-weight:bold">Players: @(server.CurrentPlayers - 1)/@(server.MaximumPlayers - 1)</p>
|
||||||
<div>
|
<div>
|
||||||
@if (server.IsStarted)
|
@if (server.IsStarted)
|
||||||
@ -38,128 +40,98 @@
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<h1>The client EXE</h1>
|
||||||
|
<p>Use this exe to connect: <a href="https://lerppu.net/wannabethesis/nblood/20200113-2073/">https://lerppu.net/wannabethesis/nblood/20200113-2073/</a></p>
|
||||||
|
|
||||||
<html>
|
<p><span class="bolder" style="font-size:larger">@Model.ManHoursPlayed</span> man-hours played since @Model.RunningSinceUtc</p>
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width" />
|
|
||||||
<title>Blood servers</title>
|
|
||||||
<style>
|
|
||||||
.code {
|
|
||||||
font-family: Consolas;
|
|
||||||
background-color: black;
|
|
||||||
color: whitesmoke;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning {
|
<div class="bordered" style="padding-left:1em;padding-bottom:1em">
|
||||||
border: dashed 4px red;
|
<p>If you want to play with friends only and keep your port secret, request a private server by clicking the button below:</p>
|
||||||
font-weight: bold;
|
<form method="get">
|
||||||
}
|
<button asp-route="RequestPrivateServer">Request Private Server</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
.bolder {
|
<h2>@b 1.21</h2>
|
||||||
font-weight: bolder;
|
<details>
|
||||||
}
|
<summary>Show @b <span class="bolder">version 1.21</span> servers</summary>
|
||||||
|
<p>The below files must be in your Blood directory:</p>
|
||||||
.main {
|
<ul>
|
||||||
margin: auto;
|
<li>BLOOD.INI</li>
|
||||||
max-width: 900px;
|
<li>BLOOD.RFF</li>
|
||||||
padding: 10px;
|
<li>GUI.RFF</li>
|
||||||
}
|
<li>SOUNDS.RFF</li>
|
||||||
</style>
|
<li>SURFACE.DAT</li>
|
||||||
</head>
|
<li>TILES000.ART-TILES017.ART</li>
|
||||||
<body style="background-color: cornsilk">
|
<li>VOXEL.DAT</li>
|
||||||
<div class="main">
|
</ul>
|
||||||
<h1>The client EXE</h1>
|
@ListServers(@b)
|
||||||
<p>Use this exe to connect: <a href="https://lerppu.net/wannabethesis/nblood/20200113-2073/">https://lerppu.net/wannabethesis/nblood/20200113-2073/</a></p>
|
</details>
|
||||||
|
<br />
|
||||||
<p><span class="bolder" style="font-size:larger">@Model.ManHoursPlayed</span> man-hours played since @Model.RunningSinceUtc</p>
|
<h2>@cp</h2>
|
||||||
|
<details>
|
||||||
<h2>@b 1.21</h2>
|
<summary>Show @cp servers</summary>
|
||||||
<details>
|
<p>The below files must be in your Blood directory:</p>
|
||||||
<summary>Show @b <span class="bolder">version 1.21</span> servers</summary>
|
<ul>
|
||||||
<p>The below files must be in your Blood directory:</p>
|
<li>CP01-CP09.MAP</li>
|
||||||
|
<li>CPART07.AR_ (Fresh Supply owners need to copy tiles007.ART from <span class="code">\addons\Cryptic Passage</span> and rename it</li>
|
||||||
|
<li>CPART15.AR_ (Fresh Supply owners need to copy tiles015.ART from <span class="code">\addons\Cryptic Passage</span> and rename it</li>
|
||||||
|
<li>CPBB01.MAP-CPBB04.MAP</li>
|
||||||
|
<li>CPSL.MAP</li>
|
||||||
|
<li>CRYPTIC.INI</li>
|
||||||
|
</ul>
|
||||||
|
<p class="warning">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!</p>
|
||||||
|
@ListServers(@cp)
|
||||||
|
</details>
|
||||||
|
<br />
|
||||||
|
<h2>@dw 1.6.10</h2>
|
||||||
|
<details>
|
||||||
|
<summary>Show @dw <span class="bolder">version 1.6.10</span> servers</summary>
|
||||||
|
<p>The below files must be in your Blood directory:</p>
|
||||||
|
<ul>
|
||||||
|
<li>dw.ini</li>
|
||||||
|
<li>DWBB1.MAP-DWBB3.MAP</li>
|
||||||
|
<li>DWE1M1.MAP-DWE1M12.MAP</li>
|
||||||
|
<li>DWE2M1.MAP-DWE2M12.MAP</li>
|
||||||
|
<li>DWE3M1.MAP-DWE3M12.MAP</li>
|
||||||
|
</ul>
|
||||||
|
@ListServers(@dw)
|
||||||
|
</details>
|
||||||
|
<br />
|
||||||
|
<h2>@twoira 1.0.1</h2>
|
||||||
|
<details>
|
||||||
|
<summary>Show @twoira <span class="bolder">version 1.0.1</span> servers</summary>
|
||||||
|
<p>@twoira </p>
|
||||||
|
<p>The below folder (TWOIRA) must be in your Blood directory, and inside that the other additional files:</p>
|
||||||
|
<ul>
|
||||||
|
<li>TWOIRA</li>
|
||||||
|
<li>
|
||||||
<ul>
|
<ul>
|
||||||
<li>BLOOD.INI</li>
|
<li>IRA01.MAP</li>
|
||||||
<li>BLOOD.RFF</li>
|
<li>IRA02_A.MAP</li>
|
||||||
<li>GUI.RFF</li>
|
<li>IRA02_B.MAP</li>
|
||||||
<li>SOUNDS.RFF</li>
|
<li>IRA03.MAP</li>
|
||||||
|
<li>IRA04.MAP</li>
|
||||||
|
<li>IRA05.MAP</li>
|
||||||
|
<li>IRA06.MAP</li>
|
||||||
|
<li>IRA07.MAP</li>
|
||||||
|
<li>IRA08.MAP</li>
|
||||||
<li>SURFACE.DAT</li>
|
<li>SURFACE.DAT</li>
|
||||||
<li>TILES000.ART-TILES017.ART</li>
|
<li>TILES18.ART</li>
|
||||||
<li>VOXEL.DAT</li>
|
<li>twoira.ini</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ListServers(@b)
|
</li>
|
||||||
</details>
|
</ul>
|
||||||
<br />
|
@ListServers(twoira)
|
||||||
<h2>@cp</h2>
|
</details>
|
||||||
<details>
|
<h2>@fo 1.3</h2>
|
||||||
<summary>Show @cp servers</summary>
|
<details>
|
||||||
<p>The below files must be in your Blood directory:</p>
|
<summary>Show @fo <span class="bolder">version 1.3</span> servers</summary>
|
||||||
<ul>
|
<p>The below files must be in your Blood directory:</p>
|
||||||
<li>CP01-CP09.MAP</li>
|
<ul>
|
||||||
<li>CPART07.AR_ (Fresh Supply owners need to copy tiles007.ART from <span class="code">\addons\Cryptic Passage</span> and rename it</li>
|
<li>fo.INI</li>
|
||||||
<li>CPART15.AR_ (Fresh Supply owners need to copy tiles015.ART from <span class="code">\addons\Cryptic Passage</span> and rename it</li>
|
<li>fo1m1.MAP-fo1m8.MAP</li>
|
||||||
<li>CPBB01.MAP-CPBB04.MAP</li>
|
</ul>
|
||||||
<li>CPSL.MAP</li>
|
<p class="warning">Not tested in co-op! But I hope it works! :)</p>
|
||||||
<li>CRYPTIC.INI</li>
|
@ListServers(@fo)
|
||||||
</ul>
|
</details>
|
||||||
<p class="warning">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!</p>
|
|
||||||
@ListServers(@cp)
|
|
||||||
</details>
|
|
||||||
<br />
|
|
||||||
<h2>@dw 1.6.10</h2>
|
|
||||||
<details>
|
|
||||||
<summary>Show @dw <span class="bolder">version 1.6.10</span> servers</summary>
|
|
||||||
<p>The below files must be in your Blood directory:</p>
|
|
||||||
<ul>
|
|
||||||
<li>dw.ini</li>
|
|
||||||
<li>DWBB1.MAP-DWBB3.MAP</li>
|
|
||||||
<li>DWE1M1.MAP-DWE1M12.MAP</li>
|
|
||||||
<li>DWE2M1.MAP-DWE2M12.MAP</li>
|
|
||||||
<li>DWE3M1.MAP-DWE3M12.MAP</li>
|
|
||||||
</ul>
|
|
||||||
@ListServers(@dw)
|
|
||||||
</details>
|
|
||||||
<br />
|
|
||||||
<h2>@twoira 1.0.1</h2>
|
|
||||||
<details>
|
|
||||||
<summary>Show @twoira <span class="bolder">version 1.0.1</span> servers</summary>
|
|
||||||
<p>@twoira </p>
|
|
||||||
<p>The below folder (TWOIRA) must be in your Blood directory, and inside that the other additional files:</p>
|
|
||||||
<ul>
|
|
||||||
<li>TWOIRA</li>
|
|
||||||
<li>
|
|
||||||
<ul>
|
|
||||||
<li>IRA01.MAP</li>
|
|
||||||
<li>IRA02_A.MAP</li>
|
|
||||||
<li>IRA02_B.MAP</li>
|
|
||||||
<li>IRA03.MAP</li>
|
|
||||||
<li>IRA04.MAP</li>
|
|
||||||
<li>IRA05.MAP</li>
|
|
||||||
<li>IRA06.MAP</li>
|
|
||||||
<li>IRA07.MAP</li>
|
|
||||||
<li>IRA08.MAP</li>
|
|
||||||
<li>SURFACE.DAT</li>
|
|
||||||
<li>TILES18.ART</li>
|
|
||||||
<li>twoira.ini</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
@ListServers(twoira)
|
|
||||||
</details>
|
|
||||||
<h2>@fo 1.3</h2>
|
|
||||||
<details>
|
|
||||||
<summary>Show @fo <span class="bolder">version 1.3</span> servers</summary>
|
|
||||||
<p>The below files must be in your Blood directory:</p>
|
|
||||||
<ul>
|
|
||||||
<li>fo.INI</li>
|
|
||||||
<li>fo1m1.MAP-fo1m8.MAP</li>
|
|
||||||
</ul>
|
|
||||||
<p class="warning">Not tested in co-op! But I hope it works! :)</p>
|
|
||||||
@ListServers(@fo)
|
|
||||||
</details>
|
|
||||||
<br />
|
|
||||||
<hr />
|
|
||||||
<p>Do you think this page is ugly? You are right! If you want to make it look better, PRs are welcomed (but please, don't use any JavaScript, thanks): <a href="https://github.com/CommonLoon102/NBloodServerSupervisor">https://github.com/CommonLoon102/NBloodServerSupervisor</a></p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
16
WebInterface/Views/Private/Index.cshtml
Normal file
16
WebInterface/Views/Private/Index.cshtml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
@model PrivateViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Request Private Server";
|
||||||
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||||
|
}
|
||||||
|
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
<div class="bordered" style="padding:4px">
|
||||||
|
<form method="post">
|
||||||
|
<label asp-for="ModName"></label>
|
||||||
|
<select asp-for="ModName" asp-items="Model.ModNames"></select> <br />
|
||||||
|
<label asp-for="Players"></label>
|
||||||
|
<input type="number" min="2" max="7" asp-for="Players" /> <br />
|
||||||
|
<button asp-route="RequestPrivateServer">Request</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
17
WebInterface/Views/Private/Result.cshtml
Normal file
17
WebInterface/Views/Private/Result.cshtml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
@model StartServerResponse
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Your Private Server";
|
||||||
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="bordered">
|
||||||
|
@if (Model.IsSuccess)
|
||||||
|
{
|
||||||
|
<p>Command: <span class="code">@Model.CommandLine</span></p>
|
||||||
|
<p>You have 10 minutes to join.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>Error: @Model.ErrorMessage</p>
|
||||||
|
}
|
||||||
|
</div>
|
59
WebInterface/Views/Shared/_Layout.cshtml
Normal file
59
WebInterface/Views/Shared/_Layout.cshtml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>@ViewData["Title"]</title>
|
||||||
|
<style>
|
||||||
|
.code {
|
||||||
|
font-family: Consolas;
|
||||||
|
background-color: black;
|
||||||
|
color: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
border: dashed 4px red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bolder {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
margin: auto;
|
||||||
|
max-width: 900px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bordered {
|
||||||
|
border: 2px solid cornflowerblue;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body style="background-color: cornsilk">
|
||||||
|
<div class="main">
|
||||||
|
<header>
|
||||||
|
<form method="get">
|
||||||
|
<button asp-route="Home">Home</button>
|
||||||
|
</form>
|
||||||
|
<hr />
|
||||||
|
<p>I live... Again!</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<main role="main">
|
||||||
|
@RenderBody()
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="border-top footer text-muted">
|
||||||
|
<div>
|
||||||
|
<hr />
|
||||||
|
<p>Do you think this page is ugly? You are right! If you want to make it look better, PRs are welcomed (but please, don't use any JavaScript, thanks): <a href="https://github.com/CommonLoon102/NBloodServerSupervisor">https://github.com/CommonLoon102/NBloodServerSupervisor</a></p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
2
WebInterface/Views/_ViewImports.cshtml
Normal file
2
WebInterface/Views/_ViewImports.cshtml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
@using WebInterface
|
||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
Loading…
Reference in New Issue
Block a user