fix(combat): match retail live attack feedback

Keep AttackDone control statuses out of chat, preserve dead motion across zero-velocity position updates, render the authored power meter from right to left, and retain the rotatable viewer offset during target tracking.

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-11 21:10:01 +02:00
parent 33cc9aa16a
commit 5276a83087
16 changed files with 231 additions and 67 deletions

View file

@ -41,6 +41,11 @@ public sealed class UiScrollbar : UiElement
/// </summary>
public Func<float?> ScalarFill { get; set; } = () => null;
public uint ScalarFillSprite { get; set; }
/// <summary>
/// Draw the scalar meter from the power end (right) toward the speed end
/// (left). This is the authored gmCombatUI power meter direction.
/// </summary>
public bool ScalarFillFromRight { get; set; }
/// <summary>Programmatically set retail scrollbar attribute 0x86 without broadcasting.</summary>
public void SetScalarPosition(float position)
@ -105,6 +110,14 @@ public sealed class UiScrollbar : UiElement
return (y, h);
}
/// <summary>Returns the clipped scalar-meter span in local pixels.</summary>
public static (float x, float width) ScalarFillRect(
float totalWidth, float fill, bool fromRight)
{
float visibleWidth = totalWidth * Math.Clamp(fill, 0f, 1f);
return (fromRight ? totalWidth - visibleWidth : 0f, visibleWidth);
}
protected override void OnDraw(UiRenderContext ctx)
{
if (SpriteResolve is not { } resolve) return;
@ -113,8 +126,12 @@ public sealed class UiScrollbar : UiElement
DrawTiled(ctx, resolve, TrackSprite, 0f, 0f, Width, Height);
if (ScalarFill() is float fill && ScalarFillSprite != 0)
{
float visibleWidth = Width * Math.Clamp(fill, 0f, 1f);
DrawTiled(ctx, resolve, ScalarFillSprite, 0f, 0f, visibleWidth, Height);
var (fillX, visibleWidth) = ScalarFillRect(Width, fill, ScalarFillFromRight);
if (ScalarFillFromRight)
DrawTiledRightClipped(
ctx, resolve, ScalarFillSprite, fillX, visibleWidth, Height);
else
DrawTiled(ctx, resolve, ScalarFillSprite, 0f, 0f, visibleWidth, Height);
}
float thumbWidth = ScalarThumbWidth(resolve);
float travel = MathF.Max(0f, Width - thumbWidth);
@ -178,6 +195,25 @@ public sealed class UiScrollbar : UiElement
ctx.DrawSprite(tex, x, y, w, h, 0f, 0f, w / tw, h / th, Vector4.One);
}
/// <summary>
/// Draws the rightmost portion of a full-width tiled scalar image. UVs
/// start at the same offset they would have in the uncut image, matching
/// retail meter clipping rather than restarting the wave at the clip edge.
/// </summary>
private void DrawTiledRightClipped(
UiRenderContext ctx,
Func<uint, (uint tex, int w, int h)> resolve,
uint id,
float x,
float w,
float h)
{
if (id == 0 || w <= 0f || h <= 0f) return;
var (tex, tw, th) = resolve(id);
if (tex == 0 || tw == 0 || th == 0) return;
ctx.DrawSprite(tex, x, 0f, w, h, x / tw, 0f, (x + w) / tw, h / th, Vector4.One);
}
public override bool OnEvent(in UiEvent e)
{
if (Horizontal && ScalarChanged is not null)