21 Commits

Author SHA1 Message Date
ede49c1777 feat(ui/hud): barras EXP/JOB em % via exp-to-next do snapshot
FZMMOAttributesSnapshot ganha BaseExpToNext/JobExpToNext espelhando o wire do
server. ApplySnapshot liga ExpBar/JobBar pelo ratio (exp / exp-to-next) e os
readouts mostram a porcentagem (ou "MAX" no cap).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 03:58:33 -03:00
484c8d5d4f feat(ui/hud): readouts HP/SP + level multi-cor via RichText (DT_UI_VitalsReadout)
Readouts e LvlText do PlayerVitals viram RichTextBlock com markup inline,
estilizados pelo DT_UI_VitalsReadout (fonte JetBrains Mono Bold):
- HP/SP: atual (branco) + "/" (sep, #3a3830) + max (#6c6860)
- LVL: "LVL " (#b8b3a8) + numero (num, gold-bright #ffd888, maior)

Cores espelham hud_layout_modal_New.html (text-1/2/3/4 + gold-bright).
UZMMOHudPlayerVitalsWidget: HpReadout/SpReadout/ExpReadout/JobReadout/LvlText
agora URichTextBlock; ApplyHpSp/ApplySnapshot montam o markup.

EXP/JOB readout segue exp absoluto (sem %) ate o server enviar exp-to-next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 03:06:44 -03:00
65edf1081b chore(ui/fonts): importa familias Cormorant Garamond, JetBrains Mono e Manrope
Fontes usadas pelos templates do designer (loading_screen.html,
hud_layout_modal_New.html): Cormorant Garamond (titulos serif), JetBrains Mono
(labels/readouts mono) e Manrope (corpo sans). Cada uma com Font composto +
FontFaces estaticos por peso.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 01:08:33 -03:00
e1fca1ae8d feat(ui/hud): painel de vitais (Nome/Job/Level + HP/SP/EXP) substituindo HpSpBar
UZMMOHudPlayerVitalsWidget (novo): sub-widget do HUD com 4 UUIProgressBar_Base
(HP/SP/BASE EXP/JOB EXP) + nome/classe/level. ApplySnapshot anima HP/SP via
SetTargetPrimaryLevel e formata os readouts; ApplyHpSp pro tick rapido.

UZMMOHudWidget propaga o snapshot pro PlayerVitals e resolve identidade: nome
do AZMMOPlayerState.CharName + job via UZMMOJobsLibrary.GetJobDisplayName(ClassId).
HpSpBar legado removido do WBP_HUD.

WBP_PlayerVitalsPanel: layout compacto (520w) com colunas alinhadas (label 28 /
bar FILL / readout 116 right-align) independente do tamanho do numero. Sem portrait.

MIs por barra: UI_M_ProgressBar_SP/BaseEXP/JobEXP/Loading (cores do
hud_layout_modal_New.html).

EXP/JOB readout mostra exp absoluto por ora — barras de EXP aguardam o servidor
enviar exp-to-next no snapshot pra calcular a porcentagem.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:34:03 -03:00
c9492a3910 feat(ui/progressbar): integracao com DT_UI_Styles (FUIStyleProgressBar multi-map + Variant)
FUIStyleProgressBar vira multi-map Hyper-style: TMap<FName, FUIStyleProgressBarVariant>
indexado por variante (HP/MP/XP/Cast/...). Cada variante carrega materiais por
layer (Fill/BG/TrackGrid/Edge/Shimmer) + style (DrawStyle/CornerRadius/Border) +
toggles de efeito.

UUIProgressBar_Base ganha bUseTheme + Variant (dropdown via meta=GetOptions
resolvendo o DT) + RefreshUIStyle() que aplica a variante via ThemeSubsystem
(runtime) ou DT direto (design-time), espelhando o padrao do UUIPanel_Base.
HandleThemeChanged reaplica em troca de tema.

EUIProgressBarStyle movido de UIProgressBar_Base.h para UIStyleTypes.h (usado
pela struct do DT).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:33:43 -03:00
88eb20eefa feat(ui/progressbar): edge (linha + glow) que acompanha PrimaryLevel + auto-hide nos extremos
Novo material UI_M_ProgressBar_Edge: linha vertical sharp no lado direito
do Fill (U = PrimaryLevel) com halo lateral configuravel.

Params:
- EdgeColour: cor + alpha da linha (default branco-creme 0.85)
- EdgeWidth: largura da linha em UV
- EdgeGlowWidth: largura do halo lateral (0 = sem glow)
- EdgeGlowIntensity: forca do halo (0..1)
- PrimaryLevel: posicao (sincronizada pelo C++)

WBP UI_ProgressBar_Master ganha EdgeImage entre Fill e Shimmer (Overlay).

C++ UUIProgressBar_Base:
- BindWidgetOptional EdgeImage + EdgeMID + UPROPERTYs override
- 5 setters runtime (Visible, Colour, Width, GlowWidth, GlowIntensity)
- ApplyPrimaryToMID propaga PrimaryLevel pro EdgeMID em cada tick de animacao
- UpdateEdgeVisibility() helper auto-oculta Edge quando PrimaryLevel <= 0
  ou >= 1 (linha encostaria na borda)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 21:47:54 -03:00
8843ac3d1c feat(ui/progressbar): BG + TrackGrid + Shimmer com RoundedBox nativo do Slate
Substitui o SDF de cantos arredondados no material por DrawAs=RoundedBox
nativo do Slate (OutlineSettings.CornerRadii em pixel space, qualidade
perfeita independente do aspect ratio).

Adiciona 3 materiais separados pra layers de efeito:
- UI_M_ProgressBar_BG: fundo solido (1 param BackgroundColour)
- UI_M_ProgressBar_TrackGrid: linhas verticais a cada N divisoes
  (GridColour, GridDivisions, GridLineThickness)
- UI_M_ProgressBar_Shimmer: faixa de luz animada (Time node) deslizando
  E->D em loop (ShimmerColour, ShimmerWidth, ShimmerSpeed, ShimmerSoftness)

WBP UI_ProgressBar_Master ganha 3 UImage irmaos do FillImage no Overlay
(z-order: BG -> TrackGrid -> Fill -> Shimmer), todos com BindWidgetOptional.

C++ UUIProgressBar_Base:
- Enum EUIProgressBarStyle (Box/Rounded) controla DrawAs dos 4 brushes
- CornerRadius em pixels (0..100), aplicado via OutlineSettings.CornerRadii
- bShowBorder + BorderColour + BorderWidth pra outline so no BG
- bShowTrackGrid / bShowShimmer toggles (visibility do widget)
- ShimmerImage com inset = BorderWidth (slot padding + corner reduzido)
  pra nao sobrepor o outline do BG quando passa pelas extremidades
- UPROPERTYs override pra cada cor/param de cada material com
  InlineEditConditionToggle, setters runtime que persistem o flag

Inclui MI exemplo UI_M_ProgressBar_HP atualizada.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 21:26:50 -03:00
5d44647170 refactor(ui/panel): TMap<EUIPanelTexture> -> TMap<FName> com PostSerialize migration
FUIStylePanel migra de TMap<EUIPanelTexture, FSlateBrush> (enum legacy) para
TMap<FName, FSlateBrush>. Permite adicionar/remover categorias diretamente
no DT_UI_Styles via dropdown (meta=GetOptions) sem mexer no enum em C++.

PanelBackground, PanelOutline e PanelOutlineEffect viraram *_DEPRECATED.
Versoes novas: PanelBackgroundByName, PanelOutlineByName,
PanelOutlineEffectByName.

PostSerialize migra os legacy -> ByName no PostLoad de cada FUIStyleRow.
Apos o primeiro re-save no editor os deprecated ficam vazios e o codigo
vira no-op. PropertyRedirects em DefaultEngine.ini preservam a leitura
do asset antigo (3 entries: PanelBackground, PanelOutline, PanelOutlineEffect).

Atualiza UIPanel_Base pra ler ByName + DT_UI_Styles, UI_Panel_Master e
UI_PlayerStatus_Window resalvos. Inclui nova textura
T_Panel_Outline_Effect_Square_02.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 19:30:48 -03:00
1794e5d418 feat(ui/progressbar): UProgressBar -> UImage com material custom Primary/Secondary
UUIProgressBar_Base envolve UImage + MID em vez do UProgressBar nativo.
Material UI_M_ProgressBar expoe PrimaryLevel/SecondaryLevel (modelo
assimetrico: Secondary so aparece se > Primary), 5 cores (Primary +
ColourBottom, Secondary + ColourBottom, Empty) e gradient toggle com
bilinear remap controlado por GradientSplit.

Cores e gradient ficam expostos como UPROPERTY com InlineEditConditionToggle:
quando o toggle esta off usa o valor da MI atribuida no Brush, quando on
sobrescreve via MID em PreConstruct. Setters runtime tambem marcam o flag
pra persistir o override no proximo re-construct.

Wrappers semanticos: ShowHealPreview, ShowDamageLag, ShowBuffer, ClearSecondary.
NativeTick com ease-out cubica e duracoes separadas pra Primary e Secondary.

Inclui MI exemplo UI_M_ProgressBar_HP e WBP UI_ProgressBar_Master refatorado
(UProgressBar nativo substituido por UImage FillImage com brush apontando
para o material pai).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 19:29:43 -03:00
7fa10b4cb9 fix(ui/loading): memoiza steps quando widget em async load + draggable status
- UIFrontEndFlowSubsystem: HandlePostLoadMap/HandlePlayerSpawned agora
  registram o StepId em PendingLoadingSteps_ quando ActiveLoadingScreen
  ainda eh null (race com RequestAsyncLoad do WBP_Loading). Lambda do
  callback async drena o set apos criar a tela, fechando o loading
  retroativamente. Servidor rapido (TryFinalizeEnterWorld) deixava o
  cliente preso em "entrando no mundo" quando o async load atrasava.

- UIPlayerStatus_Window: herda de UIDraggableWindow_Base (drag + persist
  via UZMMOUISaveGame). Rename CloseBtn -> Button_Close, adiciona
  Background_Panel/Container_Header/Container_Stats, NativeOnKeyDown
  (Alt+A fecha), NativeGetDesiredFocusTarget=self. Remove bIsBackHandler
  pra parar o "Cannot create action binding" no log (projeto nao tem
  DefaultBackAction no CommonUISettings).

- UIInGameFlowSubsystem.SetState: skip re-push do HUD quando voltando de
  StatusWindow/Inventory/Menu pro Playing (Old != None && != Playing) pra
  evitar Construct novo do HUD e o flicker dos textos HP/SP.

- Novos: UIDraggableWindow_Base (drag generico + persistencia) e
  UIWindowSaveGame (TMap<FName, FVector2D> por WindowId).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:50:49 -03:00
079d05c117 feat(ui/loading): tela de loading dinamica data-driven (etapas + dicas)
- UUILoadingScreen_Base (Game/UI/Common/) generica: Configure(Context),
  MarkStepRunning/Done/Failed, OnLoadingComplete delegate, progress bar
  reativa e rotacao automatica de dicas via timer.
- Etapas configuradas por contexto em UZMMOLoadingProfilesDataAsset
  (DA_LoadingProfiles). Perfil inicial FrontEndEnteringWorld:
  Travel -> MapLoaded -> Spawn.
- Dicas em DT_LoadingTips (FText localizavel + filtro por contexto +
  GameplayTag de categoria). 4 dicas seed.
- UIFrontEndFlowSubsystem agora bloqueia a transicao InWorld ate o
  HandlePlayerSpawned (bIsLocal=true) — usuario nao ve mundo vazio.
- WBP_Loading em /UI/Shared/Loading/ (neutro, reusavel pelo InGame futuro);
  DA_FrontEndScreenSet[EnteringWorld] aponta pra ele.
- DefaultGame.ini wire LoadingProfilesAsset + LoadingTipsAsset.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 02:24:48 -03:00
90484876f0 feat(player): CharName do DB no PlayerState + header do Status Window
- PlayerState ganha CharName (Replicated) + SetCharInfo(CharName, GuildName)
- ZMMOPlayerCharacter assina FZeusOnCharInfoReceived; TryApplyCachedCharInfo
  resolve race se S_CHAR_INFO chega antes do pawn possuir
- UIPlayerStatus_Window header le PlayerState->CharName (fallback p/ GetPlayerName)
  em vez do "Mateuus-61E9B19A4FB2" generico

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 02:24:29 -03:00
87d9ca4dae chore(ui): textura do panel gradient quadrado + ignore /AutoCreated
- Adiciona as 3 texturas que o refactor de panel referencia via
  EUIPanelTexture::Rectangle_Square_Gradient_01 (background gradient,
  outline e outline effect) em /UI/Themes/Default/Panel/.
- Ignora Content/AutoCreated/ no .gitignore: pasta de saida do
  ZeusUMGForge web converter, regenerada sob demanda, nao deve ser
  versionada.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 00:14:38 -03:00
037aed30ce refactor(ui/base): panel data-driven 3-brush + button via FButtonStyle
UIPanel_Base: substitui PanelType/Padding por bUseTheme + 3 chaves
(Background/Outline/OutlineEffectKey) lidas de FUIStylePanel. Modo manual
(bUseTheme=false) expoe 3 FSlateBrush diretos via FUIPanelBrushSet pra
override por instancia. Adiciona BindWidgetOptional OutlineEffect (3o
Border sobre Outline pra glow/embossing). Defaults NoDrawType (regra
Hyper: brush vazio nao pinta branco).

UIButton_Base: remove auto-config (Variants enum, Number/Icon/Size/
InputAction/Timer). Vira shell minimal com BindWidgetOptional + helpers
GetActiveUIStyle/GetActiveButtonStyle pra leitura de DT_UI_Styles.

UIStyleTokens: FUIStylePanel ganha PanelOutlineEffect map. FUIPanelBrushSet
vira 3 FSlateBrush diretos. FUIStyleButton refatora pra 7 sub-maps por
variante FName (ButtonStyle=FButtonStyle do Slate, ButtonStroke, ButtonFont,
Text/Icon colors). Tudo com defaults NoDrawType.

UIStyleTypes: adiciona enum EUIPanelTexture::Rectangle_Square_Gradient_01.

Content: UI_Panel_Master e UI_Button_Master_New consomem a API nova;
DT_UI_Styles atualizado com rows/keys novas. UIServerCard_Base ajustado
pro consumer da API nova.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 00:12:58 -03:00
9390ed9da9 feat(ui/status): nova janela com sub-widgets StatRow/BonusRow + cleanup
- 3 novas classes C++ (UUIPlayerStatus_Window/StatRow/BonusRow) substituem
  UZMMOStatusWindowWidget (removida); StatRow/BonusRow sao reutilizaveis
  via BindWidgetOptional + delegate OnPlusClicked(StatId).
- Window bindea no UZMMOAttributeComponent (PlayerState) e popula header
  (CharName, Job via UZMMOJobsSubsystem, BaseLevel/JobLevel) + 14 rows
  (6 primarios em StatRow com botao "+", 8 derivados em BonusRow).
- Fix spam-click do "+": bPlusButtonsLocked desabilita botoes ate
  OnStatAllocReply do server ou proximo snapshot.
- WBPs movidos pra /UI/Screens/PlayerStatus/, material pra /UI/Materials/.
- 58 widgets renomeados seguindo convencao
  (Frame_/Layout_/Panel_/Container_/Border_/Text_/Image_/Spacer_/List_).
- DA_InGameScreenSet auto-atualizado pelo move.
- Remove WBP_StatusWindow inline (legado).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 00:09:28 -03:00
28bfa9a567 feat(attributes): MATK range min~max no snapshot + display
Espelha breaking change do server (S_ATTRIBUTE_SNAPSHOT_FULL agora envia
matkBaseMin/matkBaseMax em vez de matkBase unico) para suportar MATK
pre-renewal puro (rathena status_base_matk_min/max).

- FZeusAttributesPayload: MatkBaseMin/Max em vez de MatkBase
- FZMMOAttributesSnapshot: idem (BlueprintReadOnly)
- ZMMOStatusWindowWidget: exibe "MATK min ~ max + equip"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 01:52:48 -03:00
6391372f00 feat(jobs): Jobs.1 client UI infra (handler + library + tecla J) + UI tweaks
Infraestrutura cliente da promocao de classe (Jobs.1). WBP_JobChangePanel
NAO esta neste commit — foi exploracao descartada (criar UI dinamica de
botoes via MCP BP graph esbarrou em limites do CreateWidget node). A
fundacao C++ fica pronta pra ser usada quando voltarmos pra UI.

INFRA C++ NOVA (Source/ZMMOJobs/)
* UZMMOJobChangeNetworkHandler (WorldSubsystem novo) — bind no
  OnJobChangeResult do plugin e re-broadcast via FZMMOOnJobChangeResultBP
  (dynamic multicast assignavel em BP). WBP futura escuta direto.
* UZMMOJobsSubsystem helpers: GetJobsByTier(EZMMOJobTier) +
  GetEligibleNextJobs(currentClassId) — filtra por ParentClassId. Pure.
* UZMMOJobsLibrary BPFL wrappers + SendJobChangeRequest convenience
  (delega pro UZeusNetworkSubsystem). Dep ZeusNetwork adicionada no
  Build.cs.

ENUM + INPUT
* EZMMOInGameUIState ganha JobChangePanel (entre StatusWindow e
  Inventory). DA_InGameScreenSet pendente de receber entry quando
  WBP for criada (tecla J vai falhar silenciosamente ate la).
* ZMMOPlayerController: tecla J -> ToggleJobChangePanel ->
  Flow->ToggleScreen(JobChangePanel). Espelha pattern do Alt+A do
  StatusWindow (InputComponent legacy BindKey + FInputChord).

UI TWEAKS
* WBP_StatusWindow + DT_UI_Styles: ajustes visuais no Editor durante a
  sessao (sem mudanca funcional documentavel — provavelmente fontes/
  spacing/cores).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 01:37:04 -03:00
f832c1a4df fix(log): remove HandleZeusNetworkLog forwarder (duplicava LogZMMO)
UZMMOGameInstance assinava OnNetworkLog do plugin e re-logava em LogZMMO
com prefix [Zeus]. Resultado: cada linha do plugin aparecia 2x no console
(LogZeusNetwork + LogZMMO). Era diagnostico inicial da fase de bring-up
(sessao 2026-05-07); agora so' polui o log.

Bind removido em BindZeusDelegates. Funcao HandleZeusNetworkLog mantida no
.h/.cpp (UFUNCTION dynamic delegate target) com comentario explicando como
reativar via descomentar a linha do AddDynamic.

Plugin continua logando em LogZeusNetwork (console) + Saved/Logs/ZeusNetwork/
(arquivo proprio) — diagnostico nao foi perdido, so' deduplicado.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 01:36:34 -03:00
c3df705a7a feat(jobs): Jobs.1 client — DA expandido + 7 DA_Job assets organizados por tier
* UZMMOJobDataAsset (Source/ZMMOJobs/Public/) ganha 3 campos novos
  espelhando o JobDef do server:
    - Tier             (EZMMOJobTier)
    - ParentClassId    (int32)
    - AllowedWeapons   (TArray<EZMMOWeaponType>)

* Novo header ZMMOJobTypes.h com 2 UENUM(BlueprintType):
    - EZMMOJobTier      (6 valores: Beginner..Mastery)
    - EZMMOWeaponType   (19 valores: Fist..Gun)
  Todos com UMETA(DisplayName="pt-BR") pro Editor mostrar nomes
  amigaveis em dropdowns. Ordem casa byte-a-byte com EJobTier/
  EWeaponType do server (necessario pq snapshot carrega tier u8 + mask).

* 7 DA_Job_*.uasset criados via MCP Python, organizados por tier:
    Content/ZMMO/Data/Jobs/Beginner/
      DA_Job_Novice    (ClassId=0, AllowedWeapons=[Fist,Dagger])
    Content/ZMMO/Data/Jobs/Vocation/
      DA_Job_Warrior   (ClassId=1, 8 armas: Sword+Spear+Axe variations)
      DA_Job_Arcanist  (ClassId=2, 5 armas: Staff+Book)
      DA_Job_Ranger    (ClassId=3, 5 armas: Bow+Musical+Whip)
      DA_Job_Artisan   (ClassId=4, 5 armas: Sword1H+Mace1H+Axe1H)
      DA_Job_Rogue     (ClassId=5, 5 armas: Sword1H+Bow+Katar)
      DA_Job_Devout    (ClassId=6, 5 armas: Mace+Staff1H+Knuckle)
  ParentClassId=0 (Novice) em todas Vocacoes. Tier=Vocation.
  JobIcon/Sound/Anim ficam None — preencher conforme arte for produzida.

  ZMMOJobsSubsystem busca por classe (GetAssetsByClass), entao a
  reorganizacao em subpastas NAO requer mudanca em .cpp/.h.

* ZMMOAttributeTypes.h: comentario de opcode 1200 -> 1500 (espelha
  refactor Open-Closed do server ranges; valores wire format mudaram).

Boot esperado: [ZMMOJobsSubsystem] Initialize: 7 job(s) carregados.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 23:53:34 -03:00
a354d9eefc chore(jobs): renomeia display "Aprendiz" -> "Novato"
DA_Job_Novice DisplayName e docs de arquitetura (3 ARQUITETURA*.md)
sincronizados com a nova terminologia PT-BR. Server (Novice.json +
JobsDatabase.cpp) sera commitado separado no repo do ZeusServerEngine.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 21:29:12 -03:00
ca304a99dd Merge pull request 'feat(client): AttributeSystem (Fase 1-3) + ZMMOJobs + UI in-game' (#3) from feat/attribute-system-mmo into main
Reviewed-on: #3
2026-05-23 21:06:47 -03:00
136 changed files with 4003 additions and 1283 deletions

3
.gitignore vendored
View File

@@ -29,3 +29,6 @@ desktop.ini
*.log *.log
*.pdb *.pdb
.vscode/ .vscode/
# Hyper / ZeusUMGForge web converter outputs (assets gerados, não versionar)
Content/AutoCreated/

View File

@@ -9,7 +9,7 @@
> **Extensões da arquitetura** (documentos normativos paralelos, escopo específico): > **Extensões da arquitetura** (documentos normativos paralelos, escopo específico):
> - [`ARQUITETURA_SERVER_SELECT.md`](ARQUITETURA_SERVER_SELECT.md) — Server Select, lista de mundos, handoff CharServer↔WorldServer, multi-region, queue, Valkey. Roadmap em 7 fases. > - [`ARQUITETURA_SERVER_SELECT.md`](ARQUITETURA_SERVER_SELECT.md) — Server Select, lista de mundos, handoff CharServer↔WorldServer, multi-region, queue, Valkey. Roadmap em 7 fases.
> - [`ARQUITETURA_CHARACTER_MODEL.md`](ARQUITETURA_CHARACTER_MODEL.md) — Stats primários (STR/AGI/VIT/INT/DEX/LUK), classes (Aprendiz → especializações), fórmulas de stats derivados (ATK/MATK/DEF/...), `jobs.yml` data-driven, stat allocation server-side. Espelha rathena. > - [`ARQUITETURA_CHARACTER_MODEL.md`](ARQUITETURA_CHARACTER_MODEL.md) — Stats primários (STR/AGI/VIT/INT/DEX/LUK), classes (Novato → especializações), fórmulas de stats derivados (ATK/MATK/DEF/...), `jobs.yml` data-driven, stat allocation server-side. Espelha rathena.
> **Sumário de mudanças (2026-05-11):** adicionada secção de **temas sazonais > **Sumário de mudanças (2026-05-11):** adicionada secção de **temas sazonais
> de UI** (`UI/Themes/`), com tutorial §4.7, regra de "no hard ref a textura > de UI** (`UI/Themes/`), com tutorial §4.7, regra de "no hard ref a textura

View File

@@ -6,7 +6,7 @@
## Context ## Context
O ZMMO usa modelo de atributos estilo Ragnarok Online — 6 stats primários que o jogador aloca pontos (STR/AGI/VIT/INT/DEX/LUK), com stats derivados calculados a partir deles + level + classe + equip. Jogador nasce **Aprendiz** (Novice) e especializa em outras classes ao chegar a critérios (job level + quest de mudança). O ZMMO usa modelo de atributos estilo Ragnarok Online — 6 stats primários que o jogador aloca pontos (STR/AGI/VIT/INT/DEX/LUK), com stats derivados calculados a partir deles + level + classe + equip. Jogador nasce **Novato** (Novice) e especializa em outras classes ao chegar a critérios (job level + quest de mudança).
Este doc fixa: Este doc fixa:
1. O que persiste no MySQL (CharServer) vs o que vive em memória (WorldServer). 1. O que persiste no MySQL (CharServer) vs o que vive em memória (WorldServer).
@@ -58,10 +58,10 @@ Padrão rathena: `db/re/job_stats.yml` carregado no boot. Hot-reloadable.
| Componente | Quem decide | Quem persiste | | Componente | Quem decide | Quem persiste |
|---|---|---| |---|---|---|
| Criar char (stats iniciais, class=Aprendiz) | CharServer (valida) | CharServer → MySQL | | Criar char (stats iniciais, class=Novato) | CharServer (valida) | CharServer → MySQL |
| Stat allocation (gastar `status_point` em STR/etc.) | CharServer **ou** WorldServer | CharServer → MySQL (via writeback) | | Stat allocation (gastar `status_point` em STR/etc.) | CharServer **ou** WorldServer | CharServer → MySQL (via writeback) |
| Subir de level / ganhar EXP | WorldServer (kill mob, quest) | WorldServer → writeback → CharServer → MySQL | | Subir de level / ganhar EXP | WorldServer (kill mob, quest) | WorldServer → writeback → CharServer → MySQL |
| Mudar classe (Aprendiz → Espadachim) | WorldServer (cumpre quest) | WorldServer → writeback → CharServer → MySQL | | Mudar classe (Novato → Espadachim) | WorldServer (cumpre quest) | WorldServer → writeback → CharServer → MySQL |
| Recalcular ATK/MATK/DEF/etc. | WorldServer (memória, toda hora) | NÃO persiste | | Recalcular ATK/MATK/DEF/etc. | WorldServer (memória, toda hora) | NÃO persiste |
| Casting de skill | WorldServer | NÃO persiste (efêmero) | | Casting de skill | WorldServer | NÃO persiste (efêmero) |
| Resultado de skill (HP perdido, item dropado) | WorldServer | WorldServer → writeback | | Resultado de skill (HP perdido, item dropado) | WorldServer | WorldServer → writeback |
@@ -260,7 +260,7 @@ version: 1
jobs: jobs:
- id: 0 - id: 0
name: Novice # nome técnico (inglês, estável) name: Novice # nome técnico (inglês, estável)
display_name_ptbr: Aprendiz display_name_ptbr: Novato
parent_job: null parent_job: null
max_base_level: 99 max_base_level: 99
max_job_level: 10 max_job_level: 10
@@ -322,7 +322,7 @@ jobs:
## 5. Fluxos ## 5. Fluxos
### 5.1 Criação de personagem (Aprendiz) ### 5.1 Criação de personagem (Novato)
``` ```
Cliente → C_CHAR_CREATE { name, world_id, class_id=NOVICE, appearance } Cliente → C_CHAR_CREATE { name, world_id, class_id=NOVICE, appearance }
@@ -342,7 +342,7 @@ CharServer:
4. → S_CHAR_CREATE_OK 4. → S_CHAR_CREATE_OK
``` ```
**Nota:** o player **sempre** nasce Aprendiz. Outras classes só via job change in-game (Fase pós-Fase 3). **Nota:** o player **sempre** nasce Novato. Outras classes só via job change in-game (Fase pós-Fase 3).
### 5.2 Stat allocation (após level up) ### 5.2 Stat allocation (após level up)
@@ -377,7 +377,7 @@ WorldServer (em kill mob / complete quest):
Mesmo padrão pra job_exp/job_level (com skill_point em vez de status_point). Mesmo padrão pra job_exp/job_level (com skill_point em vez de status_point).
### 5.4 Job change (Aprendiz → Espadachim, etc.) ### 5.4 Job change (Novato → Espadachim, etc.)
``` ```
WorldServer (player cumpriu quest): WorldServer (player cumpriu quest):
@@ -571,13 +571,13 @@ Audit log entries (Fase 3 do ServerSelect doc): toda mudança de zeny>1000, leve
## 8. Roadmap de implementação ## 8. Roadmap de implementação
### Fase A — Schema + criação de char Aprendiz (parte da Fase 1 do ServerSelect) ### Fase A — Schema + criação de char Novato (parte da Fase 1 do ServerSelect)
- Schema `characters` com todos os campos (já especificado no `ARQUITETURA_SERVER_SELECT.md`). - Schema `characters` com todos os campos (já especificado no `ARQUITETURA_SERVER_SELECT.md`).
- `jobs.yml` apenas com `Novice` (Aprendiz) — outras classes ficam pra B. - `jobs.yml` apenas com `Novice` (Novato) — outras classes ficam pra B.
- CharServer carrega `jobs.yml` no boot. - CharServer carrega `jobs.yml` no boot.
- `C_CHAR_CREATE` valida `class_id == NOVICE` e usa defaults do yml. - `C_CHAR_CREATE` valida `class_id == NOVICE` e usa defaults do yml.
- Cliente UI: tela de criação com nome + appearance (sem stat allocation aqui — Aprendiz nasce com todos os stats=1). - Cliente UI: tela de criação com nome + appearance (sem stat allocation aqui — Novato nasce com todos os stats=1).
### Fase B — Classes adicionais + job change ### Fase B — Classes adicionais + job change
@@ -628,7 +628,7 @@ Audit log entries (Fase 3 do ServerSelect doc): toda mudança de zeny>1000, leve
1. **Stats primários flat** na tabela `characters` (str/agi/vit/int/dex/luk + level/exp + hp/sp/max_hp/max_sp + status_point/skill_point + zeny + class_id). **Sem entidade separada.** 1. **Stats primários flat** na tabela `characters` (str/agi/vit/int/dex/luk + level/exp + hp/sp/max_hp/max_sp + status_point/skill_point + zeny + class_id). **Sem entidade separada.**
2. **Stats derivados NUNCA persistidos** — sempre recalculados em runtime no WorldServer (`StatusCalc::ComputeAll`). 2. **Stats derivados NUNCA persistidos** — sempre recalculados em runtime no WorldServer (`StatusCalc::ComputeAll`).
3. **Jobs data-driven** via `Server/ZeusCharServer/data/jobs.yml` (versionado no git, hot-reloadable). `characters.class_id` é lookup lógico. 3. **Jobs data-driven** via `Server/ZeusCharServer/data/jobs.yml` (versionado no git, hot-reloadable). `characters.class_id` é lookup lógico.
4. **Player nasce Aprendiz** (`class_id=NOVICE`); especialização via quest in-game (Fase B). 4. **Player nasce Novato** (`class_id=NOVICE`); especialização via quest in-game (Fase B).
5. **Stat allocation acontece no WorldServer** (não CharServer). Validação `cost = (s-1)/10 + 2` server-side. 5. **Stat allocation acontece no WorldServer** (não CharServer). Validação `cost = (s-1)/10 + 2` server-side.
6. **Anti-cheat:** toda mudança de estado é server-authoritative. Cliente apenas renderiza. 6. **Anti-cheat:** toda mudança de estado é server-authoritative. Cliente apenas renderiza.
7. **Fórmulas espelham rathena** (`status_calc_pc_` em `src/map/status.cpp:4996`) — adapta valores pra balanceamento Zeus depois. 7. **Fórmulas espelham rathena** (`status_calc_pc_` em `src/map/status.cpp:4996`) — adapta valores pra balanceamento Zeus depois.

View File

@@ -311,11 +311,11 @@ ALTER TABLE characters
**Notas:** **Notas:**
- `world_id` é nullable (sem chars em prod hoje — evita downtime futuro). Validação no service: novos chars devem ter `world_id`. - `world_id` é nullable (sem chars em prod hoje — evita downtime futuro). Validação no service: novos chars devem ter `world_id`.
- Estrutura flat segue rathena (`.bases/rathena/rathena-master/sql-files/main.sql:209-296`) — padrão da indústria emuladora. Sem entidade `character_stats` separada (1:1 FK seria JOIN inútil). - Estrutura flat segue rathena (`.bases/rathena/rathena-master/sql-files/main.sql:209-296`) — padrão da indústria emuladora. Sem entidade `character_stats` separada (1:1 FK seria JOIN inútil).
- Defaults vêm da row Aprendiz no `jobs.yml` (ver doc Character Model). - Defaults vêm da row Novato no `jobs.yml` (ver doc Character Model).
- **Stats derivados (ATK/MATK/DEF/MDEF/hit/flee/crit/aspd) NÃO entram aqui** — são sempre recalculados em runtime no WorldServer via fórmulas. Persistir é bug fest (esquece atualizar quando muda equip/buff/level). - **Stats derivados (ATK/MATK/DEF/MDEF/hit/flee/crit/aspd) NÃO entram aqui** — são sempre recalculados em runtime no WorldServer via fórmulas. Persistir é bug fest (esquece atualizar quando muda equip/buff/level).
- `version` (optimistic locking) usado em writeback da Fase 3. - `version` (optimistic locking) usado em writeback da Fase 3.
> **Para o detalhamento do modelo de personagem** (stats primários vs derivados, fórmulas de ATK/MATK/DEF, jobs.yml, stat allocation, leveling curve, classes Aprendiz → especialização), ver doc dedicado [`ARQUITETURA_CHARACTER_MODEL.md`](ARQUITETURA_CHARACTER_MODEL.md). > **Para o detalhamento do modelo de personagem** (stats primários vs derivados, fórmulas de ATK/MATK/DEF, jobs.yml, stat allocation, leveling curve, classes Novato → especialização), ver doc dedicado [`ARQUITETURA_CHARACTER_MODEL.md`](ARQUITETURA_CHARACTER_MODEL.md).
### Schema Valkey (Fase 2+) ### Schema Valkey (Fase 2+)

View File

@@ -86,6 +86,15 @@ GameViewportClientClassName=/Script/CommonUI.CommonGameViewportClient
+ActiveClassRedirects=(OldClassName="TP_ThirdPersonCharacter",NewClassName="ZMMOPlayerCharacter") +ActiveClassRedirects=(OldClassName="TP_ThirdPersonCharacter",NewClassName="ZMMOPlayerCharacter")
+ActiveClassRedirects=(OldClassName="ZMMOCharacter",NewClassName="ZMMOPlayerCharacter") +ActiveClassRedirects=(OldClassName="ZMMOCharacter",NewClassName="ZMMOPlayerCharacter")
[CoreRedirects]
; Migração v1→v2 do FUIStylePanel: TMap<EUIPanelTexture,...> (Enum) → TMap<FName,...>.
; Os campos *_DEPRECATED preservam a leitura do asset antigo; PostSerialize copia
; pros novos ByName e após re-save os legacy ficam vazios. Os redirects podem
; ser removidos quando todos os DTs do projeto tiverem sido re-salvos.
+PropertyRedirects=(OldName="/Script/ZMMO.UIStylePanel.PanelBackground",NewName="/Script/ZMMO.UIStylePanel.PanelBackground_DEPRECATED")
+PropertyRedirects=(OldName="/Script/ZMMO.UIStylePanel.PanelOutline",NewName="/Script/ZMMO.UIStylePanel.PanelOutline_DEPRECATED")
+PropertyRedirects=(OldName="/Script/ZMMO.UIStylePanel.PanelOutlineEffect",NewName="/Script/ZMMO.UIStylePanel.PanelOutlineEffect_DEPRECATED")
[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] [/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings]
bEnablePlugin=True bEnablePlugin=True
bAllowNetworkConnection=True bAllowNetworkConnection=True

View File

@@ -35,6 +35,12 @@ ScreenSetAsset=/Game/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.DA_FrontEndScreenSet
; cliente resolve `mapId -> ClientLevel/spawns` via FZMMOMapDef rows. ; cliente resolve `mapId -> ClientLevel/spawns` via FZMMOMapDef rows.
MapsTableAsset=/Game/ZMMO/Data/World/DT_Maps.DT_Maps MapsTableAsset=/Game/ZMMO/Data/World/DT_Maps.DT_Maps
; Loading dinâmico (etapas + dicas). DA_LoadingProfiles tem 1 perfil por
; contexto (EZMMOLoadingContext); DT_LoadingTips rotaciona durante o load.
; Cliente local marca etapas via eventos (sem opcode novo).
LoadingProfilesAsset=/Game/ZMMO/Data/UI/Loading/DA_LoadingProfiles.DA_LoadingProfiles
LoadingTipsAsset=/Game/ZMMO/Data/UI/Loading/DT_LoadingTips.DT_LoadingTips
; ----------------------------------------------------------------------------- ; -----------------------------------------------------------------------------
; UI in-game (PR 19+). Espelho do front-end pattern: ; UI in-game (PR 19+). Espelho do front-end pattern:
; - ScreenSetAsset mapeia EZMMOInGameUIState -> WBP (Playing -> WBP_HUD, ; - ScreenSetAsset mapeia EZMMOInGameUIState -> WBP (Playing -> WBP_HUD,

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -27,6 +27,7 @@ enum class EZMMOInGameUIState : uint8
None, ///< Antes do player local spawnar; UI in-game inativa. None, ///< Antes do player local spawnar; UI in-game inativa.
Playing, ///< HUD principal visivel (HP/SP/level/etc). Playing, ///< HUD principal visivel (HP/SP/level/etc).
StatusWindow, ///< Janela de atributos + botões de alocacao (Fase 4). StatusWindow, ///< Janela de atributos + botões de alocacao (Fase 4).
JobChangePanel, ///< Painel de promocao de classe (Jobs.1). Toggle tecla J.
Inventory, ///< (futuro) Bag/equipamentos. Inventory, ///< (futuro) Bag/equipamentos.
SkillTree, ///< (futuro) Arvore de skills. SkillTree, ///< (futuro) Arvore de skills.
EscapeMenu, ///< (futuro) Pausa, settings, logout. EscapeMenu, ///< (futuro) Pausa, settings, logout.

View File

@@ -0,0 +1,66 @@
#pragma once
#include "CoreMinimal.h"
#include "LoadingTypes.generated.h"
/**
* Contexto do loading screen genérico. Cada contexto resolve um perfil
* (lista de etapas) no UZMMOLoadingProfilesDataAsset.
*
* Cliente local rastreia o progresso assinando eventos próprios
* (HandlePostLoadMap, OnPlayerSpawned, etc) — server não envia opcode de
* progresso, ver decisão em [[project_ui_loading_dynamic]].
*/
UENUM(BlueprintType)
enum class EZMMOLoadingContext : uint8
{
None,
FrontEndEnteringWorld, ///< FrontEnd → mundo (handoff CharServer→WorldServer + spawn).
InGameEnteringInstance, ///< Mundo → dungeon/instance (futuro).
InGameRespawn, ///< Pós-morte, retorno ao mundo (futuro).
};
UENUM(BlueprintType)
enum class EZMMOLoadingStepStatus : uint8
{
Pending, ///< Ainda não começou.
Running, ///< Em andamento (o "barber pole").
Done, ///< Concluída.
Failed, ///< Falhou (com reason em Status).
};
/**
* Uma etapa do loading. StepId é o identificador estável usado por código
* pra chamar MarkStepDone(StepId); Label é o texto localizado mostrado.
*/
USTRUCT(BlueprintType)
struct FZMMOLoadingStepDef
{
GENERATED_BODY()
/** Id estável (ex.: "Travel", "MapLoaded", "Connect", "Spawn"). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
FName StepId;
/** Texto localizado ("Carregando mapa...", "Conectando..."). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
FText Label;
};
/**
* Perfil de loading por contexto. Designer edita no DA — código só
* consulta StepId pra avançar etapa.
*/
USTRUCT(BlueprintType)
struct FZMMOLoadingProfile
{
GENERATED_BODY()
/** Etapas em ordem. ProgressBar = #Done / #Steps. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
TArray<FZMMOLoadingStepDef> Steps;
/** Título da tela (vazio = usa default da classe). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
FText TitleOverride;
};

View File

@@ -0,0 +1,48 @@
#include "UIStyleTokens.h"
#include "UObject/Class.h"
#include "UObject/UnrealType.h"
void FUIStylePanel::PostSerialize(const FArchive& Ar)
{
// Migração v1→v2: TMap<EUIPanelTexture, ...> (legacy) → TMap<FName, ...> (atual).
// Roda no PostLoad de cada FUIStyleRow; depois do primeiro re-save no editor
// os campos *_DEPRECATED ficam vazios e este código vira no-op.
if (!Ar.IsLoading())
{
return;
}
const UEnum* EnumPtr = StaticEnum<EUIPanelTexture>();
if (EnumPtr == nullptr)
{
return;
}
auto MigrateOne = [EnumPtr](
TMap<EUIPanelTexture, FSlateBrush>& Legacy,
TMap<FName, FSlateBrush>& ByName)
{
if (Legacy.IsEmpty())
{
return;
}
for (const TPair<EUIPanelTexture, FSlateBrush>& Pair : Legacy)
{
if (Pair.Key == EUIPanelTexture::None)
{
continue;
}
const FName KeyName(*EnumPtr->GetAuthoredNameStringByValue(static_cast<int64>(Pair.Key)));
if (!ByName.Contains(KeyName))
{
ByName.Add(KeyName, Pair.Value);
}
}
Legacy.Reset();
};
MigrateOne(PanelBackground_DEPRECATED, PanelBackgroundByName);
MigrateOne(PanelOutline_DEPRECATED, PanelOutlineByName);
MigrateOne(PanelOutlineEffect_DEPRECATED, PanelOutlineEffectByName);
}

View File

@@ -3,6 +3,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "Fonts/SlateFontInfo.h" #include "Fonts/SlateFontInfo.h"
#include "Styling/SlateBrush.h" #include "Styling/SlateBrush.h"
#include "Styling/SlateTypes.h"
#include "Layout/Margin.h" #include "Layout/Margin.h"
#include "Templates/SubclassOf.h" #include "Templates/SubclassOf.h"
#include "Engine/Texture2D.h" #include "Engine/Texture2D.h"
@@ -195,117 +196,13 @@ struct ZMMO_API FUITextStyle
}; };
// ===================================================================== // =====================================================================
// FUIStyleButton — padrão multi-map (Hyper-style). 3 sub-mapas com a MESMA // FUIStyleButton — padrão Hyper "Buttons": 7 sub-mapas independentes
// chave (nome da variante: "Primary", "Secondary", "Danger", "Ghost", ou // indexados pela MESMA chave FName de variante (autor define livremente:
// customizada). UUIButton_Base.Variant é FName e busca em cada sub-mapa. // "Default", "Square", "Topbar", "Home_Main_Menu", ...). UUIButton_Base.Variant
// O struct FUIStyleButtonVariant (composto, FLAT) permanece com a forma // é FName e busca o pedaço necessário em cada sub-mapa. ButtonStyle traz
// antiga — assim o hook BP_ApplyUIStyle dos WBP_Master continua funcional. // FButtonStyle do Slate completo (Normal/Hovered/Pressed/Disabled como
// FSlateBrush, Foregrounds, Padding, Sounds).
// ===================================================================== // =====================================================================
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleButtonBackground
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BgNormal = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BgHover = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BgPressed = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BgDisabled = FLinearColor(0.f, 0.f, 0.f, 0.42f);
/** Textura opcional como background (Hyper-style). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
TObjectPtr<UTexture2D> BackgroundTexture = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FMargin BackgroundMargin = FMargin(8.f);
};
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleButtonStroke
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BorderNormal = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BorderHover = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button", meta = (ClampMin = "0"))
float BorderWidth = 1.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button", meta = (ClampMin = "0"))
float CornerRadius = 8.f;
};
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleButtonFont
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor TextColor = FLinearColor::White;
/** CommonTextStyle define fonte/tamanho/peso; TextColor sobrescreve a cor. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
TSubclassOf<UCommonTextStyle> TextStyle;
};
/**
* View FLAT composta da variante de botão (Background + Stroke + Font).
* Mantida com a forma antiga (BgNormal/BgHover/.../TextStyle no topo) para
* NÃO QUEBRAR o hook BP_ApplyUIStyle nos WBP_Master existentes. É construída
* pelo UUIButton_Base::ResolveVariant a partir dos 3 sub-mapas do tema.
*/
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleButtonVariant
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BgNormal = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BgHover = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BgPressed = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BgDisabled = FLinearColor(0.f, 0.f, 0.f, 0.42f);
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BorderNormal = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor BorderHover = FLinearColor::Transparent;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FLinearColor TextColor = FLinearColor::White;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
TSubclassOf<UCommonTextStyle> TextStyle;
// Campos extras vindos das sub-categorias (background texture + stroke):
UPROPERTY(BlueprintReadOnly, Category = "UI Style|Button")
TObjectPtr<UTexture2D> BackgroundTexture = nullptr;
UPROPERTY(BlueprintReadOnly, Category = "UI Style|Button")
FMargin BackgroundMargin = FMargin(8.f);
UPROPERTY(BlueprintReadOnly, Category = "UI Style|Button")
float BorderWidth = 1.f;
UPROPERTY(BlueprintReadOnly, Category = "UI Style|Button")
float CornerRadius = 8.f;
};
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleButton struct ZMMO_API FUIStyleButton
{ {
@@ -313,163 +210,138 @@ struct ZMMO_API FUIStyleButton
FUIStyleButton() FUIStyleButton()
{ {
// ---- Backgrounds (Primary/Secondary/Danger/Ghost) ---- // Defaults "no draw" pra não pintar branco quando o brush estiver vazio
FUIStyleButtonBackground PriBg; // (regra Hyper — ver feedback_slatebrush_empty_white). Autor preenche
PriBg.BgNormal = FLinearColor(FColor(201, 167, 90)); // gold // no DT_UI_Styles.
PriBg.BgHover = FLinearColor(FColor(231, 200, 115)); // gold-hi FButtonStyle DefaultStyle;
PriBg.BgPressed = FLinearColor(FColor(201, 167, 90)); DefaultStyle.Normal.DrawAs = ESlateBrushDrawType::NoDrawType;
PriBg.BgDisabled = FLinearColor(FColor(201, 167, 90, 107)); DefaultStyle.Hovered.DrawAs = ESlateBrushDrawType::NoDrawType;
Backgrounds.Add(TEXT("Primary"), PriBg); DefaultStyle.Pressed.DrawAs = ESlateBrushDrawType::NoDrawType;
DefaultStyle.Disabled.DrawAs = ESlateBrushDrawType::NoDrawType;
ButtonStyle.Add(TEXT("Default"), DefaultStyle);
FUIStyleButtonBackground SecBg; FSlateBrush DefaultStroke;
SecBg.BgNormal = FLinearColor(FColor(17, 24, 42)); // panel-2 DefaultStroke.DrawAs = ESlateBrushDrawType::NoDrawType;
SecBg.BgHover = FLinearColor(FColor(22, 32, 58)); ButtonStroke.Add(TEXT("Default"), DefaultStroke);
SecBg.BgPressed = FLinearColor(FColor(17, 24, 42));
Backgrounds.Add(TEXT("Secondary"), SecBg);
FUIStyleButtonBackground DngBg; ButtonFont.Add(TEXT("Default"), FSlateFontInfo());
DngBg.BgNormal = FLinearColor(FColor(217, 92, 92, 36));
DngBg.BgHover = FLinearColor(FColor(217, 92, 92, 66));
DngBg.BgPressed = FLinearColor(FColor(217, 92, 92, 36));
Backgrounds.Add(TEXT("Danger"), DngBg);
FUIStyleButtonBackground GhoBg; ButtonTextColorRegular.Add(TEXT("Default"), FLinearColor::White);
GhoBg.BgNormal = FLinearColor::Transparent; ButtonTextColorHovered.Add(TEXT("Default"), FLinearColor::White);
GhoBg.BgHover = FLinearColor(FColor(22, 32, 58, 128)); ButtonTextColorSelected.Add(TEXT("Default"), FLinearColor::White);
GhoBg.BgPressed = FLinearColor::Transparent;
Backgrounds.Add(TEXT("Ghost"), GhoBg);
// ---- Strokes ---- ButtonIconColor.Add(TEXT("Default"), FLinearColor::White);
FUIStyleButtonStroke PriS;
PriS.BorderNormal = FLinearColor(FColor(201, 167, 90));
PriS.BorderHover = FLinearColor(FColor(231, 200, 115));
Strokes.Add(TEXT("Primary"), PriS);
FUIStyleButtonStroke SecS;
SecS.BorderNormal = FLinearColor(FColor(58, 75, 120));
SecS.BorderHover = FLinearColor(FColor(78, 163, 255));
Strokes.Add(TEXT("Secondary"), SecS);
FUIStyleButtonStroke DngS;
DngS.BorderNormal = FLinearColor(FColor(217, 92, 92));
DngS.BorderHover = FLinearColor(FColor(217, 92, 92));
Strokes.Add(TEXT("Danger"), DngS);
FUIStyleButtonStroke GhoS;
GhoS.BorderNormal = FLinearColor(FColor(58, 75, 120, 140));
GhoS.BorderHover = FLinearColor(FColor(78, 163, 255));
Strokes.Add(TEXT("Ghost"), GhoS);
// ---- Fonts ----
FUIStyleButtonFont PriF; PriF.TextColor = FLinearColor(FColor(10, 10, 18));
Fonts.Add(TEXT("Primary"), PriF);
FUIStyleButtonFont SecF; SecF.TextColor = FLinearColor(FColor(244, 240, 230));
Fonts.Add(TEXT("Secondary"), SecF);
FUIStyleButtonFont DngF; DngF.TextColor = FLinearColor(FColor(240, 182, 182));
Fonts.Add(TEXT("Danger"), DngF);
FUIStyleButtonFont GhoF; GhoF.TextColor = FLinearColor(FColor(174, 182, 200));
Fonts.Add(TEXT("Ghost"), GhoF);
} }
/** Cores/textura de fundo por variante (chave FName). */ /** FButtonStyle do Slate por variante: Normal/Hovered/Pressed/Disabled
* como FSlateBrush completo + Foregrounds + Padding + Sounds. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
TMap<FName, FUIStyleButtonBackground> Backgrounds; TMap<FName, FButtonStyle> ButtonStyle;
/** Borda (cor, espessura, raio) por variante. */ /** Stroke/contorno como brush independente (texturizável). Hoje o
* UUIButton_Base não consome este campo automaticamente — quem precisa
* de stroke decora o FSlateBrush dentro de ButtonStyle.Normal via
* OutlineSettings. Reservado pra evolução com 2 Borders empilhados. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
TMap<FName, FUIStyleButtonStroke> Strokes; TMap<FName, FSlateBrush> ButtonStroke;
/** Tipografia (cor de texto, CommonTextStyle) por variante. */ /** Fonte do texto por variante. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
TMap<FName, FUIStyleButtonFont> Fonts; TMap<FName, FSlateFontInfo> ButtonFont;
// Globais comuns às variantes:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
FMargin Padding = FMargin(22.f, 12.f);
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
float MinHeight = 48.f; TMap<FName, FLinearColor> ButtonTextColorRegular;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
float HoverScale = 1.02f; TMap<FName, FLinearColor> ButtonTextColorHovered;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
float PressedScale = 0.98f; TMap<FName, FLinearColor> ButtonTextColorSelected;
/** Tamanho por silhueta (Square/Rectangle/Topbar/Header). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
TMap<EUIButtonShape, FVector2D> ShapeSizes; TMap<FName, FLinearColor> ButtonIconColor;
}; };
/** /**
* Espelha 1:1 o Struct_UI_Style_Panels do Hyper: só os dois mapas de brush * Override manual por instância (modo bUseTheme = false). 3 brushes diretos
* (fundo + contorno) indexados por EUIPanelTexture. É o que o UI_Panel_Master * pros 3 Borders do painel — designer edita o brush bruto no Details.
* do Hyper expõe editável na instância (Details). EditAnywhere/ReadWrite para * Default = NoDrawType pra não pintar branco quando vazio (regra Hyper).
* o designer ajustar brush por widget; o tema (DT) só faz seed/fallback.
*/ */
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct ZMMO_API FUIPanelBrushSet struct ZMMO_API FUIPanelBrushSet
{ {
GENERATED_BODY() GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel|Texture") FUIPanelBrushSet()
TMap<EUIPanelTexture, FSlateBrush> PanelBackground; {
PanelBackground.DrawAs = ESlateBrushDrawType::NoDrawType;
PanelOutline.DrawAs = ESlateBrushDrawType::NoDrawType;
PanelOutlineEffect.DrawAs = ESlateBrushDrawType::NoDrawType;
}
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel|Texture") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel|Brush")
TMap<EUIPanelTexture, FSlateBrush> PanelOutline; FSlateBrush PanelBackground;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel|Brush")
FSlateBrush PanelOutline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel|Brush")
FSlateBrush PanelOutlineEffect;
}; };
/**
* Espelha 1:1 o Struct_UI_Style_Panels do Hyper. 3 brushes por categoria
* (FName livre): fundo, contorno e efeito sobre o contorno. Categorias são
* editadas direto no DT_UI_Styles — UUIPanel_Base resolve via dropdown
* dinâmico (meta=GetOptions).
*
* Migração: assets v1 tinham TMap<EUIPanelTexture, ...>; os campos
* *_DEPRECATED preservam essas keys via PropertyRedirect no DefaultEngine.ini.
* PostSerialize copia legacy→ByName no PostLoad — após re-save no editor,
* o legacy fica vazio e o asset só carrega o ByName.
*/
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct ZMMO_API FUIStylePanel struct ZMMO_API FUIStylePanel
{ {
GENERATED_BODY() GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel") // ---- Novo (FName livre) -------------------------------------------
FLinearColor Bg = FLinearColor(FColor(12, 18, 34, 235)); // Panel
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel")
FLinearColor BgRaised = FLinearColor(FColor(17, 24, 42)); // Panel2
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel")
FLinearColor BgSunken = FLinearColor(FColor(14, 20, 36)); // Panel3
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel")
FLinearColor BorderColor = FLinearColor(FColor(58, 75, 120));
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel")
FLinearColor BorderSoftColor = FLinearColor(FColor(58, 75, 120, 140));
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel")
float CornerRadius = 12.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel")
float CornerRadiusSmall = 10.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel")
float BorderWidth = 1.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel")
FMargin Padding = FMargin(30.f);
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel")
FMargin PaddingSmall = FMargin(20.f);
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel")
FLinearColor CardSelectedBorder = FLinearColor(FColor(201, 167, 90)); // Accent
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel")
FLinearColor CardSelectedBg = FLinearColor(FColor(24, 32, 58));
/**
* Modo texturizado (espelha Struct_UI_Style_Panels do Hyper): brush por
* EUIPanelTexture. PanelBackground = fundo; PanelOutline = contorno.
* Se vazio p/ a chave, o painel usa o modo cor (RoundedBox) acima.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel|Texture")
TMap<EUIPanelTexture, FSlateBrush> PanelBackground;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel|Texture") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel|Texture")
TMap<EUIPanelTexture, FSlateBrush> PanelOutline; TMap<FName, FSlateBrush> PanelBackgroundByName;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel|Texture")
TMap<FName, FSlateBrush> PanelOutlineByName;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel|Texture")
TMap<FName, FSlateBrush> PanelOutlineEffectByName;
// ---- Legacy (carrega assets antigos via CoreRedirect) -------------
//
// Não aparecem no Details (DeprecatedProperty); persistem só pra leitura
// do asset pré-migração. PostSerialize copia o conteúdo pros campos
// ByName e zera os legacy.
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Use PanelBackgroundByName (FName)."))
TMap<EUIPanelTexture, FSlateBrush> PanelBackground_DEPRECATED;
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Use PanelOutlineByName (FName)."))
TMap<EUIPanelTexture, FSlateBrush> PanelOutline_DEPRECATED;
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Use PanelOutlineEffectByName (FName)."))
TMap<EUIPanelTexture, FSlateBrush> PanelOutlineEffect_DEPRECATED;
/** Chamado automaticamente após Serialize. Migra dados legacy→ByName. */
void PostSerialize(const FArchive& Ar);
};
template<>
struct TStructOpsTypeTraits<FUIStylePanel> : public TStructOpsTypeTraitsBase2<FUIStylePanel>
{
enum
{
WithPostSerialize = true,
};
}; };
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
@@ -502,29 +374,78 @@ struct ZMMO_API FUIStyleBorders
float RuleSoftThickness = 1.f; float RuleSoftThickness = 1.f;
}; };
/**
* Variante completa de ProgressBar: materiais por layer (Fill/BG/Track/Edge/Shimmer)
* + parametros visuais (DrawStyle, CornerRadius, Border) + toggles de efeito.
*
* Material nulo = nao sobrescreve o brush do WBP (usa o que esta atribuido la).
* Aplicada pelo UUIProgressBar_Base quando bUseTheme=true via Variant FName.
*/
USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleProgressBarVariant
{
GENERATED_BODY()
// ---- Materiais (opcionais; nullptr = mantem brush do WBP) ----
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Materials")
TObjectPtr<UMaterialInterface> FillMaterial;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Materials")
TObjectPtr<UMaterialInterface> BackgroundMaterial;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Materials")
TObjectPtr<UMaterialInterface> TrackGridMaterial;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Materials")
TObjectPtr<UMaterialInterface> EdgeMaterial;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Materials")
TObjectPtr<UMaterialInterface> ShimmerMaterial;
// ---- Style (forma + bordas) ----
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Style")
EUIProgressBarStyle DrawStyle = EUIProgressBarStyle::Rounded;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Style",
meta = (ClampMin = "0", ClampMax = "100"))
float CornerRadius = 8.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Style")
bool bShowBorder = false;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Style")
FLinearColor BorderColour = FLinearColor(0.f, 0.f, 0.f, 0.8f);
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Style",
meta = (ClampMin = "0", ClampMax = "10"))
float BorderWidth = 1.f;
// ---- Toggles de efeito ----
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Effects")
bool bShowTrackGrid = false;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Effects")
bool bShowEdge = false;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Effects")
bool bShowShimmer = false;
};
/**
* ProgressBar — padrao multi-map Hyper-style. Variantes (HP/MP/XP/Cast/...) sao
* editadas direto no DT_UI_Styles; cada UUIProgressBar_Base referencia uma key
* FName (dropdown via meta=GetOptions resolve esse mapa em runtime+editor).
*/
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct ZMMO_API FUIStyleProgressBar struct ZMMO_API FUIStyleProgressBar
{ {
GENERATED_BODY() GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar")
FLinearColor TrackColor = FLinearColor(FColor(14, 20, 36)); // Panel3 TMap<FName, FUIStyleProgressBarVariant> Variants;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar")
FLinearColor BorderColor = FLinearColor(FColor(58, 75, 120, 140));
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar")
float Height = 10.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar")
float CornerRadius = 6.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar")
float BorderWidth = 1.f;
/** Cor de preenchimento por tipo (Health/Mana/XP...). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar")
TMap<EUIProgressBarType, FLinearColor> FillByType;
}; };
USTRUCT(BlueprintType) USTRUCT(BlueprintType)

View File

@@ -66,6 +66,7 @@ enum class EUIPanelTexture : uint8
Rectangle_Vertical_03 UMETA(DisplayName = "Rectangle Vertical 03"), Rectangle_Vertical_03 UMETA(DisplayName = "Rectangle Vertical 03"),
Rectangle_Vertical_04 UMETA(DisplayName = "Rectangle Vertical 04"), Rectangle_Vertical_04 UMETA(DisplayName = "Rectangle Vertical 04"),
Rectangle_Square_01 UMETA(DisplayName = "Rectangle Square 01"), Rectangle_Square_01 UMETA(DisplayName = "Rectangle Square 01"),
Rectangle_Square_Gradient_01 UMETA(DisplayName = "Rectangle Square Gradient 01"),
Vertical_Footer_01 UMETA(DisplayName = "Vertical Footer 01") Vertical_Footer_01 UMETA(DisplayName = "Vertical Footer 01")
}; };
@@ -80,6 +81,16 @@ enum class EUIProgressBarType : uint8
Generic UMETA(DisplayName = "Generic") Generic UMETA(DisplayName = "Generic")
}; };
/** Preset que controla Brush.DrawAs em todos os UImages da ProgressBar. */
UENUM(BlueprintType)
enum class EUIProgressBarStyle : uint8
{
/** DrawAs=Image. Sem mascara nos cantos. */
Box UMETA(DisplayName = "Box"),
/** DrawAs=RoundedBox. Cantos arredondados via Slate nativo (pixel-perfect). */
Rounded UMETA(DisplayName = "Rounded"),
};
UENUM(BlueprintType) UENUM(BlueprintType)
enum class EUIHeaderType : uint8 enum class EUIHeaderType : uint8
{ {

View File

@@ -104,6 +104,14 @@ void AZMMOPlayerController::SetupInputComponent()
this, &AZMMOPlayerController::ToggleStatusWindow); this, &AZMMOPlayerController::ToggleStatusWindow);
Binding.bConsumeInput = true; Binding.bConsumeInput = true;
Binding.bExecuteWhenPaused = true; // dispara mesmo com StatusWindow aberto (CommonUI menu mode) Binding.bExecuteWhenPaused = true; // dispara mesmo com StatusWindow aberto (CommonUI menu mode)
// Hotkey J -> toggle JobChangePanel (Jobs.1).
FInputKeyBinding& JobBinding = InputComponent->BindKey(
FInputChord(EKeys::J, /*bShift*/ false, /*bCtrl*/ false, /*bAlt*/ false, /*bCmd*/ false),
IE_Pressed,
this, &AZMMOPlayerController::ToggleJobChangePanel);
JobBinding.bConsumeInput = true;
JobBinding.bExecuteWhenPaused = true;
} }
} }
@@ -118,6 +126,17 @@ void AZMMOPlayerController::ToggleStatusWindow()
} }
} }
void AZMMOPlayerController::ToggleJobChangePanel()
{
if (UGameInstance* GI = GetGameInstance())
{
if (UUIInGameFlowSubsystem* Flow = GI->GetSubsystem<UUIInGameFlowSubsystem>())
{
Flow->ToggleScreen(EZMMOInGameUIState::JobChangePanel);
}
}
}
bool AZMMOPlayerController::ShouldUseTouchControls() const bool AZMMOPlayerController::ShouldUseTouchControls() const
{ {
return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls; return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls;

View File

@@ -50,4 +50,7 @@ protected:
/** Hotkey Alt+A → toggle StatusWindow via UUIInGameFlowSubsystem. */ /** Hotkey Alt+A → toggle StatusWindow via UUIInGameFlowSubsystem. */
void ToggleStatusWindow(); void ToggleStatusWindow();
/** Hotkey J → toggle JobChangePanel (Jobs.1 — promocao de classe). */
void ToggleJobChangePanel();
}; };

