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);
}
}
}