Add project files.

This commit is contained in:
User 2020-01-23 21:03:54 +01:00
parent 6971176d7d
commit 0ac65ffd08
32 changed files with 1053 additions and 0 deletions

7
Common/Common.csproj Normal file
View File

@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>

26
Common/PortUtils.cs Normal file
View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
namespace Common
{
public class PortUtils
{
public static int GetPort()
{
IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
var usedPorts = ipGlobalProperties.GetActiveTcpConnections().Select(c => c.LocalEndPoint.Port).ToList();
usedPorts.AddRange(ipGlobalProperties.GetActiveTcpListeners().Select(c => c.Port).ToList());
usedPorts.AddRange(ipGlobalProperties.GetActiveUdpListeners().Select(c => c.Port).ToList());
var availablePorts = Enumerable.Range(1025, ushort.MaxValue).ToList().Except(usedPorts).ToList();
Random rnd = new Random();
int index = rnd.Next(0, availablePorts.Count() - 1);
int port = availablePorts[index];
return port;
}
}
}

7
Model/Model.csproj Normal file
View File

@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>

13
Model/Player.cs Normal file
View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Model
{
[Serializable]
public class Player
{
public string Name { get; set; }
public int Score { get; set; }
}
}

20
Model/Server.cs Normal file
View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Model
{
[Serializable]
public class Server
{
public DateTime SpawnedAtUtc { get; set; } = DateTime.UtcNow;
public int ProcessId { get; set; }
public int Port { get; set; }
public bool IsStarted { get; set; }
public bool IsPrivate { get; set; }
public int CurrentPlayers { get; set; }
public int MaximumPlayers { get; set; }
public string GameType { get; set; }
public IList<Player> Players { get; set; } = new List<Player>();
}
}

12
Model/State.cs Normal file
View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
namespace Model
{
public class State
{
public ConcurrentDictionary<int, Server> Servers { get; } = new ConcurrentDictionary<int, Server>();
}
}

12
Model/StateResponse.cs Normal file
View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Model
{
[Serializable]
public class StateResponse
{
public IList<Server> Servers { get; set; }
}
}

View File

