Files
ZMMO/Source/ZMMO/Game/UI/FrontEnd/UICharCard_Base.cpp
Mateus Rodrigues 166650671f feat(frontend): Fase 1 + 1.5 — ServerSelect dinamico + Lobby (chars por mundo)
ServerSelect (Fase 1):
- UIServerSelectScreen_Base: subscribe OnRawMessage + C_WORLD_LIST_REQUEST
- WBP_ServerCard instanciado dinamicamente em GridPanel CardContainer (2 cols)
- UIServerCard_Base: SetFromEntry + OnCardPressed delegate + UI_Button_Master
- Push real-time S_WORLD_STATUS_UPDATE (opcode 2062) atualiza card in-place
- FZMMOWorldEntry struct, EZMMOWorldState enum no wire (offline/online/maint)
- Sem hardcap de slots — cards usam toda largura via ColumnFill weights

Lobby/CharSelect (Fase 1.5):
- UIUserLobbyScreen_Base + WBP_UserLobby (WidgetSwitcher: lista + create)
- C_CHAR_LIST_REQUEST filtrado por SelectedWorldId do FlowSubsystem
- UICharCard_Base + WBP_CharCard com Select/Delete + Accept/Cancel quando
  delete agendado; Text_DeleteCountdown atualiza em tempo real (timer 1s)
- UICharacterCreatePage_Base + WBP_CharacterCreate (Name + ComboBox class)
- Handlers S_CHAR_SELECT_OK (handoff parse), CHAR_CREATE_OK/REJECT,
  CHAR_DELETE_ACK/ACCEPT_ACK/CANCEL_ACK (refresh lista)

Auxiliares + fixes:
- ARQUITETURA_SERVER_SELECT.md + ARQUITETURA_CHARACTER_MODEL.md
- UIFrontEndFlowSubsystem: SelectedWorldId state + transicao Lobby
- DA_FrontEndScreenSet: Lobby -> WBP_UserLobby_C
- UICheckBox_Base + UICommonText_Base (componentes auxiliares)
- BSB_Button_Transparent.Disabled DrawAs=IMAGE -> NoDraw (fix bg branco)
- WBP_ServerCard layout: HBox para chips Region/Ping/Status, SizeBox 10x10
  no Dot verde (anti-overflow); SelfHitTestInvisible em containers (hover)
2026-05-21 20:06:23 -03:00

152 lines
4.4 KiB
C++

