refactor: prefixo ZMMO -> Zeus em classes/módulos C++ do cliente
- Renomeia ~50 classes UCLASS/USTRUCT/UENUM/UINTERFACE pra Zeus* (AZeusGameMode, AZeusCharacter, UZeusGameInstance, IZeusEntityInterface, UZeusWorldSubsystem, FZeusEntitySnapshot, etc.) - Encurta AZMMOPlayerCharacter -> AZeusCharacter - Renomeia módulos satélite ZMMOAttributes -> ZeusAttributes e ZMMOJobs -> ZeusJobs (pasta + .Build.cs + .uproject) - Mantém módulo principal ZMMO (renomeia depois quando jogo ganhar nome) - DefaultEngine.ini: ActiveClassRedirects ZMMOX -> ZeusX (preserva BPs/assets) - DefaultGame.ini: sections atualizadas pra ZeusThemeSubsystem/ZeusPlayerState - Category="ZMMO|..." -> "Zeus|..." em UPROPERTYs - Preserva LogZMMO, ZMMO_API, /Script/ZMMO., /Game/ZMMO/, classes Target Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
309
Source/ZMMO/Game/Network/ZeusWorldSubsystem.cpp
Normal file
309
Source/ZMMO/Game/Network/ZeusWorldSubsystem.cpp
Normal file
@@ -0,0 +1,309 @@
|
||||
#include "ZeusWorldSubsystem.h"
|
||||
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/World.h"
|
||||
#include "ZMMO.h"
|
||||
#include "ZeusEntity.h"
|
||||
#include "ZeusEntityInterface.h"
|
||||
#include "ZeusPlayerProxy.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
|
||||
void UZeusWorldSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
|
||||
if (RemoteEntityClasses.IsEmpty())
|
||||
{
|
||||
RemoteEntityClasses.Add(EZeusEntityType::Player, AZeusPlayerProxy::StaticClass());
|
||||
RemoteEntityClasses.Add(EZeusEntityType::Mob, AZeusEntity::StaticClass());
|
||||
RemoteEntityClasses.Add(EZeusEntityType::NPC, AZeusEntity::StaticClass());
|
||||
RemoteEntityClasses.Add(EZeusEntityType::Object, AZeusEntity::StaticClass());
|
||||
}
|
||||
// Bind + replay vivem em OnWorldBeginPlay (mundo pronto). Initialize roda
|
||||
// MUITO cedo no pipeline de LoadMap — antes do GameMode ser instanciado
|
||||
// e antes do WorldPartition terminar de carregar cells. Spawns dinamicos
|
||||
// feitos aqui podem ser perdidos durante a fase de setup posterior.
|
||||
}
|
||||
|
||||
void UZeusWorldSubsystem::OnWorldBeginPlay(UWorld& InWorld)
|
||||
{
|
||||
Super::OnWorldBeginPlay(InWorld);
|
||||
|
||||
if (UZeusNetworkSubsystem* ZeusNet = ResolveZeusNetworkSubsystem())
|
||||
{
|
||||
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."));
|
||||
|
||||
// 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).
|
||||
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);
|
||||
++ReplayCount;
|
||||
});
|
||||
if (ReplayCount > 0)
|
||||
{
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: replay de %d proxy(ies) cacheados."), ReplayCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning, TEXT("ZeusWorldSubsystem: ZeusNetworkSubsystem not found at OnWorldBeginPlay."));
|
||||
}
|
||||
}
|
||||
|
||||
void UZeusWorldSubsystem::Deinitialize()
|
||||
{
|
||||
if (UZeusNetworkSubsystem* ZeusNet = ResolveZeusNetworkSubsystem())
|
||||
{
|
||||
ZeusNet->OnPlayerSpawned.RemoveDynamic(this, &UZeusWorldSubsystem::HandlePlayerSpawned);
|
||||
ZeusNet->OnPlayerDespawned.RemoveDynamic(this, &UZeusWorldSubsystem::HandlePlayerDespawned);
|
||||
ZeusNet->OnPlayerStateUpdate.RemoveDynamic(this, &UZeusWorldSubsystem::HandlePlayerStateUpdate);
|
||||
}
|
||||
|
||||
RemoteEntities.Reset();
|
||||
LocalEntityId = 0;
|
||||
Super::Deinitialize();
|
||||
}
|
||||
|
||||
void UZeusWorldSubsystem::RegisterLocalEntity(const int64 EntityId, AActor* LocalActor)
|
||||
{
|
||||
if (EntityId == 0 || LocalActor == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LocalEntityId = EntityId;
|
||||
RemoteEntities.Add(EntityId, TWeakObjectPtr<AActor>(LocalActor));
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: local entity registered EntityId=%lld actor=%s"),
|
||||
EntityId, *GetNameSafe(LocalActor));
|
||||
}
|
||||
|
||||
void UZeusWorldSubsystem::HandlePlayerSpawned(const int64 EntityId, const bool bIsLocal,
|
||||
const FVector PosCm, const float YawDeg, const int64 ServerTimeMs)
|
||||
{
|
||||
if (EntityId == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (bIsLocal)
|
||||
{
|
||||
// O proprio AZeusCharacter cuida da sua identidade quando recebe o
|
||||
// delegate por outro caminho. Aqui apenas memorizamos para filtrar
|
||||
// snapshots locais (cliente solto, sem reconciliacao em V0).
|
||||
LocalEntityId = EntityId;
|
||||
return;
|
||||
}
|
||||
|
||||
if (RemoteEntities.Contains(EntityId))
|
||||
{
|
||||
// Ja existe um proxy: tenta reactivar e re-snap. Se o weak ptr ficou
|
||||
// stale (actor coletado pelo GC, stream-out do World Partition, etc.),
|
||||
// remove a entry orfa e cai no caminho de spawn novo — evita SPAWN
|
||||
// silenciosamente ignorado depois de oscilacoes na AOI.
|
||||
if (AActor* Existing = RemoteEntities[EntityId].Get())
|
||||
{
|
||||
if (IZeusEntityInterface* AsEntity = Cast<IZeusEntityInterface>(Existing))
|
||||
{
|
||||
AsEntity->SetEntityRelevant(true);
|
||||
}
|
||||
Existing->SetActorLocationAndRotation(PosCm, FRotator(0.0f, YawDeg, 0.0f));
|
||||
UE_LOG(LogZMMO, Verbose, TEXT("ZeusWorldSubsystem: re-snap EntityId=%d"), EntityId);
|
||||
return;
|
||||
}
|
||||
// weak ptr stale — limpa entry e cai pro spawn novo abaixo.
|
||||
RemoteEntities.Remove(EntityId);
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: stale entry para EntityId=%d, respawnando"), EntityId);
|
||||
}
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UClass* ProxyClass = ResolveActorClass(EZeusEntityType::Player);
|
||||
if (!ProxyClass)
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning, TEXT("ZeusWorldSubsystem: no class registered for Player; spawn aborted."));
|
||||
return;
|
||||
}
|
||||
|
||||
FActorSpawnParameters Params;
|
||||
Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
const FRotator SpawnRot(0.0f, YawDeg, 0.0f);
|
||||
AActor* SpawnedActor = World->SpawnActor<AActor>(ProxyClass, PosCm, SpawnRot, Params);
|
||||
if (!SpawnedActor)
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning, TEXT("ZeusWorldSubsystem: SpawnActor failed for EntityId=%d"), EntityId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IZeusEntityInterface* AsEntity = Cast<IZeusEntityInterface>(SpawnedActor))
|
||||
{
|
||||
// Inject identity via dedicated setter when the spawned actor exposes one.
|
||||
if (AZeusPlayerProxy* Proxy = Cast<AZeusPlayerProxy>(SpawnedActor))
|
||||
{
|
||||
Proxy->SetZeusIdentity(EntityId, EZeusEntityType::Player);
|
||||
}
|
||||
else if (AZeusEntity* Entity = Cast<AZeusEntity>(SpawnedActor))
|
||||
{
|
||||
Entity->SetZeusIdentity(EntityId, EZeusEntityType::Player);
|
||||
}
|
||||
AsEntity->SetEntityRelevant(true);
|
||||
}
|
||||
|
||||
RemoteEntities.Add(EntityId, TWeakObjectPtr<AActor>(SpawnedActor));
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: spawned remote EntityId=%d at (%s) yaw=%.1f t=%lld"),
|
||||
EntityId, *PosCm.ToString(), YawDeg, ServerTimeMs);
|
||||
}
|
||||
|
||||
void UZeusWorldSubsystem::HandlePlayerDespawned(const int64 EntityId)
|
||||
{
|
||||
const TWeakObjectPtr<AActor>* Entry = RemoteEntities.Find(EntityId);
|
||||
if (!Entry)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Destruir o actor pra valer + remover do mapa. Antes, despawn apenas
|
||||
// fazia SetActorHiddenInGame(true) via SetEntityRelevant(false), o que
|
||||
// deixava o entry no mapa apontando pra um actor escondido. SPAWN
|
||||
// subsequente do mesmo entityId era detectado como "duplicate" e nada
|
||||
// acontecia — pawn ficava invisivel ate' o weak ptr ser GC'd. Bug
|
||||
// confirmado nos logs do AOI ao oscilar entrada/saida da ZI/ZD.
|
||||
if (AActor* Actor = Entry->Get())
|
||||
{
|
||||
if (IZeusEntityInterface* AsEntity = Cast<IZeusEntityInterface>(Actor))
|
||||
{
|
||||
AsEntity->SetEntityRelevant(false);
|
||||
}
|
||||
Actor->Destroy();
|
||||
}
|
||||
RemoteEntities.Remove(EntityId);
|
||||
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: despawn EntityId=%d (destroyed)"), EntityId);
|
||||
}
|
||||
|
||||
void UZeusWorldSubsystem::HandlePlayerStateUpdate(const int64 EntityId, const int32 InputSeq,
|
||||
const FVector PosCm, const FVector VelCmS, const bool bGrounded, const int64 ServerTimeMs)
|
||||
{
|
||||
if (EntityId == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (LocalEntityId != 0 && static_cast<int64>(EntityId) == LocalEntityId)
|
||||
{
|
||||
// Cliente local solto: ignoramos snapshots autoritativos para o nosso
|
||||
// proprio personagem em V0 (ADR 0038). Reconciliacao opcional pode ser
|
||||
// activada por flag em `AZeusCharacter` quando colisao de objetos
|
||||
// chegar.
|
||||
return;
|
||||
}
|
||||
|
||||
const TWeakObjectPtr<AActor>* Entry = RemoteEntities.Find(EntityId);
|
||||
if (!Entry)
|
||||
{
|
||||
// Snapshot antes de spawn: o spawn vira em `OnPlayerSpawned` em breve.
|
||||
return;
|
||||
}
|
||||
|
||||
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.LastProcessedInputSeq = InputSeq;
|
||||
Snapshot.PositionCm = PosCm;
|
||||
Snapshot.VelocityCmS = VelCmS;
|
||||
|
||||
// Etapa 7 (server-side networking refactor): yaw agora chega no
|
||||
// snapshot quantizado v3. Buscamos via TryGetLastPlayerStateExtended
|
||||
// (cache local do plugin). Se PSF_HasYaw nao estiver set ou se o cache
|
||||
// nao tiver entrada, caimos no fallback antigo (derivar do XY da
|
||||
// velocidade ou manter rotacao do ator).
|
||||
bool bYawFromSnapshot = false;
|
||||
if (UZeusNetworkSubsystem* ZeusNet = ResolveZeusNetworkSubsystem())
|
||||
{
|
||||
int32 CachedInputSeq = 0;
|
||||
FVector CachedPos = FVector::ZeroVector;
|
||||
FVector CachedVel = FVector::ZeroVector;
|
||||
float CachedYawDeg = 0.f;
|
||||
int32 CachedFlags = 0;
|
||||
int64 CachedServerTimeMs = 0;
|
||||
if (ZeusNet->TryGetLastPlayerStateExtended(
|
||||
EntityId, CachedInputSeq, CachedPos, CachedVel,
|
||||
CachedYawDeg, CachedFlags, CachedServerTimeMs))
|
||||
{
|
||||
constexpr int32 PSF_HasYaw = 1 << 5;
|
||||
if ((CachedFlags & PSF_HasYaw) != 0)
|
||||
{
|
||||
Snapshot.YawDeg = CachedYawDeg;
|
||||
bYawFromSnapshot = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!bYawFromSnapshot)
|
||||
{
|
||||
// Fallback: deriva yaw do XY da velocidade quando significativo;
|
||||
// caso contrario mantem rotacao do ator (comportamento pre-Etapa 7).
|
||||
if (!FVector(VelCmS.X, VelCmS.Y, 0.0f).IsNearlyZero(1.0f))
|
||||
{
|
||||
Snapshot.YawDeg = FMath::RadiansToDegrees(FMath::Atan2(VelCmS.Y, VelCmS.X));
|
||||
}
|
||||
else
|
||||
{
|
||||
Snapshot.YawDeg = Actor->GetActorRotation().Yaw;
|
||||
}
|
||||
}
|
||||
Snapshot.bGrounded = bGrounded;
|
||||
Snapshot.ServerTimeMs = ServerTimeMs;
|
||||
|
||||
AsEntity->ApplyEntitySnapshot(Snapshot);
|
||||
}
|
||||
|
||||
UClass* UZeusWorldSubsystem::ResolveActorClass(const EZeusEntityType EntityType) const
|
||||
{
|
||||
if (const TSubclassOf<AActor>* Found = RemoteEntityClasses.Find(EntityType))
|
||||
{
|
||||
if (Found->Get())
|
||||
{
|
||||
return Found->Get();
|
||||
}
|
||||
}
|
||||
return AZeusEntity::StaticClass();
|
||||
}
|
||||
|
||||
UZeusNetworkSubsystem* UZeusWorldSubsystem::ResolveZeusNetworkSubsystem() const
|
||||
{
|
||||
const UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
UGameInstance* GI = World->GetGameInstance();
|
||||
if (!GI)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return GI->GetSubsystem<UZeusNetworkSubsystem>();
|
||||
}
|
||||
Reference in New Issue
Block a user