neon-sprawl/server/NeonSprawl.Server/Game/Npc/NpcRuntimeSnapshotWorldApi.cs

106 lines
4.1 KiB
C#

using NeonSprawl.Server.Game.Combat;
using NeonSprawl.Server.Game.PositionState;
namespace NeonSprawl.Server.Game.Npc;
/// <summary>Maps <c>GET /game/world/npc-runtime-snapshot</c> (NEO-94).</summary>
public static class NpcRuntimeSnapshotWorldApi
{
public static WebApplication MapNpcRuntimeSnapshotWorldApi(this WebApplication app)
{
app.MapGet(
"/game/world/npc-runtime-snapshot",
(
TimeProvider clock,
INpcRuntimeStateStore runtimeStore,
IThreatStateStore threatStore,
INpcBehaviorDefinitionRegistry behaviorRegistry,
IAbilityDefinitionRegistry abilityRegistry,
ICombatEntityHealthStore combatHealthStore,
IPositionStateStore positionStore,
IPlayerCombatHealthStore playerHealthStore) =>
{
var now = clock.GetUtcNow();
NpcRuntimeOperations.AdvanceAll(
now,
runtimeStore,
threatStore,
behaviorRegistry,
abilityRegistry,
combatHealthStore,
positionStore,
playerHealthStore);
return Results.Json(BuildSnapshot(now, runtimeStore, threatStore, behaviorRegistry));
});
return app;
}
internal static NpcRuntimeSnapshotResponse BuildSnapshot(
DateTimeOffset nowUtc,
INpcRuntimeStateStore runtimeStore,
IThreatStateStore threatStore,
INpcBehaviorDefinitionRegistry behaviorRegistry)
{
var ids = PrototypeNpcRegistry.GetInstanceIdsInOrder();
var instances = new List<NpcInstanceRuntimeJson>(ids.Count);
foreach (var npcInstanceId in ids)
{
if (!PrototypeNpcRegistry.TryGet(npcInstanceId, out var entry))
{
throw new InvalidOperationException(
$"Prototype NPC instance '{npcInstanceId}' is missing from {nameof(PrototypeNpcRegistry)}.");
}
if (!behaviorRegistry.TryGetDefinition(entry.BehaviorDefId, out var behavior))
{
throw new InvalidOperationException(
$"Behavior definition '{entry.BehaviorDefId}' for NPC '{npcInstanceId}' is missing from {nameof(INpcBehaviorDefinitionRegistry)}.");
}
if (!runtimeStore.TryGet(npcInstanceId, out var runtime))
{
throw new InvalidOperationException(
$"Prototype NPC runtime row '{npcInstanceId}' is registered but missing from {nameof(INpcRuntimeStateStore)}.");
}
string? aggroHolderPlayerId = null;
if (threatStore.TryGet(npcInstanceId, out var threat))
{
aggroHolderPlayerId = threat.AggroHolderPlayerId;
}
ActiveTelegraphJson? activeTelegraph = null;
if (runtime.BehaviorState == NpcBehaviorState.TelegraphWindup &&
runtime.ActiveTelegraph is { } telegraph)
{
var elapsedSeconds = (nowUtc - telegraph.WindupStartedUtc).TotalSeconds;
var remainingSeconds = Math.Max(0, behavior.TelegraphWindupSeconds - elapsedSeconds);
activeTelegraph = new ActiveTelegraphJson
{
TelegraphId = telegraph.TelegraphId,
NpcInstanceId = npcInstanceId,
WindupRemainingSeconds = remainingSeconds,
ArchetypeKind = behavior.ArchetypeKind,
};
}
instances.Add(
new NpcInstanceRuntimeJson
{
NpcInstanceId = npcInstanceId,
BehaviorDefId = entry.BehaviorDefId,
State = NpcBehaviorStateWire.ToWireName(runtime.BehaviorState),
AggroHolderPlayerId = aggroHolderPlayerId,
ActiveTelegraph = activeTelegraph,
});
}
return new NpcRuntimeSnapshotResponse
{
ServerTimeUtc = nowUtc,
NpcInstances = instances,
};
}
}