using System.Collections.Frozen;
using AcDream.Core.Items;
using AcDream.Core.Spells;
using DatReaderWriter;
using DatReaderWriter.DBObjs;
using CoreSpellTable = AcDream.Core.Spells.SpellTable;
namespace AcDream.Content;
///
/// Immutable presentation and gameplay metadata for one retail spell
/// component WCID.
///
public sealed record SpellComponentDescriptor(
uint WeenieClassId,
string Name,
uint Category,
uint IconId);
///
/// Process-shareable projection of retail's spell, component, and
/// school-focus DAT tables. The catalog contains no player, inventory,
/// session, or presentation state.
///
public sealed class MagicCatalog
{
private const uint SpellTableDid = 0x0E00000Eu;
private const uint SpellComponentTableDid = 0x0E00000Fu;
private const uint ComponentIdMapDid = 0x27000002u;
private readonly FrozenDictionary
_formulaDefinitions;
private readonly FrozenDictionary _wcidByScid;
private readonly FrozenDictionary _magicPackWcidBySchool;
private readonly FrozenDictionary _spellLevels;
private MagicCatalog(
CoreSpellTable spellTable,
FrozenDictionary components,
FrozenDictionary formulaDefinitions,
FrozenDictionary wcidByScid,
FrozenDictionary magicPackWcidBySchool,
FrozenDictionary spellLevels)
{
SpellTable = spellTable;
Components = components;
_formulaDefinitions = formulaDefinitions;
_wcidByScid = wcidByScid;
_magicPackWcidBySchool = magicPackWcidBySchool;
_spellLevels = spellLevels;
}
public static MagicCatalog Empty { get; } = new(
CoreSpellTable.Empty,
EmptyFrozen(),
EmptyFrozen(),
EmptyFrozen(),
EmptyFrozen(),
EmptyFrozen());
public CoreSpellTable SpellTable { get; }
public IReadOnlyDictionary Components
{
get;
}
public bool TryGetComponentBySpellComponentId(
uint spellComponentId,
out SpellComponentDescriptor descriptor)
{
if (_wcidByScid.TryGetValue(
spellComponentId,
out uint weenieClassId)
&& Components.TryGetValue(
weenieClassId,
out SpellComponentDescriptor? found))
{
descriptor = found;
return true;
}
descriptor = null!;
return false;
}
public bool IsComponentPack(uint weenieClassId) =>
Components.ContainsKey(weenieClassId);
public int GetSpellLevel(uint spellId) =>
_spellLevels.TryGetValue(spellId, out int level)
? level
: 0;
///
/// Creates the per-session policy view over this immutable catalog. The
/// returned service borrows only the supplied session's object table and
/// identity/account delegates.
///
public SpellComponentRequirementService CreateRequirementService(
ClientObjectTable objects,
Func playerGuid,
Func accountName) =>
new(
objects,
playerGuid,
accountName,
_formulaDefinitions,
_wcidByScid,
_magicPackWcidBySchool);
public static MagicCatalog Load(IDatReaderWriter dats)
{
ArgumentNullException.ThrowIfNull(dats);
var components =
new Dictionary();
var wcidByScid = new Dictionary();
SpellComponentTable? componentTable =
dats.Get(SpellComponentTableDid);
DualEnumIDMap? componentIds =
dats.Get(ComponentIdMapDid);
if (componentTable is not null && componentIds is not null)
{
foreach (var pair in componentTable.Components)
{
if (!componentIds.ClientEnumToID.TryGetValue(
pair.Key,
out uint wcid))
{
continue;
}
wcidByScid[pair.Key] = wcid;
components[wcid] = new SpellComponentDescriptor(
wcid,
pair.Value.Name.Value,
pair.Value.Category,
pair.Value.Icon.DataId);
}
}
var metadata = new List();
var formulaDefinitions =
new Dictionary();
var spellLevels = new Dictionary();
DatReaderWriter.DBObjs.SpellTable spellTable =
dats.Get(SpellTableDid)
?? throw new InvalidOperationException(
$"Required retail SpellTable 0x{SpellTableDid:X8} is missing from portal.dat.");
foreach (var pair in spellTable.Spells)
{
SpellMetadata spell = RetailSpellMetadataProjector.Project(
pair.Key,
pair.Value,
componentTable);
metadata.Add(spell);
uint[] formula = spell.FormulaComponents.ToArray();
formulaDefinitions[pair.Key] = new SpellFormulaDefinition(
spell.FormulaVersion,
formula,
(uint)spell.SchoolId);
spellLevels[pair.Key] = spell.Generation;
}
var magicPackWcidBySchool = new Dictionary();
// Retail IsComponentPack resolves enum key 0x10000001 in category
// 0x28; that key is not itself a portal.dat file id.
uint magicPackMapDid = RetailDataIdResolver.Resolve(
dats,
0x10000001u,
0x28u);
if (magicPackMapDid != 0u
&& dats.Portal.TryGet(
magicPackMapDid,
out EnumIDMap? magicPackMap)
&& magicPackMap is not null)
{
foreach (var pair in magicPackMap.ClientEnumToID)
magicPackWcidBySchool[pair.Key] = pair.Value;
}
return new MagicCatalog(
CoreSpellTable.Create(metadata),
components.ToFrozenDictionary(),
formulaDefinitions.ToFrozenDictionary(),
wcidByScid.ToFrozenDictionary(),
magicPackWcidBySchool.ToFrozenDictionary(),
spellLevels.ToFrozenDictionary());
}
private static FrozenDictionary EmptyFrozen() =>
new Dictionary().ToFrozenDictionary();
}