Files
ZMMO/Source/ZMMO/Game/UI/FrontEnd/UIUserLobbyScreen_Base.cpp
Mateus Rodrigues 6a6a28a086 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>
2026-06-02 17:24:38 -03:00

440 lines
15 KiB
C++

#include "UIUserLobbyScreen_Base.h"
#include "ZMMO.h"
#include "CharServerOpcodes.h"
#include "UIFrontEndFlowSubsystem.h"
#include "UICharCard_Base.h"
#include "UICharacterCreatePage_Base.h"
#include "WireHelpers.h"
#include "ZeusCharServerSubsystem.h"
#include "ZeusNetworkSubsystem.h"
#include "CommonTextBlock.h"
#include "Components/PanelWidget.h"
#include "Components/WidgetSwitcher.h"
#include "Engine/GameInstance.h"
#include "UI/Widgets/UIButton_Base.h"
using ZeusWire::ReadU8;
using ZeusWire::ReadU16;
using ZeusWire::ReadU32;
using ZeusWire::ReadU64;
using ZeusWire::ReadFloat;
using ZeusWire::ReadStringUtf8;
using ZeusWire::ReadUuid16;
using ZeusWire::WriteUuid16;
void UUIUserLobbyScreen_Base::NativeOnActivated()
{
Super::NativeOnActivated();
UE_LOG(LogZMMO, Log, TEXT("Lobby: ativada."));
if (UZeusCharServerSubsystem* Char = GetCharServer())
{
if (!bSubscribed)
{
Char->OnRawMessage.AddDynamic(this, &UUIUserLobbyScreen_Base::HandleCharRawMessage);
bSubscribed = true;
}
}
if (!bButtonsBound)
{
if (Btn_Back) Btn_Back->OnClicked().AddUObject(this, &UUIUserLobbyScreen_Base::BackToServerSelect);
if (Btn_Create) Btn_Create->OnClicked().AddUObject(this, &UUIUserLobbyScreen_Base::ShowCharCreatePage);
bButtonsBound = true;
}
ShowCharSelectPage();
RequestCharList();
}
void UUIUserLobbyScreen_Base::NativeOnDeactivated()
{
if (bSubscribed)
{
if (UZeusCharServerSubsystem* Char = GetCharServer())
{
Char->OnRawMessage.RemoveDynamic(this, &UUIUserLobbyScreen_Base::HandleCharRawMessage);
}
bSubscribed = false;
}
Super::NativeOnDeactivated();
}
void UUIUserLobbyScreen_Base::RequestCharList()
{
UZeusCharServerSubsystem* Char = GetCharServer();
if (!Char || !Char->IsConnected()) return;
FString RealmId;
if (UGameInstance* GI = GetGameInstance())
{
if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem<UUIFrontEndFlowSubsystem>())
{
RealmId = Flow->GetSelectedRealmId();
}
}
// Wire: uint8 hasFilter + (uint8[16] realmId se hasFilter==1)
TArray<uint8> Payload;
if (RealmId.IsEmpty())
{
Payload.Add(0);
}
else
{
Payload.Add(1);
if (!WriteUuid16(Payload, RealmId))
{
UE_LOG(LogZMMO, Warning, TEXT("Lobby: SelectedRealmId invalido (%s) — pedindo sem filtro"), *RealmId);
Payload.Reset();
Payload.Add(0);
}
}
Char->SendCharRequest(ZeusCharOp::C_CHAR_LIST_REQUEST, Payload);
}
void UUIUserLobbyScreen_Base::ShowCharSelectPage()
{
if (PageSwitcher) PageSwitcher->SetActiveWidgetIndex(0);
}
void UUIUserLobbyScreen_Base::ShowCharCreatePage()
{
if (PageSwitcher) PageSwitcher->SetActiveWidgetIndex(1);
}
void UUIUserLobbyScreen_Base::BackToServerSelect()
{
UGameInstance* GI = GetGameInstance();
if (!GI) return;
if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem<UUIFrontEndFlowSubsystem>())
{
Flow->ClearSelectedRealm();
Flow->SetState(EZeusFrontEndState::ServerSelect);
}
}
void UUIUserLobbyScreen_Base::HandleCharRawMessage(int32 Opcode, const TArray<uint8>& Payload)
{
switch (Opcode)
{
case ZeusCharOp::S_CHAR_LIST: ParseCharList(Payload); break;
case ZeusCharOp::S_CHAR_SELECT_OK: HandleCharSelectOk(Payload); break;
case ZeusCharOp::S_CHAR_SELECT_REJECT: HandleCharSelectReject(Payload); break;
case ZeusCharOp::S_CHAR_CREATE_OK: HandleCharCreateOk(Payload); break;
case ZeusCharOp::S_CHAR_CREATE_REJECT: HandleCharCreateReject(Payload); break;
case ZeusCharOp::S_CHAR_DELETE_ACK: HandleCharDeleteAck(Payload); break;
case ZeusCharOp::S_CHAR_DELETE_ACCEPT_ACK: HandleCharDeleteAcceptAck(Payload); break;
case ZeusCharOp::S_CHAR_DELETE_CANCEL_ACK: HandleCharDeleteCancelAck(Payload); break;
default: break;
}
}
void UUIUserLobbyScreen_Base::ParseCharList(const TArray<uint8>& Payload)
{
int32 Pos = 0;
uint16 Count = 0;
if (!ReadU16(Payload, Pos, Count))
{
UE_LOG(LogZMMO, Warning, TEXT("Lobby: S_CHAR_LIST malformado (sem count)"));
return;
}
TArray<FZeusCharSummary> New;
New.Reserve(Count);
for (uint16 i = 0; i < Count; ++i)
{
FZeusCharSummary E;
uint64 charId64 = 0;
uint8 slot = 0;
uint16 classId = 0;
uint32 baseLvl = 0, baseExp = 0, jobLvl = 0, jobExp = 0;
uint16 s = 0, a = 0, v = 0, in_ = 0, d = 0, l = 0;
uint32 hp = 0, maxHp = 0, sp = 0, maxSp = 0, money = 0;
uint16 mapId16 = 0;
float px = 0, py = 0, pz = 0, yaw = 0;
uint32 hair = 0, hairColor = 0, skinColor = 0;
uint8 bodyType = 0;
uint64 deleteAt = 0;
if (!ReadU64(Payload, Pos, charId64)
|| !ReadUuid16(Payload, Pos, E.RealmId)
|| !ReadU8(Payload, Pos, slot)
|| !ReadStringUtf8(Payload, Pos, E.Name)
|| !ReadU16(Payload, Pos, classId)
|| !ReadU32(Payload, Pos, baseLvl)
|| !ReadU32(Payload, Pos, baseExp)
|| !ReadU32(Payload, Pos, jobLvl)
|| !ReadU32(Payload, Pos, jobExp)
|| !ReadU16(Payload, Pos, s) || !ReadU16(Payload, Pos, a) || !ReadU16(Payload, Pos, v)
|| !ReadU16(Payload, Pos, in_) || !ReadU16(Payload, Pos, d) || !ReadU16(Payload, Pos, l)
|| !ReadU32(Payload, Pos, hp) || !ReadU32(Payload, Pos, maxHp)
|| !ReadU32(Payload, Pos, sp) || !ReadU32(Payload, Pos, maxSp)
|| !ReadU32(Payload, Pos, money)
|| !ReadU16(Payload, Pos, mapId16)
|| !ReadFloat(Payload, Pos, px) || !ReadFloat(Payload, Pos, py) || !ReadFloat(Payload, Pos, pz)
|| !ReadFloat(Payload, Pos, yaw)
|| !ReadU32(Payload, Pos, hair) || !ReadU32(Payload, Pos, hairColor) || !ReadU32(Payload, Pos, skinColor)
|| !ReadU8(Payload, Pos, bodyType)
|| !ReadU64(Payload, Pos, deleteAt))
{
UE_LOG(LogZMMO, Warning, TEXT("Lobby: S_CHAR_LIST malformado (entry %d)"), i);
return;
}
E.CharId = FString::Printf(TEXT("%llu"), static_cast<unsigned long long>(charId64));
E.Slot = slot; E.ClassId = classId;
E.BaseLevel = baseLvl; E.BaseExp = static_cast<int32>(baseExp);
E.JobLevel = jobLvl; E.JobExp = static_cast<int32>(jobExp);
E.Stats.Str = s; E.Stats.Agi = a; E.Stats.Vit = v;
E.Stats.Int = in_; E.Stats.Dex = d; E.Stats.Luk = l;
E.Hp = hp; E.MaxHp = maxHp; E.Sp = sp; E.MaxSp = maxSp;
E.Money = static_cast<int32>(money);
E.MapId = static_cast<int32>(mapId16);
E.Position = FVector(px, py, pz); E.YawDeg = yaw;
E.Appearance.Hair = hair; E.Appearance.HairColor = hairColor;
E.Appearance.SkinColor = skinColor; E.Appearance.BodyType = bodyType;
E.DeleteScheduledAtMs = static_cast<int64>(deleteAt);
New.Add(MoveTemp(E));
}
Chars = MoveTemp(New);
UE_LOG(LogZMMO, Log, TEXT("Lobby: recebidos %d chars"), Chars.Num());
RebuildCards();
OnCharListReceived();
}
void UUIUserLobbyScreen_Base::RebuildCards()
{
if (!CardContainer || !CharCardClass) return;
for (UUICharCard_Base* Old : SpawnedCards)
{
if (Old)
{
Old->OnCardSelected.RemoveDynamic(this, &UUIUserLobbyScreen_Base::HandleCardSelected);
Old->OnCardDeleteRequest.RemoveDynamic(this, &UUIUserLobbyScreen_Base::HandleCardDeleteRequest);
Old->OnCardDeleteAccept.RemoveDynamic(this, &UUIUserLobbyScreen_Base::HandleCardAcceptDeleteRequest);
Old->OnCardDeleteCancel.RemoveDynamic(this, &UUIUserLobbyScreen_Base::HandleCardCancelDeleteRequest);
}
}
SpawnedCards.Reset();
CardContainer->ClearChildren();
for (const FZeusCharSummary& C : Chars)
{
UUICharCard_Base* Card = CreateWidget<UUICharCard_Base>(this, CharCardClass);
if (!Card) continue;
Card->SetFromSummary(C);
Card->OnCardSelected.AddDynamic(this, &UUIUserLobbyScreen_Base::HandleCardSelected);
Card->OnCardDeleteRequest.AddDynamic(this, &UUIUserLobbyScreen_Base::HandleCardDeleteRequest);
Card->OnCardDeleteAccept.AddDynamic(this, &UUIUserLobbyScreen_Base::HandleCardAcceptDeleteRequest);
Card->OnCardDeleteCancel.AddDynamic(this, &UUIUserLobbyScreen_Base::HandleCardCancelDeleteRequest);
CardContainer->AddChild(Card);
SpawnedCards.Add(Card);
}
}
void UUIUserLobbyScreen_Base::HandleCardSelected(FString CharId)
{
UE_LOG(LogZMMO, Log, TEXT("Lobby: char %s selecionado — enviando C_CHAR_SELECT"), *CharId);
UZeusCharServerSubsystem* Char = GetCharServer();
if (!Char) return;
uint64 CharId64 = 0;
LexFromString(CharId64, *CharId);
// Wire: uint64 charId
TArray<uint8> Payload;
for (int32 i = 0; i < 8; ++i) Payload.Add(static_cast<uint8>((CharId64 >> (8 * i)) & 0xFF));
Char->SendCharRequest(ZeusCharOp::C_CHAR_SELECT, Payload);
}
void UUIUserLobbyScreen_Base::HandleCardDeleteRequest(FString CharId)
{
UE_LOG(LogZMMO, Log, TEXT("Lobby: DELETE_REQUEST char %s"), *CharId);
SendCharIdOpcode(ZeusCharOp::C_CHAR_DELETE_REQUEST, CharId);
}
void UUIUserLobbyScreen_Base::HandleCardAcceptDeleteRequest(FString CharId)
{
UE_LOG(LogZMMO, Log, TEXT("Lobby: DELETE_ACCEPT char %s"), *CharId);
SendCharIdOpcode(ZeusCharOp::C_CHAR_DELETE_ACCEPT, CharId);
}
void UUIUserLobbyScreen_Base::HandleCardCancelDeleteRequest(FString CharId)
{
UE_LOG(LogZMMO, Log, TEXT("Lobby: DELETE_CANCEL char %s"), *CharId);
SendCharIdOpcode(ZeusCharOp::C_CHAR_DELETE_CANCEL, CharId);
}
void UUIUserLobbyScreen_Base::SendCharIdOpcode(int32 Opcode, const FString& CharId)
{
UZeusCharServerSubsystem* Char = GetCharServer();
if (!Char) return;
uint64 CharId64 = 0;
LexFromString(CharId64, *CharId);
TArray<uint8> Payload;
for (int32 i = 0; i < 8; ++i) Payload.Add(static_cast<uint8>((CharId64 >> (8 * i)) & 0xFF));
Char->SendCharRequest(Opcode, Payload);
}
void UUIUserLobbyScreen_Base::HandleCharDeleteAck(const TArray<uint8>& Payload)
{
// Wire: uint64 charId + uint16 reason + uint64 effectiveAtMs
int32 Pos = 0; uint64 CharId64 = 0; uint16 Reason = 0; uint64 EffectiveAtMs = 0;
if (!ReadU64(Payload, Pos, CharId64) || !ReadU16(Payload, Pos, Reason) || !ReadU64(Payload, Pos, EffectiveAtMs))
{
UE_LOG(LogZMMO, Warning, TEXT("Lobby: S_CHAR_DELETE_ACK malformado"));
return;
}
if (Reason == 0)
{
UE_LOG(LogZMMO, Log, TEXT("Lobby: delete agendado char=%llu (effective=%llu)"),
static_cast<unsigned long long>(CharId64), static_cast<unsigned long long>(EffectiveAtMs));
}
else
{
UE_LOG(LogZMMO, Warning, TEXT("Lobby: DELETE_ACK rejeitado (reason=%d)"), Reason);
}
RequestCharList();
}
void UUIUserLobbyScreen_Base::HandleCharDeleteAcceptAck(const TArray<uint8>& Payload)
{
int32 Pos = 0; uint64 CharId64 = 0; uint16 Reason = 0;
ReadU64(Payload, Pos, CharId64); ReadU16(Payload, Pos, Reason);
if (Reason == 0)
{
UE_LOG(LogZMMO, Log, TEXT("Lobby: char %llu DELETADO"), static_cast<unsigned long long>(CharId64));
}
else
{
UE_LOG(LogZMMO, Warning, TEXT("Lobby: DELETE_ACCEPT rejeitado (reason=%d)"), Reason);
}
RequestCharList();
}
void UUIUserLobbyScreen_Base::HandleCharDeleteCancelAck(const TArray<uint8>& Payload)
{
int32 Pos = 0; uint64 CharId64 = 0; uint16 Reason = 0;
ReadU64(Payload, Pos, CharId64); ReadU16(Payload, Pos, Reason);
if (Reason == 0)
{
UE_LOG(LogZMMO, Log, TEXT("Lobby: delete cancelado char %llu"), static_cast<unsigned long long>(CharId64));
}
RequestCharList();
}
void UUIUserLobbyScreen_Base::HandleCharSelectOk(const TArray<uint8>& Payload)
{
// Wire: uint64 charId + uint16 mapId + float x/y/z + float yaw +
// string gatewayEndpoint + string handoffToken + string region
// gatewayEndpoint = "host:port" (UMA single string — refator R4 unificou
// host+port). Server-side: char-select.service.ts.
int32 Pos = 0;
uint64 CharId64 = 0; FString GatewayEndpoint, HandoffToken, Region;
uint16 MapId16 = 0;
float Px=0, Py=0, Pz=0, Yaw=0;
if (!ReadU64(Payload, Pos, CharId64)
|| !ReadU16(Payload, Pos, MapId16)
|| !ReadFloat(Payload, Pos, Px) || !ReadFloat(Payload, Pos, Py) || !ReadFloat(Payload, Pos, Pz)
|| !ReadFloat(Payload, Pos, Yaw)
|| !ReadStringUtf8(Payload, Pos, GatewayEndpoint)
|| !ReadStringUtf8(Payload, Pos, HandoffToken)
|| !ReadStringUtf8(Payload, Pos, Region))
{
UE_LOG(LogZMMO, Warning, TEXT("Lobby: S_CHAR_SELECT_OK malformado"));
return;
}
UE_LOG(LogZMMO, Log, TEXT("Lobby: handoff -> realm gateway=%s mapId=%u token=%s..."),
*GatewayEndpoint, static_cast<uint32>(MapId16), *HandoffToken.Left(8));
// Split "host:port" para a API legada do ZeusNetworkSubsystem que ainda
// recebe os dois separados. Defensivo: se o server mandar string vazia
// ou sem ':' a gente loga e aborta o handoff em vez de tentar conectar
// em endereco invalido.
FString GwHost;
FString GwPortStr;
if (!GatewayEndpoint.Split(TEXT(":"), &GwHost, &GwPortStr, ESearchCase::CaseSensitive, ESearchDir::FromEnd))
{
UE_LOG(LogZMMO, Error, TEXT("Lobby: gatewayEndpoint malformado (sem ':') = %s"), *GatewayEndpoint);
return;
}
int32 GwPort = 0;
LexFromString(GwPort, *GwPortStr);
if (GwHost.IsEmpty() || GwPort <= 0)
{
UE_LOG(LogZMMO, Error, TEXT("Lobby: gatewayEndpoint invalido host=%s port=%d"), *GwHost, GwPort);
return;
}
UGameInstance* GI = GetGameInstance();
if (!GI) return;
UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem<UUIFrontEndFlowSubsystem>();
if (Flow)
{
Flow->SetState(EZeusFrontEndState::EnteringWorld);
// Memoriza pose pra `AZeusCharacter::BeginPlay` reposicionar
// o pawn local na pos salva no DB (em vez do PlayerStart default).
Flow->SetPendingSpawnPose(FVector(Px, Py, Pz), Yaw);
}
// Fase 3: handoff UDP — apresenta o ticket no `C_CONNECT_REQUEST`. O
// Gateway/ZeusServer valida via GETDEL no Valkey regional. Server envia
// `S_CONNECT_OK` (sucesso) ou `S_CONNECT_REJECT` (ticket invalido/expirado).
// Disparamos PRIMEIRO o connect (nao-bloqueante), depois o OpenLevel —
// ZeusNetworkSubsystem e per-GameInstance e persiste cross-travel.
if (UZeusNetworkSubsystem* ZeusNet = GI->GetSubsystem<UZeusNetworkSubsystem>())
{
ZeusNet->ConnectToZeusServerWithTicket(GwHost, GwPort, HandoffToken);
}
else
{
UE_LOG(LogZMMO, Error, TEXT("Lobby: ZeusNetworkSubsystem ausente — handoff abortado"));
}
// Fase 4: lookup mapId no DT_Maps e dispara OpenLevel pro level certo.
// Se DT_Maps nao tem entrada (ou ainda nao foi criada no editor),
// caimos no fallback do S_TRAVEL_TO_MAP do server (mapName/mapPath string).
if (Flow && MapId16 != 0)
{
if (!Flow->TravelToMapById(static_cast<int32>(MapId16)))
{
UE_LOG(LogZMMO, Warning, TEXT("Lobby: mapId=%u nao resolvido no DT_Maps — esperando S_TRAVEL_TO_MAP do server."),
static_cast<uint32>(MapId16));
}
}
}
void UUIUserLobbyScreen_Base::HandleCharSelectReject(const TArray<uint8>& Payload)
{
int32 Pos = 0;
uint16 Reason = 0;
ReadU16(Payload, Pos, Reason);
UE_LOG(LogZMMO, Warning, TEXT("Lobby: S_CHAR_SELECT_REJECT reason=%d"), Reason);
}
void UUIUserLobbyScreen_Base::HandleCharCreateOk(const TArray<uint8>& /*Payload*/)
{
UE_LOG(LogZMMO, Log, TEXT("Lobby: S_CHAR_CREATE_OK — refresh lista"));
ShowCharSelectPage();
RequestCharList();
}
void UUIUserLobbyScreen_Base::HandleCharCreateReject(const TArray<uint8>& Payload)
{
int32 Pos = 0;
uint16 Reason = 0;
ReadU16(Payload, Pos, Reason);
UE_LOG(LogZMMO, Warning, TEXT("Lobby: S_CHAR_CREATE_REJECT reason=%d"), Reason);
}
UZeusCharServerSubsystem* UUIUserLobbyScreen_Base::GetCharServer() const
{
if (const UGameInstance* GI = GetGameInstance())
{
return GI->GetSubsystem<UZeusCharServerSubsystem>();
}
return nullptr;
}