using AcDream.Content.CharGen;
using AcDream.Core.CharGen;
using DatReaderWriter;
using DatReaderWriter.Options;
namespace AcDream.Content.Tests.CharGen;
///
/// Installed-DAT gate for : proves the real
/// CharGen table (portal.dat 0x0E000002) loads through the SAME
/// production uses and lands in a
/// plausible shape. Env-gated like the rest of Content.Tests' installed-DAT
/// suite () — skips
/// cleanly (with a console note) when no DAT directory is configured,
/// matching e.g. PakEquivalenceTests / RetailDatLoaderTests.
///
public sealed class ChargenTableReaderInstalledDatTests
{
// ACE ACE.Entity.Enum.HeritageGroup — the four heritages CC1's spec
// calls out by name.
private const uint AluvianId = 1u;
private const uint GharundimId = 2u;
private const uint ShoId = 3u;
private const uint ViamontianId = 4u;
private const uint OlthoiId = 12u;
private const uint OlthoiAcidId = 13u;
[Fact]
public void InstalledCharGenTable_LoadsAndHasThirteenHeritageGroups()
{
string? datDir = ContentConformanceDats.ResolveDatDir();
if (datDir is null)
{
Console.WriteLine("SKIP: installed retail DAT directory is unavailable.");
return;
}
using var dats = new DatCollection(datDir, DatAccessType.Read);
using var adapter = new DatCollectionAdapter(dats);
ChargenOptions options = ChargenTableReader.Load(adapter);
// ACE's loader comment: "11 standard player races and 2 Olthoi".
Assert.Equal(13, options.HeritagesById.Count);
Assert.NotEmpty(options.StarterAreas);
}
[Fact]
public void InstalledCharGenTable_HasTheFourNamedHeritagesWithRetailDisplayNames()
{
string? datDir = ContentConformanceDats.ResolveDatDir();
if (datDir is null)
{
Console.WriteLine("SKIP: installed retail DAT directory is unavailable.");
return;
}
using var dats = new DatCollection(datDir, DatAccessType.Read);
using var adapter = new DatCollectionAdapter(dats);
ChargenOptions options = ChargenTableReader.Load(adapter);
Assert.True(options.TryGetHeritage(AluvianId, out ChargenHeritageOptions aluvian));
Assert.Equal("Aluvian", aluvian.Name);
Assert.True(options.TryGetHeritage(GharundimId, out ChargenHeritageOptions gharundim));
Assert.Equal("Gharu'ndim", gharundim.Name);
Assert.True(options.TryGetHeritage(ShoId, out ChargenHeritageOptions sho));
Assert.Equal("Sho", sho.Name);
Assert.True(options.TryGetHeritage(ViamontianId, out ChargenHeritageOptions viamontian));
Assert.Equal("Viamontian", viamontian.Name);
Assert.True(options.TryGetHeritage(OlthoiId, out ChargenHeritageOptions olthoi));
Assert.True(olthoi.IsOlthoi);
Assert.True(options.TryGetHeritage(OlthoiAcidId, out ChargenHeritageOptions olthoiAcid));
Assert.True(olthoiAcid.IsOlthoi);
}
[Fact]
public void InstalledHeritages_EachHasAtLeastOneGenderWithNonEmptyAppearanceOptions()
{
string? datDir = ContentConformanceDats.ResolveDatDir();
if (datDir is null)
{
Console.WriteLine("SKIP: installed retail DAT directory is unavailable.");
return;
}
using var dats = new DatCollection(datDir, DatAccessType.Read);
using var adapter = new DatCollectionAdapter(dats);
ChargenOptions options = ChargenTableReader.Load(adapter);
Assert.NotEmpty(options.HeritagesById);
foreach (ChargenHeritageOptions heritage in options.HeritagesById.Values)
{
Assert.NotEmpty(heritage.GendersByKey);
Assert.Contains(
heritage.GendersByKey.Values,
gender => gender.HasAnyAppearanceOptions);
}
}
///
/// Every template's six attributes fall within retail's 10..100 range,
/// and no template's total exceeds its heritage's AttributeCredits
/// budget. NOT every template fully spends the budget — each of the
/// four human heritages (and Olthoi Acid) ships an "Adventurer" template
/// sitting at the floor (60 of a 330 budget; confirmed against the
/// installed DAT), which is retail's "Custom" starting point delivered
/// as a real TemplateCG entry rather than a special-cased UI-only
/// option. Every OTHER named human template (Bow Hunter, Swashbuckler,
/// Life Caster, War Mage, Wayfarer, Soldier) exactly exhausts its
/// heritage's credits, and Olthoi's single "Ripper" template exactly
/// exhausts its (much smaller, non-customizable) 60-credit budget — so
/// SOME template somewhere fully spends its budget, even though no
/// single heritage is guaranteed to (Olthoi Acid's only template is the
/// unspent "Adventurer" one).
///
[Fact]
public void InstalledHeritages_EveryTemplateAttributeSpreadFitsTheAttributeBudget()
{
string? datDir = ContentConformanceDats.ResolveDatDir();
if (datDir is null)
{
Console.WriteLine("SKIP: installed retail DAT directory is unavailable.");
return;
}
using var dats = new DatCollection(datDir, DatAccessType.Read);
using var adapter = new DatCollectionAdapter(dats);
ChargenOptions options = ChargenTableReader.Load(adapter);
int templatesChecked = 0;
bool anyFullySpentAnywhere = false;
foreach (ChargenHeritageOptions heritage in options.HeritagesById.Values)
{
foreach (ChargenTemplate template in heritage.Templates)
{
templatesChecked++;
Assert.True(
ChargenAttributeMath.AreAllWithinRange(template.Attributes),
$"{heritage.Name}/{template.Name}: an attribute fell outside " +
$"{ChargenAttributeMath.AttributeMin}..{ChargenAttributeMath.AttributeMax} " +
$"({template.Attributes}).");
Assert.True(
template.Attributes.Total <= heritage.AttributeCredits,
$"{heritage.Name}/{template.Name}: attribute spread totals " +
$"{template.Attributes.Total}, exceeding the {heritage.AttributeCredits}-credit budget.");
anyFullySpentAnywhere |= ChargenAttributeMath.IsFullySpent(heritage.AttributeCredits, template.Attributes);
}
}
Assert.True(templatesChecked > 0, "Expected at least one profession template across all heritages.");
Assert.True(
anyFullySpentAnywhere,
"Expected at least one named preset template to fully spend its heritage's credit budget.");
}
[Fact]
public void InstalledHeritages_PrimaryAndSecondaryStartAreaIndicesResolveIntoTheSharedList()
{
string? datDir = ContentConformanceDats.ResolveDatDir();
if (datDir is null)
{
Console.WriteLine("SKIP: installed retail DAT directory is unavailable.");
return;
}
using var dats = new DatCollection(datDir, DatAccessType.Read);
using var adapter = new DatCollectionAdapter(dats);
ChargenOptions options = ChargenTableReader.Load(adapter);
int indicesChecked = 0;
foreach (ChargenHeritageOptions heritage in options.HeritagesById.Values)
{
foreach (int index in heritage.PrimaryStartAreaIndices.Concat(heritage.SecondaryStartAreaIndices))
{
indicesChecked++;
Assert.True(
options.TryGetStarterArea(index, out ChargenStarterArea area),
$"{heritage.Name}: start-area index {index} does not resolve into the shared StarterAreas list.");
Assert.False(string.IsNullOrEmpty(area.Name));
}
}
Assert.True(indicesChecked > 0, "Expected at least one heritage start-area reference.");
}
[Fact]
public void InstalledHeritages_SkillCostsResolveToKnownWireSkillIds()
{
string? datDir = ContentConformanceDats.ResolveDatDir();
if (datDir is null)
{
Console.WriteLine("SKIP: installed retail DAT directory is unavailable.");
return;
}
using var dats = new DatCollection(datDir, DatAccessType.Read);
using var adapter = new DatCollectionAdapter(dats);
ChargenOptions options = ChargenTableReader.Load(adapter);
Assert.True(options.TryGetHeritage(AluvianId, out ChargenHeritageOptions aluvian));
Assert.NotEmpty(aluvian.SkillCostsBySkillId);
foreach (var pair in aluvian.SkillCostsBySkillId)
{
Assert.Equal(pair.Key, pair.Value.SkillId);
Assert.InRange(pair.Key, 1u, 54u);
Assert.True(pair.Value.NormalCost >= 0);
Assert.True(pair.Value.PrimaryCost >= 0);
}
}
}