feat: Add metro transit, gadgets, shop, social apps, and traffic models checkpoint

This commit is contained in:
CAtlas0784 committed 2026-10-29 01:55:01 +07:00
1 parent caa42b84ed
commit 74e2963a12
79 files changed
+527962 -496

No files matched your search

@@ -2,6 +2,7 @@ using System.Collections.Concurrent;
using System.Text.Json;
using System.Text.Json.Serialization;
using Ananta.SDK.Network;
using Ananta.Server.ClientData.Client4229938;
using Ananta.Server.Protocol.Client4229938;
namespace Ananta.Server.Gameplay.Crowd;
@@ -121,8 +122,43 @@ public sealed class UrbanCrowdEngine
40968790, // Kaneko Mei - Pedestrian
];
// Common idle / chatter / phone POI action IDs
private static readonly uint[] AmbientPoiActions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Contextual ambient POI pools, built from the client's UrbanDiversityPOIActionConfig by
// NAME keyword. The old fixed [1..10] roll included 走路 (walk-in-place), 跑步 (run) and
// 受惊 (startled) — motion/fear states that looked broken on a standing NPC. 站姿* rows
// are the authored standing set (phone, look-around, lean, clap…); shop rows add
// browsing/vending actions for interior patrons.
private uint[]? _idlePoiPool;
private uint[]? _shopPoiPool;
private uint[] IdlePoiPool()
{
if (_idlePoiPool is null)
{
var pool = PoiActionCatalog4229938.All()
.Where(p => p.MainActions.Length > 0 && p.Name.StartsWith("站姿", StringComparison.Ordinal))
.Select(p => p.Id)
.ToList();
pool.Insert(0, 2u); // plain idle is always valid
_idlePoiPool = pool.ToArray();
}
return _idlePoiPool;
}
private uint[] ShopPoiPool()
{
if (_shopPoiPool is null)
{
var pool = PoiActionCatalog4229938.All()
.Where(p => p.MainActions.Length > 0 &&
(p.Name.StartsWith("站姿", StringComparison.Ordinal) ||
p.Name.Contains("商品") || p.Name.Contains("售货机") || p.Name.Contains("购物")))
.Select(p => p.Id)
.ToList();
pool.Insert(0, 2u);
_shopPoiPool = pool.ToArray();
}
return _shopPoiPool;
}
private readonly Random _rng = new();
@@ -400,7 +436,8 @@ public sealed class UrbanCrowdEngine
// Spawn 1 shopkeeper/patron inside
var formworkId = CitizenFormworkPool[_rng.Next(CitizenFormworkPool.Length)];
var poiAction = AmbientPoiActions[_rng.Next(AmbientPoiActions.Length)];
var shopPool = ShopPoiPool();
var poiAction = shopPool[_rng.Next(shopPool.Length)];
var facing = _rng.Next(0, 360);
var (ok, ids) = await _driver.SpawnCrowdPedestriansAsync(
@@ -475,7 +512,8 @@ public sealed class UrbanCrowdEngine
var spawnY = MathF.Abs(pt0[1] - py) > 2.5f ? py : pt0[1];
var formworkId = CitizenFormworkPool[_rng.Next(CitizenFormworkPool.Length)];
var isWalker = _rng.Next(100) < 60; // 60% walkers, 40% ambient idle/talkers
var poiAction = isWalker ? 0u : AmbientPoiActions[_rng.Next(AmbientPoiActions.Length)];
var idlePool = IdlePoiPool();
var poiAction = isWalker ? 0u : idlePool[_rng.Next(idlePool.Length)];
var facing = _rng.Next(0, 360);
var (ok, ids) = await _driver.SpawnCrowdPedestriansAsync(