fix(client): catch-up de presenca + wire helpers compartilhados
ZMMOWorldSubsystem: - Bind dos delegates + replay do cache de proxies remotos movidos de Initialize pra OnWorldBeginPlay. Initialize roda muito cedo no pipeline de LoadMap (antes do GameMode ser instanciado e do WorldPartition terminar de carregar cells) — atores dinamicos spawnados ali eram perdidos durante o setup posterior do mundo. - Replay agora acontece quando o World REALMENTE comeca (GameMode ativo, cells inicializadas) — proxies remotos persistem. WireHelpers.h (novo): - Helpers de leitura/escrita binarios LE/UTF-8 (ReadU8/U16/U32/U64/ Float/StringUtf8/Uuid16 + Write*) como inline em namespace ZMMOWire. Substitui anonymous namespaces duplicados que existiam em 3 telas do FrontEnd. UBT Adaptive Build agrupava os .cpp em unity files diferentes; quando reorganizava as unities, dois arquivos caiam na mesma TU e os anonymous namespaces colidiam (ODR violation — "funcao ja tem corpo"). - inline em header tem semantica de "uma definicao por programa", resolvendo a colisao independente de como o UBT organize unities. UIServerSelectScreen_Base, UIUserLobbyScreen_Base, UICharacterCreatePage_Base: - Removidas as definicoes duplicadas, agora usam ZMMOWire::* via using-declarations. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,15 @@ void UZMMOWorldSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
RemoteEntityClasses.Add(EZMMOEntityType::NPC, AZMMOEntity::StaticClass());
|
||||
RemoteEntityClasses.Add(EZMMOEntityType::Object, AZMMOEntity::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 UZMMOWorldSubsystem::OnWorldBeginPlay(UWorld& InWorld)
|
||||
{
|
||||
Super::OnWorldBeginPlay(InWorld);
|
||||
|
||||
if (UZeusNetworkSubsystem* ZeusNet = ResolveZeusNetworkSubsystem())
|
||||
{
|
||||
@@ -26,10 +35,26 @@ void UZMMOWorldSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
ZeusNet->OnPlayerDespawned.AddDynamic(this, &UZMMOWorldSubsystem::HandlePlayerDespawned);
|
||||
ZeusNet->OnPlayerStateUpdate.AddDynamic(this, &UZMMOWorldSubsystem::HandlePlayerStateUpdate);
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem 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 int32 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("ZMMOWorldSubsystem: replay de %d proxy(ies) cacheados."), ReplayCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning, TEXT("ZMMOWorldSubsystem: ZeusNetworkSubsystem not found at Initialize."));
|
||||
UE_LOG(LogZMMO, Warning, TEXT("ZMMOWorldSubsystem: ZeusNetworkSubsystem not found at OnWorldBeginPlay."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ class ZMMO_API UZMMOWorldSubsystem : public UWorldSubsystem
|
||||
|
||||
public:
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void OnWorldBeginPlay(UWorld& InWorld) override;
|
||||
virtual void Deinitialize() override;
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "ZMMO.h"
|
||||
#include "CharServerOpcodes.h"
|
||||
#include "UIFrontEndFlowSubsystem.h"
|
||||
#include "WireHelpers.h"
|
||||
#include "ZeusCharServerSubsystem.h"
|
||||
#include "Components/EditableTextBox.h"
|
||||
#include "Components/ComboBoxString.h"
|
||||
@@ -10,48 +11,11 @@
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "UI/Widgets/UIButton_Base.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool WriteUuid16(TArray<uint8>& Buf, const FString& Uuid)
|
||||
{
|
||||
FString Hex = Uuid.Replace(TEXT("-"), TEXT(""));
|
||||
if (Hex.Len() != 32) return false;
|
||||
for (int32 i = 0; i < 16; ++i)
|
||||
{
|
||||
TCHAR hi = Hex[i * 2];
|
||||
TCHAR lo = Hex[i * 2 + 1];
|
||||
auto HexVal = [](TCHAR c) -> int32 {
|
||||
if (c >= TEXT('0') && c <= TEXT('9')) return c - TEXT('0');
|
||||
if (c >= TEXT('a') && c <= TEXT('f')) return c - TEXT('a') + 10;
|
||||
if (c >= TEXT('A') && c <= TEXT('F')) return c - TEXT('A') + 10;
|
||||
return -1;
|
||||
};
|
||||
int32 H = HexVal(hi);
|
||||
int32 L = HexVal(lo);
|
||||
if (H < 0 || L < 0) return false;
|
||||
Buf.Add(static_cast<uint8>((H << 4) | L));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WriteUInt8(TArray<uint8>& Buf, uint8 v) { Buf.Add(v); }
|
||||
void WriteUInt16(TArray<uint8>& Buf, uint16 v)
|
||||
{
|
||||
Buf.Add(static_cast<uint8>(v & 0xFF));
|
||||
Buf.Add(static_cast<uint8>((v >> 8) & 0xFF));
|
||||
}
|
||||
void WriteUInt32(TArray<uint8>& Buf, uint32 v)
|
||||
{
|
||||
for (int32 i = 0; i < 4; ++i) Buf.Add(static_cast<uint8>((v >> (8 * i)) & 0xFF));
|
||||
}
|
||||
void WriteStringUtf8(TArray<uint8>& Buf, const FString& S)
|
||||
{
|
||||
FTCHARToUTF8 Conv(*S);
|
||||
const int32 Len = Conv.Length();
|
||||
WriteUInt16(Buf, static_cast<uint16>(Len));
|
||||
Buf.Append(reinterpret_cast<const uint8*>(Conv.Get()), Len);
|
||||
}
|
||||
}
|
||||
using ZMMOWire::WriteUuid16;
|
||||
using ZMMOWire::WriteUInt8;
|
||||
using ZMMOWire::WriteUInt16;
|
||||
using ZMMOWire::WriteUInt32;
|
||||
using ZMMOWire::WriteStringUtf8;
|
||||
|
||||
void UUICharacterCreatePage_Base::NativeConstruct()
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "CharServerOpcodes.h"
|
||||
#include "UIFrontEndFlowSubsystem.h"
|
||||
#include "UIServerCard_Base.h"
|
||||
#include "WireHelpers.h"
|
||||
#include "ZeusCharServerSubsystem.h"
|
||||
#include "CommonTextBlock.h"
|
||||
#include "Components/Border.h"
|
||||
@@ -15,6 +16,10 @@
|
||||
#include "Engine/DataTable.h"
|
||||
#include "UI/UIStyleRow.h"
|
||||
|
||||
using ZMMOWire::ReadU8;
|
||||
using ZMMOWire::ReadU16;
|
||||
using ZMMOWire::ReadStringUtf8;
|
||||
|
||||
namespace
|
||||
{
|
||||
FUIStyle ResolveServerSelectStyle(const UUserWidget* Widget, const FUIStyle& Fallback)
|
||||
@@ -43,40 +48,6 @@ namespace
|
||||
}
|
||||
return bHas ? DesignStyle : Fallback;
|
||||
}
|
||||
|
||||
// ---- Wire reader (LE) ----
|
||||
bool ReadU8(const TArray<uint8>& Buf, int32& Pos, uint8& Out)
|
||||
{
|
||||
if (Pos + 1 > Buf.Num()) return false;
|
||||
Out = Buf[Pos];
|
||||
Pos += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadU16(const TArray<uint8>& Buf, int32& Pos, uint16& Out)
|
||||
{
|
||||
if (Pos + 2 > Buf.Num()) return false;
|
||||
Out = static_cast<uint16>(Buf[Pos]) | (static_cast<uint16>(Buf[Pos + 1]) << 8);
|
||||
Pos += 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadStringUtf8(const TArray<uint8>& Buf, int32& Pos, FString& Out)
|
||||
{
|
||||
uint16 Len = 0;
|
||||
if (!ReadU16(Buf, Pos, Len)) return false;
|
||||
if (Pos + Len > Buf.Num()) return false;
|
||||
if (Len == 0)
|
||||
{
|
||||
Out.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
Out = FString(FUTF8ToTCHAR(reinterpret_cast<const ANSICHAR*>(Buf.GetData() + Pos), Len));
|
||||
}
|
||||
Pos += Len;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void UUIServerSelectScreen_Base::NativePreConstruct()
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "UIFrontEndFlowSubsystem.h"
|
||||
#include "UICharCard_Base.h"
|
||||
#include "UICharacterCreatePage_Base.h"
|
||||
#include "WireHelpers.h"
|
||||
#include "ZeusCharServerSubsystem.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
#include "CommonTextBlock.h"
|
||||
@@ -13,96 +14,14 @@
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "UI/Widgets/UIButton_Base.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool ReadU8(const TArray<uint8>& Buf, int32& Pos, uint8& Out)
|
||||
{
|
||||
if (Pos + 1 > Buf.Num()) return false;
|
||||
Out = Buf[Pos]; Pos += 1; return true;
|
||||
}
|
||||
bool ReadU16(const TArray<uint8>& Buf, int32& Pos, uint16& Out)
|
||||
{
|
||||
if (Pos + 2 > Buf.Num()) return false;
|
||||
Out = static_cast<uint16>(Buf[Pos]) | (static_cast<uint16>(Buf[Pos + 1]) << 8);
|
||||
Pos += 2; return true;
|
||||
}
|
||||
bool ReadU32(const TArray<uint8>& Buf, int32& Pos, uint32& Out)
|
||||
{
|
||||
if (Pos + 4 > Buf.Num()) return false;
|
||||
Out = static_cast<uint32>(Buf[Pos])
|
||||
| (static_cast<uint32>(Buf[Pos + 1]) << 8)
|
||||
| (static_cast<uint32>(Buf[Pos + 2]) << 16)
|
||||
| (static_cast<uint32>(Buf[Pos + 3]) << 24);
|
||||
Pos += 4; return true;
|
||||
}
|
||||
bool ReadU64(const TArray<uint8>& Buf, int32& Pos, uint64& Out)
|
||||
{
|
||||
if (Pos + 8 > Buf.Num()) return false;
|
||||
Out = 0;
|
||||
for (int i = 0; i < 8; ++i) Out |= (static_cast<uint64>(Buf[Pos + i]) << (8 * i));
|
||||
Pos += 8; return true;
|
||||
}
|
||||
bool ReadFloat(const TArray<uint8>& Buf, int32& Pos, float& Out)
|
||||
{
|
||||
uint32 raw;
|
||||
if (!ReadU32(Buf, Pos, raw)) return false;
|
||||
FMemory::Memcpy(&Out, &raw, sizeof(float));
|
||||
return true;
|
||||
}
|
||||
bool ReadStringUtf8(const TArray<uint8>& Buf, int32& Pos, FString& Out)
|
||||
{
|
||||
uint16 Len = 0;
|
||||
if (!ReadU16(Buf, Pos, Len)) return false;
|
||||
if (Pos + Len > Buf.Num()) return false;
|
||||
if (Len == 0) { Out.Reset(); return true; }
|
||||
Out = FString(FUTF8ToTCHAR(reinterpret_cast<const ANSICHAR*>(Buf.GetData() + Pos), Len));
|
||||
Pos += Len;
|
||||
return true;
|
||||
}
|
||||
bool ReadUuid16(const TArray<uint8>& Buf, int32& Pos, FString& Out)
|
||||
{
|
||||
if (Pos + 16 > Buf.Num()) return false;
|
||||
// 16 bytes raw -> 8-4-4-4-12
|
||||
auto Hex = [](uint8 b, ANSICHAR* dst)
|
||||
{
|
||||
static const ANSICHAR* H = "0123456789abcdef";
|
||||
dst[0] = H[(b >> 4) & 0xF];
|
||||
dst[1] = H[b & 0xF];
|
||||
};
|
||||
ANSICHAR raw[37]; raw[36] = 0;
|
||||
int32 j = 0;
|
||||
for (int32 i = 0; i < 16; ++i)
|
||||
{
|
||||
if (i == 4 || i == 6 || i == 8 || i == 10) raw[j++] = '-';
|
||||
Hex(Buf[Pos + i], &raw[j]); j += 2;
|
||||
}
|
||||
Out = ANSI_TO_TCHAR(raw);
|
||||
Pos += 16;
|
||||
return true;
|
||||
}
|
||||
bool WriteUuid16(TArray<uint8>& Buf, const FString& Uuid)
|
||||
{
|
||||
// Espera xxxxxxxx-xxxx-...
|
||||
FString Hex = Uuid.Replace(TEXT("-"), TEXT(""));
|
||||
if (Hex.Len() != 32) return false;
|
||||
for (int32 i = 0; i < 16; ++i)
|
||||
{
|
||||
TCHAR hi = Hex[i * 2];
|
||||
TCHAR lo = Hex[i * 2 + 1];
|
||||
auto HexVal = [](TCHAR c) -> int32 {
|
||||
if (c >= TEXT('0') && c <= TEXT('9')) return c - TEXT('0');
|
||||
if (c >= TEXT('a') && c <= TEXT('f')) return c - TEXT('a') + 10;
|
||||
if (c >= TEXT('A') && c <= TEXT('F')) return c - TEXT('A') + 10;
|
||||
return -1;
|
||||
};
|
||||
int32 H = HexVal(hi);
|
||||
int32 L = HexVal(lo);
|
||||
if (H < 0 || L < 0) return false;
|
||||
Buf.Add(static_cast<uint8>((H << 4) | L));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
using ZMMOWire::ReadU8;
|
||||
using ZMMOWire::ReadU16;
|
||||
using ZMMOWire::ReadU32;
|
||||
using ZMMOWire::ReadU64;
|
||||
using ZMMOWire::ReadFloat;
|
||||
using ZMMOWire::ReadStringUtf8;
|
||||
using ZMMOWire::ReadUuid16;
|
||||
using ZMMOWire::WriteUuid16;
|
||||
|
||||
void UUIUserLobbyScreen_Base::NativeOnActivated()
|
||||
{
|
||||
|
||||
136
Source/ZMMO/Game/UI/FrontEnd/WireHelpers.h
Normal file
136
Source/ZMMO/Game/UI/FrontEnd/WireHelpers.h
Normal file
@@ -0,0 +1,136 @@
|
||||
#pragma once
|
||||
|
||||
// Helpers de leitura/escrita binarios LE (UTF-8) compartilhados pelas telas
|
||||
// do FrontEnd (Boot/Login/ServerSelect/Lobby/CharCreate). Funcoes `inline`
|
||||
// pra evitar ODR violation quando dois .cpp caem no mesmo unity build do
|
||||
// UBT — definicoes em anonymous namespace cada um colidiam apos a
|
||||
// reorganizacao automatica de unities.
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
namespace ZMMOWire
|
||||
{
|
||||
inline bool ReadU8(const TArray<uint8>& Buf, int32& Pos, uint8& Out)
|
||||
{
|
||||
if (Pos + 1 > Buf.Num()) return false;
|
||||
Out = Buf[Pos];
|
||||
Pos += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ReadU16(const TArray<uint8>& Buf, int32& Pos, uint16& Out)
|
||||
{
|
||||
if (Pos + 2 > Buf.Num()) return false;
|
||||
Out = static_cast<uint16>(Buf[Pos]) | (static_cast<uint16>(Buf[Pos + 1]) << 8);
|
||||
Pos += 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ReadU32(const TArray<uint8>& Buf, int32& Pos, uint32& Out)
|
||||
{
|
||||
if (Pos + 4 > Buf.Num()) return false;
|
||||
Out = static_cast<uint32>(Buf[Pos])
|
||||
| (static_cast<uint32>(Buf[Pos + 1]) << 8)
|
||||
| (static_cast<uint32>(Buf[Pos + 2]) << 16)
|
||||
| (static_cast<uint32>(Buf[Pos + 3]) << 24);
|
||||
Pos += 4;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ReadU64(const TArray<uint8>& Buf, int32& Pos, uint64& Out)
|
||||
{
|
||||
if (Pos + 8 > Buf.Num()) return false;
|
||||
Out = 0;
|
||||
for (int32 i = 0; i < 8; ++i)
|
||||
{
|
||||
Out |= (static_cast<uint64>(Buf[Pos + i]) << (8 * i));
|
||||
}
|
||||
Pos += 8;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ReadFloat(const TArray<uint8>& Buf, int32& Pos, float& Out)
|
||||
{
|
||||
uint32 Raw = 0;
|
||||
if (!ReadU32(Buf, Pos, Raw)) return false;
|
||||
FMemory::Memcpy(&Out, &Raw, sizeof(float));
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ReadStringUtf8(const TArray<uint8>& Buf, int32& Pos, FString& Out)
|
||||
{
|
||||
uint16 Len = 0;
|
||||
if (!ReadU16(Buf, Pos, Len)) return false;
|
||||
if (Pos + Len > Buf.Num()) return false;
|
||||
if (Len == 0) { Out.Reset(); return true; }
|
||||
Out = FString(FUTF8ToTCHAR(reinterpret_cast<const ANSICHAR*>(Buf.GetData() + Pos), Len));
|
||||
Pos += Len;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Le 16 bytes raw e devolve UUID canonico "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx". */
|
||||
inline bool ReadUuid16(const TArray<uint8>& Buf, int32& Pos, FString& Out)
|
||||
{
|
||||
if (Pos + 16 > Buf.Num()) return false;
|
||||
auto HexNibble = [](uint8 B, ANSICHAR* Dst)
|
||||
{
|
||||
static const ANSICHAR* H = "0123456789abcdef";
|
||||
Dst[0] = H[(B >> 4) & 0xF];
|
||||
Dst[1] = H[B & 0xF];
|
||||
};
|
||||
ANSICHAR Raw[37]; Raw[36] = 0;
|
||||
int32 J = 0;
|
||||
for (int32 I = 0; I < 16; ++I)
|
||||
{
|
||||
if (I == 4 || I == 6 || I == 8 || I == 10) Raw[J++] = '-';
|
||||
HexNibble(Buf[Pos + I], &Raw[J]);
|
||||
J += 2;
|
||||
}
|
||||
Out = ANSI_TO_TCHAR(Raw);
|
||||
Pos += 16;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void WriteUInt8(TArray<uint8>& Buf, uint8 V) { Buf.Add(V); }
|
||||
|
||||
inline void WriteUInt16(TArray<uint8>& Buf, uint16 V)
|
||||
{
|
||||
Buf.Add(static_cast<uint8>(V & 0xFF));
|
||||
Buf.Add(static_cast<uint8>((V >> 8) & 0xFF));
|
||||
}
|
||||
|
||||
inline void WriteUInt32(TArray<uint8>& Buf, uint32 V)
|
||||
{
|
||||
for (int32 I = 0; I < 4; ++I) Buf.Add(static_cast<uint8>((V >> (8 * I)) & 0xFF));
|
||||
}
|
||||
|
||||
inline void WriteStringUtf8(TArray<uint8>& Buf, const FString& S)
|
||||
{
|
||||
FTCHARToUTF8 Conv(*S);
|
||||
const int32 Len = Conv.Length();
|
||||
WriteUInt16(Buf, static_cast<uint16>(Len));
|
||||
Buf.Append(reinterpret_cast<const uint8*>(Conv.Get()), Len);
|
||||
}
|
||||
|
||||
/** Aceita UUID canonico (com hifens) e escreve 16 bytes binarios. */
|
||||
inline bool WriteUuid16(TArray<uint8>& Buf, const FString& Uuid)
|
||||
{
|
||||
FString Hex = Uuid.Replace(TEXT("-"), TEXT(""));
|
||||
if (Hex.Len() != 32) return false;
|
||||
auto HexVal = [](TCHAR C) -> int32
|
||||
{
|
||||
if (C >= TEXT('0') && C <= TEXT('9')) return C - TEXT('0');
|
||||
if (C >= TEXT('a') && C <= TEXT('f')) return C - TEXT('a') + 10;
|
||||
if (C >= TEXT('A') && C <= TEXT('F')) return C - TEXT('A') + 10;
|
||||
return -1;
|
||||
};
|
||||
for (int32 I = 0; I < 16; ++I)
|
||||
{
|
||||
const int32 H = HexVal(Hex[I * 2]);
|
||||
const int32 L = HexVal(Hex[I * 2 + 1]);
|
||||
if (H < 0 || L < 0) return false;
|
||||
Buf.Add(static_cast<uint8>((H << 4) | L));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user