refactor the skill system to adhere to the lore handbook
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
@@ -7,6 +8,8 @@ namespace NightclubArcadia.Skills
|
||||
[CreateAssetMenu(menuName = "Nightclub Arcadia/Skill Database", fileName = "SkillDatabase")]
|
||||
public sealed class SkillDatabase : ScriptableObject
|
||||
{
|
||||
static readonly Regex IdPattern = new Regex(@"^[a-z][a-z0-9_]*$", RegexOptions.Compiled);
|
||||
|
||||
[SerializeField] List<SkillDefinition> skills = new List<SkillDefinition>();
|
||||
|
||||
Dictionary<string, SkillDefinition> _lookup;
|
||||
@@ -58,6 +61,11 @@ namespace NightclubArcadia.Skills
|
||||
}
|
||||
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var byId = new Dictionary<string, SkillDefinition>(StringComparer.OrdinalIgnoreCase);
|
||||
var axisCounts = new Dictionary<SkillAxis, int>();
|
||||
var channelCounts = new Dictionary<TraceChannel, int>();
|
||||
var firingSum = 0f;
|
||||
|
||||
foreach (var skill in skills)
|
||||
{
|
||||
if (skill == null)
|
||||
@@ -72,10 +80,127 @@ namespace NightclubArcadia.Skills
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IdPattern.IsMatch(skill.Id))
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[{nameof(SkillDatabase)}] Skill Id '{skill.Id}' must match ^[a-z][a-z0-9_]*$.",
|
||||
this);
|
||||
}
|
||||
|
||||
if (!seen.Add(skill.Id))
|
||||
{
|
||||
Debug.LogError($"[{nameof(SkillDatabase)}] Duplicate skill Id '{skill.Id}'.", this);
|
||||
}
|
||||
else
|
||||
{
|
||||
byId[skill.Id] = skill;
|
||||
}
|
||||
|
||||
if (skill.TargetFiringsPerHour <= 0f)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[{nameof(SkillDatabase)}] Skill '{skill.Id}' has targetFiringsPerHour <= 0.",
|
||||
this);
|
||||
}
|
||||
else
|
||||
{
|
||||
firingSum += skill.TargetFiringsPerHour;
|
||||
}
|
||||
|
||||
if (!axisCounts.ContainsKey(skill.Axis))
|
||||
{
|
||||
axisCounts[skill.Axis] = 0;
|
||||
}
|
||||
|
||||
axisCounts[skill.Axis]++;
|
||||
|
||||
if (!channelCounts.ContainsKey(skill.PrimaryChannel))
|
||||
{
|
||||
channelCounts[skill.PrimaryChannel] = 0;
|
||||
}
|
||||
|
||||
channelCounts[skill.PrimaryChannel]++;
|
||||
}
|
||||
|
||||
foreach (var skill in byId.Values)
|
||||
{
|
||||
ValidateReference(skill.OpposedTo, skill.Id, "opposedTo", byId);
|
||||
if (skill.AlliedWith != null)
|
||||
{
|
||||
foreach (var ally in skill.AlliedWith)
|
||||
{
|
||||
ValidateReference(ally, skill.Id, "alliedWith", byId);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(skill.OpposedTo) || !byId.TryGetValue(skill.OpposedTo, out var opposed))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Symmetry required only when both skills are non-Specialist.
|
||||
if (skill.Axis == SkillAxis.Specialist || opposed.Axis == SkillAxis.Specialist)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.Equals(opposed.OpposedTo, skill.Id, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[{nameof(SkillDatabase)}] opposedTo asymmetry: '{skill.Id}' → '{skill.OpposedTo}' " +
|
||||
$"but '{opposed.Id}' → '{opposed.OpposedTo}'.",
|
||||
this);
|
||||
}
|
||||
}
|
||||
|
||||
AssertAxisCount(axisCounts, SkillAxis.Reason, 2);
|
||||
AssertAxisCount(axisCounts, SkillAxis.Body, 2);
|
||||
AssertAxisCount(axisCounts, SkillAxis.Social, 2);
|
||||
AssertAxisCount(axisCounts, SkillAxis.Self, 2);
|
||||
AssertAxisCount(axisCounts, SkillAxis.Specialist, 3);
|
||||
|
||||
Debug.Log(
|
||||
$"[{nameof(SkillDatabase)}] targetFiringsPerHour sum = {firingSum} (Bible expects 71).",
|
||||
this);
|
||||
|
||||
channelCounts.TryGetValue(TraceChannel.Physical, out var physical);
|
||||
channelCounts.TryGetValue(TraceChannel.Testimonial, out var testimonial);
|
||||
channelCounts.TryGetValue(TraceChannel.Behavioural, out var behavioural);
|
||||
channelCounts.TryGetValue(TraceChannel.Documentary, out var documentary);
|
||||
Debug.Log(
|
||||
$"[{nameof(SkillDatabase)}] primary-channel tally: physical {physical} / " +
|
||||
$"testimonial {testimonial} / behavioural {behavioural} / documentary {documentary} " +
|
||||
$"(Bible expects 3 / 3 / 3 / 2).",
|
||||
this);
|
||||
}
|
||||
|
||||
void ValidateReference(
|
||||
string refId,
|
||||
string ownerId,
|
||||
string fieldName,
|
||||
Dictionary<string, SkillDefinition> byId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(refId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!byId.ContainsKey(refId))
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[{nameof(SkillDatabase)}] Skill '{ownerId}' {fieldName} '{refId}' is not in this database.",
|
||||
this);
|
||||
}
|
||||
}
|
||||
|
||||
void AssertAxisCount(Dictionary<SkillAxis, int> counts, SkillAxis axis, int expected)
|
||||
{
|
||||
counts.TryGetValue(axis, out var actual);
|
||||
if (actual != expected)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[{nameof(SkillDatabase)}] Axis {axis} has {actual} skills; expected {expected}.",
|
||||
this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user