View File

@@ -18,6 +18,7 @@
#include "UObject/ConstructorHelpers.h" #include "UObject/ConstructorHelpers.h"
#include "ZMMO.h" #include "ZMMO.h"
#include "ZMMOAttributeComponent.h" #include "ZMMOAttributeComponent.h"
#include "ZMMOPlayerState.h"
#include "ZMMOWorldSubsystem.h" #include "ZMMOWorldSubsystem.h"
#include "ZeusNetworkSubsystem.h" #include "ZeusNetworkSubsystem.h"
#include "UI/FrontEnd/UIFrontEndFlowSubsystem.h" #include "UI/FrontEnd/UIFrontEndFlowSubsystem.h"
@@ -278,6 +279,7 @@ void AZMMOPlayerCharacter::BindZeusSpawnDelegate()
} }
ZeusNetwork->OnPlayerSpawned.AddDynamic(this, &AZMMOPlayerCharacter::HandleZeusPlayerSpawned); ZeusNetwork->OnPlayerSpawned.AddDynamic(this, &AZMMOPlayerCharacter::HandleZeusPlayerSpawned);
ZeusNetwork->OnCharInfoReceived.AddDynamic(this, &AZMMOPlayerCharacter::HandleZeusCharInfo);
bSpawnDelegateBound = true; bSpawnDelegateBound = true;
} }
@@ -289,6 +291,7 @@ void AZMMOPlayerCharacter::UnbindZeusSpawnDelegate()
return; return;
} }
ZeusNetwork->OnPlayerSpawned.RemoveDynamic(this, &AZMMOPlayerCharacter::HandleZeusPlayerSpawned); ZeusNetwork->OnPlayerSpawned.RemoveDynamic(this, &AZMMOPlayerCharacter::HandleZeusPlayerSpawned);
ZeusNetwork->OnCharInfoReceived.RemoveDynamic(this, &AZMMOPlayerCharacter::HandleZeusCharInfo);
bSpawnDelegateBound = false; bSpawnDelegateBound = false;
} }
@@ -307,6 +310,10 @@ void AZMMOPlayerCharacter::TryRegisterLocalEntityFromCachedSpawn()
{ {
HandleLocalSpawnReady(CachedEntityId, CachedPosCm, CachedYawDeg, CachedServerTimeMs); HandleLocalSpawnReady(CachedEntityId, CachedPosCm, CachedYawDeg, CachedServerTimeMs);
} }
// Race fix: o S_CHAR_INFO chega ANTES de S_SPAWN_PLAYER mas o pawn pode
// nao existir ainda (OpenLevel em andamento). Aplica o ultimo cacheado.
TryApplyCachedCharInfo();
} }
void AZMMOPlayerCharacter::HandleZeusPlayerSpawned(const int32 InEntityId, const bool bIsLocal, void AZMMOPlayerCharacter::HandleZeusPlayerSpawned(const int32 InEntityId, const bool bIsLocal,
@@ -349,25 +356,74 @@ void AZMMOPlayerCharacter::HandleLocalSpawnReady(const int32 InEntityId, const F
} }
} }
// Seed do EntityId no AttributeComponent do PlayerState. O componente // Identidade publica (EntityId) + seed no AttributeComponent. CharName/Guild
// vive no AZMMOPlayerState via Component Registry — busca via PlayerState // vem separado via S_CHAR_INFO -> HandleZeusCharInfo. AttributeComponent vive
// (sobrevive ao despawn do Pawn). S_ATTRIBUTE_SNAPSHOT_FULL chega via // como subobject e recebe seed pra que o ZMMOAttributeNetworkHandler consiga
// UDP e e' rouado pelo ZMMOAttributeNetworkHandler via lookup por // rotear o snapshot por EntityId desde o primeiro pacote.
// EntityId no PlayerArray do GameState.
if (APlayerState* PS = GetPlayerState()) if (APlayerState* PS = GetPlayerState())
{ {
if (AZMMOPlayerState* ZMMOPs = Cast<AZMMOPlayerState>(PS))
{
ZMMOPs->SetPublicIdentity(EntityIdAsInt64, /*CharId*/ FString(),
/*BaseLevel*/ 1, /*ClassId*/ 0);
}
if (UZMMOAttributeComponent* AttrComp = PS->FindComponentByClass<UZMMOAttributeComponent>()) if (UZMMOAttributeComponent* AttrComp = PS->FindComponentByClass<UZMMOAttributeComponent>())
{ {
AttrComp->SeedEntityId(InEntityId); AttrComp->SeedEntityId(InEntityId);
} }
} }
// Aplica char info cacheada (caso S_CHAR_INFO tenha chegado antes do PS existir).
TryApplyCachedCharInfo();
// UI in-game e' responsabilidade do AZMMOHUD (GameMode.HUDClass), nao do // UI in-game e' responsabilidade do AZMMOHUD (GameMode.HUDClass), nao do
// Pawn. AHUD::BeginPlay chama UUIInGameFlowSubsystem::StartInGame, que // Pawn. AHUD::BeginPlay chama UUIInGameFlowSubsystem::StartInGame, que
// pushea WBP_HUD em UI.Layer.Game. UZMMOHudWidget::NativeOnActivated auto-binda // pushea WBP_HUD em UI.Layer.Game. UZMMOHudWidget::NativeOnActivated auto-binda
// no UZMMOAttributeComponent via PlayerState. // no UZMMOAttributeComponent via PlayerState.
} }
void AZMMOPlayerCharacter::HandleZeusCharInfo(const int32 InEntityId, const FString& CharName, const FString& GuildName)
{
UE_LOG(LogZMMOPlayer, Log,
TEXT("AZMMOPlayerCharacter: S_CHAR_INFO recebido EntityId=%d name=%s guild=%s"),
InEntityId, *CharName, *GuildName);
APlayerState* PS = GetPlayerState();
if (!PS)
{
// Race fix: pawn pode nao ter PlayerState ainda. Subsystem ja cacheou
// (LastLocalCharInfoCache); TryApplyCachedCharInfo aplica em
// HandleLocalSpawnReady ou em outra oportunidade.
return;
}
if (AZMMOPlayerState* ZMMOPs = Cast<AZMMOPlayerState>(PS))
{
ZMMOPs->SetCharInfo(CharName, GuildName);
}
}
void AZMMOPlayerCharacter::TryApplyCachedCharInfo()
{
if (!ZeusNetwork)
{
return;
}
int32 CachedEntityId = 0;
FString CachedCharName;
FString CachedGuildName;
if (!ZeusNetwork->TryGetLastLocalCharInfo(CachedEntityId, CachedCharName, CachedGuildName))
{
return;
}
if (APlayerState* PS = GetPlayerState())
{
if (AZMMOPlayerState* ZMMOPs = Cast<AZMMOPlayerState>(PS))
{
ZMMOPs->SetCharInfo(CachedCharName, CachedGuildName);
}
}
}
void AZMMOPlayerCharacter::FlushInputAxisToServer(const float DeltaSeconds) void AZMMOPlayerCharacter::FlushInputAxisToServer(const float DeltaSeconds)
{ {
ResolveZeusNetworkSubsystem(); ResolveZeusNetworkSubsystem();

View File

@@ -133,12 +133,19 @@ private:
/** /**
* Memoriza `EntityId` autoritativo e regista o pawn no `UZMMOWorldSubsystem` * Memoriza `EntityId` autoritativo e regista o pawn no `UZMMOWorldSubsystem`
* para o registry partilhado por proxies remotos (filtra snapshots locais). * para o registry partilhado por proxies remotos (filtra snapshots locais).
* Metadados publicos do char (Name/Guild) chegam via S_CHAR_INFO em
* HandleZeusCharInfo (servidor envia ANTES de S_SPAWN_PLAYER).
*/ */
void HandleLocalSpawnReady(int32 InEntityId, FVector PosCm, float YawDeg, int64 ServerTimeMs); void HandleLocalSpawnReady(int32 InEntityId, FVector PosCm, float YawDeg, int64 ServerTimeMs);
UFUNCTION() UFUNCTION()
void HandleZeusPlayerSpawned(int32 InEntityId, bool bIsLocal, FVector PosCm, float YawDeg, int64 ServerTimeMs); void HandleZeusPlayerSpawned(int32 InEntityId, bool bIsLocal, FVector PosCm, float YawDeg, int64 ServerTimeMs);
UFUNCTION()
void HandleZeusCharInfo(int32 InEntityId, const FString& CharName, const FString& GuildName);
void TryApplyCachedCharInfo();
UPROPERTY() UPROPERTY()
TObjectPtr<UZeusNetworkSubsystem> ZeusNetwork; TObjectPtr<UZeusNetworkSubsystem> ZeusNetwork;

View File

@@ -57,7 +57,11 @@ void UZMMOGameInstance::BindZeusDelegates(UZeusNetworkSubsystem* Zeus)
Zeus->OnConnected.AddDynamic(this, &UZMMOGameInstance::HandleZeusConnected); Zeus->OnConnected.AddDynamic(this, &UZMMOGameInstance::HandleZeusConnected);
Zeus->OnConnectionFailed.AddDynamic(this, &UZMMOGameInstance::HandleZeusConnectionFailed); Zeus->OnConnectionFailed.AddDynamic(this, &UZMMOGameInstance::HandleZeusConnectionFailed);
Zeus->OnDisconnected.AddDynamic(this, &UZMMOGameInstance::HandleZeusDisconnected); Zeus->OnDisconnected.AddDynamic(this, &UZMMOGameInstance::HandleZeusDisconnected);
Zeus->OnNetworkLog.AddDynamic(this, &UZMMOGameInstance::HandleZeusNetworkLog); // OnNetworkLog: NAO assinamos. Plugin ja loga em LogZeusNetwork (console)
// + arquivo proprio em Saved/Logs/ZeusNetwork/. Forwarding em LogZMMO era
// diagnostico inicial (sessao 2026-05-07); agora gera duplicacao de cada
// linha. Remova esta nota e descomente abaixo se quiser ver no LogZMMO:
// Zeus->OnNetworkLog.AddDynamic(this, &UZMMOGameInstance::HandleZeusNetworkLog);
} }
void UZMMOGameInstance::UnbindZeusDelegates(UZeusNetworkSubsystem* Zeus) void UZMMOGameInstance::UnbindZeusDelegates(UZeusNetworkSubsystem* Zeus)
@@ -69,7 +73,7 @@ void UZMMOGameInstance::UnbindZeusDelegates(UZeusNetworkSubsystem* Zeus)
Zeus->OnConnected.RemoveDynamic(this, &UZMMOGameInstance::HandleZeusConnected); Zeus->OnConnected.RemoveDynamic(this, &UZMMOGameInstance::HandleZeusConnected);
Zeus->OnConnectionFailed.RemoveDynamic(this, &UZMMOGameInstance::HandleZeusConnectionFailed); Zeus->OnConnectionFailed.RemoveDynamic(this, &UZMMOGameInstance::HandleZeusConnectionFailed);
Zeus->OnDisconnected.RemoveDynamic(this, &UZMMOGameInstance::HandleZeusDisconnected); Zeus->OnDisconnected.RemoveDynamic(this, &UZMMOGameInstance::HandleZeusDisconnected);
Zeus->OnNetworkLog.RemoveDynamic(this, &UZMMOGameInstance::HandleZeusNetworkLog); // OnNetworkLog: nao foi assinado (ver BindZeusDelegates) — nada a remover.
} }
void UZMMOGameInstance::HandleZeusConnected() void UZMMOGameInstance::HandleZeusConnected()

