using System.Text.Json.Serialization; using NeonSprawl.Server.Game.PositionState; namespace NeonSprawl.Server.Game.Targeting; /// JSON body for GET /game/players/{{id}}/target and nested targetState on select (NEO-23). public sealed class PlayerTargetStateResponse { public const int CurrentSchemaVersion = 1; [JsonPropertyName("schemaVersion")] public int SchemaVersion { get; init; } = CurrentSchemaVersion; [JsonPropertyName("playerId")] public required string PlayerId { get; init; } /// Lowercase registry id when locked; JSON null when no lock (key always serialized). [JsonPropertyName("lockedTargetId")] [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public string? LockedTargetId { get; init; } /// One of string constants. [JsonPropertyName("validity")] public required string Validity { get; init; } [JsonPropertyName("sequence")] public int Sequence { get; init; } } /// POST body for POST /game/players/{{id}}/target/select (NEO-23). /// /// Omit targetId or send JSON null to clear the lock. Whitespace-only after trim → HTTP 400. /// public sealed class TargetSelectRequest { public const int CurrentSchemaVersion = 1; [JsonPropertyName("schemaVersion")] public int SchemaVersion { get; init; } /// Registry key after trim + case-insensitive lookup; null or omitted ⇒ clear intent. [JsonPropertyName("targetId")] public string? TargetId { get; init; } /// /// Optional client-reported current position. When present the server uses these coordinates /// for the range check instead of the stored PositionState snapshot, eliminating the /// race between move-stream and target/select POSTs (NEO-24 follow-up #5). Purely /// advisory: it does not write to the position store (move-stream remains the /// only write path). Real combat (E5.M1) will bound the hint against the stored snap / movement /// budget so this cannot be used to "teleport" the radius check — prototype does not yet. /// [JsonPropertyName("positionHint")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public PositionVector? PositionHint { get; init; } } /// POST response for target selection (NEO-23). public sealed class TargetSelectResponse { public const int CurrentSchemaVersion = 1; [JsonPropertyName("schemaVersion")] public int SchemaVersion { get; init; } = CurrentSchemaVersion; [JsonPropertyName("selectionApplied")] public bool SelectionApplied { get; init; } [JsonPropertyName("targetState")] public required PlayerTargetStateResponse TargetState { get; init; } /// Required when is false; omitted on success. [JsonPropertyName("reasonCode")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? ReasonCode { get; init; } }