using System; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; // 스토리(StoryLogic 26장 · StoryLocale) — 조건 충족 시 대화 장면 1회 (트리거 문자열은 원작 표 그대로 파싱) namespace VS.Sim { [Serializable] public class StoryLineRow { public string key; public int characterId; public string emotion; public string lineEvent; } [Serializable] public class StoryChapterRow { public string id; public string trigger; public string backgroundId; public bool useLiteUi; public List lines; } [Serializable] public class StoryLogicTable { public List chapters; } [Serializable] public class StoryLocaleLine { public string key; public string speakerName; public string text; } [Serializable] public class StoryCharName { public string id; public string name; } [Serializable] public class StoryLocaleTable { public List lines; public List characterNames; } public partial class Game { public StoryLogicTable Story; Dictionary storyText; static readonly Regex RxItem = new Regex(@"^ITEM_(\d+)_HOUSE_(\d+)_LEVEL_?\s*>=\s*(\d+)$", RegexOptions.IgnoreCase); static readonly Regex RxHouseItem = new Regex(@"^ITEM_HOUSE_(\d+)_LEVEL_?\s*>=\s*(\d+)$", RegexOptions.IgnoreCase); static readonly Regex RxUnlock = new Regex(@"^UNLOCK_ROOM_(\d+)$", RegexOptions.IgnoreCase); static readonly Regex RxFollower = new Regex(@"^FOLLOWERS?_?\s*>=\s*(\d+)$", RegexOptions.IgnoreCase); void InitStory() { try { if (!string.IsNullOrEmpty(T.StoryLogicJson)) Story = JsonUtility.FromJson(T.StoryLogicJson); storyText = new Dictionary(); if (!string.IsNullOrEmpty(T.StoryLocaleJson)) { var loc = JsonUtility.FromJson(T.StoryLocaleJson); foreach (var l in loc.lines) storyText[l.key] = l; } } catch (Exception e) { Say("story load failed " + e.Message); } } public StoryLocaleLine StoryLine(string key) => storyText != null && storyText.TryGetValue(key, out var l) ? l : null; /// 트리거 평가 — 아이템 번호·레벨은 화면 표기(1부터 · 레벨 = 저장값+1) 기준 (추정: 시각 단계 문턱 24·49 ↔ 표 25·50) public bool StoryTriggerMet(string trig) { if (string.IsNullOrEmpty(trig)) return false; trig = trig.Trim(); if (trig.Equals("START_GAME", StringComparison.OrdinalIgnoreCase)) return true; var m = RxItem.Match(trig); if (m.Success) { int item = int.Parse(m.Groups[1].Value) - 1, house = int.Parse(m.Groups[2].Value) - 1, lv = int.Parse(m.Groups[3].Value); return house == S.house && RoomLevel(item) + 1 >= lv || house < S.house; } m = RxHouseItem.Match(trig); if (m.Success) { // ItemHouseLevel: 지난 집이면 즉시 · 같은 집이면 11개 모두 구매 + 최솟값(표시 레벨) ≥ L (TryGetMinHouseDisplayItemLevel RVA 0x25C434C) int house = int.Parse(m.Groups[1].Value) - 1, lv = int.Parse(m.Groups[2].Value); if (house < S.house) return true; if (house > S.house) return false; int min = int.MaxValue; foreach (var it in Room.Items) { int l = RoomLevel(it.Index); if (l < 0) return false; min = Math.Min(min, l + 1); } return min >= lv; } m = RxUnlock.Match(trig); if (m.Success) return S.house >= int.Parse(m.Groups[1].Value) - 1; m = RxFollower.Match(trig); if (m.Success) return Follower >= System.Numerics.BigInteger.Parse(m.Groups[1].Value); return false; } /// 다음에 보여 줄 장(원작 순서 = 표 순서 · 이미 본 장 제외) public StoryChapterRow NextStory() { if (Story == null || !T.Cfg.remote.OnOffStory) return null; foreach (var c in Story.chapters) { if (S.seenStories.Contains(c.id)) continue; if (StoryTriggerMet(c.trigger)) return c; // 앞 장을 안 봤으면 뒤 장은 보류(표 순서 유지) return null; } return null; } public void MarkStorySeen(string id) { if (!S.seenStories.Contains(id)) S.seenStories.Add(id); } } }