From bc07c4755743ab09bf08d7655fd1ac62b5e9c3cb Mon Sep 17 00:00:00 2001 From: Mateus Rodrigues Date: Tue, 19 May 2026 01:56:16 -0300 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20esqueleto=20CommonUI=20de=20M?= =?UTF-8?q?ainMenu=20(boot=E2=86=92login=E2=86=92lobby)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Estrutura principal de front-end estilo Lyra portada para C++ proprio do ZMMO (sem CommonGame), seguindo o padrao C++ Abstract -> WBP concreto: - EZMMOFrontEndState / EZMMOLobbyPage e GameplayTags UI.Layer.* nativos - UUIActivatableScreen_Base: base de tela (input config, hook de tema, voltar) espelhando o padrao de UUIButton_Base - UUIPrimaryGameLayout_Base: switch principal (4 stacks CommonUI/camada) - UUIManagerSubsystem (LocalPlayer): dono do root layout, idempotente - UUIFrontEndFlowSubsystem (GameInstance): maquina de estados + driver de rede (Boot->Connecting->Login->ServerSelect->Lobby->EnteringWorld ->InWorld); Lobby e o hub logado, paginas internas = EZMMOLobbyPage - UUIFrontEndScreenSet (DataAsset): mapa estado->tela, forge-ready - AZMMOFrontEndGameMode + AZMMOFrontEndPlayerController - GameInstance: bAutoConnectOnStart=false (conexao dirigida pelo fluxo) - Build.cs: +GameplayTags; ARQUITETURA.md PR-first (s2.1/2.2/3.3/4.8/5) - Assets: L_FrontEnd (GameMode via World Settings), WBP_PrimaryGameLayout, DA_FrontEndScreenSet; Config wiring WBPs de pagina (Boot/Login/...) virao do Zeus UMG Forge depois (s4.8). Smoke test PIE OK: root layout no viewport, fluxo Boot->Connecting, no-op+log para telas nao configuradas, sem crash. Co-Authored-By: Claude Opus 4.7 (1M context) --- ARQUITETURA.md | 81 ++++- Config/DefaultEngine.ini | 4 +- Config/DefaultGame.ini | 5 + Content/ZMMO/Maps/FrontEnd/L_FrontEnd.umap | Bin 0 -> 8665 bytes .../UI/FrontEnd/DA_FrontEndScreenSet.uasset | Bin 0 -> 1417 bytes .../UI/FrontEnd/WBP_PrimaryGameLayout.uasset | Bin 0 -> 29668 bytes Source/ZMMO/Data/UI/FrontEndTypes.h | 40 +++ Source/ZMMO/Data/UI/UILayerTags.cpp | 9 + Source/ZMMO/Data/UI/UILayerTags.h | 21 ++ .../ZMMOFrontEndPlayerController.cpp | 24 ++ .../Controller/ZMMOFrontEndPlayerController.h | 26 ++ .../ZMMO/Game/Modes/ZMMOFrontEndGameMode.cpp | 10 + Source/ZMMO/Game/Modes/ZMMOFrontEndGameMode.h | 22 ++ Source/ZMMO/Game/Modes/ZMMOGameInstance.h | 7 +- .../UI/FrontEnd/UIActivatableScreen_Base.cpp | 99 +++++ .../UI/FrontEnd/UIActivatableScreen_Base.h | 77 ++++ .../UI/FrontEnd/UIFrontEndFlowSubsystem.cpp | 339 ++++++++++++++++++ .../UI/FrontEnd/UIFrontEndFlowSubsystem.h | 109 ++++++ .../Game/UI/FrontEnd/UIFrontEndScreenSet.cpp | 19 + .../Game/UI/FrontEnd/UIFrontEndScreenSet.h | 45 +++ .../Game/UI/FrontEnd/UIManagerSubsystem.cpp | 95 +++++ .../Game/UI/FrontEnd/UIManagerSubsystem.h | 55 +++ .../UI/FrontEnd/UIPrimaryGameLayout_Base.cpp | 64 ++++ .../UI/FrontEnd/UIPrimaryGameLayout_Base.h | 72 ++++ Source/ZMMO/ZMMO.Build.cs | 2 + 25 files changed, 1214 insertions(+), 11 deletions(-) create mode 100644 Content/ZMMO/Maps/FrontEnd/L_FrontEnd.umap create mode 100644 Content/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.uasset create mode 100644 Content/ZMMO/UI/FrontEnd/WBP_PrimaryGameLayout.uasset create mode 100644 Source/ZMMO/Data/UI/FrontEndTypes.h create mode 100644 Source/ZMMO/Data/UI/UILayerTags.cpp create mode 100644 Source/ZMMO/Data/UI/UILayerTags.h create mode 100644 Source/ZMMO/Game/Controller/ZMMOFrontEndPlayerController.cpp create mode 100644 Source/ZMMO/Game/Controller/ZMMOFrontEndPlayerController.h create mode 100644 Source/ZMMO/Game/Modes/ZMMOFrontEndGameMode.cpp create mode 100644 Source/ZMMO/Game/Modes/ZMMOFrontEndGameMode.h create mode 100644 Source/ZMMO/Game/UI/FrontEnd/UIActivatableScreen_Base.cpp create mode 100644 Source/ZMMO/Game/UI/FrontEnd/UIActivatableScreen_Base.h create mode 100644 Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp create mode 100644 Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h create mode 100644 Source/ZMMO/Game/UI/FrontEnd/UIFrontEndScreenSet.cpp create mode 100644 Source/ZMMO/Game/UI/FrontEnd/UIFrontEndScreenSet.h create mode 100644 Source/ZMMO/Game/UI/FrontEnd/UIManagerSubsystem.cpp create mode 100644 Source/ZMMO/Game/UI/FrontEnd/UIManagerSubsystem.h create mode 100644 Source/ZMMO/Game/UI/FrontEnd/UIPrimaryGameLayout_Base.cpp create mode 100644 Source/ZMMO/Game/UI/FrontEnd/UIPrimaryGameLayout_Base.h 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 0000000000000000000000000000000000000000..47d31b85759732e886eed4b9584c69654e1d600f GIT binary patch literal 8665 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 SY|Y7(@uB;74{=xo@c$RARBE>X literal 0 HcmV?d00001 diff --git a/Content/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.uasset b/Content/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.uasset new file mode 100644 index 0000000000000000000000000000000000000000..bdf26a09670692553fe9d3eba7baf257f9d881e2 GIT binary patch literal 1417 zcmX@utTpfZ|Ns9Jm>C$jm>3v-0cjAJ_}{uB?MK$K(-Xed@w@LWS?jr^SeuoB0jPwb zO+q%I#Xn`uy~|>&t{s2H`w%GhHekftp_8sx|ns7F*-K(Y)BAR43w zM1$060BHjtb^u}+xHs8D!ujMRuj$9q=SSOZnh8<|!XQVnGO#nSFt{BMWEKMQw6#NB zO|^qkb5awFQ*}-C%)#;u4DVyEGGUhoY61hv_P9SFMkUbRP+))}17JM-2L&=4P%sin zP8Z0>5s=|d0r3GvnYoEYm7tLJNvzB-En&zEH(dnM@0XvK%J7BxD6cG#6OfpkotU2L zlb@WJlUbEml9`|92h>trkeHmxAXHM>Apleql%HP$F~m71vACE)*=s+iFi<==Kdr<+ zDJwO(B%mn2AhoEZk|ElA-7g8C6jmq1J2U)tyZn>XB=AwtT~P#RramxLWEPa@JLear zhC=LR_~EH*2Z{-p94JvSq^K`?0SYdNYfH*2CW-=iSd*8+oXcGtKuKsgFciCYh=S}2 z^~6ky?ziP!fKp(96cJ!PC`Eu$7zii=F~Q`?4pz#*zzL=Q0i&4*$_5z*12A>mFaaP9 zaxe%Ws{JHQXL$>paKMBHVA_Ov_t_G t0|)?T!&L$04gw3+>ED(%=vg1R2udk32=!oA**%LumQ(vR4WQy6^#D4PFpdBK literal 0 HcmV?d00001 diff --git a/Content/ZMMO/UI/FrontEnd/WBP_PrimaryGameLayout.uasset b/Content/ZMMO/UI/FrontEnd/WBP_PrimaryGameLayout.uasset new file mode 100644 index 0000000000000000000000000000000000000000..3903827301d3ac18b120fa6e6db2b08a65bb9173 GIT binary patch literal 29668 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 + { + 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",