using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using DorkNet.Server.Auth; using DorkNet.Server.Data; using DorkNet.Server.Data.Entities; namespace DorkNet.Server.Controllers.API.PlayerElo; /// /// api.rec.net/api/PlayerElo/* — match-result Elo deltas. The 2020 /// client posts after each ranked match (Royale, Paintball, Soccer) /// with a delta the server applies to the per-player Elo row. /// /// Wire request matches RecNet.Elo.ReportPlayerElo: /// {GameMode(int), EloDelta(int), Won(bool)} /// (we tolerate a few synonym fields). Response: the new Elo as a /// JSON primitive int. /// [ApiController] public class PlayerEloController(DorkNetDbContext db) : ControllerBase { private long Me => this.RequireCurrentPlayerId(); public sealed class ReportEloRequest { public int GameMode { get; set; } public int EloDelta { get; set; } public bool Won { get; set; } } [HttpPost("api/PlayerElo/v1/reportPlayerElo")] [Authorize] public async Task ReportPlayerElo([FromBody] ReportEloRequest req) { var pid = Me; var row = await db.PlayerElo.FirstOrDefaultAsync(p => p.PlayerId == pid && p.GameMode == req.GameMode); if (row is null) { row = new PlayerEloEntity { PlayerId = pid, GameMode = req.GameMode, }; db.PlayerElo.Add(row); } // Clamp Elo to a reasonable range to avoid overflow exploits. row.Elo = Math.Clamp(row.Elo + req.EloDelta, 0, 5000); row.MatchesPlayed += 1; if (req.Won) row.Wins += 1; else row.Losses += 1; row.UpdatedAt = DateTime.UtcNow; await db.SaveChangesAsync(); return Ok(row.Elo); } [HttpGet("api/PlayerElo/v1/get")] public async Task Get( [FromQuery] long? playerId, [FromQuery] int gameMode = 0) { var pid = playerId ?? this.CurrentPlayerId() ?? 0; if (pid == 0) return Ok(new { Elo = 1000, Wins = 0, Losses = 0 }); var row = await db.PlayerElo.FirstOrDefaultAsync(p => p.PlayerId == pid && p.GameMode == gameMode); return Ok(row is null ? new { Elo = 1000, Wins = 0, Losses = 0 } : new { row.Elo, row.Wins, row.Losses }); } }