274 lines
11 KiB
C#
274 lines
11 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Creates SkillDefinition assets, SkillDatabase, and wires SkillRuntime + roll
|
|
/// banner + commentary into Assets/Scenes/DialogueTest.unity.
|
|
/// </summary>
|
|
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();
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
static void EnsureSkillAssetsOnly()
|
|
{
|
|
EnsureFolder("Assets", "Skills");
|
|
EnsureFolder("Assets/Skills", "Definitions");
|
|
|
|
var definitions = new List<SkillDefinition>();
|
|
foreach (var spec in Roster)
|
|
{
|
|
definitions.Add(CreateOrUpdateDefinition(spec));
|
|
}
|
|
|
|
var database = AssetDatabase.LoadAssetAtPath<SkillDatabase>(DatabasePath);
|
|
if (database == null)
|
|
{
|
|
database = ScriptableObject.CreateInstance<SkillDatabase>();
|
|
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<SkillDefinition>();
|
|
foreach (var spec in Roster)
|
|
{
|
|
definitions.Add(CreateOrUpdateDefinition(spec));
|
|
}
|
|
|
|
var database = AssetDatabase.LoadAssetAtPath<SkillDatabase>(DatabasePath);
|
|
if (database == null)
|
|
{
|
|
database = ScriptableObject.CreateInstance<SkillDatabase>();
|
|
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<SkillDefinition>(path);
|
|
if (asset == null)
|
|
{
|
|
asset = ScriptableObject.CreateInstance<SkillDefinition>();
|
|
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<DialogueRunner>();
|
|
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<SkillRuntime>() ?? skillGo.AddComponent<SkillRuntime>();
|
|
var commentary = skillGo.GetComponent<CommentarySystem>() ?? skillGo.AddComponent<CommentarySystem>();
|
|
var idle = skillGo.GetComponent<CommentaryIdleTimer>() ?? skillGo.AddComponent<CommentaryIdleTimer>();
|
|
|
|
var banner = EnsureBanner(runner.transform);
|
|
var view = banner.GetComponent<SkillCheckResultView>() ?? banner.AddComponent<SkillCheckResultView>();
|
|
|
|
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<CanvasGroup>();
|
|
var label = banner.GetComponentInChildren<TMP_Text>(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<Canvas>(true);
|
|
if (canvas == null)
|
|
{
|
|
var canvasGo = new GameObject("Skill Check Canvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
|
|
canvas = canvasGo.GetComponent<Canvas>();
|
|
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<RectTransform>();
|
|
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>();
|
|
image.color = new Color(0f, 0f, 0f, 0.75f);
|
|
|
|
var group = banner.GetComponent<CanvasGroup>();
|
|
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<RectTransform>();
|
|
labelRt.anchorMin = Vector2.zero;
|
|
labelRt.anchorMax = Vector2.one;
|
|
labelRt.offsetMin = new Vector2(16f, 8f);
|
|
labelRt.offsetMax = new Vector2(-16f, -8f);
|
|
|
|
var tmp = labelGo.AddComponent<TextMeshProUGUI>();
|
|
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);
|
|
}
|
|
}
|
|
}
|