Files
ZMMO/Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.cpp
Mateus Rodrigues e1fca1ae8d feat(ui/hud): painel de vitais (Nome/Job/Level + HP/SP/EXP) substituindo HpSpBar
UZMMOHudPlayerVitalsWidget (novo): sub-widget do HUD com 4 UUIProgressBar_Base
(HP/SP/BASE EXP/JOB EXP) + nome/classe/level. ApplySnapshot anima HP/SP via
SetTargetPrimaryLevel e formata os readouts; ApplyHpSp pro tick rapido.

UZMMOHudWidget propaga o snapshot pro PlayerVitals e resolve identidade: nome
do AZMMOPlayerState.CharName + job via UZMMOJobsLibrary.GetJobDisplayName(ClassId).
HpSpBar legado removido do WBP_HUD.

WBP_PlayerVitalsPanel: layout compacto (520w) com colunas alinhadas (label 28 /
bar FILL / readout 116 right-align) independente do tamanho do numero. Sem portrait.

MIs por barra: UI_M_ProgressBar_SP/BaseEXP/JobEXP/Loading (cores do
hud_layout_modal_New.html).

EXP/JOB readout mostra exp absoluto por ora — barras de EXP aguardam o servidor
enviar exp-to-next no snapshot pra calcular a porcentagem.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:34:03 -03:00

182 lines
5.6 KiB
C++

#include "ZMMOHudWidget.h"
#include "Components/TextBlock.h"
#include "Components/Widget.h"
#include "CommonInputTypeEnum.h" // ECommonInputMode
#include "Engine/World.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/PlayerState.h"
#include "TimerManager.h"
#include "ZMMO.h"
#include "ZMMOAttributeComponent.h"
#include "ZMMOHudHpSpWidget.h"
#include "ZMMOHudPlayerVitalsWidget.h"
#include "ZMMOJobsLibrary.h"
#include "ZMMOPlayerState.h"
UZMMOHudWidget::UZMMOHudWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// HUD nao bloqueia "back" (Esc / B no controller). Menus reais (StatusWindow,
// EscapeMenu) sim — eles setam bIsBackHandler=true.
bIsBackHandler = false;
// HUD nao reativa stack ao virar topo (e' sempre fundo). Garante que o
// UCommonActivatableWidgetStack do Layer.Game nao force activate/deactivate.
bAutoActivate = true;
}
TOptional<FUIInputConfig> UZMMOHudWidget::GetDesiredInputConfig() const
{
// Modo Game: input flui pro PlayerController/Pawn, mouse capturado (3a pessoa).
// Sem isso, CommonUI assume Menu config (mostra cursor + bloqueia movimento).
return FUIInputConfig(ECommonInputMode::Game, EMouseCaptureMode::CapturePermanently);
}
void UZMMOHudWidget::NativeOnActivated()
{
Super::NativeOnActivated();
// HUD nao intercepta cliques — mouse passa direto pro Pawn (camera, etc.).
// Sub-widgets que precisam de click (futuro: hotkeys) podem override.
SetVisibility(ESlateVisibility::HitTestInvisible);
// Toast comeca escondido — so' aparece em HandleLevelUp.
HideLevelUpToast();
BindToLocalPlayer();
UE_LOG(LogZMMO, Log, TEXT("ZMMOHudWidget activated (HpSpBar=%s)"),
HpSpBar ? *HpSpBar->GetName() : TEXT("none"));
}
void UZMMOHudWidget::NativeOnDeactivated()
{
UnbindFromComponent();
Super::NativeOnDeactivated();
}
void UZMMOHudWidget::NativeDestruct()
{
UnbindFromComponent();
Super::NativeDestruct();
}
void UZMMOHudWidget::BindToLocalPlayer()
{
UWorld* World = GetWorld();
if (!World) { return; }
APlayerController* PC = World->GetFirstPlayerController();
if (!PC) { return; }
APlayerState* PS = PC->PlayerState;
if (!PS)
{
// PlayerState pode nao existir ainda — race com GameMode setup.
// AZMMOPlayerCharacter::HandleLocalSpawnReady chama BindToAttributeComponent
// manualmente como fallback (seed do EntityId).
UE_LOG(LogZMMO, Log, TEXT("ZMMOHudWidget: PlayerState ausente no activate (race fix via HandleLocalSpawnReady esperado)"));
return;
}
UZMMOAttributeComponent* Comp = PS->FindComponentByClass<UZMMOAttributeComponent>();
if (!Comp) { return; }
if (Comp == BoundComponent.Get()) { return; } // idempotente
UnbindFromComponent();
BoundComponent = Comp;
Comp->OnAttributesChanged.AddDynamic(this, &UZMMOHudWidget::HandleAttributesChanged);
Comp->OnHpSpChanged.AddDynamic(this, &UZMMOHudWidget::HandleHpSpChanged);
Comp->OnLevelUp.AddDynamic(this, &UZMMOHudWidget::HandleLevelUp);
// Refresh imediato — caso o snapshot ja' tenha chegado.
HandleAttributesChanged(Comp->GetSnapshot());
}
void UZMMOHudWidget::UnbindFromComponent()
{
if (UZMMOAttributeComponent* Old = BoundComponent.Get())
{
Old->OnAttributesChanged.RemoveDynamic(this, &UZMMOHudWidget::HandleAttributesChanged);
Old->OnHpSpChanged.RemoveDynamic(this, &UZMMOHudWidget::HandleHpSpChanged);
Old->OnLevelUp.RemoveDynamic(this, &UZMMOHudWidget::HandleLevelUp);
}
BoundComponent.Reset();
}
void UZMMOHudWidget::HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot)
{
// Propaga snapshot aos sub-widgets que ja existem.
if (HpSpBar)
{
HpSpBar->ApplySnapshot(Snapshot);
}
if (PlayerVitals)
{
PlayerVitals->ApplySnapshot(Snapshot);
// Identidade: nome vem do PlayerState (não está no snapshot); job vem
// do JobsSubsystem resolvendo ClassId -> display name.
if (UZMMOAttributeComponent* Comp = BoundComponent.Get())
{
if (const AZMMOPlayerState* PS = Cast<AZMMOPlayerState>(Comp->GetOwner()))
{
PlayerVitals->SetCharName(PS->CharName);
}
}
PlayerVitals->SetClassName(
UZMMOJobsLibrary::GetJobDisplayName(this, Snapshot.ClassId).ToString());
}
// Futuros: Buffs->RefreshIcons, etc.
}
void UZMMOHudWidget::HandleHpSpChanged(int32 Hp, int32 Sp)
{
if (UZMMOAttributeComponent* Comp = BoundComponent.Get())
{
const FZMMOAttributesSnapshot& Snap = Comp->GetSnapshot();
if (HpSpBar)
{
HpSpBar->ApplyHpSp(Hp, Snap.MaxHp, Sp, Snap.MaxSp);
}
if (PlayerVitals)
{
PlayerVitals->ApplyHpSp(Hp, Snap.MaxHp, Sp, Snap.MaxSp);
}
}
}
void UZMMOHudWidget::HandleLevelUp(int32 NewBaseLevel, int32 StatusPointDelta)
{
// Snapshot subsequente atualiza HpSpBar (level/maxhp/maxsp). Aqui mostra
// toast visual instantaneo. StatusPointDelta nao usado no texto (UI mostra
// no StatusWindow). Polish futuro: SFX + particle aura.
UE_LOG(LogZMMO, Log, TEXT("[HUD] LEVEL UP! newLevel=%d +sp=%d"), NewBaseLevel, StatusPointDelta);
ShowLevelUpToast(NewBaseLevel);
}
void UZMMOHudWidget::ShowLevelUpToast(int32 NewBaseLevel)
{
if (LevelUpToastText)
{
LevelUpToastText->SetText(FText::FromString(
FString::Printf(TEXT("LEVEL UP! Lv %d"), NewBaseLevel)));
}
if (LevelUpToastContainer)
{
LevelUpToastContainer->SetVisibility(ESlateVisibility::HitTestInvisible);
}
// Cancela timer anterior (caso level up duplo em sequencia) + agenda hide.
if (UWorld* World = GetWorld())
{
World->GetTimerManager().ClearTimer(LevelUpToastHideTimer);
World->GetTimerManager().SetTimer(LevelUpToastHideTimer,
FTimerDelegate::CreateUObject(this, &UZMMOHudWidget::HideLevelUpToast),
LevelUpToastDurationSec, /*loop*/ false);
}
}
void UZMMOHudWidget::HideLevelUpToast()
{
if (LevelUpToastContainer)
{
LevelUpToastContainer->SetVisibility(ESlateVisibility::Collapsed);
}
}