diff --git a/Content/ZMMO/UI/HUD/WBP_StatusWindow.uasset b/Content/ZMMO/UI/HUD/WBP_StatusWindow.uasset deleted file mode 100644 index 53f54bc..0000000 Binary files a/Content/ZMMO/UI/HUD/WBP_StatusWindow.uasset and /dev/null differ diff --git a/Content/ZMMO/UI/InGame/DA_InGameScreenSet.uasset b/Content/ZMMO/UI/InGame/DA_InGameScreenSet.uasset index 1c5275b..b16070b 100644 Binary files a/Content/ZMMO/UI/InGame/DA_InGameScreenSet.uasset and b/Content/ZMMO/UI/InGame/DA_InGameScreenSet.uasset differ diff --git a/Content/ZMMO/UI/Materials/M_UI_Gradient_TopToBottom.uasset b/Content/ZMMO/UI/Materials/M_UI_Gradient_TopToBottom.uasset new file mode 100644 index 0000000..3c7a7d3 Binary files /dev/null and b/Content/ZMMO/UI/Materials/M_UI_Gradient_TopToBottom.uasset differ diff --git a/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_BonusRow.uasset b/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_BonusRow.uasset new file mode 100644 index 0000000..1b74989 Binary files /dev/null and b/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_BonusRow.uasset differ diff --git a/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_StatRow.uasset b/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_StatRow.uasset new file mode 100644 index 0000000..34fbf4b Binary files /dev/null and b/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_StatRow.uasset differ diff --git a/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_Window.uasset b/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_Window.uasset new file mode 100644 index 0000000..2490686 Binary files /dev/null and b/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_Window.uasset differ diff --git a/Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.cpp b/Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.cpp deleted file mode 100644 index c100af9..0000000 --- a/Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.cpp +++ /dev/null @@ -1,169 +0,0 @@ -#include "ZMMOStatusWindowWidget.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 "UI/InGame/UIInGameFlowSubsystem.h" -#include "UI/InGameTypes.h" - -UZMMOStatusWindowWidget::UZMMOStatusWindowWidget(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - bIsBackHandler = true; // Esc/B fecha - bAutoActivate = true; -} - -TOptional UZMMOStatusWindowWidget::GetDesiredInputConfig() const -{ - // Modo Menu: cursor visivel, input flui pra UI (clicks nos botões), - // movimento bloqueado. Padrao pra menus modais. - return FUIInputConfig(ECommonInputMode::Menu, EMouseCaptureMode::NoCapture); -} - -void UZMMOStatusWindowWidget::NativeOnActivated() -{ - Super::NativeOnActivated(); - BindToLocalPlayer(); - - if (PlusBtn_Str) PlusBtn_Str->OnClicked.AddDynamic(this, &UZMMOStatusWindowWidget::OnPlusStr); - if (PlusBtn_Agi) PlusBtn_Agi->OnClicked.AddDynamic(this, &UZMMOStatusWindowWidget::OnPlusAgi); - if (PlusBtn_Vit) PlusBtn_Vit->OnClicked.AddDynamic(this, &UZMMOStatusWindowWidget::OnPlusVit); - if (PlusBtn_Int) PlusBtn_Int->OnClicked.AddDynamic(this, &UZMMOStatusWindowWidget::OnPlusInt); - if (PlusBtn_Dex) PlusBtn_Dex->OnClicked.AddDynamic(this, &UZMMOStatusWindowWidget::OnPlusDex); - if (PlusBtn_Luk) PlusBtn_Luk->OnClicked.AddDynamic(this, &UZMMOStatusWindowWidget::OnPlusLuk); - if (CloseBtn) CloseBtn->OnClicked.AddDynamic(this, &UZMMOStatusWindowWidget::OnCloseClicked); - - UE_LOG(LogZMMO, Log, TEXT("ZMMOStatusWindow activated")); -} - -void UZMMOStatusWindowWidget::NativeOnDeactivated() -{ - UnbindFromComponent(); - - if (PlusBtn_Str) PlusBtn_Str->OnClicked.RemoveDynamic(this, &UZMMOStatusWindowWidget::OnPlusStr); - if (PlusBtn_Agi) PlusBtn_Agi->OnClicked.RemoveDynamic(this, &UZMMOStatusWindowWidget::OnPlusAgi); - if (PlusBtn_Vit) PlusBtn_Vit->OnClicked.RemoveDynamic(this, &UZMMOStatusWindowWidget::OnPlusVit); - if (PlusBtn_Int) PlusBtn_Int->OnClicked.RemoveDynamic(this, &UZMMOStatusWindowWidget::OnPlusInt); - if (PlusBtn_Dex) PlusBtn_Dex->OnClicked.RemoveDynamic(this, &UZMMOStatusWindowWidget::OnPlusDex); - if (PlusBtn_Luk) PlusBtn_Luk->OnClicked.RemoveDynamic(this, &UZMMOStatusWindowWidget::OnPlusLuk); - if (CloseBtn) CloseBtn->OnClicked.RemoveDynamic(this, &UZMMOStatusWindowWidget::OnCloseClicked); - - Super::NativeOnDeactivated(); -} - -void UZMMOStatusWindowWidget::BindToLocalPlayer() -{ - UWorld* World = GetWorld(); - if (!World) { return; } - APlayerController* PC = World->GetFirstPlayerController(); - if (!PC || !PC->PlayerState) { return; } - UZMMOAttributeComponent* Comp = PC->PlayerState->FindComponentByClass(); - if (!Comp || Comp == BoundComponent.Get()) { return; } - - UnbindFromComponent(); - BoundComponent = Comp; - Comp->OnAttributesChanged.AddDynamic(this, &UZMMOStatusWindowWidget::HandleAttributesChanged); - Comp->OnStatAllocReply.AddDynamic(this, &UZMMOStatusWindowWidget::HandleStatAllocReply); - RefreshFromSnapshot(Comp->GetSnapshot()); -} - -void UZMMOStatusWindowWidget::UnbindFromComponent() -{ - if (UZMMOAttributeComponent* Old = BoundComponent.Get()) - { - Old->OnAttributesChanged.RemoveDynamic(this, &UZMMOStatusWindowWidget::HandleAttributesChanged); - Old->OnStatAllocReply.RemoveDynamic(this, &UZMMOStatusWindowWidget::HandleStatAllocReply); - } - BoundComponent.Reset(); -} - -void UZMMOStatusWindowWidget::HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot) -{ - RefreshFromSnapshot(Snapshot); -} - -void UZMMOStatusWindowWidget::HandleStatAllocReply(bool bAccepted, int32 Reason) -{ - UE_LOG(LogZMMO, Log, TEXT("StatusWindow: alloc reply accepted=%d reason=%d"), - bAccepted ? 1 : 0, Reason); - // Snapshot subsequente vai disparar HandleAttributesChanged e atualizar UI. - // Aqui poderia popup de erro visual quando bAccepted=false (TODO polish). -} - -void UZMMOStatusWindowWidget::RefreshFromSnapshot(const FZMMOAttributesSnapshot& S) -{ - if (StrText) StrText->SetText(FText::FromString(FString::Printf(TEXT("STR %d"), S.Str))); - if (AgiText) AgiText->SetText(FText::FromString(FString::Printf(TEXT("AGI %d"), S.Agi))); - if (VitText) VitText->SetText(FText::FromString(FString::Printf(TEXT("VIT %d"), S.Vit))); - if (IntText) IntText->SetText(FText::FromString(FString::Printf(TEXT("INT %d"), S.Int))); - if (DexText) DexText->SetText(FText::FromString(FString::Printf(TEXT("DEX %d"), S.Dex))); - if (LukText) LukText->SetText(FText::FromString(FString::Printf(TEXT("LUK %d"), S.Luk))); - if (StatusPointText) - { - StatusPointText->SetText(FText::FromString( - FString::Printf(TEXT("Status Points: %d"), S.StatusPoint))); - } - - // Derivados — formato RO "STAT base + equip" (ex.: "ATK 51 + 0"). - // Servidor manda os dois separados; cliente NUNCA recalcula. - if (AtkText) AtkText->SetText(FText::FromString(FString::Printf(TEXT("ATK %d + %d"), S.AtkBase, S.AtkEquipBonus))); - // MATK pre-renewal: range "min ~ max + equip" (rathena status_base_matk_min/max). - if (MatkText) MatkText->SetText(FText::FromString(FString::Printf(TEXT("MATK %d ~ %d + %d"), S.MatkBaseMin, S.MatkBaseMax, S.MatkEquipBonus))); - if (DefText) DefText->SetText(FText::FromString(FString::Printf(TEXT("DEF %d + %d"), S.DefBase, S.DefEquipBonus))); - if (MdefText) MdefText->SetText(FText::FromString(FString::Printf(TEXT("MDEF %d + %d"), S.MdefBase, S.MdefEquipBonus))); - if (HitText) HitText->SetText(FText::FromString(FString::Printf(TEXT("HIT %d + %d"), S.HitBase, S.HitEquipBonus))); - if (FleeText) FleeText->SetText(FText::FromString(FString::Printf(TEXT("FLEE %d + %d"), S.FleeBase, S.FleeEquipBonus))); - if (CritText) - { - // CRIT vem ×10 em ambas as camadas — soma X10 antes de converter pra - // decimal (10 = 1.0). Display: "CRIT base + equip" com 1 casa cada. - const int32 BaseInt = S.CritBaseX10 / 10; - const int32 BaseFrac = S.CritBaseX10 % 10; - const int32 EquipInt = S.CritEquipBonusX10 / 10; - const int32 EquipFrac = S.CritEquipBonusX10 % 10; - CritText->SetText(FText::FromString(FString::Printf( - TEXT("CRIT %d.%d + %d.%d"), BaseInt, BaseFrac, EquipInt, EquipFrac))); - } - // ASPD nao splita (calculo nao-linear server-side via aspd_base × stats). - if (AspdText) AspdText->SetText(FText::FromString(FString::Printf(TEXT("ASPD %d"), S.Aspd))); - // Habilita botões `+` somente se ha status_point disponível. - const bool bCanAlloc = S.StatusPoint > 0; - if (PlusBtn_Str) PlusBtn_Str->SetIsEnabled(bCanAlloc); - if (PlusBtn_Agi) PlusBtn_Agi->SetIsEnabled(bCanAlloc); - if (PlusBtn_Vit) PlusBtn_Vit->SetIsEnabled(bCanAlloc); - if (PlusBtn_Int) PlusBtn_Int->SetIsEnabled(bCanAlloc); - if (PlusBtn_Dex) PlusBtn_Dex->SetIsEnabled(bCanAlloc); - if (PlusBtn_Luk) PlusBtn_Luk->SetIsEnabled(bCanAlloc); -} - -void UZMMOStatusWindowWidget::RequestAlloc(int32 StatId) -{ - if (UZMMOAttributeComponent* Comp = BoundComponent.Get()) - { - Comp->RequestStatAlloc(StatId, /*Amount*/ 1); - } -} - -void UZMMOStatusWindowWidget::OnPlusStr() { RequestAlloc(0); } -void UZMMOStatusWindowWidget::OnPlusAgi() { RequestAlloc(1); } -void UZMMOStatusWindowWidget::OnPlusVit() { RequestAlloc(2); } -void UZMMOStatusWindowWidget::OnPlusInt() { RequestAlloc(3); } -void UZMMOStatusWindowWidget::OnPlusDex() { RequestAlloc(4); } -void UZMMOStatusWindowWidget::OnPlusLuk() { RequestAlloc(5); } - -void UZMMOStatusWindowWidget::OnCloseClicked() -{ - if (UGameInstance* GI = GetGameInstance()) - { - if (UUIInGameFlowSubsystem* Flow = GI->GetSubsystem()) - { - Flow->SetState(EZMMOInGameUIState::Playing); - } - } -} diff --git a/Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.h b/Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.h deleted file mode 100644 index af8bcef..0000000 --- a/Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.h +++ /dev/null @@ -1,106 +0,0 @@ -#pragma once - -#include "CommonActivatableWidget.h" -#include "CoreMinimal.h" -#include "ZMMOAttributeTypes.h" -#include "ZMMOStatusWindowWidget.generated.h" - -class UButton; -class UTextBlock; -class UCommonButtonBase; -class UZMMOAttributeComponent; - -/** - * Tela "Status" — atributos do jogador + alocacao via botões `+`. - * - * Modo Menu: cursor visível, movimento bloqueado, Esc fecha. Push em - * UI.Layer.GameMenu pelo `UUIInGameFlowSubsystem::ToggleScreen(StatusWindow)`. - * HUD continua visível em UI.Layer.Game por baixo. - * - * Composicao do WBP_StatusWindow (BindWidgetOptional pra permitir variantes): - * - Labels de stats primarios: StrText / AgiText / VitText / IntText / DexText / LukText - * - Botões +: PlusBtn_Str / PlusBtn_Agi / PlusBtn_Vit / PlusBtn_Int / PlusBtn_Dex / PlusBtn_Luk - * - Derivados (server-authoritative): AtkText / MatkText / DefText / MdefText / - * HitText / FleeText / CritText / AspdText - * - StatusPointText (pontos disponíveis pra alocar) - * - CloseBtn (fecha — fallback do Esc/back handler) - * - * Workflow: - * 1. NativeOnActivated: busca AttributeComponent (PlayerState do player local) - * 2. Subscreve OnAttributesChanged + OnStatAllocReply - * 3. Refresh inicial via snapshot atual - * 4. Clique no `+X` chama `Comp->RequestStatAlloc(X, 1)` → server valida - * 5. Server responde com S_ATTRIBUTE_STAT_ALLOC_OK + (se ok) snapshot novo - * 6. UI atualiza via delegate - */ -UCLASS(Abstract, Blueprintable, BlueprintType) -class ZMMO_API UZMMOStatusWindowWidget : public UCommonActivatableWidget -{ - GENERATED_BODY() - -public: - UZMMOStatusWindowWidget(const FObjectInitializer& ObjectInitializer); - -protected: - virtual void NativeOnActivated() override; - virtual void NativeOnDeactivated() override; - - /// Modo Menu: cursor visível + bloqueia input de gameplay (movimento). - virtual TOptional GetDesiredInputConfig() const override; - - /// Texto dos stats (label + valor). BindWidgetOptional pra permitir - /// WBPs custom (ex.: sem display de SP em variantes). - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UTextBlock* StrText = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UTextBlock* AgiText = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UTextBlock* VitText = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UTextBlock* IntText = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UTextBlock* DexText = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UTextBlock* LukText = nullptr; - - /// Pontos restantes pra alocar + label "Status Points: N". - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UTextBlock* StatusPointText = nullptr; - - /// Derivados (sempre calculados pelo servidor — cliente NUNCA recalcula). - /// Mostra `base + bonus` quando aplicavel (camadas equip/buff vem do server - /// pre-somadas no snapshot). BindWidgetOptional pra suportar HUDs minimalistas. - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* AtkText = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* MatkText = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* DefText = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* MdefText = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* HitText = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* FleeText = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* CritText = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* AspdText = nullptr; - - /// Botoes + por stat. - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UButton* PlusBtn_Str = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UButton* PlusBtn_Agi = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UButton* PlusBtn_Vit = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UButton* PlusBtn_Int = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UButton* PlusBtn_Dex = nullptr; - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UButton* PlusBtn_Luk = nullptr; - - /// Botao Close (fallback do Esc/back). - UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UButton* CloseBtn = nullptr; - -private: - void BindToLocalPlayer(); - void UnbindFromComponent(); - void RefreshFromSnapshot(const FZMMOAttributesSnapshot& Snap); - - UFUNCTION() void HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot); - UFUNCTION() void HandleStatAllocReply(bool bAccepted, int32 Reason); - - UFUNCTION() void OnPlusStr(); - UFUNCTION() void OnPlusAgi(); - UFUNCTION() void OnPlusVit(); - UFUNCTION() void OnPlusInt(); - UFUNCTION() void OnPlusDex(); - UFUNCTION() void OnPlusLuk(); - UFUNCTION() void OnCloseClicked(); - - void RequestAlloc(int32 StatId); - - UPROPERTY(Transient) - TWeakObjectPtr BoundComponent; -}; diff --git a/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_BonusRow.cpp b/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_BonusRow.cpp new file mode 100644 index 0000000..411b4c8 --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_BonusRow.cpp @@ -0,0 +1,69 @@ +#include "UIPlayerStatus_BonusRow.h" + +#include "Components/TextBlock.h" +#include "Internationalization/Text.h" + +void UUIPlayerStatus_BonusRow::SetStatName(FText DisplayName) +{ + if (Text_StatName) Text_StatName->SetText(DisplayName); +} + +void UUIPlayerStatus_BonusRow::SetValues(int32 Base, int32 Bonus) +{ + if (Text_BasePart) Text_BasePart->SetText(FText::AsNumber(Base)); + + const bool bShowBonus = (Bonus != 0); + SetBonusVisibility(bShowBonus); + if (bShowBonus) + { + if (Text_SplitOp) Text_SplitOp->SetText(FText::FromString(Bonus > 0 ? TEXT("+") : TEXT("-"))); + if (Text_BonusPart) Text_BonusPart->SetText(FText::AsNumber(FMath::Abs(Bonus))); + } +} + +void UUIPlayerStatus_BonusRow::SetRangeValues(int32 MinBase, int32 MaxBase, int32 Bonus) +{ + if (Text_BasePart) + { + Text_BasePart->SetText(FText::FromString(FString::Printf(TEXT("%d~%d"), MinBase, MaxBase))); + } + + const bool bShowBonus = (Bonus != 0); + SetBonusVisibility(bShowBonus); + if (bShowBonus) + { + if (Text_SplitOp) Text_SplitOp->SetText(FText::FromString(Bonus > 0 ? TEXT("+") : TEXT("-"))); + if (Text_BonusPart) Text_BonusPart->SetText(FText::AsNumber(FMath::Abs(Bonus))); + } +} + +void UUIPlayerStatus_BonusRow::SetValuesX10(int32 BaseX10, int32 BonusX10) +{ + auto FormatX10 = [](int32 ValX10) -> FString + { + const int32 Sign = ValX10 < 0 ? -1 : 1; + const int32 Abs = FMath::Abs(ValX10); + return FString::Printf(TEXT("%d.%d"), Sign * (Abs / 10), Abs % 10); + }; + + if (Text_BasePart) Text_BasePart->SetText(FText::FromString(FormatX10(BaseX10))); + + const bool bShowBonus = (BonusX10 != 0); + SetBonusVisibility(bShowBonus); + if (bShowBonus) + { + if (Text_SplitOp) Text_SplitOp->SetText(FText::FromString(BonusX10 > 0 ? TEXT("+") : TEXT("-"))); + if (Text_BonusPart) + { + const int32 Abs = FMath::Abs(BonusX10); + Text_BonusPart->SetText(FText::FromString(FString::Printf(TEXT("%d.%d"), Abs / 10, Abs % 10))); + } + } +} + +void UUIPlayerStatus_BonusRow::SetBonusVisibility(bool bShow) +{ + const ESlateVisibility Vis = bShow ? ESlateVisibility::Visible : ESlateVisibility::Collapsed; + if (Text_SplitOp) Text_SplitOp->SetVisibility(Vis); + if (Text_BonusPart) Text_BonusPart->SetVisibility(Vis); +} diff --git a/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_BonusRow.h b/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_BonusRow.h new file mode 100644 index 0000000..95a0c77 --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_BonusRow.h @@ -0,0 +1,46 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CommonUserWidget.h" +#include "UIPlayerStatus_BonusRow.generated.h" + +class UTextBlock; + +/** + * Linha de um stat derivado (ATK/DEF/HIT/etc.) na janela de Status. + * Layout: nome + valor base + (separador + bônus). Sem botão. + * + * Três modos de valor: + * - SetValues(base, bonus) "10 + 2" + * - SetRangeValues(min, max, bonus) "10~15 + 2" (MATK pre-renewal) + * - SetValuesX10(baseX10, bonusX10) "1.5 + 0.0" (CRIT armazenado ×10) + * + * Pareada com o WBP UI_PlayerStatus_BonusRow. + */ +UCLASS(Abstract, Blueprintable) +class ZMMO_API UUIPlayerStatus_BonusRow : public UCommonUserWidget +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = "Zeus|Status") + void SetStatName(FText DisplayName); + + UFUNCTION(BlueprintCallable, Category = "Zeus|Status") + void SetValues(int32 Base, int32 Bonus); + + UFUNCTION(BlueprintCallable, Category = "Zeus|Status") + void SetRangeValues(int32 MinBase, int32 MaxBase, int32 Bonus); + + UFUNCTION(BlueprintCallable, Category = "Zeus|Status") + void SetValuesX10(int32 BaseX10, int32 BonusX10); + +protected: + UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Text_StatName; + UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Text_BasePart; + UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Text_SplitOp; + UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Text_BonusPart; + +private: + void SetBonusVisibility(bool bShow); +}; diff --git a/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_StatRow.cpp b/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_StatRow.cpp new file mode 100644 index 0000000..7accb9f --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_StatRow.cpp @@ -0,0 +1,73 @@ +#include "UIPlayerStatus_StatRow.h" + +#include "Components/Button.h" +#include "Components/HorizontalBox.h" +#include "Components/TextBlock.h" +#include "Internationalization/Text.h" + +void UUIPlayerStatus_StatRow::SetupStat(int32 InStatId, FText IconLetter, FText DisplayName) +{ + StatId = InStatId; + if (Border_StatIconWrap_Label) Border_StatIconWrap_Label->SetText(IconLetter); + if (Text_StatName) Text_StatName->SetText(DisplayName); +} + +void UUIPlayerStatus_StatRow::SetValues(int32 Base, int32 Bonus) +{ + if (Text_StatBase) Text_StatBase->SetText(FText::AsNumber(Base)); + + const bool bShowBonus = (Bonus != 0); + const ESlateVisibility BonusVis = bShowBonus ? ESlateVisibility::Visible : ESlateVisibility::Collapsed; + + if (Layout_BonusGroup) + { + Layout_BonusGroup->SetVisibility(BonusVis); + } + else + { + if (Text_Sign) Text_Sign->SetVisibility(BonusVis); + if (Text_StatBonus) Text_StatBonus->SetVisibility(BonusVis); + } + + if (bShowBonus) + { + if (Text_Sign) Text_Sign->SetText(FText::FromString(Bonus > 0 ? TEXT("+") : TEXT("-"))); + if (Text_StatBonus) Text_StatBonus->SetText(FText::AsNumber(FMath::Abs(Bonus))); + } +} + +void UUIPlayerStatus_StatRow::SetPlusVisible(bool bVisible) +{ + if (Button_PlusBtn) + { + Button_PlusBtn->SetVisibility(bVisible ? ESlateVisibility::Visible : ESlateVisibility::Collapsed); + } +} + +void UUIPlayerStatus_StatRow::SetPlusEnabled(bool bEnabled) +{ + if (Button_PlusBtn) Button_PlusBtn->SetIsEnabled(bEnabled); +} + +void UUIPlayerStatus_StatRow::NativeOnInitialized() +{ + Super::NativeOnInitialized(); + if (Button_PlusBtn) + { + Button_PlusBtn->OnClicked.AddDynamic(this, &UUIPlayerStatus_StatRow::HandlePlusClicked); + } +} + +void UUIPlayerStatus_StatRow::NativeDestruct() +{ + if (Button_PlusBtn) + { + Button_PlusBtn->OnClicked.RemoveDynamic(this, &UUIPlayerStatus_StatRow::HandlePlusClicked); + } + Super::NativeDestruct(); +} + +void UUIPlayerStatus_StatRow::HandlePlusClicked() +{ + OnPlusClicked.Broadcast(StatId); +} diff --git a/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_StatRow.h b/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_StatRow.h new file mode 100644 index 0000000..ae9ce20 --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_StatRow.h @@ -0,0 +1,68 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CommonUserWidget.h" +#include "UIPlayerStatus_StatRow.generated.h" + +class UButton; +class UHorizontalBox; +class UTextBlock; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FZMMOOnStatRowPlusClicked, int32, StatId); + +/** + * Linha de um stat primário (STR/AGI/VIT/INT/DEX/LUK) na janela de Status. + * Layout: ícone + nome + valor base + (sinal + bônus) + botão "+". + * + * Sem lógica de domínio — a janela (UUIPlayerStatus_Window) injeta o + * StatId e os valores via Setup/SetValues; o clique no "+" é re-emitido + * como OnPlusClicked(StatId). + * + * Pareada com o WBP UI_PlayerStatus_StatRow. + */ +UCLASS(Abstract, Blueprintable) +class ZMMO_API UUIPlayerStatus_StatRow : public UCommonUserWidget +{ + GENERATED_BODY() + +public: + /** Setup único chamado pela window quando inicializa a linha. */ + UFUNCTION(BlueprintCallable, Category = "Zeus|Status") + void SetupStat(int32 InStatId, FText IconLetter, FText DisplayName); + + /** Atualiza valores. Bonus=0 esconde a parte "+N". */ + UFUNCTION(BlueprintCallable, Category = "Zeus|Status") + void SetValues(int32 Base, int32 Bonus); + + /** Mostra/esconde o botão "+". */ + UFUNCTION(BlueprintCallable, Category = "Zeus|Status") + void SetPlusVisible(bool bVisible); + + /** Habilita/desabilita o botão "+" (independente da visibilidade). */ + UFUNCTION(BlueprintCallable, Category = "Zeus|Status") + void SetPlusEnabled(bool bEnabled); + + /** Disparado quando o usuário clica em "+". */ + UPROPERTY(BlueprintAssignable, Category = "Zeus|Status") + FZMMOOnStatRowPlusClicked OnPlusClicked; + +protected: + virtual void NativeOnInitialized() override; + virtual void NativeDestruct() override; + + UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Border_StatIconWrap_Label; + UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Text_StatName; + UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Text_StatBase; + UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Text_Sign; + UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Text_StatBonus; + UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Button_PlusBtn; + + /** HBox que envolve sinal+bonus; ocultado quando bonus = 0. Bind opcional — + * se o WBP não expuser, cai pra ocultar Text_Sign/Text_StatBonus individualmente. */ + UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Layout_BonusGroup; + +private: + UFUNCTION() void HandlePlusClicked(); + + int32 StatId = 0; +}; diff --git a/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.cpp b/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.cpp new file mode 100644 index 0000000..2a8c682 --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.cpp @@ -0,0 +1,220 @@ +#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); + } + } +} diff --git a/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.h b/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.h new file mode 100644 index 0000000..6440ed9 --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.h @@ -0,0 +1,82 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CommonActivatableWidget.h" +#include "ZMMOAttributeTypes.h" +#include "UIPlayerStatus_Window.generated.h" + +class UButton; +class UTextBlock; +class UUIPlayerStatus_BonusRow; +class UUIPlayerStatus_StatRow; +class UZMMOAttributeComponent; + +/** + * Janela de Status do player — composta por instâncias dos sub-widgets + * UUIPlayerStatus_StatRow (6 primários) e UUIPlayerStatus_BonusRow (8 derivados). + * + * Consome o UZMMOAttributeComponent do PlayerState e a pipeline de + * C_STAT_ALLOC (RequestStatAlloc → server valida → OnStatAllocReply). + * + * Pareada com o WBP UI_PlayerStatus_Window. Os 14 rows são BindWidgetOptional + * pra suportar variantes (ex.: HUD compacto sem CRIT/ASPD). + */ +UCLASS(Abstract, Blueprintable, BlueprintType) +class ZMMO_API UUIPlayerStatus_Window : public UCommonActivatableWidget +{ + GENERATED_BODY() + +public: + UUIPlayerStatus_Window(const FObjectInitializer& ObjectInitializer); + +protected: + virtual void NativeOnActivated() override; + virtual void NativeOnDeactivated() override; + virtual TOptional GetDesiredInputConfig() const override; + + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr TitleText; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr StatusPointText; + + // Header: identidade do char. + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Header") TObjectPtr Text_CharName; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Header") TObjectPtr Text_Job; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Header") TObjectPtr Text_BaseLevel_Value; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Header") TObjectPtr Text_JobLevel_Value; + + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr Row_Str; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr Row_Agi; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr Row_Vit; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr Row_Int; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr Row_Dex; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr Row_Luk; + + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr Row_Atk; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr Row_Matk; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr Row_Def; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr Row_Mdef; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr Row_Hit; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr Row_Flee; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr Row_Crit; + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr Row_Aspd; + + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr CloseBtn; + +private: + void BindToLocalPlayer(); + void UnbindFromComponent(); + void RefreshFromSnapshot(const FZMMOAttributesSnapshot& Snap); + void SetAllPrimaryRowsPlusEnabled(bool bEnabled); + void BindRow(UUIPlayerStatus_StatRow* Row, int32 StatId, const TCHAR* IconLetter, const TCHAR* DisplayName); + + UFUNCTION() void HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot); + UFUNCTION() void HandleStatAllocReply(bool bAccepted, int32 Reason); + UFUNCTION() void HandleRowPlusClicked(int32 StatId); + UFUNCTION() void OnCloseClicked(); + + UPROPERTY(Transient) + TWeakObjectPtr BoundComponent; + + /** Trava local pra evitar spam-click: vira true ao clicar, false ao + * receber StatAllocReply (ou snapshot novo, o que vier primeiro). */ + bool bPlusButtonsLocked = false; +};