diff --git a/ARQUITETURA.md b/ARQUITETURA.md index 08e4cb5..9000898 100644 --- a/ARQUITETURA.md +++ b/ARQUITETURA.md @@ -203,10 +203,16 @@ Content/ │ │ ├── Environment/ ← Architecture/ Nature/ Props/ │ │ └── Interactables/ ← baús, portas, warps, recursos │ │ +│ ├── Maps/ ← mapas que NÃO são de mundo +│ │ └── FrontEnd/ ← L_FrontEnd (boot/menu; GameMode via +│ │ World Settings = AZMMOFrontEndGameMode) +│ │ │ ├── UI/ ← HUD, Login, CharacterSelect, Inventory, │ │ │ Chat, Party, Guild, Trade, Quest, Map, │ │ │ Shared, Icons │ │ ├── Shared/ ← WBPs reutilizáveis (UI_Button_Master…) +│ │ ├── FrontEnd/ ← WBP_PrimaryGameLayout, DA_FrontEndScreenSet, +│ │ │ telas refinadas (Boot/Login/… vindas do Forge) │ │ ├── Fonts/ ← Font_*/FF_* (Cinzel, Rajdhani) │ │ ├── CommonUI/Style/ ← estilos CommonUI (espelha o Hyper) │ │ │ ├── Button/ ← BSB_* (UCommonButtonStyle) @@ -236,6 +242,9 @@ Content/ │ ├── Developers/ ← rascunho por dev (não cooka) │ └── / +├── AutoCreated/ ← saída CRUA do Zeus UMG Forge (HTML→WBP). +│ └── / Quarentena: refina-se e MOVE p/ ZMMO/UI/ +│ FrontEnd/ antes de produção (não cooka cru) ├── ExternalContent/ ← Fab / Megascans / Marketplace │ ├── Fab/ Megascans/ Marketplace/ ├── Collections/ @@ -252,9 +261,20 @@ Source/ZMMO/ │ ├── Game/ ← gameplay runtime (já existe) │ ├── Entity/ ← AZMMOEntity + derivados -│ ├── Controller/ ← AZMMOPlayerController -│ ├── Modes/ ← GameMode + GameInstance -│ └── Network/ ← UZMMOWorldSubsystem +│ ├── Controller/ ← AZMMOPlayerController, +│ │ AZMMOFrontEndPlayerController +│ ├── Modes/ ← GameMode + GameInstance, +│ │ AZMMOFrontEndGameMode +│ ├── Network/ ← UZMMOWorldSubsystem +│ └── UI/ ← runtime de UI (não é contrato de dados) +│ ├── ZMMOThemeSubsystem.* ← resolução de tema +│ ├── Widgets/ ← UUIButton_Base, UUIPanel_Base +│ └── FrontEnd/ ← infra de navegação CommonUI: +│ UUIActivatableScreen_Base, +│ UUIPrimaryGameLayout_Base, +│ UUIManagerSubsystem (LocalPlayer), +│ UUIFrontEndFlowSubsystem (GameInstance), +│ UUIFrontEndScreenSet (UDataAsset) │ └── Data/ ← contrato de dados (USTRUCT + UENUM) ├── ZMMOData.h ← header agregador (opcional) @@ -278,7 +298,9 @@ Source/ZMMO/ ├── UI/ │ ├── ThemeKeys.h ← EZMMOThemeKey (HUD_Frame, Login_BG…) │ ├── ThemeRow.h ← FThemeEntry (Key → SoftAsset) - │ └── ThemeCalendarRow.h ← FThemeCalendarRow (data → ThemeId) + │ ├── ThemeCalendarRow.h ← FThemeCalendarRow (data → ThemeId) + │ ├── FrontEndTypes.h ← EZMMOFrontEndState, EZMMOLobbyPage + │ └── UILayerTags.h/.cpp ← GameplayTags nativos UI.Layer.* └── World/ └── ZoneRow.h ``` @@ -365,6 +387,16 @@ base de widget = `UUI_Base`, sempre `UCLASS(Abstract)` (não instanciável direto; só os filhos derivam — padrão `_Abstract` do Hyper). Ex.: `UUIButton_Base`, `UUIPanel_Base`, `UUIProgressBar_Base`. +**Sub-regra — infra de navegação de front-end:** as classes que orquestram a +navegação CommonUI seguem o mesmo prefixo `UUI` (sem `ZMMO`) por coerência com +a família de UI, **mesmo não sendo widgets**. Assim: o layout raiz é +`UUIPrimaryGameLayout_Base` (Abstract, widget), a base de tela é +`UUIActivatableScreen_Base` (Abstract, widget), e os subsistemas de UI são +`UUIManagerSubsystem` (`ULocalPlayerSubsystem`) e `UUIFrontEndFlowSubsystem` +(`UGameInstanceSubsystem`). Atores de gameplay do front-end (GameMode, +PlayerController) **mantêm** o prefixo `AZMMO` — não são UI, espelham +`AZMMOGameMode`: `AZMMOFrontEndGameMode`, `AZMMOFrontEndPlayerController`. + **Regra do UMG (importante):** um Widget Blueprint **não pode** herdar de outro Widget Blueprint que tenha árvore de widgets (UMG aborta a compilação: "apenas um deles deve ter árvore"). Portanto a camada abstrata é **só a classe C++** @@ -577,6 +609,38 @@ o opcode de spawn. O cliente nunca instancia mob "por nome" — sempre via id. O subsystem aplica fallback internamente: pergunta ao tema activo; se a chave não estiver preenchida, devolve a do `Default`. +### 4.8. Adicionar uma tela de front-end (ex: `Login`) + +**Pré-requisito:** já existem `UUIActivatableScreen_Base` e +`UUIFrontEndFlowSubsystem` em `Source/ZMMO/Game/UI/FrontEnd/`, o +`WBP_PrimaryGameLayout` e o `DA_FrontEndScreenSet` em +`Content/ZMMO/UI/FrontEnd/`. + +Fluxo geral: **Boot → Connecting → Login → ServerSelect → Lobby → EnteringWorld +→ InWorld**. O **Lobby** é o hub principal logado; suas páginas internas +(CharacterSelect, CharacterCreate, Shop, Settings…) são `EZMMOLobbyPage`, +navegadas por um stack CommonUI **dentro** da tela do Lobby — não são estados +de topo do fluxo. + +1. Prototipar a tela em HTML/CSS em `Tools/Templates/MMO_Widget/` (ou usar os + templates já existentes). +2. Rodar o Zeus UMG Forge (HTML→UMG). Saída crua: `Content/AutoCreated//`. +3. Abrir o `WBP_*` gerado e **reparent** a classe pai para + `UUIActivatableScreen_Base` (a base é `UCommonActivatableWidget`; o WBP tem + árvore própria — respeita §3.3, nunca encadeia WBP→WBP). +4. **Reconectar tema:** o Forge gera hard ref de textura (proibido §5). + Substituir por consumo via `UZMMOThemeSubsystem` por `EZMMOThemeKey` no + override de `RefreshUIStyle`; remontar botões/painéis com os widgets + compartilhados (`UI_Button_Master`, `UI_Panel_Master`). +5. **Mover** o asset refinado de `Content/AutoCreated/…` para + `Content/ZMMO/UI/FrontEnd/` (o cru não cooka). +6. Registrar no `DA_FrontEndScreenSet`: `StateScreens[EZMMOFrontEndState::Login]` + = soft class do WBP refinado (páginas do Lobby vão no map de + `EZMMOLobbyPage` da tela de Lobby). Sem registro, o flow faz no-op + log. + +**Regra:** uma tela nunca segura hard ref a asset de tema (textura/som/VFX) — +sempre via `UZMMOThemeSubsystem` por `EZMMOThemeKey` (igual §4.7 e §5). + --- ## 5. O que **NÃO** fazer @@ -588,7 +652,9 @@ chave não estiver preenchida, devolve a do `Default`. - **Não** misturar assets de uma entidade com assets de outra. Cada Wolf_Gray, Sword_Iron, etc. é uma pasta isolada. - **Não** colocar nada na raiz `Content/`. Tudo é `Content/ZMMO/...` ou - `Content/{Developers,ExternalContent}/...`. + `Content/{Developers,AutoCreated,ExternalContent}/...`. `AutoCreated/` é + quarentena do Zeus UMG Forge: refina-se e **move** para `ZMMO/UI/FrontEnd/` + antes de produção; o cru não cooka. - **Não** importar conteúdo do Fab/Megascans directo para dentro de `ZMMO/`. Vai sempre para `ExternalContent/` e, se quiser usar, **referencia** dali. - **Não** colocar dados tabulares em Blueprint (lista hardcoded de itens, @@ -598,7 +664,10 @@ chave não estiver preenchida, devolve a do `Default`. - **Não** colocar hard ref de textura/som de tema dentro de um Widget (`TObjectPtr ChristmasFrame` é proibido). Widgets pedem ao `UZMMOThemeSubsystem` via `EZMMOThemeKey`. Hard ref a asset de tema - bloqueia a troca em runtime e quebra a política de fallback. + bloqueia a troca em runtime e quebra a política de fallback. Vale também + para **telas de front-end** (Boot/Login/ServerSelect/Lobby/…): a saída crua + do Forge traz hard ref — tem de ser reconectada ao subsystem antes do merge + (ver §4.8). - **Não** criar chave de tema "ad-hoc" como `FName("HUD.Frame")`. Toda chave é entrada do enum C++ `EZMMOThemeKey`. String literal é proibida — o compilador tem de quebrar quando alguém esquecer de adicionar a chave. diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini index ce6b91b..265a76e 100644 --- a/Config/DefaultEngine.ini +++ b/Config/DefaultEngine.ini @@ -1,6 +1,6 @@ [/Script/EngineSettings.GameMapsSettings] -GameDefaultMap=/Game/ThirdPerson/Lvl_ThirdPerson.Lvl_ThirdPerson -EditorStartupMap=/Game/ThirdPerson/Lvl_ThirdPerson.Lvl_ThirdPerson +GameDefaultMap=/Game/ZMMO/Maps/FrontEnd/L_FrontEnd.L_FrontEnd +EditorStartupMap=/Game/ZMMO/Maps/FrontEnd/L_FrontEnd.L_FrontEnd ; GameMode/PlayerController/PlayerCharacter agora moram em C++ (Game/Modes/Entity/Controller). ; O motor pode spawnar AZMMOGameMode directamente; um BP filho continua opcional. GlobalDefaultGameMode=/Script/ZMMO.ZMMOGameMode diff --git a/Config/DefaultGame.ini b/Config/DefaultGame.ini index 39991e1..2b56716 100644 --- a/Config/DefaultGame.ini +++ b/Config/DefaultGame.ini @@ -24,3 +24,8 @@ ProjectName=Third Person Game Template ;CalendarTable=/Game/ZMMO/UI/Themes/DT_ThemeCalendar.DT_ThemeCalendar UIStyleTable=/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles ;DevThemeOverride=Christmas + +[/Script/ZMMO.UIFrontEndFlowSubsystem] +; Mapa estado->tela. O DA já existe (RootLayoutClass = WBP_PrimaryGameLayout); +; StateScreens fica vazio até os WBPs de página virem do Zeus UMG Forge (§4.8). +ScreenSetAsset=/Game/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.DA_FrontEndScreenSet diff --git a/Content/ZMMO/Maps/FrontEnd/L_FrontEnd.umap b/Content/ZMMO/Maps/FrontEnd/L_FrontEnd.umap new file mode 100644 index 0000000..47d31b8 Binary files /dev/null and b/Content/ZMMO/Maps/FrontEnd/L_FrontEnd.umap differ diff --git a/Content/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.uasset b/Content/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.uasset new file mode 100644 index 0000000..bdf26a0 Binary files /dev/null and b/Content/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.uasset differ diff --git a/Content/ZMMO/UI/FrontEnd/WBP_PrimaryGameLayout.uasset b/Content/ZMMO/UI/FrontEnd/WBP_PrimaryGameLayout.uasset new file mode 100644 index 0000000..3903827 Binary files /dev/null and b/Content/ZMMO/UI/FrontEnd/WBP_PrimaryGameLayout.uasset differ diff --git a/Source/ZMMO/Data/UI/FrontEndTypes.h b/Source/ZMMO/Data/UI/FrontEndTypes.h new file mode 100644 index 0000000..3eb7066 --- /dev/null +++ b/Source/ZMMO/Data/UI/FrontEndTypes.h @@ -0,0 +1,40 @@ +#pragma once + +#include "CoreMinimal.h" +#include "FrontEndTypes.generated.h" + +/** + * Estados de topo do fluxo de front-end (MainMenu). Dirigidos pelo + * UUIFrontEndFlowSubsystem; cada estado resolve uma tela + * (UUIActivatableScreen_Base) via DA_FrontEndScreenSet. + * + * Fluxo: Boot → Connecting → Login → ServerSelect → Lobby → EnteringWorld + * → InWorld. O Lobby é o hub principal logado; suas páginas internas são + * EZMMOLobbyPage (não são estados de topo — ver ARQUITETURA.md §4.8). + */ +UENUM(BlueprintType) +enum class EZMMOFrontEndState : uint8 +{ + None, + Boot, // splash inicial enquanto subsistemas sobem + Connecting, // conectando ao servidor Zeus (UDP) + Login, // autenticação + ServerSelect, // escolha de servidor/realm + Lobby, // hub principal logado (host das EZMMOLobbyPage) + EnteringWorld, // loading/handoff: OpenLevel do mapa de mundo + InWorld // no mundo (HUD de gameplay assume) +}; + +/** + * Páginas internas do Lobby — navegadas por um stack CommonUI DENTRO da tela + * de Lobby ("Switch principal"), não pelo fluxo de topo. + */ +UENUM(BlueprintType) +enum class EZMMOLobbyPage : uint8 +{ + None, + CharacterSelect, + CharacterCreate, + Shop, + Settings +}; diff --git a/Source/ZMMO/Data/UI/UILayerTags.cpp b/Source/ZMMO/Data/UI/UILayerTags.cpp new file mode 100644 index 0000000..9b6f8e0 --- /dev/null +++ b/Source/ZMMO/Data/UI/UILayerTags.cpp @@ -0,0 +1,9 @@ +#include "UI/UILayerTags.h" + +namespace ZMMOUITags +{ + UE_DEFINE_GAMEPLAY_TAG(UI_Layer_Game, "UI.Layer.Game"); + UE_DEFINE_GAMEPLAY_TAG(UI_Layer_GameMenu, "UI.Layer.GameMenu"); + UE_DEFINE_GAMEPLAY_TAG(UI_Layer_Menu, "UI.Layer.Menu"); + UE_DEFINE_GAMEPLAY_TAG(UI_Layer_Modal, "UI.Layer.Modal"); +} diff --git a/Source/ZMMO/Data/UI/UILayerTags.h b/Source/ZMMO/Data/UI/UILayerTags.h new file mode 100644 index 0000000..ef7263f --- /dev/null +++ b/Source/ZMMO/Data/UI/UILayerTags.h @@ -0,0 +1,21 @@ +#pragma once + +#include "NativeGameplayTags.h" + +/** + * GameplayTags nativos das camadas do switch principal de UI. Declaração + * nativa (não depende de DefaultGameplayTags.ini) — registradas no startup + * do módulo. Mapeadas para os stacks do UUIPrimaryGameLayout_Base. + * + * UI.Layer.Game — HUD de gameplay (camada de fundo) + * UI.Layer.GameMenu — menus sobre o jogo (inventário, mapa…) + * UI.Layer.Menu — front-end (Boot/Login/ServerSelect/Lobby) + * UI.Layer.Modal — diálogos/loading por cima de tudo + */ +namespace ZMMOUITags +{ + ZMMO_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_Game); + ZMMO_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_GameMenu); + ZMMO_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_Menu); + ZMMO_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_Modal); +} diff --git a/Source/ZMMO/Game/Controller/ZMMOFrontEndPlayerController.cpp b/Source/ZMMO/Game/Controller/ZMMOFrontEndPlayerController.cpp new file mode 100644 index 0000000..11aa832 --- /dev/null +++ b/Source/ZMMO/Game/Controller/ZMMOFrontEndPlayerController.cpp @@ -0,0 +1,24 @@ +#include "ZMMOFrontEndPlayerController.h" + +#include "UIFrontEndFlowSubsystem.h" +#include "Engine/GameInstance.h" + +AZMMOFrontEndPlayerController::AZMMOFrontEndPlayerController() +{ + bShowMouseCursor = true; +} + +void AZMMOFrontEndPlayerController::BeginPlay() +{ + Super::BeginPlay(); + + SetInputMode(FInputModeUIOnly()); + + if (const UGameInstance* GI = GetGameInstance()) + { + if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem()) + { + Flow->StartFrontEnd(); + } + } +} diff --git a/Source/ZMMO/Game/Controller/ZMMOFrontEndPlayerController.h b/Source/ZMMO/Game/Controller/ZMMOFrontEndPlayerController.h new file mode 100644 index 0000000..e12bc43 --- /dev/null +++ b/Source/ZMMO/Game/Controller/ZMMOFrontEndPlayerController.h @@ -0,0 +1,26 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" +#include "ZMMOFrontEndPlayerController.generated.h" + +/** + * AZMMOFrontEndPlayerController + * + * Controller do mapa de front-end. Não herda de AZMMOPlayerController (sem + * Enhanced Input de gameplay/touch). Em BeginPlay arranca o fluxo de + * front-end (UUIFrontEndFlowSubsystem) — feito aqui, e não no + * GameInstance::Init, porque os subsistemas já existem neste ponto (ver + * aviso no header do ZeusNetworkSubsystem). + */ +UCLASS(Blueprintable, BlueprintType) +class ZMMO_API AZMMOFrontEndPlayerController : public APlayerController +{ + GENERATED_BODY() + +public: + AZMMOFrontEndPlayerController(); + +protected: + virtual void BeginPlay() override; +}; diff --git a/Source/ZMMO/Game/Modes/ZMMOFrontEndGameMode.cpp b/Source/ZMMO/Game/Modes/ZMMOFrontEndGameMode.cpp new file mode 100644 index 0000000..26a9666 --- /dev/null +++ b/Source/ZMMO/Game/Modes/ZMMOFrontEndGameMode.cpp @@ -0,0 +1,10 @@ +#include "ZMMOFrontEndGameMode.h" + +#include "ZMMOFrontEndPlayerController.h" + +AZMMOFrontEndGameMode::AZMMOFrontEndGameMode() +{ + // UI pura: sem pawn. O controller cria o root layout e arranca o fluxo. + DefaultPawnClass = nullptr; + PlayerControllerClass = AZMMOFrontEndPlayerController::StaticClass(); +} diff --git a/Source/ZMMO/Game/Modes/ZMMOFrontEndGameMode.h b/Source/ZMMO/Game/Modes/ZMMOFrontEndGameMode.h new file mode 100644 index 0000000..0cef995 --- /dev/null +++ b/Source/ZMMO/Game/Modes/ZMMOFrontEndGameMode.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "ZMMOFrontEndGameMode.generated.h" + +/** + * AZMMOFrontEndGameMode + * + * GameMode do mapa de front-end (L_FrontEnd): só UI, sem pawn nem network + * play. NÃO é o GameMode global (esse continua AZMMOGameMode para mapas de + * mundo) — aplicado via override no World Settings de L_FrontEnd + * (ARQUITETURA.md §3.3 / decisão de mapa dedicado). + */ +UCLASS(Blueprintable, BlueprintType) +class ZMMO_API AZMMOFrontEndGameMode : public AGameModeBase +{ + GENERATED_BODY() + +public: + AZMMOFrontEndGameMode(); +}; diff --git a/Source/ZMMO/Game/Modes/ZMMOGameInstance.h b/Source/ZMMO/Game/Modes/ZMMOGameInstance.h index 3a5d59f..89d195c 100644 --- a/Source/ZMMO/Game/Modes/ZMMOGameInstance.h +++ b/Source/ZMMO/Game/Modes/ZMMOGameInstance.h @@ -32,11 +32,12 @@ public: /** * Se `true`, tenta ligar ao servidor Zeus em `Init()` usando - * `ZeusServerHost`/`ZeusServerPort`. Default `true` em V0 (smoke test); - * passar a `false` quando existir UI de login que controle a conexao. + * `ZeusServerHost`/`ZeusServerPort`. Default `false`: a conexao agora e + * dirigida pelo `UUIFrontEndFlowSubsystem` (fluxo Boot→Connecting→Login). + * Pôr `true` apenas para smoke test legado sem front-end. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "ZMMO|Network") - bool bAutoConnectOnStart = true; + bool bAutoConnectOnStart = false; /** Host (IPv4 ou hostname) do servidor Zeus. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "ZMMO|Network") diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIActivatableScreen_Base.cpp b/Source/ZMMO/Game/UI/FrontEnd/UIActivatableScreen_Base.cpp new file mode 100644 index 0000000..562c62e --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/UIActivatableScreen_Base.cpp @@ -0,0 +1,99 @@ +#include "UIActivatableScreen_Base.h" + +#include "ZMMOThemeSubsystem.h" +#include "UIFrontEndFlowSubsystem.h" +#include "Engine/GameInstance.h" +#include "Input/UIActionBindingHandle.h" +#include "CommonInputModeTypes.h" + +TOptional UUIActivatableScreen_Base::GetDesiredInputConfig() const +{ + switch (InputConfig) + { + case EZMMOScreenInputMode::GameAndMenu: + return FUIInputConfig(ECommonInputMode::All, GameMouseCaptureMode); + case EZMMOScreenInputMode::Game: + return FUIInputConfig(ECommonInputMode::Game, GameMouseCaptureMode); + case EZMMOScreenInputMode::Menu: + return FUIInputConfig(ECommonInputMode::Menu, EMouseCaptureMode::NoCapture); + case EZMMOScreenInputMode::Default: + default: + return TOptional(); + } +} + +void UUIActivatableScreen_Base::RefreshUIStyle_Implementation() +{ + // Base no-op: cada WBP/filho sobrescreve para reaplicar tokens do tema + // ativo (via UZMMOThemeSubsystem). Mantém o contrato igual a + // UUIButton_Base::RefreshUIStyle / UUIPanel_Base::RefreshUIStyle. +} + +void UUIActivatableScreen_Base::NativeConstruct() +{ + Super::NativeConstruct(); + + if (!bThemeBound) + { + if (const UGameInstance* GI = GetGameInstance()) + { + if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem()) + { + Theme->OnThemeChanged.AddDynamic(this, &UUIActivatableScreen_Base::HandleThemeChanged); + bThemeBound = true; + } + } + } + + RefreshUIStyle(); +} + +void UUIActivatableScreen_Base::NativeDestruct() +{ + if (bThemeBound) + { + if (const UGameInstance* GI = GetGameInstance()) + { + if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem()) + { + Theme->OnThemeChanged.RemoveDynamic(this, &UUIActivatableScreen_Base::HandleThemeChanged); + } + } + bThemeBound = false; + } + + Super::NativeDestruct(); +} + +void UUIActivatableScreen_Base::NativeOnActivated() +{ + Super::NativeOnActivated(); + RefreshUIStyle(); + BP_OnScreenActivated(); +} + +void UUIActivatableScreen_Base::NativeOnDeactivated() +{ + Super::NativeOnDeactivated(); + BP_OnScreenDeactivated(); +} + +bool UUIActivatableScreen_Base::NativeOnHandleBackAction() +{ + if (const UGameInstance* GI = GetGameInstance()) + { + if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem()) + { + if (Flow->RequestBack()) + { + return true; // consumido pelo fluxo + } + } + } + return Super::NativeOnHandleBackAction(); +} + +void UUIActivatableScreen_Base::HandleThemeChanged(FName /*NewThemeId*/) +{ + RefreshUIStyle(); +} diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIActivatableScreen_Base.h b/Source/ZMMO/Game/UI/FrontEnd/UIActivatableScreen_Base.h new file mode 100644 index 0000000..f07eb8b --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/UIActivatableScreen_Base.h @@ -0,0 +1,77 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CommonActivatableWidget.h" +#include "Engine/EngineBaseTypes.h" +#include "UIActivatableScreen_Base.generated.h" + +/** + * Modo de input desejado pela tela (espelha o padrão do Lyra). O + * UUIActivatableScreen_Base converte isto num FUIInputConfig em + * GetDesiredInputConfig. + */ +UENUM(BlueprintType) +enum class EZMMOScreenInputMode : uint8 +{ + Default, // não força nada (herda do CommonUI) + GameAndMenu, // jogo + UI (ex.: HUD com cursor) + Game, // só jogo + Menu // só UI (telas de front-end — default) +}; + +/** + * Base de TODAS as telas de front-end (Boot/Login/ServerSelect/Lobby/…). + * + * Fundação CommonUI (UCommonActivatableWidget) cuida de + * ativação/foco/voltar/stack. Camada Abstract; o WBP concreto herda DIRETO + * desta classe C++ (UMG não encadeia árvores — ARQUITETURA.md §3.3). Os WBPs + * gerados pelo Zeus UMG Forge são reparented para cá (§4.8). + * + * Reage à troca de tema via UZMMOThemeSubsystem (mesmo padrão de + * UUIButton_Base) e encaminha o "voltar" ao UUIFrontEndFlowSubsystem. + */ +UCLASS(Abstract, Blueprintable) +class ZMMO_API UUIActivatableScreen_Base : public UCommonActivatableWidget +{ + GENERATED_BODY() + +public: + /** Re-resolve os tokens do tema ativo e reaplica. WBP/filho sobrescreve. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "UI Style") + void RefreshUIStyle(); + virtual void RefreshUIStyle_Implementation(); + +protected: + // ---- Input config (desktop/gamepad) ---- + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input") + EZMMOScreenInputMode InputConfig = EZMMOScreenInputMode::Menu; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input") + EMouseCaptureMode GameMouseCaptureMode = EMouseCaptureMode::CapturePermanently; + + virtual TOptional GetDesiredInputConfig() const override; + + // ---- Lifecycle (espelha o hook de tema de UUIButton_Base) ---- + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + virtual void NativeOnActivated() override; + virtual void NativeOnDeactivated() override; + + /** "Voltar" (Esc/B): encaminha ao UUIFrontEndFlowSubsystem::RequestBack. */ + virtual bool NativeOnHandleBackAction() override; + + /** Hooks Blueprint para o WBP refinado. */ + UFUNCTION(BlueprintImplementableEvent, Category = "UI", + meta = (DisplayName = "On Screen Activated")) + void BP_OnScreenActivated(); + + UFUNCTION(BlueprintImplementableEvent, Category = "UI", + meta = (DisplayName = "On Screen Deactivated")) + void BP_OnScreenDeactivated(); + +private: + UFUNCTION() + void HandleThemeChanged(FName NewThemeId); + + bool bThemeBound = false; +}; diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp new file mode 100644 index 0000000..0fb443a --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp @@ -0,0 +1,339 @@ +#include "UIFrontEndFlowSubsystem.h" + +#include "ZMMO.h" +#include "UIFrontEndScreenSet.h" +#include "UIManagerSubsystem.h" +#include "UIPrimaryGameLayout_Base.h" +#include "UI/UILayerTags.h" +#include "ZMMOGameInstance.h" +#include "ZMMOThemeSubsystem.h" +#include "ZeusNetworkSubsystem.h" +#include "CommonActivatableWidget.h" +#include "Engine/GameInstance.h" +#include "Engine/LocalPlayer.h" +#include "UObject/UObjectGlobals.h" +#include "Engine/AssetManager.h" +#include "Engine/StreamableManager.h" +#include "Kismet/GameplayStatics.h" + +void UUIFrontEndFlowSubsystem::Initialize(FSubsystemCollectionBase& Collection) +{ + Super::Initialize(Collection); + + // Detecta chegada ao mundo após o OpenLevel disparado por travel. + PostLoadMapHandle = FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject( + this, &UUIFrontEndFlowSubsystem::HandlePostLoadMap); +} + +void UUIFrontEndFlowSubsystem::Deinitialize() +{ + if (PostLoadMapHandle.IsValid()) + { + FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(PostLoadMapHandle); + PostLoadMapHandle.Reset(); + } + UnbindNetwork(); + Super::Deinitialize(); +} + +void UUIFrontEndFlowSubsystem::StartFrontEnd() +{ + UUIFrontEndScreenSet* SS = GetScreenSet(); + + if (UUIManagerSubsystem* Mgr = GetUIManager()) + { + TSubclassOf LayoutClass; + if (SS && !SS->RootLayoutClass.IsNull()) + { + LayoutClass = SS->RootLayoutClass.LoadSynchronous(); + } + Mgr->CreateAndAddRootLayout(LayoutClass); + } + + BindNetwork(); + + SetState(EZMMOFrontEndState::Boot); + + // Substitui o auto-connect do GameInstance: o fluxo dirige a conexão. + if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork()) + { + FString Host = TEXT("127.0.0.1"); + int32 Port = 27777; + if (const UZMMOGameInstance* GI = Cast(GetGameInstance())) + { + Host = GI->ZeusServerHost; + Port = GI->ZeusServerPort; + } + SetState(EZMMOFrontEndState::Connecting); + Zeus->ConnectToZeusServer(Host, Port); + } + else + { + UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: ZeusNetworkSubsystem indisponível; sem conexão.")); + } +} + +void UUIFrontEndFlowSubsystem::SetState(EZMMOFrontEndState NewState) +{ + if (NewState == CurrentState) + { + return; + } + + CurrentState = NewState; + UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: estado → %s"), + *UEnum::GetValueAsString(NewState)); + OnStateChanged.Broadcast(NewState); + + const FGameplayTag MenuLayer = ZMMOUITags::UI_Layer_Menu.GetTag(); + const FGameplayTag ModalLayer = ZMMOUITags::UI_Layer_Modal.GetTag(); + + switch (NewState) + { + case EZMMOFrontEndState::Boot: + case EZMMOFrontEndState::Connecting: + case EZMMOFrontEndState::Login: + case EZMMOFrontEndState::ServerSelect: + case EZMMOFrontEndState::Lobby: + // Telas do front-end vivem na camada Menu (uma por vez). + if (UUIManagerSubsystem* Mgr = GetUIManager()) + { + Mgr->ClearLayer(MenuLayer); + } + ResolveAndPushScreen(NewState); + break; + + case EZMMOFrontEndState::EnteringWorld: + // Loading/handoff por cima de tudo (camada Modal). + ResolveAndPushScreen(NewState); + break; + + case EZMMOFrontEndState::InWorld: + // HUD de gameplay assume — limpa front-end e loading. + if (UUIManagerSubsystem* Mgr = GetUIManager()) + { + Mgr->ClearLayer(ModalLayer); + Mgr->ClearLayer(MenuLayer); + } + break; + + case EZMMOFrontEndState::None: + default: + break; + } +} + +void UUIFrontEndFlowSubsystem::ShowLobbyPage(EZMMOLobbyPage Page) +{ + UUIFrontEndScreenSet* SS = GetScreenSet(); + UUIManagerSubsystem* Mgr = GetUIManager(); + if (!SS || !Mgr) + { + return; + } + + const TSoftClassPtr Soft = SS->GetLobbyPage(Page); + if (Soft.IsNull()) + { + UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: página de Lobby %s não configurada (DA vazio)."), + *UEnum::GetValueAsString(Page)); + return; + } + + const FGameplayTag MenuLayer = ZMMOUITags::UI_Layer_Menu.GetTag(); + const FSoftObjectPath Path = Soft.ToSoftObjectPath(); + UAssetManager::GetStreamableManager().RequestAsyncLoad( + Path, + FStreamableDelegate::CreateWeakLambda(this, [this, Soft, MenuLayer]() + { + if (UUIManagerSubsystem* M = GetUIManager()) + { + if (UClass* Cls = Soft.Get()) + { + M->PushScreenToLayer(MenuLayer, Cls); + } + } + })); +} + +bool UUIFrontEndFlowSubsystem::RequestBack() +{ + switch (CurrentState) + { + case EZMMOFrontEndState::ServerSelect: + SetState(EZMMOFrontEndState::Login); + return true; + case EZMMOFrontEndState::Lobby: + SetState(EZMMOFrontEndState::ServerSelect); + return true; + default: + return false; + } +} + +void UUIFrontEndFlowSubsystem::ResolveAndPushScreen(EZMMOFrontEndState State) +{ + UUIFrontEndScreenSet* SS = GetScreenSet(); + if (!SS) + { + UE_LOG(LogZMMO, Warning, + TEXT("FrontEndFlow: ScreenSet não configurado; tela de %s ignorada."), + *UEnum::GetValueAsString(State)); + return; + } + + const TSoftClassPtr Soft = SS->GetScreenForState(State); + if (Soft.IsNull()) + { + UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: Screen for state %s not configured."), + *UEnum::GetValueAsString(State)); + return; + } + + UUIManagerSubsystem* Mgr = GetUIManager(); + if (!Mgr) + { + return; + } + + const FGameplayTag Layer = (State == EZMMOFrontEndState::EnteringWorld) + ? ZMMOUITags::UI_Layer_Modal.GetTag() + : ZMMOUITags::UI_Layer_Menu.GetTag(); + + const FSoftObjectPath Path = Soft.ToSoftObjectPath(); + UAssetManager::GetStreamableManager().RequestAsyncLoad( + Path, + FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer]() + { + if (UUIManagerSubsystem* M = GetUIManager()) + { + if (UClass* Cls = Soft.Get()) + { + M->PushScreenToLayer(Layer, Cls); + } + } + })); +} + +UZeusNetworkSubsystem* UUIFrontEndFlowSubsystem::GetZeusNetwork() const +{ + const UGameInstance* GI = GetGameInstance(); + return GI ? GI->GetSubsystem() : nullptr; +} + +UUIManagerSubsystem* UUIFrontEndFlowSubsystem::GetUIManager() const +{ + if (const UGameInstance* GI = GetGameInstance()) + { + if (ULocalPlayer* LP = GI->GetFirstGamePlayer()) + { + return LP->GetSubsystem(); + } + } + return nullptr; +} + +UUIFrontEndScreenSet* UUIFrontEndFlowSubsystem::GetScreenSet() +{ + if (ScreenSet) + { + return ScreenSet; + } + if (!ScreenSetAsset.IsNull()) + { + ScreenSet = ScreenSetAsset.LoadSynchronous(); + } + else + { + UE_LOG(LogZMMO, Warning, + TEXT("FrontEndFlow: ScreenSetAsset não setado em DefaultGame.ini; telas farão no-op.")); + } + return ScreenSet; +} + +void UUIFrontEndFlowSubsystem::BindNetwork() +{ + if (bNetBound) + { + return; + } + if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork()) + { + Zeus->OnConnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnected); + Zeus->OnConnectionFailed.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed); + Zeus->OnDisconnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleDisconnected); + Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested); + bNetBound = true; + } +} + +void UUIFrontEndFlowSubsystem::UnbindNetwork() +{ + if (!bNetBound) + { + return; + } + if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork()) + { + Zeus->OnConnected.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnected); + Zeus->OnConnectionFailed.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed); + Zeus->OnDisconnected.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleDisconnected); + Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested); + } + bNetBound = false; +} + +void UUIFrontEndFlowSubsystem::HandleConnected() +{ + UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: conectado → Login.")); + SetState(EZMMOFrontEndState::Login); +} + +void UUIFrontEndFlowSubsystem::HandleConnectionFailed(FString Reason) +{ + UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: conexão falhou (%s) → volta a Login."), *Reason); + SetState(EZMMOFrontEndState::Login); +} + +void UUIFrontEndFlowSubsystem::HandleDisconnected() +{ + UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: desconectado.")); + if (CurrentState != EZMMOFrontEndState::InWorld) + { + SetState(EZMMOFrontEndState::Login); + } +} + +void UUIFrontEndFlowSubsystem::HandleServerTravelRequested(const FString& MapName, const FString& MapPath) +{ + UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: travel pedido pelo servidor → %s (%s)."), *MapName, *MapPath); + bTravelingToWorld = true; + SetState(EZMMOFrontEndState::EnteringWorld); + + if (!MapPath.IsEmpty()) + { + UGameplayStatics::OpenLevel(GetGameInstance(), FName(*MapPath)); + } +} + +void UUIFrontEndFlowSubsystem::HandlePostLoadMap(UWorld* /*LoadedWorld*/) +{ + if (bTravelingToWorld) + { + bTravelingToWorld = false; + SetState(EZMMOFrontEndState::InWorld); + } +} + +void UUIFrontEndFlowSubsystem::HandleServerHelloTheme(FName ThemeId) +{ + // Dívida técnica (D5): ainda não há gancho no ZeusNetworkSubsystem para o + // ThemeId do ServerHello. Mantido pronto para quando o plugin expuser. + if (const UGameInstance* GI = GetGameInstance()) + { + if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem()) + { + Theme->ApplyServerTheme(ThemeId); + } + } +} diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h new file mode 100644 index 0000000..33d00ff --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h @@ -0,0 +1,109 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/GameInstanceSubsystem.h" +#include "UI/FrontEndTypes.h" +#include "UIFrontEndFlowSubsystem.generated.h" + +class UUIFrontEndScreenSet; +class UUIManagerSubsystem; +class UZeusNetworkSubsystem; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnZMMOFrontEndStateChanged, EZMMOFrontEndState, NewState); + +/** + * Orquestrador do fluxo de front-end. UGameInstanceSubsystem porque o + * GameInstance persiste o travel boot→mundo (a máquina de estados não pode + * morrer no OpenLevel). + * + * Responsável por: (a) dirigir a conexão via UZeusNetworkSubsystem; + * (b) manter EZMMOFrontEndState; (c) resolver estado→tela pelo + * DA_FrontEndScreenSet e pedir ao UUIManagerSubsystem para empurrar na + * camada certa; (d) detectar a chegada ao mundo. + * + * As telas (WBP) ainda não existem — virão do Zeus UMG Forge (§4.8). + * Enquanto não registradas no DA, cada transição faz no-op + log. + */ +UCLASS(Config = Game) +class ZMMO_API UUIFrontEndFlowSubsystem : public UGameInstanceSubsystem +{ + GENERATED_BODY() + +public: + // USubsystem + virtual void Initialize(FSubsystemCollectionBase& Collection) override; + virtual void Deinitialize() override; + + /** + * Ponto de entrada chamado pelo AZMMOFrontEndPlayerController::BeginPlay + * (garante que os subsistemas já existem — ver aviso no header do + * ZeusNetworkSubsystem sobre GameInstance::Init). Cria o root layout, + * liga os delegates de rede e dispara Boot→Connecting. + */ + UFUNCTION(BlueprintCallable, Category = "FrontEnd") + void StartFrontEnd(); + + UFUNCTION(BlueprintCallable, Category = "FrontEnd") + void SetState(EZMMOFrontEndState NewState); + + UFUNCTION(BlueprintPure, Category = "FrontEnd") + EZMMOFrontEndState GetCurrentState() const { return CurrentState; } + + /** Mostra uma página interna do Lobby ("switch principal"). */ + UFUNCTION(BlueprintCallable, Category = "FrontEnd") + void ShowLobbyPage(EZMMOLobbyPage Page); + + /** "Voltar". Retorna true se o fluxo consumiu (tela base encaminha aqui). */ + UFUNCTION(BlueprintCallable, Category = "FrontEnd") + bool RequestBack(); + + UPROPERTY(BlueprintAssignable, Category = "FrontEnd") + FOnZMMOFrontEndStateChanged OnStateChanged; + +protected: + /** DA com o mapa estado→tela. Config em DefaultGame.ini. */ + UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd") + TSoftObjectPtr ScreenSetAsset; + +private: + void BindNetwork(); + void UnbindNetwork(); + UZeusNetworkSubsystem* GetZeusNetwork() const; + UUIManagerSubsystem* GetUIManager() const; + UUIFrontEndScreenSet* GetScreenSet(); + + /** Resolve a soft class do estado e empurra na camada Menu (no-op+log se vazio). */ + void ResolveAndPushScreen(EZMMOFrontEndState State); + + UFUNCTION() + void HandleConnected(); + + UFUNCTION() + void HandleConnectionFailed(FString Reason); + + UFUNCTION() + void HandleDisconnected(); + + UFUNCTION() + void HandleServerTravelRequested(const FString& MapName, const FString& MapPath); + + void HandlePostLoadMap(UWorld* LoadedWorld); + + /** + * Dívida técnica (D5): ServerHello traz ThemeId, mas o UZeusNetworkSubsystem + * ainda não expõe um delegate/getter dedicado. Quando expuser, ligar aqui + * → UZMMOThemeSubsystem::ApplyServerTheme. Sem isto, o tema resolve por + * calendário/Default (ARQUITETURA.md §1.10). + */ + void HandleServerHelloTheme(FName ThemeId); + + UPROPERTY(Transient) + EZMMOFrontEndState CurrentState = EZMMOFrontEndState::None; + + UPROPERTY(Transient) + TObjectPtr ScreenSet; + + bool bNetBound = false; + bool bTravelingToWorld = false; + FDelegateHandle PostLoadMapHandle; +}; diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndScreenSet.cpp b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndScreenSet.cpp new file mode 100644 index 0000000..b00badc --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndScreenSet.cpp @@ -0,0 +1,19 @@ +#include "UIFrontEndScreenSet.h" + +TSoftClassPtr UUIFrontEndScreenSet::GetScreenForState(EZMMOFrontEndState State) const +{ + if (const TSoftClassPtr* Found = StateScreens.Find(State)) + { + return *Found; + } + return TSoftClassPtr(); +} + +TSoftClassPtr UUIFrontEndScreenSet::GetLobbyPage(EZMMOLobbyPage Page) const +{ + if (const TSoftClassPtr* Found = LobbyPages.Find(Page)) + { + return *Found; + } + return TSoftClassPtr(); +} diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndScreenSet.h b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndScreenSet.h new file mode 100644 index 0000000..5f5e5fb --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndScreenSet.h @@ -0,0 +1,45 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" +#include "Templates/SubclassOf.h" +#include "UI/FrontEndTypes.h" +#include "UIFrontEndScreenSet.generated.h" + +class UCommonActivatableWidget; +class UUIPrimaryGameLayout_Base; + +/** + * Mapa data-driven estado → tela. Desacopla o UUIFrontEndFlowSubsystem das + * classes concretas: as telas (WBP) ainda não existem — serão geradas pelo + * Zeus UMG Forge, refinadas e registradas aqui depois (ARQUITETURA.md §4.8). + * Enquanto o registro estiver vazio, o flow faz no-op + log. + * + * Asset concreto: DA_FrontEndScreenSet em Content/ZMMO/UI/FrontEnd/. + */ +UCLASS(BlueprintType) +class ZMMO_API UUIFrontEndScreenSet : public UDataAsset +{ + GENERATED_BODY() + +public: + /** WBP de layout raiz (herda UUIPrimaryGameLayout_Base). */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "FrontEnd") + TSoftClassPtr RootLayoutClass; + + /** Tela por estado de topo do fluxo. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "FrontEnd") + TMap> StateScreens; + + /** Páginas internas do Lobby (host = a tela de Lobby; "switch principal"). */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "FrontEnd") + TMap> LobbyPages; + + /** Soft class da tela do estado (vazio se não configurado). */ + UFUNCTION(BlueprintCallable, Category = "FrontEnd") + TSoftClassPtr GetScreenForState(EZMMOFrontEndState State) const; + + /** Soft class da página de Lobby (vazio se não configurado). */ + UFUNCTION(BlueprintCallable, Category = "FrontEnd") + TSoftClassPtr GetLobbyPage(EZMMOLobbyPage Page) const; +}; diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIManagerSubsystem.cpp b/Source/ZMMO/Game/UI/FrontEnd/UIManagerSubsystem.cpp new file mode 100644 index 0000000..5eb1fea --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/UIManagerSubsystem.cpp @@ -0,0 +1,95 @@ +#include "UIManagerSubsystem.h" + +#include "ZMMO.h" +#include "UIPrimaryGameLayout_Base.h" +#include "CommonActivatableWidget.h" +#include "Engine/LocalPlayer.h" +#include "GameFramework/PlayerController.h" +#include "Blueprint/UserWidget.h" + +namespace +{ + constexpr int32 GRootLayoutZOrder = 1000; +} + +void UUIManagerSubsystem::CreateAndAddRootLayout(TSubclassOf LayoutClass) +{ + // Idempotente: sobrevive ao travel boot→mundo sem recriar. + if (RootLayout && RootLayout->IsInViewport()) + { + return; + } + + if (!LayoutClass) + { + UE_LOG(LogZMMO, Warning, + TEXT("UIManagerSubsystem: RootLayoutClass não configurado (DA_FrontEndScreenSet vazio?). Sem root layout.")); + return; + } + + ULocalPlayer* LP = GetLocalPlayer(); + if (!LP) + { + return; + } + + APlayerController* PC = LP->GetPlayerController(LP->GetWorld()); + if (!PC) + { + UE_LOG(LogZMMO, Warning, TEXT("UIManagerSubsystem: PlayerController ausente; root layout adiado.")); + return; + } + + RootLayout = CreateWidget(PC, LayoutClass); + if (!RootLayout) + { + UE_LOG(LogZMMO, Warning, TEXT("UIManagerSubsystem: falha ao criar o root layout.")); + return; + } + + RootLayout->AddToViewport(GRootLayoutZOrder); + UE_LOG(LogZMMO, Log, TEXT("UIManagerSubsystem: root layout criado e adicionado ao viewport.")); +} + +bool UUIManagerSubsystem::IsRootLayoutReady() const +{ + return RootLayout != nullptr && RootLayout->IsInViewport(); +} + +UCommonActivatableWidget* UUIManagerSubsystem::PushScreenToLayer( + FGameplayTag LayerTag, + TSubclassOf WidgetClass) +{ + if (!RootLayout) + { + UE_LOG(LogZMMO, Warning, TEXT("UIManagerSubsystem: PushScreenToLayer sem root layout.")); + return nullptr; + } + return RootLayout->PushWidgetToLayer(LayerTag, WidgetClass); +} + +void UUIManagerSubsystem::PopScreenFromLayer(FGameplayTag LayerTag, UCommonActivatableWidget* Widget) +{ + if (RootLayout) + { + RootLayout->PopWidgetFromLayer(LayerTag, Widget); + } +} + +void UUIManagerSubsystem::ClearLayer(FGameplayTag LayerTag) +{ + if (RootLayout) + { + RootLayout->ClearLayer(LayerTag); + } +} + +void UUIManagerSubsystem::Deinitialize() +{ + if (RootLayout) + { + RootLayout->RemoveFromParent(); + RootLayout = nullptr; + } + Super::Deinitialize(); +} diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIManagerSubsystem.h b/Source/ZMMO/Game/UI/FrontEnd/UIManagerSubsystem.h new file mode 100644 index 0000000..9f1b66b --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/UIManagerSubsystem.h @@ -0,0 +1,55 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/LocalPlayerSubsystem.h" +#include "GameplayTagContainer.h" +#include "Templates/SubclassOf.h" +#include "UIManagerSubsystem.generated.h" + +class UCommonActivatableWidget; +class UUIPrimaryGameLayout_Base; + +/** + * Dono do layout raiz para o LocalPlayer. ULocalPlayerSubsystem porque o + * LocalPlayer persiste entre mapas (non-seamless do mesmo GameInstance) e + * troca de PlayerController — o root layout sobrevive ao travel + * boot→mundo sem ser recriado (idempotente). + * + * É "burro": recebe classes já resolvidas. Quem resolve estado→tela e dirige + * a navegação é o UUIFrontEndFlowSubsystem. + */ +UCLASS() +class ZMMO_API UUIManagerSubsystem : public ULocalPlayerSubsystem +{ + GENERATED_BODY() + +public: + /** Cria o root layout e adiciona ao viewport. Idempotente. */ + UFUNCTION(BlueprintCallable, Category = "UI|Manager") + void CreateAndAddRootLayout(TSubclassOf LayoutClass); + + UFUNCTION(BlueprintPure, Category = "UI|Manager") + bool IsRootLayoutReady() const; + + UFUNCTION(BlueprintPure, Category = "UI|Manager") + UUIPrimaryGameLayout_Base* GetRootLayout() const { return RootLayout; } + + /** Empurra a widget no topo do stack da camada. */ + UFUNCTION(BlueprintCallable, Category = "UI|Manager") + UCommonActivatableWidget* PushScreenToLayer( + FGameplayTag LayerTag, + TSubclassOf WidgetClass); + + UFUNCTION(BlueprintCallable, Category = "UI|Manager") + void PopScreenFromLayer(FGameplayTag LayerTag, UCommonActivatableWidget* Widget); + + UFUNCTION(BlueprintCallable, Category = "UI|Manager") + void ClearLayer(FGameplayTag LayerTag); + + // USubsystem + virtual void Deinitialize() override; + +private: + UPROPERTY(Transient) + TObjectPtr RootLayout; +}; diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIPrimaryGameLayout_Base.cpp b/Source/ZMMO/Game/UI/FrontEnd/UIPrimaryGameLayout_Base.cpp new file mode 100644 index 0000000..87165f0 --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/UIPrimaryGameLayout_Base.cpp @@ -0,0 +1,64 @@ +#include "UIPrimaryGameLayout_Base.h" + +#include "CommonActivatableWidget.h" +#include "Widgets/CommonActivatableWidgetContainer.h" +#include "UI/UILayerTags.h" + +void UUIPrimaryGameLayout_Base::NativeOnInitialized() +{ + Super::NativeOnInitialized(); + + RegisterLayer(ZMMOUITags::UI_Layer_Game, Stack_Game); + RegisterLayer(ZMMOUITags::UI_Layer_GameMenu, Stack_GameMenu); + RegisterLayer(ZMMOUITags::UI_Layer_Menu, Stack_Menu); + RegisterLayer(ZMMOUITags::UI_Layer_Modal, Stack_Modal); +} + +void UUIPrimaryGameLayout_Base::RegisterLayer(FGameplayTag LayerTag, UCommonActivatableWidgetStack* Stack) +{ + if (Stack) + { + Layers.Add(LayerTag, Stack); + } +} + +UCommonActivatableWidgetStack* UUIPrimaryGameLayout_Base::GetLayerStack(FGameplayTag LayerTag) const +{ + const TObjectPtr* Found = Layers.Find(LayerTag); + return Found ? *Found : nullptr; +} + +UCommonActivatableWidget* UUIPrimaryGameLayout_Base::PushWidgetToLayer( + FGameplayTag LayerTag, + TSubclassOf WidgetClass) +{ + if (!WidgetClass) + { + return nullptr; + } + if (UCommonActivatableWidgetStack* Stack = GetLayerStack(LayerTag)) + { + return Stack->AddWidget(WidgetClass); + } + return nullptr; +} + +void UUIPrimaryGameLayout_Base::PopWidgetFromLayer(FGameplayTag LayerTag, UCommonActivatableWidget* Widget) +{ + if (!Widget) + { + return; + } + if (UCommonActivatableWidgetStack* Stack = GetLayerStack(LayerTag)) + { + Stack->RemoveWidget(*Widget); + } +} + +void UUIPrimaryGameLayout_Base::ClearLayer(FGameplayTag LayerTag) +{ + if (UCommonActivatableWidgetStack* Stack = GetLayerStack(LayerTag)) + { + Stack->ClearWidgets(); + } +} diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIPrimaryGameLayout_Base.h b/Source/ZMMO/Game/UI/FrontEnd/UIPrimaryGameLayout_Base.h new file mode 100644 index 0000000..a01a074 --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/UIPrimaryGameLayout_Base.h @@ -0,0 +1,72 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CommonUserWidget.h" +#include "GameplayTagContainer.h" +#include "Templates/SubclassOf.h" +#include "UIPrimaryGameLayout_Base.generated.h" + +class UCommonActivatableWidget; +class UCommonActivatableWidgetStack; + +/** + * Layout raiz ("switch principal") do cliente. Mantém 4 stacks CommonUI, + * um por camada (GameplayTag UI.Layer.* — ver UI/UILayerTags.h): + * + * Stack_Game ← UI.Layer.Game (HUD de gameplay) + * Stack_GameMenu ← UI.Layer.GameMenu (menus sobre o jogo) + * Stack_Menu ← UI.Layer.Menu (front-end: Boot/Login/Lobby/…) + * Stack_Modal ← UI.Layer.Modal (diálogos/loading por cima) + * + * Camada Abstract; o WBP concreto (WBP_PrimaryGameLayout) herda DIRETO desta + * classe C++ e nomeia os 4 stacks exatamente como os BindWidget abaixo + * (ARQUITETURA.md §3.3). O UUIManagerSubsystem instancia o WBP e o adiciona + * ao viewport; o UUIFrontEndFlowSubsystem empurra telas por camada. + */ +UCLASS(Abstract, Blueprintable) +class ZMMO_API UUIPrimaryGameLayout_Base : public UCommonUserWidget +{ + GENERATED_BODY() + +public: + /** Stack da camada (nullptr se a tag não for uma UI.Layer.* conhecida). */ + UFUNCTION(BlueprintCallable, Category = "UI|Layout") + UCommonActivatableWidgetStack* GetLayerStack(FGameplayTag LayerTag) const; + + /** Instancia e empurra a widget no topo do stack da camada. */ + UFUNCTION(BlueprintCallable, Category = "UI|Layout") + UCommonActivatableWidget* PushWidgetToLayer( + FGameplayTag LayerTag, + TSubclassOf WidgetClass); + + /** Remove uma widget específica do stack da camada. */ + UFUNCTION(BlueprintCallable, Category = "UI|Layout") + void PopWidgetFromLayer(FGameplayTag LayerTag, UCommonActivatableWidget* Widget); + + /** Esvazia o stack da camada. */ + UFUNCTION(BlueprintCallable, Category = "UI|Layout") + void ClearLayer(FGameplayTag LayerTag); + +protected: + virtual void NativeOnInitialized() override; + + /** Registra um stack numa camada (chamado em NativeOnInitialized). */ + void RegisterLayer(FGameplayTag LayerTag, UCommonActivatableWidgetStack* Stack); + + // ---- Stacks por camada (o WBP concreto deve nomear assim) ---- + UPROPERTY(BlueprintReadOnly, Category = "UI|Layout", meta = (BindWidget)) + TObjectPtr Stack_Game; + + UPROPERTY(BlueprintReadOnly, Category = "UI|Layout", meta = (BindWidget)) + TObjectPtr Stack_GameMenu; + + UPROPERTY(BlueprintReadOnly, Category = "UI|Layout", meta = (BindWidget)) + TObjectPtr Stack_Menu; + + UPROPERTY(BlueprintReadOnly, Category = "UI|Layout", meta = (BindWidget)) + TObjectPtr Stack_Modal; + +private: + UPROPERTY(Transient) + TMap> Layers; +}; diff --git a/Source/ZMMO/ZMMO.Build.cs b/Source/ZMMO/ZMMO.Build.cs index cf1f3c9..1550ba0 100644 --- a/Source/ZMMO/ZMMO.Build.cs +++ b/Source/ZMMO/ZMMO.Build.cs @@ -17,6 +17,7 @@ public class ZMMO : ModuleRules "Niagara", "CommonUI", "CommonInput", + "GameplayTags", "ZeusNetwork" }); @@ -33,6 +34,7 @@ public class ZMMO : ModuleRules "ZMMO/Game/Network", "ZMMO/Game/UI", "ZMMO/Game/UI/Widgets", + "ZMMO/Game/UI/FrontEnd", "ZMMO/Data", "ZMMO/Data/Items", "ZMMO/Data/Mobs",