using System.Collections.Generic; using System.IO; using NightclubArcadia.Skills; using TMPro; using UnityEditor; using UnityEditor.Callbacks; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.UI; using Yarn.Unity; /// /// Creates SkillDefinition assets, SkillDatabase, and wires SkillRuntime + roll /// banner + commentary into Assets/Scenes/DialogueTest.unity. /// public static class SkillSystemSetup { const string DefinitionsFolder = "Assets/Skills/Definitions"; const string SkillsFolder = "Assets/Skills"; const string DatabasePath = "Assets/Skills/SkillDatabase.asset"; const string ScenePath = "Assets/Scenes/DialogueTest.unity"; const string AutoSetupSessionKey = "NightclubArcadia.SkillSystem.AutoSetupDone"; struct SkillSpec { public string Id; public string DisplayName; public SkillDomain Domain; public Color Accent; public int StartingRank; } static readonly SkillSpec[] Roster = { new SkillSpec { Id = "deduction", DisplayName = "Deduction", Domain = SkillDomain.Reason, Accent = new Color(0.55f, 0.75f, 1f), StartingRank = 1 }, new SkillSpec { Id = "streetwise", DisplayName = "Streetwise", Domain = SkillDomain.Reason, Accent = new Color(0.7f, 0.85f, 0.55f), StartingRank = 1 }, new SkillSpec { Id = "recall", DisplayName = "Recall", Domain = SkillDomain.Reason, Accent = new Color(0.65f, 0.7f, 1f), StartingRank = 1 }, new SkillSpec { Id = "read_room", DisplayName = "Read the Room", Domain = SkillDomain.Instinct, Accent = new Color(1f, 0.75f, 0.45f), StartingRank = 1 }, new SkillSpec { Id = "paranoia", DisplayName = "Paranoia", Domain = SkillDomain.Instinct, Accent = new Color(1f, 0.45f, 0.55f), StartingRank = 1 }, new SkillSpec { Id = "reflexes", DisplayName = "Reflexes", Domain = SkillDomain.Instinct, Accent = new Color(1f, 0.9f, 0.4f), StartingRank = 1 }, new SkillSpec { Id = "persuasion", DisplayName = "Persuasion", Domain = SkillDomain.Presence, Accent = new Color(0.85f, 0.65f, 1f), StartingRank = 1 }, // Rank 2 so the Bouncer "Let me through" option gated by skill_rank >= 2 is visible in the demo. new SkillSpec { Id = "intimidation", DisplayName = "Intimidation", Domain = SkillDomain.Presence, Accent = new Color(1f, 0.4f, 0.4f), StartingRank = 2 }, new SkillSpec { Id = "rapport", DisplayName = "Rapport", Domain = SkillDomain.Presence, Accent = new Color(0.55f, 0.95f, 0.85f), StartingRank = 1 }, new SkillSpec { Id = "endurance", DisplayName = "Endurance", Domain = SkillDomain.Body, Accent = new Color(0.85f, 0.7f, 0.5f), StartingRank = 1 }, new SkillSpec { Id = "sleight_of_hand", DisplayName = "Sleight of Hand", Domain = SkillDomain.Body, Accent = new Color(0.7f, 0.9f, 0.95f), StartingRank = 1 }, new SkillSpec { Id = "nerve", DisplayName = "Nerve", Domain = SkillDomain.Body, Accent = new Color(0.95f, 0.6f, 0.35f), StartingRank = 1 }, }; [DidReloadScripts] static void AutoSetupAfterReload() { if (EditorApplication.isPlayingOrWillChangePlaymode) { return; } if (SessionState.GetBool(AutoSetupSessionKey, false)) { return; } // Defer one frame so AssetDatabase is fully ready after domain reload. EditorApplication.delayCall += () => { if (SessionState.GetBool(AutoSetupSessionKey, false)) { return; } SessionState.SetBool(AutoSetupSessionKey, true); EnsureSkillAssetsOnly(); }; } /// /// Creates/updates ScriptableObject assets without opening a scene. /// Safe to run on domain reload. Scene wiring stays on the menu item / /// the checked-in DialogueTest.unity Skill System objects. /// static void EnsureSkillAssetsOnly() { EnsureFolder("Assets", "Skills"); EnsureFolder("Assets/Skills", "Definitions"); var definitions = new List(); foreach (var spec in Roster) { definitions.Add(CreateOrUpdateDefinition(spec)); } var database = AssetDatabase.LoadAssetAtPath(DatabasePath); if (database == null) { database = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(database, DatabasePath); } database.EditorSetSkills(definitions); EditorUtility.SetDirty(database); AssetDatabase.SaveAssets(); Debug.Log($"[SkillSystemSetup] Skill assets ready at {DatabasePath}. Run Tools/Nightclub Arcadia/Set Up Skill System if scene refs need rewiring."); } [MenuItem("Tools/Nightclub Arcadia/Set Up Skill System")] public static void SetUpSkillSystem() { EnsureFolder("Assets", "Skills"); EnsureFolder("Assets/Skills", "Definitions"); var definitions = new List(); foreach (var spec in Roster) { definitions.Add(CreateOrUpdateDefinition(spec)); } var database = AssetDatabase.LoadAssetAtPath(DatabasePath); if (database == null) { database = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(database, DatabasePath); } database.EditorSetSkills(definitions); EditorUtility.SetDirty(database); AssetDatabase.SaveAssets(); WireDialogueTestScene(database); AssetDatabase.SaveAssets(); Debug.Log($"Skill system setup complete. Database at {DatabasePath}; scene wired at {ScenePath}."); } static SkillDefinition CreateOrUpdateDefinition(SkillSpec spec) { var path = $"{DefinitionsFolder}/{spec.Id}.asset"; var asset = AssetDatabase.LoadAssetAtPath(path); if (asset == null) { asset = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(asset, path); } asset.EditorSet( spec.Id, spec.DisplayName, spec.Domain, $"Placeholder skill — replace when Skill_System_Draft.md is available.", spec.StartingRank, spec.Accent); EditorUtility.SetDirty(asset); return asset; } static void WireDialogueTestScene(SkillDatabase database) { if (!File.Exists(ScenePath)) { Debug.LogError($"Scene not found at {ScenePath}. Run Tools/Nightclub Arcadia/Set Up Yarn Dialogue Test first."); return; } var scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single); var runner = Object.FindAnyObjectByType(); if (runner == null) { Debug.LogError("No DialogueRunner in DialogueTest.unity."); return; } var skillGo = GameObject.Find("Skill System"); if (skillGo == null) { skillGo = new GameObject("Skill System"); } var runtime = skillGo.GetComponent() ?? skillGo.AddComponent(); var commentary = skillGo.GetComponent() ?? skillGo.AddComponent(); var idle = skillGo.GetComponent() ?? skillGo.AddComponent(); var banner = EnsureBanner(runner.transform); var view = banner.GetComponent() ?? banner.AddComponent(); var runtimeSo = new SerializedObject(runtime); runtimeSo.FindProperty("database").objectReferenceValue = database; runtimeSo.FindProperty("dialogueRunner").objectReferenceValue = runner; runtimeSo.FindProperty("resultView").objectReferenceValue = view; runtimeSo.FindProperty("commentary").objectReferenceValue = commentary; runtimeSo.FindProperty("debugContextId").stringValue = "Entrance_Queue"; runtimeSo.ApplyModifiedPropertiesWithoutUndo(); var commentarySo = new SerializedObject(commentary); commentarySo.FindProperty("dialogueRunner").objectReferenceValue = runner; commentarySo.FindProperty("database").objectReferenceValue = database; commentarySo.ApplyModifiedPropertiesWithoutUndo(); var idleSo = new SerializedObject(idle); idleSo.FindProperty("dialogueRunner").objectReferenceValue = runner; idleSo.FindProperty("contextId").stringValue = "Entrance_Queue"; idleSo.ApplyModifiedPropertiesWithoutUndo(); var group = banner.GetComponent(); var label = banner.GetComponentInChildren(true); var viewSo = new SerializedObject(view); viewSo.FindProperty("group").objectReferenceValue = group; viewSo.FindProperty("label").objectReferenceValue = label; viewSo.ApplyModifiedPropertiesWithoutUndo(); EditorSceneManager.MarkSceneDirty(scene); EditorSceneManager.SaveScene(scene); } static GameObject EnsureBanner(Transform dialogueRoot) { var canvas = dialogueRoot.GetComponentInChildren(true); if (canvas == null) { var canvasGo = new GameObject("Skill Check Canvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster)); canvas = canvasGo.GetComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvasGo.transform.SetParent(dialogueRoot, false); } var existing = canvas.transform.Find("Skill Check Banner"); if (existing != null) { return existing.gameObject; } var banner = new GameObject("Skill Check Banner", typeof(RectTransform), typeof(CanvasGroup), typeof(Image)); banner.transform.SetParent(canvas.transform, false); var rt = banner.GetComponent(); rt.anchorMin = new Vector2(0.1f, 0.82f); rt.anchorMax = new Vector2(0.9f, 0.95f); rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero; var image = banner.GetComponent(); image.color = new Color(0f, 0f, 0f, 0.75f); var group = banner.GetComponent(); group.alpha = 0f; group.blocksRaycasts = false; group.interactable = false; var labelGo = new GameObject("Label", typeof(RectTransform)); labelGo.transform.SetParent(banner.transform, false); var labelRt = labelGo.GetComponent(); labelRt.anchorMin = Vector2.zero; labelRt.anchorMax = Vector2.one; labelRt.offsetMin = new Vector2(16f, 8f); labelRt.offsetMax = new Vector2(-16f, -8f); var tmp = labelGo.AddComponent(); tmp.text = string.Empty; tmp.alignment = TextAlignmentOptions.Center; tmp.fontSize = 28f; tmp.textWrappingMode = TextWrappingModes.Normal; tmp.color = Color.white; return banner; } static void EnsureFolder(string parent, string child) { var path = $"{parent}/{child}"; if (!AssetDatabase.IsValidFolder(path)) { AssetDatabase.CreateFolder(parent, child); } } }