Build Game Server / build (push) Successful in 17s
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>
83 lines
3.2 KiB
C#
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");
|
|
}
|
|
}
|