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
|
|
@ -33,6 +33,7 @@ namespace AcDream.App.UI;
|
|||
public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
||||
{
|
||||
private readonly ElementInfo _info;
|
||||
private readonly ElementInfo _mediaInfo;
|
||||
private readonly Func<uint, (uint tex, int w, int h)> _resolve;
|
||||
private readonly HashSet<uint> _availableStates = new();
|
||||
private bool _pressed;
|
||||
|
|
@ -86,6 +87,16 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
|||
/// <summary>Label color (default white).</summary>
|
||||
public Vector4 LabelColor { get; set; } = Vector4.One;
|
||||
|
||||
/// <summary>Optional authored face rectangle. Full-button by default; retail
|
||||
/// UIOption_Checkbox uses its 13x13 indicator child as the button face.</summary>
|
||||
public float FaceLeft { get; set; }
|
||||
public float FaceTop { get; set; }
|
||||
public float FaceWidth { get; set; }
|
||||
public float FaceHeight { get; set; }
|
||||
|
||||
/// <summary>Additional left inset for left-aligned labels.</summary>
|
||||
public float LabelOffsetX { get; set; } = 3f;
|
||||
|
||||
/// <summary>Horizontal alignment of <see cref="Label"/>. Center (default) for normal buttons;
|
||||
/// Left for the paperdoll "Slots" caption that sits at the left edge, before the slots.</summary>
|
||||
public LabelAlignment LabelAlign { get; set; } = LabelAlignment.Center;
|
||||
|
|
@ -122,7 +133,7 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
|||
{
|
||||
if (string.IsNullOrEmpty(ActiveState))
|
||||
return UiStateInfo.DirectStateId;
|
||||
foreach (var (id, state) in _info.States)
|
||||
foreach (var (id, state) in _mediaInfo.States)
|
||||
if (string.Equals(state.Name, ActiveState, StringComparison.Ordinal))
|
||||
return id;
|
||||
return UiButtonStateMachine.TryStateId(ActiveState, out uint standard)
|
||||
|
|
@ -164,12 +175,12 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
|||
|
||||
if (stateId == UiStateInfo.DirectStateId)
|
||||
{
|
||||
if (!_info.States.ContainsKey(stateId) && !_info.StateMedia.ContainsKey(""))
|
||||
if (!_mediaInfo.States.ContainsKey(stateId) && !_mediaInfo.StateMedia.ContainsKey(""))
|
||||
return false;
|
||||
ActiveState = "";
|
||||
return true;
|
||||
}
|
||||
if (_info.States.TryGetValue(stateId, out var state))
|
||||
if (_mediaInfo.States.TryGetValue(stateId, out var state))
|
||||
{
|
||||
ActiveState = state.Name;
|
||||
return true;
|
||||
|
|
@ -177,7 +188,7 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
|||
string stateName = UiButtonStateMachine.StateName(stateId);
|
||||
if (string.IsNullOrEmpty(stateName))
|
||||
stateName = RetailUiStateIds.StateName(stateId);
|
||||
if (!string.IsNullOrEmpty(stateName) && _info.StateMedia.ContainsKey(stateName))
|
||||
if (!string.IsNullOrEmpty(stateName) && _mediaInfo.StateMedia.ContainsKey(stateName))
|
||||
{
|
||||
ActiveState = stateName;
|
||||
return true;
|
||||
|
|
@ -188,9 +199,13 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
|||
/// <param name="info">Merged <see cref="ElementInfo"/> for this element.</param>
|
||||
/// <param name="resolve">Dat file-id → (GL texture handle, native px width, native px height).
|
||||
/// Returns (0,0,0) when the texture is not yet uploaded.</param>
|
||||
public UiButton(ElementInfo info, Func<uint, (uint tex, int w, int h)> resolve)
|
||||
public UiButton(
|
||||
ElementInfo info,
|
||||
Func<uint, (uint tex, int w, int h)> resolve,
|
||||
ElementInfo? mediaInfo = null)
|
||||
{
|
||||
_info = info;
|
||||
_mediaInfo = mediaInfo ?? info;
|
||||
_resolve = resolve;
|
||||
ClickThrough = false; // buttons are interactive — opt OUT of click-through
|
||||
|
||||
|
|
@ -198,7 +213,7 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
|||
// Retail layouts commonly declare an empty Normal_pressed descriptor while
|
||||
// supplying art only for Normal/Highlight. Treating that property-only state
|
||||
// as drawable briefly blanks the button during mouse-down.
|
||||
foreach (string stateName in info.StateMedia.Keys)
|
||||
foreach (string stateName in _mediaInfo.StateMedia.Keys)
|
||||
if (UiButtonStateMachine.TryStateId(stateName, out uint stateId))
|
||||
_availableStates.Add(stateId);
|
||||
|
||||
|
|
@ -218,11 +233,13 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
|||
// DefaultStateName wins; else "Normal" if that state has a sprite; else DirectState ("").
|
||||
if (!string.IsNullOrEmpty(info.DefaultStateName))
|
||||
ActiveState = info.DefaultStateName;
|
||||
else if (info.StateMedia.ContainsKey("Normal"))
|
||||
else if (_mediaInfo.StateMedia.ContainsKey("Normal"))
|
||||
ActiveState = "Normal";
|
||||
// else ActiveState stays "" (DirectState)
|
||||
|
||||
Enabled = !disabled;
|
||||
FaceWidth = mediaInfo?.Width ?? info.Width;
|
||||
FaceHeight = mediaInfo?.Height ?? info.Height;
|
||||
UpdateVisualState();
|
||||
}
|
||||
|
||||
|
|
@ -242,8 +259,8 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
|||
/// Mirrors <see cref="UiDatElement.ActiveMedia()"/>.
|
||||
/// </summary>
|
||||
private uint ActiveFile()
|
||||
=> _info.StateMedia.TryGetValue(ActiveState, out var m) ? m.File
|
||||
: _info.StateMedia.TryGetValue("", out var d) ? d.File : 0u;
|
||||
=> _mediaInfo.StateMedia.TryGetValue(ActiveState, out var m) ? m.File
|
||||
: _mediaInfo.StateMedia.TryGetValue("", out var d) ? d.File : 0u;
|
||||
|
||||
protected override void OnDraw(UiRenderContext ctx)
|
||||
{
|
||||
|
|
@ -255,14 +272,17 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
|||
{
|
||||
// Tiled draw — same call shape as UiDatElement.OnDraw (UV-repeat; GL_REPEAT-wrapped
|
||||
// UI texture). Matches ImgTex::TileCSI; no Stretch mode exists.
|
||||
ctx.DrawSprite(tex, 0, 0, Width, Height, 0, 0, Width / tw, Height / th, Vector4.One);
|
||||
float faceWidth = FaceWidth > 0f ? FaceWidth : Width;
|
||||
float faceHeight = FaceHeight > 0f ? FaceHeight : Height;
|
||||
ctx.DrawSprite(tex, FaceLeft, FaceTop, faceWidth, faceHeight,
|
||||
0, 0, faceWidth / tw, faceHeight / th, Vector4.One);
|
||||
}
|
||||
}
|
||||
|
||||
if (Label is { Length: > 0 } label && LabelFont is { } lf)
|
||||
{
|
||||
float tx = LabelAlign == LabelAlignment.Left
|
||||
? 3f // small left pad (room for a future checkbox box)
|
||||
? LabelOffsetX
|
||||
: (Width - lf.MeasureWidth(label)) * 0.5f; // centered (default)
|
||||
float ty = (Height - lf.LineHeight) * 0.5f;
|
||||
ctx.DrawStringDat(lf, label, tx, ty, LabelColor);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue