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