- 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>
276 lines
9.7 KiB
C++
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;
|
|
};
|