diff --git a/Content/ZMMO/UI/HUD/WBP_StatusWindow.uasset b/Content/ZMMO/UI/HUD/WBP_StatusWindow.uasset new file mode 100644 index 0000000..0311ba8 Binary files /dev/null and b/Content/ZMMO/UI/HUD/WBP_StatusWindow.uasset differ diff --git a/Content/ZMMO/UI/InGame/DA_InGameScreenSet.uasset b/Content/ZMMO/UI/InGame/DA_InGameScreenSet.uasset index 16bcec6..1c5275b 100644 Binary files a/Content/ZMMO/UI/InGame/DA_InGameScreenSet.uasset and b/Content/ZMMO/UI/InGame/DA_InGameScreenSet.uasset differ 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; };