View File

@@ -2,6 +2,7 @@
#include "Components/ActorComponent.h" #include "Components/ActorComponent.h"
#include "Misc/ConfigCacheIni.h" #include "Misc/ConfigCacheIni.h"
#include "Net/UnrealNetwork.h"
#include "ZMMO.h" #include "ZMMO.h"
AZMMOPlayerState::AZMMOPlayerState() AZMMOPlayerState::AZMMOPlayerState()
@@ -72,6 +73,17 @@ AZMMOPlayerState::AZMMOPlayerState()
} }
} }
void AZMMOPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AZMMOPlayerState, ZMMOEntityId);
DOREPLIFETIME(AZMMOPlayerState, CharId);
DOREPLIFETIME(AZMMOPlayerState, CharName);
DOREPLIFETIME(AZMMOPlayerState, BaseLevel);
DOREPLIFETIME(AZMMOPlayerState, ClassId);
DOREPLIFETIME(AZMMOPlayerState, GuildName);
}
void AZMMOPlayerState::SetPublicIdentity(int64 InEntityId, const FString& InCharId, void AZMMOPlayerState::SetPublicIdentity(int64 InEntityId, const FString& InCharId,
int32 InBaseLevel, int32 InClassId) int32 InBaseLevel, int32 InClassId)
{ {
@@ -80,3 +92,9 @@ void AZMMOPlayerState::SetPublicIdentity(int64 InEntityId, const FString& InChar
BaseLevel = InBaseLevel; BaseLevel = InBaseLevel;
ClassId = InClassId; ClassId = InClassId;
} }
void AZMMOPlayerState::SetCharInfo(const FString& InCharName, const FString& InGuildName)
{
CharName = InCharName;
GuildName = InGuildName;
}

View File

@@ -5,6 +5,7 @@
#include "Templates/SubclassOf.h" #include "Templates/SubclassOf.h"
#include "ZMMOPlayerState.generated.h" #include "ZMMOPlayerState.generated.h"
class FLifetimeProperty;
class UActorComponent; class UActorComponent;
/** /**
@@ -52,36 +53,55 @@ public:
AZMMOPlayerState(); AZMMOPlayerState();
// === Identidade publica === // === Identidade publica ===
//
// Todos os campos publicos sao Replicated — hoje o cliente UE5 roda em
// Standalone (servidor C++ standalone, sem replicacao UE5 ativa), entao
// a marca nao tem efeito em runtime; deixa a classe pronta caso o projeto
// passe a usar dedicated UE5 no futuro.
/** EntityId autoritativo do server. 0 ate o spawn local confirmar. */ /** EntityId autoritativo do server. 0 ate o spawn local confirmar. */
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Identity") UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Identity")
int64 ZMMOEntityId = 0; int64 ZMMOEntityId = 0;
/** CharId (BIGINT no DB; FString pra preservar 64 bits em Blueprint). */ /** CharId (BIGINT no DB; FString pra preservar 64 bits em Blueprint). */
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Identity") UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Identity")
FString CharId; FString CharId;
/** Nome do char vindo do DB (`characters.name`). Setado por
* AZMMOPlayerCharacter::HandleLocalSpawnReady com o payload do
* S_SPAWN_PLAYER. UI prefere isto a APlayerState::GetPlayerName(). */
UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Identity")
FString CharName;
// === Progressao publica === // === Progressao publica ===
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress") UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress")
int32 BaseLevel = 1; int32 BaseLevel = 1;
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress") UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress")
int32 ClassId = 0; int32 ClassId = 0;
// === Guild (futuro) === // === Guild (futuro) ===
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Guild") UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Guild")
FString GuildName; FString GuildName;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
// === Helpers === // === Helpers ===
/// Atualizado quando S_ATTRIBUTE_SNAPSHOT_FULL chega — UZMMOHudWidget chama /// Setado por AZMMOPlayerCharacter::HandleLocalSpawnReady (pose/EntityId)
/// isso pra refletir os dados publicos do snapshot no PlayerState. /// e atualizado por handlers de snapshot/level up para refletir dados
/// publicos do char no PlayerState.
UFUNCTION(BlueprintCallable, Category = "ZMMO|Identity") UFUNCTION(BlueprintCallable, Category = "ZMMO|Identity")
void SetPublicIdentity(int64 InEntityId, const FString& InCharId, void SetPublicIdentity(int64 InEntityId, const FString& InCharId,
int32 InBaseLevel, int32 InClassId); int32 InBaseLevel, int32 InClassId);
/// Setado pelo handler do S_CHAR_INFO (chega antes de S_SPAWN_PLAYER) —
/// metadados publicos do char vindos do servidor.
UFUNCTION(BlueprintCallable, Category = "ZMMO|Identity")
void SetCharInfo(const FString& InCharName, const FString& InGuildName);
/// Template helper: PS->GetZMMOComponent<UZMMOAttributeComponent>() /// Template helper: PS->GetZMMOComponent<UZMMOAttributeComponent>()
template <typename T> template <typename T>
T* GetZMMOComponent() const T* GetZMMOComponent() const

