diff --git a/Source/ZMMO/Game/Network/ZeusWorldSubsystem.cpp b/Source/ZMMO/Game/Network/ZeusWorldSubsystem.cpp index 205b8ec..2ca28c1 100644 --- a/Source/ZMMO/Game/Network/ZeusWorldSubsystem.cpp +++ b/Source/ZMMO/Game/Network/ZeusWorldSubsystem.cpp @@ -42,6 +42,7 @@ void UZeusWorldSubsystem::OnWorldBeginPlay(UWorld& InWorld) Net->OnEntityDespawned.AddDynamic(this, &UZeusWorldSubsystem::OnNetEntityDespawned); Net->OnEntityDeltaApplied.AddDynamic(this, &UZeusWorldSubsystem::OnNetEntityDelta); Net->OnSelfEntityAssigned.AddDynamic(this, &UZeusWorldSubsystem::OnNetSelfEntityAssigned); + Net->OnCharInfo.AddDynamic(this, &UZeusWorldSubsystem::OnNetCharInfo); UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem bound to network client subsystem delegates.")); // O subsystem de rede e' per-GameInstance e sobrevive ao OpenLevel, entao @@ -77,9 +78,11 @@ void UZeusWorldSubsystem::Deinitialize() Net->OnEntityDespawned.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetEntityDespawned); Net->OnEntityDeltaApplied.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetEntityDelta); Net->OnSelfEntityAssigned.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetSelfEntityAssigned); + Net->OnCharInfo.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetCharInfo); } RemoteEntities.Reset(); + PendingCharInfo.Reset(); LocalEntityId = 0; Super::Deinitialize(); } @@ -112,6 +115,9 @@ void UZeusWorldSubsystem::RegisterLocalEntity(const int64 EntityId, AActor* Loca } RemoteEntities.Add(EntityId, TWeakObjectPtr(LocalActor)); + // V1-CHARINFO: o nome do proprio char pode ter chegado antes do pawn local + // existir (CHAR_INFO vem logo apos ENT_SELF); aplica agora. + FlushPendingCharInfo(EntityId); UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: local entity registered EntityId=%lld actor=%s"), EntityId, *GetNameSafe(LocalActor)); } @@ -255,10 +261,63 @@ void UZeusWorldSubsystem::HandlePlayerSpawned(const int64 EntityId, const bool b } RemoteEntities.Add(EntityId, TWeakObjectPtr(SpawnedActor)); + // V1-CHARINFO: o nome chega ANTES do ator (server emite ENT_CHAR_INFO antes + // do ENT_SPAWN); aplica agora que o proxy + PlayerState ja existem. + FlushPendingCharInfo(EntityId); UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: spawned remote EntityId=%d at (%s) yaw=%.1f t=%lld"), EntityId, *PosCm.ToString(), YawDeg, ServerTimeMs); } +void UZeusWorldSubsystem::OnNetCharInfo(const int64 EntityId, const FString& CharName, const FString& GuildName) +{ + if (EntityId == 0) + { + return; + } + // Aplica direto se o ator/PlayerState ja existe; senao guarda pra aplicar no spawn. + if (!ApplyCharInfoToEntity(EntityId, CharName, GuildName)) + { + PendingCharInfo.Add(EntityId, FZeusPendingCharInfo{ CharName, GuildName }); + } +} + +bool UZeusWorldSubsystem::ApplyCharInfoToEntity(const int64 EntityId, const FString& CharName, const FString& GuildName) +{ + const TWeakObjectPtr* Found = RemoteEntities.Find(EntityId); + if (!Found || !Found->IsValid()) + { + return false; + } + APawn* Pawn = Cast(Found->Get()); + if (!Pawn) + { + return false; + } + AZeusPlayerState* PS = Pawn->GetPlayerState(); + if (!PS) + { + return false; + } + PS->SetCharInfo(CharName, GuildName); + UE_LOG(LogZMMO, Log, + TEXT("ZeusWorldSubsystem: CHAR_INFO aplicado EntityId=%lld name='%s' guild='%s'"), + EntityId, *CharName, *GuildName); + return true; +} + +void UZeusWorldSubsystem::FlushPendingCharInfo(const int64 EntityId) +{ + const FZeusPendingCharInfo* Pending = PendingCharInfo.Find(EntityId); + if (!Pending) + { + return; + } + if (ApplyCharInfoToEntity(EntityId, Pending->CharName, Pending->GuildName)) + { + PendingCharInfo.Remove(EntityId); + } +} + void UZeusWorldSubsystem::HandlePlayerDespawned(const int64 EntityId) { const TWeakObjectPtr* Entry = RemoteEntities.Find(EntityId); diff --git a/Source/ZMMO/Game/Network/ZeusWorldSubsystem.h b/Source/ZMMO/Game/Network/ZeusWorldSubsystem.h index 5050f9a..3bf7811 100644 --- a/Source/ZMMO/Game/Network/ZeusWorldSubsystem.h +++ b/Source/ZMMO/Game/Network/ZeusWorldSubsystem.h @@ -96,10 +96,24 @@ private: UFUNCTION() void OnNetSelfEntityAssigned(int64 EntityId); + // V1-CHARINFO: nome/guild do char (opcode ENT_CHAR_INFO 6045). Chega ANTES + // do ENT_SPAWN do proxy / do RegisterLocalEntity do self, entao se o ator + // ainda nao existe o dado fica em PendingCharInfo e e' aplicado no spawn. + UFUNCTION() + void OnNetCharInfo(int64 EntityId, const FString& CharName, const FString& GuildName); + UClass* ResolveActorClass(EZeusEntityType EntityType) const; UZeusNetworkSubsystem* ResolveZeusNetworkSubsystem() const; UZeusNetworkingClientSubsystem* ResolveNetClientSubsystem() const; + // Aplica nome/guild no AZeusPlayerState do EntityId (self ou proxy). Retorna + // false se o ator/PlayerState ainda nao existe (caller deve cachear). + bool ApplyCharInfoToEntity(int64 EntityId, const FString& CharName, const FString& GuildName); + + // Aplica (e consome) o char info pendente de EntityId, se houver. Chamado + // quando o ator passa a existir (spawn de proxy / RegisterLocalEntity). + void FlushPendingCharInfo(int64 EntityId); + /** * Registry de proxies remotos. Chave = `EntityId` autoritativo do servidor. * Usamos `TWeakObjectPtr` para nao impedir GC se algo der errado e o ator @@ -110,4 +124,12 @@ private: /** Cached id do jogador local, para ignorar snapshots dele (cliente solto). */ int64 LocalEntityId = 0; + + /** V1-CHARINFO: nome/guild recebido antes do ator existir. Chave = EntityId. */ + struct FZeusPendingCharInfo + { + FString CharName; + FString GuildName; + }; + TMap PendingCharInfo; }; diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp index 5d7074c..c9379a1 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp +++ b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp @@ -11,6 +11,7 @@ #include "ZeusGameInstance.h" #include "ZeusThemeSubsystem.h" #include "ZeusNetworkSubsystem.h" +#include "ZeusNetworkingClientSubsystem.h" // V1 canonico (sinal de spawn local) #include "ZeusCharServerSubsystem.h" #include "CommonActivatableWidget.h" #include "Engine/DataTable.h" @@ -265,6 +266,12 @@ UZeusNetworkSubsystem* UUIFrontEndFlowSubsystem::GetZeusNetwork() const return GI ? GI->GetSubsystem() : nullptr; } +UZeusNetworkingClientSubsystem* UUIFrontEndFlowSubsystem::GetNetClient() const +{ + const UGameInstance* GI = GetGameInstance(); + return GI ? GI->GetSubsystem() : nullptr; +} + UZeusCharServerSubsystem* UUIFrontEndFlowSubsystem::GetCharServer() const { const UGameInstance* GI = GetGameInstance(); @@ -314,11 +321,20 @@ void UUIFrontEndFlowSubsystem::BindNetwork() Char->OnConnectionFailed.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed); Char->OnDisconnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleCharDisconnected); } - // UDP (world server) só interessa para o handoff de travel. + // UDP (world server): o travel (TRAVEL_TO_MAP) ainda passa pelo objeto legacy + // como PONTE (HandleTravelToMap V1 reusa OnServerTravelRequested) -- isso some + // na Fase D quando o travel ganhar delegate proprio no V1. if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork()) { Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested); - Zeus->OnPlayerSpawned.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned); + } + // V1 CANONICO: o spawn do player local chega via ENT_SELF (o cliente aprende + // o proprio entityId) -> OnSelfEntityAssigned. O legacy OnPlayerSpawned NAO + // dispara mais com V1, entao a etapa "Spawn" do loading ficava eterna + // (loading travado). Liga a etapa ao sinal V1. + if (UZeusNetworkingClientSubsystem* Net = GetNetClient()) + { + Net->OnSelfEntityAssigned.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleV1LocalSpawned); } bNetBound = true; } @@ -338,7 +354,10 @@ void UUIFrontEndFlowSubsystem::UnbindNetwork() if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork()) { Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested); - Zeus->OnPlayerSpawned.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned); + } + if (UZeusNetworkingClientSubsystem* Net = GetNetClient()) + { + Net->OnSelfEntityAssigned.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleV1LocalSpawned); } bNetBound = false; } @@ -505,6 +524,13 @@ void UUIFrontEndFlowSubsystem::HandlePlayerSpawned(int64 /*EntityId*/, bool bIsL } } +void UUIFrontEndFlowSubsystem::HandleV1LocalSpawned(int64 EntityId) +{ + // ENT_SELF e' SEMPRE o proprio char (o cliente aprendendo seu entityId), entao + // bIsLocal=true. Reusa a logica de marcar a etapa "Spawn" + memoizacao anti-race. + HandlePlayerSpawned(EntityId, /*bIsLocal=*/true, FVector::ZeroVector, 0.0f, 0); +} + void UUIFrontEndFlowSubsystem::HandleLoadingComplete() { if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get()) diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h index c24a181..89ec05f 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h +++ b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h @@ -11,6 +11,7 @@ class UUIManagerSubsystem; class UUILoadingScreen_Base; class UZeusLoadingProfilesDataAsset; class UZeusNetworkSubsystem; +class UZeusNetworkingClientSubsystem; class UZeusCharServerSubsystem; struct FZeusMapDef; @@ -171,6 +172,7 @@ private: void BindNetwork(); void UnbindNetwork(); UZeusNetworkSubsystem* GetZeusNetwork() const; + UZeusNetworkingClientSubsystem* GetNetClient() const; // V1 canonico UZeusCharServerSubsystem* GetCharServer() const; UUIManagerSubsystem* GetUIManager() const; UUIFrontEndScreenSet* GetScreenSet(); @@ -198,6 +200,15 @@ private: 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();