using Ananta.SDK.Logging; using Ananta.SDK.Network; using Ananta.SDK.Rpc; using Ananta.Server.App.Admin; using Ananta.Server.Configuration; using Ananta.Server.Handlers.Game; using Ananta.Server.Handlers.LoginGate; using Ananta.Server.Protocol.Client4229938; using Ananta.Server.Gameplay.Traffic; using Ananta.Server.Gameplay.Crowd; using Ananta.Server.Gameplay.World; using Ananta.Server.Network; namespace Ananta.Server.App; /// /// Wires configuration, RPC routers and TCP listeners together. /// Game behavior lives in Ananta.Handlers; protocol encoding lives in Ananta.Core/Protocol/Client4229938. /// internal sealed class PrivateServerApplication(PrivateServerConfig config) { internal async Task RunAsync(CancellationToken cancellationToken) { var gateSessions = new GateSessionHub(); var gameSessions = new GameSessionHub(); var loginRouter = new LoginGateRouter(gateSessions).Build(); var gameRouter = new GameRouter(gateSessions).Build(); var loginPortA = config.Network.LoginPorts[0]; var loginPortB = config.Network.LoginPorts[1]; var bindHost = config.Network.BindHost; var servers = new[] { CreateServer("login-0", bindHost, loginPortA, loginRouter), CreateServer("login-1", bindHost, loginPortB, loginRouter), CreateServer("game", bindHost, config.Network.GamePort, gameRouter, gameSessions), }; // Initialize Autonomous City Traffic Engine and Living Urban Crowd Engine var clientDataFolder = Path.GetDirectoryName(PrivateServerConfigStore.ResolveProjectPath(config.Paths.ClientConfigs)) ?? PrivateServerConfigStore.ResolveProjectPath(config.Paths.ClientConfigs); CityTrafficEngine.Instance.Initialize( clientDataFolder, TrafficAndCrowdDriver.Instance, () => gameSessions.Current is not null ? [gameSessions.Current] : Array.Empty()); UrbanCrowdEngine.Instance.Initialize( clientDataFolder, TrafficAndCrowdDriver.Instance, () => gameSessions.Current is not null ? [gameSessions.Current] : Array.Empty()); // Interactive gadget + scene-item grid streaming GadgetStreamingEngine.Instance.Initialize( clientDataFolder, TrafficAndCrowdDriver.Instance, () => gameSessions.Current is not null ? [gameSessions.Current] : Array.Empty()); // Localhost debug API for debug-panel / WebUI (port 5809) using var debugApi = config.Debug.Enabled ? new DebugApiServer(config, gameSessions) : null; var webInfo = "web=off"; if (debugApi is not null) { try { debugApi.Start(); webInfo = $"http://{config.Debug.Host}:{config.Debug.Port}/"; } catch (Exception ex) { Console.WriteLine($"[DEBUG-API] disabled: {ex.Message}"); } } // Bootstrap diagnostics stay in console-latest/archive; runtime output becomes visible here. RuntimeLogs.EndBootstrapQuietMode(); Console.WriteLine($"[READY] Ananta Private Server (HyNB v2.4 Modular) | client={config.Client.Version} | pid={Profile.PlayerPid} | login={config.Network.AdvertisedHost}:{loginPortA},{loginPortB} | game=:{config.Network.GamePort}"); if (config.Admin.Enabled) Console.WriteLine($"[READY] Admin GM panel at http://{config.Admin.Host}:{config.Admin.Port}/"); if (webInfo != "web=off") Console.WriteLine($"[READY] Debug WebUI panel at {webInfo}"); var adminTask = config.Admin.Enabled ? new AdminWebServer(config).RunAsync(cancellationToken) : Task.CompletedTask; var serverTasks = servers.Select(server => server.RunAsync(cancellationToken)); await Task.WhenAll(serverTasks.Prepend(adminTask)); } private static TcpServer CreateServer(string name, string host, int port, RpcRouter router, GameSessionHub? gameSessions = null) { var net = PrivateServerConfigStore.Current.Network; return new(name, host, port, router, async (session, frame, token) => { gameSessions?.Touch(session); GameAdminBridge.Touch(session); await RpcFrameDispatcher.DispatchAsync(router, session, frame, token); }, onClosed: null, heartbeatMs: net.SocketHeartbeatMs, writeTimeoutMs: net.WriteTimeoutMs); } }