feat(traffic): implement autonomous City Traffic Engine and Living Urban Crowd Engine from CBT dump
This commit is contained in:
1 parent
f2a36fe188
commit
67bdbae24b
17 files changed
+18759
No files matched your search
@@ -156,6 +156,94 @@ internal sealed partial class GameRouter
|
||||
return (true, $"sent spawn for {ids.Count} static NPC(s) in front of you", ids.ToArray());
|
||||
}
|
||||
|
||||
internal static async Task<(bool Ok, string Message, ulong[] Ids)> SpawnStaticNpcAtAsync(
|
||||
TcpSession session, uint npcFormworkId, uint poiActionId, float x, float y, float z, float facing)
|
||||
{
|
||||
if (npcFormworkId > int.MaxValue || !NpcCatalog4229938.TryGet(npcFormworkId, out var npc))
|
||||
return (false, $"unknown NPC formwork {npcFormworkId}", []);
|
||||
if (poiActionId != 0 && !PoiActionCatalog4229938.TryGet(poiActionId, out _))
|
||||
return (false, $"unknown NPC POI action {poiActionId}", []);
|
||||
|
||||
var state = GetStateIfExists(session);
|
||||
if (state is null)
|
||||
return (false, "no player world state", []);
|
||||
|
||||
var id = unchecked((ulong)Interlocked.Increment(ref s_nextStaticNpcId));
|
||||
PoiActionCatalog4229938.TryGet(poiActionId, out var poi);
|
||||
var mainActionId = poi?.SelectMainAction(id) ?? 0;
|
||||
var position = new SceneMethods.UxVector3(x, y, z);
|
||||
var item = new AdminNpcSpawnItem(npcFormworkId, poiActionId);
|
||||
var agentSyncClientInfo = CreateStaticNpcSyncInfo(item, npc, poi, mainActionId);
|
||||
|
||||
try
|
||||
{
|
||||
await SendAetherInitForNpcAsync(session);
|
||||
await session.NotifyAsync(MethodId.SyncAetherAIStaticNpcAddData,
|
||||
UxSerializer.Serialize(new GameMethods.ClientStaticNpcInitData4229938
|
||||
{
|
||||
StaticNpcInfoId = npcFormworkId,
|
||||
NpcFormworkId = npcFormworkId,
|
||||
AgentPersonaId = npc.AgentPersonaId,
|
||||
SPoiActionId = 0,
|
||||
CPoiActionId = poiActionId,
|
||||
UrbanDiversityId = 88888000,
|
||||
IgnoreAllStim = false,
|
||||
TaskRelated = false,
|
||||
NpcPid = (int)npcFormworkId,
|
||||
AgentSyncClientInfo = agentSyncClientInfo,
|
||||
ForceGo = true,
|
||||
SourceType = 1,
|
||||
Id = id,
|
||||
Position = position,
|
||||
Facing = facing,
|
||||
EulerAngles = new SceneMethods.UxVector3(0, facing, 0),
|
||||
}), CancellationToken.None);
|
||||
|
||||
lock (state.SyncRoot)
|
||||
state.StaticNpcPreparedPlotEvents[id] = 0;
|
||||
|
||||
await session.NotifyAsync(MethodId.SyncManagedLogicAgent,
|
||||
UxSerializer.Serialize(WorldCodec.ManagedLogicAgent(id, Profile.PlayerPid, 0)), CancellationToken.None);
|
||||
|
||||
if (poi is { UsageId: not 0 })
|
||||
{
|
||||
await session.NotifyAsync(MethodId.SyncAgentBelongings,
|
||||
UxSerializer.Serialize(new GameMethods.SyncNpcBelongings4229938
|
||||
{
|
||||
Id = id,
|
||||
Items = poi.BelongingSceneItems.Select(configId => new GameMethods.NpcBelongingItem4229938
|
||||
{
|
||||
InstanceId = unchecked((ulong)Interlocked.Increment(ref s_nextNpcBelongingId)),
|
||||
ConfigId = configId,
|
||||
}).ToList(),
|
||||
DebugAgentId = npcFormworkId,
|
||||
}), CancellationToken.None);
|
||||
await session.NotifyAsync(MethodId.SyncAgentUseBelongingChanged,
|
||||
UxSerializer.Serialize(new GameMethods.SyncNpcBelongingUsage4229938
|
||||
{
|
||||
Id = id,
|
||||
UsageIds = [poi.UsageId],
|
||||
}), CancellationToken.None);
|
||||
}
|
||||
|
||||
if (poiActionId != 0)
|
||||
await session.NotifyAsync(MethodId.IGameSceneToClient_SyncAgentPlayPOIAction,
|
||||
UxSerializer.Serialize(new GameMethods.ClientNpcPoiActionData4229938
|
||||
{
|
||||
Id = id,
|
||||
CPoiActionId = poiActionId,
|
||||
IsActive = true,
|
||||
PlayPoiSpeed = 1f,
|
||||
}), CancellationToken.None);
|
||||
|
||||
return (true, "spawned", [id]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (false, ex.Message, []);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task SendAetherInitForNpcAsync(TcpSession session)
|
||||
{
|
||||
var settings = PrivateServerConfigStore.Current.Gameplay.Vehicles;
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
using Ananta.SDK.Network;
|
||||
using Ananta.SDK.Serialization;
|
||||
using Ananta.Server.Gameplay.Crowd;
|
||||
using Ananta.Server.Gameplay.Traffic;
|
||||
using Ananta.Server.Protocol.Client4229938;
|
||||
using Ananta.Server.RpcTypes.Client4229938;
|
||||
using SceneMethods = Ananta.Server.RpcTypes.Client4229938.Methods.GameScene;
|
||||
|
||||
namespace Ananta.Server.Handlers.Game;
|
||||
|
||||
internal sealed class TrafficAndCrowdDriver : ITrafficVehicleDriver, IUrbanCrowdDriver
|
||||
{
|
||||
public static TrafficAndCrowdDriver Instance { get; } = new();
|
||||
|
||||
public async Task<(bool Ok, ulong EntityId)> SpawnTrafficVehicleAsync(
|
||||
TcpSession session, uint configId, float x, float y, float z, float yaw)
|
||||
{
|
||||
var result = await GameRouter.SpawnDirectAsync(
|
||||
session, configId, new Vec3(x, y, z), yaw, rightOffset: 0f, sourceType: 2, reason: "city-traffic");
|
||||
return (result.Ok, result.EntityId);
|
||||
}
|
||||
|
||||
public async Task DestroyTrafficVehicleAsync(TcpSession session, ulong entityId)
|
||||
{
|
||||
await GameRouter.DestroyDirectAsync(session, entityId, "traffic-despawn");
|
||||
}
|
||||
|
||||
public async Task SendVehicleMoveAsync(
|
||||
TcpSession session, ulong entityId, float x, float y, float z, float yaw, float vx, float vz)
|
||||
{
|
||||
var moveData = new SceneMethods.RaidVehicleSyncData
|
||||
{
|
||||
Id = entityId,
|
||||
Position = new SceneMethods.UxVector3(x, y, z),
|
||||
FacingDirection = yaw,
|
||||
EulerAngles = new SceneMethods.UxVector3(0f, yaw, 0f),
|
||||
Velocity = new SceneMethods.UxVector3(vx, 0f, vz),
|
||||
Bits = [],
|
||||
MoveToken = 0,
|
||||
};
|
||||
await session.NotifyAsync(MethodId.SyncVehicleMove, UxSerializer.Serialize(moveData), CancellationToken.None);
|
||||
GameRouter.TrackVehicleMove(entityId, x, y, z, yaw);
|
||||
}
|
||||
|
||||
public bool IsVehiclePlayerControlled(ulong entityId)
|
||||
{
|
||||
return GameRouter.TryGetSummoned(entityId, out var summoned) && summoned?.ControllerPid != 0;
|
||||
}
|
||||
|
||||
public (bool HasPlayer, float X, float Y, float Z, float Yaw, uint RaidId) GetPlayerPosition(TcpSession session)
|
||||
{
|
||||
var state = GameRouter.GetStateIfExists(session);
|
||||
if (state is null)
|
||||
return (false, 0, 0, 0, 0, 0);
|
||||
|
||||
lock (state.SyncRoot)
|
||||
{
|
||||
if (!state.Ready || !state.HasLastReportedPlayerTransform)
|
||||
return (false, 0, 0, 0, 0, 0);
|
||||
|
||||
var pos = state.LastReportedPlayerPosition;
|
||||
var rot = state.LastReportedPlayerRotation;
|
||||
return (true, pos.X, pos.Y, pos.Z, rot.Y, state.ActiveRaidId);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Ok, ulong[] EntityIds)> SpawnCrowdPedestriansAsync(
|
||||
TcpSession session, uint npcFormworkId, uint poiActionId, float x, float y, float z, float facing)
|
||||
{
|
||||
var result = await GameRouter.SpawnStaticNpcAtAsync(session, npcFormworkId, poiActionId, x, y, z, facing);
|
||||
return (result.Ok, result.Ids);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user