using System.Collections.Generic; using System.Globalization; using UnityEngine; namespace TKS { /// /// 시나리오 이벤트 표(2026-11-12 PD 「스테이지 선택 때 전장 설명 · 보스 등장 포커스 + 대사 + 등장 연출」 · 개발 요청서 C1·C2). /// SOT = 조직 레포 `공유/기획팀_산출물/삼국지슬링샷/tables/` → 사본 `Assets/Resources/Scenario/*.csv`(utf-8-sig · 고친 뒤 기획 tables 도 같이). /// stage_story.csv = n_chapter,n_stage,s_desc_ko,Memo · boss_intro.csv = n_chapter,n_stage,n_order,s_boss_id,s_title_ko,s_line_ko,Memo · /// boss_intro_config.csv = s_key,f_value,Memo — 값은 전부 표에서 읽는다(C45 · 코드에 값 없음). /// public static class ScenarioData { public const string Root = "Scenario/"; public class BossLine { public int chapter; public int stage; public int order; public string id; public string title; public string line; } static bool loaded; static readonly Dictionary desc = new Dictionary(); static readonly List bosses = new List(); static readonly Dictionary cfg = new Dictionary(); static readonly HashSet missingReported = new HashSet(); /// 읽기 오류(형식 · 빠진 칸 · 없는 키) — 데이터 검사 테스트가 0 인지 본다. public static readonly List Errors = new List(); public static void Reload() { loaded = false; Load(); } static void Load() { if (loaded) return; loaded = true; desc.Clear(); bosses.Clear(); cfg.Clear(); Errors.Clear(); missingReported.Clear(); foreach (var r in Rows("stage_story")) { int c = Int(r, "n_chapter", "stage_story"), s = Int(r, "n_stage", "stage_story"); desc[Key(c, s)] = Get(r, "s_desc_ko"); } foreach (var r in Rows("boss_intro")) { bosses.Add(new BossLine { chapter = Int(r, "n_chapter", "boss_intro"), stage = Int(r, "n_stage", "boss_intro"), order = Int(r, "n_order", "boss_intro"), id = Get(r, "s_boss_id"), title = Get(r, "s_title_ko"), line = Get(r, "s_line_ko"), }); } bosses.Sort((a, b) => a.chapter != b.chapter ? a.chapter.CompareTo(b.chapter) : a.stage != b.stage ? a.stage.CompareTo(b.stage) : a.order.CompareTo(b.order)); foreach (var r in Rows("boss_intro_config")) { float v; if (float.TryParse(Get(r, "f_value"), NumberStyles.Float, CultureInfo.InvariantCulture, out v)) cfg[Get(r, "s_key")] = v; else Errors.Add("boss_intro_config.csv 행 " + Get(r, "#") + ": f_value 숫자 아님"); } foreach (var e in Errors) Debug.LogError("[TKS] 시나리오 표 " + e); } static List> Rows(string file) { var ta = Resources.Load(Root + file); if (ta == null) { Errors.Add("Resources/" + Root + file + ".csv 없음"); return new List>(); } return StageTable.ReadCsv(ta.text, file + ".csv", Errors); } static long Key(int chapter, int stage) { return chapter * 1000L + stage; } static string Get(Dictionary r, string k) { string v; return r.TryGetValue(k, out v) ? v : ""; } static int Int(Dictionary r, string k, string file) { int v; if (int.TryParse(Get(r, k), NumberStyles.Integer, CultureInfo.InvariantCulture, out v)) return v; Errors.Add(file + ".csv 행 " + Get(r, "#") + ": " + k + " 정수 아님"); return 0; } /// 스테이지 선택 팝업의 전장 설명(없으면 ""). public static string Desc(int chapter, int stage) { Load(); string s; return desc.TryGetValue(Key(chapter, stage), out s) ? s : ""; } /// 그 스테이지의 보스 등장 행(n_order 순 · 없으면 빈 목록). public static List Bosses(int chapter, int stage) { Load(); return bosses.FindAll(b => b.chapter == chapter && b.stage == stage); } public static IList AllBosses { get { Load(); return bosses; } } public static IEnumerable> AllDesc { get { Load(); return desc; } } /// 연출 값(boss_intro_config.csv) — 키가 없으면 콘솔 오류 1회 + 0. public static float Cfg(string key) { Load(); float v; if (cfg.TryGetValue(key, out v)) return v; if (missingReported.Add(key)) { Errors.Add("boss_intro_config.csv 에 키 없음: " + key); Debug.LogError("[TKS] boss_intro_config.csv 에 키 없음: " + key); } return 0f; } public static bool HasCfg(string key) { Load(); return cfg.ContainsKey(key); } } }