Files
ZMMO/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h
Mateus Rodrigues 77d52a703b ZMMO: CHAR_INFO via V1 (Fase A) + fix loading travado
- ZeusWorldSubsystem consome ENT_CHAR_INFO (delegate OnCharInfo): aplica
  nome/guild no PlayerState do entityId certo (self ou proxy), com cache porque
  o CHAR_INFO chega antes do ator existir (server emite antes do ENT_SPAWN).
- FIX loading travado: a etapa "Spawn" do loading dependia do legacy
  OnPlayerSpawned, que não dispara mais com V1 -> loading eterno. Agora a etapa
  é marcada pelo sinal V1 OnSelfEntityAssigned (ENT_SELF); bind legacy removido.
  Travel ainda passa por OnServerTravelRequested (ponte legacy) até a Fase D.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 13:06:42 -03:00

276 lines
9.7 KiB
C++

#pragma once
#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "UI/FrontEndTypes.h"
#include "UIFrontEndFlowSubsystem.generated.h"
class UUIFrontEndScreenSet;
class UUIManagerSubsystem;
class UUILoadingScreen_Base;
class UZeusLoadingProfilesDataAsset;
class UZeusNetworkSubsystem;
class UZeusNetworkingClientSubsystem;
class UZeusCharServerSubsystem;
struct FZeusMapDef;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnZeusFrontEndStateChanged, EZeusFrontEndState, 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 EZeusFrontEndState; (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 AZeusFrontEndPlayerController::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();
/**
* Garante que o root layout (UUIPrimaryGameLayout_Base) esta no viewport.
* Chamado pelo UIInGameFlowSubsystem::StartInGame apos OpenLevel — o
* `OpenLevel` invalida widgets no viewport ("InvalidateAllWidgets") e o
* RootLayout fica orfao. Idempotente: CreateAndAddRootLayout do
* UIManagerSubsystem checa `IsInViewport` antes de recriar.
*
* C++ puro (sem UFUNCTION) — Live Coding nao registra UFUNCTIONs novas;
* consumidores externos chamam direto via C++ (UIInGameFlowSubsystem).
*/
void EnsureRootLayout();
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
void SetState(EZeusFrontEndState NewState);
UFUNCTION(BlueprintPure, Category = "FrontEnd")
EZeusFrontEndState GetCurrentState() const { return CurrentState; }
/** Mostra uma página interna do Lobby ("switch principal"). */
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
void ShowLobbyPage(EZeusLobbyPage Page);
/** "Voltar". Retorna true se o fluxo consumiu (tela base encaminha aqui). */
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
bool RequestBack();
/** Chamado pela tela Boot quando o usuário clica "Iniciar" (já conectado). */
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
void RequestEnterLogin();
/** Chamado pela tela Login após autenticação OK no CharServer. */
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
void RequestEnterServerSelect();
/**
* Memoriza o Realm escolhido na ServerSelect (UUID v4 string). Lido pelo
* CharSelect/CharCreate na hora de filtrar/criar personagens. Limpo no
* logout/back para ServerSelect.
*/
UFUNCTION(BlueprintCallable, Category = "FrontEnd|Realms")
void SetSelectedRealmId(const FString& RealmId);
UFUNCTION(BlueprintPure, Category = "FrontEnd|Realms")
FString GetSelectedRealmId() const { return SelectedRealmId; }
UFUNCTION(BlueprintCallable, Category = "FrontEnd|Realms")
void ClearSelectedRealm();
/**
* Resolve `MapId` (uint16 recebido do CharServer) → row do DT_Maps.
* Carrega o asset sync (PIE/dev OK; em prod considerar StreamableManager).
* Retorna nullptr se MapId=0, DT_Maps nao configurado, ou mapId nao
* encontrado.
*/
const FZeusMapDef* FindMapDef(int32 MapId) const;
/**
* Helper: resolve `MapId` no DT_Maps e retorna `ClientLevel.ToSoftObjectPath().GetLongPackageName()`
* (ex.: `/Game/ThirdPerson/TestWorld`). String vazia se nao encontrado.
*/
UFUNCTION(BlueprintPure, Category = "FrontEnd|Maps")
FString ResolveLevelPathByMapId(int32 MapId) const;
/**
* Inicia transicao pra mundo dado um `MapId`: faz `OpenLevel` com
* `?game=/Script/ZMMO.ZeusGameMode`. No-op se `MapId` invalido ou
* DT_Maps nao tem entrada.
*/
UFUNCTION(BlueprintCallable, Category = "FrontEnd|Maps")
bool TravelToMapById(int32 MapId);
/**
* Memoriza a pose autoritativa do char vinda no `S_CHAR_SELECT_OK`
* (pos salva no DB + yaw). Sobrevive ao travel porque vivemos no
* GameInstance. Lida pelo `AZeusCharacter::BeginPlay` pra
* reposicionar o pawn local em vez de spawnar no PlayerStart default
* do level. Limpa apos consumo (a proxima sessao reenvia).
*/
UFUNCTION(BlueprintCallable, Category = "FrontEnd|Spawn")
void SetPendingSpawnPose(FVector PosCm, float YawDeg);
UFUNCTION(BlueprintCallable, Category = "FrontEnd|Spawn")
bool ConsumePendingSpawnPose(FVector& OutPosCm, float& OutYawDeg);
UPROPERTY(BlueprintAssignable, Category = "FrontEnd")
FOnZeusFrontEndStateChanged OnStateChanged;
protected:
/** DA com o mapa estado→tela. Config em DefaultGame.ini. */
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd")
TSoftObjectPtr<UUIFrontEndScreenSet> ScreenSetAsset;
/**
* DataTable com FZeusMapDef. Source of truth client-side para
* `MapId -> ClientLevel/spawns/displayName`. Configure em DefaultGame.ini:
* [/Script/ZMMO.UIFrontEndFlowSubsystem]
* MapsTableAsset=/Game/ZMMO/Data/World/DT_Maps.DT_Maps
*/
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Maps")
TSoftObjectPtr<UDataTable> MapsTableAsset;
/**
* DataAsset com os perfis de loading (etapas por contexto). Configure em
* DefaultGame.ini:
* [/Script/ZMMO.UIFrontEndFlowSubsystem]
* LoadingProfilesAsset=/Game/ZMMO/Data/UI/Loading/DA_LoadingProfiles.DA_LoadingProfiles
*/
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Loading")
TSoftObjectPtr<UZeusLoadingProfilesDataAsset> LoadingProfilesAsset;
/**
* DataTable com dicas (rows = FZeusLoadingTipRow). Opcional — vazio
* significa "sem dicas". Configure em DefaultGame.ini:
* [/Script/ZMMO.UIFrontEndFlowSubsystem]
* LoadingTipsAsset=/Game/ZMMO/Data/UI/Loading/DT_LoadingTips.DT_LoadingTips
*/
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Loading")
TSoftObjectPtr<UDataTable> LoadingTipsAsset;
private:
void BindNetwork();
void UnbindNetwork();
UZeusNetworkSubsystem* GetZeusNetwork() const;
UZeusNetworkingClientSubsystem* GetNetClient() const; // V1 canonico
UZeusCharServerSubsystem* GetCharServer() const;
UUIManagerSubsystem* GetUIManager() const;
UUIFrontEndScreenSet* GetScreenSet();
/** Resolve a soft class do estado e empurra na camada Menu (no-op+log se vazio). */
void ResolveAndPushScreen(EZeusFrontEndState State);
UFUNCTION()
void HandleConnected();
UFUNCTION()
void HandleConnectionFailed(FString Reason);
UFUNCTION()
void HandleCharDisconnected(int32 StatusCode, FString Reason);
UFUNCTION()
void HandleServerTravelRequested(const FString& MapName, const FString& MapPath);
/**
* Spawn do player local — fim da última etapa do loading EnteringWorld.
* Assinatura espelha FZeusOnPlayerSpawned (FiveParams).
*/
UFUNCTION()
void HandlePlayerSpawned(int64 EntityId, bool bIsLocal, FVector PosCm,
float YawDeg, int64 ServerTimeMs);
/**
* V1 canonico: o spawn do player local chega via ENT_SELF (o cliente
* aprende o proprio entityId) -> OnSelfEntityAssigned. Marca a etapa
* "Spawn" do loading no fluxo V1 (o legacy OnPlayerSpawned nao dispara
* mais). Adapta a assinatura OneParam pra logica de HandlePlayerSpawned.
*/
UFUNCTION()
void HandleV1LocalSpawned(int64 EntityId);
/** Disparado pela tela de loading quando todas as etapas viram Done. */
UFUNCTION()
void HandleLoadingComplete();
void HandlePostLoadMap(UWorld* LoadedWorld);
/** Resolve+carrega o DA de perfis (sync). */
UZeusLoadingProfilesDataAsset* GetLoadingProfiles();
/** Resolve+carrega o DT de dicas (sync). */
UDataTable* GetLoadingTips();
/**
* Dívida técnica (D5): ServerHello traz ThemeId, mas o UZeusNetworkSubsystem
* ainda não expõe um delegate/getter dedicado. Quando expuser, ligar aqui
* → UZeusThemeSubsystem::ApplyServerTheme. Sem isto, o tema resolve por
* calendário/Default (ARQUITETURA.md §1.10).
*/
void HandleServerHelloTheme(FName ThemeId);
UPROPERTY(Transient)
EZeusFrontEndState CurrentState = EZeusFrontEndState::None;
UPROPERTY(Transient)
FString SelectedRealmId;
/** Pose autoritativa pendente (do S_CHAR_SELECT_OK). */
UPROPERTY(Transient)
FVector PendingSpawnPosCm = FVector::ZeroVector;
UPROPERTY(Transient)
float PendingSpawnYawDeg = 0.f;
UPROPERTY(Transient)
bool bHasPendingSpawnPose = false;
UPROPERTY(Transient)
TObjectPtr<UUIFrontEndScreenSet> ScreenSet;
UPROPERTY(Transient)
TObjectPtr<UZeusLoadingProfilesDataAsset> LoadingProfiles;
UPROPERTY(Transient)
TObjectPtr<UDataTable> LoadingTips;
/**
* Tela de loading atualmente em Modal. Weak porque o stack do CommonUI
* é dono — se popar por outro caminho não queremos dangling.
*/
TWeakObjectPtr<UUILoadingScreen_Base> ActiveLoadingScreen;
/**
* Race fix: o widget de loading é criado via RequestAsyncLoad (assíncrono).
* Se o servidor for rápido (TryFinalizeEnterWorld), HandlePostLoadMap e/ou
* HandlePlayerSpawned podem disparar ANTES de ActiveLoadingScreen existir
* — nesse caso o MarkStepDone vira no-op e o loading fica eterno.
* Memoizamos os StepIds aqui e drenamos quando a tela for criada.
*/
TSet<FName> PendingLoadingSteps_;
bool bNetBound = false;
bool bTravelingToWorld = false;
FDelegateHandle PostLoadMapHandle;
};