feat(ui/hud): readouts HP/SP + level multi-cor via RichText (DT_UI_VitalsReadout)

Readouts e LvlText do PlayerVitals viram RichTextBlock com markup inline,
estilizados pelo DT_UI_VitalsReadout (fonte JetBrains Mono Bold):
- HP/SP: atual (branco) + "/" (sep, #3a3830) + max (#6c6860)
- LVL: "LVL " (#b8b3a8) + numero (num, gold-bright #ffd888, maior)

Cores espelham hud_layout_modal_New.html (text-1/2/3/4 + gold-bright).
UZMMOHudPlayerVitalsWidget: HpReadout/SpReadout/ExpReadout/JobReadout/LvlText
agora URichTextBlock; ApplyHpSp/ApplySnapshot montam o markup.

EXP/JOB readout segue exp absoluto (sem %) ate o server enviar exp-to-next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 03:06:44 -03:00
parent 65edf1081b
commit 484c8d5d4f
4 changed files with 21 additions and 14 deletions

Binary file not shown.

View File

@@ -1,5 +1,6 @@
#include "ZMMOHudPlayerVitalsWidget.h"
#include "Components/RichTextBlock.h"
#include "Components/TextBlock.h"
#include "UI/Widgets/UIProgressBar_Base.h"
#include "Internationalization/Text.h"
@@ -25,14 +26,16 @@ void UZMMOHudPlayerVitalsWidget::ApplySnapshot(const FZMMOAttributesSnapshot& Sn
// HP / SP (pool efetivo — % real)
ApplyHpSp(Snapshot.Hp, Snapshot.MaxHp, Snapshot.Sp, Snapshot.MaxSp);
// Level
// Level — markup: "LVL " (pct/cinza) + número (num/gold-bright maior)
if (LvlText)
{
LvlText->SetText(FText::FromString(FString::Printf(TEXT("LVL %d"), Snapshot.BaseLevel)));
LvlText->SetText(FText::FromString(
FString::Printf(TEXT("<pct>LVL </><num>%d</>"), Snapshot.BaseLevel)));
}
// BASE / JOB EXP — sem "exp-to-next" no snapshot. Readout mostra valor
// absoluto acumulado; as barras ficam pra quando a curva de exp existir.
// absoluto acumulado (estilo Default, sem markup de %); as barras + o "%"
// ficam pra quando a curva de exp existir no snapshot do servidor.
if (ExpReadout)
{
ExpReadout->SetText(FormatNumber(Snapshot.BaseExp));
@@ -60,17 +63,20 @@ void UZMMOHudPlayerVitalsWidget::ApplyHpSp(int32 Hp, int32 MaxHp, int32 Sp, int3
{
SpBar->SetTargetPrimaryLevel(SafeRatio(Sp, MaxSp));
}
// Markup RichText: atual (Default/branco) + "/" (sep/escuro) + max (max/cinza).
// Tags resolvem no DT_UI_VitalsReadout (TextStyleSet do RichTextBlock).
auto MakeReadout = [](int32 Cur, int32 Max) -> FText
{
return FText::FromString(FString::Printf(TEXT("%s<sep>/</><max>%s</>"),
*FText::AsNumber(Cur).ToString(), *FText::AsNumber(Max).ToString()));
};
if (HpReadout)
{
HpReadout->SetText(FText::Format(
NSLOCTEXT("ZMMOHud", "HpSpReadout", "{0}/{1}"),
FormatNumber(Hp), FormatNumber(MaxHp)));
HpReadout->SetText(MakeReadout(Hp, MaxHp));
}
if (SpReadout)
{
SpReadout->SetText(FText::Format(
NSLOCTEXT("ZMMOHud", "HpSpReadout", "{0}/{1}"),
FormatNumber(Sp), FormatNumber(MaxSp)));
SpReadout->SetText(MakeReadout(Sp, MaxSp));
}
}

View File

@@ -7,6 +7,7 @@
class UUIProgressBar_Base;
class UTextBlock;
class URichTextBlock;
/**
* Sub-widget do HUD: painel de vitais do player (portrait + nome/job/level +
@@ -71,19 +72,19 @@ protected:
TObjectPtr<UTextBlock> ClassText;
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD")
TObjectPtr<UTextBlock> LvlText;
TObjectPtr<URichTextBlock> LvlText;
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD")
TObjectPtr<UTextBlock> HpReadout;
TObjectPtr<URichTextBlock> HpReadout;
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD")
TObjectPtr<UTextBlock> SpReadout;
TObjectPtr<URichTextBlock> SpReadout;
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD")
TObjectPtr<UTextBlock> ExpReadout;
TObjectPtr<URichTextBlock> ExpReadout;
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD")
TObjectPtr<UTextBlock> JobReadout;
TObjectPtr<URichTextBlock> JobReadout;
private:
/** Último snapshot recebido (cache pra ApplyHpSp manter MaxHp/MaxSp). */