146 lines
5.0 KiB
C#
146 lines
5.0 KiB
C#
namespace NeonSprawl.Server.Game.Quests;
|
|
|
|
/// <summary>
|
|
/// Server-authoritative quest accept, step advance, and complete with reason codes (NEO-117).
|
|
/// Objective wiring: E7M1-07 (NEO-118). HTTP: E7M1-08/09 (NEO-119/NEO-120).
|
|
/// </summary>
|
|
public static class QuestStateOperations
|
|
{
|
|
/// <summary>
|
|
/// Validates quest id and prerequisites, then activates at step 0 when not started.
|
|
/// </summary>
|
|
public static QuestStateOperationResult TryAccept(
|
|
string playerId,
|
|
string questId,
|
|
IQuestDefinitionRegistry questRegistry,
|
|
IPlayerQuestStateStore progressStore)
|
|
{
|
|
if (!questRegistry.TryNormalizeKnown(questId, out var normalizedQuestId) ||
|
|
!questRegistry.TryGetDefinition(normalizedQuestId, out var definition))
|
|
{
|
|
return Deny(QuestStateReasonCodes.UnknownQuest);
|
|
}
|
|
|
|
if (progressStore.TryGetProgress(playerId, normalizedQuestId, out var existing))
|
|
{
|
|
if (existing.Status == QuestProgressStatus.Completed)
|
|
{
|
|
return Deny(QuestStateReasonCodes.AlreadyCompleted, existing);
|
|
}
|
|
|
|
return Deny(QuestStateReasonCodes.AlreadyActive, existing);
|
|
}
|
|
|
|
foreach (var prerequisiteId in definition.PrerequisiteQuestIds)
|
|
{
|
|
if (!progressStore.TryGetProgress(playerId, prerequisiteId, out var prerequisite) ||
|
|
prerequisite.Status != QuestProgressStatus.Completed)
|
|
{
|
|
return Deny(QuestStateReasonCodes.PrerequisiteIncomplete);
|
|
}
|
|
}
|
|
|
|
if (progressStore.TryActivate(playerId, normalizedQuestId, out var snapshot))
|
|
{
|
|
return Success(snapshot);
|
|
}
|
|
|
|
return Deny(QuestStateReasonCodes.UnknownPlayer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Advances an active quest to <paramref name="newStepIndex"/> when within catalog bounds.
|
|
/// </summary>
|
|
public static QuestStateOperationResult TryAdvanceStep(
|
|
string playerId,
|
|
string questId,
|
|
int newStepIndex,
|
|
IQuestDefinitionRegistry questRegistry,
|
|
IPlayerQuestStateStore progressStore)
|
|
{
|
|
if (!questRegistry.TryNormalizeKnown(questId, out var normalizedQuestId) ||
|
|
!questRegistry.TryGetDefinition(normalizedQuestId, out var definition))
|
|
{
|
|
return Deny(QuestStateReasonCodes.UnknownQuest);
|
|
}
|
|
|
|
if (!progressStore.TryGetProgress(playerId, normalizedQuestId, out var existing))
|
|
{
|
|
return Deny(QuestStateReasonCodes.NotActive);
|
|
}
|
|
|
|
if (existing.Status == QuestProgressStatus.Completed)
|
|
{
|
|
return Deny(QuestStateReasonCodes.AlreadyCompleted, existing);
|
|
}
|
|
|
|
if (existing.Status != QuestProgressStatus.Active)
|
|
{
|
|
return Deny(QuestStateReasonCodes.NotActive);
|
|
}
|
|
|
|
if (newStepIndex <= existing.CurrentStepIndex || newStepIndex >= definition.Steps.Count)
|
|
{
|
|
return Deny(QuestStateReasonCodes.InvalidStepIndex, existing);
|
|
}
|
|
|
|
if (progressStore.TryAdvanceStep(playerId, normalizedQuestId, newStepIndex, out var snapshot))
|
|
{
|
|
return Success(snapshot);
|
|
}
|
|
|
|
return Deny(QuestStateReasonCodes.UnknownPlayer, existing);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Marks a quest complete. Idempotent: replays return success with the existing snapshot.
|
|
/// </summary>
|
|
public static QuestStateOperationResult TryMarkComplete(
|
|
string playerId,
|
|
string questId,
|
|
IQuestDefinitionRegistry questRegistry,
|
|
IPlayerQuestStateStore progressStore,
|
|
TimeProvider timeProvider)
|
|
{
|
|
if (!questRegistry.TryNormalizeKnown(questId, out var normalizedQuestId) ||
|
|
!questRegistry.TryGetDefinition(normalizedQuestId, out _))
|
|
{
|
|
return Deny(QuestStateReasonCodes.UnknownQuest);
|
|
}
|
|
|
|
if (!progressStore.TryGetProgress(playerId, normalizedQuestId, out var progress))
|
|
{
|
|
return Deny(QuestStateReasonCodes.NotActive);
|
|
}
|
|
|
|
if (progress.Status == QuestProgressStatus.Completed)
|
|
{
|
|
return Success(progress);
|
|
}
|
|
|
|
if (progress.Status != QuestProgressStatus.Active)
|
|
{
|
|
return Deny(QuestStateReasonCodes.NotActive);
|
|
}
|
|
|
|
if (progressStore.TryMarkComplete(playerId, normalizedQuestId, timeProvider.GetUtcNow(), out var snapshot))
|
|
{
|
|
return Success(snapshot);
|
|
}
|
|
|
|
if (progressStore.TryGetProgress(playerId, normalizedQuestId, out var afterAttempt) &&
|
|
afterAttempt.Status == QuestProgressStatus.Completed)
|
|
{
|
|
return Success(afterAttempt);
|
|
}
|
|
|
|
return Deny(QuestStateReasonCodes.UnknownPlayer);
|
|
}
|
|
|
|
private static QuestStateOperationResult Success(QuestStepState snapshot) =>
|
|
new(Success: true, ReasonCode: null, Snapshot: snapshot);
|
|
|
|
private static QuestStateOperationResult Deny(string reasonCode, QuestStepState? snapshot = null) =>
|
|
new(Success: false, ReasonCode: reasonCode, Snapshot: snapshot);
|
|
}
|