125 lines
4.4 KiB
C#
125 lines
4.4 KiB
C#
using NeonSprawl.Server.Game.PositionState;
|
|
|
|
namespace NeonSprawl.Server.Game.Mastery;
|
|
|
|
/// <summary>
|
|
/// Maps <c>GET</c>/<c>POST /game/players/{{id}}/perk-state</c> — read snapshot and branch selection (NEO-48).
|
|
/// </summary>
|
|
public static class PerkStateApi
|
|
{
|
|
public static WebApplication MapPerkStateApi(this WebApplication app)
|
|
{
|
|
app.MapGet(
|
|
"/game/players/{id}/perk-state",
|
|
(string id, IPositionStateStore positions, IPlayerPerkStateStore perkStore) =>
|
|
{
|
|
var trimmedId = id.Trim();
|
|
if (trimmedId.Length == 0 || !positions.TryGetPosition(trimmedId, out _))
|
|
{
|
|
return Results.NotFound();
|
|
}
|
|
|
|
return Results.Json(BuildSnapshot(trimmedId, perkStore));
|
|
});
|
|
|
|
app.MapPost(
|
|
"/game/players/{id}/perk-state",
|
|
(string id, PerkBranchSelectRequest? body, IPositionStateStore positions, PerkUnlockEngine perkUnlockEngine) =>
|
|
{
|
|
if (body is null || body.SchemaVersion != PerkBranchSelectRequest.CurrentSchemaVersion)
|
|
{
|
|
return Results.BadRequest();
|
|
}
|
|
|
|
var trimmedId = id.Trim();
|
|
if (trimmedId.Length == 0 || !positions.TryGetPosition(trimmedId, out _))
|
|
{
|
|
return Results.NotFound();
|
|
}
|
|
|
|
var outcome = perkUnlockEngine.TrySelectBranch(
|
|
trimmedId,
|
|
body.SkillId,
|
|
body.TierIndex,
|
|
body.BranchId);
|
|
|
|
var perkState = BuildSnapshot(trimmedId, outcome.Snapshot);
|
|
if (!outcome.Success)
|
|
{
|
|
return Results.Json(
|
|
new PerkBranchSelectResponse
|
|
{
|
|
Selected = false,
|
|
ReasonCode = outcome.ReasonCode,
|
|
PerkState = perkState,
|
|
UnlockedEvents = [],
|
|
});
|
|
}
|
|
|
|
return Results.Json(
|
|
new PerkBranchSelectResponse
|
|
{
|
|
Selected = true,
|
|
PerkState = perkState,
|
|
UnlockedEvents = MapEvents(outcome.Events),
|
|
});
|
|
});
|
|
|
|
return app;
|
|
}
|
|
|
|
internal static PerkStateSnapshotResponse BuildSnapshot(string playerId, IPlayerPerkStateStore store) =>
|
|
BuildSnapshot(playerId, store.GetSnapshot(playerId));
|
|
|
|
internal static PerkStateSnapshotResponse BuildSnapshot(string playerId, PerkStateSnapshot snapshot)
|
|
{
|
|
var branchPicks = new List<PerkBranchPickTrackJson>(snapshot.BranchPicksBySkillId.Count);
|
|
foreach (var (skillId, tiers) in snapshot.BranchPicksBySkillId.OrderBy(static kv => kv.Key, StringComparer.Ordinal))
|
|
{
|
|
var picks = tiers
|
|
.OrderBy(static kv => kv.Key)
|
|
.Select(static kv => new PerkBranchPickRowJson { TierIndex = kv.Key, BranchId = kv.Value })
|
|
.ToList();
|
|
branchPicks.Add(new PerkBranchPickTrackJson { SkillId = skillId, Picks = picks });
|
|
}
|
|
|
|
var unlocked = snapshot.UnlockedPerkIds.OrderBy(static id => id, StringComparer.Ordinal).ToList();
|
|
|
|
return new PerkStateSnapshotResponse
|
|
{
|
|
PlayerId = playerId,
|
|
BranchPicks = branchPicks,
|
|
UnlockedPerkIds = unlocked,
|
|
};
|
|
}
|
|
|
|
private static List<PerkUnlockedEventJson> MapEvents(IReadOnlyList<PerkUnlockEvent> events)
|
|
{
|
|
if (events.Count == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var mapped = new List<PerkUnlockedEventJson>(events.Count);
|
|
foreach (var e in events)
|
|
{
|
|
mapped.Add(
|
|
new PerkUnlockedEventJson
|
|
{
|
|
PerkId = e.PerkId,
|
|
SkillId = e.SkillId,
|
|
TierIndex = e.TierIndex,
|
|
BranchId = e.BranchId,
|
|
Source = e.Source switch
|
|
{
|
|
PerkUnlockSource.BranchPick => "branch_pick",
|
|
PerkUnlockSource.LevelUp => "level_up",
|
|
_ => throw new InvalidOperationException($"Unexpected perk unlock source: {e.Source}"),
|
|
},
|
|
});
|
|
}
|
|
|
|
return mapped;
|
|
}
|
|
}
|