fix(ui): select and examine favorite spells like retail

This commit is contained in:
Erik 2026-07-24 06:47:54 +02:00
parent 043ab10b3c
commit 3e31b0ac70
23 changed files with 974 additions and 34 deletions

View file

@ -17,6 +17,12 @@ public sealed record SpellComponentDescriptor(
uint Category,
uint IconId);
/// <summary>One resolved formula cell in retail's spell examination subview.</summary>
public readonly record struct SpellExamineComponent(
uint SpellComponentId,
SpellComponentDescriptor Descriptor,
bool Owned);
/// <summary>
/// App-layer projection of retail's spell, component, and school-focus DAT tables.
/// Keeping this catalog outside <c>GameWindow</c> makes the SCID/WCID boundary and
@ -52,6 +58,20 @@ public sealed class MagicCatalog
public CoreSpellTable SpellTable { get; }
public IReadOnlyDictionary<uint, SpellComponentDescriptor> 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);
@ -143,15 +163,43 @@ public sealed class MagicCatalog
/// </summary>
public sealed class MagicRuntime
{
private MagicRuntime(MagicCatalog catalog, SpellCastingController casting)
private readonly SpellComponentRequirementService _requirements;
private MagicRuntime(
MagicCatalog catalog,
SpellCastingController casting,
SpellComponentRequirementService requirements)
{
Catalog = catalog;
Casting = casting;
_requirements = requirements;
}
public MagicCatalog Catalog { get; }
public SpellCastingController Casting { get; }
public IReadOnlyList<SpellExamineComponent> GetExamineComponents(uint spellId)
{
IReadOnlyList<uint> formula = _requirements.GetAppropriateFormula(spellId);
if (formula.Count == 0)
return [];
var result = new List<SpellExamineComponent>(formula.Count);
foreach (uint spellComponentId in formula)
{
if (spellComponentId == 0u
|| !Catalog.TryGetComponentBySpellComponentId(
spellComponentId,
out SpellComponentDescriptor descriptor))
continue;
result.Add(new SpellExamineComponent(
spellComponentId,
descriptor,
_requirements.IsComponentOwned(spellComponentId)));
}
return result;
}
public static MagicRuntime Create(
MagicCatalog catalog,
Spellbook spellbook,
@ -198,7 +246,7 @@ public sealed class MagicRuntime
incrementBusy: incrementBusy,
canSend: canSend,
targetCompatibleSilent: (target, spell) => TargetCompatible(target, spell, false));
return new MagicRuntime(catalog, casting);
return new MagicRuntime(catalog, casting, requirements);
}
public void Reset() => Casting.Reset();

View file

@ -75,6 +75,31 @@ public sealed class SpellComponentRequirementService
/// (0x00567D50): an infused school or a directly carried school focus uses
/// the scarab-only formula; otherwise the account-customized formula wins.
/// </summary>
public IReadOnlyList<uint> GetAppropriateFormula(uint spellId)
{
if (!_formulas.TryGetValue(spellId, out SpellFormulaDefinition? formula))
return [];
uint playerGuid = _playerGuid();
return GetAppropriateFormula(
formula,
_objects.Get(playerGuid),
playerGuid);
}
/// <summary>
/// Whether the component tracker would leave a formula icon unghosted.
/// The input is a retail spell-component id (SCID), not a weenie class id.
/// </summary>
public bool IsComponentOwned(uint spellComponentId)
{
if (!_wcidByScid.TryGetValue(spellComponentId, out uint weenieClassId))
return false;
uint playerGuid = _playerGuid();
return _objects.Objects.Any(item =>
item.WeenieClassId == weenieClassId
&& IsOwnedByPlayer(item, playerGuid));
}
private IReadOnlyList<uint> GetAppropriateFormula(
SpellFormulaDefinition formula,
ClientObject? player,