Files
BSO-Server/Program.cs
T
MrMahoandClaude Opus 5.5 4e333c8250 0.3.0: Serverliste und BSO-Konten
- 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>
2026-10-28 18:10:51 +01:00

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;
}