ZMMO: migração para ZeusNetworking V1 canônico + fix overshoot do proxy
- ZeusCharacter/ZeusWorldSubsystem passam a usar UZeusNetworkingClientSubsystem: ENT_SELF (entidade própria), INPUT 6077 via EmitInput, spawn/despawn/delta rebindados pro subsystem novo (legacy vira fallback). - ZeusPlayerProxy consome velocidade/grounded/serverTimeMs (delta 0x02): anima, vira na direção do movimento e cola no chão (MOVE_Walking). - Fix overshoot/inércia: FlushInputAxisToServer considera velocidade residual (braking) e envia a 30Hz até o dono parar de fato; o proxy desacelera suave, sem extrapolar ~1m além nem snap-back. - Char-select -> ConnectWithTicket (handoff ticket) quando bUseZeusNetworkingV1. Inclui logs [InputDbg] (ZeusCharacter) e DEBUG-PROXY (ZeusPlayerProxy) mantidos para testes posteriores de input/jitter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
#include "ZeusPlayerProxy.h"
|
||||
#include "ZeusPlayerState.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
#include "ZeusNetworkingClientSubsystem.h"
|
||||
|
||||
void UZeusWorldSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
@@ -32,42 +33,50 @@ void UZeusWorldSubsystem::OnWorldBeginPlay(UWorld& 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);
|
||||
ZeusNet->OnPlayerDespawned.AddDynamic(this, &UZeusWorldSubsystem::HandlePlayerDespawned);
|
||||
ZeusNet->OnPlayerStateUpdate.AddDynamic(this, &UZeusWorldSubsystem::HandlePlayerStateUpdate);
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem bound to ZeusNetworkSubsystem delegates."));
|
||||
Net->OnEntitySpawned.AddDynamic(this, &UZeusWorldSubsystem::OnNetEntitySpawned);
|
||||
Net->OnEntityDespawned.AddDynamic(this, &UZeusWorldSubsystem::OnNetEntityDespawned);
|
||||
Net->OnEntityDeltaApplied.AddDynamic(this, &UZeusWorldSubsystem::OnNetEntityDelta);
|
||||
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
|
||||
// enquanto o cliente novo ainda esta em OpenLevel. UZeusNetworkSubsystem
|
||||
// cacheia em PendingRemoteSpawnsByEntity. Aplicamos aqui — mundo ja
|
||||
// esta pronto (GameMode ativo, WorldPartition cells inicializadas).
|
||||
// O subsystem de rede e' per-GameInstance e sobrevive ao OpenLevel, entao
|
||||
// o self-entity e os ENT_SPAWN podem ter chegado ANTES deste bind. Puxa o
|
||||
// self cacheado + faz replay dos proxies ja conhecidos (anti-race).
|
||||
if (const int64 CachedSelf = Net->GetLocalEntityId())
|
||||
{
|
||||
OnNetSelfEntityAssigned(CachedSelf);
|
||||
}
|
||||
int32 ReplayCount = 0;
|
||||
ZeusNet->ForEachPendingRemoteSpawn(
|
||||
[this, &ReplayCount](const int64 EntityId, const FVector PosCm, const float YawDeg, const int64 ServerTimeMs)
|
||||
Net->ForEachRemoteEntity(
|
||||
[this, &ReplayCount](int64 EntityId, FVector PosCm, float YawDeg)
|
||||
{
|
||||
HandlePlayerSpawned(EntityId, /*bIsLocal=*/false, PosCm, YawDeg, ServerTimeMs);
|
||||
OnNetEntitySpawned(EntityId, /*Kind=*/1 /*Player*/, PosCm, YawDeg);
|
||||
++ReplayCount;
|
||||
});
|
||||
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
|
||||
{
|
||||
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()
|
||||
{
|
||||
if (UZeusNetworkSubsystem* ZeusNet = ResolveZeusNetworkSubsystem())
|
||||
if (UZeusNetworkingClientSubsystem* Net = ResolveNetClientSubsystem())
|
||||
{
|
||||
ZeusNet->OnPlayerSpawned.RemoveDynamic(this, &UZeusWorldSubsystem::HandlePlayerSpawned);
|
||||
ZeusNet->OnPlayerDespawned.RemoveDynamic(this, &UZeusWorldSubsystem::HandlePlayerDespawned);
|
||||
ZeusNet->OnPlayerStateUpdate.RemoveDynamic(this, &UZeusWorldSubsystem::HandlePlayerStateUpdate);
|
||||
Net->OnEntitySpawned.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetEntitySpawned);
|
||||
Net->OnEntityDespawned.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetEntityDespawned);
|
||||
Net->OnEntityDeltaApplied.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetEntityDelta);
|
||||
Net->OnSelfEntityAssigned.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetSelfEntityAssigned);
|
||||
}
|
||||
|
||||
RemoteEntities.Reset();
|
||||
@@ -83,6 +92,25 @@ void UZeusWorldSubsystem::RegisterLocalEntity(const int64 EntityId, AActor* Loca
|
||||
}
|
||||
|
||||
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));
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: local entity registered EntityId=%lld actor=%s"),
|
||||
EntityId, *GetNameSafe(LocalActor));
|
||||
@@ -363,6 +391,97 @@ void UZeusWorldSubsystem::HandlePlayerStateUpdate(const int64 EntityId, const in
|
||||
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
|
||||
{
|
||||
if (const TSubclassOf<AActor>* Found = RemoteEntityClasses.Find(EntityType))
|
||||
@@ -389,3 +508,18 @@ UZeusNetworkSubsystem* UZeusWorldSubsystem::ResolveZeusNetworkSubsystem() const
|
||||
}
|
||||
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>();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user