acdream/tests/AcDream.App.Tests/UI/Layout/IndicatorBarControllerTests.cs
Erik d1d603105f fix(ui): complete retail indicator detail panels
Port the authored effect row template, remaining-time and selection details, synchronize the full gmPanelUI child geometry, and route the burden indicator to Character Information panel 3.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-17 11:36:43 +02:00

232 lines
9 KiB
C#

using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.Core.Items;
using AcDream.Core.Net;
using AcDream.Core.Spells;
namespace AcDream.App.Tests.UI.Layout;
public sealed class IndicatorBarControllerTests
{
private const uint Player = 0x50000001u;
private static (uint, int, int) NoTex(uint _) => (0u, 0, 0);
[Fact]
public void AuthoredFixture_BuildsAllSevenRetailIndicatorButtons()
{
ElementInfo info = FixtureLoader.LoadIndicatorsInfos();
Assert.Equal(0x10000610u, info.Id);
Assert.Equal(150f, info.Width);
Assert.Equal(30f, info.Height);
ImportedLayout layout = LayoutImporter.Build(info, NoTex, datFont: null);
uint[] ids =
[
IndicatorBarController.LinkButtonId,
IndicatorBarController.HelpfulButtonId,
IndicatorBarController.HarmfulButtonId,
IndicatorBarController.VitaeButtonId,
IndicatorBarController.BurdenButtonId,
IndicatorBarController.MiniGameButtonId,
IndicatorBarController.EndCharacterSessionButtonId,
];
foreach (uint id in ids)
Assert.IsType<UiButton>(layout.FindElement(id));
}
[Fact]
public void Enchantments_UpdateHelpfulHarmfulAndVitae_AndDispatchPanels()
{
var h = CreateHarness();
using IndicatorBarController controller = h.Controller;
UiButton helpful = h.Button(IndicatorBarController.HelpfulButtonId);
UiButton harmful = h.Button(IndicatorBarController.HarmfulButtonId);
UiButton vitae = h.Button(IndicatorBarController.VitaeButtonId);
Assert.False(helpful.Enabled);
Assert.False(harmful.Enabled);
Assert.False(vitae.Enabled);
h.Spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
42u, 1u, 60f, 1u, Bucket: 1u, SpellCategory: 99u));
h.Spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
43u, 2u, 60f, 2u, Bucket: 2u, SpellCategory: 99u));
Assert.True(helpful.Enabled);
Assert.True(harmful.Enabled);
helpful.OnEvent(new UiEvent(0, helpful, UiEventType.Click));
harmful.OnEvent(new UiEvent(0, harmful, UiEventType.Click));
Assert.Equal(
[RetailPanelCatalog.PositiveEffects, RetailPanelCatalog.NegativeEffects],
h.ToggledPanels);
h.Spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
50u, 3u, 60f, 1u, StatModValue: 1f, Bucket: 4u));
Assert.False(vitae.Enabled);
h.Spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
50u, 3u, 60f, 1u, StatModValue: 0.99f, Bucket: 4u));
Assert.True(vitae.Enabled);
Assert.Equal(UiButtonStateMachine.Normal, vitae.ActiveRetailStateId);
vitae.OnEvent(new UiEvent(0, vitae, UiEventType.Click));
Assert.Equal(RetailPanelCatalog.Vitae, h.ToggledPanels[^1]);
h.Spellbook.OnEnchantmentRemoved(3u, 50u);
Assert.False(vitae.Enabled);
}
[Theory]
[InlineData(1499, IndicatorBarController.UnencumberedState)]
[InlineData(1500, IndicatorBarController.EncumberedState)]
[InlineData(2999, IndicatorBarController.EncumberedState)]
[InlineData(3000, IndicatorBarController.HeavilyEncumberedState)]
public void Burden_UsesExactRetailLoadBoundaries(int burden, uint expectedState)
{
var h = CreateHarness(strength: 10);
using IndicatorBarController controller = h.Controller;
h.Objects.UpdateIntProperty(Player, 5u, burden);
UiButton button = h.Button(IndicatorBarController.BurdenButtonId);
Assert.Equal(expectedState, button.ActiveRetailStateId);
}
[Fact]
public void Burden_ClickOpensCharacterInformationPanel()
{
var h = CreateHarness();
using IndicatorBarController controller = h.Controller;
UiButton burden = h.Button(IndicatorBarController.BurdenButtonId);
burden.OnEvent(new UiEvent(0, burden, UiEventType.Click));
Assert.Equal([RetailPanelCatalog.CharacterInformation], h.ToggledPanels);
}
[Theory]
[InlineData(true, 4.999, IndicatorBarController.ConnectionGoodState)]
[InlineData(true, 5.0, IndicatorBarController.ConnectionUncertainState)]
[InlineData(true, 19.999, IndicatorBarController.ConnectionUncertainState)]
[InlineData(true, 20.0, IndicatorBarController.ConnectionBadState)]
[InlineData(true, 39.999, IndicatorBarController.ConnectionBadState)]
[InlineData(true, 40.0, IndicatorBarController.ConnectionDisconnectedState)]
[InlineData(false, 0.0, IndicatorBarController.ConnectionDisconnectedState)]
public void LinkStatus_UsesRetailThresholds(
bool connected,
double age,
uint expectedState)
{
var h = CreateHarness();
using IndicatorBarController controller = h.Controller;
h.LinkStatus = new LinkStatusSnapshot(connected, age);
h.Time = 4.0;
controller.Tick();
Assert.Equal(
expectedState,
h.Button(IndicatorBarController.LinkButtonId).ActiveRetailStateId);
}
[Fact]
public void LinkStatus_UpdatesAtFourSeconds_AndBadFlashesEveryPointSevenFive()
{
var h = CreateHarness();
using IndicatorBarController controller = h.Controller;
UiButton link = h.Button(IndicatorBarController.LinkButtonId);
h.LinkStatus = new LinkStatusSnapshot(true, 20d);
h.Time = 3.999;
controller.Tick();
Assert.Equal(IndicatorBarController.ConnectionGoodState, link.ActiveRetailStateId);
h.Time = 4.0;
controller.Tick();
Assert.Equal(IndicatorBarController.ConnectionBadState, link.ActiveRetailStateId);
h.Time = 4.749;
controller.Tick();
Assert.Equal(IndicatorBarController.ConnectionBadState, link.ActiveRetailStateId);
h.Time = 4.75;
controller.Tick();
Assert.Equal(IndicatorBarController.ConnectionUncertainState, link.ActiveRetailStateId);
h.Time = 5.5;
controller.Tick();
Assert.Equal(IndicatorBarController.ConnectionBadState, link.ActiveRetailStateId);
}
[Fact]
public void MiniGameNoticesAndEndSessionActionUseAuthoredButtons()
{
var h = CreateHarness();
using IndicatorBarController controller = h.Controller;
UiButton miniGame = h.Button(IndicatorBarController.MiniGameButtonId);
UiButton endSession = h.Button(IndicatorBarController.EndCharacterSessionButtonId);
Assert.False(miniGame.Enabled);
controller.SetMiniGameActive(true);
Assert.True(miniGame.Enabled);
miniGame.OnEvent(new UiEvent(0, miniGame, UiEventType.Click));
Assert.Equal(RetailPanelCatalog.MiniGame, h.ToggledPanels[^1]);
controller.SetMiniGameActive(false);
Assert.False(miniGame.Enabled);
endSession.OnEvent(new UiEvent(0, endSession, UiEventType.Click));
Assert.Equal(1, h.EndSessionRequests);
}
[Fact]
public void LinkButton_DispatchesSharedLinkStatusPanel()
{
var h = CreateHarness();
using IndicatorBarController controller = h.Controller;
UiButton link = h.Button(IndicatorBarController.LinkButtonId);
link.OnEvent(new UiEvent(0, link, UiEventType.Click));
Assert.Equal([RetailPanelCatalog.LinkStatus], h.ToggledPanels);
}
private static Harness CreateHarness(int strength = 10)
{
var table = SpellTable.LoadFromReader(new StringReader(
"Spell ID,Name,Flags [Hex]\n42,Boon,0x4\n43,Bane,0x0\n50,Vitae,0x0\n"));
var spellbook = new Spellbook(table);
var objects = new ClientObjectTable();
objects.AddOrUpdate(new ClientObject { ObjectId = Player });
ImportedLayout layout = LayoutImporter.Build(
FixtureLoader.LoadIndicatorsInfos(), NoTex, datFont: null);
var harness = new Harness(layout, spellbook, objects, strength);
harness.Controller = IndicatorBarController.Bind(
layout,
new IndicatorBarBindings(
spellbook,
objects,
() => Player,
() => strength,
() => harness.LinkStatus,
() => harness.Time,
harness.ToggledPanels.Add,
() => harness.EndSessionRequests++))!;
return harness;
}
private sealed class Harness(
ImportedLayout layout,
Spellbook spellbook,
ClientObjectTable objects,
int strength)
{
public ImportedLayout Layout { get; } = layout;
public Spellbook Spellbook { get; } = spellbook;
public ClientObjectTable Objects { get; } = objects;
public int Strength { get; } = strength;
public List<uint> ToggledPanels { get; } = [];
public double Time { get; set; }
public LinkStatusSnapshot LinkStatus { get; set; } = new(true, 0d);
public int EndSessionRequests { get; set; }
public IndicatorBarController Controller { get; set; } = null!;
public UiButton Button(uint id) => Assert.IsType<UiButton>(Layout.FindElement(id));
}
}