feat(char-spawn): Fase 4 client + UI WBPs paralelo #2

Merged
Mateuus merged 8 commits from feat/frontend-mainmenu-skeleton into main 2026-05-23 02:16:28 -03:00
3 changed files with 85 additions and 27 deletions
Showing only changes of commit 518a346cf5 - Show all commits

View File

@@ -0,0 +1,46 @@
#pragma once
#include "CoreMinimal.h"
/**
* Opcodes do CharServer (WebSocket) consumidos pelo cliente. Espelha
* `Server/ZeusCharServer/src/protocol/CharOpcodes.ts` (faixa 2000-2099).
* Mantido mínimo: só os opcodes que o front-end usa hoje (auth da Login).
* Ampliar conforme as telas (lista/criação/seleção de personagem) chegarem.
*
* Wire: string = uint16 LE (len) + UTF-8; inteiros LE. O cabeçalho de 32B
* é responsabilidade do UZeusCharServerSubsystem (Send/OnRawMessage operam
* sobre o payload já sem header).
*/
namespace ZMMOCharOp
{
// Autenticação — payload começa com uint8 method (ver ZMMOCharAuthMethod)
constexpr int32 C_CHAR_AUTH_REQUEST = 2000;
constexpr int32 S_CHAR_AUTH_OK = 2001; // string(accountId)+string(user)+uint64(expiresMs)
constexpr int32 S_CHAR_AUTH_REJECT = 2002; // uint16(reason)
}
/**
* Método de autenticação enviado como uint8 prefix no payload do
* C_CHAR_AUTH_REQUEST. TOKEN mantém o fluxo Stub/JWT (handoff de outro
* serviço); PASSWORD chama AccountService.login direto no CharServer.
* Espelha `CharAuthMethod` em CharOpcodes.ts.
*/
namespace ZMMOCharAuthMethod
{
constexpr uint8 TOKEN = 0;
constexpr uint8 PASSWORD = 1;
}
/** Espelha `CharRejectReason` em CharOpcodes.ts (subset usado no front-end). */
namespace ZMMOCharRejectReason
{
constexpr uint16 Unknown = 0;
constexpr uint16 InvalidToken = 1;
constexpr uint16 AccountBanned = 2;
constexpr uint16 AlreadyOnline = 3;
constexpr uint16 InvalidCredentials = 4;
constexpr uint16 AccountLocked = 5;
constexpr uint16 AccountSuspended = 6;
constexpr uint16 CredentialsAuthDisabled = 7;
}

View File

@@ -51,19 +51,6 @@ namespace
return bHas ? DesignStyle : Fallback; return bHas ? DesignStyle : Fallback;
} }
// Token de DEV: o StubTokenValidator aceita `dev:<numero>` ou `dev:<uuid>`.
// Um nome de usuário comum NÃO valida — então em dev mapeamos para
// `dev:<numero>` (o número digitado, se houver; senão 1). Auth real
// (HTTP /auth/login + JWT) entra em tarefa dedicada.
FString MakeDevToken(const FString& User)
{
const FString Trimmed = User.TrimStartAndEnd();
if (!Trimmed.IsEmpty() && Trimmed.IsNumeric())
{
return FString::Printf(TEXT("dev:%s"), *Trimmed);
}
return TEXT("dev:1");
}
} }
void UUILoginScreen_Base::NativePreConstruct() void UUILoginScreen_Base::NativePreConstruct()
@@ -210,12 +197,10 @@ void UUILoginScreen_Base::HandleLoginClicked()
// padrão dos launchers comerciais). // padrão dos launchers comerciais).
SaveOrClearRememberedAccess(User); SaveOrClearRememberedAccess(User);
// Escopo dev: token aceito pelo StubTokenValidator (senha ignorada no
// stub; mantida na UI para o fluxo real futuro).
const FString Token = MakeDevToken(User);
FZeusPacketWriter W; FZeusPacketWriter W;
W.WriteStringUtf8(Token); W.WriteUInt8(ZMMOCharAuthMethod::PASSWORD);
W.WriteStringUtf8(User.TrimStartAndEnd());
W.WriteStringUtf8(Pass);
const bool bSent = Char->SendCharRequest( const bool bSent = Char->SendCharRequest(
ZMMOCharOp::C_CHAR_AUTH_REQUEST, W.GetBuffer()); ZMMOCharOp::C_CHAR_AUTH_REQUEST, W.GetBuffer());
@@ -229,7 +214,7 @@ void UUILoginScreen_Base::HandleLoginClicked()
bAwaitingAuth = true; bAwaitingAuth = true;
SetBusy(true); SetBusy(true);
SetStatus(AuthenticatingText); SetStatus(AuthenticatingText);
UE_LOG(LogZMMO, Log, TEXT("Login: C_CHAR_AUTH_REQUEST enviado (token dev).")); UE_LOG(LogZMMO, Log, TEXT("Login: C_CHAR_AUTH_REQUEST enviado (PASSWORD)."));
} }
void UUILoginScreen_Base::HandleBackClicked() void UUILoginScreen_Base::HandleBackClicked()
@@ -278,9 +263,22 @@ void UUILoginScreen_Base::HandleCharRawMessage(int32 Opcode, const TArray<uint8>
R.ReadUInt16(Reason); R.ReadUInt16(Reason);
UE_LOG(LogZMMO, Warning, TEXT("Login: S_CHAR_AUTH_REJECT (reason=%u)."), Reason); UE_LOG(LogZMMO, Warning, TEXT("Login: S_CHAR_AUTH_REJECT (reason=%u)."), Reason);
SetBusy(false); SetBusy(false);
SetStatus(FText::Format(
NSLOCTEXT("ZMMO", "LoginRejectFmt", "{0} (cód. {1})"), FText Message;
RejectedText, FText::AsNumber(Reason))); switch (Reason)
{
case ZMMOCharRejectReason::InvalidCredentials: Message = InvalidCredentialsText; break;
case ZMMOCharRejectReason::AccountLocked: Message = AccountLockedText; break;
case ZMMOCharRejectReason::AccountSuspended: Message = AccountSuspendedText; break;
case ZMMOCharRejectReason::AccountBanned: Message = AccountSuspendedText; break;
case ZMMOCharRejectReason::CredentialsAuthDisabled: Message = CredentialsDisabledText; break;
default:
Message = FText::Format(
NSLOCTEXT("ZMMO", "LoginRejectFmt", "{0} (cód. {1})"),
RejectedText, FText::AsNumber(Reason));
break;
}
SetStatus(Message);
} }
} }

