FixtureProvider.Populate for 0x21000023 was binding only InventoryController and omitting PaperdollController, so the ~21 equip slots rendered empty even though sample equipped items existed in the table. Also, the three per-list empty-slot sprites were not being resolved from the dat (contentsEmpty / sideBagEmpty / mainPackEmpty all defaulted to 0), so the slot cells had no background art. Fixes: - FixtureProvider.Populate gains a DatCollection dats param (mirrors the GameWindow.OnLoad lookup at line 2233-2235). The 0x21000023 case now resolves all three empty sprites via ItemListCellTemplate.ResolveEmptySprite and passes them to InventoryController.Bind. - PaperdollController.Bind is now called after InventoryController in the same 0x21000023 case, matching GameWindow:2257-2265 (same layout subtree, contentsEmpty as the equip-slot placeholder, sendWield: null since there is no live session in the studio). - StudioWindow.OnLoad passes _dats! to the updated Populate signature. SampleData.AddEquipped was already correct: MoveItem(guid, PlayerGuid, -1, equipMask) at ClientObjectTable.cs:134 sets CurrentlyEquippedLocation = newEquipLocation and calls Reindex which places the item in _containerIndex[PlayerGuid]. No SampleData change needed. Tests: 2 new assertions in FixtureProviderTests — sideBags_matchInventoryControllerFilter (Type.HasFlag(Container) || ItemsCapacity > 0 filter must match both bags) and equippedItems_retainLocationAndAreInContents (equipped items have CurrentlyEquippedLocation != None AND appear in GetContents so the paperdoll controller can find them). 604 pass / 2 skip / 0 fail. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
160 lines
5.8 KiB
C#
160 lines
5.8 KiB
C#
using AcDream.App.Studio;
|
|
using AcDream.Core.Items;
|
|
|
|
namespace AcDream.App.Tests.Studio;
|
|
|
|
/// <summary>
|
|
/// Unit tests for <see cref="FixtureProvider"/> and <see cref="SampleData"/>.
|
|
/// These tests have NO GL/dat dependency — they only exercise the in-memory
|
|
/// ClientObjectTable population that FixtureProvider uses.
|
|
/// </summary>
|
|
public class FixtureProviderTests
|
|
{
|
|
/// <summary>
|
|
/// SampleData.BuildObjectTable() must place at least 6 items in the
|
|
/// player's main pack (ContainerId == PlayerGuid) and the player object
|
|
/// itself must exist with the right capacities.
|
|
/// </summary>
|
|
[Fact]
|
|
public void SampleTable_hasPackContents()
|
|
{
|
|
var t = SampleData.BuildObjectTable();
|
|
|
|
// Player object must exist.
|
|
var player = t.Get(SampleData.PlayerGuid);
|
|
Assert.NotNull(player);
|
|
Assert.Equal(102, player!.ItemsCapacity);
|
|
Assert.Equal(7, player.ContainersCapacity);
|
|
|
|
// At least 6 loose items must be in the main pack.
|
|
var contents = t.GetContents(SampleData.PlayerGuid);
|
|
Assert.True(contents.Count >= 6,
|
|
$"Expected >= 6 items in player pack; got {contents.Count}");
|
|
|
|
// Verify the first item has a non-zero IconId and a recognised Type.
|
|
var firstId = contents[0];
|
|
var first = t.Get(firstId);
|
|
Assert.NotNull(first);
|
|
Assert.NotEqual(0u, first!.IconId);
|
|
Assert.NotEqual(ItemType.None, first.Type);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Spot-check that the sample table also seeds a sword-like item
|
|
/// (MeleeWeapon) and a piece of armor so icon resolution has
|
|
/// recognisable item types to work with.
|
|
/// </summary>
|
|
[Fact]
|
|
public void SampleTable_hasWeaponAndArmor()
|
|
{
|
|
var t = SampleData.BuildObjectTable();
|
|
var contents = t.GetContents(SampleData.PlayerGuid);
|
|
|
|
bool hasMelee = false, hasArmor = false;
|
|
foreach (var guid in contents)
|
|
{
|
|
var obj = t.Get(guid);
|
|
if (obj is null) continue;
|
|
if ((obj.Type & ItemType.MeleeWeapon) != 0) hasMelee = true;
|
|
if ((obj.Type & ItemType.Armor) != 0) hasArmor = true;
|
|
}
|
|
|
|
Assert.True(hasMelee, "Expected at least one MeleeWeapon in sample pack");
|
|
Assert.True(hasArmor, "Expected at least one Armor item in sample pack");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sample table must include at least 1 equipped item whose
|
|
/// CurrentlyEquippedLocation is non-None.
|
|
/// </summary>
|
|
[Fact]
|
|
public void SampleTable_hasEquippedItems()
|
|
{
|
|
var t = SampleData.BuildObjectTable();
|
|
|
|
bool anyEquipped = false;
|
|
foreach (var obj in t.Objects)
|
|
{
|
|
if (obj.CurrentlyEquippedLocation != EquipMask.None)
|
|
{ anyEquipped = true; break; }
|
|
}
|
|
|
|
Assert.True(anyEquipped, "Expected at least one equipped item in sample table");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sample table must include at least 2 side-bag containers in the
|
|
/// player's pack (ContainerId == PlayerGuid, Type has Container bit).
|
|
/// </summary>
|
|
[Fact]
|
|
public void SampleTable_hasSideBags()
|
|
{
|
|
var t = SampleData.BuildObjectTable();
|
|
|
|
int bagCount = 0;
|
|
foreach (var guid in t.GetContents(SampleData.PlayerGuid))
|
|
{
|
|
var obj = t.Get(guid);
|
|
if (obj is not null && (obj.Type & ItemType.Container) != 0)
|
|
bagCount++;
|
|
}
|
|
|
|
Assert.True(bagCount >= 2,
|
|
$"Expected >= 2 side-bag containers in player pack; got {bagCount}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Side bags must match the InventoryController filter: ItemType.Container OR ItemsCapacity > 0.
|
|
/// This ensures they appear in the side-bag column even if the exact Type flag changes.
|
|
/// </summary>
|
|
[Fact]
|
|
public void SampleTable_sideBags_matchInventoryControllerFilter()
|
|
{
|
|
var t = SampleData.BuildObjectTable();
|
|
|
|
int bagCount = 0;
|
|
foreach (var guid in t.GetContents(SampleData.PlayerGuid))
|
|
{
|
|
var obj = t.Get(guid);
|
|
if (obj is null) continue;
|
|
// InventoryController.Populate line ~203: Type.HasFlag(Container) OR ItemsCapacity > 0
|
|
bool isBag = obj.Type.HasFlag(ItemType.Container) || obj.ItemsCapacity > 0;
|
|
if (isBag) bagCount++;
|
|
}
|
|
|
|
Assert.True(bagCount >= 2,
|
|
$"Expected >= 2 items matching the side-bag filter; got {bagCount}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Equipped items must BOTH (a) retain CurrentlyEquippedLocation != None after
|
|
/// seeding AND (b) appear in GetContents(PlayerGuid) so PaperdollController.Populate
|
|
/// can find them. These are the two conditions PaperdollController checks on every
|
|
/// equipped item (PaperdollController.Populate: ContainerId == playerGuid AND
|
|
/// CurrentlyEquippedLocation != None).
|
|
/// </summary>
|
|
[Fact]
|
|
public void SampleTable_equippedItems_retainLocationAndAreInContents()
|
|
{
|
|
var t = SampleData.BuildObjectTable();
|
|
var contents = new System.Collections.Generic.HashSet<uint>(t.GetContents(SampleData.PlayerGuid));
|
|
|
|
int equippedCount = 0;
|
|
foreach (var obj in t.Objects)
|
|
{
|
|
if (obj.CurrentlyEquippedLocation == EquipMask.None) continue;
|
|
equippedCount++;
|
|
|
|
// Must appear in GetContents so the controller can pick them up.
|
|
Assert.True(contents.Contains(obj.ObjectId),
|
|
$"Equipped item 0x{obj.ObjectId:X8} ('{obj.Name}', loc={obj.CurrentlyEquippedLocation}) " +
|
|
$"is NOT in GetContents(PlayerGuid). PaperdollController will miss it.");
|
|
|
|
// The equip location must survive the MoveItem call.
|
|
Assert.NotEqual(EquipMask.None, obj.CurrentlyEquippedLocation);
|
|
}
|
|
|
|
Assert.True(equippedCount >= 3,
|
|
$"Expected >= 3 equipped items in sample table; got {equippedCount}");
|
|
}
|
|
}
|