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>
182 lines
5.6 KiB
C++
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);
|
|
}
|
|
}
|