From f585b0d2b92f61ff7bfd5dad42b693d2d5cbc1c9 Mon Sep 17 00:00:00 2001 From: Mateus Rodrigues Date: Sat, 23 May 2026 11:22:58 -0300 Subject: [PATCH] =?UTF-8?q?feat(attributes/ui):=20Status=20Window=20com=20?= =?UTF-8?q?aloca=C3=A7=C3=A3o=20STR/AGI/VIT/INT/DEX/LUK=20+=20derivados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ZMMOStatusWindowWidget (UCommonActivatableWidget, modo Menu): grid 2 colunas estilo RO. Esquerda: stats primários com botões + por stat (RequestStatAlloc). Direita: derivados ATK/MATK/DEF/MDEF/HIT/FLEE/CRIT/ASPD vindos do snapshot do server — cliente NUNCA recalcula (anti-cheat foundation). - Botão + envia C_STAT_ALLOC → server valida cost RO (floor((stat-1)/10)+2), aplica + recalcula derivados + SaveCharFull async, manda S_ATTRIBUTE_SNAPSHOT_FULL + reply. UI atualiza em tempo real via OnAttributesChanged. - ZMMOAttributeComponent ganha RequestStatAlloc + NotifyStatAllocReply + delegate OnStatAllocReply para feedback de rejeição. - ZMMOAttributeNetworkHandler: HandleStatAllocReply roteia pro componente do player local (reply não traz EntityId — sempre quem fez o request). - PlayerController: hotkey Alt+A (FInputChord legacy + BindKey, sem precisar de IA asset). ToggleStatusWindow via UUIInGameFlowSubsystem. - WBP_StatusWindow: layout 2 colunas via MCP set_widget_tree (43 widgets) + BgBorder escuro semi-transparente + centralizado no overlay. - DA_InGameScreenSet: StatusWindow → WBP_StatusWindow. --- Content/ZMMO/UI/HUD/WBP_StatusWindow.uasset | Bin 0 -> 81121 bytes .../ZMMO/UI/InGame/DA_InGameScreenSet.uasset | Bin 1560 -> 1711 bytes .../Game/Controller/ZMMOPlayerController.cpp | 30 ++++ .../Game/Controller/ZMMOPlayerController.h | 3 + .../Game/UI/InGame/ZMMOStatusWindowWidget.cpp | 162 ++++++++++++++++++ .../Game/UI/InGame/ZMMOStatusWindowWidget.h | 106 ++++++++++++ .../Private/ZMMOAttributeComponent.cpp | 19 ++ .../Private/ZMMOAttributeNetworkHandler.cpp | 27 +++ .../Public/ZMMOAttributeComponent.h | 19 ++ .../Public/ZMMOAttributeNetworkHandler.h | 6 + 10 files changed, 372 insertions(+) create mode 100644 Content/ZMMO/UI/HUD/WBP_StatusWindow.uasset create mode 100644 Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.cpp create mode 100644 Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.h diff --git a/Content/ZMMO/UI/HUD/WBP_StatusWindow.uasset b/Content/ZMMO/UI/HUD/WBP_StatusWindow.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0311ba8817b3c5c56606314a33cf96ec6648513e GIT binary patch literal 81121 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 literal 0 HcmV?d00001 diff --git a/Content/ZMMO/UI/InGame/DA_InGameScreenSet.uasset b/Content/ZMMO/UI/InGame/DA_InGameScreenSet.uasset index 16bcec6d6234aa58d070e8a418c1a58b4bfb3f56..1c5275ba6b7451420d8ba149224afe15024b7433 100644 GIT binary patch delta 611 zcmbQivz~W?glO~WJ0?>DkM+bC?Nq)X{UpFbN~GlHMBR8^Q3eKvg^UagOhEej#F;u) z|Ct#WfP97)7Koq;kOqM-NHjlhU$%Q1-yqR34+rE_4iBU6X8FwiV?o$<~LY8$z9C&uVZ{>oG>$_sZ3 z#D^d*-{eAOVKy$X%*0vh^#V{ef>8P&(A6SPHYbz@Q80BNYheHs8X$nI4&-ZyD1;P( za=3wX6O;`K6)2co#UePlfth8pD~k$a-Q++PVK6U*MFdPwWYJ~i0jhpF`7q1;dXRm< zh=2edD9r(-L6XoI14k3g4p7v>912njHvq^6ISK|8U<{CAs(bW#W$~7eA!s3KKf+dMBR8kAqED9g^UagK*W&2Jeik8 z-44iNs9}Z(F|sf)Fah}+fqW1s1oB}tNDYV&(pn0nYk|0zW#WG&Mxn_{jKNBWfcz({ z5cM$fKM>>r#dv@MFIXo}XVjVej$5tx~z>r4@6D&W1a5>G7M%Q0~b_38WkdVFlC$1}K_QCQUCw| diff --git a/Source/ZMMO/Game/Controller/ZMMOPlayerController.cpp b/Source/ZMMO/Game/Controller/ZMMOPlayerController.cpp index b6fb1cb..801f186 100644 --- a/Source/ZMMO/Game/Controller/ZMMOPlayerController.cpp +++ b/Source/ZMMO/Game/Controller/ZMMOPlayerController.cpp @@ -1,12 +1,17 @@ #include "ZMMOPlayerController.h" #include "Blueprint/UserWidget.h" +#include "Components/InputComponent.h" #include "EnhancedInputSubsystems.h" +#include "Engine/GameInstance.h" #include "Engine/LocalPlayer.h" +#include "Framework/Commands/InputChord.h" #include "InputMappingContext.h" #include "UObject/ConstructorHelpers.h" #include "Widgets/Input/SVirtualJoystick.h" #include "ZMMO.h" +#include "UI/InGame/UIInGameFlowSubsystem.h" +#include "UI/InGameTypes.h" AZMMOPlayerController::AZMMOPlayerController() { @@ -86,6 +91,31 @@ void AZMMOPlayerController::SetupInputComponent() } } } + + // Hotkey global Alt+A -> toggle StatusWindow. Uso InputComponent legacy + // (BindKey com FInputChord) pra nao depender de assets InputAction. + // Migrar pra Enhanced Input (IA_OpenStatus, IA_OpenInventory, etc.) quando + // vier outro menu (Inventory I, SkillTree K). + if (InputComponent) + { + FInputKeyBinding& Binding = InputComponent->BindKey( + FInputChord(EKeys::A, /*bShift*/ false, /*bCtrl*/ false, /*bAlt*/ true, /*bCmd*/ false), + IE_Pressed, + this, &AZMMOPlayerController::ToggleStatusWindow); + Binding.bConsumeInput = true; + Binding.bExecuteWhenPaused = true; // dispara mesmo com StatusWindow aberto (CommonUI menu mode) + } +} + +void AZMMOPlayerController::ToggleStatusWindow() +{ + if (UGameInstance* GI = GetGameInstance()) + { + if (UUIInGameFlowSubsystem* Flow = GI->GetSubsystem()) + { + Flow->ToggleScreen(EZMMOInGameUIState::StatusWindow); + } + } } bool AZMMOPlayerController::ShouldUseTouchControls() const diff --git a/Source/ZMMO/Game/Controller/ZMMOPlayerController.h b/Source/ZMMO/Game/Controller/ZMMOPlayerController.h index 83a8d76..1f2c057 100644 --- a/Source/ZMMO/Game/Controller/ZMMOPlayerController.h +++ b/Source/ZMMO/Game/Controller/ZMMOPlayerController.h @@ -47,4 +47,7 @@ protected: virtual void SetupInputComponent() override; bool ShouldUseTouchControls() const; + + /** Hotkey Alt+A → toggle StatusWindow via UUIInGameFlowSubsystem. */ + void ToggleStatusWindow(); }; diff --git a/Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.cpp b/Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.cpp new file mode 100644 index 0000000..b32c6f0 --- /dev/null +++ b/Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.cpp @@ -0,0 +1,162 @@ +#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 — sempre vem prontos do servidor (camadas equip/buff ja somadas). + // CritX10 vem internamente x10 (1.0 = 10); divide pra exibicao com 1 casa. + if (AtkText) AtkText->SetText(FText::FromString(FString::Printf(TEXT("ATK %d"), S.Atk))); + if (MatkText) MatkText->SetText(FText::FromString(FString::Printf(TEXT("MATK %d"), S.Matk))); + if (DefText) DefText->SetText(FText::FromString(FString::Printf(TEXT("DEF %d"), S.Def))); + if (MdefText) MdefText->SetText(FText::FromString(FString::Printf(TEXT("MDEF %d"), S.Mdef))); + if (HitText) HitText->SetText(FText::FromString(FString::Printf(TEXT("HIT %d"), S.Hit))); + if (FleeText) FleeText->SetText(FText::FromString(FString::Printf(TEXT("FLEE %d"), S.Flee))); + if (CritText) + { + const int32 CritInt = S.CritX10 / 10; + const int32 CritFrac = S.CritX10 % 10; + CritText->SetText(FText::FromString(FString::Printf(TEXT("CRIT %d.%d"), CritInt, CritFrac))); + } + 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 new file mode 100644 index 0000000..af8bcef --- /dev/null +++ b/Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.h @@ -0,0 +1,106 @@ +#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/ZMMOAttributes/Private/ZMMOAttributeComponent.cpp b/Source/ZMMOAttributes/Private/ZMMOAttributeComponent.cpp index fc7d371..d2aa07d 100644 --- a/Source/ZMMOAttributes/Private/ZMMOAttributeComponent.cpp +++ b/Source/ZMMOAttributes/Private/ZMMOAttributeComponent.cpp @@ -1,5 +1,8 @@ #include "ZMMOAttributeComponent.h" +#include "Engine/GameInstance.h" +#include "ZeusNetworkSubsystem.h" + UZMMOAttributeComponent::UZMMOAttributeComponent() { PrimaryComponentTick.bCanEverTick = false; @@ -34,3 +37,19 @@ void UZMMOAttributeComponent::NotifyLevelUp(int32 NewBaseLevel, int32 StatusPoin // aqui apenas dispara o delegate para efeitos visuais imediatos. OnLevelUp.Broadcast(NewBaseLevel, StatusPointDelta); } + +void UZMMOAttributeComponent::NotifyStatAllocReply(bool bAccepted, int32 Reason) +{ + OnStatAllocReply.Broadcast(bAccepted, Reason); +} + +void UZMMOAttributeComponent::RequestStatAlloc(int32 StatId, int32 Amount) +{ + UWorld* World = GetWorld(); + if (!World) { return; } + UGameInstance* GI = World->GetGameInstance(); + if (!GI) { return; } + UZeusNetworkSubsystem* Net = GI->GetSubsystem(); + if (!Net) { return; } + Net->SendStatAlloc(StatId, Amount); +} diff --git a/Source/ZMMOAttributes/Private/ZMMOAttributeNetworkHandler.cpp b/Source/ZMMOAttributes/Private/ZMMOAttributeNetworkHandler.cpp index da21f17..3a20330 100644 --- a/Source/ZMMOAttributes/Private/ZMMOAttributeNetworkHandler.cpp +++ b/Source/ZMMOAttributes/Private/ZMMOAttributeNetworkHandler.cpp @@ -3,6 +3,7 @@ #include "Engine/GameInstance.h" #include "Engine/World.h" #include "GameFramework/GameStateBase.h" +#include "GameFramework/PlayerController.h" #include "GameFramework/PlayerState.h" #include "ZMMOAttributeComponent.h" #include "ZMMOAttributeTypes.h" @@ -91,6 +92,8 @@ void UZMMOAttributeNetworkHandler::Initialize(FSubsystemCollectionBase& Collecti this, &UZMMOAttributeNetworkHandler::HandleHpSpUpdate); LevelUpHandle = Net->OnLevelUp.AddUObject( this, &UZMMOAttributeNetworkHandler::HandleLevelUp); + StatAllocReplyHandle = Net->OnStatAllocReply.AddUObject( + this, &UZMMOAttributeNetworkHandler::HandleStatAllocReply); } } @@ -113,6 +116,11 @@ void UZMMOAttributeNetworkHandler::Deinitialize() Net->OnLevelUp.Remove(LevelUpHandle); LevelUpHandle.Reset(); } + if (StatAllocReplyHandle.IsValid()) + { + Net->OnStatAllocReply.Remove(StatAllocReplyHandle); + StatAllocReplyHandle.Reset(); + } } Super::Deinitialize(); } @@ -179,3 +187,22 @@ void UZMMOAttributeNetworkHandler::HandleLevelUp(int32 EntityId, int32 NewBaseLe Comp->NotifyLevelUp(NewBaseLevel, StatusPointDelta); } } + +void UZMMOAttributeNetworkHandler::HandleStatAllocReply(bool bAccepted, int32 Reason) +{ + // S_ATTRIBUTE_STAT_ALLOC_OK nao traz EntityId no payload — sempre do + // player local que fez o request. Busca o AttributeComponent via PC. + if (UZMMOAttributeComponent* Comp = FindLocalPlayerAttributeComponent()) + { + Comp->NotifyStatAllocReply(bAccepted, Reason); + } +} + +UZMMOAttributeComponent* UZMMOAttributeNetworkHandler::FindLocalPlayerAttributeComponent() const +{ + UWorld* World = GetWorld(); + if (!World) { return nullptr; } + APlayerController* PC = World->GetFirstPlayerController(); + if (!PC || !PC->PlayerState) { return nullptr; } + return PC->PlayerState->FindComponentByClass(); +} diff --git a/Source/ZMMOAttributes/Public/ZMMOAttributeComponent.h b/Source/ZMMOAttributes/Public/ZMMOAttributeComponent.h index efc5fb5..22522ed 100644 --- a/Source/ZMMOAttributes/Public/ZMMOAttributeComponent.h +++ b/Source/ZMMOAttributes/Public/ZMMOAttributeComponent.h @@ -9,6 +9,10 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FZMMOOnAttributesChanged, const FZMM DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FZMMOOnHpSpChanged, int32, Hp, int32, Sp); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FZMMOOnLevelUp, int32, NewBaseLevel, int32, StatusPointDelta); +/// Resposta server ao C_STAT_ALLOC. Reason e' EAllocRejectReason (None=0, +/// InvalidStat=1, InvalidAmount=2, NotEnoughPoints=3, StatCapped=4, NoSession=5). +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FZMMOOnStatAllocReply, bool, bAccepted, int32, Reason); + /** * Componente de atributos do MMO ligado ao ator que o representa em-jogo * (player local, proxies remotos, NPCs futuros). @@ -44,6 +48,17 @@ public: UFUNCTION(BlueprintCallable, Category = "Zeus|Attributes") void NotifyLevelUp(int32 NewBaseLevel, int32 StatusPointDelta); + /// Notifica resposta server ao C_STAT_ALLOC. UI pode reagir (toast/SFX + /// quando aceito, erro vermelho quando rejeitado). + UFUNCTION(BlueprintCallable, Category = "Zeus|Attributes") + void NotifyStatAllocReply(bool bAccepted, int32 Reason); + + /// Envia C_STAT_ALLOC pro server. Wrapper conveniente — busca o + /// UZeusNetworkSubsystem via GameInstance + chama SendStatAlloc. + /// `StatId`: 0=STR, 1=AGI, 2=VIT, 3=INT, 4=DEX, 5=LUK. `Amount`: 1..10. + UFUNCTION(BlueprintCallable, Category = "Zeus|Attributes") + void RequestStatAlloc(int32 StatId, int32 Amount = 1); + /// Seed do EntityId vindo de S_SPAWN_PLAYER local. Chamado pelo /// `AZMMOPlayerCharacter` antes do primeiro S_ATTRIBUTE_SNAPSHOT_FULL /// chegar, garantindo que o NetworkHandler consiga rotear via lookup @@ -80,6 +95,10 @@ public: UPROPERTY(BlueprintAssignable, Category = "Zeus|Attributes") FZMMOOnLevelUp OnLevelUp; + /** Resposta ao C_STAT_ALLOC (aceito ou rejeitado). UI reage. */ + UPROPERTY(BlueprintAssignable, Category = "Zeus|Attributes") + FZMMOOnStatAllocReply OnStatAllocReply; + protected: UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") FZMMOAttributesSnapshot Current; diff --git a/Source/ZMMOAttributes/Public/ZMMOAttributeNetworkHandler.h b/Source/ZMMOAttributes/Public/ZMMOAttributeNetworkHandler.h index 45ba179..0f47348 100644 --- a/Source/ZMMOAttributes/Public/ZMMOAttributeNetworkHandler.h +++ b/Source/ZMMOAttributes/Public/ZMMOAttributeNetworkHandler.h @@ -36,10 +36,16 @@ private: void HandleAttributeSnapshotFull(const FZeusAttributesPayload& Payload); void HandleHpSpUpdate(int32 EntityId, int32 Hp, int32 Sp); void HandleLevelUp(int32 EntityId, int32 NewBaseLevel, int32 StatusPointDelta); + void HandleStatAllocReply(bool bAccepted, int32 Reason); UZeusNetworkSubsystem* GetZeusNetSubsystem() const; + /// Acha o AttributeComponent do player local (Pawn->PlayerState). + /// Usado pro StatAllocReply (sem EntityId no payload). + class UZMMOAttributeComponent* FindLocalPlayerAttributeComponent() const; + FDelegateHandle SnapshotFullHandle; FDelegateHandle HpSpUpdateHandle; FDelegateHandle LevelUpHandle; + FDelegateHandle StatAllocReplyHandle; };