using SVSim.Database.Common; namespace SVSim.Database.Models; /// /// One row per AI opponent shown on the practice (solo-play) opponent select screen. /// Populated from seeds/practice-opponents.json by SVSim.Bootstrap.PracticeOpponentImporter. /// /// The (, ) pair MUST exist in the client's /// baked-in master CSV `ai/practice_ai_setting`; if it doesn't, the client's /// PracticeAISettingDataSet.GetSettingData throws InvalidOperationException and the /// difficulty-select dialog crashes BEFORE /practice/start is sent. Prod's catalog is /// the safe source of truth — we can't see the CSV directly. /// public class PracticeOpponentEntry : BaseEntity { /// Practice slot id (Id = practice_id from the wire; also unique). public int PracticeId { get => Id; set => Id = value; } /// Text-table key resolved client-side via Data.Master.GetPracticeText. public string TextId { get; set; } = string.Empty; /// Class (leader) id the AI plays. public int ClassId { get; set; } /// Portrait / character id (leader art). public int CharaId { get; set; } /// Title-degree id shown next to the AI name. -1 when unset. public int DegreeId { get; set; } /// AI deck-strength tier; key into the client's practice_ai_setting CSV. public int AiDeckLevel { get; set; } /// AI decision-making tier. public int AiLogicLevel { get; set; } /// Starting HP for the AI side (typically 20; 10 for the easiest "tutorial" rows). public int AiMaxLife { get; set; } /// 3D battlefield asset id (string on the wire; client int.TryParse's it). public string Battle3dFieldId { get; set; } = "1"; /// true => entry shown but disabled with a maintenance suffix. public bool IsMaintenance { get; set; } /// true => entry is a special event-tied "campaign" practice. public bool IsCampaignPractice { get; set; } }