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,