Files
SVSimServer/SVSim.EmulatedEntrypoint/Matching/BotRoster.cs
gamer147 45c4461515 fix(rank-battle): use real rm_ai_setting.csv ai_id values in BotRoster
Phase 3 shipped placeholder ai_id values 4001..4008, which the client's
RankMatchAISettingList.GetSettingData() couldn't resolve — the lookup
is .First() against the rm_ai_setting.csv master table and throws
InvalidOperationException ("Sequence contains no matching element")
when the id isn't present. Surfaced on live smoke as a Unity error
during battle load:

  Wizard.RankMatchAISettingDataSet.GetSettingData (System.Int32 enemyAiId)
  BattleUI+<WaitForSetUp>d__9.MoveNext ()

Replaced with the series-1 enemy_ai_id per class from
data_dumps/client-assets/rm_ai_setting.csv:
  1111=Forest, 1121=Sword, 1131=Rune, 1141=Dragon,
  1151=Shadow, 1161=Blood, 1171=Haven, 1181=Portal

Practice mode's AI catalog (practice_ai_setting.csv) uses a different
schema keyed by (class_id, difficulty) with no enemy_ai_id field, so
practice ids aren't reusable here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 09:41:38 -04:00

59 lines
3.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using SVSim.BattleNode.Bridge;
namespace SVSim.EmulatedEntrypoint.Matching;
/// <summary>
/// Phase 3 hardcoded fixture: one stub bot per class (1..8). <c>ai_id</c> values are
/// the "series 1" tier from the client's <c>rm_ai_setting.csv</c> master table
/// (<c>1111</c>=Forest, <c>1121</c>=Sword, … <c>1181</c>=Portal). The client's
/// <c>RankMatchAISettingList.GetSettingData(aiId)</c> calls <c>.First(...)</c> against
/// this table and throws <c>InvalidOperationException</c> if the id is absent — so the
/// ids MUST match. See <c>data_dumps/client-assets/rm_ai_setting.csv</c> for the full
/// catalog (32 rows: classes 1-8 × tiers 1-2 × deck variants 1-2).
/// </summary>
public sealed class BotRoster : IBotRoster
{
// Cosmetic ids (sleeve / emblem / degree / field) intentionally use safe
// default values that match the master tables shipped in the project.
// The client-side AI catalog reads ai_id but renders cosmetics from the
// sleeveId/emblemId/etc returned here.
private static readonly IReadOnlyList<AIBotProfile> Roster = new[]
{
new AIBotProfile(AiId: 1111, CountryCode: "NONE", UserName: "Forestcraft AI",
SleeveId: 1001, EmblemId: 1, DegreeId: 1, FieldId: 1, IsOfficial: 0,
ClassId: 1, CharaId: 1, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
new AIBotProfile(AiId: 1121, CountryCode: "NONE", UserName: "Swordcraft AI",
SleeveId: 1001, EmblemId: 1, DegreeId: 1, FieldId: 1, IsOfficial: 0,
ClassId: 2, CharaId: 2, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
new AIBotProfile(AiId: 1131, CountryCode: "NONE", UserName: "Runecraft AI",
SleeveId: 1001, EmblemId: 1, DegreeId: 1, FieldId: 1, IsOfficial: 0,
ClassId: 3, CharaId: 3, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
new AIBotProfile(AiId: 1141, CountryCode: "NONE", UserName: "Dragoncraft AI",
SleeveId: 1001, EmblemId: 1, DegreeId: 1, FieldId: 1, IsOfficial: 0,
ClassId: 4, CharaId: 4, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
new AIBotProfile(AiId: 1151, CountryCode: "NONE", UserName: "Shadowcraft AI",
SleeveId: 1001, EmblemId: 1, DegreeId: 1, FieldId: 1, IsOfficial: 0,
ClassId: 5, CharaId: 5, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
new AIBotProfile(AiId: 1161, CountryCode: "NONE", UserName: "Bloodcraft AI",
SleeveId: 1001, EmblemId: 1, DegreeId: 1, FieldId: 1, IsOfficial: 0,
ClassId: 6, CharaId: 6, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
new AIBotProfile(AiId: 1171, CountryCode: "NONE", UserName: "Havencraft AI",
SleeveId: 1001, EmblemId: 1, DegreeId: 1, FieldId: 1, IsOfficial: 0,
ClassId: 7, CharaId: 7, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
new AIBotProfile(AiId: 1181, CountryCode: "NONE", UserName: "Portalcraft AI",
SleeveId: 1001, EmblemId: 1, DegreeId: 1, FieldId: 1, IsOfficial: 0,
ClassId: 8, CharaId: 8, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
};
public AIBotProfile Pick(MatchContext selfCtx)
{
// Deterministic: hash the ctx and pick from the roster. Same ctx →
// same bot so a mid-flight retry of /ai_<fmt>/start returns the same
// opponent (no fresh roster pick on each call).
var hash = StringComparer.Ordinal.GetHashCode(selfCtx.UserName)
^ StringComparer.Ordinal.GetHashCode(selfCtx.ClassId);
var index = (int)((uint)hash % Roster.Count);
return Roster[index];
}
}