From 42b0fd3d6e8fb4501d2aa727bd3c3978a4f0622a Mon Sep 17 00:00:00 2001 From: gamer147 Date: Sat, 4 Jul 2026 13:22:02 -0400 Subject: [PATCH] =?UTF-8?q?feat(mission):=20wire=20Tier=201=20emits=20?= =?UTF-8?q?=E2=80=94=20rank/free/challenge=20match=20events?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds IMissionProgressService to RankBattle, FreeBattle, ArenaTwoPickBattle, and ArenaTwoPick controllers. Each finish handler emits the appropriate MissionEventKeys builder result: - RankBattle.Finish (win) → Ranked.WinAll(classId) Includes AI-rank routes; on a private server AI wins count the same. - FreeBattle.Finish (win) → Free.WinAll() (aggregates only) - ArenaTwoPickBattle.Finish → Challenge.MatchPlayAll or MatchWinAll Always fires challenge_play; wins additionally fire challenge_win and the ranked/arena/daily aggregates. - ArenaTwoPick.Finish (5-win full clear) → Challenge.FullClearAll Gated on RunFinishOutcome.WasFullClear from the previous commit. This closes six of the eight remaining catalog wire-up gaps; class_level_up and rank_achieved follow in Tier 2 (need pre-state detection). Co-Authored-By: Claude Opus 4.7 --- .../Controllers/ArenaTwoPickBattleController.cs | 14 +++++++++++++- .../Controllers/ArenaTwoPickController.cs | 16 ++++++++++++++-- .../Controllers/FreeBattleController.cs | 11 +++++++++++ .../Controllers/RankBattleController.cs | 11 +++++++++++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/SVSim.EmulatedEntrypoint/Controllers/ArenaTwoPickBattleController.cs b/SVSim.EmulatedEntrypoint/Controllers/ArenaTwoPickBattleController.cs index 3db879dd..5f4dddab 100644 --- a/SVSim.EmulatedEntrypoint/Controllers/ArenaTwoPickBattleController.cs +++ b/SVSim.EmulatedEntrypoint/Controllers/ArenaTwoPickBattleController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using SVSim.BattleNode.Bridge; using SVSim.Database.Enums; +using SVSim.Database.Services; using SVSim.Database.Services.Friend; using SVSim.Database.Services.Replay; using SVSim.EmulatedEntrypoint.Extensions; @@ -20,6 +21,7 @@ public class ArenaTwoPickBattleController : SVSimController private readonly IBattleContextStore _battleContextStore; private readonly IBattleHistoryWriter _historyWriter; private readonly IPlayedTogetherWriter _playedTogetherWriter; + private readonly IMissionProgressService _missionProgress; public ArenaTwoPickBattleController( IArenaTwoPickService svc, @@ -27,7 +29,8 @@ public class ArenaTwoPickBattleController : SVSimController IMatchingResolver resolver, IBattleContextStore battleContextStore, IBattleHistoryWriter historyWriter, - IPlayedTogetherWriter playedTogetherWriter) + IPlayedTogetherWriter playedTogetherWriter, + IMissionProgressService missionProgress) { _svc = svc; _matchContextBuilder = matchContextBuilder; @@ -35,6 +38,7 @@ public class ArenaTwoPickBattleController : SVSimController _battleContextStore = battleContextStore; _historyWriter = historyWriter; _playedTogetherWriter = playedTogetherWriter; + _missionProgress = missionProgress; } [HttpPost("do_matching")] @@ -117,6 +121,14 @@ public class ArenaTwoPickBattleController : SVSimController } var result = await _svc.RecordBattleResultAsync(vid, isWin); + + // Mission counters — TK2 matches always advance challenge_play, wins additionally + // advance challenge_win + the ranked/arena/daily aggregates. + await _missionProgress.RecordEventAsync( + vid, + isWin ? MissionEventKeys.Challenge.MatchWinAll() : MissionEventKeys.Challenge.MatchPlayAll(), + ct: ct); + return Ok(new BattleFinishResponseDto { BattleResult = result.BattleResult, diff --git a/SVSim.EmulatedEntrypoint/Controllers/ArenaTwoPickController.cs b/SVSim.EmulatedEntrypoint/Controllers/ArenaTwoPickController.cs index 1a836526..8715da2f 100644 --- a/SVSim.EmulatedEntrypoint/Controllers/ArenaTwoPickController.cs +++ b/SVSim.EmulatedEntrypoint/Controllers/ArenaTwoPickController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using SVSim.Database.Services; using SVSim.EmulatedEntrypoint.Models.Dtos.Requests.ArenaTwoPick; using SVSim.EmulatedEntrypoint.Services; @@ -8,7 +9,13 @@ namespace SVSim.EmulatedEntrypoint.Controllers; public class ArenaTwoPickController : SVSimController { private readonly IArenaTwoPickService _svc; - public ArenaTwoPickController(IArenaTwoPickService svc) => _svc = svc; + private readonly IMissionProgressService _missionProgress; + + public ArenaTwoPickController(IArenaTwoPickService svc, IMissionProgressService missionProgress) + { + _svc = svc; + _missionProgress = missionProgress; + } [HttpPost("top")] public async Task Top([FromBody] TopRequest _) @@ -46,12 +53,17 @@ public class ArenaTwoPickController : SVSimController } [HttpPost("finish")] - public async Task Finish([FromBody] FinishRequest _) + public async Task Finish([FromBody] FinishRequest _, CancellationToken ct = default) { if (!TryGetViewerId(out var vid)) return Unauthorized(); return await GuardAsync(async () => { var outcome = await _svc.FinishAsync(vid); + if (outcome.WasFullClear) + { + await _missionProgress.RecordEventAsync( + vid, MissionEventKeys.Challenge.FullClearAll(), ct: ct); + } return outcome.Response; }); } diff --git a/SVSim.EmulatedEntrypoint/Controllers/FreeBattleController.cs b/SVSim.EmulatedEntrypoint/Controllers/FreeBattleController.cs index a290620a..2bc95d1d 100644 --- a/SVSim.EmulatedEntrypoint/Controllers/FreeBattleController.cs +++ b/SVSim.EmulatedEntrypoint/Controllers/FreeBattleController.cs @@ -4,6 +4,7 @@ using SVSim.BattleNode.Bridge; using SVSim.Database; using SVSim.Database.Enums; using SVSim.Database.Repositories.Viewer; +using SVSim.Database.Services; using SVSim.Database.Services.BattleXp; using SVSim.Database.Services.Friend; using SVSim.Database.Services.Replay; @@ -37,6 +38,7 @@ public sealed class FreeBattleController : ControllerBase private readonly IPlayedTogetherWriter _playedTogetherWriter; private readonly IViewerRepository _viewers; private readonly IBattleXpService _xp; + private readonly IMissionProgressService _missionProgress; private readonly SVSimDbContext _db; private readonly ILogger _log; @@ -48,6 +50,7 @@ public sealed class FreeBattleController : ControllerBase IPlayedTogetherWriter playedTogetherWriter, IViewerRepository viewers, IBattleXpService xp, + IMissionProgressService missionProgress, SVSimDbContext db, ILogger log) { @@ -58,6 +61,7 @@ public sealed class FreeBattleController : ControllerBase _playedTogetherWriter = playedTogetherWriter; _viewers = viewers; _xp = xp; + _missionProgress = missionProgress; _db = db; _log = log; } @@ -120,6 +124,13 @@ public sealed class FreeBattleController : ControllerBase level = xp.Level == 0 ? 1 : xp.Level; } + // Mission counters — unranked wins feed ranked_or_arena_win + daily_match_win. + if (isWin) + { + await _missionProgress.RecordEventAsync( + vid, MissionEventKeys.Free.WinAll(), ct: ct); + } + return Ok(new FreeBattleFinishResponseDto { BattleResult = req.BattleResult, diff --git a/SVSim.EmulatedEntrypoint/Controllers/RankBattleController.cs b/SVSim.EmulatedEntrypoint/Controllers/RankBattleController.cs index e7d1e109..d7231e31 100644 --- a/SVSim.EmulatedEntrypoint/Controllers/RankBattleController.cs +++ b/SVSim.EmulatedEntrypoint/Controllers/RankBattleController.cs @@ -5,6 +5,7 @@ using SVSim.BattleNode.Sessions; using SVSim.Database; using SVSim.Database.Enums; using SVSim.Database.Repositories.Viewer; +using SVSim.Database.Services; using SVSim.Database.Services.BattleXp; using SVSim.Database.Services.Friend; using SVSim.Database.Services.RankProgress; @@ -40,6 +41,7 @@ public sealed class RankBattleController : ControllerBase private readonly IViewerRepository _viewers; private readonly IBattleXpService _xp; private readonly IRankProgressService _rankProgress; + private readonly IMissionProgressService _missionProgress; private readonly SVSimDbContext _db; private readonly ILogger _log; @@ -54,6 +56,7 @@ public sealed class RankBattleController : ControllerBase IViewerRepository viewers, IBattleXpService xp, IRankProgressService rankProgress, + IMissionProgressService missionProgress, SVSimDbContext db, ILogger log) { @@ -67,6 +70,7 @@ public sealed class RankBattleController : ControllerBase _viewers = viewers; _xp = xp; _rankProgress = rankProgress; + _missionProgress = missionProgress; _db = db; _log = log; } @@ -161,6 +165,13 @@ public sealed class RankBattleController : ControllerBase level = xp.Level == 0 ? 1 : xp.Level; } + // Mission counters — AI-rank routes emit the same keys as human-rank on a private server. + if (isWin) + { + await _missionProgress.RecordEventAsync( + vid, MissionEventKeys.Ranked.WinAll(req.ClassId), ct: ct); + } + return Ok(new RankBattleFinishResponseDto { BattleResult = req.BattleResult,