feat(missions): emit progress events on story/finish and practice/finish

Story emits story_chapter_finish:<main|limited|event>:<story_id>.
Practice emits practice_win:<difficulty>:<enemy_class_id> on win only.

Practice catalog rows use opponent NAMES (e.g. practice_win:elite:arisa)
not numeric class_ids, so captured catalog rows won't match yet. The
infrastructure is in place; bridging numeric→name is a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-05-27 10:51:05 -04:00
parent 5693ec0302
commit 8e35501954
2 changed files with 52 additions and 6 deletions

View File

@@ -7,6 +7,7 @@ using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests.Common;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests.Practice;
using SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Practice;
using SVSim.EmulatedEntrypoint.Services;
namespace SVSim.EmulatedEntrypoint.Controllers;
@@ -14,11 +15,16 @@ public class PracticeController : SVSimController
{
private readonly IDeckRepository _deckRepository;
private readonly IGlobalsRepository _globalsRepository;
private readonly IMissionProgressService _missionProgress;
public PracticeController(IDeckRepository deckRepository, IGlobalsRepository globalsRepository)
public PracticeController(
IDeckRepository deckRepository,
IGlobalsRepository globalsRepository,
IMissionProgressService missionProgress)
{
_deckRepository = deckRepository;
_globalsRepository = globalsRepository;
_missionProgress = missionProgress;
}
/// <summary>
@@ -83,15 +89,29 @@ public class PracticeController : SVSimController
/// XP / no rewards. Class XP bookkeeping is deferred until a per-class XP store exists.
/// </summary>
[HttpPost("finish")]
public Task<PracticeFinishResponse> Finish(PracticeFinishRequest request)
public async Task<PracticeFinishResponse> Finish(PracticeFinishRequest request)
{
return Task.FromResult(new PracticeFinishResponse
// Mission/achievement progress hook. Catalog rows for practice_win achievements use
// opponent NAMES (e.g. "practice_win:elite:arisa") — we only have numeric class_id /
// difficulty here, so we emit numeric forms. Bridging numeric→name to match captured
// catalog rows is a follow-up; the infrastructure is in place.
if (request.IsWin == 1 && TryGetViewerId(out long viewerId))
{
await _missionProgress.RecordEventAsync(viewerId, new[]
{
"practice_win",
$"practice_win:{request.Difficulty}",
$"practice_win:{request.Difficulty}:{request.EnemyClassId}",
});
}
return new PracticeFinishResponse
{
GetClassExperience = 0,
ClassExperience = 0,
ClassLevel = 1,
AchievedInfo = new Dictionary<string, object>(),
RewardList = new List<Models.Dtos.Common.Reward>()
});
};
}
}