feat(mission): add RankTier + Class/Ranked/Free/Challenge/ClassLevel/Rank builders
MissionEventKeys grows six new nested static classes covering the wire-up
gap for the counter families that Practice/Story left uncovered. Each mirrors
the established (Practice/Story) shape: named accessors for individual keys,
`{Family}.WinAll(...)` / `AchievedAll(...)` etc. as convenience arrays that
callers pass straight to RecordEventAsync.
RankTier lives at SVSim.Database/Enums/RankTier.cs since the concept (rank_id
→ tier bucket) is data-shaped, not emit-specific, and RankProgressService
will need it internally for tier-crossing detection. Ranked/ClassLevel/Rank
builders in MissionEventKeys compose Class.Name and RankTier.Name.
12 new registry tests cover the family shapes plus tier boundary edges
(rank 4/5, 24/25, 25/26).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
using SVSim.Database.Enums;
|
||||
|
||||
namespace SVSim.Database.Services;
|
||||
|
||||
/// <summary>
|
||||
@@ -109,6 +111,115 @@ public static class MissionEventKeys
|
||||
};
|
||||
}
|
||||
|
||||
// ---- Class-name mapping (shared by Ranked and ClassLevel families) ----
|
||||
|
||||
public static class Class
|
||||
{
|
||||
/// <summary>
|
||||
/// Wire <c>class_id</c> 1-8 → catalog-facing craft name. Ordering matches the
|
||||
/// <see cref="SVSim.BattleNode.Bridge.CardClass"/> enum (1=Forestcraft ... 8=Portalcraft).
|
||||
/// Duplicated as a string switch rather than reused from the enum's <c>ToString()</c>
|
||||
/// because the catalog uses lowercase — an accidental case change would silently break
|
||||
/// counter alignment.
|
||||
/// </summary>
|
||||
public static string? Name(int classId) => classId switch
|
||||
{
|
||||
1 => "forestcraft",
|
||||
2 => "swordcraft",
|
||||
3 => "runecraft",
|
||||
4 => "dragoncraft",
|
||||
5 => "shadowcraft",
|
||||
6 => "bloodcraft",
|
||||
7 => "havencraft",
|
||||
8 => "portalcraft",
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
// ---- Ranked / Free / Challenge hierarchical builders ----
|
||||
|
||||
public static class Ranked
|
||||
{
|
||||
/// <summary>
|
||||
/// Rank-battle win: emits <c>ranked_win</c>, the class-qualified variant, and the two
|
||||
/// aggregate keys (<c>ranked_or_arena_win</c>, <c>daily_match_win</c>) that every
|
||||
/// ranked/arena win advances.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> WinAll(int classId)
|
||||
{
|
||||
var list = new List<string>(4)
|
||||
{
|
||||
RankedWin,
|
||||
RankedOrArenaWin,
|
||||
DailyMatchWin,
|
||||
};
|
||||
if (Class.Name(classId) is { } name)
|
||||
list.Add($"{RankedWin}:{name}");
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Free
|
||||
{
|
||||
/// <summary>
|
||||
/// Unranked/free-battle win: only the two aggregates. There is no <c>free_win</c>
|
||||
/// catalog prefix — free battles just count toward "any match" mission lines.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> WinAll() => new[] { RankedOrArenaWin, DailyMatchWin };
|
||||
}
|
||||
|
||||
public static class Challenge
|
||||
{
|
||||
/// <summary>Any TK2 match finish (win OR loss). Advances <c>challenge_play</c>.</summary>
|
||||
public static IReadOnlyList<string> MatchPlayAll() => new[] { ChallengePlay };
|
||||
|
||||
/// <summary>
|
||||
/// TK2 match win: fires both <c>challenge_win</c> and <c>challenge_play</c> (a win is
|
||||
/// also a play) plus the two aggregates. Keeping <c>challenge_play</c> in this list
|
||||
/// makes the invariant "always increment play, additionally increment win" obvious.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> MatchWinAll() => new[]
|
||||
{
|
||||
ChallengeWin, ChallengePlay, RankedOrArenaWin, DailyMatchWin,
|
||||
};
|
||||
|
||||
/// <summary>TK2 run ended with all 5 wins — advances <c>challenge_full_clear</c>.</summary>
|
||||
public static IReadOnlyList<string> FullClearAll() => new[] { ChallengeFullClear };
|
||||
}
|
||||
|
||||
// ---- ClassLevel / Rank hierarchical builders ----
|
||||
|
||||
public static class ClassLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Class went up at least one level in this battle. Callsite must gate on
|
||||
/// <c>BattleXpGrantResult.LeveledUp</c> — this method doesn't check.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> UpAll(int classId)
|
||||
{
|
||||
var list = new List<string>(2) { ClassLevelUp };
|
||||
if (Class.Name(classId) is { } name)
|
||||
list.Add($"{ClassLevelUp}:{name}");
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Rank
|
||||
{
|
||||
/// <summary>
|
||||
/// Viewer's rank crossed into a new tier. Callsite must gate on
|
||||
/// <c>RankProgressResult.TierAdvanced</c>. Tier name comes from
|
||||
/// <see cref="RankTier.Name(int)"/>.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> AchievedAll(int rankId)
|
||||
{
|
||||
var list = new List<string>(2) { RankAchieved };
|
||||
if (RankTier.Name(rankId) is { } name)
|
||||
list.Add($"{RankAchieved}:{name}");
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Seed-import validation ----
|
||||
|
||||
private static readonly IReadOnlySet<string> _registeredPrefixes = new HashSet<string>
|
||||
|
||||
Reference in New Issue
Block a user