View File

@@ -18,11 +18,13 @@ class UZeusCharServerSubsystem;
* UI_CheckBox_Master). Camada Abstract; o WBP concreto (WBP_Login) herda * UI_CheckBox_Master). Camada Abstract; o WBP concreto (WBP_Login) herda
* DIRETO desta classe (ARQUITETURA.md §3.3). * DIRETO desta classe (ARQUITETURA.md §3.3).
* *
* Auth (escopo atual — dev): ao clicar "Entrar", envia C_CHAR_AUTH_REQUEST * Auth: ao clicar "Entrar", envia C_CHAR_AUTH_REQUEST (opcode 2000) pelo
* (opcode 2000) pelo WebSocket do UZeusCharServerSubsystem com um token * WebSocket do UZeusCharServerSubsystem como `uint8(PASSWORD) +
* `dev:<n>` (StubTokenValidator aceita em dev; auth real virá em tarefa * string(user) + string(password)`. CharServer chama AccountService.login
* dedicada). S_CHAR_AUTH_OK → Flow::RequestEnterServerSelect; reject → * (Argon2id + lockout). S_CHAR_AUTH_OK → Flow::RequestEnterServerSelect;
* mensagem de erro. Sem hard-ref de asset de tema (§5). * reject (InvalidCredentials/AccountLocked/Suspended/etc.) → status
* mapeado. Sem hard-ref de asset de tema (§5). Contas são criadas no
* servidor via `npm run account`.
*/ */
UCLASS(Abstract, Blueprintable) UCLASS(Abstract, Blueprintable)
class ZMMO_API UUILoginScreen_Base : public UUIActivatableScreen_Base class ZMMO_API UUILoginScreen_Base : public UUIActivatableScreen_Base
@@ -51,6 +53,18 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText RejectedText = FText::FromString(TEXT("Acesso negado pelo Gateway.")); FText RejectedText = FText::FromString(TEXT("Acesso negado pelo Gateway."));
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText InvalidCredentialsText = FText::FromString(TEXT("Usuário ou senha inválidos."));
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText AccountLockedText = FText::FromString(TEXT("Conta bloqueada por excesso de tentativas. Aguarde e tente novamente."));
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText AccountSuspendedText = FText::FromString(TEXT("Conta suspensa. Contate o suporte."));
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText CredentialsDisabledText = FText::FromString(TEXT("Autenticação por credenciais indisponível no servidor."));
// ---- Widgets do WBP (nomes esperados) ---- // ---- Widgets do WBP (nomes esperados) ----
UPROPERTY(meta = (BindWidgetOptional)) UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UBorder> Border_Bg; TObjectPtr<UBorder> Border_Bg;