diff --git a/ARQUITETURA.md b/ARQUITETURA.md index 9000898..3d2e7fc 100644 --- a/ARQUITETURA.md +++ b/ARQUITETURA.md @@ -210,7 +210,8 @@ Content/ │ ├── UI/ ← HUD, Login, CharacterSelect, Inventory, │ │ │ Chat, Party, Guild, Trade, Quest, Map, │ │ │ Shared, Icons -│ │ ├── Shared/ ← WBPs reutilizáveis (UI_Button_Master…) +│ │ ├── Shared/ ← WBPs reutilizáveis (UI_Button_Master, +│ │ │ UI_Panel_Master, UI_Spinner_Master) │ │ ├── FrontEnd/ ← WBP_PrimaryGameLayout, DA_FrontEndScreenSet, │ │ │ telas refinadas (Boot/Login/… vindas do Forge) │ │ ├── Fonts/ ← Font_*/FF_* (Cinzel, Rajdhani) @@ -268,9 +269,11 @@ Source/ZMMO/ │ ├── Network/ ← UZMMOWorldSubsystem │ └── UI/ ← runtime de UI (não é contrato de dados) │ ├── ZMMOThemeSubsystem.* ← resolução de tema -│ ├── Widgets/ ← UUIButton_Base, UUIPanel_Base +│ ├── Widgets/ ← UUIButton_Base, UUIPanel_Base, +│ │ UUISpinner_Base │ └── FrontEnd/ ← infra de navegação CommonUI: │ UUIActivatableScreen_Base, +│ UUIBootScreen_Base (1ª tela), │ UUIPrimaryGameLayout_Base, │ UUIManagerSubsystem (LocalPlayer), │ UUIFrontEndFlowSubsystem (GameInstance), @@ -622,6 +625,15 @@ Fluxo geral: **Boot → Connecting → Login → ServerSelect → Lobby → Ente navegadas por um stack CommonUI **dentro** da tela do Lobby — não são estados de topo do fluxo. +**Rede do pré-login:** o `UUIFrontEndFlowSubsystem` dirige a conexão ao +**`UZeusCharServerSubsystem`** (WebSocket, `CharServerUrl` de Project Settings, +default `ws://127.0.0.1:7100/zeus`) ao entrar em **Boot** — NÃO o +`UZeusNetworkSubsystem` (UDP, world server, usado só no travel +EnteringWorld→InWorld). A tela Boot (`UUIBootScreen_Base`) observa o +CharServer: libera o botão "Iniciar" no `OnConnected`; o clique chama +`UUIFrontEndFlowSubsystem::RequestEnterLogin()` → estado Login. Tela nunca +segura hard-ref de asset de tema (cores via `FUIStyle`; §5). + 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//`. diff --git a/Content/ZMMO/Data/UI/DT_UI_Styles.uasset b/Content/ZMMO/Data/UI/DT_UI_Styles.uasset index c239beb..4ae3003 100644 Binary files a/Content/ZMMO/Data/UI/DT_UI_Styles.uasset and b/Content/ZMMO/Data/UI/DT_UI_Styles.uasset differ diff --git a/Content/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.uasset b/Content/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.uasset index bdf26a0..8f52edb 100644 Binary files a/Content/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.uasset and b/Content/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.uasset differ diff --git a/Content/ZMMO/UI/FrontEnd/WBP_Boot.uasset b/Content/ZMMO/UI/FrontEnd/WBP_Boot.uasset new file mode 100644 index 0000000..ba440a5 Binary files /dev/null and b/Content/ZMMO/UI/FrontEnd/WBP_Boot.uasset differ diff --git a/Content/ZMMO/UI/FrontEnd/WBP_PrimaryGameLayout.uasset b/Content/ZMMO/UI/FrontEnd/WBP_PrimaryGameLayout.uasset index 3903827..a406cea 100644 Binary files a/Content/ZMMO/UI/FrontEnd/WBP_PrimaryGameLayout.uasset and b/Content/ZMMO/UI/FrontEnd/WBP_PrimaryGameLayout.uasset differ diff --git a/Content/ZMMO/UI/Shared/UI_Spinner_Master.uasset b/Content/ZMMO/UI/Shared/UI_Spinner_Master.uasset new file mode 100644 index 0000000..53d0eea Binary files /dev/null and b/Content/ZMMO/UI/Shared/UI_Spinner_Master.uasset differ diff --git a/Content/ZMMO/UI/Themes/Default/Boot/T_Boot_Background.uasset b/Content/ZMMO/UI/Themes/Default/Boot/T_Boot_Background.uasset new file mode 100644 index 0000000..fa1c77b Binary files /dev/null and b/Content/ZMMO/UI/Themes/Default/Boot/T_Boot_Background.uasset differ diff --git a/Source/ZMMO/Data/UI/ThemeKeys.h b/Source/ZMMO/Data/UI/ThemeKeys.h index d51d30e..c7b2eca 100644 --- a/Source/ZMMO/Data/UI/ThemeKeys.h +++ b/Source/ZMMO/Data/UI/ThemeKeys.h @@ -12,6 +12,11 @@ enum class EZMMOThemeKey : uint8 HUD_HealthBar UMETA(DisplayName = "HUD - Health Bar"), HUD_ManaBar UMETA(DisplayName = "HUD - Mana Bar"), + // Boot + Boot_Background UMETA(DisplayName = "Boot - Background"), + Boot_Logo UMETA(DisplayName = "Boot - Logo"), + Boot_Music UMETA(DisplayName = "Boot - Music"), + // Login / Character Select Login_Background UMETA(DisplayName = "Login - Background"), Login_Logo UMETA(DisplayName = "Login - Logo"), diff --git a/Source/ZMMO/Data/UI/UIStyleRow.h b/Source/ZMMO/Data/UI/UIStyleRow.h index c52f8f9..2a395c6 100644 --- a/Source/ZMMO/Data/UI/UIStyleRow.h +++ b/Source/ZMMO/Data/UI/UIStyleRow.h @@ -42,6 +42,17 @@ struct ZMMO_API FUIStyle UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style") FUIStyleTooltip Tooltip; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style") + FUIStyleSpinner Spinner; + + /** + * Fundo de tela cheia do front-end (ex.: Boot/Login). Brush data-driven: + * a tela aplica no seu Border_Bg via RefreshUIStyle — nunca hard-ref no + * WBP (ARQUITETURA.md §5). Preenchido em DT_UI_Styles. + */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style") + FSlateBrush ScreenBackground; }; /** diff --git a/Source/ZMMO/Data/UI/UIStyleTokens.h b/Source/ZMMO/Data/UI/UIStyleTokens.h index 17f505c..137ddaf 100644 --- a/Source/ZMMO/Data/UI/UIStyleTokens.h +++ b/Source/ZMMO/Data/UI/UIStyleTokens.h @@ -444,3 +444,34 @@ struct ZMMO_API FUIStyleTooltip UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Tooltip") FLinearColor TextColor = FLinearColor(FColor(244, 240, 230)); // Text }; + +/** Spinner/throbber indeterminado (ex.: "Conectando..." no Boot). */ +USTRUCT(BlueprintType) +struct ZMMO_API FUIStyleSpinner +{ + GENERATED_BODY() + + /** Cor das peças do spinner. Default = Gold (Aurora Arcana). */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner") + FLinearColor Color = FLinearColor(FColor(201, 167, 90)); // Gold + + /** Cor do "trilho"/peças apagadas (UCircularThrobber não usa; reservado). */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner") + FLinearColor TrackColor = FLinearColor(FColor(58, 75, 120, 140)); + + /** Raio do anel (px). */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner") + float Radius = 28.f; + + /** Espessura da peça (px). */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner") + float Thickness = 3.f; + + /** Número de peças do UCircularThrobber. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner", meta = (ClampMin = "1")) + int32 NumberOfPieces = 8; + + /** Período de uma volta (s). */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner", meta = (ClampMin = "0.05")) + float Period = 0.9f; +}; diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIBootScreen_Base.cpp b/Source/ZMMO/Game/UI/FrontEnd/UIBootScreen_Base.cpp new file mode 100644 index 0000000..ecf0d63 --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/UIBootScreen_Base.cpp @@ -0,0 +1,175 @@ +#include "UIBootScreen_Base.h" + +#include "ZMMO.h" +#include "UIButton_Base.h" +#include "UISpinner_Base.h" +#include "UIFrontEndFlowSubsystem.h" +#include "ZMMOThemeSubsystem.h" +#include "ZeusCharServerSubsystem.h" +#include "CommonTextBlock.h" +#include "Components/Border.h" +#include "Engine/GameInstance.h" +#include "Engine/DataTable.h" +#include "UI/UIStyleRow.h" + +namespace +{ + // Runtime usa o tema ativo; design-time carrega a row "Default" de + // DT_UI_Styles para o Designer ver o fundo (mesmo padrão de UUIPanel_Base). + const FUIStyle& ResolveBootStyle(const UUserWidget* Widget, const FUIStyle& Fallback) + { + if (Widget) + { + if (const UGameInstance* GI = Widget->GetGameInstance()) + { + if (const UZMMOThemeSubsystem* Theme = GI->GetSubsystem()) + { + return Theme->GetActiveUIStyle(); + } + } + } + static FUIStyle DesignStyle; + static bool bLoaded = false; + if (!bLoaded) + { + if (const UDataTable* DT = LoadObject(nullptr, + TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles"))) + { + if (const FUIStyleRow* Row = DT->FindRow( + FName(TEXT("Default")), TEXT("UIBootDesign"), false)) + { + DesignStyle = Row->Style; + bLoaded = true; + } + } + } + return bLoaded ? DesignStyle : Fallback; + } +} + +void UUIBootScreen_Base::NativePreConstruct() +{ + Super::NativePreConstruct(); + RefreshUIStyle(); // preview do fundo no Designer (DT_UI_Styles "Default") +} + +void UUIBootScreen_Base::NativeOnActivated() +{ + Super::NativeOnActivated(); // base: RefreshUIStyle + BP_OnScreenActivated + + if (Btn_Start) + { + Btn_Start->OnClicked().AddUObject(this, &UUIBootScreen_Base::HandleStartClicked); + } + + UZeusCharServerSubsystem* Char = GetCharServer(); + const bool bConnected = Char && Char->IsConnected(); + SetReady(bConnected); + + if (Char && !bCharBound) + { + Char->OnConnected.AddDynamic(this, &UUIBootScreen_Base::HandleCharConnected); + Char->OnConnectionFailed.AddDynamic(this, &UUIBootScreen_Base::HandleCharConnectionFailed); + bCharBound = true; + } + // A conexão em si é disparada pelo UUIFrontEndFlowSubsystem (estado Boot). +} + +void UUIBootScreen_Base::NativeOnDeactivated() +{ + if (Btn_Start) + { + Btn_Start->OnClicked().RemoveAll(this); + } + if (bCharBound) + { + if (UZeusCharServerSubsystem* Char = GetCharServer()) + { + Char->OnConnected.RemoveDynamic(this, &UUIBootScreen_Base::HandleCharConnected); + Char->OnConnectionFailed.RemoveDynamic(this, &UUIBootScreen_Base::HandleCharConnectionFailed); + } + bCharBound = false; + } + Super::NativeOnDeactivated(); +} + +void UUIBootScreen_Base::RefreshUIStyle_Implementation() +{ + Super::RefreshUIStyle_Implementation(); + + const FUIStyle Fallback; + const FUIStyle& AS = ResolveBootStyle(this, Fallback); + + if (Border_Bg) + { + // Fundo data-driven do DT_UI_Styles (T_Boot_Background) — sem + // hard-ref de textura no WBP (§5). Fallback p/ cor se vazio. + if (AS.ScreenBackground.GetResourceObject() != nullptr) + { + Border_Bg->SetBrush(AS.ScreenBackground); + } + else + { + Border_Bg->SetBrushColor(AS.Palette.Bg0); + } + } + if (Text_Status) + { + Text_Status->SetColorAndOpacity(FSlateColor(AS.Semantic.OnSurfaceMuted)); + } +} + +void UUIBootScreen_Base::SetReady(bool bReady) +{ + if (Btn_Start) + { + Btn_Start->SetIsInteractionEnabled(bReady); + } + if (Spinner_Connect) + { + Spinner_Connect->SetVisibility( + bReady ? ESlateVisibility::Collapsed : ESlateVisibility::HitTestInvisible); + } + if (Text_Status) + { + Text_Status->SetText(bReady ? ReadyText : ConnectingText); + } +} + +UZeusCharServerSubsystem* UUIBootScreen_Base::GetCharServer() const +{ + const UGameInstance* GI = GetGameInstance(); + return GI ? GI->GetSubsystem() : nullptr; +} + +void UUIBootScreen_Base::HandleStartClicked() +{ + UZeusCharServerSubsystem* Char = GetCharServer(); + if (!Char || !Char->IsConnected()) + { + return; // botão só age quando conectado (defensivo) + } + if (const UGameInstance* GI = GetGameInstance()) + { + if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem()) + { + Flow->RequestEnterLogin(); + } + } +} + +void UUIBootScreen_Base::HandleCharConnected() +{ + UE_LOG(LogZMMO, Log, TEXT("BootScreen: CharServer conectado — botão liberado.")); + SetReady(true); +} + +void UUIBootScreen_Base::HandleCharConnectionFailed(FString Reason) +{ + UE_LOG(LogZMMO, Warning, TEXT("BootScreen: falha ao conectar ao CharServer: %s"), *Reason); + SetReady(false); + if (Text_Status) + { + Text_Status->SetText(FailedText); + } +} diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIBootScreen_Base.h b/Source/ZMMO/Game/UI/FrontEnd/UIBootScreen_Base.h new file mode 100644 index 0000000..de1cbc1 --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/UIBootScreen_Base.h @@ -0,0 +1,73 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UIActivatableScreen_Base.h" +#include "UIBootScreen_Base.generated.h" + +class UUIButton_Base; +class UUISpinner_Base; +class UCommonTextBlock; +class UBorder; +class UZeusCharServerSubsystem; + +/** + * Tela de Boot — primeira tela do front-end. Reconstruída a partir do mock + * do Zeus UMG Forge com os widgets compartilhados (UI_Button_Master, + * UI_Spinner_Master). Camada Abstract; o WBP concreto (WBP_Boot) herda + * DIRETO desta classe (ARQUITETURA.md §3.3). + * + * O UUIFrontEndFlowSubsystem dispara a conexão ao ZeusCharServer (WebSocket) + * ao entrar no estado Boot. Esta tela OBSERVA o UZeusCharServerSubsystem: + * enquanto não conecta, mostra o spinner e o botão desabilitado; ao conectar, + * libera o botão "Iniciar" (clique → Flow::RequestEnterLogin → Login). + * Sem hard-ref de asset de tema (§5): cores vêm do FUIStyle. + */ +UCLASS(Abstract, Blueprintable) +class ZMMO_API UUIBootScreen_Base : public UUIActivatableScreen_Base +{ + GENERATED_BODY() + +protected: + virtual void NativePreConstruct() override; + virtual void NativeOnActivated() override; + virtual void NativeOnDeactivated() override; + virtual void RefreshUIStyle_Implementation() override; + + /** Texto exibido enquanto conecta / quando pronto / em erro. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Boot") + FText ConnectingText = FText::FromString(TEXT("Conectando ao Gateway")); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Boot") + FText ReadyText = FText::FromString(TEXT("Pronto")); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Boot") + FText FailedText = FText::FromString(TEXT("Falha ao conectar — tentando novamente")); + + // ---- Widgets do WBP (nomes esperados) ---- + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Btn_Start; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Spinner_Connect; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Text_Status; + + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Border_Bg; + +private: + void SetReady(bool bReady); + UZeusCharServerSubsystem* GetCharServer() const; + + UFUNCTION() + void HandleStartClicked(); + + UFUNCTION() + void HandleCharConnected(); + + UFUNCTION() + void HandleCharConnectionFailed(FString Reason); + + bool bCharBound = false; +}; diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp index 0fb443a..9a0f13a 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp +++ b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp @@ -8,6 +8,7 @@ #include "ZMMOGameInstance.h" #include "ZMMOThemeSubsystem.h" #include "ZeusNetworkSubsystem.h" +#include "ZeusCharServerSubsystem.h" #include "CommonActivatableWidget.h" #include "Engine/GameInstance.h" #include "Engine/LocalPlayer.h" @@ -54,22 +55,15 @@ void UUIFrontEndFlowSubsystem::StartFrontEnd() SetState(EZMMOFrontEndState::Boot); - // Substitui o auto-connect do GameInstance: o fluxo dirige a conexão. - if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork()) + // Pré-login fala com o ZeusCharServer (WebSocket), NÃO com o world server + // UDP. A tela Boot observa o CharServer e libera o botão ao conectar. + if (UZeusCharServerSubsystem* Char = GetCharServer()) { - 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); + Char->ConnectToDefaultCharServer(); // lê CharServerUrl de Project Settings } else { - UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: ZeusNetworkSubsystem indisponível; sem conexão.")); + UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: ZeusCharServerSubsystem indisponível; sem conexão de Boot.")); } } @@ -221,6 +215,12 @@ UZeusNetworkSubsystem* UUIFrontEndFlowSubsystem::GetZeusNetwork() const return GI ? GI->GetSubsystem() : nullptr; } +UZeusCharServerSubsystem* UUIFrontEndFlowSubsystem::GetCharServer() const +{ + const UGameInstance* GI = GetGameInstance(); + return GI ? GI->GetSubsystem() : nullptr; +} + UUIManagerSubsystem* UUIFrontEndFlowSubsystem::GetUIManager() const { if (const UGameInstance* GI = GetGameInstance()) @@ -257,14 +257,19 @@ void UUIFrontEndFlowSubsystem::BindNetwork() { return; } + // CharServer (WebSocket) dirige o pré-login: Boot→Login. + if (UZeusCharServerSubsystem* Char = GetCharServer()) + { + Char->OnConnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnected); + Char->OnConnectionFailed.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed); + Char->OnDisconnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleCharDisconnected); + } + // UDP (world server) só interessa para o handoff de travel. 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; } + bNetBound = true; } void UUIFrontEndFlowSubsystem::UnbindNetwork() @@ -273,11 +278,14 @@ void UUIFrontEndFlowSubsystem::UnbindNetwork() { return; } + if (UZeusCharServerSubsystem* Char = GetCharServer()) + { + Char->OnConnected.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnected); + Char->OnConnectionFailed.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed); + Char->OnDisconnected.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleCharDisconnected); + } 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; @@ -285,20 +293,38 @@ void UUIFrontEndFlowSubsystem::UnbindNetwork() void UUIFrontEndFlowSubsystem::HandleConnected() { - UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: conectado → Login.")); - SetState(EZMMOFrontEndState::Login); + // NÃO auto-avança: a tela Boot libera o botão "Iniciar"; o usuário clica + // (→ RequestEnterLogin). Mantém o estado Boot. + UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: CharServer conectado (estado %s)."), + *UEnum::GetValueAsString(CurrentState)); } void UUIFrontEndFlowSubsystem::HandleConnectionFailed(FString Reason) { - UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: conexão falhou (%s) → volta a Login."), *Reason); - SetState(EZMMOFrontEndState::Login); + // Permanece em Boot; a tela mostra erro e o CharServer pode re-tentar. + UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: conexão ao CharServer falhou (%s)."), *Reason); } -void UUIFrontEndFlowSubsystem::HandleDisconnected() +void UUIFrontEndFlowSubsystem::HandleCharDisconnected(int32 StatusCode, FString Reason) { - UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: desconectado.")); - if (CurrentState != EZMMOFrontEndState::InWorld) + UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: CharServer desconectou (code=%d, %s)."), + StatusCode, *Reason); + // Se caiu antes de entrar no mundo, volta a Boot para reconectar. + if (CurrentState != EZMMOFrontEndState::InWorld && + CurrentState != EZMMOFrontEndState::EnteringWorld && + CurrentState != EZMMOFrontEndState::Boot) + { + SetState(EZMMOFrontEndState::Boot); + if (UZeusCharServerSubsystem* Char = GetCharServer()) + { + Char->ConnectToDefaultCharServer(); + } + } +} + +void UUIFrontEndFlowSubsystem::RequestEnterLogin() +{ + if (CurrentState == EZMMOFrontEndState::Boot) { SetState(EZMMOFrontEndState::Login); } diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h index 33d00ff..7224f5d 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h +++ b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h @@ -8,6 +8,7 @@ class UUIFrontEndScreenSet; class UUIManagerSubsystem; class UZeusNetworkSubsystem; +class UZeusCharServerSubsystem; DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnZMMOFrontEndStateChanged, EZMMOFrontEndState, NewState); @@ -57,6 +58,10 @@ public: UFUNCTION(BlueprintCallable, Category = "FrontEnd") bool RequestBack(); + /** Chamado pela tela Boot quando o usuário clica "Iniciar" (já conectado). */ + UFUNCTION(BlueprintCallable, Category = "FrontEnd") + void RequestEnterLogin(); + UPROPERTY(BlueprintAssignable, Category = "FrontEnd") FOnZMMOFrontEndStateChanged OnStateChanged; @@ -69,6 +74,7 @@ private: void BindNetwork(); void UnbindNetwork(); UZeusNetworkSubsystem* GetZeusNetwork() const; + UZeusCharServerSubsystem* GetCharServer() const; UUIManagerSubsystem* GetUIManager() const; UUIFrontEndScreenSet* GetScreenSet(); @@ -82,7 +88,7 @@ private: void HandleConnectionFailed(FString Reason); UFUNCTION() - void HandleDisconnected(); + void HandleCharDisconnected(int32 StatusCode, FString Reason); UFUNCTION() void HandleServerTravelRequested(const FString& MapName, const FString& MapPath); diff --git a/Source/ZMMO/Game/UI/Widgets/UISpinner_Base.cpp b/Source/ZMMO/Game/UI/Widgets/UISpinner_Base.cpp new file mode 100644 index 0000000..ed93bb7 --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UISpinner_Base.cpp @@ -0,0 +1,104 @@ +#include "UISpinner_Base.h" + +#include "ZMMOThemeSubsystem.h" +#include "Engine/GameInstance.h" +#include "Engine/DataTable.h" +#include "Components/CircularThrobber.h" +#include "UI/UIStyleRow.h" + +namespace +{ + // Mesmo padrão de UUIPanel_Base: runtime usa o tema ativo; design-time + // carrega a row "Default" de DT_UI_Styles para o Designer ver igual. + const FUIStyle& ResolveSpinnerStyle(const UUserWidget* Widget, const FUIStyle& Fallback) + { + if (Widget) + { + if (const UGameInstance* GI = Widget->GetGameInstance()) + { + if (const UZMMOThemeSubsystem* Theme = GI->GetSubsystem()) + { + return Theme->GetActiveUIStyle(); + } + } + } + static FUIStyle DesignStyle; + static bool bDesignLoaded = false; + if (!bDesignLoaded) + { + if (const UDataTable* DT = LoadObject(nullptr, + TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles"))) + { + if (const FUIStyleRow* Row = DT->FindRow( + FName(TEXT("Default")), TEXT("UISpinnerDesign"), false)) + { + DesignStyle = Row->Style; + bDesignLoaded = true; + } + } + } + return bDesignLoaded ? DesignStyle : Fallback; + } +} + +void UUISpinner_Base::RefreshUIStyle() +{ + const FUIStyle Fallback; + const FUIStyle& AS = ResolveSpinnerStyle(this, Fallback); + const FUIStyleSpinner& S = AS.Spinner; + + if (Throbber) + { + Throbber->SetNumberOfPieces(FMath::Max(1, S.NumberOfPieces)); + Throbber->SetPeriod(FMath::Max(0.05f, S.Period)); + Throbber->SetRadius(S.Radius); + } + + // Cor/brush: o WBP aplica no throbber (mesma divisão de UUIPanel_Base). + BP_ApplySpinnerStyle(S); +} + +void UUISpinner_Base::NativePreConstruct() +{ + Super::NativePreConstruct(); + RefreshUIStyle(); +} + +void UUISpinner_Base::NativeConstruct() +{ + Super::NativeConstruct(); + + if (!bThemeBound) + { + if (const UGameInstance* GI = GetGameInstance()) + { + if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem()) + { + Theme->OnThemeChanged.AddDynamic(this, &UUISpinner_Base::HandleThemeChanged); + bThemeBound = true; + } + } + } + RefreshUIStyle(); +} + +void UUISpinner_Base::NativeDestruct() +{ + if (bThemeBound) + { + if (const UGameInstance* GI = GetGameInstance()) + { + if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem()) + { + Theme->OnThemeChanged.RemoveDynamic(this, &UUISpinner_Base::HandleThemeChanged); + } + } + bThemeBound = false; + } + Super::NativeDestruct(); +} + +void UUISpinner_Base::HandleThemeChanged(FName /*NewThemeId*/) +{ + RefreshUIStyle(); +} diff --git a/Source/ZMMO/Game/UI/Widgets/UISpinner_Base.h b/Source/ZMMO/Game/UI/Widgets/UISpinner_Base.h new file mode 100644 index 0000000..f88a787 --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UISpinner_Base.h @@ -0,0 +1,49 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CommonUserWidget.h" +#include "UI/UIStyleTokens.h" +#include "UISpinner_Base.generated.h" + +class UCircularThrobber; + +/** + * Spinner/throbber indeterminado do ZMMO — mesmo padrão de UUIPanel_Base. + * Fundação CommonUI (UCommonUserWidget). Camada Abstract; o WBP concreto + * (UI_Spinner_Master) herda DIRETO desta classe C++ (UMG não encadeia + * árvores — ARQUITETURA.md §3.3). + * + * Visual data-driven por FUIStyle.Spinner (UZMMOThemeSubsystem). C++ aplica + * NumberOfPieces/Period/Radius no UCircularThrobber; cor/brush vão pelo hook + * BP_ApplySpinnerStyle (o WBP tinge a imagem do throbber). + */ +UCLASS(Abstract, Blueprintable) +class ZMMO_API UUISpinner_Base : public UCommonUserWidget +{ + GENERATED_BODY() + +public: + /** Re-resolve os tokens do tema ativo e reaplica no throbber. */ + UFUNCTION(BlueprintCallable, Category = "UI Style") + void RefreshUIStyle(); + +protected: + virtual void NativePreConstruct() override; + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + + /** Hook opcional: o WBP aplica cor/brush do spinner (FUIStyleSpinner). */ + UFUNCTION(BlueprintImplementableEvent, Category = "UI Style", + meta = (DisplayName = "Apply Spinner Style")) + void BP_ApplySpinnerStyle(const FUIStyleSpinner& Spinner); + + /** Throbber circular (nome esperado no WBP: "Throbber"). */ + UPROPERTY(meta = (BindWidgetOptional)) + TObjectPtr Throbber; + +private: + UFUNCTION() + void HandleThemeChanged(FName NewThemeId); + + bool bThemeBound = false; +};