From 4e14f1feb64eb9d28aa5614473a7e5a4324ff21a Mon Sep 17 00:00:00 2001 From: Mateus Rodrigues Date: Sun, 31 May 2026 20:02:47 -0300 Subject: [PATCH] refactor(charserver): World -> Realm + parse gatewayEndpoint + fix PendingLoadingSteps race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ZMMOWorldEntry -> ZMMORealmEntry (RealmId + GatewayEndpoint single string) - CharServerOpcodes: S_WORLD_* -> S_REALM_* - UIServerSelectScreen: parse S_REALM_LIST com gatewayEndpoint - UIUserLobbyScreen: parse S_CHAR_SELECT_OK, split gatewayEndpoint via FString::Split(":", FromEnd) — IPv6 tolerante - UICharacterCreatePage: SubmitCreate envia realmId - ZMMOCharSummary: WorldId -> RealmId - UIFrontEndFlowSubsystem: GetSelectedRealmId; fix de race em PendingLoadingSteps_ (TSet snapshot via MoveTemp antes de iterar; evita access violation em TConstSetBitIterator durante async lambda) Conferido E2E: 2 standalone instances entram simultaneamente em Provinces diferentes via Gateway. --- .../ZMMO/Game/UI/FrontEnd/CharServerOpcodes.h | 33 +++--- .../FrontEnd/UICharacterCreatePage_Base.cpp | 10 +- .../UI/FrontEnd/UICharacterCreatePage_Base.h | 2 +- .../UI/FrontEnd/UIFrontEndFlowSubsystem.cpp | 25 +++-- .../UI/FrontEnd/UIFrontEndFlowSubsystem.h | 16 +-- .../Game/UI/FrontEnd/UIServerCard_Base.cpp | 16 +-- .../ZMMO/Game/UI/FrontEnd/UIServerCard_Base.h | 16 +-- .../UI/FrontEnd/UIServerSelectScreen_Base.cpp | 106 +++++++++--------- .../UI/FrontEnd/UIServerSelectScreen_Base.h | 24 ++-- .../UI/FrontEnd/UIUserLobbyScreen_Base.cpp | 54 ++++++--- .../Game/UI/FrontEnd/UIUserLobbyScreen_Base.h | 11 +- .../ZMMO/Game/UI/FrontEnd/ZMMOCharSummary.h | 2 +- Source/ZMMO/Game/UI/FrontEnd/ZMMORealmEntry.h | 48 ++++++++ Source/ZMMO/Game/UI/FrontEnd/ZMMOWorldEntry.h | 45 -------- 14 files changed, 222 insertions(+), 186 deletions(-) create mode 100644 Source/ZMMO/Game/UI/FrontEnd/ZMMORealmEntry.h delete mode 100644 Source/ZMMO/Game/UI/FrontEnd/ZMMOWorldEntry.h diff --git a/Source/ZMMO/Game/UI/FrontEnd/CharServerOpcodes.h b/Source/ZMMO/Game/UI/FrontEnd/CharServerOpcodes.h index 3b32cf0..f609dad 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/CharServerOpcodes.h +++ b/Source/ZMMO/Game/UI/FrontEnd/CharServerOpcodes.h @@ -18,10 +18,10 @@ namespace ZMMOCharOp constexpr int32 S_CHAR_AUTH_REJECT = 2002; // uint16(reason) // Listagem/criação/seleção de personagem - constexpr int32 C_CHAR_LIST_REQUEST = 2010; // uint8 hasWorldFilter [+ uint8[16] worldId] + constexpr int32 C_CHAR_LIST_REQUEST = 2010; // uint8 hasRealmFilter [+ uint8[16] realmId] constexpr int32 S_CHAR_LIST = 2011; - constexpr int32 C_CHAR_CREATE = 2020; // uint8[16] worldId + slot + ... + constexpr int32 C_CHAR_CREATE = 2020; // uint8[16] realmId + slot + ... constexpr int32 S_CHAR_CREATE_OK = 2021; constexpr int32 S_CHAR_CREATE_REJECT= 2022; @@ -37,14 +37,15 @@ namespace ZMMOCharOp constexpr int32 S_CHAR_SELECT_OK = 2041; constexpr int32 S_CHAR_SELECT_REJECT= 2042; - // Listagem de mundos (Fase 1 do ARQUITETURA_SERVER_SELECT) - constexpr int32 C_WORLD_LIST_REQUEST = 2060; // payload vazio - constexpr int32 S_WORLD_LIST = 2061; // uint16 count + entries + // Listagem de Realms (renomeado de WORLD_LIST no refator R4 — Server + // Meshing). Server-side: `Server/ZeusCharServer/src/services/realm-list.service.ts`. + constexpr int32 C_REALM_LIST_REQUEST = 2060; // payload vazio + constexpr int32 S_REALM_LIST = 2061; // uint16 count + entries /** - * Push do CharServer com update de UM mundo (state/pop/queueLen). - * Wire: string worldId + uint16 pop + uint16 queueLen + uint8 state + * Push do CharServer com update de UM Realm (state/pop/queueLen). + * Wire: string realmId + uint16 pop + uint16 queueLen + uint8 state */ - constexpr int32 S_WORLD_STATUS_UPDATE = 2062; + constexpr int32 S_REALM_STATUS_UPDATE = 2062; } /** @@ -68,11 +69,13 @@ namespace ZMMOCharRejectReason constexpr uint16 AccountLocked = 5; constexpr uint16 AccountSuspended = 6; constexpr uint16 CredentialsAuthDisabled = 7; - constexpr uint16 WorldOffline = 8; - constexpr uint16 WorldMaintenance = 9; - constexpr uint16 WorldFull = 10; - constexpr uint16 WorldRegionMismatch = 11; - constexpr uint16 WorldNotFound = 12; + constexpr uint16 RealmOffline = 8; + constexpr uint16 RealmMaintenance = 9; + constexpr uint16 RealmFull = 10; + constexpr uint16 RealmRegionMismatch = 11; + constexpr uint16 RealmNotFound = 12; + /** Posicao do char nao esta dentro de nenhuma cell do grid_layout do Realm (dead zone). */ + constexpr uint16 CharOutsideAnyCell = 13; constexpr uint16 NameInUse = 20; constexpr uint16 InvalidName = 21; constexpr uint16 SlotOccupied = 22; @@ -81,8 +84,8 @@ namespace ZMMOCharRejectReason constexpr uint16 DeleteNotScheduled = 25; } -/** Estado do mundo no wire (uint8). Espelha `WireWorldState` em CharOpcodes.ts. */ -namespace ZMMOWireWorldState +/** Estado do Realm no wire (uint8). Espelha `WireRealmState` em CharOpcodes.ts. */ +namespace ZMMOWireRealmState { constexpr uint8 Offline = 0; constexpr uint8 Online = 1; diff --git a/Source/ZMMO/Game/UI/FrontEnd/UICharacterCreatePage_Base.cpp b/Source/ZMMO/Game/UI/FrontEnd/UICharacterCreatePage_Base.cpp index e318910..ac0b166 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/UICharacterCreatePage_Base.cpp +++ b/Source/ZMMO/Game/UI/FrontEnd/UICharacterCreatePage_Base.cpp @@ -55,10 +55,10 @@ void UUICharacterCreatePage_Base::SubmitCreate() UZeusCharServerSubsystem* Char = GI->GetSubsystem(); if (!Flow || !Char) return; - const FString WorldId = Flow->GetSelectedWorldId(); - if (WorldId.IsEmpty()) + const FString RealmId = Flow->GetSelectedRealmId(); + if (RealmId.IsEmpty()) { - if (Text_Error) Text_Error->SetText(FText::FromString(TEXT("Mundo nao selecionado."))); + if (Text_Error) Text_Error->SetText(FText::FromString(TEXT("Realm nao selecionado."))); return; } @@ -73,9 +73,9 @@ void UUICharacterCreatePage_Base::SubmitCreate() const uint8 SlotIdx = 0; TArray Payload; - if (!WriteUuid16(Payload, WorldId)) + if (!WriteUuid16(Payload, RealmId)) { - if (Text_Error) Text_Error->SetText(FText::FromString(TEXT("WorldId invalido."))); + if (Text_Error) Text_Error->SetText(FText::FromString(TEXT("RealmId invalido."))); return; } WriteUInt8(Payload, SlotIdx); diff --git a/Source/ZMMO/Game/UI/FrontEnd/UICharacterCreatePage_Base.h b/Source/ZMMO/Game/UI/FrontEnd/UICharacterCreatePage_Base.h index 68f3e51..084fd8a 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/UICharacterCreatePage_Base.h +++ b/Source/ZMMO/Game/UI/FrontEnd/UICharacterCreatePage_Base.h @@ -19,7 +19,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FZMMOOnCharCreateCancelled); * S_CHAR_CREATE_OK/REJECT. * * Wire C_CHAR_CREATE (espelha char-create.service.ts): - * uint8[16] worldId + uint8 slot + string name + uint16 classId + * uint8[16] realmId + uint8 slot + string name + uint16 classId * + uint32 hair + uint32 hairColor + uint32 skinColor + uint8 bodyType */ UCLASS(Abstract, Blueprintable) diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp index 5aaf239..2165ad4 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp +++ b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.cpp @@ -238,11 +238,18 @@ void UUIFrontEndFlowSubsystem::ResolveAndPushScreen(EZMMOFrontEndState State) // (HandlePostLoadMap/HandlePlayerSpawned podem ter // rodado durante o RequestAsyncLoad). Sem isso o // loading fica eterno se o servidor for rápido demais. - for (const FName StepId : PendingLoadingSteps_) + // + // Snapshot via MoveTemp pra evitar invalidacao do iterator se + // outro tick mexer no TSet durante o loop: CreateWeakLambda + // garante `this` vivo, NAO garante que membros nao sejam + // realocados entre captura e execucao (crash em + // TConstSetBitIterator::FindFirstSetBit historico). + TSet StepsSnapshot = MoveTemp(PendingLoadingSteps_); + PendingLoadingSteps_.Reset(); + for (const FName StepId : StepsSnapshot) { Loading->MarkStepDone(StepId); } - PendingLoadingSteps_.Reset(); } } } @@ -381,18 +388,18 @@ void UUIFrontEndFlowSubsystem::RequestEnterServerSelect() } } -void UUIFrontEndFlowSubsystem::SetSelectedWorldId(const FString& WorldId) +void UUIFrontEndFlowSubsystem::SetSelectedRealmId(const FString& RealmId) { - SelectedWorldId = WorldId; - UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: mundo selecionado = %s"), *SelectedWorldId); + SelectedRealmId = RealmId; + UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: realm selecionado = %s"), *SelectedRealmId); } -void UUIFrontEndFlowSubsystem::ClearSelectedWorld() +void UUIFrontEndFlowSubsystem::ClearSelectedRealm() { - if (!SelectedWorldId.IsEmpty()) + if (!SelectedRealmId.IsEmpty()) { - UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: limpando mundo selecionado (%s)"), *SelectedWorldId); - SelectedWorldId.Reset(); + UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: limpando realm selecionado (%s)"), *SelectedRealmId); + SelectedRealmId.Reset(); } } diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h index 18339f6..637c361 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h +++ b/Source/ZMMO/Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h @@ -83,18 +83,18 @@ public: void RequestEnterServerSelect(); /** - * Memoriza o mundo escolhido na ServerSelect (UUID v4 string). Lido pelo + * Memoriza o Realm escolhido na ServerSelect (UUID v4 string). Lido pelo * CharSelect/CharCreate na hora de filtrar/criar personagens. Limpo no * logout/back para ServerSelect. */ - UFUNCTION(BlueprintCallable, Category = "FrontEnd|Worlds") - void SetSelectedWorldId(const FString& WorldId); + UFUNCTION(BlueprintCallable, Category = "FrontEnd|Realms") + void SetSelectedRealmId(const FString& RealmId); - UFUNCTION(BlueprintPure, Category = "FrontEnd|Worlds") - FString GetSelectedWorldId() const { return SelectedWorldId; } + UFUNCTION(BlueprintPure, Category = "FrontEnd|Realms") + FString GetSelectedRealmId() const { return SelectedRealmId; } - UFUNCTION(BlueprintCallable, Category = "FrontEnd|Worlds") - void ClearSelectedWorld(); + UFUNCTION(BlueprintCallable, Category = "FrontEnd|Realms") + void ClearSelectedRealm(); /** * Resolve `MapId` (uint16 recebido do CharServer) → row do DT_Maps. @@ -222,7 +222,7 @@ private: EZMMOFrontEndState CurrentState = EZMMOFrontEndState::None; UPROPERTY(Transient) - FString SelectedWorldId; + FString SelectedRealmId; /** Pose autoritativa pendente (do S_CHAR_SELECT_OK). */ UPROPERTY(Transient) diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIServerCard_Base.cpp b/Source/ZMMO/Game/UI/FrontEnd/UIServerCard_Base.cpp index bd87530..cf88bf9 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/UIServerCard_Base.cpp +++ b/Source/ZMMO/Game/UI/FrontEnd/UIServerCard_Base.cpp @@ -41,17 +41,17 @@ void UUIServerCard_Base::NativeDestruct() Super::NativeDestruct(); } -void UUIServerCard_Base::SetFromEntry(const FZMMOWorldEntry& InEntry) +void UUIServerCard_Base::SetFromEntry(const FZMMORealmEntry& InEntry) { Entry = InEntry; if (Text_Name) { - Text_Name->SetText(FText::FromString(Entry.WorldName)); + Text_Name->SetText(FText::FromString(Entry.Name)); } if (Text_Pop) { - Text_Pop->SetText(FText::FromString(FString::Printf(TEXT("%d/%d"), Entry.Population, Entry.Capacity))); + Text_Pop->SetText(FText::FromString(FString::Printf(TEXT("%d/%d"), Entry.Pop, Entry.Capacity))); } if (Text_Status) { @@ -60,15 +60,15 @@ void UUIServerCard_Base::SetFromEntry(const FZMMOWorldEntry& InEntry) if (Bar_Pop) { const float Ratio = (Entry.Capacity > 0) - ? FMath::Clamp(static_cast(Entry.Population) / static_cast(Entry.Capacity), 0.f, 1.f) + ? FMath::Clamp(static_cast(Entry.Pop) / static_cast(Entry.Capacity), 0.f, 1.f) : 0.f; Bar_Pop->SetPercent(Ratio); } - // Botao "Selecionar" sempre habilitado — mesmo com world offline, + // Botao "Selecionar" sempre habilitado — mesmo com realm offline, // o usuario pode entrar no Lobby pra ver a lista de personagens, criar/ - // deletar. O gate de "entrar no mundo" fica no proprio Lobby (botao - // "Entrar no Servidor"), que checa World.State antes do handoff. + // deletar. O gate de "entrar no realm" fica no proprio Lobby (botao + // "Entrar no Servidor"), que checa Realm.State antes do handoff. // TODO(ui-system): reativar quando UUIButton_Base voltar a ter RefreshUIStyle. // if (Btn_Enter) // { @@ -78,7 +78,7 @@ void UUIServerCard_Base::SetFromEntry(const FZMMOWorldEntry& InEntry) void UUIServerCard_Base::HandleEnterClicked() { - OnCardPressed.Broadcast(Entry.WorldId, Entry.State); + OnCardPressed.Broadcast(Entry.RealmId, Entry.State); } FText UUIServerCard_Base::FormatStatus_Implementation(uint8 State) const diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIServerCard_Base.h b/Source/ZMMO/Game/UI/FrontEnd/UIServerCard_Base.h index 564c314..5c8c367 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/UIServerCard_Base.h +++ b/Source/ZMMO/Game/UI/FrontEnd/UIServerCard_Base.h @@ -2,7 +2,7 @@ #include "CoreMinimal.h" #include "Blueprint/UserWidget.h" -#include "ZMMOWorldEntry.h" +#include "ZMMORealmEntry.h" #include "UIServerCard_Base.generated.h" class UCommonTextBlock; @@ -11,10 +11,10 @@ class UUIButton_Base; class UProgressBar; /** - * Card de servidor (mundo) instanciado dinamicamente pela ServerSelect. + * Card de servidor (realm) instanciado dinamicamente pela ServerSelect. * - * Recebe um `FZMMOWorldEntry` e renderiza nome/pop/status/bar; ao clicar - * em "Entrar" dispara `OnCardPressed.Broadcast(WorldId, State)`. A tela + * Recebe um `FZMMORealmEntry` e renderiza nome/pop/status/bar; ao clicar + * em "Entrar" dispara `OnCardPressed.Broadcast(RealmId, State)`. A tela * dona (UUIServerSelectScreen_Base) escuta esse delegate. * * WBP filho (`WBP_ServerCard`) precisa expor (via nomes de variavel): @@ -25,7 +25,7 @@ class UProgressBar; * - Btn_Enter : Button * - Bar_Pop : ProgressBar (opcional) */ -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FZMMOOnServerCardPressed, FString, WorldId, uint8, State); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FZMMOOnServerCardPressed, FString, RealmId, uint8, State); UCLASS(Abstract, Blueprintable) class ZMMO_API UUIServerCard_Base : public UUserWidget @@ -34,14 +34,14 @@ class ZMMO_API UUIServerCard_Base : public UUserWidget public: /** - * Aplica os dados de um mundo no card. + * Aplica os dados de um realm no card. */ UFUNCTION(BlueprintCallable, Category = "Zeus|ServerCard") - void SetFromEntry(const FZMMOWorldEntry& Entry); + void SetFromEntry(const FZMMORealmEntry& Entry); /** Dados atuais (copia). */ UPROPERTY(BlueprintReadOnly, Category = "Zeus|ServerCard") - FZMMOWorldEntry Entry; + FZMMORealmEntry Entry; /** * Disparado quando o botao "Entrar" do card e clicado. diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIServerSelectScreen_Base.cpp b/Source/ZMMO/Game/UI/FrontEnd/UIServerSelectScreen_Base.cpp index 671717b..0737cf2 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/UIServerSelectScreen_Base.cpp +++ b/Source/ZMMO/Game/UI/FrontEnd/UIServerSelectScreen_Base.cpp @@ -70,7 +70,7 @@ void UUIServerSelectScreen_Base::NativeOnActivated() } } - RequestWorldList(); + RequestRealmList(); } void UUIServerSelectScreen_Base::NativeOnDeactivated() @@ -110,23 +110,23 @@ void UUIServerSelectScreen_Base::RefreshUIStyle_Implementation() } } -void UUIServerSelectScreen_Base::RequestWorldList() +void UUIServerSelectScreen_Base::RequestRealmList() { UZeusCharServerSubsystem* Char = GetCharServer(); if (!Char || !Char->IsConnected()) { - UE_LOG(LogZMMO, Warning, TEXT("ServerSelect: CharServer indisponivel — pulando RequestWorldList")); + UE_LOG(LogZMMO, Warning, TEXT("ServerSelect: CharServer indisponivel — pulando RequestRealmList")); return; } TArray EmptyPayload; - Char->SendCharRequest(ZMMOCharOp::C_WORLD_LIST_REQUEST, EmptyPayload); + Char->SendCharRequest(ZMMOCharOp::C_REALM_LIST_REQUEST, EmptyPayload); } -void UUIServerSelectScreen_Base::SelectWorldAndProceed(const FString& WorldId) +void UUIServerSelectScreen_Base::SelectRealmAndProceed(const FString& RealmId) { - if (WorldId.IsEmpty()) + if (RealmId.IsEmpty()) { - UE_LOG(LogZMMO, Warning, TEXT("ServerSelect: SelectWorldAndProceed com WorldId vazio.")); + UE_LOG(LogZMMO, Warning, TEXT("ServerSelect: SelectRealmAndProceed com RealmId vazio.")); return; } UGameInstance* GI = GetGameInstance(); @@ -136,18 +136,18 @@ void UUIServerSelectScreen_Base::SelectWorldAndProceed(const FString& WorldId) } if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem()) { - Flow->SetSelectedWorldId(WorldId); + Flow->SetSelectedRealmId(RealmId); Flow->SetState(EZMMOFrontEndState::Lobby); } } void UUIServerSelectScreen_Base::HandleCharRawMessage(int32 Opcode, const TArray& Payload) { - if (Opcode == ZMMOCharOp::S_WORLD_LIST) + if (Opcode == ZMMOCharOp::S_REALM_LIST) { - ParseWorldList(Payload); + ParseRealmList(Payload); } - else if (Opcode == ZMMOCharOp::S_WORLD_STATUS_UPDATE) + else if (Opcode == ZMMOCharOp::S_REALM_STATUS_UPDATE) { ApplyStatusUpdate(Payload); } @@ -155,24 +155,24 @@ void UUIServerSelectScreen_Base::HandleCharRawMessage(int32 Opcode, const TArray void UUIServerSelectScreen_Base::ApplyStatusUpdate(const TArray& Payload) { - // Wire: string worldId + uint16 pop + uint16 queueLen + uint8 state + // Wire: string realmId + uint16 pop + uint16 queueLen + uint8 state int32 Pos = 0; - FString WorldId; + FString RealmId; uint16 Pop = 0; uint16 QueueLen = 0; uint8 State = 0; - if (!ReadStringUtf8(Payload, Pos, WorldId) + if (!ReadStringUtf8(Payload, Pos, RealmId) || !ReadU16(Payload, Pos, Pop) || !ReadU16(Payload, Pos, QueueLen) || !ReadU8(Payload, Pos, State)) { - UE_LOG(LogZMMO, Warning, TEXT("ServerSelect: S_WORLD_STATUS_UPDATE malformado")); + UE_LOG(LogZMMO, Warning, TEXT("ServerSelect: S_REALM_STATUS_UPDATE malformado")); return; } int32 Idx = INDEX_NONE; - for (int32 i = 0; i < Worlds.Num(); ++i) + for (int32 i = 0; i < Realms.Num(); ++i) { - if (Worlds[i].WorldId == WorldId) + if (Realms[i].RealmId == RealmId) { Idx = i; break; @@ -180,76 +180,76 @@ void UUIServerSelectScreen_Base::ApplyStatusUpdate(const TArray& Payload) } if (Idx == INDEX_NONE) { - // Mundo desconhecido (ainda nao recebemos via S_WORLD_LIST). Pede lista - // completa pra trazer dados estaticos (name/host/region/capacity). - UE_LOG(LogZMMO, Log, TEXT("ServerSelect: STATUS_UPDATE de mundo desconhecido (%s) — pedindo lista"), *WorldId); - RequestWorldList(); + // Realm desconhecido (ainda nao recebemos via S_REALM_LIST). Pede lista + // completa pra trazer dados estaticos (name/gatewayEndpoint/region/capacity). + UE_LOG(LogZMMO, Log, TEXT("ServerSelect: STATUS_UPDATE de realm desconhecido (%s) — pedindo lista"), *RealmId); + RequestRealmList(); return; } - FZMMOWorldEntry& Entry = Worlds[Idx]; - Entry.Population = static_cast(Pop); + FZMMORealmEntry& Entry = Realms[Idx]; + Entry.Pop = static_cast(Pop); Entry.QueueLen = static_cast(QueueLen); Entry.State = State; - UE_LOG(LogZMMO, Log, TEXT("ServerSelect: world %s atualizado (state=%d pop=%d)"), - *WorldId, State, Entry.Population); + UE_LOG(LogZMMO, Log, TEXT("ServerSelect: realm %s atualizado (state=%d pop=%d)"), + *RealmId, State, Entry.Pop); // Atualiza apenas o card afetado (preserva animacoes/scroll dos demais). if (SpawnedCards.IsValidIndex(Idx) && SpawnedCards[Idx]) { SpawnedCards[Idx]->SetFromEntry(Entry); } - OnWorldListReceived(); + OnRealmListReceived(); } -void UUIServerSelectScreen_Base::ParseWorldList(const TArray& Payload) +void UUIServerSelectScreen_Base::ParseRealmList(const TArray& Payload) { int32 Pos = 0; uint16 Count = 0; if (!ReadU16(Payload, Pos, Count)) { - UE_LOG(LogZMMO, Warning, TEXT("ServerSelect: S_WORLD_LIST malformado (sem count)")); + UE_LOG(LogZMMO, Warning, TEXT("ServerSelect: S_REALM_LIST malformado (sem count)")); return; } - TArray NewWorlds; - NewWorlds.Reserve(Count); + TArray NewRealms; + NewRealms.Reserve(Count); for (uint16 i = 0; i < Count; ++i) { - FZMMOWorldEntry Entry; + FZMMORealmEntry Entry; FString RegionStr; - uint16 Port = 0; uint16 Cap = 0; uint16 Pop = 0; uint16 QueueLen = 0; uint8 State = 0; - if (!ReadStringUtf8(Payload, Pos, Entry.WorldId) - || !ReadStringUtf8(Payload, Pos, Entry.WorldName) + // Wire: string realmId + string name + string region + string gatewayEndpoint + // + uint16 capacity + uint16 pop + uint16 queueLen + uint8 state + // Server-side: Server/ZeusCharServer/src/services/realm-list.service.ts + if (!ReadStringUtf8(Payload, Pos, Entry.RealmId) + || !ReadStringUtf8(Payload, Pos, Entry.Name) || !ReadStringUtf8(Payload, Pos, RegionStr) - || !ReadStringUtf8(Payload, Pos, Entry.Host) - || !ReadU16(Payload, Pos, Port) + || !ReadStringUtf8(Payload, Pos, Entry.GatewayEndpoint) || !ReadU16(Payload, Pos, Cap) || !ReadU16(Payload, Pos, Pop) || !ReadU16(Payload, Pos, QueueLen) || !ReadU8(Payload, Pos, State)) { - UE_LOG(LogZMMO, Warning, TEXT("ServerSelect: S_WORLD_LIST malformado (entry %d)"), i); + UE_LOG(LogZMMO, Warning, TEXT("ServerSelect: S_REALM_LIST malformado (entry %d)"), i); return; } Entry.Region = RegionStr; - Entry.Port = static_cast(Port); Entry.Capacity = static_cast(Cap); - Entry.Population = static_cast(Pop); + Entry.Pop = static_cast(Pop); Entry.QueueLen = static_cast(QueueLen); Entry.State = State; - NewWorlds.Add(MoveTemp(Entry)); + NewRealms.Add(MoveTemp(Entry)); } - Worlds = MoveTemp(NewWorlds); - UE_LOG(LogZMMO, Log, TEXT("ServerSelect: recebidos %d mundos"), Worlds.Num()); + Realms = MoveTemp(NewRealms); + UE_LOG(LogZMMO, Log, TEXT("ServerSelect: recebidos %d realms"), Realms.Num()); RebuildCards(); - OnWorldListReceived(); + OnRealmListReceived(); } void UUIServerSelectScreen_Base::RebuildCards() @@ -280,15 +280,15 @@ void UUIServerSelectScreen_Base::RebuildCards() const int32 GridColumns = 2; // bate com o mock (ColumnFill[0]/[1]) int32 Index = 0; - for (const FZMMOWorldEntry& W : Worlds) + for (const FZMMORealmEntry& R : Realms) { UUIServerCard_Base* Card = CreateWidget(this, CardClass); if (!Card) { - UE_LOG(LogZMMO, Warning, TEXT("ServerSelect: falha ao criar card para %s"), *W.WorldName); + UE_LOG(LogZMMO, Warning, TEXT("ServerSelect: falha ao criar card para %s"), *R.Name); continue; } - Card->SetFromEntry(W); + Card->SetFromEntry(R); Card->OnCardPressed.AddDynamic(this, &UUIServerSelectScreen_Base::HandleCardPressed); CardContainer->AddChild(Card); @@ -311,14 +311,14 @@ void UUIServerSelectScreen_Base::RebuildCards() } } -void UUIServerSelectScreen_Base::HandleCardPressed(FString WorldId, uint8 State) +void UUIServerSelectScreen_Base::HandleCardPressed(FString RealmId, uint8 State) { - // O ServerSelect permite escolher qualquer mundo (online/maintenance/ - // offline) — o usuario entra no Lobby pra gerenciar chars desse mundo. - // O bloqueio real (handoff -> world) acontece no botao "Entrar no - // Servidor" do Lobby, que checa World.State antes do C_CHAR_SELECT. - UE_LOG(LogZMMO, Log, TEXT("ServerSelect: mundo %s selecionado (state=%d)"), *WorldId, State); - SelectWorldAndProceed(WorldId); + // O ServerSelect permite escolher qualquer realm (online/maintenance/ + // offline) — o usuario entra no Lobby pra gerenciar chars desse realm. + // O bloqueio real (handoff -> gateway) acontece no botao "Entrar no + // Servidor" do Lobby, que checa Realm.State antes do C_CHAR_SELECT. + UE_LOG(LogZMMO, Log, TEXT("ServerSelect: realm %s selecionado (state=%d)"), *RealmId, State); + SelectRealmAndProceed(RealmId); } UZeusCharServerSubsystem* UUIServerSelectScreen_Base::GetCharServer() const diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIServerSelectScreen_Base.h b/Source/ZMMO/Game/UI/FrontEnd/UIServerSelectScreen_Base.h index e9c4895..039ae02 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/UIServerSelectScreen_Base.h +++ b/Source/ZMMO/Game/UI/FrontEnd/UIServerSelectScreen_Base.h @@ -2,7 +2,7 @@ #include "CoreMinimal.h" #include "UIActivatableScreen_Base.h" -#include "ZMMOWorldEntry.h" +#include "ZMMORealmEntry.h" #include "UIServerSelectScreen_Base.generated.h" class UCommonTextBlock; @@ -12,15 +12,15 @@ class UUIServerCard_Base; class UZeusCharServerSubsystem; /** - * Tela de Server Select — Fase 1 do ARQUITETURA_SERVER_SELECT. + * Tela de Realm Select — Fase 1 do ARQUITETURA_SERVER_SELECT. * * Fluxo: * 1. NativeOnActivated subscreve OnRawMessage do UZeusCharServerSubsystem - * 2. Envia C_WORLD_LIST_REQUEST (payload vazio) - * 3. ParseWorldList monta `Worlds` + * 2. Envia C_REALM_LIST_REQUEST (payload vazio) + * 3. ParseRealmList monta `Realms` * 4. RebuildCards apaga filhos de `CardContainer` e instancia 1 WBP_ServerCard - * por mundo, bindando `OnCardPressed -> SelectWorldAndProceed` - * 5. Dispara `OnWorldListReceived` (BIE) pra extensoes em BP + * por realm, bindando `OnCardPressed -> SelectRealmAndProceed` + * 5. Dispara `OnRealmListReceived` (BIE) pra extensoes em BP */ UCLASS(Abstract, Blueprintable) class ZMMO_API UUIServerSelectScreen_Base : public UUIActivatableScreen_Base @@ -29,16 +29,16 @@ class ZMMO_API UUIServerSelectScreen_Base : public UUIActivatableScreen_Base public: UFUNCTION(BlueprintCallable, Category = "Zeus|ServerSelect") - void RequestWorldList(); + void RequestRealmList(); UFUNCTION(BlueprintCallable, Category = "Zeus|ServerSelect") - void SelectWorldAndProceed(const FString& WorldId); + void SelectRealmAndProceed(const FString& RealmId); UPROPERTY(BlueprintReadOnly, Category = "Zeus|ServerSelect") - TArray Worlds; + TArray Realms; UFUNCTION(BlueprintImplementableEvent, Category = "Zeus|ServerSelect") - void OnWorldListReceived(); + void OnRealmListReceived(); protected: virtual void NativePreConstruct() override; @@ -50,10 +50,10 @@ protected: void HandleCharRawMessage(int32 Opcode, const TArray& Payload); UFUNCTION() - void HandleCardPressed(FString WorldId, uint8 State); + void HandleCardPressed(FString RealmId, uint8 State); UZeusCharServerSubsystem* GetCharServer() const; - void ParseWorldList(const TArray& Payload); + void ParseRealmList(const TArray& Payload); void ApplyStatusUpdate(const TArray& Payload); void RebuildCards(); diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIUserLobbyScreen_Base.cpp b/Source/ZMMO/Game/UI/FrontEnd/UIUserLobbyScreen_Base.cpp index 47f1127..e041ffd 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/UIUserLobbyScreen_Base.cpp +++ b/Source/ZMMO/Game/UI/FrontEnd/UIUserLobbyScreen_Base.cpp @@ -66,27 +66,27 @@ void UUIUserLobbyScreen_Base::RequestCharList() UZeusCharServerSubsystem* Char = GetCharServer(); if (!Char || !Char->IsConnected()) return; - FString WorldId; + FString RealmId; if (UGameInstance* GI = GetGameInstance()) { if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem()) { - WorldId = Flow->GetSelectedWorldId(); + RealmId = Flow->GetSelectedRealmId(); } } - // Wire: uint8 hasFilter + (uint8[16] worldId se hasFilter==1) + // Wire: uint8 hasFilter + (uint8[16] realmId se hasFilter==1) TArray Payload; - if (WorldId.IsEmpty()) + if (RealmId.IsEmpty()) { Payload.Add(0); } else { Payload.Add(1); - if (!WriteUuid16(Payload, WorldId)) + if (!WriteUuid16(Payload, RealmId)) { - UE_LOG(LogZMMO, Warning, TEXT("Lobby: SelectedWorldId invalido (%s) — pedindo sem filtro"), *WorldId); + UE_LOG(LogZMMO, Warning, TEXT("Lobby: SelectedRealmId invalido (%s) — pedindo sem filtro"), *RealmId); Payload.Reset(); Payload.Add(0); } @@ -110,7 +110,7 @@ void UUIUserLobbyScreen_Base::BackToServerSelect() if (!GI) return; if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem()) { - Flow->ClearSelectedWorld(); + Flow->ClearSelectedRealm(); Flow->SetState(EZMMOFrontEndState::ServerSelect); } } @@ -160,7 +160,7 @@ void UUIUserLobbyScreen_Base::ParseCharList(const TArray& Payload) uint64 deleteAt = 0; if (!ReadU64(Payload, Pos, charId64) - || !ReadUuid16(Payload, Pos, E.WorldId) + || !ReadUuid16(Payload, Pos, E.RealmId) || !ReadU8(Payload, Pos, slot) || !ReadStringUtf8(Payload, Pos, E.Name) || !ReadU16(Payload, Pos, classId) @@ -328,25 +328,45 @@ void UUIUserLobbyScreen_Base::HandleCharDeleteCancelAck(const TArray& Pay void UUIUserLobbyScreen_Base::HandleCharSelectOk(const TArray& Payload) { // Wire: uint64 charId + uint16 mapId + float x/y/z + float yaw + - // string worldHost + uint16 worldPort + string handoffToken + string region + // 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 WorldHost, HandoffToken, Region; + uint64 CharId64 = 0; FString GatewayEndpoint, HandoffToken, Region; uint16 MapId16 = 0; - float Px=0, Py=0, Pz=0, Yaw=0; uint16 WorldPort = 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, WorldHost) - || !ReadU16(Payload, Pos, WorldPort) + || !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 -> world=%s:%d mapId=%u token=%s..."), - *WorldHost, WorldPort, static_cast(MapId16), *HandoffToken.Left(8)); + UE_LOG(LogZMMO, Log, TEXT("Lobby: handoff -> realm gateway=%s mapId=%u token=%s..."), + *GatewayEndpoint, static_cast(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; @@ -360,13 +380,13 @@ void UUIUserLobbyScreen_Base::HandleCharSelectOk(const TArray& Payload) } // Fase 3: handoff UDP — apresenta o ticket no `C_CONNECT_REQUEST`. O - // WorldServer valida via GETDEL no Valkey regional. WorldServer envia + // 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()) { - ZeusNet->ConnectToZeusServerWithTicket(WorldHost, static_cast(WorldPort), HandoffToken); + ZeusNet->ConnectToZeusServerWithTicket(GwHost, GwPort, HandoffToken); } else { diff --git a/Source/ZMMO/Game/UI/FrontEnd/UIUserLobbyScreen_Base.h b/Source/ZMMO/Game/UI/FrontEnd/UIUserLobbyScreen_Base.h index 077c0f7..f8c1a34 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/UIUserLobbyScreen_Base.h +++ b/Source/ZMMO/Game/UI/FrontEnd/UIUserLobbyScreen_Base.h @@ -18,13 +18,13 @@ class UZeusCharServerSubsystem; * Tela Lobby (host) — Fase 1.5 do ARQUITETURA_SERVER_SELECT. * * Fluxo: - * 1. NativeOnActivated: pega `SelectedWorldId` do FlowSubsystem, envia - * C_CHAR_LIST_REQUEST filtrado por esse mundo + * 1. NativeOnActivated: pega `SelectedRealmId` do FlowSubsystem, envia + * C_CHAR_LIST_REQUEST filtrado por esse realm * 2. Parse S_CHAR_LIST -> Chars[]; instancia 1 WBP_CharCard por entry * 3. Card "Selecionar" -> C_CHAR_SELECT(charId) -> S_CHAR_SELECT_OK -> - * handoff UDP pro WorldServer + * handoff UDP pro Gateway do Realm * 4. Botao "Criar Personagem" -> mostra page interna `CharacterCreate` - * 5. Botao "Voltar" -> ClearSelectedWorld + Flow.SetState(ServerSelect) + * 5. Botao "Voltar" -> ClearSelectedRealm + Flow.SetState(ServerSelect) * * Pages internas via UWidgetSwitcher: * - PageIndex 0: Lista de chars (CardContainer) @@ -96,6 +96,9 @@ protected: UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr CharCreatePage; UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Text_Title; + // TODO(R4): renomear o widget Text_WorldName -> Text_RealmName no WBP_UserLobby.uasset + // tambem; aqui mantivemos o nome antigo pra nao quebrar o BindWidgetOptional ate + // alguem reabrir o WBP no editor. UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Text_WorldName; UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Btn_Back; UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr Btn_Create; diff --git a/Source/ZMMO/Game/UI/FrontEnd/ZMMOCharSummary.h b/Source/ZMMO/Game/UI/FrontEnd/ZMMOCharSummary.h index c4c6cb6..c765906 100644 --- a/Source/ZMMO/Game/UI/FrontEnd/ZMMOCharSummary.h +++ b/Source/ZMMO/Game/UI/FrontEnd/ZMMOCharSummary.h @@ -43,7 +43,7 @@ struct FZMMOCharSummary FString CharId; UPROPERTY(BlueprintReadOnly, Category = "Zeus|Char") - FString WorldId; + FString RealmId; UPROPERTY(BlueprintReadOnly, Category = "Zeus|Char") int32 Slot = 0; diff --git a/Source/ZMMO/Game/UI/FrontEnd/ZMMORealmEntry.h b/Source/ZMMO/Game/UI/FrontEnd/ZMMORealmEntry.h new file mode 100644 index 0000000..0709953 --- /dev/null +++ b/Source/ZMMO/Game/UI/FrontEnd/ZMMORealmEntry.h @@ -0,0 +1,48 @@ +#pragma once + +#include "CoreMinimal.h" +#include "ZMMORealmEntry.generated.h" + +/** + * Entrada de Realm (cluster Gateway+Provinces) recebida do CharServer via + * S_REALM_LIST. Espelha `RealmRecord` em + * `Server/ZeusCharServer/src/types/realm.types.ts`. + * + * `GatewayEndpoint` e' o endereco "host:port" do Gateway do Realm (UMA + * single string vinda do server — substitui o par host+port da versao + * anterior). Cliente faz split por `:` quando precisa abrir UDP. + * + * State usa o wire raw (0=Offline, 1=Online, 2=Maintenance — ver + * ZMMOWireRealmState em CharServerOpcodes.h). + */ +USTRUCT(BlueprintType) +struct FZMMORealmEntry +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, Category = "Zeus|Realm") + FString RealmId; + + UPROPERTY(BlueprintReadOnly, Category = "Zeus|Realm") + FString Name; + + UPROPERTY(BlueprintReadOnly, Category = "Zeus|Realm") + FString Region; + + /** Endpoint do Gateway no formato "host:port" (ex: "127.0.0.1:7777"). */ + UPROPERTY(BlueprintReadOnly, Category = "Zeus|Realm") + FString GatewayEndpoint; + + UPROPERTY(BlueprintReadOnly, Category = "Zeus|Realm") + int32 Capacity = 0; + + UPROPERTY(BlueprintReadOnly, Category = "Zeus|Realm") + int32 Pop = 0; + + UPROPERTY(BlueprintReadOnly, Category = "Zeus|Realm") + int32 QueueLen = 0; + + /** 0=Offline, 1=Online, 2=Maintenance (vide ZMMOWireRealmState). */ + UPROPERTY(BlueprintReadOnly, Category = "Zeus|Realm") + uint8 State = 0; +}; diff --git a/Source/ZMMO/Game/UI/FrontEnd/ZMMOWorldEntry.h b/Source/ZMMO/Game/UI/FrontEnd/ZMMOWorldEntry.h deleted file mode 100644 index 34b0b8c..0000000 --- a/Source/ZMMO/Game/UI/FrontEnd/ZMMOWorldEntry.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "ZMMOWorldEntry.generated.h" - -/** - * Entrada de mundo (server) recebida do CharServer via S_WORLD_LIST. - * Espelha `WorldRecord` em `Server/ZeusCharServer/src/types/world.types.ts`. - * - * State usa o wire raw (0=Offline, 1=Online, 2=Maintenance — ver - * ZMMOWireWorldState em CharServerOpcodes.h). - */ -USTRUCT(BlueprintType) -struct FZMMOWorldEntry -{ - GENERATED_BODY() - - UPROPERTY(BlueprintReadOnly, Category = "Zeus|World") - FString WorldId; - - UPROPERTY(BlueprintReadOnly, Category = "Zeus|World") - FString WorldName; - - UPROPERTY(BlueprintReadOnly, Category = "Zeus|World") - FString Region; - - UPROPERTY(BlueprintReadOnly, Category = "Zeus|World") - FString Host; - - UPROPERTY(BlueprintReadOnly, Category = "Zeus|World") - int32 Port = 0; - - UPROPERTY(BlueprintReadOnly, Category = "Zeus|World") - int32 Capacity = 0; - - UPROPERTY(BlueprintReadOnly, Category = "Zeus|World") - int32 Population = 0; - - UPROPERTY(BlueprintReadOnly, Category = "Zeus|World") - int32 QueueLen = 0; - - /** 0=Offline, 1=Online, 2=Maintenance (vide ZMMOWireWorldState). */ - UPROPERTY(BlueprintReadOnly, Category = "Zeus|World") - uint8 State = 0; -};