feat(ui): port retail jump power bar

Import gmFloatyPowerBarUI LayoutDesc 0x21000072, teach the meter factory its stateful single-image shape, and project the movement-owned retail jump charge through a focused retained controller. Preserve authored resize constraints and state-managed visibility, with named-decomp pseudocode plus controller, movement, and production-DAT conformance coverage.

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-13 10:22:03 +02:00
parent 27619b2328
commit db03b4bda8
18 changed files with 3045 additions and 10 deletions

View file

@ -0,0 +1,91 @@
using AcDream.App.Input;
namespace AcDream.App.UI.Layout;
/// <summary>
/// Projects <see cref="PlayerMovementController.JumpCharge"/> into the imported
/// retail <c>gmFloatyPowerBarUI</c>. The movement controller owns the clock; this
/// controller owns only DAT state, fill, and window visibility.
/// </summary>
/// <remarks>
/// Retail references: <c>gmPowerbarUI::RecvNotice_BeginPowerbar</c>
/// (0x004DA730), <c>RecvNotice_SetPowerbarLevel</c> (0x004DA550),
/// <c>RecvNotice_FinishPowerbar</c> (0x004DA580), and
/// <c>ClientCombatSystem::CommenceJump</c> (0x0056AF90).
/// </remarks>
public sealed class JumpPowerbarController : IRetainedPanelController
{
public const uint LayoutId = 0x21000072u;
public const uint MeterId = 0x10000034u;
public const uint JumpModeState = 0x10000042u;
private readonly UiMeter _meter;
private readonly Func<JumpChargeSnapshot> _snapshot;
private readonly Action<bool> _setWindowVisible;
private float _power;
private bool _isCharging;
private bool _hasSynced;
private bool _disposed;
private JumpPowerbarController(
UiMeter meter,
Func<JumpChargeSnapshot> snapshot,
Action<bool> setWindowVisible)
{
_meter = meter;
_snapshot = snapshot;
_setWindowVisible = setWindowVisible;
_meter.Fill = () => _power;
SyncVisibility(force: true);
}
public static JumpPowerbarController? Bind(
ImportedLayout layout,
Func<JumpChargeSnapshot> snapshot,
Action<bool> setWindowVisible)
{
ArgumentNullException.ThrowIfNull(layout);
ArgumentNullException.ThrowIfNull(snapshot);
ArgumentNullException.ThrowIfNull(setWindowVisible);
if (layout.FindElement(MeterId) is not UiMeter meter
|| !meter.TrySetRetailState(JumpModeState))
return null;
return new JumpPowerbarController(meter, snapshot, setWindowVisible);
}
/// <summary>Applies the latest movement-owned charge snapshot.</summary>
public void Tick() => SyncVisibility(force: false);
/// <summary>
/// Re-applies the state-driven visibility after the window has been mounted.
/// This covers a jump key already held during retained-UI construction.
/// </summary>
public void SyncVisibility() => SyncVisibility(force: true);
private void SyncVisibility(bool force)
{
if (_disposed) return;
JumpChargeSnapshot state = _snapshot();
_power = state.IsCharging ? Math.Clamp(state.Power, 0f, 1f) : 0f;
if (force || !_hasSynced || state.IsCharging != _isCharging)
{
_isCharging = state.IsCharging;
_hasSynced = true;
_setWindowVisible(_isCharging);
}
}
public void OnShown() => SyncVisibility(force: false);
public void Dispose()
{
if (_disposed) return;
_disposed = true;
_power = 0f;
_meter.Fill = () => null;
}
}