View File

@@ -0,0 +1,131 @@
#include "UIDraggableWindow_Base.h"
#include "Components/Border.h"
#include "Engine/Engine.h"
#include "Engine/GameViewportClient.h"
#include "Input/Reply.h"
#include "UIWindowSaveGame.h"
void UUIDraggableWindow_Base::NativeOnActivated()
{
Super::NativeOnActivated();
ApplySavedPosition();
// Validacao usa translation + viewport size, ambos disponiveis ja' aqui
// (sem precisar do CachedGeometry/layout pass).
ValidatePositionInViewport();
}
void UUIDraggableWindow_Base::ApplySavedPosition()
{
const FName Id = GetWindowId();
if (Id.IsNone()) return;
UZMMOUISaveGame* Save = UZMMOUISaveGame::LoadOrCreate();
FVector2D Pos;
if (Save && Save->TryGetWindowPosition(Id, Pos))
{
SetRenderTranslation(Pos);
}
}
void UUIDraggableWindow_Base::PersistCurrentPosition()
{
const FName Id = GetWindowId();
if (Id.IsNone()) return;
UZMMOUISaveGame* Save = UZMMOUISaveGame::LoadOrCreate();
if (!Save) return;
Save->SetWindowPosition(Id, GetRenderTransform().Translation);
UZMMOUISaveGame::SaveNow(Save);
}
bool UUIDraggableWindow_Base::IsDragHandleHit(const FPointerEvent& MouseEvent) const
{
if (!Border_DragHandle) return false;
const FGeometry HandleGeom = Border_DragHandle->GetCachedGeometry();
return HandleGeom.IsUnderLocation(MouseEvent.GetScreenSpacePosition());
}
FReply UUIDraggableWindow_Base::NativeOnMouseButtonDown(const FGeometry& InGeometry,
const FPointerEvent& InMouseEvent)
{
if (InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && IsDragHandleHit(InMouseEvent))
{
bMaybeDragging = true;
bDragging = false;
DragStartScreenPos = InMouseEvent.GetScreenSpacePosition();
WidgetStartTranslation = GetRenderTransform().Translation;
return FReply::Handled().CaptureMouse(TakeWidget());
}
return Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);
}
FReply UUIDraggableWindow_Base::NativeOnMouseMove(const FGeometry& InGeometry,
const FPointerEvent& InMouseEvent)
{
if (!bMaybeDragging && !bDragging)
{
return Super::NativeOnMouseMove(InGeometry, InMouseEvent);
}
const FVector2D Delta = InMouseEvent.GetScreenSpacePosition() - DragStartScreenPos;
if (!bDragging && Delta.SizeSquared() < DragStartThresholdPx * DragStartThresholdPx)
{
return FReply::Handled();
}
bDragging = true;
SetRenderTranslation(WidgetStartTranslation + Delta);
return FReply::Handled();
}
FReply UUIDraggableWindow_Base::NativeOnMouseButtonUp(const FGeometry& InGeometry,
const FPointerEvent& InMouseEvent)
{
if (InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && (bMaybeDragging || bDragging))
{
const bool bWasRealDrag = bDragging;
bMaybeDragging = false;
bDragging = false;
if (bWasRealDrag)
{
PersistCurrentPosition();
}
return FReply::Handled().ReleaseMouseCapture();
}
return Super::NativeOnMouseButtonUp(InGeometry, InMouseEvent);
}
void UUIDraggableWindow_Base::NativeOnMouseLeave(const FPointerEvent& InMouseEvent)
{
Super::NativeOnMouseLeave(InMouseEvent);
}
void UUIDraggableWindow_Base::ValidatePositionInViewport()
{
if (!GEngine || !GEngine->GameViewport) return;
FVector2D ViewportSize;
GEngine->GameViewport->GetViewportSize(ViewportSize);
if (ViewportSize.IsNearlyZero()) return;
// RenderTransform.Translation esta em coordenadas de viewport (pixels),
// independente de DPI/offset do editor — comparacao direta funciona em
// PIE e em build standalone. Assume slot centralizado (default UMG
// stack); meio-viewport e' o quanto a translation pode crescer antes
// da janela sair completamente.
const FVector2D Tr = GetRenderTransform().Translation;
const float HalfX = ViewportSize.X * 0.5f;
const float HalfY = ViewportSize.Y * 0.5f;
const bool bOutOfBounds =
FMath::Abs(Tr.X) > HalfX - MinVisiblePixels ||
FMath::Abs(Tr.Y) > HalfY - MinVisiblePixels;
UE_LOG(LogTemp, Verbose, TEXT("DraggableWindow::Validate translation=(%.0f,%.0f) viewport=(%.0f,%.0f) outOfBounds=%d"),
Tr.X, Tr.Y, ViewportSize.X, ViewportSize.Y, bOutOfBounds ? 1 : 0);
if (bOutOfBounds)
{
SetRenderTranslation(FVector2D::ZeroVector);
PersistCurrentPosition();
}
}

