refactor the skill system to adhere to the lore handbook

This commit is contained in:
2026-08-15 11:13:15 +02:00
parent 063e94f6eb
commit 9e5c3277bf
70 changed files with 2271 additions and 445 deletions
@@ -8,6 +8,8 @@ namespace NightclubArcadia.Skills
/// <summary>
/// Passive skill commentary outside the Yarn node tree.
/// Fires at most one speaker per RequestCommentary call.
/// Per-skill budget: 3600 / targetFiringsPerHour seconds between fires for that voice,
/// on top of the global minSecondsBetween floor.
/// </summary>
public sealed class CommentarySystem : MonoBehaviour
{
@@ -19,6 +21,8 @@ namespace NightclubArcadia.Skills
readonly List<string> _recent = new List<string>();
readonly HashSet<string> _firedContexts = new HashSet<string>(StringComparer.Ordinal);
readonly Dictionary<string, float> _lastFiredBySkill =
new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);
float _lastFiredAt = float.NegativeInfinity;
public void RequestCommentary(string contextId)
@@ -61,6 +65,7 @@ namespace NightclubArcadia.Skills
return;
}
var now = Time.time;
var candidates = new List<SkillDefinition>();
foreach (var skill in database.All)
{
@@ -74,6 +79,16 @@ namespace NightclubArcadia.Skills
continue;
}
if (!_lastFiredBySkill.TryGetValue(skill.Id, out var lastFired))
{
lastFired = float.NegativeInfinity;
}
if (!IsOffCooldown(lastFired, now, skill.TargetFiringsPerHour))
{
continue;
}
var nodeName = $"Commentary_{contextId}_{skill.Id}";
if (!NodeExists(nodeName))
{
@@ -91,7 +106,8 @@ namespace NightclubArcadia.Skills
var startNode = $"Commentary_{contextId}_{picked.Id}";
_ = dialogueRunner.StartDialogue(startNode);
_lastFiredAt = Time.time;
_lastFiredAt = now;
_lastFiredBySkill[picked.Id] = now;
_firedContexts.Add(contextId);
_recent.Add(picked.Id);
while (_recent.Count > Math.Max(1, recencyWindow))
@@ -100,6 +116,21 @@ namespace NightclubArcadia.Skills
}
}
/// <summary>
/// True when enough time has elapsed since lastFired given targetFiringsPerHour.
/// targetFiringsPerHour &lt;= 0 means the skill never fires.
/// </summary>
public static bool IsOffCooldown(float lastFired, float now, float targetPerHour)
{
if (targetPerHour <= 0f)
{
return false;
}
var minInterval = 3600f / targetPerHour;
return now - lastFired >= minInterval;
}
bool NodeExists(string nodeName)
{
if (dialogueRunner.Dialogue != null && dialogueRunner.Dialogue.NodeExists(nodeName))