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>
This commit is contained in:
parent
7b7ffcd278
commit
07be994d97
84 changed files with 17822 additions and 1051 deletions
|
|
@ -0,0 +1,180 @@
|
|||
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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue