using NeonSprawl.Server.Game.PositionState; using NeonSprawl.Server.Game.World; namespace NeonSprawl.Server.Game.Interaction; /// Maps POST /game/players/{{id}}/interact (NS-18). public static class InteractionApi { public const string ReasonOutOfRange = "out_of_range"; public const string ReasonUnknownInteractable = "unknown_interactable"; public static WebApplication MapInteractionApi(this WebApplication app) { app.MapPost( "/game/players/{id}/interact", (string id, InteractionRequest? body, IPositionStateStore store) => { if (body is null || body.SchemaVersion != InteractionRequest.CurrentSchemaVersion) { return Results.BadRequest(); } var rawId = body.InteractableId; if (rawId is null) { return Results.BadRequest(); } var interactableKey = rawId.Trim(); if (interactableKey.Length == 0) { return Results.BadRequest(); } var lookupKey = interactableKey.ToLowerInvariant(); var playerKey = id.Trim(); if (!store.TryGetPosition(playerKey, out var snap)) { return Results.NotFound(); } if (!PrototypeInteractableRegistry.TryGet(lookupKey, out var entry)) { return Results.Json( new InteractionResponse { SchemaVersion = InteractionResponse.CurrentSchemaVersion, Allowed = false, ReasonCode = ReasonUnknownInteractable, }); } if (!HorizontalReach.IsWithinHorizontalRadius( snap.X, snap.Z, entry.X, entry.Z, entry.InteractionRadius)) { return Results.Json( new InteractionResponse { SchemaVersion = InteractionResponse.CurrentSchemaVersion, Allowed = false, ReasonCode = ReasonOutOfRange, }); } return Results.Json( new InteractionResponse { SchemaVersion = InteractionResponse.CurrentSchemaVersion, Allowed = true, InteractableId = lookupKey, Payload = new InteractionPlaceholderPayload(), }); }); return app; } }