feat(ui/loading): tela de loading dinamica data-driven (etapas + dicas)

- UUILoadingScreen_Base (Game/UI/Common/) generica: Configure(Context),
  MarkStepRunning/Done/Failed, OnLoadingComplete delegate, progress bar
  reativa e rotacao automatica de dicas via timer.
- Etapas configuradas por contexto em UZMMOLoadingProfilesDataAsset
  (DA_LoadingProfiles). Perfil inicial FrontEndEnteringWorld:
  Travel -> MapLoaded -> Spawn.
- Dicas em DT_LoadingTips (FText localizavel + filtro por contexto +
  GameplayTag de categoria). 4 dicas seed.
- UIFrontEndFlowSubsystem agora bloqueia a transicao InWorld ate o
  HandlePlayerSpawned (bIsLocal=true) — usuario nao ve mundo vazio.
- WBP_Loading em /UI/Shared/Loading/ (neutro, reusavel pelo InGame futuro);
  DA_FrontEndScreenSet[EnteringWorld] aponta pra ele.
- DefaultGame.ini wire LoadingProfilesAsset + LoadingTipsAsset.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 02:24:48 -03:00
parent 90484876f0
commit 079d05c117
13 changed files with 627 additions and 5 deletions

View File

@@ -35,6 +35,12 @@ ScreenSetAsset=/Game/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.DA_FrontEndScreenSet
; cliente resolve `mapId -> ClientLevel/spawns` via FZMMOMapDef rows.
MapsTableAsset=/Game/ZMMO/Data/World/DT_Maps.DT_Maps
; Loading dinâmico (etapas + dicas). DA_LoadingProfiles tem 1 perfil por
; contexto (EZMMOLoadingContext); DT_LoadingTips rotaciona durante o load.
; Cliente local marca etapas via eventos (sem opcode novo).
LoadingProfilesAsset=/Game/ZMMO/Data/UI/Loading/DA_LoadingProfiles.DA_LoadingProfiles
LoadingTipsAsset=/Game/ZMMO/Data/UI/Loading/DT_LoadingTips.DT_LoadingTips
; -----------------------------------------------------------------------------
; UI in-game (PR 19+). Espelho do front-end pattern:
; - ScreenSetAsset mapeia EZMMOInGameUIState -> WBP (Playing -> WBP_HUD,

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,66 @@
#pragma once
#include "CoreMinimal.h"
#include "LoadingTypes.generated.h"
/**
* Contexto do loading screen genérico. Cada contexto resolve um perfil
* (lista de etapas) no UZMMOLoadingProfilesDataAsset.
*
* Cliente local rastreia o progresso assinando eventos próprios
* (HandlePostLoadMap, OnPlayerSpawned, etc) — server não envia opcode de
* progresso, ver decisão em [[project_ui_loading_dynamic]].
*/
UENUM(BlueprintType)
enum class EZMMOLoadingContext : uint8
{
None,
FrontEndEnteringWorld, ///< FrontEnd → mundo (handoff CharServer→WorldServer + spawn).
InGameEnteringInstance, ///< Mundo → dungeon/instance (futuro).
InGameRespawn, ///< Pós-morte, retorno ao mundo (futuro).
};
UENUM(BlueprintType)
enum class EZMMOLoadingStepStatus : uint8
{
Pending, ///< Ainda não começou.
Running, ///< Em andamento (o "barber pole").
Done, ///< Concluída.
Failed, ///< Falhou (com reason em Status).
};
/**
* Uma etapa do loading. StepId é o identificador estável usado por código
* pra chamar MarkStepDone(StepId); Label é o texto localizado mostrado.
*/
USTRUCT(BlueprintType)
struct FZMMOLoadingStepDef
{
GENERATED_BODY()
/** Id estável (ex.: "Travel", "MapLoaded", "Connect", "Spawn"). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
FName StepId;
/** Texto localizado ("Carregando mapa...", "Conectando..."). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
FText Label;
};
/**
* Perfil de loading por contexto. Designer edita no DA — código só
* consulta StepId pra avançar etapa.
*/
USTRUCT(BlueprintType)
struct FZMMOLoadingProfile
{
GENERATED_BODY()
/** Etapas em ordem. ProgressBar = #Done / #Steps. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
TArray<FZMMOLoadingStepDef> Steps;
/** Título da tela (vazio = usa default da classe). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
FText TitleOverride;
};

View File

@@ -0,0 +1,10 @@
#include "UILoadingProfilesDataAsset.h"
FZMMOLoadingProfile UZMMOLoadingProfilesDataAsset::GetProfile(EZMMOLoadingContext Context) const
{
if (const FZMMOLoadingProfile* Found = Profiles.Find(Context))
{
return *Found;
}
return FZMMOLoadingProfile();
}

View File

@@ -0,0 +1,29 @@
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "UI/LoadingTypes.h"
#include "UILoadingProfilesDataAsset.generated.h"
/**
* Mapa data-driven contexto → perfil (etapas + título). Designer edita o
* asset; código consulta por EZMMOLoadingContext na hora de configurar a
* tela. Asset canônico: DA_LoadingProfiles em /Game/ZMMO/Data/UI/Loading/.
*
* Configurado em DefaultGame.ini:
* [/Script/ZMMO.UIFrontEndFlowSubsystem]
* LoadingProfilesAsset=/Game/ZMMO/Data/UI/Loading/DA_LoadingProfiles.DA_LoadingProfiles
*/
UCLASS(BlueprintType)
class ZMMO_API UZMMOLoadingProfilesDataAsset : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Loading")
TMap<EZMMOLoadingContext, FZMMOLoadingProfile> Profiles;
/** Retorna o perfil do contexto ou um perfil vazio (sem etapas). */
UFUNCTION(BlueprintCallable, Category = "Loading")
FZMMOLoadingProfile GetProfile(EZMMOLoadingContext Context) const;
};

View File

@@ -0,0 +1,235 @@
#include "UILoadingScreen_Base.h"
#include "Components/ProgressBar.h"
#include "CommonTextBlock.h"
#include "Engine/DataTable.h"
#include "Engine/World.h"
#include "TimerManager.h"
#include "UILoadingProfilesDataAsset.h"
#include "UILoadingTipRow.h"
void UUILoadingScreen_Base::Configure(EZMMOLoadingContext InContext,
UZMMOLoadingProfilesDataAsset* Profiles,
UDataTable* TipsTable)
{
Context_ = InContext;
bCompleteFired_ = false;
// Perfil (etapas + título).
Profile_ = (Profiles != nullptr)
? Profiles->GetProfile(InContext)
: FZMMOLoadingProfile();
StepStatus_.Reset();
for (const FZMMOLoadingStepDef& Step : Profile_.Steps)
{
StepStatus_.Add(Step.StepId, EZMMOLoadingStepStatus::Pending);
}
// Pool de dicas filtrada por contexto (None = qualquer).
TipPool_.Reset();
if (TipsTable != nullptr)
{
TipsTable->ForeachRow<FZMMOLoadingTipRow>(TEXT("UUILoadingScreen_Base::Configure"),
[this](const FName& /*Key*/, const FZMMOLoadingTipRow& Row)
{
if (Row.Context == EZMMOLoadingContext::None || Row.Context == Context_)
{
if (!Row.Tip.IsEmpty())
{
TipPool_.Add(Row.Tip);
}
}
});
}
NextTipIndex_ = 0;
RefreshUI();
if (IsActivated())
{
StartTipTimer();
}
}
void UUILoadingScreen_Base::NativeOnActivated()
{
Super::NativeOnActivated();
RefreshUI();
StartTipTimer();
}
void UUILoadingScreen_Base::NativeOnDeactivated()
{
StopTipTimer();
Super::NativeOnDeactivated();
}
void UUILoadingScreen_Base::MarkStepRunning(FName StepId)
{
if (EZMMOLoadingStepStatus* Status = StepStatus_.Find(StepId))
{
// Já Done não regride.
if (*Status != EZMMOLoadingStepStatus::Done && *Status != EZMMOLoadingStepStatus::Failed)
{
*Status = EZMMOLoadingStepStatus::Running;
RefreshUI();
}
}
}
void UUILoadingScreen_Base::MarkStepDone(FName StepId)
{
if (EZMMOLoadingStepStatus* Status = StepStatus_.Find(StepId))
{
*Status = EZMMOLoadingStepStatus::Done;
RefreshUI();
if (!bCompleteFired_ && AreAllStepsDone())
{
bCompleteFired_ = true;
OnLoadingComplete.Broadcast();
}
}
}
void UUILoadingScreen_Base::MarkStepFailed(FName StepId, const FText& Reason)
{
if (EZMMOLoadingStepStatus* Status = StepStatus_.Find(StepId))
{
*Status = EZMMOLoadingStepStatus::Failed;
if (Text_Status && !Reason.IsEmpty())
{
Text_Status->SetText(Reason);
}
RefreshUI();
}
}
bool UUILoadingScreen_Base::AreAllStepsDone() const
{
if (Profile_.Steps.Num() == 0)
{
return false;
}
for (const FZMMOLoadingStepDef& Step : Profile_.Steps)
{
const EZMMOLoadingStepStatus* Status = StepStatus_.Find(Step.StepId);
if (Status == nullptr || *Status != EZMMOLoadingStepStatus::Done)
{
return false;
}
}
return true;
}
int32 UUILoadingScreen_Base::FindStepIndex(FName StepId) const
{
for (int32 i = 0; i < Profile_.Steps.Num(); ++i)
{
if (Profile_.Steps[i].StepId == StepId)
{
return i;
}
}
return INDEX_NONE;
}
EZMMOLoadingStepStatus UUILoadingScreen_Base::GetStepStatus(int32 Index) const
{
if (!Profile_.Steps.IsValidIndex(Index)) return EZMMOLoadingStepStatus::Pending;
const EZMMOLoadingStepStatus* Status = StepStatus_.Find(Profile_.Steps[Index].StepId);
return Status ? *Status : EZMMOLoadingStepStatus::Pending;
}
FText UUILoadingScreen_Base::ComputeCurrentStatusText() const
{
// Prioridade: Running > último Done > primeiro Pending > Status default.
int32 LastDone = INDEX_NONE;
for (int32 i = 0; i < Profile_.Steps.Num(); ++i)
{
const EZMMOLoadingStepStatus St = GetStepStatus(i);
if (St == EZMMOLoadingStepStatus::Running)
{
return Profile_.Steps[i].Label;
}
if (St == EZMMOLoadingStepStatus::Done)
{
LastDone = i;
}
}
if (LastDone != INDEX_NONE)
{
return Profile_.Steps[LastDone].Label;
}
if (Profile_.Steps.Num() > 0)
{
return Profile_.Steps[0].Label;
}
return StatusDefault;
}
float UUILoadingScreen_Base::ComputeProgress01() const
{
const int32 N = Profile_.Steps.Num();
if (N == 0) return 0.f;
float Acc = 0.f;
for (int32 i = 0; i < N; ++i)
{
const EZMMOLoadingStepStatus St = GetStepStatus(i);
if (St == EZMMOLoadingStepStatus::Done) Acc += 1.f;
else if (St == EZMMOLoadingStepStatus::Running) Acc += 0.5f;
}
return FMath::Clamp(Acc / static_cast<float>(N), 0.f, 1.f);
}
void UUILoadingScreen_Base::RefreshUI()
{
if (Text_Title)
{
const FText& Title = Profile_.TitleOverride.IsEmpty() ? TitleDefault : Profile_.TitleOverride;
Text_Title->SetText(Title);
}
if (Text_Status)
{
Text_Status->SetText(ComputeCurrentStatusText());
}
if (ProgressBar_Progress)
{
ProgressBar_Progress->SetPercent(ComputeProgress01());
}
if (Text_Tip && TipPool_.Num() > 0 && Text_Tip->GetText().IsEmpty())
{
// Primeira dica na inicialização (timer cuida das próximas).
Text_Tip->SetText(TipPool_[0]);
NextTipIndex_ = TipPool_.Num() > 1 ? 1 : 0;
}
}
void UUILoadingScreen_Base::RotateTip()
{
if (!Text_Tip || TipPool_.Num() == 0) return;
Text_Tip->SetText(TipPool_[NextTipIndex_]);
NextTipIndex_ = (NextTipIndex_ + 1) % TipPool_.Num();
}
void UUILoadingScreen_Base::StartTipTimer()
{
StopTipTimer();
if (TipRotationIntervalSeconds <= 0.f || TipPool_.Num() <= 1) return;
if (UWorld* W = GetWorld())
{
W->GetTimerManager().SetTimer(TipTimerHandle_, this,
&UUILoadingScreen_Base::RotateTip,
TipRotationIntervalSeconds, /*bLoop=*/true);
}
}
void UUILoadingScreen_Base::StopTipTimer()
{
if (UWorld* W = GetWorld())
{
W->GetTimerManager().ClearTimer(TipTimerHandle_);
}
TipTimerHandle_.Invalidate();
}

View File

@@ -0,0 +1,124 @@
#pragma once
#include "CoreMinimal.h"
#include "UIActivatableScreen_Base.h"
#include "UI/LoadingTypes.h"
#include "UILoadingScreen_Base.generated.h"
class UCommonTextBlock;
class UProgressBar;
class UDataTable;
class UZMMOLoadingProfilesDataAsset;
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FZMMOOnLoadingComplete);
/**
* Tela de loading genérica. Reusada pelo FrontEnd (handoff CharServer →
* WorldServer) e pelo InGame (entrada em instance/dungeon, respawn). Cada
* uso passa um EZMMOLoadingContext em Configure(), que resolve o perfil
* no UZMMOLoadingProfilesDataAsset (lista de etapas + título).
*
* Quem orquestra (UIFrontEndFlowSubsystem, futuro UIInGameFlowSubsystem)
* assina os próprios eventos (HandlePostLoadMap, OnPlayerSpawned, etc) e
* chama MarkStepDone(StepId) — server NÃO envia opcode de progresso (ver
* [[project_ui_loading_dynamic]]).
*
* Dismiss: a tela emite OnLoadingComplete quando todas as etapas estão
* Done. Caller decide quando popar (geralmente após pequeno fade-out).
*
* WBP concreto herda DIRETO desta classe. Subclasses C++ específicas só
* surgem quando houver necessidade real de estilo divergente.
*/
UCLASS(Abstract, Blueprintable)
class ZMMO_API UUILoadingScreen_Base : public UUIActivatableScreen_Base
{
GENERATED_BODY()
public:
/**
* Configura a tela: carrega o perfil do contexto, popula a pool de
* dicas filtrada e reseta estado das etapas. Pode ser chamado depois
* de NativeOnActivated (estado é reaplicado em RefreshUI).
*/
UFUNCTION(BlueprintCallable, Category = "Loading")
void Configure(EZMMOLoadingContext InContext,
UZMMOLoadingProfilesDataAsset* Profiles,
UDataTable* TipsTable);
UFUNCTION(BlueprintCallable, Category = "Loading")
void MarkStepRunning(FName StepId);
UFUNCTION(BlueprintCallable, Category = "Loading")
void MarkStepDone(FName StepId);
UFUNCTION(BlueprintCallable, Category = "Loading")
void MarkStepFailed(FName StepId, const FText& Reason);
UFUNCTION(BlueprintPure, Category = "Loading")
bool AreAllStepsDone() const;
/** Disparado UMA vez quando todas as etapas viram Done. */
UPROPERTY(BlueprintAssignable, Category = "Loading")
FZMMOOnLoadingComplete OnLoadingComplete;
protected:
virtual void NativeOnActivated() override;
virtual void NativeOnDeactivated() override;
/** Reaplica todo o estado aos widgets bindados (texto, progress, tip). */
void RefreshUI();
void RotateTip();
void StartTipTimer();
void StopTipTimer();
/** Acha índice da etapa por StepId; -1 se não existir. */
int32 FindStepIndex(FName StepId) const;
/** Status atual da etapa em [Steps_]; Pending se não rastreada. */
EZMMOLoadingStepStatus GetStepStatus(int32 Index) const;
/** Texto exibido em Text_Status — etapa Running atual ou última Done. */
FText ComputeCurrentStatusText() const;
/** Progresso 0..1: #Done / max(1,#Steps). Running conta como meio passo. */
float ComputeProgress01() const;
// ---- Bind widgets (BindWidgetOptional — WBP pode omitir qualquer) ----
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UCommonTextBlock> Text_Title;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UCommonTextBlock> Text_Status;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UProgressBar> ProgressBar_Progress;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UCommonTextBlock> Text_Tip;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
FText TitleDefault = FText::FromString(TEXT("Carregando"));
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
FText StatusDefault = FText::FromString(TEXT("Entrando no mundo..."));
/** Intervalo entre dicas. 0 = sem rotação (mostra a primeira dica fixa). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
float TipRotationIntervalSeconds = 6.f;
private:
EZMMOLoadingContext Context_ = EZMMOLoadingContext::None;
UPROPERTY(Transient)
FZMMOLoadingProfile Profile_;
UPROPERTY(Transient)
TMap<FName, EZMMOLoadingStepStatus> StepStatus_;
UPROPERTY(Transient)
TArray<FText> TipPool_;
int32 NextTipIndex_ = 0;
bool bCompleteFired_ = false;
FTimerHandle TipTimerHandle_;
};

View File

@@ -0,0 +1,30 @@
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "GameplayTagContainer.h"
#include "UI/LoadingTypes.h"
#include "UILoadingTipRow.generated.h"
/**
* Row do DT_LoadingTips. Cada row = uma dica mostrada (rotaciona durante o
* loading). FText é localizável (basta extrair pelo gather de tradução do
* UE). Filtra por contexto: Context=None significa "qualquer loading".
*/
USTRUCT(BlueprintType)
struct FZMMOLoadingTipRow : public FTableRowBase
{
GENERATED_BODY()
/** Texto da dica (localizável). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
FText Tip;
/** Restringe a dica a um contexto. None = todos. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
EZMMOLoadingContext Context = EZMMOLoadingContext::None;
/** Tag opcional pra categorizar (PvE/PvP/Crafting...) — futuro filtro. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
FGameplayTag Category;
};

View File

@@ -5,6 +5,8 @@
#include "UIFrontEndScreenSet.h"
#include "UIManagerSubsystem.h"
#include "UIPrimaryGameLayout_Base.h"
#include "UI/Common/UILoadingProfilesDataAsset.h"
#include "UI/Common/UILoadingScreen_Base.h"
#include "UI/UILayerTags.h"
#include "ZMMOGameInstance.h"
#include "ZMMOThemeSubsystem.h"
@@ -212,13 +214,27 @@ void UUIFrontEndFlowSubsystem::ResolveAndPushScreen(EZMMOFrontEndState State)
const FSoftObjectPath Path = Soft.ToSoftObjectPath();
UAssetManager::GetStreamableManager().RequestAsyncLoad(
Path,
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer]()
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer, State]()
{
if (UUIManagerSubsystem* M = GetUIManager())
{
if (UClass* Cls = Soft.Get())
{
M->PushScreenToLayer(Layer, Cls);
UCommonActivatableWidget* Pushed = M->PushScreenToLayer(Layer, Cls);
// Loading: configura logo após o push e marca etapa "Travel"
// (chegamos aqui via HandleServerTravelRequested → SetState).
if (State == EZMMOFrontEndState::EnteringWorld)
{
if (UUILoadingScreen_Base* Loading = Cast<UUILoadingScreen_Base>(Pushed))
{
ActiveLoadingScreen = Loading;
Loading->Configure(EZMMOLoadingContext::FrontEndEnteringWorld,
GetLoadingProfiles(), GetLoadingTips());
Loading->OnLoadingComplete.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleLoadingComplete);
Loading->MarkStepDone(TEXT("Travel"));
}
}
}
}
}));
@@ -283,6 +299,7 @@ void UUIFrontEndFlowSubsystem::BindNetwork()
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
{
Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
Zeus->OnPlayerSpawned.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned);
}
bNetBound = true;
}
@@ -302,6 +319,7 @@ void UUIFrontEndFlowSubsystem::UnbindNetwork()
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
{
Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
Zeus->OnPlayerSpawned.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned);
}
bNetBound = false;
}
@@ -390,11 +408,65 @@ void UUIFrontEndFlowSubsystem::HandleServerTravelRequested(const FString& MapNam
void UUIFrontEndFlowSubsystem::HandlePostLoadMap(UWorld* /*LoadedWorld*/)
{
if (bTravelingToWorld)
if (!bTravelingToWorld)
{
bTravelingToWorld = false;
SetState(EZMMOFrontEndState::InWorld);
return;
}
// Loading dinâmico: marca a etapa "MapLoaded" e espera o spawn antes de
// liberar o InWorld (a tela limpa via OnLoadingComplete). Fallback: se
// não há screen ativa (DA não configurado), comportamento legado —
// transita já pra InWorld pra não travar.
if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
{
Loading->MarkStepDone(TEXT("MapLoaded"));
return;
}
bTravelingToWorld = false;
SetState(EZMMOFrontEndState::InWorld);
}
void UUIFrontEndFlowSubsystem::HandlePlayerSpawned(int32 /*EntityId*/, bool bIsLocal,
FVector /*PosCm*/, float /*YawDeg*/,
int64 /*ServerTimeMs*/)
{
if (!bIsLocal) return;
if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
{
Loading->MarkStepDone(TEXT("Spawn"));
}
}
void UUIFrontEndFlowSubsystem::HandleLoadingComplete()
{
if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
{
Loading->OnLoadingComplete.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleLoadingComplete);
}
ActiveLoadingScreen.Reset();
bTravelingToWorld = false;
SetState(EZMMOFrontEndState::InWorld);
}
UZMMOLoadingProfilesDataAsset* UUIFrontEndFlowSubsystem::GetLoadingProfiles()
{
if (LoadingProfiles) return LoadingProfiles;
if (!LoadingProfilesAsset.IsNull())
{
LoadingProfiles = LoadingProfilesAsset.LoadSynchronous();
}
return LoadingProfiles;
}
UDataTable* UUIFrontEndFlowSubsystem::GetLoadingTips()
{
if (LoadingTips) return LoadingTips;
if (!LoadingTipsAsset.IsNull())
{
LoadingTips = LoadingTipsAsset.LoadSynchronous();
}
return LoadingTips;
}
void UUIFrontEndFlowSubsystem::HandleServerHelloTheme(FName ThemeId)

View File

@@ -8,6 +8,8 @@
class UUIFrontEndScreenSet;
class UUIManagerSubsystem;
class UUILoadingScreen_Base;
class UZMMOLoadingProfilesDataAsset;
class UZeusNetworkSubsystem;
class UZeusCharServerSubsystem;
struct FZMMOMapDef;
@@ -147,6 +149,24 @@ protected:
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<UZMMOLoadingProfilesDataAsset> LoadingProfilesAsset;
/**
* DataTable com dicas (rows = FZMMOLoadingTipRow). 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();
@@ -170,8 +190,26 @@ private:
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(int32 EntityId, bool bIsLocal, FVector PosCm,
float YawDeg, int64 ServerTimeMs);
/** 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). */
UZMMOLoadingProfilesDataAsset* 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
@@ -199,6 +237,18 @@ private:
UPROPERTY(Transient)
TObjectPtr<UUIFrontEndScreenSet> ScreenSet;
UPROPERTY(Transient)
TObjectPtr<UZMMOLoadingProfilesDataAsset> 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;
bool bNetBound = false;
bool bTravelingToWorld = false;
FDelegateHandle PostLoadMapHandle;