From 90484876f06c9fc40b54c181ec4653f8cb8292f0 Mon Sep 17 00:00:00 2001 From: Mateus Rodrigues Date: Tue, 26 May 2026 02:24:29 -0300 Subject: [PATCH] feat(player): CharName do DB no PlayerState + header do Status Window - PlayerState ganha CharName (Replicated) + SetCharInfo(CharName, GuildName) - ZMMOPlayerCharacter assina FZeusOnCharInfoReceived; TryApplyCachedCharInfo resolve race se S_CHAR_INFO chega antes do pawn possuir - UIPlayerStatus_Window header le PlayerState->CharName (fallback p/ GetPlayerName) em vez do "Mateuus-61E9B19A4FB2" generico Co-Authored-By: Claude Opus 4.7 (1M context) --- .../UI_PlayerStatus_Window.uasset | Bin 158027 -> 158032 bytes .../ZMMO/Game/Entity/ZMMOPlayerCharacter.cpp | 66 ++++++++++++++++-- Source/ZMMO/Game/Entity/ZMMOPlayerCharacter.h | 7 ++ Source/ZMMO/Game/Modes/ZMMOPlayerState.cpp | 18 +++++ Source/ZMMO/Game/Modes/ZMMOPlayerState.h | 34 +++++++-- .../Game/UI/Widgets/UIPlayerStatus_Window.cpp | 15 +++- 6 files changed, 126 insertions(+), 14 deletions(-) diff --git a/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_Window.uasset b/Content/ZMMO/UI/Screens/PlayerStatus/UI_PlayerStatus_Window.uasset index 249068670ff3bb678549a0172014889630f73e8c..d06fac09485be3ca093a2ec1faf9530fb97055cb 100644 GIT binary patch delta 9428 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 delta 9050 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 S4tAAR+hNmKY||(uBm6JD^wcN- 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();