- 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>
221 lines
7.2 KiB
C++
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);
|
|
}
|
|
}
|
|
}
|