NEO-95: add player combat HP store and NPC attack resolve
Wire telegraph-complete damage into AdvanceAll and expose GET
/game/players/{id}/combat-health for session player HP read model.
pull/133/head
parent
ac4e1dff10
commit
cb1692ce85
|
|
@ -1,4 +1,6 @@
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using NeonSprawl.Server.Game.Npc;
|
using NeonSprawl.Server.Game.Npc;
|
||||||
|
using NeonSprawl.Server.Game.PositionState;
|
||||||
|
|
||||||
namespace NeonSprawl.Server.Game.Combat;
|
namespace NeonSprawl.Server.Game.Combat;
|
||||||
|
|
||||||
|
|
@ -9,7 +11,13 @@ public static class CombatTargetFixtureApi
|
||||||
{
|
{
|
||||||
app.MapPost(
|
app.MapPost(
|
||||||
"/game/__dev/combat-targets-fixture",
|
"/game/__dev/combat-targets-fixture",
|
||||||
(CombatTargetFixtureRequest? body, ICombatEntityHealthStore healthStore, IThreatStateStore threatStore, INpcRuntimeStateStore runtimeStore) =>
|
(
|
||||||
|
CombatTargetFixtureRequest? body,
|
||||||
|
ICombatEntityHealthStore healthStore,
|
||||||
|
IThreatStateStore threatStore,
|
||||||
|
INpcRuntimeStateStore runtimeStore,
|
||||||
|
IPlayerCombatHealthStore playerHealthStore,
|
||||||
|
IOptions<GamePositionOptions> gameOptions) =>
|
||||||
{
|
{
|
||||||
if (body is null || body.SchemaVersion != CombatTargetFixtureRequest.CurrentSchemaVersion)
|
if (body is null || body.SchemaVersion != CombatTargetFixtureRequest.CurrentSchemaVersion)
|
||||||
{
|
{
|
||||||
|
|
@ -27,6 +35,12 @@ public static class CombatTargetFixtureApi
|
||||||
AggroOperations.ClearAllPrototypeHolders(threatStore);
|
AggroOperations.ClearAllPrototypeHolders(threatStore);
|
||||||
NpcRuntimeOperations.ResetAllPrototypeRows(runtimeStore);
|
NpcRuntimeOperations.ResetAllPrototypeRows(runtimeStore);
|
||||||
|
|
||||||
|
var devPlayerId = gameOptions.Value.DevPlayerId;
|
||||||
|
if (!playerHealthStore.TryResetToFull(devPlayerId, out _))
|
||||||
|
{
|
||||||
|
return Results.NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
return Results.Json(
|
return Results.Json(
|
||||||
new CombatTargetFixtureResponse
|
new CombatTargetFixtureResponse
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
namespace NeonSprawl.Server.Game.Combat;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Session in-memory player combat HP for incoming NPC damage (NEO-95).
|
||||||
|
/// No Postgres persistence in Epic 5 Slice 2.
|
||||||
|
/// </summary>
|
||||||
|
public interface IPlayerCombatHealthStore
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the current snapshot for a player. Lazy-initializes HP to
|
||||||
|
/// <see cref="PlayerCombatHealthDefaults.MaxHp"/> on first access.
|
||||||
|
/// </summary>
|
||||||
|
bool TryGet(string? playerId, out PlayerCombatHealthSnapshot snapshot);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies non-negative NPC attack damage. Lazy-initializes on first access.
|
||||||
|
/// Floors <see cref="PlayerCombatHealthSnapshot.CurrentHp"/> at zero.
|
||||||
|
/// </summary>
|
||||||
|
bool TryApplyDamage(string? playerId, int damage, out PlayerCombatHealthSnapshot snapshot);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restores a player to full HP. Creates the row when absent (dev fixture + tests).
|
||||||
|
/// </summary>
|
||||||
|
bool TryResetToFull(string? playerId, out PlayerCombatHealthSnapshot snapshot);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace NeonSprawl.Server.Game.Combat;
|
||||||
|
|
||||||
|
/// <summary>Thread-safe in-memory session player combat HP; empty at startup — lazy rows only (NEO-95).</summary>
|
||||||
|
public sealed class InMemoryPlayerCombatHealthStore : IPlayerCombatHealthStore
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<string, int> currentHpById = new(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, object> idLocks = new(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool TryGet(string? playerId, out PlayerCombatHealthSnapshot snapshot)
|
||||||
|
{
|
||||||
|
var key = NormalizePlayerId(playerId);
|
||||||
|
if (key.Length == 0)
|
||||||
|
{
|
||||||
|
snapshot = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (idLocks.GetOrAdd(key, _ => new object()))
|
||||||
|
{
|
||||||
|
EnsureRowInitialized(key);
|
||||||
|
snapshot = CreateSnapshot(key, currentHpById[key]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool TryApplyDamage(string? playerId, int damage, out PlayerCombatHealthSnapshot snapshot)
|
||||||
|
{
|
||||||
|
snapshot = default;
|
||||||
|
if (damage < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = NormalizePlayerId(playerId);
|
||||||
|
if (key.Length == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (idLocks.GetOrAdd(key, _ => new object()))
|
||||||
|
{
|
||||||
|
EnsureRowInitialized(key);
|
||||||
|
var current = currentHpById[key];
|
||||||
|
var next = Math.Max(0, current - damage);
|
||||||
|
currentHpById[key] = next;
|
||||||
|
snapshot = CreateSnapshot(key, next);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool TryResetToFull(string? playerId, out PlayerCombatHealthSnapshot snapshot)
|
||||||
|
{
|
||||||
|
var key = NormalizePlayerId(playerId);
|
||||||
|
if (key.Length == 0)
|
||||||
|
{
|
||||||
|
snapshot = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (idLocks.GetOrAdd(key, _ => new object()))
|
||||||
|
{
|
||||||
|
currentHpById[key] = PlayerCombatHealthDefaults.MaxHp;
|
||||||
|
snapshot = CreateSnapshot(key, PlayerCombatHealthDefaults.MaxHp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureRowInitialized(string normalizedId)
|
||||||
|
{
|
||||||
|
if (!currentHpById.ContainsKey(normalizedId))
|
||||||
|
{
|
||||||
|
currentHpById[normalizedId] = PlayerCombatHealthDefaults.MaxHp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PlayerCombatHealthSnapshot CreateSnapshot(string playerId, int currentHp) =>
|
||||||
|
new(
|
||||||
|
playerId,
|
||||||
|
PlayerCombatHealthDefaults.MaxHp,
|
||||||
|
currentHp,
|
||||||
|
currentHp <= 0);
|
||||||
|
|
||||||
|
private static string NormalizePlayerId(string? playerId) =>
|
||||||
|
playerId?.Trim().ToLowerInvariant() ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
using NeonSprawl.Server.Game.Npc;
|
||||||
|
|
||||||
|
namespace NeonSprawl.Server.Game.Combat;
|
||||||
|
|
||||||
|
/// <summary>Deterministic NPC telegraph-complete damage resolve (NEO-95).</summary>
|
||||||
|
public static class NpcAttackOperations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Applies catalog <paramref name="attackDamage"/> to <paramref name="holderPlayerId"/> when
|
||||||
|
/// the threat row still names that holder for <paramref name="npcInstanceId"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static bool TryResolveTelegraphComplete(
|
||||||
|
string npcInstanceId,
|
||||||
|
string holderPlayerId,
|
||||||
|
int attackDamage,
|
||||||
|
IPlayerCombatHealthStore playerHealthStore,
|
||||||
|
IThreatStateStore threatStore)
|
||||||
|
{
|
||||||
|
if (attackDamage <= 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(holderPlayerId) ||
|
||||||
|
!threatStore.TryGet(npcInstanceId, out var threat) ||
|
||||||
|
string.IsNullOrEmpty(threat.AggroHolderPlayerId) ||
|
||||||
|
!string.Equals(threat.AggroHolderPlayerId, holderPlayerId, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playerHealthStore.TryApplyDamage(holderPlayerId, attackDamage, out var snapshot))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.Defeated)
|
||||||
|
{
|
||||||
|
// TODO(E9.M1): player_death
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
using NeonSprawl.Server.Game.PositionState;
|
||||||
|
|
||||||
|
namespace NeonSprawl.Server.Game.Combat;
|
||||||
|
|
||||||
|
/// <summary>Maps <c>GET /game/players/{id}/combat-health</c> (NEO-95).</summary>
|
||||||
|
public static class PlayerCombatHealthApi
|
||||||
|
{
|
||||||
|
public static WebApplication MapPlayerCombatHealthApi(this WebApplication app)
|
||||||
|
{
|
||||||
|
app.MapGet(
|
||||||
|
"/game/players/{id}/combat-health",
|
||||||
|
(string id, IPositionStateStore positions, IPlayerCombatHealthStore playerHealthStore) =>
|
||||||
|
{
|
||||||
|
if (!positions.TryGetPosition(id, out _))
|
||||||
|
{
|
||||||
|
return Results.NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playerHealthStore.TryGet(id, out var snapshot))
|
||||||
|
{
|
||||||
|
return Results.NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Results.Json(BuildResponse(snapshot));
|
||||||
|
});
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static PlayerCombatHealthResponse BuildResponse(in PlayerCombatHealthSnapshot snapshot) =>
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
PlayerId = snapshot.PlayerId,
|
||||||
|
MaxHp = snapshot.MaxHp,
|
||||||
|
CurrentHp = snapshot.CurrentHp,
|
||||||
|
Defeated = snapshot.Defeated,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace NeonSprawl.Server.Game.Combat;
|
||||||
|
|
||||||
|
/// <summary>Prototype session player combat HP defaults (E5M2-09 / NEO-95).</summary>
|
||||||
|
public static class PlayerCombatHealthDefaults
|
||||||
|
{
|
||||||
|
/// <summary>Lazy-init max HP for session player combat rows.</summary>
|
||||||
|
public const int MaxHp = 100;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace NeonSprawl.Server.Game.Combat;
|
||||||
|
|
||||||
|
/// <summary>JSON body for <c>GET /game/players/{id}/combat-health</c> (NEO-95).</summary>
|
||||||
|
public sealed class PlayerCombatHealthResponse
|
||||||
|
{
|
||||||
|
public const int CurrentSchemaVersion = 1;
|
||||||
|
|
||||||
|
[JsonPropertyName("schemaVersion")]
|
||||||
|
public int SchemaVersion { get; init; } = CurrentSchemaVersion;
|
||||||
|
|
||||||
|
[JsonPropertyName("playerId")]
|
||||||
|
public required string PlayerId { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("maxHp")]
|
||||||
|
public required int MaxHp { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("currentHp")]
|
||||||
|
public required int CurrentHp { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("defeated")]
|
||||||
|
public required bool Defeated { get; init; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace NeonSprawl.Server.Game.Combat;
|
||||||
|
|
||||||
|
/// <summary>Registers session player combat health store (NEO-95).</summary>
|
||||||
|
public static class PlayerCombatHealthServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
/// <summary>Registers <see cref="IPlayerCombatHealthStore"/> as an in-memory singleton.</summary>
|
||||||
|
public static IServiceCollection AddPlayerCombatHealthStore(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<IPlayerCombatHealthStore, InMemoryPlayerCombatHealthStore>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace NeonSprawl.Server.Game.Combat;
|
||||||
|
|
||||||
|
/// <summary>Authoritative HP snapshot for a session player (NEO-95).</summary>
|
||||||
|
/// <param name="PlayerId">Normalized lowercase player id.</param>
|
||||||
|
/// <param name="MaxHp">Prototype session max HP.</param>
|
||||||
|
/// <param name="CurrentHp">Current HP after NPC damage; floored at zero.</param>
|
||||||
|
/// <param name="Defeated"><c>true</c> when <paramref name="CurrentHp"/> is zero.</param>
|
||||||
|
public readonly record struct PlayerCombatHealthSnapshot(
|
||||||
|
string PlayerId,
|
||||||
|
int MaxHp,
|
||||||
|
int CurrentHp,
|
||||||
|
bool Defeated);
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using NeonSprawl.Server.Game.Combat;
|
||||||
|
|
||||||
namespace NeonSprawl.Server.Game.Npc;
|
namespace NeonSprawl.Server.Game.Npc;
|
||||||
|
|
||||||
/// <summary>Deterministic prototype NPC behavior state machine + lazy tick advance (NEO-93).</summary>
|
/// <summary>Deterministic prototype NPC behavior state machine + lazy tick advance (NEO-93).</summary>
|
||||||
|
|
@ -14,6 +16,7 @@ public static class NpcRuntimeOperations
|
||||||
INpcRuntimeStateStore runtimeStore,
|
INpcRuntimeStateStore runtimeStore,
|
||||||
IThreatStateStore threatStore,
|
IThreatStateStore threatStore,
|
||||||
INpcBehaviorDefinitionRegistry behaviorRegistry,
|
INpcBehaviorDefinitionRegistry behaviorRegistry,
|
||||||
|
IPlayerCombatHealthStore playerHealthStore,
|
||||||
double maxDeltaSeconds = DefaultMaxDeltaSeconds)
|
double maxDeltaSeconds = DefaultMaxDeltaSeconds)
|
||||||
{
|
{
|
||||||
if (maxDeltaSeconds <= 0)
|
if (maxDeltaSeconds <= 0)
|
||||||
|
|
@ -32,7 +35,8 @@ public static class NpcRuntimeOperations
|
||||||
nowUtc,
|
nowUtc,
|
||||||
runtimeStore,
|
runtimeStore,
|
||||||
threatStore,
|
threatStore,
|
||||||
behaviorRegistry);
|
behaviorRegistry,
|
||||||
|
playerHealthStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
runtimeStore.LastAdvancedUtc = nowUtc;
|
runtimeStore.LastAdvancedUtc = nowUtc;
|
||||||
|
|
@ -50,7 +54,8 @@ public static class NpcRuntimeOperations
|
||||||
nowUtc,
|
nowUtc,
|
||||||
runtimeStore,
|
runtimeStore,
|
||||||
threatStore,
|
threatStore,
|
||||||
behaviorRegistry);
|
behaviorRegistry,
|
||||||
|
playerHealthStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
@ -68,7 +73,8 @@ public static class NpcRuntimeOperations
|
||||||
windowEnd,
|
windowEnd,
|
||||||
runtimeStore,
|
runtimeStore,
|
||||||
threatStore,
|
threatStore,
|
||||||
behaviorRegistry);
|
behaviorRegistry,
|
||||||
|
playerHealthStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
runtimeStore.LastAdvancedUtc = windowEnd;
|
runtimeStore.LastAdvancedUtc = windowEnd;
|
||||||
|
|
@ -84,7 +90,8 @@ public static class NpcRuntimeOperations
|
||||||
DateTimeOffset windowEnd,
|
DateTimeOffset windowEnd,
|
||||||
INpcRuntimeStateStore runtimeStore,
|
INpcRuntimeStateStore runtimeStore,
|
||||||
IThreatStateStore threatStore,
|
IThreatStateStore threatStore,
|
||||||
INpcBehaviorDefinitionRegistry behaviorRegistry)
|
INpcBehaviorDefinitionRegistry behaviorRegistry,
|
||||||
|
IPlayerCombatHealthStore playerHealthStore)
|
||||||
{
|
{
|
||||||
if (!PrototypeNpcRegistry.TryGet(npcInstanceId, out var entry) ||
|
if (!PrototypeNpcRegistry.TryGet(npcInstanceId, out var entry) ||
|
||||||
!behaviorRegistry.TryGetDefinition(entry.BehaviorDefId, out var behavior))
|
!behaviorRegistry.TryGetDefinition(entry.BehaviorDefId, out var behavior))
|
||||||
|
|
@ -102,7 +109,7 @@ public static class NpcRuntimeOperations
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryHasAggroHolder(npcInstanceId, threatStore, out _))
|
if (!TryHasAggroHolder(npcInstanceId, threatStore, out var aggroHolderPlayerId))
|
||||||
{
|
{
|
||||||
if (snapshot.BehaviorState != NpcBehaviorState.Idle || snapshot.ActiveTelegraph is not null)
|
if (snapshot.BehaviorState != NpcBehaviorState.Idle || snapshot.ActiveTelegraph is not null)
|
||||||
{
|
{
|
||||||
|
|
@ -130,7 +137,7 @@ public static class NpcRuntimeOperations
|
||||||
var cursor = windowStart;
|
var cursor = windowStart;
|
||||||
while (cursor < windowEnd)
|
while (cursor < windowEnd)
|
||||||
{
|
{
|
||||||
if (!TryHasAggroHolder(npcInstanceId, threatStore, out _))
|
if (!TryHasAggroHolder(npcInstanceId, threatStore, out aggroHolderPlayerId))
|
||||||
{
|
{
|
||||||
WriteIdle(runtimeStore, npcInstanceId);
|
WriteIdle(runtimeStore, npcInstanceId);
|
||||||
return;
|
return;
|
||||||
|
|
@ -176,24 +183,36 @@ public static class NpcRuntimeOperations
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor = phaseEnd;
|
cursor = phaseEnd;
|
||||||
|
NpcAttackOperations.TryResolveTelegraphComplete(
|
||||||
|
npcInstanceId,
|
||||||
|
aggroHolderPlayerId,
|
||||||
|
behavior.AttackDamage,
|
||||||
|
playerHealthStore,
|
||||||
|
threatStore);
|
||||||
WriteState(
|
WriteState(
|
||||||
runtimeStore,
|
runtimeStore,
|
||||||
npcInstanceId,
|
npcInstanceId,
|
||||||
NpcBehaviorState.Recover,
|
NpcBehaviorState.Recover,
|
||||||
cursor,
|
cursor,
|
||||||
activeTelegraph: null);
|
activeTelegraph: null);
|
||||||
// telemetry: telegraph_fired / npc_state_transition (NEO-96); logical attack_execute → recover (NEO-95 applies attackDamage)
|
// telemetry: telegraph_fired / npc_state_transition (NEO-96); logical attack_execute → recover
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case NpcBehaviorState.AttackExecute:
|
case NpcBehaviorState.AttackExecute:
|
||||||
|
NpcAttackOperations.TryResolveTelegraphComplete(
|
||||||
|
npcInstanceId,
|
||||||
|
aggroHolderPlayerId,
|
||||||
|
behavior.AttackDamage,
|
||||||
|
playerHealthStore,
|
||||||
|
threatStore);
|
||||||
WriteState(
|
WriteState(
|
||||||
runtimeStore,
|
runtimeStore,
|
||||||
npcInstanceId,
|
npcInstanceId,
|
||||||
NpcBehaviorState.Recover,
|
NpcBehaviorState.Recover,
|
||||||
cursor,
|
cursor,
|
||||||
activeTelegraph: null);
|
activeTelegraph: null);
|
||||||
// telemetry: npc_state_transition (NEO-96); NEO-95 applies attackDamage here
|
// telemetry: npc_state_transition (NEO-96)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NpcBehaviorState.Recover:
|
case NpcBehaviorState.Recover:
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using NeonSprawl.Server.Game.Combat;
|
||||||
|
|
||||||
namespace NeonSprawl.Server.Game.Npc;
|
namespace NeonSprawl.Server.Game.Npc;
|
||||||
|
|
||||||
/// <summary>Maps <c>GET /game/world/npc-runtime-snapshot</c> (NEO-94).</summary>
|
/// <summary>Maps <c>GET /game/world/npc-runtime-snapshot</c> (NEO-94).</summary>
|
||||||
|
|
@ -11,10 +13,16 @@ public static class NpcRuntimeSnapshotWorldApi
|
||||||
TimeProvider clock,
|
TimeProvider clock,
|
||||||
INpcRuntimeStateStore runtimeStore,
|
INpcRuntimeStateStore runtimeStore,
|
||||||
IThreatStateStore threatStore,
|
IThreatStateStore threatStore,
|
||||||
INpcBehaviorDefinitionRegistry behaviorRegistry) =>
|
INpcBehaviorDefinitionRegistry behaviorRegistry,
|
||||||
|
IPlayerCombatHealthStore playerHealthStore) =>
|
||||||
{
|
{
|
||||||
var now = clock.GetUtcNow();
|
var now = clock.GetUtcNow();
|
||||||
NpcRuntimeOperations.AdvanceAll(now, runtimeStore, threatStore, behaviorRegistry);
|
NpcRuntimeOperations.AdvanceAll(
|
||||||
|
now,
|
||||||
|
runtimeStore,
|
||||||
|
threatStore,
|
||||||
|
behaviorRegistry,
|
||||||
|
playerHealthStore);
|
||||||
return Results.Json(BuildSnapshot(now, runtimeStore, threatStore, behaviorRegistry));
|
return Results.Json(BuildSnapshot(now, runtimeStore, threatStore, behaviorRegistry));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ builder.Services.AddAbilityDefinitionCatalog(builder.Configuration);
|
||||||
builder.Services.AddNpcBehaviorDefinitionCatalog(builder.Configuration);
|
builder.Services.AddNpcBehaviorDefinitionCatalog(builder.Configuration);
|
||||||
builder.Services.AddThreatStateStore();
|
builder.Services.AddThreatStateStore();
|
||||||
builder.Services.AddNpcRuntimeStateStore();
|
builder.Services.AddNpcRuntimeStateStore();
|
||||||
|
builder.Services.AddPlayerCombatHealthStore();
|
||||||
builder.Services.AddMasteryCatalog(builder.Configuration);
|
builder.Services.AddMasteryCatalog(builder.Configuration);
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
@ -59,6 +60,7 @@ app.MapAbilityDefinitionsWorldApi();
|
||||||
app.MapNpcBehaviorDefinitionsWorldApi();
|
app.MapNpcBehaviorDefinitionsWorldApi();
|
||||||
app.MapCombatTargetsWorldApi();
|
app.MapCombatTargetsWorldApi();
|
||||||
app.MapNpcRuntimeSnapshotWorldApi();
|
app.MapNpcRuntimeSnapshotWorldApi();
|
||||||
|
app.MapPlayerCombatHealthApi();
|
||||||
app.MapResourceNodeDefinitionsWorldApi();
|
app.MapResourceNodeDefinitionsWorldApi();
|
||||||
app.MapPlayerInventoryApi();
|
app.MapPlayerInventoryApi();
|
||||||
app.MapPlayerCraftApi();
|
app.MapPlayerCraftApi();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue