Files
ZMMO/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.cpp
Mateus Rodrigues 9390ed9da9 feat(ui/status): nova janela com sub-widgets StatRow/BonusRow + cleanup
- 3 novas classes C++ (UUIPlayerStatus_Window/StatRow/BonusRow) substituem
  UZMMOStatusWindowWidget (removida); StatRow/BonusRow sao reutilizaveis
  via BindWidgetOptional + delegate OnPlusClicked(StatId).
- Window bindea no UZMMOAttributeComponent (PlayerState) e popula header
  (CharName, Job via UZMMOJobsSubsystem, BaseLevel/JobLevel) + 14 rows
  (6 primarios em StatRow com botao "+", 8 derivados em BonusRow).
- Fix spam-click do "+": bPlusButtonsLocked desabilita botoes ate
  OnStatAllocReply do server ou proximo snapshot.
- WBPs movidos pra /UI/Screens/PlayerStatus/, material pra /UI/Materials/.
- 58 widgets renomeados seguindo convencao
  (Frame_/Layout_/Panel_/Container_/Border_/Text_/Image_/Spacer_/List_).
- DA_InGameScreenSet auto-atualizado pelo move.
- Remove WBP_StatusWindow inline (legado).

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

221 lines
7.2 KiB
C++

#include "UIPlayerStatus_Window.h"
#include "CommonInputTypeEnum.h"
#include "Components/Button.h"
#include "Components/TextBlock.h"
#include "Engine/World.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/PlayerState.h"
#include "Internationalization/Text.h"
#include "ZMMO.h"
#include "ZMMOAttributeComponent.h"
#include "ZMMOJobDataAsset.h"
#include "ZMMOJobsSubsystem.h"
#include "UI/InGame/UIInGameFlowSubsystem.h"
#include "UI/InGameTypes.h"
#include "UI/Widgets/UIPlayerStatus_BonusRow.h"
#include "UI/Widgets/UIPlayerStatus_StatRow.h"
UUIPlayerStatus_Window::UUIPlayerStatus_Window(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bIsBackHandler = true;
bAutoActivate = true;
}
TOptional<FUIInputConfig> UUIPlayerStatus_Window::GetDesiredInputConfig() const
{
return FUIInputConfig(ECommonInputMode::Menu, EMouseCaptureMode::NoCapture);
}
void UUIPlayerStatus_Window::NativeOnActivated()
{
Super::NativeOnActivated();
BindRow(Row_Str, 0, TEXT("S"), TEXT("STR"));
BindRow(Row_Agi, 1, TEXT("A"), TEXT("AGI"));
BindRow(Row_Vit, 2, TEXT("V"), TEXT("VIT"));
BindRow(Row_Int, 3, TEXT("I"), TEXT("INT"));
BindRow(Row_Dex, 4, TEXT("D"), TEXT("DEX"));
BindRow(Row_Luk, 5, TEXT("L"), TEXT("LUK"));
if (Row_Atk) Row_Atk->SetStatName(FText::FromString(TEXT("ATK")));
if (Row_Matk) Row_Matk->SetStatName(FText::FromString(TEXT("MATK")));
if (Row_Def) Row_Def->SetStatName(FText::FromString(TEXT("DEF")));
if (Row_Mdef) Row_Mdef->SetStatName(FText::FromString(TEXT("MDEF")));
if (Row_Hit) Row_Hit->SetStatName(FText::FromString(TEXT("HIT")));
if (Row_Flee) Row_Flee->SetStatName(FText::FromString(TEXT("FLEE")));
if (Row_Crit) Row_Crit->SetStatName(FText::FromString(TEXT("CRIT")));
if (Row_Aspd) Row_Aspd->SetStatName(FText::FromString(TEXT("ASPD")));
if (TitleText) TitleText->SetText(FText::FromString(TEXT("Status")));
if (CloseBtn) CloseBtn->OnClicked.AddDynamic(this, &UUIPlayerStatus_Window::OnCloseClicked);
BindToLocalPlayer();
UE_LOG(LogZMMO, Log, TEXT("UIPlayerStatus_Window activated"));
}
void UUIPlayerStatus_Window::NativeOnDeactivated()
{
UnbindFromComponent();
auto UnbindRow = [this](UUIPlayerStatus_StatRow* Row)
{
if (Row) Row->OnPlusClicked.RemoveDynamic(this, &UUIPlayerStatus_Window::HandleRowPlusClicked);
};
UnbindRow(Row_Str); UnbindRow(Row_Agi); UnbindRow(Row_Vit);
UnbindRow(Row_Int); UnbindRow(Row_Dex); UnbindRow(Row_Luk);
if (CloseBtn) CloseBtn->OnClicked.RemoveDynamic(this, &UUIPlayerStatus_Window::OnCloseClicked);
Super::NativeOnDeactivated();
}
void UUIPlayerStatus_Window::BindRow(UUIPlayerStatus_StatRow* Row, int32 StatId, const TCHAR* IconLetter, const TCHAR* DisplayName)
{
if (!Row) return;
Row->SetupStat(StatId, FText::FromString(IconLetter), FText::FromString(DisplayName));
Row->OnPlusClicked.AddDynamic(this, &UUIPlayerStatus_Window::HandleRowPlusClicked);
}
void UUIPlayerStatus_Window::BindToLocalPlayer()
{
UWorld* World = GetWorld();
if (!World) return;
APlayerController* PC = World->GetFirstPlayerController();
if (!PC || !PC->PlayerState) return;
// CharName vem do PlayerState (estável por sessão) — popula uma vez.
if (Text_CharName)
{
Text_CharName->SetText(FText::FromString(PC->PlayerState->GetPlayerName()));
}
UZMMOAttributeComponent* Comp = PC->PlayerState->FindComponentByClass<UZMMOAttributeComponent>();
if (!Comp || Comp == BoundComponent.Get()) return;
UnbindFromComponent();
BoundComponent = Comp;
Comp->OnAttributesChanged.AddDynamic(this, &UUIPlayerStatus_Window::HandleAttributesChanged);
Comp->OnStatAllocReply.AddDynamic(this, &UUIPlayerStatus_Window::HandleStatAllocReply);
RefreshFromSnapshot(Comp->GetSnapshot());
}
void UUIPlayerStatus_Window::UnbindFromComponent()
{
if (UZMMOAttributeComponent* Old = BoundComponent.Get())
{
Old->OnAttributesChanged.RemoveDynamic(this, &UUIPlayerStatus_Window::HandleAttributesChanged);
Old->OnStatAllocReply.RemoveDynamic(this, &UUIPlayerStatus_Window::HandleStatAllocReply);
}
BoundComponent.Reset();
}
void UUIPlayerStatus_Window::HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot)
{
bPlusButtonsLocked = false;
RefreshFromSnapshot(Snapshot);
}
void UUIPlayerStatus_Window::HandleStatAllocReply(bool bAccepted, int32 Reason)
{
UE_LOG(LogZMMO, Log, TEXT("UIPlayerStatus_Window: alloc reply accepted=%d reason=%d"),
bAccepted ? 1 : 0, Reason);
bPlusButtonsLocked = false;
if (UZMMOAttributeComponent* Comp = BoundComponent.Get())
{
const FZMMOAttributesSnapshot& S = Comp->GetSnapshot();
SetAllPrimaryRowsPlusEnabled(S.StatusPoint > 0);
}
}
void UUIPlayerStatus_Window::HandleRowPlusClicked(int32 StatId)
{
if (bPlusButtonsLocked) return;
UZMMOAttributeComponent* Comp = BoundComponent.Get();
if (!Comp) return;
bPlusButtonsLocked = true;
SetAllPrimaryRowsPlusEnabled(false);
Comp->RequestStatAlloc(StatId, /*Amount*/ 1);
}
void UUIPlayerStatus_Window::SetAllPrimaryRowsPlusEnabled(bool bEnabled)
{
auto Set = [bEnabled](UUIPlayerStatus_StatRow* Row)
{
if (Row) Row->SetPlusEnabled(bEnabled);
};
Set(Row_Str); Set(Row_Agi); Set(Row_Vit);
Set(Row_Int); Set(Row_Dex); Set(Row_Luk);
}
void UUIPlayerStatus_Window::RefreshFromSnapshot(const FZMMOAttributesSnapshot& S)
{
// Primários: snapshot atual não splita base/bonus de primários (só derivados).
if (Row_Str) Row_Str->SetValues(S.Str, 0);
if (Row_Agi) Row_Agi->SetValues(S.Agi, 0);
if (Row_Vit) Row_Vit->SetValues(S.Vit, 0);
if (Row_Int) Row_Int->SetValues(S.Int, 0);
if (Row_Dex) Row_Dex->SetValues(S.Dex, 0);
if (Row_Luk) Row_Luk->SetValues(S.Luk, 0);
// Header (level/job).
if (Text_BaseLevel_Value) Text_BaseLevel_Value->SetText(FText::AsNumber(S.BaseLevel));
if (Text_JobLevel_Value) Text_JobLevel_Value->SetText(FText::AsNumber(S.JobLevel));
if (Text_Job)
{
FText JobName;
if (UGameInstance* GI = GetGameInstance())
{
if (const UZMMOJobsSubsystem* Jobs = GI->GetSubsystem<UZMMOJobsSubsystem>())
{
if (const UZMMOJobDataAsset* Data = Jobs->GetJobData(S.ClassId))
{
JobName = Data->DisplayName;
}
}
}
if (JobName.IsEmpty())
{
JobName = FText::FromString(FString::Printf(TEXT("Job %d"), S.ClassId));
}
Text_Job->SetText(JobName);
}
if (StatusPointText)
{
StatusPointText->SetText(FText::FromString(
FString::Printf(TEXT("Status Points: %d"), S.StatusPoint)));
}
// Derivados (server-authoritative: base + equip).
if (Row_Atk) Row_Atk->SetValues(S.AtkBase, S.AtkEquipBonus);
if (Row_Matk) Row_Matk->SetRangeValues(S.MatkBaseMin, S.MatkBaseMax, S.MatkEquipBonus);
if (Row_Def) Row_Def->SetValues(S.DefBase, S.DefEquipBonus);
if (Row_Mdef) Row_Mdef->SetValues(S.MdefBase, S.MdefEquipBonus);
if (Row_Hit) Row_Hit->SetValues(S.HitBase, S.HitEquipBonus);
if (Row_Flee) Row_Flee->SetValues(S.FleeBase, S.FleeEquipBonus);
if (Row_Crit) Row_Crit->SetValuesX10(S.CritBaseX10, S.CritEquipBonusX10);
// ASPD não splita.
if (Row_Aspd) Row_Aspd->SetValues(S.Aspd, 0);
const bool bCanAlloc = !bPlusButtonsLocked && S.StatusPoint > 0;
SetAllPrimaryRowsPlusEnabled(bCanAlloc);
}
void UUIPlayerStatus_Window::OnCloseClicked()
{
if (UGameInstance* GI = GetGameInstance())
{
if (UUIInGameFlowSubsystem* Flow = GI->GetSubsystem<UUIInGameFlowSubsystem>())
{
Flow->SetState(EZMMOInGameUIState::Playing);
}
}
}