Files
the-cycle/src/Prospect.Server.Game/Program.cs
T
neckfireandClaude Opus 4.8 71aca5e3d0
Build Game Server / build (push) Successful in 17s
fix(game-server): survive bad-packet exceptions in tick loop
Wrap world.Tick in try/catch so an incompatible/misaligned client packet
logs an error and the server keeps running (stable R&D bench).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 19:30:12 +02:00

83 lines
3.2 KiB
C#

using Prospect.Unreal.Core;
using Prospect.Unreal.Net.Actors;
using Prospect.Unreal.Runtime;
using Serilog;
namespace Prospect.Server.Game;
internal static class Program
{
private const float TickRate = (1000.0f / 60.0f) / 1000.0f;
private static readonly ILogger Logger = Log.ForContext(typeof(Program));
private static readonly PeriodicTimer Tick = new PeriodicTimer(TimeSpan.FromSeconds(TickRate));
public static async Task Main()
{
Console.CancelKeyPress += (_, e) =>
{
Tick.Dispose();
e.Cancel = true;
};
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] ({SourceContext,-52}) {Message:lj}{NewLine}{Exception}")
.CreateLogger();
// The Cycle: Frontier authoritative game server.
// We start on the station map (the simplest shared space to get two players spawned
// and moving); the raid maps (Bright Sands, …) come once spawn + movement replication
// work end to end. AI and loot are intentionally out of scope for now.
var map = Environment.GetEnvironmentVariable("PROSPECT_MAP") ?? "/Game/Maps/MP/Station/Station_P";
var gameMode = Environment.GetEnvironmentVariable("PROSPECT_GAMEMODE") ?? "/Script/Prospect/YGameMode_Station";
var port = int.TryParse(Environment.GetEnvironmentVariable("PROSPECT_PORT"), out var p) ? p : 7777;
Logger.Information("Starting Prospect.Server.Game — map={Map} gameMode={GameMode} port={Port}", map, gameMode, port);
var worldUrl = new FUrl { Map = map, Port = port };
worldUrl.Options.Add($"game={gameMode}");
await using (var world = new ProspectWorld())
{
world.SetGameInstance(new UGameInstance());
world.SetGameMode(worldUrl);
// The game mode needs a GameSession, otherwise NMT_Join -> SpawnPlayActor -> Login
// fails with "GameSession is null" and the player is never spawned.
var authGameMode = world.GetAuthGameMode();
if (authGameMode != null && authGameMode.GameSession == null)
{
authGameMode.GameSession = new AGameSession();
}
world.InitializeActorsForPlay(worldUrl, true);
if (!world.Listen())
{
Logger.Fatal("Failed to start listening (port {Port} already in use?)", port);
return;
}
Logger.Information("Server listening on :{Port}. Waiting for players…", port);
while (await Tick.WaitForNextTickAsync())
{
try
{
world.Tick(TickRate);
}
catch (Exception ex)
{
// A misaligned/incompatible client packet must not kill the whole server
// (R&D: the client's exact net version isn't matched yet). Log and continue.
Logger.Error(ex, "Tick error (bad packet?) — continuing");
}
}
}
Logger.Information("Shutting down");
}
}