- Heartbeat an die Website, Abmelden beim Beenden (--private, --public-address, --description, --master) - Spiel-Tickets pruefen (Signatur der Website), Name aus dem Konto, --no-auth fuer LAN - IPv4 und IPv6, Protokoll 2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
130 lines
5.8 KiB
C#
130 lines
5.8 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Diagnostics;
|
|
using System.Net.Sockets;
|
|
using BSO.Net;
|
|
using BSO.Server;
|
|
|
|
// BSO.Server [--port 7777] [--name "Mein Server"] [--description "..."] [--max 8] [--bot]
|
|
// [--private] [--public-address host] [--master https://bso.mbm.wtf] [--no-auth]
|
|
// BSO.Server --probe host[:port] (Test: verbindet sich wie die Mod und zeigt, was ankommt)
|
|
|
|
int port = Protocol.DefaultPort, max = 8;
|
|
string name = "BSO Server", description = "", probe = null, publicAddress = null, master = MasterClient.DefaultUrl;
|
|
bool bot = false, listed = true, auth = true;
|
|
for (int i = 0; i < args.Length; i++)
|
|
{
|
|
switch (args[i])
|
|
{
|
|
case "--port": port = int.Parse(args[++i]); break;
|
|
case "--name": name = args[++i]; break;
|
|
case "--description": description = args[++i]; break;
|
|
case "--max": max = int.Parse(args[++i]); break;
|
|
case "--bot": bot = true; break;
|
|
case "--private": listed = false; break; // nicht in der Serverliste
|
|
case "--public-address": publicAddress = args[++i]; break; // Adresse fuer die Liste (hinter NAT/Proxy)
|
|
case "--master": master = args[++i]; break; // andere Website (Tests: http://localhost:5080)
|
|
case "--no-auth": auth = false; break; // ohne BSO-Konto (LAN, Tests)
|
|
case "--probe": probe = args[++i]; break;
|
|
default:
|
|
Console.WriteLine("BSO.Server [--port 7777] [--name \"Name\"] [--description \"...\"] [--max 8] [--bot] [--private]\n" +
|
|
" [--public-address host] [--master url] [--no-auth] | --probe host[:port]");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (probe != null) return Probe(probe);
|
|
|
|
var server = new GameServer(port, name, max) { RequireAccount = auth };
|
|
var masterClient = new MasterClient(master, server, port, listed, publicAddress, description);
|
|
server.Master = masterClient;
|
|
if (auth) masterClient.LoadTicketKeyAsync().Wait();
|
|
else GameServer.Log("--no-auth: Spieler brauchen kein BSO-Konto, Namen sind frei waehlbar");
|
|
masterClient.Start();
|
|
if (bot) server.AddBot();
|
|
GameServer.Log("Befehle: help, status, bot, kick <id>, stop");
|
|
|
|
var commands = new ConcurrentQueue<string>();
|
|
new Thread(() =>
|
|
{
|
|
string line;
|
|
while ((line = Console.ReadLine()) != null) commands.Enqueue(line);
|
|
}) { IsBackground = true, Name = "Konsole" }.Start();
|
|
|
|
bool running = true;
|
|
Console.CancelKeyPress += (_, e) => { e.Cancel = true; running = false; };
|
|
while (running)
|
|
{
|
|
server.Tick();
|
|
while (commands.TryDequeue(out string cmd))
|
|
{
|
|
if (cmd.Trim().Equals("stop", StringComparison.OrdinalIgnoreCase)) running = false;
|
|
else server.Command(cmd);
|
|
}
|
|
Thread.Sleep(5);
|
|
}
|
|
masterClient.Stop();
|
|
server.Stop();
|
|
GameServer.Log("Server beendet");
|
|
return 0;
|
|
|
|
/// <summary>Verbindet sich wie die Mod (Info, dann Hello als "Probe") und zeigt 5 s lang alles, was ankommt.</summary>
|
|
static int Probe(string target)
|
|
{
|
|
string[] hp = target.Split(':');
|
|
int p = hp.Length > 1 ? int.Parse(hp[1]) : Protocol.DefaultPort;
|
|
var tcp = new TcpClient();
|
|
if (!tcp.ConnectAsync(hp[0], p).Wait(5000)) { Console.WriteLine("Keine Verbindung zu " + target); return 2; }
|
|
var conn = new Connection(tcp);
|
|
conn.Send(MsgType.Info, null);
|
|
conn.Send(MsgType.Hello, Wire.Build(w => { w.Write(Protocol.Version); w.Write("probe"); w.Write("Probe"); w.Write(Environment.GetEnvironmentVariable("BSO_PROBE_TICKET") ?? ""); }));
|
|
conn.Send(MsgType.Ping, Wire.Build(w => w.Write(Stopwatch.GetTimestamp())));
|
|
// Eigene Position, damit der Testbot losfaehrt
|
|
var me = new PlayerState { Mode = PlayerMode.InBus, X = 70000, Y = 30000, Z = 760 };
|
|
|
|
var watch = Stopwatch.StartNew();
|
|
int states = 0;
|
|
while (watch.Elapsed.TotalSeconds < 5 && !conn.IsClosed)
|
|
{
|
|
conn.Send(MsgType.PlayerState, Wire.Build(w => me.Write(w)));
|
|
foreach (Message m in conn.Poll())
|
|
{
|
|
BinaryReader r = m.Reader();
|
|
switch (m.Type)
|
|
{
|
|
case MsgType.InfoReply:
|
|
Console.WriteLine($"Info: \"{r.ReadString()}\" Version {r.ReadString()}, {r.ReadInt32()}/{r.ReadInt32()} Spieler");
|
|
break;
|
|
case MsgType.Welcome:
|
|
int id = r.ReadInt32();
|
|
string server = r.ReadString();
|
|
int n = r.ReadInt32();
|
|
var list = Enumerable.Range(0, n).Select(_ => $"#{r.ReadInt32()} {r.ReadString()}").ToList();
|
|
Console.WriteLine($"Welcome: ich bin #{id} auf \"{server}\", andere: {(n == 0 ? "-" : string.Join(", ", list))}");
|
|
break;
|
|
case MsgType.Reject:
|
|
Console.WriteLine("Abgelehnt: " + r.ReadString());
|
|
break;
|
|
case MsgType.Pong:
|
|
Console.WriteLine($"Ping: {Stopwatch.GetElapsedTime(r.ReadInt64()).TotalMilliseconds:0.0} ms");
|
|
break;
|
|
case MsgType.PlayerJoined:
|
|
Console.WriteLine($"Beigetreten: #{r.ReadInt32()} {r.ReadString()}");
|
|
break;
|
|
case MsgType.PlayerLeft:
|
|
Console.WriteLine($"Gegangen: #{r.ReadInt32()}");
|
|
break;
|
|
case MsgType.PlayerState:
|
|
int who = r.ReadInt32();
|
|
PlayerState s = PlayerState.Read(r);
|
|
if (states++ % 10 == 0)
|
|
Console.WriteLine($"Zustand #{who}: {s.Mode} {s.X / 100:0.0} / {s.Y / 100:0.0} / {s.Z / 100:0.0} m, {s.Speed * 0.036f:0} km/h, {s.DistanceTo(me) / 100:0.0} m entfernt");
|
|
break;
|
|
}
|
|
}
|
|
Thread.Sleep(100);
|
|
}
|
|
Console.WriteLine($"{states} Zustaende in 5 s" + (conn.IsClosed ? " - getrennt: " + conn.CloseReason : ""));
|
|
conn.Close("Probe fertig");
|
|
return 0;
|
|
}
|