implemented skill system according to designdocuments
This commit is contained in:
@@ -14,9 +14,29 @@ Bouncer: Evening. Name on the list?
|
||||
You: Depends who's asking.
|
||||
-> I'm on the list.
|
||||
<<set $claimed_to_be_on_list = true>>
|
||||
Bouncer: ...you're not on the list.
|
||||
Bouncer: But it's slow tonight. Go on.
|
||||
<<set $went_inside = true>>
|
||||
<<check persuasion standard>>
|
||||
<<if $check_result>>
|
||||
Bouncer: ...you're not on the list.
|
||||
Bouncer: But it's slow tonight. Go on.
|
||||
<<set $went_inside = true>>
|
||||
<<else>>
|
||||
Bouncer: Nobody's on the list tonight. Especially not you.
|
||||
<<set $went_inside = false>>
|
||||
<<endif>>
|
||||
-> Let me through. <<if skill_rank("intimidation") >= 2>>
|
||||
<<check intimidation hard -1>>
|
||||
<<if $check_degree == "critical_success">>
|
||||
Bouncer: ...yeah. Yeah, okay.
|
||||
<<set $reputation += 2>>
|
||||
<<set $went_inside = true>>
|
||||
<<elseif $check_result>>
|
||||
Bouncer: Fine. Don't make it a thing.
|
||||
<<set $went_inside = true>>
|
||||
<<else>>
|
||||
Bouncer: Try that again and you're talking to the pavement.
|
||||
<<set $reputation -= 1>>
|
||||
<<set $went_inside = false>>
|
||||
<<endif>>
|
||||
-> Actually, I'll head home.
|
||||
Bouncer: Smart choice.
|
||||
<<set $went_inside = false>>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Passive skill commentary. Fired by CommentarySystem, never <<jump>>ed to.
|
||||
// One node per (context, skill) pair; if a skill has nothing to say about a
|
||||
// context, simply omit the node and it won't be considered.
|
||||
|
||||
title: Commentary_Entrance_Queue_paranoia
|
||||
---
|
||||
Paranoia: The man three back hasn't looked at his phone once. Nobody waits like that.
|
||||
===
|
||||
|
||||
title: Commentary_Entrance_Queue_read_room
|
||||
---
|
||||
Read the Room: Everyone here is pretending they're not cold.
|
||||
===
|
||||
|
||||
title: Commentary_Entrance_Queue_streetwise
|
||||
---
|
||||
Streetwise: Door's been "slow tonight" for forty minutes. That's a price, not a queue.
|
||||
===
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26e9b5dea1744e39a5fe59bb1bc20fa7
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 11500000, guid: 94073015eacc34c1d8fc6786e43d60ca, type: 3}
|
||||
@@ -18,4 +18,13 @@ title: Declarations
|
||||
// Smart variable: derived, not stored. Can never drift out of sync with
|
||||
// $reputation, so there's nothing to remember to update.
|
||||
<<declare $is_vip = $reputation > 10>>
|
||||
|
||||
// Written by <<check>> (YarnSkillCommands). Never <<set>> these by hand.
|
||||
<<declare $check_result = false as bool>>
|
||||
<<declare $check_roll = 0 as number>>
|
||||
<<declare $check_total = 0 as number>>
|
||||
<<declare $check_dc = 0 as number>>
|
||||
<<declare $check_modifier = 0 as number>>
|
||||
<<declare $check_degree = "" as string>>
|
||||
<<declare $check_skill = "" as string>>
|
||||
===
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Scratch node for verifying the skill-check plumbing. Not part of the story.
|
||||
// Point DialogueRunner.startNode at "Debug_SkillCheck" to run it, then set it
|
||||
// back to "Start" when you're done.
|
||||
|
||||
title: Debug_SkillCheck
|
||||
---
|
||||
Narrator: Skill check harness.
|
||||
<<check persuasion standard>>
|
||||
<<if $check_result>>
|
||||
Narrator: Passed. Rolled {$check_roll}, total {$check_total} against DC {$check_dc}.
|
||||
<<else>>
|
||||
Narrator: Failed. Rolled {$check_roll}, total {$check_total} against DC {$check_dc}.
|
||||
<<endif>>
|
||||
|
||||
// Explicit numeric DC plus a situational modifier.
|
||||
<<check deduction 16 2>>
|
||||
Narrator: Deduction came out {$check_degree}.
|
||||
|
||||
// Deliberate typo: should log an error and continue, not deadlock.
|
||||
<<check nosuchskill standard>>
|
||||
Narrator: Survived the bad skill id.
|
||||
===
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e497c84fe7bd48ea8c828ac1702f79bf
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 11500000, guid: 94073015eacc34c1d8fc6786e43d60ca, type: 3}
|
||||
@@ -0,0 +1,273 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 231654cf350a49ce9920bc4f24a1f002
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// One-click EditMode test run for the skill system.
|
||||
/// </summary>
|
||||
public static class SkillSystemTestRunner
|
||||
{
|
||||
[MenuItem("Tools/Nightclub Arcadia/Run Skill EditMode Tests")]
|
||||
public static void RunEditModeTests()
|
||||
{
|
||||
var api = ScriptableObject.CreateInstance<TestRunnerApi>();
|
||||
var filter = new Filter
|
||||
{
|
||||
testMode = TestMode.EditMode,
|
||||
assemblyNames = new[] { "NightclubArcadia.Skills.Tests" },
|
||||
};
|
||||
|
||||
api.RegisterCallbacks(new Callbacks());
|
||||
api.Execute(new ExecutionSettings(filter));
|
||||
Debug.Log("[SkillSystemTestRunner] EditMode tests started — watch the Console / Test Runner.");
|
||||
}
|
||||
|
||||
sealed class Callbacks : ICallbacks
|
||||
{
|
||||
public void RunStarted(ITestAdaptor testsToRun) { }
|
||||
|
||||
public void RunFinished(ITestResultAdaptor result)
|
||||
{
|
||||
Debug.Log($"[SkillSystemTestRunner] Finished. Pass:{result.PassCount} Fail:{result.FailCount} Skip:{result.SkipCount}");
|
||||
if (result.FailCount > 0)
|
||||
{
|
||||
Debug.LogError("[SkillSystemTestRunner] One or more skill EditMode tests failed.");
|
||||
}
|
||||
}
|
||||
|
||||
public void TestStarted(ITestAdaptor test) { }
|
||||
|
||||
public void TestFinished(ITestResultAdaptor result)
|
||||
{
|
||||
if (result.TestStatus == TestStatus.Failed)
|
||||
{
|
||||
Debug.LogError($"[SkillSystemTestRunner] FAIL {result.FullName}\n{result.Message}\n{result.StackTrace}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c8e1a2b3d4f4e6a9b0c1d2e3f4a5b6c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -56,7 +56,7 @@ public static class YarnDemoSetup
|
||||
EditorUtility.SetDirty(runner);
|
||||
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
var eventSystems = Object.FindObjectsByType<EventSystem>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
var eventSystems = Object.FindObjectsByType<EventSystem>(FindObjectsInactive.Include);
|
||||
#else
|
||||
var eventSystems = Object.FindObjectsOfType<EventSystem>(true);
|
||||
#endif
|
||||
|
||||
@@ -562,6 +562,446 @@ MonoBehaviour:
|
||||
nextLineKeyCode: 27
|
||||
hurryUpOptionsKeyCode: 32
|
||||
cancelDialogueKeyCode: 0
|
||||
--- !u!114 &910000019 stripped
|
||||
MonoBehaviour:
|
||||
m_CorrespondingSourceObject: {fileID: 8309902628685535410, guid: 52571f68872914e24837210513edea1d, type: 3}
|
||||
m_PrefabInstance: {fileID: 1720996731}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4bec29c0a230741bdac901dba8da47ee, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: YarnSpinner.Unity::Yarn.Unity.DialogueRunner
|
||||
--- !u!1 &910000001
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 910000002}
|
||||
- component: {fileID: 910000003}
|
||||
- component: {fileID: 910000004}
|
||||
- component: {fileID: 910000005}
|
||||
m_Layer: 0
|
||||
m_Name: Skill System
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &910000002
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000001}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 910000007}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &910000003
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000001}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: cb04a8b1bce645fba398b22bbb4db58a, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: NightclubArcadia.Skills::NightclubArcadia.Skills.SkillRuntime
|
||||
database: {fileID: 11400000, guid: d15ae35514b14ded9b398d630e8ef373, type: 2}
|
||||
dialogueRunner: {fileID: 910000019}
|
||||
resultView: {fileID: 910000015}
|
||||
commentary: {fileID: 910000004}
|
||||
rollSeed: 0
|
||||
debugContextId: Entrance_Queue
|
||||
--- !u!114 &910000004
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000001}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 6626d81593b24235b36ae6c168aef87f, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: NightclubArcadia.Skills::NightclubArcadia.Skills.CommentarySystem
|
||||
dialogueRunner: {fileID: 910000019}
|
||||
database: {fileID: 11400000, guid: d15ae35514b14ded9b398d630e8ef373, type: 2}
|
||||
minSecondsBetween: 20
|
||||
minRankToSpeak: 1
|
||||
recencyWindow: 3
|
||||
--- !u!114 &910000005
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000001}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 0d65653213924ceaa756c6b6e65b522c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: NightclubArcadia.Skills::NightclubArcadia.Skills.CommentaryIdleTimer
|
||||
dialogueRunner: {fileID: 910000019}
|
||||
contextId: Entrance_Queue
|
||||
idleSeconds: 45
|
||||
--- !u!1 &910000006
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 910000007}
|
||||
- component: {fileID: 910000008}
|
||||
- component: {fileID: 910000009}
|
||||
- component: {fileID: 910000010}
|
||||
m_Layer: 5
|
||||
m_Name: Skill Check Canvas
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &910000007
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000006}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0, y: 0, z: 0}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 910000012}
|
||||
m_Father: {fileID: 910000002}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0, y: 0}
|
||||
--- !u!223 &910000008
|
||||
Canvas:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000006}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_RenderMode: 0
|
||||
m_Camera: {fileID: 0}
|
||||
m_PlaneDistance: 100
|
||||
m_PixelPerfect: 0
|
||||
m_ReceivesEvents: 1
|
||||
m_OverrideSorting: 0
|
||||
m_OverridePixelPerfect: 0
|
||||
m_SortingBucketNormalizedSize: 0
|
||||
m_VertexColorAlwaysGammaSpace: 0
|
||||
m_AdditionalShaderChannelsFlag: 25
|
||||
m_UpdateRectTransformForStandalone: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingOrder: 100
|
||||
m_TargetDisplay: 0
|
||||
--- !u!114 &910000009
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000006}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 1920, y: 1080}
|
||||
m_ScreenMatchMode: 0
|
||||
m_MatchWidthOrHeight: 0.5
|
||||
m_PhysicalUnit: 3
|
||||
m_FallbackScreenDPI: 96
|
||||
m_DefaultSpriteDPI: 96
|
||||
m_DynamicPixelsPerUnit: 1
|
||||
m_PresetInfoIsWorld: 0
|
||||
--- !u!114 &910000010
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000006}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.GraphicRaycaster
|
||||
m_IgnoreReversedGraphics: 1
|
||||
m_BlockingObjects: 0
|
||||
m_BlockingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
--- !u!1 &910000011
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 910000012}
|
||||
- component: {fileID: 910000020}
|
||||
- component: {fileID: 910000013}
|
||||
- component: {fileID: 910000014}
|
||||
- component: {fileID: 910000015}
|
||||
m_Layer: 5
|
||||
m_Name: Skill Check Banner
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &910000012
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000011}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 910000017}
|
||||
m_Father: {fileID: 910000007}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.1, y: 0.82}
|
||||
m_AnchorMax: {x: 0.9, y: 0.95}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &910000020
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000011}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!225 &910000013
|
||||
CanvasGroup:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000011}
|
||||
m_Enabled: 1
|
||||
m_Alpha: 0
|
||||
m_Interactable: 0
|
||||
m_BlocksRaycasts: 0
|
||||
m_IgnoreParentGroups: 0
|
||||
--- !u!114 &910000014
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000011}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0, g: 0, b: 0, a: 0.75}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &910000015
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000011}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: af3c81416f5f4a869baa347fd9dee9e3, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: NightclubArcadia.Skills::NightclubArcadia.Skills.SkillCheckResultView
|
||||
group: {fileID: 910000013}
|
||||
label: {fileID: 910000018}
|
||||
holdSeconds: 1.6
|
||||
fadeSeconds: 0.15
|
||||
--- !u!1 &910000016
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 910000017}
|
||||
- component: {fileID: 910000021}
|
||||
- component: {fileID: 910000018}
|
||||
m_Layer: 5
|
||||
m_Name: Label
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &910000017
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000016}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 910000012}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: -32, y: -16}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &910000021
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000016}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &910000018
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 910000016}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text:
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_sharedMaterial: {fileID: 0}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 28
|
||||
m_fontSizeBase: 28
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_TextWrappingMode: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 0
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!1660057539 &9223372036854775807
|
||||
SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -569,3 +1009,4 @@ SceneRoots:
|
||||
- {fileID: 214568766}
|
||||
- {fileID: 1516720415}
|
||||
- {fileID: 1720996731}
|
||||
- {fileID: 910000002}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13f14dfeaec742b38567bb2e4c1ba440
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26f1b62937714176b821c2ee871f6cd3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9e3484419734ba89ff5bcd5a991f8e2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
using UnityEngine;
|
||||
using Yarn.Unity;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
public sealed class CommentaryIdleTimer : MonoBehaviour
|
||||
{
|
||||
[SerializeField] DialogueRunner dialogueRunner;
|
||||
[SerializeField] string contextId = "Entrance_Queue";
|
||||
[SerializeField] float idleSeconds = 45f;
|
||||
|
||||
float _idleAccum;
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (string.IsNullOrEmpty(contextId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialogueRunner != null && dialogueRunner.IsDialogueRunning)
|
||||
{
|
||||
_idleAccum = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
_idleAccum += Time.deltaTime;
|
||||
if (_idleAccum < idleSeconds)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_idleAccum = 0f;
|
||||
|
||||
var runtime = SkillRuntime.Instance;
|
||||
if (runtime == null || runtime.Commentary == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.Commentary.RequestCommentary(contextId, repeatable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d65653213924ceaa756c6b6e65b522c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,194 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Yarn.Unity;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Passive skill commentary outside the Yarn node tree.
|
||||
/// Fires at most one speaker per RequestCommentary call.
|
||||
/// </summary>
|
||||
public sealed class CommentarySystem : MonoBehaviour
|
||||
{
|
||||
[SerializeField] DialogueRunner dialogueRunner;
|
||||
[SerializeField] SkillDatabase database;
|
||||
[SerializeField] float minSecondsBetween = 20f;
|
||||
[SerializeField] int minRankToSpeak = 1;
|
||||
[SerializeField] int recencyWindow = 3;
|
||||
|
||||
readonly List<string> _recent = new List<string>();
|
||||
readonly HashSet<string> _firedContexts = new HashSet<string>(StringComparer.Ordinal);
|
||||
float _lastFiredAt = float.NegativeInfinity;
|
||||
|
||||
public void RequestCommentary(string contextId)
|
||||
{
|
||||
RequestCommentary(contextId, repeatable: false);
|
||||
}
|
||||
|
||||
public void RequestCommentary(string contextId, bool repeatable)
|
||||
{
|
||||
if (string.IsNullOrEmpty(contextId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialogueRunner == null || database == null)
|
||||
{
|
||||
Debug.LogWarning($"[{nameof(CommentarySystem)}] Missing DialogueRunner or SkillDatabase.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialogueRunner.IsDialogueRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Time.time - _lastFiredAt < minSecondsBetween)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!repeatable && _firedContexts.Contains(contextId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var runtime = SkillRuntime.Instance;
|
||||
if (runtime == null || runtime.Skills == null || runtime.Rolls == null)
|
||||
{
|
||||
Debug.LogWarning($"[{nameof(CommentarySystem)}] SkillRuntime is not ready.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
var candidates = new List<SkillDefinition>();
|
||||
foreach (var skill in database.All)
|
||||
{
|
||||
if (skill == null || string.IsNullOrEmpty(skill.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (runtime.Skills.GetRank(skill.Id) < minRankToSpeak)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var nodeName = $"Commentary_{contextId}_{skill.Id}";
|
||||
if (!NodeExists(nodeName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
candidates.Add(skill);
|
||||
}
|
||||
|
||||
var picked = Pick(candidates, runtime.Skills, _recent, recencyWindow, runtime.Rolls);
|
||||
if (picked == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var startNode = $"Commentary_{contextId}_{picked.Id}";
|
||||
_ = dialogueRunner.StartDialogue(startNode);
|
||||
_lastFiredAt = Time.time;
|
||||
_firedContexts.Add(contextId);
|
||||
_recent.Add(picked.Id);
|
||||
while (_recent.Count > Math.Max(1, recencyWindow))
|
||||
{
|
||||
_recent.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
bool NodeExists(string nodeName)
|
||||
{
|
||||
if (dialogueRunner.Dialogue != null && dialogueRunner.Dialogue.NodeExists(nodeName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var project = dialogueRunner.YarnProject;
|
||||
if (project != null)
|
||||
{
|
||||
foreach (var name in project.NodeNames)
|
||||
{
|
||||
if (string.Equals(name, nodeName, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Weighted pick: weight = max(1, rank); recent speakers (within recencyWindow) have weight halved.
|
||||
/// </summary>
|
||||
public static SkillDefinition Pick(
|
||||
IReadOnlyList<SkillDefinition> candidates,
|
||||
PlayerSkills skills,
|
||||
IReadOnlyList<string> recent,
|
||||
int recencyWindowSize,
|
||||
IRollSource rolls)
|
||||
{
|
||||
if (candidates == null || candidates.Count == 0 || skills == null || rolls == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var weights = new int[candidates.Count];
|
||||
var total = 0;
|
||||
for (var i = 0; i < candidates.Count; i++)
|
||||
{
|
||||
var skill = candidates[i];
|
||||
var weight = Math.Max(1, skills.GetRank(skill.Id));
|
||||
if (WasRecent(skill.Id, recent, recencyWindowSize))
|
||||
{
|
||||
weight = Math.Max(1, weight / 2);
|
||||
}
|
||||
|
||||
weights[i] = weight;
|
||||
total += weight;
|
||||
}
|
||||
|
||||
if (total <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// IRollSource returns 1..sides; map onto [0, total).
|
||||
var pick = rolls.Roll(total) - 1;
|
||||
var cumulative = 0;
|
||||
for (var i = 0; i < candidates.Count; i++)
|
||||
{
|
||||
cumulative += weights[i];
|
||||
if (pick < cumulative)
|
||||
{
|
||||
return candidates[i];
|
||||
}
|
||||
}
|
||||
|
||||
return candidates[candidates.Count - 1];
|
||||
}
|
||||
|
||||
static bool WasRecent(string skillId, IReadOnlyList<string> recent, int window)
|
||||
{
|
||||
if (recent == null || recent.Count == 0 || window <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var start = Math.Max(0, recent.Count - window);
|
||||
for (var i = start; i < recent.Count; i++)
|
||||
{
|
||||
if (string.Equals(recent[i], skillId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6626d81593b24235b36ae6c168aef87f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,33 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public sealed class CommentaryTrigger : MonoBehaviour
|
||||
{
|
||||
[SerializeField] string contextId;
|
||||
[SerializeField] bool repeatable;
|
||||
[SerializeField] string playerTag = "Player";
|
||||
|
||||
void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (string.IsNullOrEmpty(contextId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(playerTag) && !other.CompareTag(playerTag))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var runtime = SkillRuntime.Instance;
|
||||
if (runtime == null || runtime.Commentary == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.Commentary.RequestCommentary(contextId, repeatable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04f12949da1741e5a591519698c9ae95
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 249854c65e7742beaac1d2c969863d03
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
public enum DcBand
|
||||
{
|
||||
Trivial = 5,
|
||||
Easy = 8,
|
||||
Standard = 11,
|
||||
Hard = 14,
|
||||
Formidable = 17,
|
||||
Legendary = 20,
|
||||
}
|
||||
|
||||
public static class DcBands
|
||||
{
|
||||
public static bool TryParse(string input, out int dc)
|
||||
{
|
||||
dc = 0;
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
input = input.Trim();
|
||||
|
||||
if (int.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out var numeric))
|
||||
{
|
||||
dc = numeric;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Enum.TryParse<DcBand>(input, ignoreCase: true, out var band))
|
||||
{
|
||||
dc = (int)band;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04013b352063419eb6f0c8932a3cb116
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
public enum SkillCheckDegree
|
||||
{
|
||||
CriticalFailure,
|
||||
Failure,
|
||||
Success,
|
||||
CriticalSuccess,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a099dc0cf50346e98ea9795411a21f99
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
public readonly struct SkillCheckResult
|
||||
{
|
||||
public SkillCheckResult(
|
||||
string skillId,
|
||||
string skillDisplayName,
|
||||
int roll,
|
||||
int rankMod,
|
||||
int situationalMod,
|
||||
int stateMod,
|
||||
int total,
|
||||
int dc,
|
||||
bool success,
|
||||
SkillCheckDegree degree)
|
||||
{
|
||||
SkillId = skillId;
|
||||
SkillDisplayName = skillDisplayName;
|
||||
Roll = roll;
|
||||
RankMod = rankMod;
|
||||
SituationalMod = situationalMod;
|
||||
StateMod = stateMod;
|
||||
Total = total;
|
||||
DC = dc;
|
||||
Success = success;
|
||||
Degree = degree;
|
||||
}
|
||||
|
||||
public string SkillId { get; }
|
||||
public string SkillDisplayName { get; }
|
||||
public int Roll { get; }
|
||||
public int RankMod { get; }
|
||||
public int SituationalMod { get; }
|
||||
public int StateMod { get; }
|
||||
public int Total { get; }
|
||||
public int DC { get; }
|
||||
public bool Success { get; }
|
||||
public SkillCheckDegree Degree { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ee1160f9fa3439280ca256e5f23193f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Nightclub Arcadia/Skill Database", fileName = "SkillDatabase")]
|
||||
public sealed class SkillDatabase : ScriptableObject
|
||||
{
|
||||
[SerializeField] List<SkillDefinition> skills = new List<SkillDefinition>();
|
||||
|
||||
Dictionary<string, SkillDefinition> _lookup;
|
||||
|
||||
public IReadOnlyList<SkillDefinition> All => skills;
|
||||
|
||||
public bool TryGet(string skillId, out SkillDefinition def)
|
||||
{
|
||||
EnsureLookup();
|
||||
if (string.IsNullOrEmpty(skillId))
|
||||
{
|
||||
def = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return _lookup.TryGetValue(skillId, out def);
|
||||
}
|
||||
|
||||
void EnsureLookup()
|
||||
{
|
||||
if (_lookup != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lookup = new Dictionary<string, SkillDefinition>(StringComparer.OrdinalIgnoreCase);
|
||||
if (skills == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var skill in skills)
|
||||
{
|
||||
if (skill == null || string.IsNullOrEmpty(skill.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_lookup[skill.Id] = skill;
|
||||
}
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
_lookup = null;
|
||||
if (skills == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var skill in skills)
|
||||
{
|
||||
if (skill == null)
|
||||
{
|
||||
Debug.LogError($"[{nameof(SkillDatabase)}] Null skill entry in {name}.", this);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(skill.Id))
|
||||
{
|
||||
Debug.LogError($"[{nameof(SkillDatabase)}] Skill '{skill.name}' has an empty Id.", this);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!seen.Add(skill.Id))
|
||||
{
|
||||
Debug.LogError($"[{nameof(SkillDatabase)}] Duplicate skill Id '{skill.Id}'.", this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void EditorSetSkills(List<SkillDefinition> newSkills)
|
||||
{
|
||||
skills = newSkills ?? new List<SkillDefinition>();
|
||||
_lookup = null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73f6e7831a60457b918d303c16fc6cec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Nightclub Arcadia/Skill Definition", fileName = "NewSkill")]
|
||||
public sealed class SkillDefinition : ScriptableObject
|
||||
{
|
||||
[SerializeField] string id;
|
||||
[SerializeField] string displayName;
|
||||
[SerializeField] SkillDomain domain;
|
||||
[TextArea] [SerializeField] string description;
|
||||
[SerializeField] int startingRank;
|
||||
[SerializeField] Color accentColor = Color.white;
|
||||
|
||||
public string Id => id;
|
||||
public string DisplayName => displayName;
|
||||
public SkillDomain Domain => domain;
|
||||
public string Description => description;
|
||||
public int StartingRank => startingRank;
|
||||
public Color AccentColor => accentColor;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void EditorSet(
|
||||
string newId,
|
||||
string newDisplayName,
|
||||
SkillDomain newDomain,
|
||||
string newDescription,
|
||||
int newStartingRank,
|
||||
Color newAccentColor)
|
||||
{
|
||||
id = newId;
|
||||
displayName = newDisplayName;
|
||||
domain = newDomain;
|
||||
description = newDescription;
|
||||
startingRank = newStartingRank;
|
||||
accentColor = newAccentColor;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a26230b6f20436891aa772e8f858f3f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
public enum SkillDomain
|
||||
{
|
||||
Reason,
|
||||
Instinct,
|
||||
Presence,
|
||||
Body,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bdf9a02be4b42a4a48a099fe924980e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "NightclubArcadia.Skills",
|
||||
"rootNamespace": "NightclubArcadia.Skills",
|
||||
"references": [
|
||||
"YarnSpinner.Unity",
|
||||
"Unity.TextMeshPro"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac01048d38154701b13372655db532b2
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e76dc9c927344424a7f8a4acf54d7253
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
public interface IRollSource
|
||||
{
|
||||
/// <summary>Returns a value in [1, sides] inclusive.</summary>
|
||||
int Roll(int sides);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43de4384f09d4bd7b142d105f2c04007
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Pure rank state. No rolling, DC comparison, modifier math, UI, or Yarn.
|
||||
/// </summary>
|
||||
public sealed class PlayerSkills
|
||||
{
|
||||
readonly Dictionary<string, int> _ranks =
|
||||
new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public PlayerSkills(SkillDatabase db)
|
||||
{
|
||||
if (db == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(db));
|
||||
}
|
||||
|
||||
foreach (var skill in db.All)
|
||||
{
|
||||
if (skill == null || string.IsNullOrEmpty(skill.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_ranks[skill.Id] = skill.StartingRank;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, int> AllRanks => _ranks;
|
||||
|
||||
public int GetRank(string skillId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(skillId))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return _ranks.TryGetValue(skillId, out var rank) ? rank : 0;
|
||||
}
|
||||
|
||||
public void SetRank(string skillId, int rank)
|
||||
{
|
||||
if (string.IsNullOrEmpty(skillId))
|
||||
{
|
||||
throw new ArgumentException("Skill id is required.", nameof(skillId));
|
||||
}
|
||||
|
||||
_ranks[skillId] = rank;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ace1f9c8cfc64ff9aa040d60495b6115
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
public sealed class SeededRollSource : IRollSource
|
||||
{
|
||||
readonly Random _random;
|
||||
|
||||
public SeededRollSource(int seed)
|
||||
{
|
||||
_random = new Random(seed);
|
||||
}
|
||||
|
||||
public int Roll(int sides)
|
||||
{
|
||||
if (sides < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(sides));
|
||||
}
|
||||
|
||||
return _random.Next(1, sides + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e89e99b02ed453db3efbd72ec8d038a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Pure check math. Exactly three modifier sources: rank, situational, state.
|
||||
/// No UnityEngine types. No side effects on PlayerSkills / VariableStorage / UI.
|
||||
/// </summary>
|
||||
public sealed class SkillCheckSystem
|
||||
{
|
||||
readonly SkillDatabase _database;
|
||||
readonly PlayerSkills _skills;
|
||||
readonly SkillStateModifiers _state;
|
||||
readonly IRollSource _rolls;
|
||||
|
||||
public SkillCheckSystem(
|
||||
SkillDatabase database,
|
||||
PlayerSkills skills,
|
||||
SkillStateModifiers state,
|
||||
IRollSource rolls)
|
||||
{
|
||||
_database = database ?? throw new ArgumentNullException(nameof(database));
|
||||
_skills = skills ?? throw new ArgumentNullException(nameof(skills));
|
||||
_state = state ?? throw new ArgumentNullException(nameof(state));
|
||||
_rolls = rolls ?? throw new ArgumentNullException(nameof(rolls));
|
||||
}
|
||||
|
||||
public SkillCheckResult Resolve(string skillId, int dc, int situationalMod)
|
||||
{
|
||||
skillId ??= string.Empty;
|
||||
|
||||
string displayName;
|
||||
int rankMod;
|
||||
|
||||
if (_database.TryGet(skillId, out var def) && def != null)
|
||||
{
|
||||
displayName = string.IsNullOrEmpty(def.DisplayName)
|
||||
? def.Id.ToUpperInvariant()
|
||||
: def.DisplayName;
|
||||
rankMod = _skills.GetRank(skillId);
|
||||
}
|
||||
else
|
||||
{
|
||||
displayName = skillId.ToUpperInvariant();
|
||||
rankMod = 0;
|
||||
}
|
||||
|
||||
var roll = _rolls.Roll(20);
|
||||
var stateMod = _state.TotalFor(skillId);
|
||||
var total = roll + rankMod + situationalMod + stateMod;
|
||||
|
||||
bool success;
|
||||
SkillCheckDegree degree;
|
||||
|
||||
if (roll == 1)
|
||||
{
|
||||
success = false;
|
||||
degree = SkillCheckDegree.CriticalFailure;
|
||||
}
|
||||
else if (roll == 20)
|
||||
{
|
||||
success = true;
|
||||
degree = SkillCheckDegree.CriticalSuccess;
|
||||
}
|
||||
else
|
||||
{
|
||||
success = total >= dc;
|
||||
degree = success ? SkillCheckDegree.Success : SkillCheckDegree.Failure;
|
||||
}
|
||||
|
||||
return new SkillCheckResult(
|
||||
skillId,
|
||||
displayName,
|
||||
roll,
|
||||
rankMod,
|
||||
situationalMod,
|
||||
stateMod,
|
||||
total,
|
||||
dc,
|
||||
success,
|
||||
degree);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83a2adbf493a4d6eb4f5911582cc15ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
using UnityEngine;
|
||||
using Yarn.Unity;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Composition root and singleton that static Yarn commands route through.
|
||||
/// </summary>
|
||||
public sealed class SkillRuntime : MonoBehaviour
|
||||
{
|
||||
public static SkillRuntime Instance { get; private set; }
|
||||
|
||||
[SerializeField] SkillDatabase database;
|
||||
[SerializeField] DialogueRunner dialogueRunner;
|
||||
[SerializeField] SkillCheckResultView resultView;
|
||||
[SerializeField] CommentarySystem commentary;
|
||||
[SerializeField] int rollSeed;
|
||||
[SerializeField] string debugContextId = "Entrance_Queue";
|
||||
|
||||
public SkillDatabase Database => database;
|
||||
public DialogueRunner DialogueRunner => dialogueRunner;
|
||||
public SkillCheckResultView ResultView => resultView;
|
||||
public CommentarySystem Commentary => commentary;
|
||||
public PlayerSkills Skills { get; private set; }
|
||||
public SkillStateModifiers States { get; private set; }
|
||||
public SkillCheckSystem Checks { get; private set; }
|
||||
public IRollSource Rolls { get; private set; }
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Debug.LogError($"[{nameof(SkillRuntime)}] Duplicate instance on '{name}'. Destroying this component.", this);
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
|
||||
if (database == null)
|
||||
{
|
||||
Debug.LogError($"[{nameof(SkillRuntime)}] SkillDatabase is not assigned.", this);
|
||||
}
|
||||
|
||||
Skills = database != null ? new PlayerSkills(database) : null;
|
||||
States = new SkillStateModifiers();
|
||||
Rolls = rollSeed != 0 ? (IRollSource)new SeededRollSource(rollSeed) : new UnityRollSource();
|
||||
|
||||
if (Skills != null)
|
||||
{
|
||||
Checks = new SkillCheckSystem(database, Skills, States, Rolls);
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (Instance == this)
|
||||
{
|
||||
Instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
[ContextMenu("Fire Commentary (debug)")]
|
||||
void FireCommentaryDebug()
|
||||
{
|
||||
if (commentary == null)
|
||||
{
|
||||
Debug.LogWarning($"[{nameof(SkillRuntime)}] No CommentarySystem assigned.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
commentary.RequestCommentary(debugContextId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb04a8b1bce645fba398b22bbb4db58a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Transient state modifiers (third of three modifier sources). Pure state — no check math.
|
||||
/// Use skillId "*" to apply to every skill.
|
||||
/// </summary>
|
||||
public sealed class SkillStateModifiers
|
||||
{
|
||||
readonly Dictionary<string, Dictionary<string, int>> _bySource =
|
||||
new Dictionary<string, Dictionary<string, int>>(StringComparer.Ordinal);
|
||||
|
||||
public void Set(string sourceKey, string skillId, int delta)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sourceKey))
|
||||
{
|
||||
throw new ArgumentException("Source key is required.", nameof(sourceKey));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(skillId))
|
||||
{
|
||||
throw new ArgumentException("Skill id is required.", nameof(skillId));
|
||||
}
|
||||
|
||||
if (!_bySource.TryGetValue(sourceKey, out var map))
|
||||
{
|
||||
map = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
_bySource[sourceKey] = map;
|
||||
}
|
||||
|
||||
map[skillId] = delta;
|
||||
}
|
||||
|
||||
public void Clear(string sourceKey)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sourceKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_bySource.Remove(sourceKey);
|
||||
}
|
||||
|
||||
public int TotalFor(string skillId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(skillId))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var total = 0;
|
||||
foreach (var map in _bySource.Values)
|
||||
{
|
||||
if (map.TryGetValue(skillId, out var specific))
|
||||
{
|
||||
total += specific;
|
||||
}
|
||||
|
||||
if (map.TryGetValue("*", out var wildcard))
|
||||
{
|
||||
total += wildcard;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02f6b5bf30ad4f8db0711ed25b083247
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
public sealed class UnityRollSource : IRollSource
|
||||
{
|
||||
public int Roll(int sides)
|
||||
{
|
||||
if (sides < 1)
|
||||
{
|
||||
throw new System.ArgumentOutOfRangeException(nameof(sides));
|
||||
}
|
||||
|
||||
return Random.Range(1, sides + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54552e499883406b9118fd2ba28e08ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bdc1a9f9c02f400fa01d8341f81dc1c3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Collections;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Transparent roll banner. Must show roll, each of the three modifiers, total, and DC.
|
||||
/// </summary>
|
||||
public sealed class SkillCheckResultView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] CanvasGroup group;
|
||||
[SerializeField] TMP_Text label;
|
||||
[SerializeField] float holdSeconds = 1.6f;
|
||||
[SerializeField] float fadeSeconds = 0.15f;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (group != null)
|
||||
{
|
||||
group.alpha = 0f;
|
||||
group.blocksRaycasts = false;
|
||||
group.interactable = false;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator Show(SkillCheckResult result)
|
||||
{
|
||||
if (label != null)
|
||||
{
|
||||
label.text = Format(result);
|
||||
label.color = ResolveAccent(result.SkillId);
|
||||
}
|
||||
|
||||
if (group == null)
|
||||
{
|
||||
yield return new WaitForSeconds(holdSeconds);
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return Fade(0f, 1f);
|
||||
yield return new WaitForSeconds(holdSeconds);
|
||||
yield return Fade(1f, 0f);
|
||||
}
|
||||
|
||||
static Color ResolveAccent(string skillId)
|
||||
{
|
||||
var runtime = SkillRuntime.Instance;
|
||||
if (runtime != null &&
|
||||
runtime.Database != null &&
|
||||
runtime.Database.TryGet(skillId, out var def) &&
|
||||
def != null)
|
||||
{
|
||||
return def.AccentColor;
|
||||
}
|
||||
|
||||
return Color.white;
|
||||
}
|
||||
|
||||
static string Format(SkillCheckResult r)
|
||||
{
|
||||
var outcome = r.Degree switch
|
||||
{
|
||||
SkillCheckDegree.CriticalSuccess => "CRITICAL SUCCESS",
|
||||
SkillCheckDegree.CriticalFailure => "CRITICAL FAILURE",
|
||||
SkillCheckDegree.Success => "SUCCESS",
|
||||
_ => "FAILURE",
|
||||
};
|
||||
|
||||
var name = string.IsNullOrEmpty(r.SkillDisplayName)
|
||||
? (r.SkillId ?? string.Empty).ToUpperInvariant()
|
||||
: r.SkillDisplayName.ToUpperInvariant();
|
||||
|
||||
return $"{name} — d20 {r.Roll} {Signed(r.RankMod)} rank {Signed(r.SituationalMod)} situation {Signed(r.StateMod)} state = {r.Total} vs DC {r.DC} · {outcome}";
|
||||
}
|
||||
|
||||
static string Signed(int value) => value >= 0 ? $"+{value}" : value.ToString();
|
||||
|
||||
IEnumerator Fade(float from, float to)
|
||||
{
|
||||
if (fadeSeconds <= 0f)
|
||||
{
|
||||
group.alpha = to;
|
||||
yield break;
|
||||
}
|
||||
|
||||
var elapsed = 0f;
|
||||
while (elapsed < fadeSeconds)
|
||||
{
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
group.alpha = Mathf.Lerp(from, to, Mathf.Clamp01(elapsed / fadeSeconds));
|
||||
yield return null;
|
||||
}
|
||||
|
||||
group.alpha = to;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af3c81416f5f4a869baa347fd9dee9e3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5b25da74b134d59828005e34c846be0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using Yarn.Unity;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Static Yarn adapter. No MonoBehaviour, no check math — translation only.
|
||||
/// Call sites: <<check persuasion standard>> / <<check intimidation hard -1>>
|
||||
/// </summary>
|
||||
public static class YarnSkillCommands
|
||||
{
|
||||
[YarnCommand("check")]
|
||||
public static IEnumerator Check(string skillId, string difficulty, int modifier = 0)
|
||||
{
|
||||
var runtime = SkillRuntime.Instance;
|
||||
if (runtime == null || runtime.Checks == null || runtime.DialogueRunner == null)
|
||||
{
|
||||
Debug.LogError("[YarnSkillCommands] <<check>> called but SkillRuntime is not ready.");
|
||||
var fallbackRunner = runtime != null ? runtime.DialogueRunner : null;
|
||||
if (fallbackRunner == null)
|
||||
{
|
||||
fallbackRunner = Object.FindFirstObjectByType<DialogueRunner>();
|
||||
}
|
||||
WriteFailedResult(fallbackRunner, skillId, difficulty);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!DcBands.TryParse(difficulty, out var dc))
|
||||
{
|
||||
Debug.LogError($"[YarnSkillCommands] <<check {skillId} {difficulty}>>: unknown difficulty; treating as Standard (11).");
|
||||
dc = (int)DcBand.Standard;
|
||||
}
|
||||
|
||||
if (runtime.Database == null || !runtime.Database.TryGet(skillId, out _))
|
||||
{
|
||||
Debug.LogError($"[YarnSkillCommands] <<check>>: no skill '{skillId}'.");
|
||||
}
|
||||
|
||||
var result = runtime.Checks.Resolve(skillId, dc, modifier);
|
||||
WriteResult(runtime.DialogueRunner.VariableStorage, result);
|
||||
|
||||
if (runtime.ResultView == null)
|
||||
{
|
||||
Debug.LogWarning("[YarnSkillCommands] SkillCheckResultView is not assigned; skipping roll banner.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return runtime.StartCoroutine(runtime.ResultView.Show(result));
|
||||
}
|
||||
|
||||
[YarnFunction("skill_rank")]
|
||||
public static float SkillRank(string skillId)
|
||||
{
|
||||
var runtime = SkillRuntime.Instance;
|
||||
if (runtime == null || runtime.Skills == null)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
return runtime.Skills.GetRank(skillId);
|
||||
}
|
||||
|
||||
static void WriteResult(VariableStorageBehaviour storage, SkillCheckResult result)
|
||||
{
|
||||
if (storage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
storage.SetValue("$check_result", result.Success);
|
||||
storage.SetValue("$check_roll", (float)result.Roll);
|
||||
storage.SetValue("$check_total", (float)result.Total);
|
||||
storage.SetValue("$check_dc", (float)result.DC);
|
||||
storage.SetValue("$check_modifier", (float)(result.RankMod + result.SituationalMod + result.StateMod));
|
||||
storage.SetValue("$check_degree", DegreeToString(result.Degree));
|
||||
storage.SetValue("$check_skill", result.SkillId ?? string.Empty);
|
||||
}
|
||||
|
||||
static void WriteFailedResult(DialogueRunner runner, string skillId, string difficulty)
|
||||
{
|
||||
if (runner == null || runner.VariableStorage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DcBands.TryParse(difficulty, out var dc);
|
||||
if (dc == 0)
|
||||
{
|
||||
dc = (int)DcBand.Standard;
|
||||
}
|
||||
|
||||
var storage = runner.VariableStorage;
|
||||
storage.SetValue("$check_result", false);
|
||||
storage.SetValue("$check_roll", 0f);
|
||||
storage.SetValue("$check_total", 0f);
|
||||
storage.SetValue("$check_dc", (float)dc);
|
||||
storage.SetValue("$check_modifier", 0f);
|
||||
storage.SetValue("$check_degree", "failure");
|
||||
storage.SetValue("$check_skill", skillId ?? string.Empty);
|
||||
}
|
||||
|
||||
static string DegreeToString(SkillCheckDegree degree)
|
||||
{
|
||||
return degree switch
|
||||
{
|
||||
SkillCheckDegree.CriticalFailure => "critical_failure",
|
||||
SkillCheckDegree.Failure => "failure",
|
||||
SkillCheckDegree.Success => "success",
|
||||
SkillCheckDegree.CriticalSuccess => "critical_success",
|
||||
_ => "failure",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c00579d400d44ac85b16d9b27be7a87
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89b18dcb780d45cc94f8de93f4ba62d4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa1191ce507748a7aea886e539eb9d27
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9a26230b6f20436891aa772e8f858f3f, type: 3}
|
||||
m_Name: deduction
|
||||
m_EditorClassIdentifier:
|
||||
id: deduction
|
||||
displayName: Deduction
|
||||
domain: 0
|
||||
description: "Placeholder skill \u2014 replace when Skill_System_Draft.md is available."
|
||||
startingRank: 1
|
||||
accentColor: {r: 0.55, g: 0.75, b: 1, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21592f6cf7a6457a8198553ab124d41b
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9a26230b6f20436891aa772e8f858f3f, type: 3}
|
||||
m_Name: endurance
|
||||
m_EditorClassIdentifier:
|
||||
id: endurance
|
||||
displayName: Endurance
|
||||
domain: 3
|
||||
description: "Placeholder skill \u2014 replace when Skill_System_Draft.md is available."
|
||||
startingRank: 1
|
||||
accentColor: {r: 0.85, g: 0.7, b: 0.5, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84586fcc502c42889a44dd32ed2a2f1d
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9a26230b6f20436891aa772e8f858f3f, type: 3}
|
||||
m_Name: intimidation
|
||||
m_EditorClassIdentifier:
|
||||
id: intimidation
|
||||
displayName: Intimidation
|
||||
domain: 2
|
||||
description: "Placeholder skill \u2014 replace when Skill_System_Draft.md is available."
|
||||
startingRank: 2
|
||||
accentColor: {r: 1, g: 0.4, b: 0.4, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 254c6612b22d44e7b3552a8aac9311be
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9a26230b6f20436891aa772e8f858f3f, type: 3}
|
||||
m_Name: nerve
|
||||
m_EditorClassIdentifier:
|
||||
id: nerve
|
||||
displayName: Nerve
|
||||
domain: 3
|
||||
description: "Placeholder skill \u2014 replace when Skill_System_Draft.md is available."
|
||||
startingRank: 1
|
||||
accentColor: {r: 0.95, g: 0.6, b: 0.35, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee266337568a41db9aca1f2af43ec466
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9a26230b6f20436891aa772e8f858f3f, type: 3}
|
||||
m_Name: paranoia
|
||||
m_EditorClassIdentifier:
|
||||
id: paranoia
|
||||
displayName: Paranoia
|
||||
domain: 1
|
||||
description: "Placeholder skill \u2014 replace when Skill_System_Draft.md is available."
|
||||
startingRank: 1
|
||||
accentColor: {r: 1, g: 0.45, b: 0.55, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9de7affe9fa49f8a1f3bb64a87d817f
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9a26230b6f20436891aa772e8f858f3f, type: 3}
|
||||
m_Name: persuasion
|
||||
m_EditorClassIdentifier:
|
||||
id: persuasion
|
||||
displayName: Persuasion
|
||||
domain: 2
|
||||
description: "Placeholder skill \u2014 replace when Skill_System_Draft.md is available."
|
||||
startingRank: 1
|
||||
accentColor: {r: 0.85, g: 0.65, b: 1, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f510b7fc944e4c1190c35a5f82a52650
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9a26230b6f20436891aa772e8f858f3f, type: 3}
|
||||
m_Name: rapport
|
||||
m_EditorClassIdentifier:
|
||||
id: rapport
|
||||
displayName: Rapport
|
||||
domain: 2
|
||||
description: "Placeholder skill \u2014 replace when Skill_System_Draft.md is available."
|
||||
startingRank: 1
|
||||
accentColor: {r: 0.55, g: 0.95, b: 0.85, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb856a5941c0497b8eb7796793c617ba
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9a26230b6f20436891aa772e8f858f3f, type: 3}
|
||||
m_Name: read_room
|
||||
m_EditorClassIdentifier:
|
||||
id: read_room
|
||||
displayName: Read the Room
|
||||
domain: 1
|
||||
description: "Placeholder skill \u2014 replace when Skill_System_Draft.md is available."
|
||||
startingRank: 1
|
||||
accentColor: {r: 1, g: 0.75, b: 0.45, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abfd9204c20644c090c30bd6404c45a6
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9a26230b6f20436891aa772e8f858f3f, type: 3}
|
||||
m_Name: recall
|
||||
m_EditorClassIdentifier:
|
||||
id: recall
|
||||
displayName: Recall
|
||||
domain: 0
|
||||
description: "Placeholder skill \u2014 replace when Skill_System_Draft.md is available."
|
||||
startingRank: 1
|
||||
accentColor: {r: 0.65, g: 0.7, b: 1, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c675bc7c6c864e6e9e4f71394ba1f07f
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9a26230b6f20436891aa772e8f858f3f, type: 3}
|
||||
m_Name: reflexes
|
||||
m_EditorClassIdentifier:
|
||||
id: reflexes
|
||||
displayName: Reflexes
|
||||
domain: 1
|
||||
description: "Placeholder skill \u2014 replace when Skill_System_Draft.md is available."
|
||||
startingRank: 1
|
||||
accentColor: {r: 1, g: 0.9, b: 0.4, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78d1f1a41b2b48358f889935c3e3094c
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9a26230b6f20436891aa772e8f858f3f, type: 3}
|
||||
m_Name: sleight_of_hand
|
||||
m_EditorClassIdentifier:
|
||||
id: sleight_of_hand
|
||||
displayName: Sleight of Hand
|
||||
domain: 3
|
||||
description: "Placeholder skill \u2014 replace when Skill_System_Draft.md is available."
|
||||
startingRank: 1
|
||||
accentColor: {r: 0.7, g: 0.9, b: 0.95, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60239bdfcace42f89d3ff82e383e98b8
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9a26230b6f20436891aa772e8f858f3f, type: 3}
|
||||
m_Name: streetwise
|
||||
m_EditorClassIdentifier:
|
||||
id: streetwise
|
||||
displayName: Streetwise
|
||||
domain: 0
|
||||
description: "Placeholder skill \u2014 replace when Skill_System_Draft.md is available."
|
||||
startingRank: 1
|
||||
accentColor: {r: 0.7, g: 0.85, b: 0.55, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 910cd1397efb4e23bca22bbe6c3c6971
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 73f6e7831a60457b918d303c16fc6cec, type: 3}
|
||||
m_Name: SkillDatabase
|
||||
m_EditorClassIdentifier:
|
||||
skills:
|
||||
- {fileID: 11400000, guid: 21592f6cf7a6457a8198553ab124d41b, type: 2}
|
||||
- {fileID: 11400000, guid: 910cd1397efb4e23bca22bbe6c3c6971, type: 2}
|
||||
- {fileID: 11400000, guid: c675bc7c6c864e6e9e4f71394ba1f07f, type: 2}
|
||||
- {fileID: 11400000, guid: abfd9204c20644c090c30bd6404c45a6, type: 2}
|
||||
- {fileID: 11400000, guid: b9de7affe9fa49f8a1f3bb64a87d817f, type: 2}
|
||||
- {fileID: 11400000, guid: 78d1f1a41b2b48358f889935c3e3094c, type: 2}
|
||||
- {fileID: 11400000, guid: f510b7fc944e4c1190c35a5f82a52650, type: 2}
|
||||
- {fileID: 11400000, guid: 254c6612b22d44e7b3552a8aac9311be, type: 2}
|
||||
- {fileID: 11400000, guid: eb856a5941c0497b8eb7796793c617ba, type: 2}
|
||||
- {fileID: 11400000, guid: 84586fcc502c42889a44dd32ed2a2f1d, type: 2}
|
||||
- {fileID: 11400000, guid: 60239bdfcace42f89d3ff82e383e98b8, type: 2}
|
||||
- {fileID: 11400000, guid: ee266337568a41db9aca1f2af43ec466, type: 2}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d15ae35514b14ded9b398d630e8ef373
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40490aad9dd94cdca1d9170d5f729cba
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c70934ddbc404dfcb0952241bec17b6c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,90 @@
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using NightclubArcadia.Skills;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NightclubArcadia.Skills.Tests
|
||||
{
|
||||
public class CommentaryPickerTests
|
||||
{
|
||||
sealed class FixedRollSource : IRollSource
|
||||
{
|
||||
readonly int _value;
|
||||
public FixedRollSource(int value) => _value = value;
|
||||
public int Roll(int sides) => _value;
|
||||
}
|
||||
|
||||
SkillDefinition Make(string id, int startingRank)
|
||||
{
|
||||
var def = ScriptableObject.CreateInstance<SkillDefinition>();
|
||||
def.EditorSet(id, id, SkillDomain.Instinct, string.Empty, startingRank, Color.white);
|
||||
return def;
|
||||
}
|
||||
|
||||
PlayerSkills SkillsFor(params (string id, int rank)[] ranks)
|
||||
{
|
||||
var db = ScriptableObject.CreateInstance<SkillDatabase>();
|
||||
var list = new List<SkillDefinition>();
|
||||
foreach (var (id, rank) in ranks)
|
||||
{
|
||||
list.Add(Make(id, rank));
|
||||
}
|
||||
|
||||
db.EditorSetSkills(list);
|
||||
return new PlayerSkills(db);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pick_EmptyCandidates_ReturnsNull()
|
||||
{
|
||||
var skills = SkillsFor(("paranoia", 2));
|
||||
var result = CommentarySystem.Pick(
|
||||
new List<SkillDefinition>(),
|
||||
skills,
|
||||
new List<string>(),
|
||||
3,
|
||||
new FixedRollSource(1));
|
||||
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pick_HigherRankPreferred_GivenLowRoll()
|
||||
{
|
||||
// ranks 1 and 3 → weights 1 and 3 → total 4
|
||||
// roll 1 maps to pick index 0 of cumulative: only weight-1 skill
|
||||
// roll 2..4 maps to higher-ranked skill
|
||||
var low = Make("read_room", 1);
|
||||
var high = Make("paranoia", 3);
|
||||
var skills = SkillsFor(("read_room", 1), ("paranoia", 3));
|
||||
|
||||
var highPick = CommentarySystem.Pick(
|
||||
new List<SkillDefinition> { low, high },
|
||||
skills,
|
||||
new List<string>(),
|
||||
3,
|
||||
new FixedRollSource(2));
|
||||
|
||||
Assert.AreEqual("paranoia", highPick.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pick_RecentSpeaker_HasHalvedWeight()
|
||||
{
|
||||
// Without recency: weights 2 and 2, total 4
|
||||
// With "a" recent: weights 1 and 2, total 3 → roll 1 picks a, roll 2+ picks b
|
||||
var a = Make("a", 2);
|
||||
var b = Make("b", 2);
|
||||
var skills = SkillsFor(("a", 2), ("b", 2));
|
||||
|
||||
var pick = CommentarySystem.Pick(
|
||||
new List<SkillDefinition> { a, b },
|
||||
skills,
|
||||
new List<string> { "a" },
|
||||
3,
|
||||
new FixedRollSource(2));
|
||||
|
||||
Assert.AreEqual("b", pick.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ba3f2a4a2a44285acd610ab51884753
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
using NUnit.Framework;
|
||||
using NightclubArcadia.Skills;
|
||||
|
||||
namespace NightclubArcadia.Skills.Tests
|
||||
{
|
||||
public class DcBandTests
|
||||
{
|
||||
[TestCase("standard", 11)]
|
||||
[TestCase("STANDARD", 11)]
|
||||
[TestCase("13", 13)]
|
||||
[TestCase("-3", -3)]
|
||||
public void TryParse_AcceptsBandNamesAndIntegers(string input, int expected)
|
||||
{
|
||||
Assert.IsTrue(DcBands.TryParse(input, out var dc));
|
||||
Assert.AreEqual(expected, dc);
|
||||
}
|
||||
|
||||
[TestCase("banana")]
|
||||
[TestCase("")]
|
||||
[TestCase(null)]
|
||||
public void TryParse_RejectsGarbage(string input)
|
||||
{
|
||||
Assert.IsFalse(DcBands.TryParse(input, out _));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9057738b1d8f402d907c1d9dab8d82a2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "NightclubArcadia.Skills.Tests",
|
||||
"rootNamespace": "NightclubArcadia.Skills.Tests",
|
||||
"references": [
|
||||
"NightclubArcadia.Skills",
|
||||
"UnityEngine.TestRunner",
|
||||
"UnityEditor.TestRunner"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"nunit.framework.dll"
|
||||
],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09d3d01ec5554542b5dd23239ace7a70
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,137 @@
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using NightclubArcadia.Skills;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NightclubArcadia.Skills.Tests
|
||||
{
|
||||
public class SkillCheckSystemTests
|
||||
{
|
||||
sealed class FixedRollSource : IRollSource
|
||||
{
|
||||
readonly int _value;
|
||||
public FixedRollSource(int value) => _value = value;
|
||||
public int Roll(int sides) => _value;
|
||||
}
|
||||
|
||||
SkillDatabase CreateDatabase(params (string id, string display, int rank)[] skills)
|
||||
{
|
||||
var db = ScriptableObject.CreateInstance<SkillDatabase>();
|
||||
var list = new List<SkillDefinition>();
|
||||
foreach (var (id, display, rank) in skills)
|
||||
{
|
||||
var def = ScriptableObject.CreateInstance<SkillDefinition>();
|
||||
def.EditorSet(id, display, SkillDomain.Presence, string.Empty, rank, Color.white);
|
||||
list.Add(def);
|
||||
}
|
||||
|
||||
db.EditorSetSkills(list);
|
||||
return db;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_SumsModifiers_AndSucceedsWhenTotalMeetsDc()
|
||||
{
|
||||
var db = CreateDatabase(("persuasion", "Persuasion", 2));
|
||||
var skills = new PlayerSkills(db);
|
||||
var state = new SkillStateModifiers();
|
||||
var system = new SkillCheckSystem(db, skills, state, new FixedRollSource(10));
|
||||
|
||||
var result = system.Resolve("persuasion", 12, 1);
|
||||
|
||||
Assert.AreEqual(13, result.Total);
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(SkillCheckDegree.Success, result.Degree);
|
||||
Assert.AreEqual(2, result.RankMod);
|
||||
Assert.AreEqual(1, result.SituationalMod);
|
||||
Assert.AreEqual(0, result.StateMod);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_NaturalOne_IsCriticalFailureEvenIfModifiersWouldPass()
|
||||
{
|
||||
var db = CreateDatabase(("persuasion", "Persuasion", 10));
|
||||
var skills = new PlayerSkills(db);
|
||||
var state = new SkillStateModifiers();
|
||||
var system = new SkillCheckSystem(db, skills, state, new FixedRollSource(1));
|
||||
|
||||
var result = system.Resolve("persuasion", 5, 5);
|
||||
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(SkillCheckDegree.CriticalFailure, result.Degree);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_NaturalTwenty_IsCriticalSuccessEvenIfBelowDc()
|
||||
{
|
||||
var db = CreateDatabase(("persuasion", "Persuasion", 0));
|
||||
var skills = new PlayerSkills(db);
|
||||
var state = new SkillStateModifiers();
|
||||
var system = new SkillCheckSystem(db, skills, state, new FixedRollSource(20));
|
||||
|
||||
var result = system.Resolve("persuasion", 25, 0);
|
||||
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(SkillCheckDegree.CriticalSuccess, result.Degree);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_ExactDc_IsSuccess()
|
||||
{
|
||||
var db = CreateDatabase(("persuasion", "Persuasion", 1));
|
||||
var skills = new PlayerSkills(db);
|
||||
var state = new SkillStateModifiers();
|
||||
var system = new SkillCheckSystem(db, skills, state, new FixedRollSource(10));
|
||||
|
||||
var result = system.Resolve("persuasion", 11, 0);
|
||||
|
||||
Assert.AreEqual(11, result.Total);
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_AppliesNegativeAndWildcardStateModifiers()
|
||||
{
|
||||
var db = CreateDatabase(("reflexes", "Reflexes", 1));
|
||||
var skills = new PlayerSkills(db);
|
||||
var state = new SkillStateModifiers();
|
||||
state.Set("drunk", "reflexes", -2);
|
||||
state.Set("crowd", "*", -1);
|
||||
var system = new SkillCheckSystem(db, skills, state, new FixedRollSource(10));
|
||||
|
||||
var result = system.Resolve("reflexes", 5, 0);
|
||||
|
||||
Assert.AreEqual(-3, result.StateMod);
|
||||
Assert.AreEqual(8, result.Total);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_UnknownSkill_DoesNotThrow_AndUsesZeroRank()
|
||||
{
|
||||
var db = CreateDatabase(("persuasion", "Persuasion", 3));
|
||||
var skills = new PlayerSkills(db);
|
||||
var state = new SkillStateModifiers();
|
||||
var system = new SkillCheckSystem(db, skills, state, new FixedRollSource(10));
|
||||
|
||||
SkillCheckResult result = default;
|
||||
Assert.DoesNotThrow(() => result = system.Resolve("nosuchskill", 10, 0));
|
||||
Assert.AreEqual(0, result.RankMod);
|
||||
Assert.AreEqual("NOSUCHSKILL", result.SkillDisplayName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Resolve_DoesNotMutatePlayerSkills()
|
||||
{
|
||||
var db = CreateDatabase(("persuasion", "Persuasion", 2));
|
||||
var skills = new PlayerSkills(db);
|
||||
var before = skills.GetRank("persuasion");
|
||||
var state = new SkillStateModifiers();
|
||||
var system = new SkillCheckSystem(db, skills, state, new FixedRollSource(10));
|
||||
|
||||
system.Resolve("persuasion", 10, 0);
|
||||
|
||||
Assert.AreEqual(before, skills.GetRank("persuasion"));
|
||||
Assert.AreEqual(1, skills.AllRanks.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78734080b9b5473db4365b8203fc79b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user