fix(paperdoll): port quest-gated Aetheria slots

Add the three authored blue, yellow, and red sigil backgrounds and drive their visibility from player PropertyInt.AetheriaBitfield bits 1/2/4 at login and on live updates, matching gmPaperDollUI::UpdateAetheria. Include the sigil equip masks in the shared slot table and narrow AP-108.

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-13 09:55:20 +02:00
parent 1be18cc4db
commit edc9be3008
11 changed files with 208 additions and 16 deletions

View file

@ -17,12 +17,19 @@ public class PaperdollControllerTests
private const uint ShieldSlot = 0x100001E1u; // Shield 0x200000
private const uint WeaponSlot = 0x100001DFu; // composite 0x3500000
private const uint FingerLSlot= 0x100001DCu; // FingerWearLeft 0x40000
private const uint BlueAetheriaSlot = 0x10000595u;
private const uint YellowAetheriaSlot = 0x10000596u;
private const uint RedAetheriaSlot = 0x10000597u;
private sealed class RootElement : UiElement { }
private static (ImportedLayout layout, Dictionary<uint, UiItemList> lists) BuildLayout()
{
var ids = new[] { HeadSlot, ChestSlot, ChestArmorSlot, ShieldSlot, WeaponSlot, FingerLSlot };
var ids = new[]
{
HeadSlot, ChestSlot, ChestArmorSlot, ShieldSlot, WeaponSlot, FingerLSlot,
BlueAetheriaSlot, YellowAetheriaSlot, RedAetheriaSlot,
};
var lists = new Dictionary<uint, UiItemList>();
var byId = new Dictionary<uint, UiElement>();
var root = new RootElement { Width = 224, Height = 214 };
@ -241,6 +248,72 @@ public class PaperdollControllerTests
Assert.Equal(sprite, lists[element].Cell.EmptySprite);
}
[Fact]
public void Aetheria_slots_default_hidden_without_unlock_property()
{
var (layout, lists) = BuildLayout();
Bind(layout, new ClientObjectTable());
Assert.False(lists[BlueAetheriaSlot].Visible);
Assert.False(lists[YellowAetheriaSlot].Visible);
Assert.False(lists[RedAetheriaSlot].Visible);
}
[Fact]
public void Aetheria_slots_follow_independent_player_unlock_bits_at_login()
{
var (layout, lists) = BuildLayout();
var objects = new ClientObjectTable();
objects.AddOrUpdate(new ClientObject { ObjectId = Player });
objects.UpdateIntProperty(
Player,
AetheriaUnlocks.PropertyId,
(int)(AetheriaUnlockState.Blue | AetheriaUnlockState.Red));
Bind(layout, objects);
Assert.True(lists[BlueAetheriaSlot].Visible);
Assert.False(lists[YellowAetheriaSlot].Visible);
Assert.True(lists[RedAetheriaSlot].Visible);
}
[Fact]
public void Live_aetheria_property_update_unlocks_new_slots()
{
var (layout, lists) = BuildLayout();
var objects = new ClientObjectTable();
objects.AddOrUpdate(new ClientObject { ObjectId = Player });
Bind(layout, objects);
objects.UpdateIntProperty(
Player,
AetheriaUnlocks.PropertyId,
(int)(AetheriaUnlockState.Blue | AetheriaUnlockState.Yellow));
Assert.True(lists[BlueAetheriaSlot].Visible);
Assert.True(lists[YellowAetheriaSlot].Visible);
Assert.False(lists[RedAetheriaSlot].Visible);
}
[Fact]
public void Unlocked_aetheria_slot_populates_its_equipped_sigil()
{
var (layout, lists) = BuildLayout();
var objects = new ClientObjectTable();
objects.AddOrUpdate(new ClientObject { ObjectId = Player });
objects.UpdateIntProperty(
Player,
AetheriaUnlocks.PropertyId,
(int)AetheriaUnlockState.Blue);
SeedEquipped(objects, 0xA37u, EquipMask.SigilOne);
Bind(layout, objects);
Assert.True(lists[BlueAetheriaSlot].Visible);
Assert.Equal(0xA37u, lists[BlueAetheriaSlot].Cell.ItemId);
}
[Fact]
public void Live_player_wield_repaints_the_slot() // event-driven: ObjectMoved → Concerns → Populate
{