neon-sprawl/server/NeonSprawl.Server/Game/Skills/SkillProgressionSnapshotApi.cs

103 lines
3.9 KiB
C#

using NeonSprawl.Server.Game.Mastery;
using NeonSprawl.Server.Game.PositionState;
namespace NeonSprawl.Server.Game.Skills;
/// <summary>
/// Maps <c>GET</c>/<c>POST /game/players/{{id}}/skill-progression</c> — read snapshot (NEO-37) and XP grant apply (NEO-38).
/// </summary>
public static class SkillProgressionSnapshotApi
{
/// <seealso cref="SkillProgressionGrantResponse.ReasonCode" />
public const string ReasonUnknownSkill = "unknown_skill";
public const string ReasonSourceKindNotAllowed = "source_kind_not_allowed";
public const string ReasonInvalidAmount = "invalid_amount";
public static WebApplication MapSkillProgressionSnapshotApi(this WebApplication app)
{
app.MapGet(
"/game/players/{id}/skill-progression",
(string id, IPositionStateStore positions, ISkillDefinitionRegistry registry, IPlayerSkillProgressionStore xpStore, ISkillLevelCurve levelCurve) =>
{
var trimmedId = id.Trim();
if (trimmedId.Length == 0 || !positions.TryGetPosition(trimmedId, out _))
{
return Results.NotFound();
}
return Results.Json(BuildSnapshot(trimmedId, registry, xpStore, levelCurve));
});
app.MapPost(
"/game/players/{id}/skill-progression",
(string id, SkillProgressionGrantRequest? body, IPositionStateStore positions,
ISkillDefinitionRegistry registry, IPlayerSkillProgressionStore xpStore, ISkillLevelCurve levelCurve,
PerkUnlockEngine perkUnlockEngine) =>
{
if (body is null || body.SchemaVersion != SkillProgressionGrantRequest.CurrentSchemaVersion)
{
return Results.BadRequest();
}
var trimmedId = id.Trim();
if (trimmedId.Length == 0 || !positions.TryGetPosition(trimmedId, out _))
{
return Results.NotFound();
}
var outcome = SkillProgressionGrantOperations.TryApplyGrant(
trimmedId,
body.SkillId,
body.Amount,
body.SourceKind,
registry,
xpStore,
levelCurve,
perkUnlockEngine);
return outcome.Kind switch
{
SkillProgressionGrantApplyKind.Denied => Results.Json(outcome.Response!),
SkillProgressionGrantApplyKind.ProgressionStoreMissing => Results.NotFound(),
SkillProgressionGrantApplyKind.Granted => Results.Json(outcome.Response!),
_ => throw new InvalidOperationException($"Unexpected grant outcome: {outcome.Kind}"),
};
});
return app;
}
internal static SkillProgressionSnapshotResponse BuildSnapshot(
string playerId,
ISkillDefinitionRegistry registry,
IPlayerSkillProgressionStore store,
ISkillLevelCurve levelCurve)
{
var xpBySkill = store.GetXpTotals(playerId);
var defs = registry.GetDefinitionsInIdOrder();
var skills = new List<SkillProgressionRowJson>(defs.Count);
foreach (var d in defs)
{
xpBySkill.TryGetValue(d.Id, out var xp);
var level = levelCurve.LevelFromTotalXp(xp);
skills.Add(
new SkillProgressionRowJson
{
Id = d.Id,
Xp = xp,
Level = level,
});
}
skills.Sort(static (a, b) => string.CompareOrdinal(a.Id, b.Id));
return new SkillProgressionSnapshotResponse
{
PlayerId = playerId,
Skills = skills,
SchemaVersion = SkillProgressionSnapshotResponse.CurrentSchemaVersion,
};
}
}