using System; using System.Collections.Generic; using System.IO; using UnityEngine; namespace BrewMonster.Config { /// One row of skill_state_action.txt: skill id, visible state id, be-hit action, optional stay-down action. [Serializable] public struct SkillStateActionRow { public int skill; public int state; public string beHitAction; public string stayDownAction; } /// Parses the classic comma-separated skill_state_action table (same rules as legacy client / CECAttacksMan TextAsset loader). public static class SkillStateActionTextParser { /// /// Strip CSV-style wrapping quotes (Excel export often writes "2244","117",...). /// Handles doubled-quote escapes inside a quoted field. /// private static string UnquoteCsvField(string raw) { if (string.IsNullOrEmpty(raw)) return string.Empty; string s = raw.Trim().TrimStart('\uFEFF'); while (s.Length >= 2 && s[0] == '"' && s[s.Length - 1] == '"') { s = s.Substring(1, s.Length - 2).Replace("\"\"", "\"").Trim(); } // Odd exports / naive splits can leave stray " on one side. s = s.Trim('"').Trim(); return s; } public static List Parse(string text, ICollection warnings = null) { var rows = new List(); if (string.IsNullOrEmpty(text)) return rows; // Saved-as-UTF-8-with-BOM breaks int.TryParse on the first column of line 1 (\ufeff123,...). text = text.TrimStart('\uFEFF'); using (var reader = new StringReader(text)) { string line; int lineNum = 0; while ((line = reader.ReadLine()) != null) { lineNum++; string trimmed = line.TrimStart('\uFEFF').Trim(); if (trimmed.Length == 0 || trimmed.StartsWith("//") || trimmed.StartsWith("#")) continue; string[] v = trimmed.Split(','); // Some configs use tabs instead of commas. if (v.Length < 3) v = trimmed.Split('\t'); if (v.Length < 3) { warnings?.Add($"Line {lineNum}: need at least 3 comma-separated fields"); continue; } string s0 = UnquoteCsvField(v[0]); string s1 = UnquoteCsvField(v[1]); if (!int.TryParse(s0, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out int skillId) || !int.TryParse(s1, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out int stateId)) { warnings?.Add($"Line {lineNum}: invalid skill or state id (got \"{s0}\", \"{s1}\")"); continue; } string beHit = UnquoteCsvField(v[2]); string stayDown = v.Length > 3 ? UnquoteCsvField(v[3]) : string.Empty; rows.Add(new SkillStateActionRow { skill = skillId, state = stateId, beHitAction = beHit, stayDownAction = stayDown }); } } return rows; } } [CreateAssetMenu(fileName = "skill_state_action", menuName = "PerfectWorld/Skill State Action Config")] public class SkillStateActionConfig : ScriptableObject { [SerializeField] private List entries = new List(); public IReadOnlyList Entries => entries; #if UNITY_EDITOR /// Editor / tools: replace serialized rows after import. public void SetEntries(List newEntries) { entries = newEntries ?? new List(); } #endif } }