neon-sprawl/server/NeonSprawl.Server/Game/AbilityInput/AbilityCastApi.cs

83 lines
3.0 KiB
C#

using NeonSprawl.Server.Game.PositionState;
namespace NeonSprawl.Server.Game.AbilityInput;
/// <summary>Maps prototype ability cast POST (NEO-31).</summary>
public static class AbilityCastApi
{
public const string ReasonSlotOutOfBounds = "slot_out_of_bounds";
public const string ReasonSlotUnbound = "slot_unbound";
public const string ReasonLoadoutMismatch = "loadout_mismatch";
public const string ReasonUnknownAbility = "unknown_ability";
public static WebApplication MapAbilityCastApi(this WebApplication app)
{
app.MapPost(
"/game/players/{id}/ability-cast",
(string id, AbilityCastRequest? body, IPositionStateStore positions, IPlayerHotbarLoadoutStore store) =>
{
if (body is null || body.SchemaVersion != AbilityCastRequest.CurrentSchemaVersion)
{
return Results.BadRequest();
}
if (!positions.TryGetPosition(id, out _))
{
return Results.NotFound();
}
if (!store.TryGetBindings(id, out var bindings))
{
return Results.NotFound();
}
if (body.SlotIndex < 0 || body.SlotIndex >= HotbarLoadoutResponse.SlotCountV1)
{
return Results.Json(
new AbilityCastResponse
{
Accepted = false,
ReasonCode = ReasonSlotOutOfBounds,
});
}
if (!bindings.TryGetValue(body.SlotIndex, out var boundAbility) ||
string.IsNullOrWhiteSpace(boundAbility))
{
return Results.Json(
new AbilityCastResponse
{
Accepted = false,
ReasonCode = ReasonSlotUnbound,
});
}
if (string.IsNullOrWhiteSpace(body.AbilityId) ||
!PrototypeAbilityRegistry.TryNormalizeKnown(body.AbilityId, out var normalizedRequest))
{
return Results.Json(
new AbilityCastResponse
{
Accepted = false,
ReasonCode = ReasonUnknownAbility,
});
}
if (!string.Equals(normalizedRequest, boundAbility, StringComparison.Ordinal))
{
return Results.Json(
new AbilityCastResponse
{
Accepted = false,
ReasonCode = ReasonLoadoutMismatch,
});
}
// TargetId echoed for NEO-28 / E5.M1; server does not validate against lock store in NEO-31.
return Results.Json(new AbilityCastResponse { Accepted = true });
});
return app;
}
}