View File

@@ -0,0 +1,90 @@
#pragma once
#include "CoreMinimal.h"
#include "CommonActivatableWidget.h"
#include "UIDraggableWindow_Base.generated.h"
class UBorder;
class UZMMOUISaveGame;
/**
* Base de janelas in-game movel + persistente. Subclasse define WindowId
* (FName estavel) via override de GetWindowId(); a Base cuida do drag
* (RenderTransform.Translation) e da persistencia (UZMMOUISaveGame).
*
* Convencao do WBP:
* - Bind opcional `Border_DragHandle` cobrindo a area arrastavel (header).
* Sem ele, a janela nao captura drag (vira static).
* - `Border_DragHandle->Visibility = Visible` (precisa receber HitTest).
*
* Por que RenderTransform e nao Slot.Position?
* - Funciona em qualquer layout slot (Overlay, VBox, Canvas), nao so
* CanvasPanelSlot.
* - Nao polui o asset (translation e' transient/visual, nao layout-layout).
*
* Persistencia: na primeira ativacao, carrega a posicao salva do WindowId;
* ao soltar o drag, grava e salva o slot.
*/
UCLASS(Abstract, Blueprintable)
class ZMMO_API UUIDraggableWindow_Base : public UCommonActivatableWidget
{
GENERATED_BODY()
public:
/**
* Identificador estavel da janela (chave no save). Subclasse OBRIGADA
* a sobrescrever — default NAME_None desativa persistencia.
*/
virtual FName GetWindowId() const { return NAME_None; }
protected:
virtual void NativeOnActivated() override;
virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
virtual FReply NativeOnMouseMove(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
virtual FReply NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
virtual void NativeOnMouseLeave(const FPointerEvent& InMouseEvent) override;
/**
* Define se o ponto sob o cursor pertence a area de drag. Default: usa
* Border_DragHandle (se bindado). Subclass pode override pra usar outro
* widget (ex.: Container_Header ja' existente) sem precisar adicionar
* um Border dedicado no WBP.
*/
virtual bool IsDragHandleHit(const FPointerEvent& MouseEvent) const;
/** Aplica a posicao salva (se houver) como RenderTransform.Translation. */
void ApplySavedPosition();
/** Grava a posicao atual no save e persiste no slot. */
void PersistCurrentPosition();
/**
* Se a janela ficou fora do viewport (ex.: usuario arrastou pra borda e
* depois a resolucao mudou ou ele fechou e reabriu), reseta translation
* pra (0,0). Chamado no proximo tick apos Activated — cached geometry
* so' tem valor depois do primeiro layout pass.
*/
UFUNCTION()
void ValidatePositionInViewport();
/** Quanto da janela precisa ficar visivel pra contar como "no viewport". */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Drag")
float MinVisiblePixels = 50.f;
/**
* Bind opcional do WBP. Apenas drags iniciados com o cursor SOBRE
* este border sao aceitos — bloqueia drag por clicar nos rows/botoes.
*/
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Drag")
TObjectPtr<UBorder> Border_DragHandle;
/** Distancia minima do mouse pra confirmar "drag iniciado" (anti-jitter). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Drag")
float DragStartThresholdPx = 4.f;
private:
bool bMaybeDragging = false; ///< pressed mas ainda nao passou do threshold
bool bDragging = false; ///< drag confirmado
FVector2D DragStartScreenPos = FVector2D::ZeroVector;
FVector2D WidgetStartTranslation = FVector2D::ZeroVector;
};

View File

@@ -0,0 +1,10 @@
#include "UILoadingProfilesDataAsset.h"
FZMMOLoadingProfile UZMMOLoadingProfilesDataAsset::GetProfile(EZMMOLoadingContext Context) const
{
if (const FZMMOLoadingProfile* Found = Profiles.Find(Context))
{
return *Found;
}
return FZMMOLoadingProfile();
}

View File

@@ -0,0 +1,29 @@
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "UI/LoadingTypes.h"
#include "UILoadingProfilesDataAsset.generated.h"
/**
* Mapa data-driven contexto → perfil (etapas + título). Designer edita o
* asset; código consulta por EZMMOLoadingContext na hora de configurar a
* tela. Asset canônico: DA_LoadingProfiles em /Game/ZMMO/Data/UI/Loading/.
*
* Configurado em DefaultGame.ini:
* [/Script/ZMMO.UIFrontEndFlowSubsystem]
* LoadingProfilesAsset=/Game/ZMMO/Data/UI/Loading/DA_LoadingProfiles.DA_LoadingProfiles
*/
UCLASS(BlueprintType)
class ZMMO_API UZMMOLoadingProfilesDataAsset : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Loading")
TMap<EZMMOLoadingContext, FZMMOLoadingProfile> Profiles;
/** Retorna o perfil do contexto ou um perfil vazio (sem etapas). */
UFUNCTION(BlueprintCallable, Category = "Loading")
FZMMOLoadingProfile GetProfile(EZMMOLoadingContext Context) const;
};

View File

@@ -0,0 +1,235 @@
#include "UILoadingScreen_Base.h"
#include "Components/ProgressBar.h"
#include "CommonTextBlock.h"
#include "Engine/DataTable.h"
#include "Engine/World.h"
#include "TimerManager.h"
#include "UILoadingProfilesDataAsset.h"
#include "UILoadingTipRow.h"
void UUILoadingScreen_Base::Configure(EZMMOLoadingContext InContext,
UZMMOLoadingProfilesDataAsset* Profiles,
UDataTable* TipsTable)
{
Context_ = InContext;
bCompleteFired_ = false;
// Perfil (etapas + título).
Profile_ = (Profiles != nullptr)
? Profiles->GetProfile(InContext)
: FZMMOLoadingProfile();
StepStatus_.Reset();
for (const FZMMOLoadingStepDef& Step : Profile_.Steps)
{
StepStatus_.Add(Step.StepId, EZMMOLoadingStepStatus::Pending);
}
// Pool de dicas filtrada por contexto (None = qualquer).
TipPool_.Reset();
if (TipsTable != nullptr)
{
TipsTable->ForeachRow<FZMMOLoadingTipRow>(TEXT("UUILoadingScreen_Base::Configure"),
[this](const FName& /*Key*/, const FZMMOLoadingTipRow& Row)
{
if (Row.Context == EZMMOLoadingContext::None || Row.Context == Context_)
{
if (!Row.Tip.IsEmpty())
{
TipPool_.Add(Row.Tip);
}
}
});
}
NextTipIndex_ = 0;
RefreshUI();
if (IsActivated())
{
StartTipTimer();
}
}
void UUILoadingScreen_Base::NativeOnActivated()
{
Super::NativeOnActivated();
RefreshUI();
StartTipTimer();
}
void UUILoadingScreen_Base::NativeOnDeactivated()
{
StopTipTimer();
Super::NativeOnDeactivated();
}
void UUILoadingScreen_Base::MarkStepRunning(FName StepId)
{
if (EZMMOLoadingStepStatus* Status = StepStatus_.Find(StepId))
{
// Já Done não regride.
if (*Status != EZMMOLoadingStepStatus::Done && *Status != EZMMOLoadingStepStatus::Failed)
{
*Status = EZMMOLoadingStepStatus::Running;
RefreshUI();
}
}
}
void UUILoadingScreen_Base::MarkStepDone(FName StepId)
{
if (EZMMOLoadingStepStatus* Status = StepStatus_.Find(StepId))
{
*Status = EZMMOLoadingStepStatus::Done;
RefreshUI();
if (!bCompleteFired_ && AreAllStepsDone())
{
bCompleteFired_ = true;
OnLoadingComplete.Broadcast();
}
}
}
void UUILoadingScreen_Base::MarkStepFailed(FName StepId, const FText& Reason)
{
if (EZMMOLoadingStepStatus* Status = StepStatus_.Find(StepId))
{
*Status = EZMMOLoadingStepStatus::Failed;
if (Text_Status && !Reason.IsEmpty())
{
Text_Status->SetText(Reason);
}
RefreshUI();
}
}
bool UUILoadingScreen_Base::AreAllStepsDone() const
{
if (Profile_.Steps.Num() == 0)
{
return false;
}
for (const FZMMOLoadingStepDef& Step : Profile_.Steps)
{
const EZMMOLoadingStepStatus* Status = StepStatus_.Find(Step.StepId);
if (Status == nullptr || *Status != EZMMOLoadingStepStatus::Done)
{
return false;
}
}
return true;
}
int32 UUILoadingScreen_Base::FindStepIndex(FName StepId) const
{
for (int32 i = 0; i < Profile_.Steps.Num(); ++i)
{
if (Profile_.Steps[i].StepId == StepId)
{
return i;
}
}
return INDEX_NONE;
}
EZMMOLoadingStepStatus UUILoadingScreen_Base::GetStepStatus(int32 Index) const
{
if (!Profile_.Steps.IsValidIndex(Index)) return EZMMOLoadingStepStatus::Pending;
const EZMMOLoadingStepStatus* Status = StepStatus_.Find(Profile_.Steps[Index].StepId);
return Status ? *Status : EZMMOLoadingStepStatus::Pending;
}
FText UUILoadingScreen_Base::ComputeCurrentStatusText() const
{
// Prioridade: Running > último Done > primeiro Pending > Status default.
int32 LastDone = INDEX_NONE;
for (int32 i = 0; i < Profile_.Steps.Num(); ++i)
{
const EZMMOLoadingStepStatus St = GetStepStatus(i);
if (St == EZMMOLoadingStepStatus::Running)
{
return Profile_.Steps[i].Label;
}
if (St == EZMMOLoadingStepStatus::Done)
{
LastDone = i;
}
}
if (LastDone != INDEX_NONE)
{
return Profile_.Steps[LastDone].Label;
}
if (Profile_.Steps.Num() > 0)
{
return Profile_.Steps[0].Label;
}
return StatusDefault;
}
float UUILoadingScreen_Base::ComputeProgress01() const
{
const int32 N = Profile_.Steps.Num();
if (N == 0) return 0.f;
float Acc = 0.f;
for (int32 i = 0; i < N; ++i)
{
const EZMMOLoadingStepStatus St = GetStepStatus(i);
if (St == EZMMOLoadingStepStatus::Done) Acc += 1.f;
else if (St == EZMMOLoadingStepStatus::Running) Acc += 0.5f;
}
return FMath::Clamp(Acc / static_cast<float>(N), 0.f, 1.f);
}
void UUILoadingScreen_Base::RefreshUI()
{
if (Text_Title)
{
const FText& Title = Profile_.TitleOverride.IsEmpty() ? TitleDefault : Profile_.TitleOverride;
Text_Title->SetText(Title);
}
if (Text_Status)
{
Text_Status->SetText(ComputeCurrentStatusText());
}
if (ProgressBar_Progress)
{
ProgressBar_Progress->SetPercent(ComputeProgress01());
}
if (Text_Tip && TipPool_.Num() > 0 && Text_Tip->GetText().IsEmpty())
{
// Primeira dica na inicialização (timer cuida das próximas).
Text_Tip->SetText(TipPool_[0]);
NextTipIndex_ = TipPool_.Num() > 1 ? 1 : 0;
}
}
void UUILoadingScreen_Base::RotateTip()
{
if (!Text_Tip || TipPool_.Num() == 0) return;
Text_Tip->SetText(TipPool_[NextTipIndex_]);
NextTipIndex_ = (NextTipIndex_ + 1) % TipPool_.Num();
}
void UUILoadingScreen_Base::StartTipTimer()
{
StopTipTimer();
if (TipRotationIntervalSeconds <= 0.f || TipPool_.Num() <= 1) return;
if (UWorld* W = GetWorld())
{
W->GetTimerManager().SetTimer(TipTimerHandle_, this,
&UUILoadingScreen_Base::RotateTip,
TipRotationIntervalSeconds, /*bLoop=*/true);
}
}
void UUILoadingScreen_Base::StopTipTimer()
{
if (UWorld* W = GetWorld())
{
W->GetTimerManager().ClearTimer(TipTimerHandle_);
}
TipTimerHandle_.Invalidate();
}

View File

@@ -0,0 +1,124 @@
#pragma once
#include "CoreMinimal.h"
#include "UIActivatableScreen_Base.h"
#include "UI/LoadingTypes.h"
#include "UILoadingScreen_Base.generated.h"
class UCommonTextBlock;
class UProgressBar;
class UDataTable;
class UZMMOLoadingProfilesDataAsset;
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FZMMOOnLoadingComplete);
/**
* Tela de loading genérica. Reusada pelo FrontEnd (handoff CharServer →
* WorldServer) e pelo InGame (entrada em instance/dungeon, respawn). Cada
* uso passa um EZMMOLoadingContext em Configure(), que resolve o perfil
* no UZMMOLoadingProfilesDataAsset (lista de etapas + título).
*
* Quem orquestra (UIFrontEndFlowSubsystem, futuro UIInGameFlowSubsystem)
* assina os próprios eventos (HandlePostLoadMap, OnPlayerSpawned, etc) e
* chama MarkStepDone(StepId) — server NÃO envia opcode de progresso (ver
* [[project_ui_loading_dynamic]]).
*
* Dismiss: a tela emite OnLoadingComplete quando todas as etapas estão
* Done. Caller decide quando popar (geralmente após pequeno fade-out).
*
* WBP concreto herda DIRETO desta classe. Subclasses C++ específicas só
* surgem quando houver necessidade real de estilo divergente.
*/
UCLASS(Abstract, Blueprintable)
class ZMMO_API UUILoadingScreen_Base : public UUIActivatableScreen_Base
{
GENERATED_BODY()
public:
/**
* Configura a tela: carrega o perfil do contexto, popula a pool de
* dicas filtrada e reseta estado das etapas. Pode ser chamado depois
* de NativeOnActivated (estado é reaplicado em RefreshUI).
*/
UFUNCTION(BlueprintCallable, Category = "Loading")
void Configure(EZMMOLoadingContext InContext,
UZMMOLoadingProfilesDataAsset* Profiles,
UDataTable* TipsTable);
UFUNCTION(BlueprintCallable, Category = "Loading")
void MarkStepRunning(FName StepId);
UFUNCTION(BlueprintCallable, Category = "Loading")
void MarkStepDone(FName StepId);
UFUNCTION(BlueprintCallable, Category = "Loading")
void MarkStepFailed(FName StepId, const FText& Reason);
UFUNCTION(BlueprintPure, Category = "Loading")
bool AreAllStepsDone() const;
/** Disparado UMA vez quando todas as etapas viram Done. */
UPROPERTY(BlueprintAssignable, Category = "Loading")
FZMMOOnLoadingComplete OnLoadingComplete;
protected:
virtual void NativeOnActivated() override;
virtual void NativeOnDeactivated() override;
/** Reaplica todo o estado aos widgets bindados (texto, progress, tip). */
void RefreshUI();
void RotateTip();
void StartTipTimer();
void StopTipTimer();
/** Acha índice da etapa por StepId; -1 se não existir. */
int32 FindStepIndex(FName StepId) const;
/** Status atual da etapa em [Steps_]; Pending se não rastreada. */
EZMMOLoadingStepStatus GetStepStatus(int32 Index) const;
/** Texto exibido em Text_Status — etapa Running atual ou última Done. */
FText ComputeCurrentStatusText() const;
/** Progresso 0..1: #Done / max(1,#Steps). Running conta como meio passo. */
float ComputeProgress01() const;
// ---- Bind widgets (BindWidgetOptional — WBP pode omitir qualquer) ----
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UCommonTextBlock> Text_Title;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UCommonTextBlock> Text_Status;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UProgressBar> ProgressBar_Progress;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UCommonTextBlock> Text_Tip;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
FText TitleDefault = FText::FromString(TEXT("Carregando"));
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
FText StatusDefault = FText::FromString(TEXT("Entrando no mundo..."));
/** Intervalo entre dicas. 0 = sem rotação (mostra a primeira dica fixa). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
float TipRotationIntervalSeconds = 6.f;
private:
EZMMOLoadingContext Context_ = EZMMOLoadingContext::None;
UPROPERTY(Transient)
FZMMOLoadingProfile Profile_;
UPROPERTY(Transient)
TMap<FName, EZMMOLoadingStepStatus> StepStatus_;
UPROPERTY(Transient)
TArray<FText> TipPool_;
int32 NextTipIndex_ = 0;
bool bCompleteFired_ = false;
FTimerHandle TipTimerHandle_;
};

Some files were not shown because too many files have changed in this diff Show More