Consolidar GameplayAbilitySystem na main (baseline cliente tudo funcionando 2026-06-16) #6
@@ -26,6 +26,7 @@
|
|||||||
#include "GameFramework/PlayerController.h"
|
#include "GameFramework/PlayerController.h"
|
||||||
#include "ZeusWorldSubsystem.h"
|
#include "ZeusWorldSubsystem.h"
|
||||||
#include "ZeusNetworkSubsystem.h"
|
#include "ZeusNetworkSubsystem.h"
|
||||||
|
#include "ZeusNetworkingClientSubsystem.h"
|
||||||
#include "UI/FrontEnd/UIFrontEndFlowSubsystem.h"
|
#include "UI/FrontEnd/UIFrontEndFlowSubsystem.h"
|
||||||
|
|
||||||
DEFINE_LOG_CATEGORY(LogZeusPlayer);
|
DEFINE_LOG_CATEGORY(LogZeusPlayer);
|
||||||
@@ -362,7 +363,7 @@ void AZeusCharacter::SetEntityRelevant(bool /*bRelevant*/)
|
|||||||
|
|
||||||
void AZeusCharacter::ResolveZeusNetworkSubsystem()
|
void AZeusCharacter::ResolveZeusNetworkSubsystem()
|
||||||
{
|
{
|
||||||
if (ZeusNetwork)
|
if (ZeusNetwork && NetClient)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -373,8 +374,15 @@ void AZeusCharacter::ResolveZeusNetworkSubsystem()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ZeusNetwork)
|
||||||
|
{
|
||||||
ZeusNetwork = GI->GetSubsystem<UZeusNetworkSubsystem>();
|
ZeusNetwork = GI->GetSubsystem<UZeusNetworkSubsystem>();
|
||||||
}
|
}
|
||||||
|
if (!NetClient)
|
||||||
|
{
|
||||||
|
NetClient = GI->GetSubsystem<UZeusNetworkingClientSubsystem>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AZeusCharacter::BindZeusSpawnDelegate()
|
void AZeusCharacter::BindZeusSpawnDelegate()
|
||||||
{
|
{
|
||||||
@@ -382,49 +390,71 @@ void AZeusCharacter::BindZeusSpawnDelegate()
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!ZeusNetwork)
|
if (!NetClient && !ZeusNetwork)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZeusNetwork->OnPlayerSpawned.AddDynamic(this, &AZeusCharacter::HandleZeusPlayerSpawned);
|
// Self-entity (entityId do proprio char) vem do sistema de rede novo.
|
||||||
|
if (NetClient)
|
||||||
|
{
|
||||||
|
NetClient->OnSelfEntityAssigned.AddDynamic(this, &AZeusCharacter::HandleSelfEntityAssigned);
|
||||||
|
}
|
||||||
|
// CHAR_INFO (nome/guild) ainda nao tem equivalente no sistema novo -> legacy.
|
||||||
|
if (ZeusNetwork)
|
||||||
|
{
|
||||||
ZeusNetwork->OnCharInfoReceived.AddDynamic(this, &AZeusCharacter::HandleZeusCharInfo);
|
ZeusNetwork->OnCharInfoReceived.AddDynamic(this, &AZeusCharacter::HandleZeusCharInfo);
|
||||||
|
}
|
||||||
bSpawnDelegateBound = true;
|
bSpawnDelegateBound = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AZeusCharacter::UnbindZeusSpawnDelegate()
|
void AZeusCharacter::UnbindZeusSpawnDelegate()
|
||||||
{
|
{
|
||||||
if (!bSpawnDelegateBound || !ZeusNetwork)
|
if (!bSpawnDelegateBound)
|
||||||
{
|
{
|
||||||
bSpawnDelegateBound = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ZeusNetwork->OnPlayerSpawned.RemoveDynamic(this, &AZeusCharacter::HandleZeusPlayerSpawned);
|
if (NetClient)
|
||||||
|
{
|
||||||
|
NetClient->OnSelfEntityAssigned.RemoveDynamic(this, &AZeusCharacter::HandleSelfEntityAssigned);
|
||||||
|
}
|
||||||
|
if (ZeusNetwork)
|
||||||
|
{
|
||||||
ZeusNetwork->OnCharInfoReceived.RemoveDynamic(this, &AZeusCharacter::HandleZeusCharInfo);
|
ZeusNetwork->OnCharInfoReceived.RemoveDynamic(this, &AZeusCharacter::HandleZeusCharInfo);
|
||||||
|
}
|
||||||
bSpawnDelegateBound = false;
|
bSpawnDelegateBound = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AZeusCharacter::TryRegisterLocalEntityFromCachedSpawn()
|
void AZeusCharacter::TryRegisterLocalEntityFromCachedSpawn()
|
||||||
{
|
{
|
||||||
if (!ZeusNetwork)
|
// Self-entity vem do sistema de rede novo (ENT_SELF). O subsystem e'
|
||||||
|
// per-GameInstance e sobrevive ao OpenLevel, entao o entityId pode ja' ter
|
||||||
|
// chegado antes deste pawn existir -- puxa o cache agora. Caso ainda nao
|
||||||
|
// tenha chegado, o bind em OnSelfEntityAssigned cobre quando chegar.
|
||||||
|
// A pos/yaw vem do proprio pawn (ja posicionado via PendingSpawnPose do DB);
|
||||||
|
// o sistema novo so' carrega o entityId.
|
||||||
|
if (NetClient)
|
||||||
{
|
{
|
||||||
return;
|
const int64 SelfId = NetClient->GetLocalEntityId();
|
||||||
|
if (SelfId != 0)
|
||||||
|
{
|
||||||
|
HandleLocalSpawnReady(SelfId, GetActorLocation(), GetActorRotation().Yaw, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int64 CachedEntityId = 0;
|
// Race fix CHAR_INFO (ainda legacy): o S_CHAR_INFO pode ter chegado antes do
|
||||||
FVector CachedPosCm = FVector::ZeroVector;
|
// pawn existir; aplica o ultimo nome/guild cacheado.
|
||||||
float CachedYawDeg = 0.0f;
|
|
||||||
int64 CachedServerTimeMs = 0;
|
|
||||||
if (ZeusNetwork->TryGetLastLocalSpawn(CachedEntityId, CachedPosCm, CachedYawDeg, CachedServerTimeMs))
|
|
||||||
{
|
|
||||||
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();
|
TryApplyCachedCharInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AZeusCharacter::HandleSelfEntityAssigned(const int64 InEntityId)
|
||||||
|
{
|
||||||
|
// Sistema novo informou o entityId do proprio char (ENT_SELF). Reusa o mesmo
|
||||||
|
// caminho do spawn local legacy (seta ZeusEntityId + RegisterLocalEntity).
|
||||||
|
// pos/yaw vem do pawn (ja posicionado); o sistema novo so' traz o entityId.
|
||||||
|
HandleLocalSpawnReady(InEntityId, GetActorLocation(), GetActorRotation().Yaw, 0);
|
||||||
|
}
|
||||||
|
|
||||||
void AZeusCharacter::HandleZeusPlayerSpawned(const int64 InEntityId, const bool bIsLocal,
|
void AZeusCharacter::HandleZeusPlayerSpawned(const int64 InEntityId, const bool bIsLocal,
|
||||||
const FVector PosCm, const float YawDeg, const int64 ServerTimeMs)
|
const FVector PosCm, const float YawDeg, const int64 ServerTimeMs)
|
||||||
{
|
{
|
||||||
@@ -548,7 +578,10 @@ void AZeusCharacter::TryApplyCachedCharInfo()
|
|||||||
void AZeusCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
void AZeusCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
||||||
{
|
{
|
||||||
ResolveZeusNetworkSubsystem();
|
ResolveZeusNetworkSubsystem();
|
||||||
if (!ZeusNetwork || !ZeusNetwork->IsConnected())
|
// Sistema de rede novo (canonico) tem prioridade; legacy e' fallback.
|
||||||
|
const bool bV1 = (NetClient
|
||||||
|
&& NetClient->GetConnectionState() == EZeusV1ConnectionState::Accepted);
|
||||||
|
if (!bV1 && (!ZeusNetwork || !ZeusNetwork->IsConnected()))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -557,7 +590,19 @@ void AZeusCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
|||||||
SendAccumulatorSec += DeltaSeconds;
|
SendAccumulatorSec += DeltaSeconds;
|
||||||
TimeSinceLastSendSec += DeltaSeconds;
|
TimeSinceLastSendSec += DeltaSeconds;
|
||||||
|
|
||||||
const bool bMovingInput = !FMath::IsNearlyZero(PendingMoveForward) || !FMath::IsNearlyZero(PendingMoveRight);
|
// Movimento p/ efeitos de rate NAO e' so' o input (W/A/S/D): o CMC do dono
|
||||||
|
// continua desacelerando (braking) por ~0.3s depois de soltar a tecla. Se
|
||||||
|
// pararmos de enviar no rate rapido assim que o input zera, o servidor fica
|
||||||
|
// congelado na ultima `simpleVelCmS` alta e o proxy remoto extrapola ~1m alem
|
||||||
|
// (NewPos = Last.Pos + Last.Vel*ExtrapSec) ate o heartbeat corrigir -> overshoot
|
||||||
|
// + snap-back ("velocidade grande que demora a zerar" + jitter). Tratar a
|
||||||
|
// velocidade residual como movimento mantem o envio a InputSendRateHz ate o
|
||||||
|
// dono realmente parar, alimentando o proxy com a curva de desaceleracao.
|
||||||
|
const bool bHasInputAxis = !FMath::IsNearlyZero(PendingMoveForward) || !FMath::IsNearlyZero(PendingMoveRight);
|
||||||
|
const FVector OwnerVelNow = GetVelocity();
|
||||||
|
constexpr float kResidualSpeedSqCmS = 25.0f * 25.0f; // abaixo de 25 cm/s tratamos como parado
|
||||||
|
const bool bHasResidualVel = OwnerVelNow.SizeSquared() > kResidualSpeedSqCmS;
|
||||||
|
const bool bMovingInput = bHasInputAxis || bHasResidualVel;
|
||||||
const bool bJumpEvent = bPendingJumpPressed || bPendingJumpReleased;
|
const bool bJumpEvent = bPendingJumpPressed || bPendingJumpReleased;
|
||||||
const bool bRateReached = SendAccumulatorSec >= SendInterval;
|
const bool bRateReached = SendAccumulatorSec >= SendInterval;
|
||||||
const bool bHeartbeatReached = TimeSinceLastSendSec >= HeartbeatIntervalSec;
|
const bool bHeartbeatReached = TimeSinceLastSendSec >= HeartbeatIntervalSec;
|
||||||
@@ -571,6 +616,23 @@ void AZeusCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
|||||||
const bool bFallingChanged = (bIsFalling != bPreviousFalling);
|
const bool bFallingChanged = (bIsFalling != bPreviousFalling);
|
||||||
|
|
||||||
const bool bShouldSend = bJumpEvent || bFallingChanged || (bMovingInput && bRateReached) || (!bMovingInput && bHeartbeatReached);
|
const bool bShouldSend = bJumpEvent || bFallingChanged || (bMovingInput && bRateReached) || (!bMovingInput && bHeartbeatReached);
|
||||||
|
// DEBUG-INPUT (2026-06-12): log ~1x/s COM o PIE. Remover apos achar o gap.
|
||||||
|
{
|
||||||
|
static float DbgInputAcc = 0.0f;
|
||||||
|
DbgInputAcc += DeltaSeconds;
|
||||||
|
if (DbgInputAcc >= 1.0f)
|
||||||
|
{
|
||||||
|
DbgInputAcc = 0.0f;
|
||||||
|
const int32 PieId = (GetWorld() && GetWorld()->GetOutermost())
|
||||||
|
? GetWorld()->GetOutermost()->GetPIEInstanceID() : -1;
|
||||||
|
UE_LOG(LogZeusPlayer, Warning,
|
||||||
|
TEXT("[PIE%d][InputDbg] bV1=%d state=%d moving=%d rate=%d should=%d hbReached=%d fwd=%.2f right=%.2f"),
|
||||||
|
PieId, bV1 ? 1 : 0,
|
||||||
|
NetClient ? static_cast<int32>(NetClient->GetConnectionState()) : -99,
|
||||||
|
bMovingInput ? 1 : 0, bRateReached ? 1 : 0, bShouldSend ? 1 : 0,
|
||||||
|
bHeartbeatReached ? 1 : 0, PendingMoveForward, PendingMoveRight);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!bShouldSend)
|
if (!bShouldSend)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -590,6 +652,36 @@ void AZeusCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
|||||||
// os outros clientes (sujeito ao clamp anti-cheat).
|
// os outros clientes (sujeito ao clamp anti-cheat).
|
||||||
const FVector PosCm = GetActorLocation();
|
const FVector PosCm = GetActorLocation();
|
||||||
const FVector Vel = GetVelocity();
|
const FVector Vel = GetVelocity();
|
||||||
|
if (bV1)
|
||||||
|
{
|
||||||
|
// Sistema novo: INPUT 6077 com a pose absoluta (ADR 0040/0041).
|
||||||
|
const bool bSent = NetClient->EmitInput(
|
||||||
|
PendingMoveForward,
|
||||||
|
PendingMoveRight,
|
||||||
|
bPendingJumpPressed,
|
||||||
|
bPendingJumpReleased,
|
||||||
|
InputSequence,
|
||||||
|
ClientTimeMs,
|
||||||
|
ViewYawDeg,
|
||||||
|
PosCm,
|
||||||
|
FVector2D(Vel.X, Vel.Y),
|
||||||
|
bIsFalling,
|
||||||
|
static_cast<float>(Vel.Z));
|
||||||
|
// DEBUG-INPUT (2026-06-12): confirma chamada + retorno do SendPacket.
|
||||||
|
static float DbgEmitAcc = 0.0f;
|
||||||
|
DbgEmitAcc += DeltaSeconds;
|
||||||
|
if (DbgEmitAcc >= 1.0f)
|
||||||
|
{
|
||||||
|
DbgEmitAcc = 0.0f;
|
||||||
|
const int32 PieId = (GetWorld() && GetWorld()->GetOutermost())
|
||||||
|
? GetWorld()->GetOutermost()->GetPIEInstanceID() : -1;
|
||||||
|
UE_LOG(LogZeusPlayer, Warning,
|
||||||
|
TEXT("[PIE%d][InputDbg] EmitInput seq=%d sent=%d pos=%s"),
|
||||||
|
PieId, InputSequence, bSent ? 1 : 0, *PosCm.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ZeusNetwork)
|
||||||
|
{
|
||||||
ZeusNetwork->SendInputAxis(
|
ZeusNetwork->SendInputAxis(
|
||||||
PendingMoveForward,
|
PendingMoveForward,
|
||||||
PendingMoveRight,
|
PendingMoveRight,
|
||||||
@@ -602,6 +694,7 @@ void AZeusCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
|||||||
FVector2D(Vel.X, Vel.Y),
|
FVector2D(Vel.X, Vel.Y),
|
||||||
bIsFalling,
|
bIsFalling,
|
||||||
static_cast<float>(Vel.Z));
|
static_cast<float>(Vel.Z));
|
||||||
|
}
|
||||||
|
|
||||||
SendAccumulatorSec = 0.0f;
|
SendAccumulatorSec = 0.0f;
|
||||||
TimeSinceLastSendSec = 0.0f;
|
TimeSinceLastSendSec = 0.0f;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class USpringArmComponent;
|
|||||||
class UCameraComponent;
|
class UCameraComponent;
|
||||||
class UInputAction;
|
class UInputAction;
|
||||||
class UZeusNetworkSubsystem;
|
class UZeusNetworkSubsystem;
|
||||||
|
class UZeusNetworkingClientSubsystem;
|
||||||
class UZeusAOIComponent;
|
class UZeusAOIComponent;
|
||||||
class UUserWidget;
|
class UUserWidget;
|
||||||
struct FInputActionValue;
|
struct FInputActionValue;
|
||||||
@@ -164,6 +165,11 @@ private:
|
|||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleZeusPlayerSpawned(int64 InEntityId, bool bIsLocal, FVector PosCm, float YawDeg, int64 ServerTimeMs);
|
void HandleZeusPlayerSpawned(int64 InEntityId, bool bIsLocal, FVector PosCm, float YawDeg, int64 ServerTimeMs);
|
||||||
|
|
||||||
|
// Self-entity do sistema de rede novo (ENT_SELF): o cliente aprende o
|
||||||
|
// proprio entityId. Substitui o caminho legacy OnPlayerSpawned(bIsLocal=true).
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleSelfEntityAssigned(int64 InEntityId);
|
||||||
|
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleZeusCharInfo(int64 InEntityId, const FString& CharName, const FString& GuildName);
|
void HandleZeusCharInfo(int64 InEntityId, const FString& CharName, const FString& GuildName);
|
||||||
|
|
||||||
@@ -172,6 +178,10 @@ private:
|
|||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TObjectPtr<UZeusNetworkSubsystem> ZeusNetwork;
|
TObjectPtr<UZeusNetworkSubsystem> ZeusNetwork;
|
||||||
|
|
||||||
|
// Sistema de rede novo (canonico): fonte do self-entity (ENT_SELF).
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UZeusNetworkingClientSubsystem> NetClient;
|
||||||
|
|
||||||
/** Instancia viva do painel admin (criada lazy no primeiro F8). */
|
/** Instancia viva do painel admin (criada lazy no primeiro F8). */
|
||||||
UPROPERTY(Transient)
|
UPROPERTY(Transient)
|
||||||
TObjectPtr<UUserWidget> AdminPanelInstance;
|
TObjectPtr<UUserWidget> AdminPanelInstance;
|
||||||
|
|||||||
@@ -207,10 +207,14 @@ void AZeusPlayerProxy::Tick(const float DeltaSeconds)
|
|||||||
LastDiagLogSec = NowSec;
|
LastDiagLogSec = NowSec;
|
||||||
const int64 NewestMs = SnapshotBuffer.Last().ServerTimeMs;
|
const int64 NewestMs = SnapshotBuffer.Last().ServerTimeMs;
|
||||||
const int64 RenderLagMs = ServerNowMs - NewestMs;
|
const int64 RenderLagMs = ServerNowMs - NewestMs;
|
||||||
UE_LOG(LogZMMO, Verbose,
|
// DEBUG-PROXY (2026-06-12): Warning temporario p/ diagnosticar jitter-andando.
|
||||||
TEXT("ZeusPlayerProxy[%lld] buffer=%d renderLagMs=%lld interpAlpha=%.2f extrap=%d delayMs=%.0f"),
|
// extrap=1 frequente => buffer faminto (subir InterpolationDelayMs/rate).
|
||||||
|
// Rebaixar p/ Verbose depois de validar. speed em cm/s da velocidade visual.
|
||||||
|
UE_LOG(LogZMMO, Warning,
|
||||||
|
TEXT("ZeusPlayerProxy[%lld] buffer=%d renderLagMs=%lld interpAlpha=%.2f extrap=%d delayMs=%.0f speed=%.0f"),
|
||||||
EntityId, SnapshotBuffer.Num(), RenderLagMs, InterpAlpha,
|
EntityId, SnapshotBuffer.Num(), RenderLagMs, InterpAlpha,
|
||||||
bExtrapolating ? 1 : 0, InterpolationDelayMs);
|
bExtrapolating ? 1 : 0, InterpolationDelayMs,
|
||||||
|
FVector(VisualVelocity.X, VisualVelocity.Y, 0.0f).Size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "ZeusPlayerProxy.h"
|
#include "ZeusPlayerProxy.h"
|
||||||
#include "ZeusPlayerState.h"
|
#include "ZeusPlayerState.h"
|
||||||
#include "ZeusNetworkSubsystem.h"
|
#include "ZeusNetworkSubsystem.h"
|
||||||
|
#include "ZeusNetworkingClientSubsystem.h"
|
||||||
|
|
||||||
void UZeusWorldSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
void UZeusWorldSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||||
{
|
{
|
||||||
@@ -32,42 +33,50 @@ void UZeusWorldSubsystem::OnWorldBeginPlay(UWorld& InWorld)
|
|||||||
{
|
{
|
||||||
Super::OnWorldBeginPlay(InWorld);
|
Super::OnWorldBeginPlay(InWorld);
|
||||||
|
|
||||||
if (UZeusNetworkSubsystem* ZeusNet = ResolveZeusNetworkSubsystem())
|
// Sistema de rede novo (canonico) -- substitui o ZeusNetworkSubsystem legacy
|
||||||
|
// para spawn/despawn/movimento de proxies. O legacy fica so' com o que ainda
|
||||||
|
// nao tem equivalente (CHAR_INFO/nome, tratado em AZeusCharacter).
|
||||||
|
if (UZeusNetworkingClientSubsystem* Net = ResolveNetClientSubsystem())
|
||||||
{
|
{
|
||||||
ZeusNet->OnPlayerSpawned.AddDynamic(this, &UZeusWorldSubsystem::HandlePlayerSpawned);
|
Net->OnEntitySpawned.AddDynamic(this, &UZeusWorldSubsystem::OnNetEntitySpawned);
|
||||||
ZeusNet->OnPlayerDespawned.AddDynamic(this, &UZeusWorldSubsystem::HandlePlayerDespawned);
|
Net->OnEntityDespawned.AddDynamic(this, &UZeusWorldSubsystem::OnNetEntityDespawned);
|
||||||
ZeusNet->OnPlayerStateUpdate.AddDynamic(this, &UZeusWorldSubsystem::HandlePlayerStateUpdate);
|
Net->OnEntityDeltaApplied.AddDynamic(this, &UZeusWorldSubsystem::OnNetEntityDelta);
|
||||||
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem bound to ZeusNetworkSubsystem delegates."));
|
Net->OnSelfEntityAssigned.AddDynamic(this, &UZeusWorldSubsystem::OnNetSelfEntityAssigned);
|
||||||
|
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem bound to network client subsystem delegates."));
|
||||||
|
|
||||||
// Catch-up race fix (Fase 3): broadcasts de proxies remotos chegam
|
// O subsystem de rede e' per-GameInstance e sobrevive ao OpenLevel, entao
|
||||||
// enquanto o cliente novo ainda esta em OpenLevel. UZeusNetworkSubsystem
|
// o self-entity e os ENT_SPAWN podem ter chegado ANTES deste bind. Puxa o
|
||||||
// cacheia em PendingRemoteSpawnsByEntity. Aplicamos aqui — mundo ja
|
// self cacheado + faz replay dos proxies ja conhecidos (anti-race).
|
||||||
// esta pronto (GameMode ativo, WorldPartition cells inicializadas).
|
if (const int64 CachedSelf = Net->GetLocalEntityId())
|
||||||
int32 ReplayCount = 0;
|
|
||||||
ZeusNet->ForEachPendingRemoteSpawn(
|
|
||||||
[this, &ReplayCount](const int64 EntityId, const FVector PosCm, const float YawDeg, const int64 ServerTimeMs)
|
|
||||||
{
|
{
|
||||||
HandlePlayerSpawned(EntityId, /*bIsLocal=*/false, PosCm, YawDeg, ServerTimeMs);
|
OnNetSelfEntityAssigned(CachedSelf);
|
||||||
|
}
|
||||||
|
int32 ReplayCount = 0;
|
||||||
|
Net->ForEachRemoteEntity(
|
||||||
|
[this, &ReplayCount](int64 EntityId, FVector PosCm, float YawDeg)
|
||||||
|
{
|
||||||
|
OnNetEntitySpawned(EntityId, /*Kind=*/1 /*Player*/, PosCm, YawDeg);
|
||||||
++ReplayCount;
|
++ReplayCount;
|
||||||
});
|
});
|
||||||
if (ReplayCount > 0)
|
if (ReplayCount > 0)
|
||||||
{
|
{
|
||||||
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: replay de %d proxy(ies) cacheados."), ReplayCount);
|
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: replay de %d proxy(ies) ja conhecidos."), ReplayCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UE_LOG(LogZMMO, Warning, TEXT("ZeusWorldSubsystem: ZeusNetworkSubsystem not found at OnWorldBeginPlay."));
|
UE_LOG(LogZMMO, Warning, TEXT("ZeusWorldSubsystem: network client subsystem not found at OnWorldBeginPlay."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UZeusWorldSubsystem::Deinitialize()
|
void UZeusWorldSubsystem::Deinitialize()
|
||||||
{
|
{
|
||||||
if (UZeusNetworkSubsystem* ZeusNet = ResolveZeusNetworkSubsystem())
|
if (UZeusNetworkingClientSubsystem* Net = ResolveNetClientSubsystem())
|
||||||
{
|
{
|
||||||
ZeusNet->OnPlayerSpawned.RemoveDynamic(this, &UZeusWorldSubsystem::HandlePlayerSpawned);
|
Net->OnEntitySpawned.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetEntitySpawned);
|
||||||
ZeusNet->OnPlayerDespawned.RemoveDynamic(this, &UZeusWorldSubsystem::HandlePlayerDespawned);
|
Net->OnEntityDespawned.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetEntityDespawned);
|
||||||
ZeusNet->OnPlayerStateUpdate.RemoveDynamic(this, &UZeusWorldSubsystem::HandlePlayerStateUpdate);
|
Net->OnEntityDeltaApplied.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetEntityDelta);
|
||||||
|
Net->OnSelfEntityAssigned.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetSelfEntityAssigned);
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoteEntities.Reset();
|
RemoteEntities.Reset();
|
||||||
@@ -83,6 +92,25 @@ void UZeusWorldSubsystem::RegisterLocalEntity(const int64 EntityId, AActor* Loca
|
|||||||
}
|
}
|
||||||
|
|
||||||
LocalEntityId = EntityId;
|
LocalEntityId = EntityId;
|
||||||
|
|
||||||
|
// Caso de borda: se o ENT_SPAWN do proprio chegou antes do self-entity, ja
|
||||||
|
// existe um proxy-fantasma com esta chave. Destroi antes de registrar o pawn
|
||||||
|
// real, senao o RemoteEntities.Add sobrescreve a entry e o fantasma fica
|
||||||
|
// orfao no mundo (sem ninguem pra despawna-lo).
|
||||||
|
if (TWeakObjectPtr<AActor>* Existing = RemoteEntities.Find(EntityId))
|
||||||
|
{
|
||||||
|
if (AActor* Ghost = Existing->Get())
|
||||||
|
{
|
||||||
|
if (Ghost != LocalActor && Ghost->IsA(AZeusPlayerProxy::StaticClass()))
|
||||||
|
{
|
||||||
|
HandlePlayerDespawned(EntityId);
|
||||||
|
UE_LOG(LogZMMO, Log,
|
||||||
|
TEXT("ZeusWorldSubsystem: ghost proxy do proprio limpo no RegisterLocalEntity EntityId=%lld"),
|
||||||
|
EntityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RemoteEntities.Add(EntityId, TWeakObjectPtr<AActor>(LocalActor));
|
RemoteEntities.Add(EntityId, TWeakObjectPtr<AActor>(LocalActor));
|
||||||
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: local entity registered EntityId=%lld actor=%s"),
|
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: local entity registered EntityId=%lld actor=%s"),
|
||||||
EntityId, *GetNameSafe(LocalActor));
|
EntityId, *GetNameSafe(LocalActor));
|
||||||
@@ -363,6 +391,97 @@ void UZeusWorldSubsystem::HandlePlayerStateUpdate(const int64 EntityId, const in
|
|||||||
AsEntity->ApplyEntitySnapshot(Snapshot);
|
AsEntity->ApplyEntitySnapshot(Snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UZeusWorldSubsystem::OnNetEntitySpawned(int64 EntityId, int32 /*Kind*/, FVector PosCm, float YawDeg)
|
||||||
|
{
|
||||||
|
// O sistema novo nao manda bIsLocal: derivamos do LocalEntityId (setado por
|
||||||
|
// ENT_SELF). Se ainda for 0 e este ENT_SPAWN for do proprio char, o
|
||||||
|
// OnNetSelfEntityAssigned destruira o fantasma quando o ENT_SELF chegar.
|
||||||
|
const bool bIsLocal = (LocalEntityId != 0 && EntityId == LocalEntityId);
|
||||||
|
HandlePlayerSpawned(EntityId, bIsLocal, PosCm, YawDeg, /*ServerTimeMs=*/0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZeusWorldSubsystem::OnNetEntityDespawned(int64 EntityId, int32 /*Reason*/)
|
||||||
|
{
|
||||||
|
// NUNCA despawnar o proprio char local via rede (ex: ENT_DESPAWN do proprio
|
||||||
|
// disparado por timeout do server). Destruir o pawn local quebraria o jogo
|
||||||
|
// do dono. O proprio so' sai quando o cliente realmente desconecta.
|
||||||
|
if (LocalEntityId != 0 && EntityId == LocalEntityId)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HandlePlayerDespawned(EntityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZeusWorldSubsystem::OnNetEntityDelta(int64 EntityId, FVector PosCm, FVector VelCmS,
|
||||||
|
float YawDeg, bool bGrounded, int64 ServerTimeMs)
|
||||||
|
{
|
||||||
|
if (EntityId == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (LocalEntityId != 0 && EntityId == LocalEntityId)
|
||||||
|
{
|
||||||
|
return; // movimento do proprio char e' local; ignora snapshot autoritativo
|
||||||
|
}
|
||||||
|
|
||||||
|
const TWeakObjectPtr<AActor>* Entry = RemoteEntities.Find(EntityId);
|
||||||
|
if (!Entry)
|
||||||
|
{
|
||||||
|
return; // spawn chega em breve via ENT_SPAWN
|
||||||
|
}
|
||||||
|
AActor* Actor = Entry->Get();
|
||||||
|
if (!Actor)
|
||||||
|
{
|
||||||
|
RemoteEntities.Remove(EntityId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IZeusEntityInterface* AsEntity = Cast<IZeusEntityInterface>(Actor);
|
||||||
|
if (!AsEntity)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FZeusEntitySnapshot Snapshot;
|
||||||
|
Snapshot.EntityId = EntityId;
|
||||||
|
Snapshot.EntityType = AsEntity->GetZeusEntityType();
|
||||||
|
Snapshot.PositionCm = PosCm;
|
||||||
|
Snapshot.YawDeg = YawDeg;
|
||||||
|
// V1-REPL-FULL (2026-06-12): velocidade + grounded + serverTimeMs -> o proxy
|
||||||
|
// anima (AnimBP ShouldMove via Acceleration!=0 derivada da vel), cola no chao
|
||||||
|
// (MOVE_Walking quando grounded) e interpola com a timeline autoritativa.
|
||||||
|
Snapshot.VelocityCmS = VelCmS;
|
||||||
|
Snapshot.bGrounded = bGrounded;
|
||||||
|
Snapshot.ServerTimeMs = ServerTimeMs;
|
||||||
|
AsEntity->ApplyEntitySnapshot(Snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZeusWorldSubsystem::OnNetSelfEntityAssigned(int64 EntityId)
|
||||||
|
{
|
||||||
|
if (EntityId == 0 || LocalEntityId == EntityId)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LocalEntityId = EntityId;
|
||||||
|
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: self entity assigned EntityId=%lld"), EntityId);
|
||||||
|
|
||||||
|
// Re-filtragem tardia: se o ENT_SPAWN do proprio chegou ANTES do ENT_SELF,
|
||||||
|
// ja existe um proxy-fantasma do proprio char. Destroi agora -- mas NUNCA o
|
||||||
|
// pawn local real (o RegisterLocalEntity do AZeusCharacter registra o pawn
|
||||||
|
// com a mesma chave; so destruimos se a entry for um AZeusPlayerProxy).
|
||||||
|
if (TWeakObjectPtr<AActor>* Entry = RemoteEntities.Find(EntityId))
|
||||||
|
{
|
||||||
|
if (AActor* Ghost = Entry->Get())
|
||||||
|
{
|
||||||
|
if (Ghost->IsA(AZeusPlayerProxy::StaticClass()))
|
||||||
|
{
|
||||||
|
HandlePlayerDespawned(EntityId);
|
||||||
|
UE_LOG(LogZMMO, Log,
|
||||||
|
TEXT("ZeusWorldSubsystem: ghost proxy do proprio destruido EntityId=%lld"), EntityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UClass* UZeusWorldSubsystem::ResolveActorClass(const EZeusEntityType EntityType) const
|
UClass* UZeusWorldSubsystem::ResolveActorClass(const EZeusEntityType EntityType) const
|
||||||
{
|
{
|
||||||
if (const TSubclassOf<AActor>* Found = RemoteEntityClasses.Find(EntityType))
|
if (const TSubclassOf<AActor>* Found = RemoteEntityClasses.Find(EntityType))
|
||||||
@@ -389,3 +508,18 @@ UZeusNetworkSubsystem* UZeusWorldSubsystem::ResolveZeusNetworkSubsystem() const
|
|||||||
}
|
}
|
||||||
return GI->GetSubsystem<UZeusNetworkSubsystem>();
|
return GI->GetSubsystem<UZeusNetworkSubsystem>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UZeusNetworkingClientSubsystem* UZeusWorldSubsystem::ResolveNetClientSubsystem() const
|
||||||
|
{
|
||||||
|
const UWorld* World = GetWorld();
|
||||||
|
if (!World)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
UGameInstance* GI = World->GetGameInstance();
|
||||||
|
if (!GI)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return GI->GetSubsystem<UZeusNetworkingClientSubsystem>();
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class AActor;
|
|||||||
class AZeusCharacter;
|
class AZeusCharacter;
|
||||||
class AZeusPlayerProxy;
|
class AZeusPlayerProxy;
|
||||||
class UZeusNetworkSubsystem;
|
class UZeusNetworkSubsystem;
|
||||||
|
class UZeusNetworkingClientSubsystem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UZeusWorldSubsystem
|
* UZeusWorldSubsystem
|
||||||
@@ -80,8 +81,24 @@ private:
|
|||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandlePlayerStateUpdate(int64 EntityId, int32 InputSeq, FVector PosCm, FVector VelCmS, bool bGrounded, int64 ServerTimeMs);
|
void HandlePlayerStateUpdate(int64 EntityId, int32 InputSeq, FVector PosCm, FVector VelCmS, bool bGrounded, int64 ServerTimeMs);
|
||||||
|
|
||||||
|
// Sistema de rede novo (canonico) -- recebe spawn/despawn/delta + self-entity.
|
||||||
|
// Adaptam a assinatura nova para os handlers de spawn/despawn/delta acima.
|
||||||
|
UFUNCTION()
|
||||||
|
void OnNetEntitySpawned(int64 EntityId, int32 Kind, FVector PosCm, float YawDeg);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnNetEntityDespawned(int64 EntityId, int32 Reason);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnNetEntityDelta(int64 EntityId, FVector PosCm, FVector VelCmS, float YawDeg,
|
||||||
|
bool bGrounded, int64 ServerTimeMs);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnNetSelfEntityAssigned(int64 EntityId);
|
||||||
|
|
||||||
UClass* ResolveActorClass(EZeusEntityType EntityType) const;
|
UClass* ResolveActorClass(EZeusEntityType EntityType) const;
|
||||||
UZeusNetworkSubsystem* ResolveZeusNetworkSubsystem() const;
|
UZeusNetworkSubsystem* ResolveZeusNetworkSubsystem() const;
|
||||||
|
UZeusNetworkingClientSubsystem* ResolveNetClientSubsystem() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registry de proxies remotos. Chave = `EntityId` autoritativo do servidor.
|
* Registry de proxies remotos. Chave = `EntityId` autoritativo do servidor.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "WireHelpers.h"
|
#include "WireHelpers.h"
|
||||||
#include "ZeusCharServerSubsystem.h"
|
#include "ZeusCharServerSubsystem.h"
|
||||||
#include "ZeusNetworkSubsystem.h"
|
#include "ZeusNetworkSubsystem.h"
|
||||||
|
#include "ZeusNetworkingClientSubsystem.h" // B4: V1 ConnectWithTicket
|
||||||
#include "CommonTextBlock.h"
|
#include "CommonTextBlock.h"
|
||||||
#include "Components/PanelWidget.h"
|
#include "Components/PanelWidget.h"
|
||||||
#include "Components/WidgetSwitcher.h"
|
#include "Components/WidgetSwitcher.h"
|
||||||
@@ -384,13 +385,29 @@ void UUIUserLobbyScreen_Base::HandleCharSelectOk(const TArray<uint8>& Payload)
|
|||||||
// `S_CONNECT_OK` (sucesso) ou `S_CONNECT_REJECT` (ticket invalido/expirado).
|
// `S_CONNECT_OK` (sucesso) ou `S_CONNECT_REJECT` (ticket invalido/expirado).
|
||||||
// Disparamos PRIMEIRO o connect (nao-bloqueante), depois o OpenLevel —
|
// Disparamos PRIMEIRO o connect (nao-bloqueante), depois o OpenLevel —
|
||||||
// ZeusNetworkSubsystem e per-GameInstance e persiste cross-travel.
|
// ZeusNetworkSubsystem e per-GameInstance e persiste cross-travel.
|
||||||
if (UZeusNetworkSubsystem* ZeusNet = GI->GetSubsystem<UZeusNetworkSubsystem>())
|
// B4 (2026-06-11): se V1 canonico, roteia ConnectWithTicket pro V1 subsystem.
|
||||||
|
// V1 envia CONN_HELLO_CLIENT (6000) com handoffTicket embutido — server
|
||||||
|
// valida via Valkey GETDEL antes do CHALLENGE (HandshakeProcessor::OnHelloClient).
|
||||||
|
const UZeusNetworkingClientSubsystem* V1Cdo = GetDefault<UZeusNetworkingClientSubsystem>();
|
||||||
|
const bool bUseV1 = V1Cdo && V1Cdo->bUseZeusNetworkingV1;
|
||||||
|
if (bUseV1)
|
||||||
|
{
|
||||||
|
if (UZeusNetworkingClientSubsystem* V1 = GI->GetSubsystem<UZeusNetworkingClientSubsystem>())
|
||||||
|
{
|
||||||
|
V1->ConnectWithTicket(GwHost, GwPort, HandoffToken);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Error, TEXT("Lobby: V1 subsystem ausente — handoff abortado"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (UZeusNetworkSubsystem* ZeusNet = GI->GetSubsystem<UZeusNetworkSubsystem>())
|
||||||
{
|
{
|
||||||
ZeusNet->ConnectToZeusServerWithTicket(GwHost, GwPort, HandoffToken);
|
ZeusNet->ConnectToZeusServerWithTicket(GwHost, GwPort, HandoffToken);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UE_LOG(LogZMMO, Error, TEXT("Lobby: ZeusNetworkSubsystem ausente — handoff abortado"));
|
UE_LOG(LogZMMO, Error, TEXT("Lobby: nenhum ZeusNetworkSubsystem disponivel — handoff abortado"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fase 4: lookup mapId no DT_Maps e dispara OpenLevel pro level certo.
|
// Fase 4: lookup mapId no DT_Maps e dispara OpenLevel pro level certo.
|
||||||
|
|||||||
Reference in New Issue
Block a user