feat(traffic): implement autonomous City Traffic Engine and Living Urban Crowd Engine from CBT dump

This commit is contained in:
CAtlas0784 committed 2026-09-18 14:19:52 +07:00
1 parent f2a36fe188
commit 67bdbae24b
17 files changed
+18759

No files matched your search

@@ -6,6 +6,8 @@ using Ananta.SDK.Serialization;
using Ananta.Server.ClientData.Client4229938;
using Ananta.Server.Configuration;
using Ananta.Server.Handlers.Game;
using Ananta.Server.Gameplay.Traffic;
using Ananta.Server.Gameplay.Crowd;
using Ananta.Server.Protocol.Client4229938;
using Ananta.Server.RpcTypes.Client4229938;
using SceneMethods = Ananta.Server.RpcTypes.Client4229938.Methods.GameScene;
@@ -151,6 +153,18 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub
await WriteJsonAsync(ctx, Scenes(), token);
else if (method == "GET" && path == "/api/npc/catalog")
await WriteJsonAsync(ctx, NpcCatalog(ctx.Request), token);
else if (method == "GET" && path == "/api/traffic/status")
await WriteJsonAsync(ctx, TrafficStatus(), token);
else if (method == "POST" && path == "/api/traffic/config")
await WriteJsonAsync(ctx, await TrafficConfigAsync(await ReadBodyAsync(ctx.Request, token), token), token);
else if (method == "POST" && path == "/api/traffic/clear")
await WriteJsonAsync(ctx, await TrafficClearAsync(token), token);
else if (method == "POST" && path == "/api/traffic/spawn-wave")
await WriteJsonAsync(ctx, await TrafficSpawnWaveAsync(token), token);
else if (method == "GET" && path == "/api/crowd/status")
await WriteJsonAsync(ctx, CrowdStatus(), token);
else if (method == "POST" && path == "/api/crowd/config")
await WriteJsonAsync(ctx, await CrowdConfigAsync(await ReadBodyAsync(ctx.Request, token), token), token);
else
await WriteJsonAsync(ctx, new { ok = false, error = "unknown route" }, token, 404);
}
@@ -1401,6 +1415,83 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub
};
}
private object TrafficStatus()
{
return new
{
ok = true,
traffic = CityTrafficEngine.Instance.GetStatus(),
crowd = UrbanCrowdEngine.Instance.GetStatus()
};
}
private Task<object> TrafficConfigAsync(string body, CancellationToken token)
{
try
{
using var doc = JsonDocument.Parse(string.IsNullOrWhiteSpace(body) ? "{}" : body);
var root = doc.RootElement;
if (root.TryGetProperty("enabled", out var en))
CityTrafficEngine.Instance.Enabled = en.GetBoolean();
if (root.TryGetProperty("density", out var den))
CityTrafficEngine.Instance.TargetDensity = Math.Clamp(den.GetInt32(), 0, 100);
if (root.TryGetProperty("speedScale", out var spd))
CityTrafficEngine.Instance.SpeedScale = Math.Clamp((float)spd.GetDouble(), 0.1f, 5.0f);
return Task.FromResult<object>(new { ok = true, status = CityTrafficEngine.Instance.GetStatus() });
}
catch (Exception ex)
{
return Task.FromResult<object>(new { ok = false, error = ex.Message });
}
}
private async Task<object> TrafficClearAsync(CancellationToken token)
{
var session = hub.Current;
if (session is null)
return new { ok = false, error = "no active game session" };
await CityTrafficEngine.Instance.ClearAllTrafficAsync(session);
return new { ok = true, message = "traffic cleared" };
}
private async Task<object> TrafficSpawnWaveAsync(CancellationToken token)
{
var session = hub.Current;
if (session is null)
return new { ok = false, error = "no active game session" };
var spawned = await CityTrafficEngine.Instance.SpawnWaveAheadAsync(session, 5);
return new { ok = true, spawned };
}
private object CrowdStatus()
{
return new
{
ok = true,
crowd = UrbanCrowdEngine.Instance.GetStatus()
};
}
private Task<object> CrowdConfigAsync(string body, CancellationToken token)
{
try
{
using var doc = JsonDocument.Parse(string.IsNullOrWhiteSpace(body) ? "{}" : body);
var root = doc.RootElement;
if (root.TryGetProperty("enabled", out var en))
UrbanCrowdEngine.Instance.Enabled = en.GetBoolean();
if (root.TryGetProperty("density", out var den))
UrbanCrowdEngine.Instance.TargetPedestrianDensity = Math.Clamp(den.GetInt32(), 0, 100);
if (root.TryGetProperty("populateShops", out var pop))
UrbanCrowdEngine.Instance.PopulateShops = pop.GetBoolean();
return Task.FromResult<object>(new { ok = true, status = UrbanCrowdEngine.Instance.GetStatus() });
}
catch (Exception ex)
{
return Task.FromResult<object>(new { ok = false, error = ex.Message });
}
}
private static async Task<string> ReadBodyAsync(HttpListenerRequest request, CancellationToken token)
{
if (!request.HasEntityBody)