namespace AcDream.Core.CharGen;
///
/// Pure port of retail's skill-credit spend calculation
/// (CharGenState::UpdateRemainingSkillCredits @ 0x005C37C0): walk
/// every skill slot, add NormalCost for Trained or PrimaryCost
/// for Specialized (never both), and subtract the total from the heritage's
/// SkillCredits budget. No DAT/DatReaderWriter dependency — callers
/// (CC3's Runtime owner) pass in the heritage's already-projected
/// lookup.
///
public static class ChargenSkillCreditMath
{
///
/// Total credits spent across every Trained/Specialized skill in
/// . A skill with no cost entry for the
/// active heritage (i.e. the heritage doesn't offer it) is skipped —
/// retail's own UI can never reach that state, so this is defensive
/// rather than a documented retail behavior.
///
public static int ComputeSpent(
ChargenSkillAdvancementSet advancement,
IReadOnlyDictionary costsBySkillId)
{
ArgumentNullException.ThrowIfNull(advancement);
ArgumentNullException.ThrowIfNull(costsBySkillId);
int spent = 0;
for (uint skillId = 1; skillId < ChargenSkillAdvancementSet.SlotCount; skillId++)
{
ChargenSkillAdvancementClass cls = advancement[skillId];
if (cls != ChargenSkillAdvancementClass.Trained
&& cls != ChargenSkillAdvancementClass.Specialized)
{
continue;
}
if (!costsBySkillId.TryGetValue(skillId, out ChargenSkillCost cost))
continue;
spent += cls == ChargenSkillAdvancementClass.Specialized
? cost.PrimaryCost
: cost.NormalCost;
}
return spent;
}
///
/// totalSkillCredits - ComputeSpent(...) — retail's
/// remainingSkillCredits. Retail's Finish gate (DoFinish @
/// 0x004E91F2-adjacent) only checks remainingAtrbCredits > 0
/// for attributes; skill credits are NOT required to hit exactly zero
/// (unspent skill credits are simply lost on creation) — callers should
/// not port an "exact spend" gate for skills the way
/// does for attributes.
///
public static int RemainingCredits(
uint totalSkillCredits,
ChargenSkillAdvancementSet advancement,
IReadOnlyDictionary costsBySkillId) =>
checked((int)totalSkillCredits) - ComputeSpent(advancement, costsBySkillId);
}