acdream/tests/AcDream.App.Tests/Spells/SpellComponentRequirementServiceTests.cs
Erik 07be994d97 feat: port retail magic lifecycle and retained spell UI
Complete the retail cast-intent, target, component, enchantment, and busy-state paths; mount the DAT-authored spell bar, spellbook, component book, effects panels, and shared panel lifecycle; and add scoped input plus conformance coverage.

Co-Authored-By: Codex <noreply@openai.com>
2026-07-15 10:55:22 +02:00

180 lines
5.9 KiB
C#

using AcDream.App.Spells;
using AcDream.Core.Items;
using AcDream.Core.Spells;
namespace AcDream.App.Tests.Spells;
public sealed class SpellComponentRequirementServiceTests
{
[Fact]
public void RequiresEveryFormulaScidMappedToOwnedWcid()
{
var objects = new ClientObjectTable();
objects.AddOrUpdate(new ClientObject { ObjectId = 1u, Name = "Player" });
objects.AddOrUpdate(new ClientObject
{
ObjectId = 2u,
WeenieClassId = 100u,
ContainerId = 1u,
IsComponentPack = true,
});
var service = Service(objects, [10u, 11u], new Dictionary<uint, uint>
{
[10u] = 100u,
[11u] = 101u,
});
Assert.False(service.HasRequiredComponents(50u));
objects.AddOrUpdate(new ClientObject
{
ObjectId = 3u,
WeenieClassId = 101u,
ContainerId = 1u,
IsComponentPack = true,
});
Assert.True(service.HasRequiredComponents(50u));
}
[Fact]
public void ServerDisabledComponents_BypassesInventoryPreflight()
{
var objects = new ClientObjectTable();
var player = new ClientObject { ObjectId = 1u, Name = "Player" };
player.Properties.Bools[SpellComponentRequirementService.SpellComponentsRequiredProperty] = false;
objects.AddOrUpdate(player);
Assert.True(Service(objects, [10u], new Dictionary<uint, uint>())
.HasRequiredComponents(50u));
}
[Fact]
public void PersonalizedFormula_UsesExactCanonicalAccountSpelling()
{
const string canonical = "CanonicalAccount";
const string loginSpelling = "canonicalaccount";
uint[] formula = [6u, 63u, 10u, 64u, 20u, 30u, 65u, 40u];
IReadOnlyList<uint> canonicalFormula = RetailSpellFormula.CustomizeForAccount(
formula, 3u, canonical);
IReadOnlyList<uint> loginFormula = RetailSpellFormula.CustomizeForAccount(
formula, 3u, loginSpelling);
Assert.NotEqual(canonicalFormula, loginFormula);
var objects = new ClientObjectTable();
objects.AddOrUpdate(new ClientObject { ObjectId = 1u, Name = "Player" });
var map = canonicalFormula.Where(scid => scid != 0u)
.Distinct()
.ToDictionary(scid => scid, scid => scid + 1000u);
foreach (uint wcid in map.Values)
objects.AddOrUpdate(new ClientObject
{
ObjectId = wcid + 10000u,
WeenieClassId = wcid,
ContainerId = 1u,
});
string account = canonical;
var service = new SpellComponentRequirementService(
objects, () => 1u, () => account,
new Dictionary<uint, SpellFormulaDefinition> { [50u] = new(3u, formula) },
map);
Assert.True(service.HasRequiredComponents(50u));
account = loginSpelling;
Assert.False(service.HasRequiredComponents(50u));
}
[Theory]
[InlineData(true, false)]
[InlineData(false, true)]
public void InfusedSchoolOrDirectlyCarriedFocus_UsesScarabOnlyFormula(
bool infused,
bool carriesFocus)
{
var objects = new ClientObjectTable();
var player = new ClientObject { ObjectId = 1u, Name = "Player" };
if (infused)
player.Properties.Ints[0x129u] = 1;
objects.AddOrUpdate(player);
if (carriesFocus)
{
objects.AddOrUpdate(new ClientObject
{
ObjectId = 2u,
WeenieClassId = 900u,
ContainerId = 1u,
Type = ItemType.Container,
});
}
// War I scarab-only formula is SCID 1 + one prismatic taper (0xBC).
objects.AddOrUpdate(new ClientObject
{
ObjectId = 3u,
WeenieClassId = 101u,
ContainerId = 1u,
});
objects.AddOrUpdate(new ClientObject
{
ObjectId = 4u,
WeenieClassId = 188u,
ContainerId = 1u,
});
var service = new SpellComponentRequirementService(
objects, () => 1u, () => "testaccount",
new Dictionary<uint, SpellFormulaDefinition>
{
[50u] = new(0u, [1u, 63u, 10u], School: 1u),
},
new Dictionary<uint, uint>
{
[1u] = 101u,
[0xBCu] = 188u,
},
new Dictionary<uint, uint> { [1u] = 900u });
Assert.True(service.HasRequiredComponents(50u));
}
[Fact]
public void FocusNestedInsidePack_DoesNotCountAsRetailMagicPackOwnership()
{
var objects = new ClientObjectTable();
objects.AddOrUpdate(new ClientObject { ObjectId = 1u, Name = "Player" });
objects.AddOrUpdate(new ClientObject
{
ObjectId = 2u,
WeenieClassId = 700u,
ContainerId = 1u,
Type = ItemType.Container,
});
objects.AddOrUpdate(new ClientObject
{
ObjectId = 3u,
WeenieClassId = 900u,
ContainerId = 2u,
Type = ItemType.Container,
});
var service = new SpellComponentRequirementService(
objects, () => 1u, () => "testaccount",
new Dictionary<uint, SpellFormulaDefinition>
{
[50u] = new(0u, [1u, 63u], School: 1u),
},
new Dictionary<uint, uint>(),
new Dictionary<uint, uint> { [1u] = 900u });
Assert.False(service.HasRequiredComponents(50u));
}
private static SpellComponentRequirementService Service(
ClientObjectTable objects,
IReadOnlyList<uint> components,
IReadOnlyDictionary<uint, uint> map) => new(
objects,
() => 1u,
() => "testaccount",
new Dictionary<uint, SpellFormulaDefinition>
{
[50u] = new(0u, components),
},
map);
}