diff --git a/Content/ZMMO/UI/HUD/WBP_HUD.uasset b/Content/ZMMO/UI/HUD/WBP_HUD.uasset index 2c72fab..3d7b3be 100644 Binary files a/Content/ZMMO/UI/HUD/WBP_HUD.uasset and b/Content/ZMMO/UI/HUD/WBP_HUD.uasset differ diff --git a/Content/ZMMO/UI/HUD/WBP_PlayerVitalsPanel.uasset b/Content/ZMMO/UI/HUD/WBP_PlayerVitalsPanel.uasset new file mode 100644 index 0000000..9a35810 Binary files /dev/null and b/Content/ZMMO/UI/HUD/WBP_PlayerVitalsPanel.uasset differ diff --git a/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_BaseEXP.uasset b/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_BaseEXP.uasset new file mode 100644 index 0000000..a1303a1 Binary files /dev/null and b/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_BaseEXP.uasset differ diff --git a/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_JobEXP.uasset b/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_JobEXP.uasset new file mode 100644 index 0000000..307ce41 Binary files /dev/null and b/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_JobEXP.uasset differ diff --git a/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_Loading.uasset b/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_Loading.uasset new file mode 100644 index 0000000..73e0bd2 Binary files /dev/null and b/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_Loading.uasset differ diff --git a/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_SP.uasset b/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_SP.uasset new file mode 100644 index 0000000..4e5600c Binary files /dev/null and b/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_SP.uasset differ diff --git a/Source/ZMMO/Game/UI/InGame/ZMMOHudPlayerVitalsWidget.cpp b/Source/ZMMO/Game/UI/InGame/ZMMOHudPlayerVitalsWidget.cpp new file mode 100644 index 0000000..8cdfa0c --- /dev/null +++ b/Source/ZMMO/Game/UI/InGame/ZMMOHudPlayerVitalsWidget.cpp @@ -0,0 +1,96 @@ +#include "ZMMOHudPlayerVitalsWidget.h" + +#include "Components/TextBlock.h" +#include "UI/Widgets/UIProgressBar_Base.h" +#include "Internationalization/Text.h" + +namespace +{ + /** Formata int com separador de milhar do locale (ex.: 9840 -> "9,840"). */ + FText FormatNumber(int64 Value) + { + return FText::AsNumber(Value); + } + + float SafeRatio(int32 Current, int32 Max) + { + return Max > 0 ? FMath::Clamp(static_cast(Current) / static_cast(Max), 0.f, 1.f) : 0.f; + } +} + +void UZMMOHudPlayerVitalsWidget::ApplySnapshot(const FZMMOAttributesSnapshot& Snapshot) +{ + LastSnapshot = Snapshot; + + // HP / SP (pool efetivo — % real) + ApplyHpSp(Snapshot.Hp, Snapshot.MaxHp, Snapshot.Sp, Snapshot.MaxSp); + + // Level + if (LvlText) + { + LvlText->SetText(FText::FromString(FString::Printf(TEXT("LVL %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. + if (ExpReadout) + { + ExpReadout->SetText(FormatNumber(Snapshot.BaseExp)); + } + if (JobReadout) + { + JobReadout->SetText(FormatNumber(Snapshot.JobExp)); + } + + OnSnapshotApplied(Snapshot); +} + +void UZMMOHudPlayerVitalsWidget::ApplyHpSp(int32 Hp, int32 MaxHp, int32 Sp, int32 MaxSp) +{ + LastSnapshot.Hp = Hp; + LastSnapshot.MaxHp = MaxHp; + LastSnapshot.Sp = Sp; + LastSnapshot.MaxSp = MaxSp; + + if (HpBar) + { + HpBar->SetTargetPrimaryLevel(SafeRatio(Hp, MaxHp)); + } + if (SpBar) + { + SpBar->SetTargetPrimaryLevel(SafeRatio(Sp, MaxSp)); + } + if (HpReadout) + { + HpReadout->SetText(FText::Format( + NSLOCTEXT("ZMMOHud", "HpSpReadout", "{0}/{1}"), + FormatNumber(Hp), FormatNumber(MaxHp))); + } + if (SpReadout) + { + SpReadout->SetText(FText::Format( + NSLOCTEXT("ZMMOHud", "HpSpReadout", "{0}/{1}"), + FormatNumber(Sp), FormatNumber(MaxSp))); + } +} + +void UZMMOHudPlayerVitalsWidget::SetCharName(const FString& InName) +{ + if (NameText) + { + NameText->SetText(FText::FromString(InName)); + } +} + +void UZMMOHudPlayerVitalsWidget::SetClassName(const FString& InClassName) +{ + if (ClassText) + { + ClassText->SetText(FText::FromString(InClassName)); + } +} + +void UZMMOHudPlayerVitalsWidget::OnSnapshotApplied_Implementation(const FZMMOAttributesSnapshot& /*Snapshot*/) +{ + // Default vazio — skin do WBP estende se quiser (low-HP flash, etc.). +} diff --git a/Source/ZMMO/Game/UI/InGame/ZMMOHudPlayerVitalsWidget.h b/Source/ZMMO/Game/UI/InGame/ZMMOHudPlayerVitalsWidget.h new file mode 100644 index 0000000..78f1887 --- /dev/null +++ b/Source/ZMMO/Game/UI/InGame/ZMMOHudPlayerVitalsWidget.h @@ -0,0 +1,92 @@ +#pragma once + +#include "CommonUserWidget.h" +#include "CoreMinimal.h" +#include "ZMMOAttributeTypes.h" +#include "ZMMOHudPlayerVitalsWidget.generated.h" + +class UUIProgressBar_Base; +class UTextBlock; + +/** + * Sub-widget do HUD: painel de vitais do player (portrait + nome/job/level + + * barras HP/SP/BASE EXP/JOB EXP). Sem auto-bind no AttributeComponent — o + * parent (UZMMOHudWidget) propaga snapshots via ApplySnapshot/ApplyHpSp. + * + * Barras são UUIProgressBar_Base (material custom Primary/Secondary). HP/SP + * têm pool efetivo no snapshot → % real. BASE/JOB EXP só têm exp acumulado + * (sem "exp-to-next") — barras de EXP ficam pra quando a curva existir; + * por ora o readout mostra o valor absoluto. + * + * WBP filho (WBP_PlayerVitalsPanel) deve ter widgets com EXATAMENTE estes + * nomes: HpBar, SpBar (obrigatórios); ExpBar, JobBar, NameText, ClassText, + * LvlText, HpReadout, SpReadout, ExpReadout, JobReadout (opcionais). + */ +UCLASS(Abstract, Blueprintable, BlueprintType) +class ZMMO_API UZMMOHudPlayerVitalsWidget : public UCommonUserWidget +{ + GENERATED_BODY() + +public: + /** Atualiza todos os campos a partir do snapshot. Chamado pelo UZMMOHudWidget. */ + UFUNCTION(BlueprintCallable, Category = "Zeus|HUD") + void ApplySnapshot(const FZMMOAttributesSnapshot& Snapshot); + + /** Atualiza apenas HP/SP (otimização do tick — sem mexer em level/exp). */ + UFUNCTION(BlueprintCallable, Category = "Zeus|HUD") + void ApplyHpSp(int32 Hp, int32 MaxHp, int32 Sp, int32 MaxSp); + + /** Nome do char — vem do PlayerState (não está no snapshot). */ + UFUNCTION(BlueprintCallable, Category = "Zeus|HUD") + void SetCharName(const FString& InName); + + /** Nome do job/classe — vem do JobsDatabase via ClassId (não é string no snapshot). */ + UFUNCTION(BlueprintCallable, Category = "Zeus|HUD") + void SetClassName(const FString& InClassName); + +protected: + /** Hook BP pra skin reagir (cores low-HP, animações, etc.). */ + UFUNCTION(BlueprintNativeEvent, Category = "Zeus|HUD") + void OnSnapshotApplied(const FZMMOAttributesSnapshot& Snapshot); + virtual void OnSnapshotApplied_Implementation(const FZMMOAttributesSnapshot& Snapshot); + + // === BindWidget — barras (HP/SP obrigatórias) === + UPROPERTY(BlueprintReadOnly, meta = (BindWidget), Category = "Zeus|HUD") + TObjectPtr HpBar; + + UPROPERTY(BlueprintReadOnly, meta = (BindWidget), Category = "Zeus|HUD") + TObjectPtr SpBar; + + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD") + TObjectPtr ExpBar; + + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD") + TObjectPtr JobBar; + + // === BindWidget — textos (todos opcionais) === + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD") + TObjectPtr NameText; + + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD") + TObjectPtr ClassText; + + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD") + TObjectPtr LvlText; + + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD") + TObjectPtr HpReadout; + + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD") + TObjectPtr SpReadout; + + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD") + TObjectPtr ExpReadout; + + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD") + TObjectPtr JobReadout; + +private: + /** Último snapshot recebido (cache pra ApplyHpSp manter MaxHp/MaxSp). */ + UPROPERTY(Transient) + FZMMOAttributesSnapshot LastSnapshot; +}; diff --git a/Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.cpp b/Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.cpp index 1e7c69e..21a8d2b 100644 --- a/Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.cpp +++ b/Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.cpp @@ -10,6 +10,9 @@ #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) @@ -98,12 +101,28 @@ void UZMMOHudWidget::UnbindFromComponent() void UZMMOHudWidget::HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot) { - // Propaga snapshot aos sub-widgets que ja existem. Em V1 so' HpSpBar. + // Propaga snapshot aos sub-widgets que ja existem. if (HpSpBar) { HpSpBar->ApplySnapshot(Snapshot); } - // Futuros: PlayerInfo->ApplySnapshot, Buffs->RefreshIcons, etc. + 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(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) @@ -115,6 +134,10 @@ void UZMMOHudWidget::HandleHpSpChanged(int32 Hp, int32 Sp) { HpSpBar->ApplyHpSp(Hp, Snap.MaxHp, Sp, Snap.MaxSp); } + if (PlayerVitals) + { + PlayerVitals->ApplyHpSp(Hp, Snap.MaxHp, Sp, Snap.MaxSp); + } } } diff --git a/Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.h b/Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.h index 39f3553..d53b532 100644 --- a/Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.h +++ b/Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.h @@ -10,6 +10,7 @@ class UTextBlock; class UWidget; class UZMMOAttributeComponent; class UZMMOHudHpSpWidget; +class UZMMOHudPlayerVitalsWidget; /** * Root da HUD de gameplay. UCommonActivatableWidget pushed pelo @@ -74,6 +75,12 @@ protected: UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD") UZMMOHudHpSpWidget* HpSpBar = nullptr; + /// Painel de vitais novo (portrait + nome/job/level + HP/SP/EXP/JOB bars). + /// Substitui o HpSpBar legado. BindWidgetOptional — coexiste durante a + /// migração; o WBP_HUD pode ter um ou outro. + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD") + UZMMOHudPlayerVitalsWidget* PlayerVitals = nullptr; + /// Container do toast "LEVEL UP!" (Fase 3). Hidden por default; em /// `HandleLevelUp` vira visivel por `LevelUpToastDurationSec` segundos /// (ver `Show/HideLevelUpToast`). WBP_HUD coloca um Border + Image + TextBlock