#include "UICharCard_Base.h"
#include "ZMMO.h"
#include "CommonTextBlock.h"
#include "Engine/World.h"
#include "TimerManager.h"
#include "UI/Widgets/UIButton_Base.h"
void UUICharCard_Base::NativeConstruct()
{
Super::NativeConstruct();
if (!bBound)
{
if (Btn_Select) Btn_Select->OnClicked().AddUObject(this, &UUICharCard_Base::HandleSelectClicked);
if (Btn_Delete) Btn_Delete->OnClicked().AddUObject(this, &UUICharCard_Base::HandleDeleteClicked);
if (Btn_AcceptDelete) Btn_AcceptDelete->OnClicked().AddUObject(this, &UUICharCard_Base::HandleAcceptDeleteClicked);
if (Btn_CancelDelete) Btn_CancelDelete->OnClicked().AddUObject(this, &UUICharCard_Base::HandleCancelDeleteClicked);
bBound = true;
}
}
void UUICharCard_Base::NativeDestruct()
{
StopCountdownTimer();
if (bBound)
{
if (Btn_Select) Btn_Select->OnClicked().RemoveAll(this);
if (Btn_Delete) Btn_Delete->OnClicked().RemoveAll(this);
if (Btn_AcceptDelete) Btn_AcceptDelete->OnClicked().RemoveAll(this);
if (Btn_CancelDelete) Btn_CancelDelete->OnClicked().RemoveAll(this);
bBound = false;
}
Super::NativeDestruct();
}
void UUICharCard_Base::SetFromSummary(const FZMMOCharSummary& InSummary)
{
Summary = InSummary;
const bool bDeleteScheduled = Summary.DeleteScheduledAtMs > 0;
if (Text_Name)
{
Text_Name->SetText(FText::FromString(Summary.Name));
}
if (Text_Level)
{
Text_Level->SetText(FText::FromString(FString::Printf(TEXT("Lv %d / %d"), Summary.BaseLevel, Summary.JobLevel)));
}
if (Text_Class)
{
Text_Class->SetText(FText::FromString(FString::Printf(TEXT("Classe %d"), Summary.ClassId)));
}
const ESlateVisibility ActionVis = bDeleteScheduled ? ESlateVisibility::Collapsed : ESlateVisibility::Visible;
const ESlateVisibility ConfirmVis = bDeleteScheduled ? ESlateVisibility::Visible : ESlateVisibility::Collapsed;
if (Btn_Select) Btn_Select->SetVisibility(ActionVis);
if (Btn_Delete) Btn_Delete->SetVisibility(ActionVis);
if (Btn_AcceptDelete) Btn_AcceptDelete->SetVisibility(ConfirmVis);
if (Btn_CancelDelete) Btn_CancelDelete->SetVisibility(ConfirmVis);
if (Text_DeleteCountdown)
{
Text_DeleteCountdown->SetVisibility(bDeleteScheduled
? ESlateVisibility::HitTestInvisible
: ESlateVisibility::Collapsed);
}
if (bDeleteScheduled)
{
TickCountdown();
StartCountdownTimer();
}
else
{
StopCountdownTimer();
}
OnSummaryApplied(bDeleteScheduled);
}
void UUICharCard_Base::TickCountdown()
{
if (!Text_DeleteCountdown || Summary.DeleteScheduledAtMs <= 0) return;
// FDateTime::UtcNow().ToUnixTimestamp() retorna segundos UNIX UTC.
// Multiplicamos por 1000 pra equiparar com Summary.DeleteScheduledAtMs (ms).
const int64 NowMs = FDateTime::UtcNow().ToUnixTimestamp() * 1000LL;
const int64 RemainingMs = Summary.DeleteScheduledAtMs - NowMs;
if (RemainingMs <= 0)
{
Text_DeleteCountdown->SetText(FText::FromString(TEXT("Pronto para deletar")));
// Pode habilitar visualmente o Btn_AcceptDelete aqui se quiser feedback
// (ja esta enabled por default; futuramente pode mudar variant cor).
StopCountdownTimer();
return;
}
const int64 TotalSec = RemainingMs / 1000;
if (TotalSec >= 60)
{
const int64 Mins = TotalSec / 60;
const int64 Secs = TotalSec % 60;
Text_DeleteCountdown->SetText(FText::FromString(FString::Printf(
TEXT("Delete em %lldm %02llds"), static_cast<long long>(Mins), static_cast<long long>(Secs))));
}
else
{
Text_DeleteCountdown->SetText(FText::FromString(FString::Printf(
TEXT("Delete em %llds"), static_cast<long long>(TotalSec))));
}
}
void UUICharCard_Base::StartCountdownTimer()
{
UWorld* W = GetWorld();
if (!W) return;
if (CountdownTimerHandle.IsValid()) return;
W->GetTimerManager().SetTimer(CountdownTimerHandle, this,
&UUICharCard_Base::TickCountdown, 1.0f, true /* loop */, 1.0f /* first delay */);
}
void UUICharCard_Base::StopCountdownTimer()
{
if (!CountdownTimerHandle.IsValid()) return;
if (UWorld* W = GetWorld())
{
W->GetTimerManager().ClearTimer(CountdownTimerHandle);
}
CountdownTimerHandle.Invalidate();
}
void UUICharCard_Base::HandleSelectClicked()
{
OnCardSelected.Broadcast(Summary.CharId);
}
void UUICharCard_Base::HandleDeleteClicked()
{
OnCardDeleteRequest.Broadcast(Summary.CharId);
}
void UUICharCard_Base::HandleAcceptDeleteClicked()
{
OnCardDeleteAccept.Broadcast(Summary.CharId);
}
void UUICharCard_Base::HandleCancelDeleteClicked()
{
OnCardDeleteCancel.Broadcast(Summary.CharId);
}