@ -0,0 +1,43 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29709.97
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Supervisor", "Supervisor\Supervisor.csproj", "{BBE1C869-D87B-4174-8EC2-0E095488E99A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model", "Model\Model.csproj", "{940B1560-86F4-4233-B59E-B502B0B5C2AF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "Common\Common.csproj", "{0BD5EB76-F38C-4C22-9213-6846251D18B8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebInterface", "WebInterface\WebInterface.csproj", "{7DC8478B-FF2E-4CA5-BF4B-23DBC3E80D09}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BBE1C869-D87B-4174-8EC2-0E095488E99A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BBE1C869-D87B-4174-8EC2-0E095488E99A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BBE1C869-D87B-4174-8EC2-0E095488E99A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BBE1C869-D87B-4174-8EC2-0E095488E99A}.Release|Any CPU.Build.0 = Release|Any CPU
{940B1560-86F4-4233-B59E-B502B0B5C2AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{940B1560-86F4-4233-B59E-B502B0B5C2AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{940B1560-86F4-4233-B59E-B502B0B5C2AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{940B1560-86F4-4233-B59E-B502B0B5C2AF}.Release|Any CPU.Build.0 = Release|Any CPU
{0BD5EB76-F38C-4C22-9213-6846251D18B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0BD5EB76-F38C-4C22-9213-6846251D18B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0BD5EB76-F38C-4C22-9213-6846251D18B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0BD5EB76-F38C-4C22-9213-6846251D18B8}.Release|Any CPU.Build.0 = Release|Any CPU
{7DC8478B-FF2E-4CA5-BF4B-23DBC3E80D09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7DC8478B-FF2E-4CA5-BF4B-23DBC3E80D09}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7DC8478B-FF2E-4CA5-BF4B-23DBC3E80D09}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7DC8478B-FF2E-4CA5-BF4B-23DBC3E80D09}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A47D63A4-8A9E-49E2-8DC8-C96C042534D4}
EndGlobalSection
EndGlobal

17
Supervisor/Frags.cs Normal file
View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Supervisor
{
class Frags : PacketData
{
public IList<int> Scores { get; set; }
public int GameType { get; set; }
public Frags()
{
IsStarted = true;
}
}
}

View File

@ -0,0 +1,166 @@
using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Supervisor
{
static class NBloodServerListener
{
private const int listenPort = 11027;
private static IPEndPoint remoteIP = new IPEndPoint(IPAddress.Loopback, listenPort);
private static UdpClient udpClient = new UdpClient(remoteIP);
public static async void StartListening()
{
while (true)
{
UdpReceiveResult rst = await udpClient.ReceiveAsync();
ProcessPacket(rst.Buffer);
}
}
private static void ProcessPacket(byte[] buffer)
{
string message = Encoding.ASCII.GetString(buffer);
switch (message[0])
{
case 'A':
ProcessPlayerCountsPacket(message);
break;
case 'B':
ProcessPlayerNamesPacket(message);
break;
case 'C':
ProcessFragsPacket(buffer);
break;
default:
break;
}
}
private static void ProcessPlayerCountsPacket(string message)
{
string[] splitMessage = message.SplitMessage();
var packetData = new PlayerCounts()
{
Port = int.Parse(splitMessage[0]),
IsStarted = int.Parse(splitMessage[1]) != 0,
CurrentPlayers = int.Parse(splitMessage[2]),
MaximumPlayers = int.Parse(splitMessage[3])
};
UpdateState(packetData);
}
private static void UpdateState(PlayerCounts packetData)
{
Program.State.Servers.AddOrUpdate(packetData.Port,
new Server()
{
Port = packetData.Port,
IsStarted = packetData.IsStarted,
IsPrivate = packetData.IsPrivate,
CurrentPlayers = packetData.CurrentPlayers,
MaximumPlayers = packetData.MaximumPlayers
},
(port, server) =>
{
server.IsStarted = packetData.IsStarted;
server.IsPrivate = packetData.IsPrivate;
server.CurrentPlayers = packetData.CurrentPlayers;
server.MaximumPlayers = packetData.MaximumPlayers;
return server;
});
}
private static void ProcessPlayerNamesPacket(string message)
{
string[] splitMessage = message.SplitMessage();
var packetData = new PlayerNames()
{
Port = int.Parse(splitMessage[0]),
Names = splitMessage.Skip(1).ToList(),
};
UpdateState(packetData);
}
private static void UpdateState(PlayerNames packetData)
{
Program.State.Servers.AddOrUpdate(packetData.Port,
new Server()
{
Port = packetData.Port,
IsStarted = packetData.IsStarted,
Players = packetData.Names.Select(name => new Player() { Name = name }).ToList()
},
(port, server) =>
{
server.IsStarted = packetData.IsStarted;
if (server.Players.Count == 0 || server.CurrentPlayers != packetData.Names.Count)
{
server.CurrentPlayers = packetData.Names.Count;
server.Players = packetData.Names.Select(name => new Player()
{
Name = name,
Score = 0
}).ToList();
}
return server;
});
}
private static void ProcessFragsPacket(byte[] buffer)
{
int port = BitConverter.ToInt32(buffer, 4);
int gameType = BitConverter.ToInt32(buffer, 8);
const int maxPlayers = 8;
int[] scores = new int[maxPlayers];
for (int i = 0; i < scores.Length; i++)
scores[i] = BitConverter.ToInt32(buffer, 12 + i * sizeof(int));
var packetData = new Frags()
{
Port = port,
GameType = gameType,
Scores = scores
};
UpdateState(packetData);
}
private static void UpdateState(Frags packetData)
{
Program.State.Servers.AddOrUpdate(packetData.Port,
new Server()
{
Port = packetData.Port,
IsStarted = packetData.IsStarted,
GameType = GetGameType(packetData.GameType)
},
(port, server) =>
{
server.IsStarted = packetData.IsStarted;
server.GameType = GetGameType(packetData.GameType);
for (int i = 0; i < server.CurrentPlayers; i++)
server.Players[i].Score = packetData.Scores[i];
return server;
});
}
private static string GetGameType(int gameType)
{
return gameType switch
{
1 => "Cooperative",
2 => "Bloodbath",
3 => "Capture The Flag",
_ => "Unknown",
};
}
}
}

13
Supervisor/PacketData.cs Normal file
View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Supervisor
{
abstract class PacketData
{
public int Port { get; set; }
public bool IsStarted { get; set; }
public bool IsPrivate { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Supervisor
{
class PlayerCounts : PacketData
{
public int CurrentPlayers { get; set; }
public int MaximumPlayers { get; set; }
public PlayerCounts()
{
IsStarted = true;
}
}
}

16
Supervisor/PlayerNames.cs Normal file
View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Supervisor
{
class PlayerNames : PacketData
{
public IList<string> Names { get; set; }
public PlayerNames()
{
IsStarted = true;
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
namespace Supervisor
{
static class PrivateServerManager
{
public static void Start()
{
Task.Factory.StartNew(() =>
{
while (true)
{
Thread.Sleep(TimeSpan.FromSeconds(2));
KillUnusedServers();
}
});
}
private static void KillUnusedServers()
{
var killables = Program.State.Servers.Values.Where(s =>
s.IsPrivate
&& !s.IsStarted
&& s.CurrentPlayers < 2
&& (DateTime.UtcNow - s.SpawnedAtUtc) > TimeSpan.FromMinutes(10));
foreach (var server in killables)
{
Process.GetProcessById(server.ProcessId).Kill();
}
}
}
}

27
Supervisor/Program.cs Normal file
View File

@ -0,0 +1,27 @@
using System;
using System.Threading;
using Model;
namespace Supervisor
{
class Program
{
public static readonly State State = new State();
public static void Main(string[] args)
{
NBloodServerListener.StartListening();
WebApiListener.StartListening();
if (args.Length > 0)
PublicServerManager.Start(args[0]);
PrivateServerManager.Start();
while (true)
{
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
}
}

View File

@ -0,0 +1,7 @@
{
"profiles": {
"Supervisor": {
"commandName": "Project"
}
}
}

View File

@ -0,0 +1,74 @@
using Common;
using Model;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Supervisor
{
class PublicServerManager
{
public static void Start(string nbloodPath)
{
Thread.Sleep(TimeSpan.FromSeconds(2));
KillOrphanedServers();
Task.Factory.StartNew(() =>
{
while (true)
{
LaunchNewServersWhenNeeded(nbloodPath);
Thread.Sleep(TimeSpan.FromSeconds(1));
}
});
}
private static void KillOrphanedServers()
{
foreach (var process in Process.GetProcessesByName("nblood_server"))
{
if (!Program.State.Servers.Values.Any(s => s.ProcessId == process.Id))
{
process.Kill();
}
}
}
private static void LaunchNewServersWhenNeeded(string nbloodPath)
{
const int maxPlayers = 8;
for (int i = 3; i <= maxPlayers; i++)
{
if (IsNewServerNeeded(i))
{
int port = PortUtils.GetPort();
var process = Process.Start(nbloodPath, $"-server {i} -port {port}");
Program.State.Servers.AddOrUpdate(port, new Server()
{
Port = port,
ProcessId = process.Id,
MaximumPlayers = i,
CurrentPlayers = 1,
},
(prt, server) =>
{
server.ProcessId = process.Id;
return server;
});
}
Thread.Sleep(TimeSpan.FromSeconds(2));
}
}
private static bool IsNewServerNeeded(int i)
{
return !Program.State.Servers.Values.Any(s =>
!s.IsPrivate && s.MaximumPlayers == i && s.CurrentPlayers < s.MaximumPlayers);
}
}
}

19
Supervisor/StringUtils.cs Normal file
View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Supervisor
{
static class StringUtils
{
public static string[] SplitMessage(this string message)
{
return message.Substring(1)
.Split('\t', StringSplitOptions.RemoveEmptyEntries)
.Select(e => e.Trim('\0'))
.Where(e => !string.IsNullOrWhiteSpace(e))
.ToArray();
}
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\Model\Model.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,85 @@
using Model;
using System;
using System.Collections.Generic;
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;
namespace Supervisor
{
static class WebApiListener
{
private const int listenPort = 11028;
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 webApiEndPoint = new IPEndPoint(IPAddress.Loopback, 11029);
public static async void StartListening()
{
while (true)
{
UdpReceiveResult rst = await udpClient.ReceiveAsync();
ProcessWebApiMessage(rst.Buffer);
}
}
private static void ProcessWebApiMessage(byte[] buffer)
{
string message = Encoding.ASCII.GetString(buffer);
switch (message[0])
{
case 'A':
ProcessGetCurrentStateRequest();
break;
case 'B':
StorePrivateServerInfo(message);
break;
default:
break;
}
}
private static void ProcessGetCurrentStateRequest()
{
var response = new StateResponse();
response.Servers = Program.State.Servers.Values.ToList();
byte[] serializedResponse = ObjectToByteArray(response);
socket.SendTo(serializedResponse, webApiEndPoint);
}
private static void StorePrivateServerInfo(string message)
{
string[] split = message.SplitMessage();
int port = int.Parse(split[0]);
int processId = int.Parse(split[1]);
Program.State.Servers.AddOrUpdate(port, new Server()
{
ProcessId = processId,
IsPrivate = true
},
(prt, server) =>
{
server.ProcessId = processId;
server.IsPrivate = true;
return server;
});
}
private static byte[] ObjectToByteArray(object obj)
{
BinaryFormatter bf = new BinaryFormatter();
using (var ms = new MemoryStream())
{
bf.Serialize(ms, obj);
return ms.ToArray();
}
}
}
}

View File

@ -0,0 +1,172 @@
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 Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Model;
namespace WebInterface.Controllers
{
[ApiController]
public class NBloodController : ControllerBase
{
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<NBloodController> _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 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<NBloodController> logger, IConfiguration config)
{
_logger = logger;
_config = config;
}
[HttpGet]
[Route("[controller]/api/startserver")]
public StartServerResponse StartServer([FromQuery] ServerParameters parameters)
{
try
{
Stopwatch sw = Stopwatch.StartNew();
while (_isBusy)
{
Thread.Sleep(TimeSpan.FromSeconds(1));
if (sw.Elapsed.TotalSeconds > 5)
throw new Exception("Request timeout: the previous request hasn't finished yet.");
}
_isBusy = true;
if (parameters.Players < 2)
parameters.Players = 2;
if (parameters.ApiKey != _config.GetValue<string>("ApiKey"))
return new StartServerResponse("Invalid ApiKey.");
string nbloodPath = _config.GetValue<string>("NBloodPath");
if (!System.IO.File.Exists(nbloodPath))
throw new Exception($"The configured path for the nblood executable is invalid.");
string processName = Path.GetFileNameWithoutExtension(nbloodPath);
int serversRunning = Process.GetProcessesByName(processName).Count();
if (serversRunning >= _config.GetValue<int>("MaximumServers"))
return new StartServerResponse("The maximum number of servers are already running.");
int port = PortUtils.GetPort();
string args = BuildArgs(parameters, port);
var process = Process.Start(nbloodPath, args);
byte[] payload = Encoding.ASCII.GetBytes($"B{port}\t{process.Id}\0");
socket.SendTo(payload, webApiListenerEndPoint);
_logger.LogInformation("Server started waiting for {0} players on port {1}.",
parameters.Players, port);
Thread.Sleep(TimeSpan.FromSeconds(2));
return new StartServerResponse(port) { CommandLine = GetCommandLine(port) };
}
catch (Exception ex)
{
_logger.LogError(ex.ToString());
return new StartServerResponse("Unhandled exception has been occured. Check the logs for details.");
}
finally
{
_isBusy = false;
}
}
private static string BuildArgs(ServerParameters parameters, int port)
{
string args = $"-server {parameters.Players} -port {port}";
if (parameters.IsBroadcast)
{
args += " -broadcast";
}
return args;
}
[HttpGet]
[Route("[controller]/api/listservers")]
public ListServersResponse ListServers()
{
try
{
if (DateTime.UtcNow - _lastRefresh > TimeSpan.FromSeconds(5)
|| _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;
_lastRefresh = DateTime.UtcNow;
}
return _lastServerList;
}
catch (Exception ex)
{
_logger.LogError(ex.ToString());
return new ListServersResponse("Unhandled exception has been occured. Check the logs for details.");
}
}
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;
}
}
private string GetCommandLine(int port)
{
return $"nblood -client {HttpContext.Request.Host.Host} -port {port}";
}
}
}

26
WebInterface/Program.cs Normal file
View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace WebInterface
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@ -0,0 +1,20 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:61021",
"sslPort": 0
}
},
"profiles": {
"WebInterface": {
"commandName": "Project",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

66
WebInterface/Startup.cs Normal file
View File

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace WebInterface
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
StartSupervisor();
}
private void StartSupervisor()
{
string nbloodPath = Configuration.GetValue<string>("NBloodPath");
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
string supervisor = "Supervisor";
if (isWindows)
supervisor += ".exe";
if (!File.Exists(nbloodPath))
throw new Exception($"Couldn't find nblood_server at {nbloodPath}");
if (!File.Exists(supervisor))
throw new Exception($"Couldn't find {supervisor} in {Directory.GetCurrentDirectory()}");
Process.Start(supervisor, nbloodPath);
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebInterface
{
public class ListServersResponse
{
public IList<Server> Servers { get; set; }
string ErrorMessage { get; set; }
public ListServersResponse()
{
}
public ListServersResponse(string errorMessage)
{
ErrorMessage = errorMessage;
}
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebInterface
{
public class Player
{
public string Name { get; set; }
public int Score { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebInterface
{
public class Server
{
public DateTime SpawnedAtUtc { get; set; } = DateTime.UtcNow;
public int Port { get; set; }
public bool IsStarted { get; set; }
public string CommandLine { get; set; }
public int CurrentPlayers { get; set; }
public int MaximumPlayers { get; set; }
public string GameType { get; set; }
public IList<Player> Players { get; set; } = new List<Player>();
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebInterface
{
public class ServerParameters
{
public string ApiKey { get; set; }
public int Players { get; set; }
public bool IsBroadcast { get; set; }
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebInterface
{
public class StartServerResponse
{
public bool IsSuccess { get; set; }
public int Port { get; set; }
public string CommandLine { get; set; }
public string ErrorMessage { get; set; }
public StartServerResponse(string errorMessage = "")
{
IsSuccess = false;
ErrorMessage = errorMessage ?? "";
}
public StartServerResponse(int port)
{
IsSuccess = true;
Port = port;
}
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\Model\Model.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"MaximumServers": 20,
"NBloodPath": "\\path\\to\\nblood_server",
"ApiKey": "!!! CHANGE ME !!!"
}