#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 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(); 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()) { 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()) { Flow->SetState(EZMMOInGameUIState::Playing); } } }