fix(combat): restore retail combat bar controls
Resolve authored StringInfo labels from local.dat, port gmCombatUI's runtime option captions and checkbox widgets, and bind horizontal scrollbar media by retail structural roles so the green power jewel is a thumb instead of a tiled track. Persist the three combat options and make Auto Target govern target acquisition. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
2215c76c7e
commit
927fa7881a
19 changed files with 24591 additions and 48 deletions
|
|
@ -0,0 +1,43 @@
|
|||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
public sealed class CombatLayoutConformanceTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("ID_CombatPanelOption_AutoRepeatAttack", 0x0718F89Bu)]
|
||||
[InlineData("ID_CombatPanelOption_AutoTarget", 0x0FB7B944u)]
|
||||
[InlineData("ID_CombatPanelOption_ViewCombatTarget", 0x0936C254u)]
|
||||
public void RetailStringHash_MatchesNamedDecompGolden(string value, uint expected)
|
||||
=> Assert.Equal(expected, DatStringResolver.ComputeHash(value));
|
||||
|
||||
[Fact]
|
||||
public void RetailFixture_UsesAuthoredScrollbarRolesAndCheckboxWidgets()
|
||||
{
|
||||
ElementInfo info = FixtureLoader.LoadCombatInfos();
|
||||
ImportedLayout layout = LayoutImporter.Build(
|
||||
info,
|
||||
_ => (0u, 0, 0),
|
||||
datFont: null,
|
||||
stringResolve: _ => "localized");
|
||||
|
||||
var power = Assert.IsType<UiScrollbar>(
|
||||
layout.FindElement(CombatUiController.PowerControlId));
|
||||
Assert.Equal(0x060074CAu, power.TrackSprite);
|
||||
Assert.Equal(0x06001923u, power.ThumbSprite);
|
||||
Assert.Equal(0x06001200u, power.ScalarFillSprite);
|
||||
|
||||
var repeat = Assert.IsType<UiButton>(
|
||||
layout.FindElement(CombatUiController.RepeatAttacksId));
|
||||
Assert.Equal(13f, repeat.FaceWidth);
|
||||
Assert.Equal(13f, repeat.FaceHeight);
|
||||
Assert.Equal(17f, repeat.LabelOffsetX);
|
||||
Assert.True(repeat.ToggleBehavior);
|
||||
|
||||
var high = Assert.IsType<UiButton>(
|
||||
layout.FindElement(CombatUiController.HighButtonId));
|
||||
Assert.Equal("localized", high.Label);
|
||||
Assert.IsType<UiText>(layout.FindElement(CombatUiController.SpeedLabelId));
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ using AcDream.App.Combat;
|
|||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Combat;
|
||||
using AcDream.UI.Abstractions.Panels.Settings;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
|
|
@ -17,8 +18,10 @@ public sealed class CombatUiControllerTests
|
|||
using var attacks = CreateAttacks(combat, () => now, []);
|
||||
var (layout, _, _, power, high, medium, low) = BuildLayout();
|
||||
var visibility = new List<bool>();
|
||||
GameplaySettings gameplay = GameplaySettings.Default;
|
||||
using var controller = CombatUiController.Bind(
|
||||
layout, combat, attacks, visibility.Add)!;
|
||||
layout, combat, attacks, () => gameplay, value => gameplay = value,
|
||||
Labels, visibility.Add)!;
|
||||
|
||||
controller.SyncVisibility();
|
||||
combat.SetCombatMode(CombatMode.Melee);
|
||||
|
|
@ -39,8 +42,10 @@ public sealed class CombatUiControllerTests
|
|||
var combat = new CombatState();
|
||||
using var attacks = CreateAttacks(combat, () => now, sent);
|
||||
var (layout, basic, advanced, power, high, _, _) = BuildLayout();
|
||||
GameplaySettings gameplay = GameplaySettings.Default;
|
||||
using var controller = CombatUiController.Bind(
|
||||
layout, combat, attacks, _ => { })!;
|
||||
layout, combat, attacks, () => gameplay, value => gameplay = value,
|
||||
Labels, _ => { })!;
|
||||
combat.SetCombatMode(CombatMode.Melee);
|
||||
|
||||
high.OnEvent(new UiEvent(0, high, UiEventType.MouseDown, Data1: 2, Data2: 2));
|
||||
|
|
@ -55,6 +60,29 @@ public sealed class CombatUiControllerTests
|
|||
Assert.False(advanced.Visible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthoredOptionCheckbox_TogglesPersistedGameplayValue()
|
||||
{
|
||||
var combat = new CombatState();
|
||||
using var attacks = CreateAttacks(combat, () => 0d, []);
|
||||
var (layout, _, _, _, _, _, _) = BuildLayout();
|
||||
GameplaySettings gameplay = GameplaySettings.Default;
|
||||
using var controller = CombatUiController.Bind(
|
||||
layout, combat, attacks, () => gameplay, value => gameplay = value,
|
||||
Labels, _ => { })!;
|
||||
var autoTarget = Assert.IsType<UiButton>(layout.FindElement(CombatUiController.AutoTargetId));
|
||||
|
||||
autoTarget.OnEvent(new UiEvent(0, autoTarget, UiEventType.MouseDown, Data1: 3, Data2: 3));
|
||||
autoTarget.OnEvent(new UiEvent(0, autoTarget, UiEventType.MouseUp, Data1: 3, Data2: 3));
|
||||
autoTarget.OnEvent(new UiEvent(0, autoTarget, UiEventType.Click, Data1: 3, Data2: 3));
|
||||
|
||||
Assert.False(gameplay.AutoTarget);
|
||||
}
|
||||
|
||||
private static readonly CombatUiLabels Labels = new(
|
||||
"Speed", "Power", "Repeat Attacks", "Auto Target", "Keep in View",
|
||||
"High", "Medium", "Low");
|
||||
|
||||
private static CombatAttackController CreateAttacks(
|
||||
CombatState combat,
|
||||
Func<double> now,
|
||||
|
|
@ -89,9 +117,21 @@ public sealed class CombatUiControllerTests
|
|||
Width = 72,
|
||||
Height = 19,
|
||||
};
|
||||
UiButton ToggleButton(uint id)
|
||||
{
|
||||
var info = new ElementInfo { Id = id, Type = 0x10000035u, Width = 100, Height = 14 };
|
||||
var state = new UiStateInfo { Id = UiStateInfo.DirectStateId };
|
||||
state.Properties.Values[0x0Bu] = new UiPropertyValue
|
||||
{ Kind = UiPropertyKind.Bool, BoolValue = true };
|
||||
info.States[UiStateInfo.DirectStateId] = state;
|
||||
return new UiButton(info, NoTex) { Width = 100, Height = 14 };
|
||||
}
|
||||
var high = Button(CombatUiController.HighButtonId);
|
||||
var medium = Button(CombatUiController.MediumButtonId);
|
||||
var low = Button(CombatUiController.LowButtonId);
|
||||
var repeat = ToggleButton(CombatUiController.RepeatAttacksId);
|
||||
var autoTarget = ToggleButton(CombatUiController.AutoTargetId);
|
||||
var keepInView = ToggleButton(CombatUiController.KeepInViewId);
|
||||
var byId = new Dictionary<uint, UiElement>
|
||||
{
|
||||
[CombatUiController.BasicPanelId] = basic,
|
||||
|
|
@ -100,6 +140,9 @@ public sealed class CombatUiControllerTests
|
|||
[CombatUiController.HighButtonId] = high,
|
||||
[CombatUiController.MediumButtonId] = medium,
|
||||
[CombatUiController.LowButtonId] = low,
|
||||
[CombatUiController.RepeatAttacksId] = repeat,
|
||||
[CombatUiController.AutoTargetId] = autoTarget,
|
||||
[CombatUiController.KeepInViewId] = keepInView,
|
||||
};
|
||||
return (new ImportedLayout(root, byId), basic, advanced, power, high, medium, low);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,12 @@ public static class FixtureLoader
|
|||
public static ElementInfo LoadCharacterInfos()
|
||||
=> LoadInfos("character_2100002E.json");
|
||||
|
||||
public static ImportedLayout LoadCombat()
|
||||
=> LayoutImporter.Build(LoadCombatInfos(), _ => (0u, 0, 0), null);
|
||||
|
||||
public static ElementInfo LoadCombatInfos()
|
||||
=> LoadInfos("combat_21000073.json");
|
||||
|
||||
// ── Shared loader ────────────────────────────────────────────────────────
|
||||
|
||||
private static AcDream.App.UI.Layout.ElementInfo LoadInfos(string fileName)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ public sealed class RetailLayoutFixtureGenerator
|
|||
(0x21000024u, "paperdoll_21000024.json"),
|
||||
(0x2100002Eu, "character_2100002E.json"),
|
||||
(0x2100006Cu, "vitals_2100006C.json"),
|
||||
(0x21000073u, "combat_21000073.json"),
|
||||
(0x21000074u, "radar_21000074.json"),
|
||||
};
|
||||
|
||||
|
|
@ -46,6 +47,18 @@ public sealed class RetailLayoutFixtureGenerator
|
|||
{
|
||||
var info = LayoutImporter.ImportInfos(dats, layoutId);
|
||||
Assert.NotNull(info);
|
||||
if (layoutId == CombatUiController.LayoutId)
|
||||
{
|
||||
var labels = CombatUiLabels.Resolve(info, new DatStringResolver(dats));
|
||||
Assert.Equal("Speed", labels.Speed);
|
||||
Assert.Equal("Power", labels.Power);
|
||||
Assert.Equal("Repeat Attacks", labels.RepeatAttacks);
|
||||
Assert.Equal("Auto Target", labels.AutoTarget);
|
||||
Assert.Equal("Keep in View", labels.KeepInView);
|
||||
Assert.Equal("High", labels.High);
|
||||
Assert.Equal("Medium", labels.Medium);
|
||||
Assert.Equal("Low", labels.Low);
|
||||
}
|
||||
var json = JsonSerializer.Serialize(info, new JsonSerializerOptions
|
||||
{
|
||||
IncludeFields = true,
|
||||
|
|
|
|||
24041
tests/AcDream.App.Tests/UI/Layout/fixtures/combat_21000073.json
Normal file
24041
tests/AcDream.App.Tests/UI/Layout/fixtures/combat_21000073.json
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue