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) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 02:24:29 -03:00
parent 87d9ca4dae
commit 90484876f0
6 changed files with 126 additions and 14 deletions

View File

@@ -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<AZMMOPlayerState>(PS))
{
ZMMOPs->SetPublicIdentity(EntityIdAsInt64, /*CharId*/ FString(),
/*BaseLevel*/ 1, /*ClassId*/ 0);
}
if (UZMMOAttributeComponent* AttrComp = PS->FindComponentByClass<UZMMOAttributeComponent>())
{
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<AZMMOPlayerState>(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<AZMMOPlayerState>(PS))
{
ZMMOPs->SetCharInfo(CachedCharName, CachedGuildName);
}
}
}
void AZMMOPlayerCharacter::FlushInputAxisToServer(const float DeltaSeconds)
{
ResolveZeusNetworkSubsystem();

View File

@@ -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<UZeusNetworkSubsystem> ZeusNetwork;

View File

@@ -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<FLifetimeProperty>& 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;
}

View File

@@ -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<FLifetimeProperty>& 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<UZMMOAttributeComponent>()
template <typename T>
T* GetZMMOComponent() const

View File

@@ -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<AZMMOPlayerState>(PC->PlayerState))
{
if (!ZMMOPs->CharName.IsEmpty())
{
DisplayName = ZMMOPs->CharName;
}
}
Text_CharName->SetText(FText::FromString(DisplayName));
}
UZMMOAttributeComponent* Comp = PC->PlayerState->FindComponentByClass<UZMMOAttributeComponent>();