147 lines
4.8 KiB
C#
147 lines
4.8 KiB
C#
namespace NeonSprawl.Server.Game.Encounters;
|
|
|
|
/// <summary>
|
|
/// Encounter activation and defeat-all progress orchestration (NEO-104).
|
|
/// Combat cast wiring: <see cref="AbilityInput.AbilityCastApi"/> (NEO-106).
|
|
/// </summary>
|
|
public static class EncounterProgressOperations
|
|
{
|
|
/// <summary>
|
|
/// Resolves the first encounter (id order) whose <c>requiredNpcInstanceIds</c> contains <paramref name="npcInstanceId"/>.
|
|
/// When multiple encounters share an NPC, the earliest id wins.
|
|
/// </summary>
|
|
public static bool TryResolveEncounterForNpc(
|
|
string npcInstanceId,
|
|
IEncounterDefinitionRegistry encounterRegistry,
|
|
out string encounterId,
|
|
out EncounterDefRow definition)
|
|
{
|
|
encounterId = string.Empty;
|
|
definition = null!;
|
|
var npcKey = EncounterProgressIds.NormalizeNpcInstanceId(npcInstanceId);
|
|
if (npcKey.Length == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var row in encounterRegistry.GetDefinitionsInIdOrder())
|
|
{
|
|
if (row.RequiredNpcInstanceIds.Any(id =>
|
|
string.Equals(
|
|
EncounterProgressIds.NormalizeNpcInstanceId(id),
|
|
npcKey,
|
|
StringComparison.Ordinal)))
|
|
{
|
|
encounterId = row.Id;
|
|
definition = row;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Activates encounter progress on first engagement with a required NPC.
|
|
/// No-op when already started, completed, or NPC is not part of any encounter.
|
|
/// </summary>
|
|
public static bool TryActivateOnFirstEngagement(
|
|
string playerId,
|
|
string npcInstanceId,
|
|
IEncounterDefinitionRegistry encounterRegistry,
|
|
IEncounterProgressStore progressStore,
|
|
IEncounterCompletionStore completionStore)
|
|
{
|
|
if (!TryResolveEncounterForNpc(npcInstanceId, encounterRegistry, out var encounterId, out _))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (completionStore.IsCompleted(playerId, encounterId))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// --- Telemetry hook site (NEO-109): future E9.M1 catalog event `encounter_start` ---
|
|
// TODO(E9.M1): catalog emit — on first activation for player+encounter (NEO-106 wires cast path).
|
|
return progressStore.TryActivate(playerId, encounterId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Records one required NPC defeat. Requires prior activation; idempotent when the NPC was already marked defeated.
|
|
/// </summary>
|
|
public static bool TryMarkTargetDefeated(
|
|
string playerId,
|
|
string npcInstanceId,
|
|
IEncounterDefinitionRegistry encounterRegistry,
|
|
IEncounterProgressStore progressStore,
|
|
IEncounterCompletionStore completionStore)
|
|
{
|
|
if (!TryResolveEncounterForNpc(npcInstanceId, encounterRegistry, out var encounterId, out var definition))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (completionStore.IsCompleted(playerId, encounterId))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var npcKey = EncounterProgressIds.NormalizeNpcInstanceId(npcInstanceId);
|
|
if (!definition.RequiredNpcInstanceIds.Any(id =>
|
|
string.Equals(EncounterProgressIds.NormalizeNpcInstanceId(id), npcKey, StringComparison.Ordinal)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!progressStore.TryGetProgress(playerId, encounterId, out var progress) || !progress.Started)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (progress.DefeatedNpcInstanceIds.Contains(npcKey))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return progressStore.TryAddDefeatedTarget(playerId, encounterId, npcInstanceId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// <c>true</c> when every <c>requiredNpcInstanceIds</c> entry is in the defeated set for a started encounter.
|
|
/// </summary>
|
|
public static bool IsAllRequiredTargetsDefeated(
|
|
string playerId,
|
|
string encounterId,
|
|
IEncounterDefinitionRegistry encounterRegistry,
|
|
IEncounterProgressStore progressStore)
|
|
{
|
|
if (!encounterRegistry.TryGetDefinition(encounterId, out var definition))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!progressStore.TryGetProgress(playerId, encounterId, out var progress) || !progress.Started)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var required = new HashSet<string>(StringComparer.Ordinal);
|
|
foreach (var id in definition.RequiredNpcInstanceIds)
|
|
{
|
|
var normalized = EncounterProgressIds.NormalizeNpcInstanceId(id);
|
|
if (normalized.Length > 0)
|
|
{
|
|
required.Add(normalized);
|
|
}
|
|
}
|
|
|
|
if (required.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return required.SetEquals(progress.DefeatedNpcInstanceIds);
|
|
}
|
|
}
|