diff --git a/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_Window.uasset b/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_Window.uasset index 2490686..d06fac0 100644 Binary files a/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_Window.uasset and b/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_Window.uasset differ diff --git a/Source/ZMMO/Game/Entity/ZMMOPlayerCharacter.cpp b/Source/ZMMO/Game/Entity/ZMMOPlayerCharacter.cpp index a4a2832..8d96d73 100644 --- a/Source/ZMMO/Game/Entity/ZMMOPlayerCharacter.cpp +++ b/Source/ZMMO/Game/Entity/ZMMOPlayerCharacter.cpp @@ -18,6 +18,7 @@ #include "UObject/ConstructorHelpers.h" #include "ZMMO.h" #include "ZMMOAttributeComponent.h" +#include "ZMMOPlayerState.h" #include "ZMMOWorldSubsystem.h" #include "ZeusNetworkSubsystem.h" #include "UI/FrontEnd/UIFrontEndFlowSubsystem.h" @@ -278,6 +279,7 @@ void AZMMOPlayerCharacter::BindZeusSpawnDelegate() } ZeusNetwork->OnPlayerSpawned.AddDynamic(this, &AZMMOPlayerCharacter::HandleZeusPlayerSpawned); + ZeusNetwork->OnCharInfoReceived.AddDynamic(this, &AZMMOPlayerCharacter::HandleZeusCharInfo); bSpawnDelegateBound = true; } @@ -289,6 +291,7 @@ void AZMMOPlayerCharacter::UnbindZeusSpawnDelegate() return; } ZeusNetwork->OnPlayerSpawned.RemoveDynamic(this, &AZMMOPlayerCharacter::HandleZeusPlayerSpawned); + ZeusNetwork->OnCharInfoReceived.RemoveDynamic(this, &AZMMOPlayerCharacter::HandleZeusCharInfo); bSpawnDelegateBound = false; } @@ -307,6 +310,10 @@ void AZMMOPlayerCharacter::TryRegisterLocalEntityFromCachedSpawn() { HandleLocalSpawnReady(CachedEntityId, CachedPosCm, CachedYawDeg, CachedServerTimeMs); } + + // Race fix: o S_CHAR_INFO chega ANTES de S_SPAWN_PLAYER mas o pawn pode + // nao existir ainda (OpenLevel em andamento). Aplica o ultimo cacheado. + TryApplyCachedCharInfo(); } void AZMMOPlayerCharacter::HandleZeusPlayerSpawned(const int32 InEntityId, const bool bIsLocal, @@ -349,25 +356,74 @@ void AZMMOPlayerCharacter::HandleLocalSpawnReady(const int32 InEntityId, const F } } - // Seed do EntityId no AttributeComponent do PlayerState. O componente - // vive no AZMMOPlayerState via Component Registry — busca via PlayerState - // (sobrevive ao despawn do Pawn). S_ATTRIBUTE_SNAPSHOT_FULL chega via - // UDP e e' rouado pelo ZMMOAttributeNetworkHandler via lookup por - // EntityId no PlayerArray do GameState. + // Identidade publica (EntityId) + seed no AttributeComponent. CharName/Guild + // vem separado via S_CHAR_INFO -> HandleZeusCharInfo. AttributeComponent vive + // como subobject e recebe seed pra que o ZMMOAttributeNetworkHandler consiga + // rotear o snapshot por EntityId desde o primeiro pacote. if (APlayerState* PS = GetPlayerState()) { + if (AZMMOPlayerState* ZMMOPs = Cast(PS)) + { + ZMMOPs->SetPublicIdentity(EntityIdAsInt64, /*CharId*/ FString(), + /*BaseLevel*/ 1, /*ClassId*/ 0); + } if (UZMMOAttributeComponent* AttrComp = PS->FindComponentByClass()) { AttrComp->SeedEntityId(InEntityId); } } + // Aplica char info cacheada (caso S_CHAR_INFO tenha chegado antes do PS existir). + TryApplyCachedCharInfo(); + // UI in-game e' responsabilidade do AZMMOHUD (GameMode.HUDClass), nao do // Pawn. AHUD::BeginPlay chama UUIInGameFlowSubsystem::StartInGame, que // pushea WBP_HUD em UI.Layer.Game. UZMMOHudWidget::NativeOnActivated auto-binda // no UZMMOAttributeComponent via PlayerState. } +void AZMMOPlayerCharacter::HandleZeusCharInfo(const int32 InEntityId, const FString& CharName, const FString& GuildName) +{ + UE_LOG(LogZMMOPlayer, Log, + TEXT("AZMMOPlayerCharacter: S_CHAR_INFO recebido EntityId=%d name=%s guild=%s"), + InEntityId, *CharName, *GuildName); + + APlayerState* PS = GetPlayerState(); + if (!PS) + { + // Race fix: pawn pode nao ter PlayerState ainda. Subsystem ja cacheou + // (LastLocalCharInfoCache); TryApplyCachedCharInfo aplica em + // HandleLocalSpawnReady ou em outra oportunidade. + return; + } + if (AZMMOPlayerState* ZMMOPs = Cast(PS)) + { + ZMMOPs->SetCharInfo(CharName, GuildName); + } +} + +void AZMMOPlayerCharacter::TryApplyCachedCharInfo() +{ + if (!ZeusNetwork) + { + return; + } + int32 CachedEntityId = 0; + FString CachedCharName; + FString CachedGuildName; + if (!ZeusNetwork->TryGetLastLocalCharInfo(CachedEntityId, CachedCharName, CachedGuildName)) + { + return; + } + if (APlayerState* PS = GetPlayerState()) + { + if (AZMMOPlayerState* ZMMOPs = Cast(PS)) + { + ZMMOPs->SetCharInfo(CachedCharName, CachedGuildName); + } + } +} + void AZMMOPlayerCharacter::FlushInputAxisToServer(const float DeltaSeconds) { ResolveZeusNetworkSubsystem(); diff --git a/Source/ZMMO/Game/Entity/ZMMOPlayerCharacter.h b/Source/ZMMO/Game/Entity/ZMMOPlayerCharacter.h index dc6d166..bc058e5 100644 --- a/Source/ZMMO/Game/Entity/ZMMOPlayerCharacter.h +++ b/Source/ZMMO/Game/Entity/ZMMOPlayerCharacter.h @@ -133,12 +133,19 @@ private: /** * Memoriza `EntityId` autoritativo e regista o pawn no `UZMMOWorldSubsystem` * para o registry partilhado por proxies remotos (filtra snapshots locais). + * Metadados publicos do char (Name/Guild) chegam via S_CHAR_INFO em + * HandleZeusCharInfo (servidor envia ANTES de S_SPAWN_PLAYER). */ void HandleLocalSpawnReady(int32 InEntityId, FVector PosCm, float YawDeg, int64 ServerTimeMs); UFUNCTION() void HandleZeusPlayerSpawned(int32 InEntityId, bool bIsLocal, FVector PosCm, float YawDeg, int64 ServerTimeMs); + UFUNCTION() + void HandleZeusCharInfo(int32 InEntityId, const FString& CharName, const FString& GuildName); + + void TryApplyCachedCharInfo(); + UPROPERTY() TObjectPtr ZeusNetwork; diff --git a/Source/ZMMO/Game/Modes/ZMMOPlayerState.cpp b/Source/ZMMO/Game/Modes/ZMMOPlayerState.cpp index e7cfda9..4fb5a2d 100644 --- a/Source/ZMMO/Game/Modes/ZMMOPlayerState.cpp +++ b/Source/ZMMO/Game/Modes/ZMMOPlayerState.cpp @@ -2,6 +2,7 @@ #include "Components/ActorComponent.h" #include "Misc/ConfigCacheIni.h" +#include "Net/UnrealNetwork.h" #include "ZMMO.h" AZMMOPlayerState::AZMMOPlayerState() @@ -72,6 +73,17 @@ AZMMOPlayerState::AZMMOPlayerState() } } +void AZMMOPlayerState::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + DOREPLIFETIME(AZMMOPlayerState, ZMMOEntityId); + DOREPLIFETIME(AZMMOPlayerState, CharId); + DOREPLIFETIME(AZMMOPlayerState, CharName); + DOREPLIFETIME(AZMMOPlayerState, BaseLevel); + DOREPLIFETIME(AZMMOPlayerState, ClassId); + DOREPLIFETIME(AZMMOPlayerState, GuildName); +} + void AZMMOPlayerState::SetPublicIdentity(int64 InEntityId, const FString& InCharId, int32 InBaseLevel, int32 InClassId) { @@ -80,3 +92,9 @@ void AZMMOPlayerState::SetPublicIdentity(int64 InEntityId, const FString& InChar BaseLevel = InBaseLevel; ClassId = InClassId; } + +void AZMMOPlayerState::SetCharInfo(const FString& InCharName, const FString& InGuildName) +{ + CharName = InCharName; + GuildName = InGuildName; +} diff --git a/Source/ZMMO/Game/Modes/ZMMOPlayerState.h b/Source/ZMMO/Game/Modes/ZMMOPlayerState.h index d7be27f..1d027f0 100644 --- a/Source/ZMMO/Game/Modes/ZMMOPlayerState.h +++ b/Source/ZMMO/Game/Modes/ZMMOPlayerState.h @@ -5,6 +5,7 @@ #include "Templates/SubclassOf.h" #include "ZMMOPlayerState.generated.h" +class FLifetimeProperty; class UActorComponent; /** @@ -52,36 +53,55 @@ public: AZMMOPlayerState(); // === Identidade publica === + // + // Todos os campos publicos sao Replicated — hoje o cliente UE5 roda em + // Standalone (servidor C++ standalone, sem replicacao UE5 ativa), entao + // a marca nao tem efeito em runtime; deixa a classe pronta caso o projeto + // passe a usar dedicated UE5 no futuro. /** EntityId autoritativo do server. 0 ate o spawn local confirmar. */ - UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Identity") + UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Identity") int64 ZMMOEntityId = 0; /** CharId (BIGINT no DB; FString pra preservar 64 bits em Blueprint). */ - UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Identity") + UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Identity") FString CharId; + /** Nome do char vindo do DB (`characters.name`). Setado por + * AZMMOPlayerCharacter::HandleLocalSpawnReady com o payload do + * S_SPAWN_PLAYER. UI prefere isto a APlayerState::GetPlayerName(). */ + UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Identity") + FString CharName; + // === Progressao publica === - UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress") + UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress") int32 BaseLevel = 1; - UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress") + UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress") int32 ClassId = 0; // === Guild (futuro) === - UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Guild") + UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Guild") FString GuildName; + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + // === Helpers === - /// Atualizado quando S_ATTRIBUTE_SNAPSHOT_FULL chega — UZMMOHudWidget chama - /// isso pra refletir os dados publicos do snapshot no PlayerState. + /// Setado por AZMMOPlayerCharacter::HandleLocalSpawnReady (pose/EntityId) + /// e atualizado por handlers de snapshot/level up para refletir dados + /// publicos do char no PlayerState. UFUNCTION(BlueprintCallable, Category = "ZMMO|Identity") void SetPublicIdentity(int64 InEntityId, const FString& InCharId, int32 InBaseLevel, int32 InClassId); + /// Setado pelo handler do S_CHAR_INFO (chega antes de S_SPAWN_PLAYER) — + /// metadados publicos do char vindos do servidor. + UFUNCTION(BlueprintCallable, Category = "ZMMO|Identity") + void SetCharInfo(const FString& InCharName, const FString& InGuildName); + /// Template helper: PS->GetZMMOComponent() template T* GetZMMOComponent() const diff --git a/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.cpp b/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.cpp index 2a8c682..dca2771 100644 --- a/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.cpp +++ b/Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.cpp @@ -11,6 +11,7 @@ #include "ZMMOAttributeComponent.h" #include "ZMMOJobDataAsset.h" #include "ZMMOJobsSubsystem.h" +#include "ZMMOPlayerState.h" #include "UI/InGame/UIInGameFlowSubsystem.h" #include "UI/InGameTypes.h" #include "UI/Widgets/UIPlayerStatus_BonusRow.h" @@ -87,10 +88,20 @@ void UUIPlayerStatus_Window::BindToLocalPlayer() APlayerController* PC = World->GetFirstPlayerController(); if (!PC || !PC->PlayerState) return; - // CharName vem do PlayerState (estável por sessão) — popula uma vez. + // CharName vem do AZMMOPlayerState (setado pelo S_SPAWN_PLAYER no spawn + // local). Fallback pra APlayerState::GetPlayerName() se o cast falhar ou + // se CharName ainda nao chegou (race com handler que dispara o widget). if (Text_CharName) { - Text_CharName->SetText(FText::FromString(PC->PlayerState->GetPlayerName())); + FString DisplayName = PC->PlayerState->GetPlayerName(); + if (const AZMMOPlayerState* ZMMOPs = Cast(PC->PlayerState)) + { + if (!ZMMOPs->CharName.IsEmpty()) + { + DisplayName = ZMMOPs->CharName; + } + } + Text_CharName->SetText(FText::FromString(DisplayName)); } UZMMOAttributeComponent* Comp = PC->PlayerState->FindComponentByClass();