Compare commits
11 Commits
feat/attri
...
079d05c117
| Author | SHA1 | Date | |
|---|---|---|---|
| 079d05c117 | |||
| 90484876f0 | |||
| 87d9ca4dae | |||
| 037aed30ce | |||
| 9390ed9da9 | |||
| 28bfa9a567 | |||
| 6391372f00 | |||
| f832c1a4df | |||
| c3df705a7a | |||
| a354d9eefc | |||
| ca304a99dd |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -29,3 +29,6 @@ desktop.ini
|
|||||||
*.log
|
*.log
|
||||||
*.pdb
|
*.pdb
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
# Hyper / ZeusUMGForge web converter outputs (assets gerados, não versionar)
|
||||||
|
Content/AutoCreated/
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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+)
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
BIN
Content/ZMMO/Data/Jobs/Beginner/DA_Job_Novice.uasset
Normal file
BIN
Content/ZMMO/Data/Jobs/Beginner/DA_Job_Novice.uasset
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/Data/Jobs/Vocation/DA_Job_Arcanist.uasset
Normal file
BIN
Content/ZMMO/Data/Jobs/Vocation/DA_Job_Arcanist.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/Data/Jobs/Vocation/DA_Job_Artisan.uasset
Normal file
BIN
Content/ZMMO/Data/Jobs/Vocation/DA_Job_Artisan.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/Data/Jobs/Vocation/DA_Job_Devout.uasset
Normal file
BIN
Content/ZMMO/Data/Jobs/Vocation/DA_Job_Devout.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/Data/Jobs/Vocation/DA_Job_Ranger.uasset
Normal file
BIN
Content/ZMMO/Data/Jobs/Vocation/DA_Job_Ranger.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/Data/Jobs/Vocation/DA_Job_Rogue.uasset
Normal file
BIN
Content/ZMMO/Data/Jobs/Vocation/DA_Job_Rogue.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/Data/Jobs/Vocation/DA_Job_Warrior.uasset
Normal file
BIN
Content/ZMMO/Data/Jobs/Vocation/DA_Job_Warrior.uasset
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/Data/UI/Loading/DA_LoadingProfiles.uasset
Normal file
BIN
Content/ZMMO/Data/UI/Loading/DA_LoadingProfiles.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/Data/UI/Loading/DT_LoadingTips.uasset
Normal file
BIN
Content/ZMMO/Data/UI/Loading/DT_LoadingTips.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/UI/Materials/M_UI_Gradient_TopToBottom.uasset
Normal file
BIN
Content/ZMMO/UI/Materials/M_UI_Gradient_TopToBottom.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/UI/Shared/Loading/WBP_Loading.uasset
Normal file
BIN
Content/ZMMO/UI/Shared/Loading/WBP_Loading.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/Shared/UI_Button_Master_New.uasset
Normal file
BIN
Content/ZMMO/UI/Shared/UI_Button_Master_New.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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.
|
||||||
|
|||||||
66
Source/ZMMO/Data/UI/LoadingTypes.h
Normal file
66
Source/ZMMO/Data/UI/LoadingTypes.h
Normal 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;
|
||||||
|
};
|
||||||
@@ -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,103 @@ 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, agora com 3 brushes por
|
||||||
|
* EUIPanelTexture: fundo, contorno e efeito sobre o contorno. Tudo
|
||||||
|
* data-driven via brush no DT_UI_Styles.
|
||||||
|
*/
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct ZMMO_API FUIStylePanel
|
struct ZMMO_API FUIStylePanel
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel")
|
|
||||||
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")
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel|Texture")
|
||||||
TMap<EUIPanelTexture, FSlateBrush> PanelBackground;
|
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<EUIPanelTexture, FSlateBrush> PanelOutline;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel|Texture")
|
||||||
|
TMap<EUIPanelTexture, FSlateBrush> PanelOutlineEffect;
|
||||||
};
|
};
|
||||||
|
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
|
|||||||
@@ -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")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
10
Source/ZMMO/Game/UI/Common/UILoadingProfilesDataAsset.cpp
Normal file
10
Source/ZMMO/Game/UI/Common/UILoadingProfilesDataAsset.cpp
Normal 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();
|
||||||
|
}
|
||||||
29
Source/ZMMO/Game/UI/Common/UILoadingProfilesDataAsset.h
Normal file
29
Source/ZMMO/Game/UI/Common/UILoadingProfilesDataAsset.h
Normal 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;
|
||||||
|
};
|
||||||
235
Source/ZMMO/Game/UI/Common/UILoadingScreen_Base.cpp
Normal file
235
Source/ZMMO/Game/UI/Common/UILoadingScreen_Base.cpp
Normal 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();
|
||||||
|
}
|
||||||
124
Source/ZMMO/Game/UI/Common/UILoadingScreen_Base.h
Normal file
124
Source/ZMMO/Game/UI/Common/UILoadingScreen_Base.h
Normal 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_;
|
||||||
|
};
|
||||||
30
Source/ZMMO/Game/UI/Common/UILoadingTipRow.h
Normal file
30
Source/ZMMO/Game/UI/Common/UILoadingTipRow.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Engine/DataTable.h"
|
||||||
|
#include "GameplayTagContainer.h"
|
||||||
|
#include "UI/LoadingTypes.h"
|
||||||
|
#include "UILoadingTipRow.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Row do DT_LoadingTips. Cada row = uma dica mostrada (rotaciona durante o
|
||||||
|
* loading). FText é localizável (basta extrair pelo gather de tradução do
|
||||||
|
* UE). Filtra por contexto: Context=None significa "qualquer loading".
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FZMMOLoadingTipRow : public FTableRowBase
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/** Texto da dica (localizável). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
|
||||||
|
FText Tip;
|
||||||
|
|
||||||
|
/** Restringe a dica a um contexto. None = todos. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
|
||||||
|
EZMMOLoadingContext Context = EZMMOLoadingContext::None;
|
||||||
|
|
||||||
|
/** Tag opcional pra categorizar (PvE/PvP/Crafting...) — futuro filtro. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
|
||||||
|
FGameplayTag Category;
|
||||||
|
};
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
#include "UIFrontEndScreenSet.h"
|
#include "UIFrontEndScreenSet.h"
|
||||||
#include "UIManagerSubsystem.h"
|
#include "UIManagerSubsystem.h"
|
||||||
#include "UIPrimaryGameLayout_Base.h"
|
#include "UIPrimaryGameLayout_Base.h"
|
||||||
|
#include "UI/Common/UILoadingProfilesDataAsset.h"
|
||||||
|
#include "UI/Common/UILoadingScreen_Base.h"
|
||||||
#include "UI/UILayerTags.h"
|
#include "UI/UILayerTags.h"
|
||||||
#include "ZMMOGameInstance.h"
|
#include "ZMMOGameInstance.h"
|
||||||
#include "ZMMOThemeSubsystem.h"
|
#include "ZMMOThemeSubsystem.h"
|
||||||
@@ -212,13 +214,27 @@ void UUIFrontEndFlowSubsystem::ResolveAndPushScreen(EZMMOFrontEndState State)
|
|||||||
const FSoftObjectPath Path = Soft.ToSoftObjectPath();
|
const FSoftObjectPath Path = Soft.ToSoftObjectPath();
|
||||||
UAssetManager::GetStreamableManager().RequestAsyncLoad(
|
UAssetManager::GetStreamableManager().RequestAsyncLoad(
|
||||||
Path,
|
Path,
|
||||||
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer]()
|
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer, State]()
|
||||||
{
|
{
|
||||||
if (UUIManagerSubsystem* M = GetUIManager())
|
if (UUIManagerSubsystem* M = GetUIManager())
|
||||||
{
|
{
|
||||||
if (UClass* Cls = Soft.Get())
|
if (UClass* Cls = Soft.Get())
|
||||||
{
|
{
|
||||||
M->PushScreenToLayer(Layer, Cls);
|
UCommonActivatableWidget* Pushed = M->PushScreenToLayer(Layer, Cls);
|
||||||
|
|
||||||
|
// Loading: configura logo após o push e marca etapa "Travel"
|
||||||
|
// (chegamos aqui via HandleServerTravelRequested → SetState).
|
||||||
|
if (State == EZMMOFrontEndState::EnteringWorld)
|
||||||
|
{
|
||||||
|
if (UUILoadingScreen_Base* Loading = Cast<UUILoadingScreen_Base>(Pushed))
|
||||||
|
{
|
||||||
|
ActiveLoadingScreen = Loading;
|
||||||
|
Loading->Configure(EZMMOLoadingContext::FrontEndEnteringWorld,
|
||||||
|
GetLoadingProfiles(), GetLoadingTips());
|
||||||
|
Loading->OnLoadingComplete.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleLoadingComplete);
|
||||||
|
Loading->MarkStepDone(TEXT("Travel"));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -283,6 +299,7 @@ void UUIFrontEndFlowSubsystem::BindNetwork()
|
|||||||
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
||||||
{
|
{
|
||||||
Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
||||||
|
Zeus->OnPlayerSpawned.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned);
|
||||||
}
|
}
|
||||||
bNetBound = true;
|
bNetBound = true;
|
||||||
}
|
}
|
||||||
@@ -302,6 +319,7 @@ void UUIFrontEndFlowSubsystem::UnbindNetwork()
|
|||||||
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
||||||
{
|
{
|
||||||
Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
||||||
|
Zeus->OnPlayerSpawned.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned);
|
||||||
}
|
}
|
||||||
bNetBound = false;
|
bNetBound = false;
|
||||||
}
|
}
|
||||||
@@ -390,11 +408,65 @@ void UUIFrontEndFlowSubsystem::HandleServerTravelRequested(const FString& MapNam
|
|||||||
|
|
||||||
void UUIFrontEndFlowSubsystem::HandlePostLoadMap(UWorld* /*LoadedWorld*/)
|
void UUIFrontEndFlowSubsystem::HandlePostLoadMap(UWorld* /*LoadedWorld*/)
|
||||||
{
|
{
|
||||||
if (bTravelingToWorld)
|
if (!bTravelingToWorld)
|
||||||
{
|
{
|
||||||
bTravelingToWorld = false;
|
return;
|
||||||
SetState(EZMMOFrontEndState::InWorld);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loading dinâmico: marca a etapa "MapLoaded" e espera o spawn antes de
|
||||||
|
// liberar o InWorld (a tela limpa via OnLoadingComplete). Fallback: se
|
||||||
|
// não há screen ativa (DA não configurado), comportamento legado —
|
||||||
|
// transita já pra InWorld pra não travar.
|
||||||
|
if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
|
||||||
|
{
|
||||||
|
Loading->MarkStepDone(TEXT("MapLoaded"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bTravelingToWorld = false;
|
||||||
|
SetState(EZMMOFrontEndState::InWorld);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIFrontEndFlowSubsystem::HandlePlayerSpawned(int32 /*EntityId*/, bool bIsLocal,
|
||||||
|
FVector /*PosCm*/, float /*YawDeg*/,
|
||||||
|
int64 /*ServerTimeMs*/)
|
||||||
|
{
|
||||||
|
if (!bIsLocal) return;
|
||||||
|
if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
|
||||||
|
{
|
||||||
|
Loading->MarkStepDone(TEXT("Spawn"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIFrontEndFlowSubsystem::HandleLoadingComplete()
|
||||||
|
{
|
||||||
|
if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
|
||||||
|
{
|
||||||
|
Loading->OnLoadingComplete.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleLoadingComplete);
|
||||||
|
}
|
||||||
|
ActiveLoadingScreen.Reset();
|
||||||
|
bTravelingToWorld = false;
|
||||||
|
SetState(EZMMOFrontEndState::InWorld);
|
||||||
|
}
|
||||||
|
|
||||||
|
UZMMOLoadingProfilesDataAsset* UUIFrontEndFlowSubsystem::GetLoadingProfiles()
|
||||||
|
{
|
||||||
|
if (LoadingProfiles) return LoadingProfiles;
|
||||||
|
if (!LoadingProfilesAsset.IsNull())
|
||||||
|
{
|
||||||
|
LoadingProfiles = LoadingProfilesAsset.LoadSynchronous();
|
||||||
|
}
|
||||||
|
return LoadingProfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
UDataTable* UUIFrontEndFlowSubsystem::GetLoadingTips()
|
||||||
|
{
|
||||||
|
if (LoadingTips) return LoadingTips;
|
||||||
|
if (!LoadingTipsAsset.IsNull())
|
||||||
|
{
|
||||||
|
LoadingTips = LoadingTipsAsset.LoadSynchronous();
|
||||||
|
}
|
||||||
|
return LoadingTips;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UUIFrontEndFlowSubsystem::HandleServerHelloTheme(FName ThemeId)
|
void UUIFrontEndFlowSubsystem::HandleServerHelloTheme(FName ThemeId)
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
class UUIFrontEndScreenSet;
|
class UUIFrontEndScreenSet;
|
||||||
class UUIManagerSubsystem;
|
class UUIManagerSubsystem;
|
||||||
|
class UUILoadingScreen_Base;
|
||||||
|
class UZMMOLoadingProfilesDataAsset;
|
||||||
class UZeusNetworkSubsystem;
|
class UZeusNetworkSubsystem;
|
||||||
class UZeusCharServerSubsystem;
|
class UZeusCharServerSubsystem;
|
||||||
struct FZMMOMapDef;
|
struct FZMMOMapDef;
|
||||||
@@ -147,6 +149,24 @@ protected:
|
|||||||
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Maps")
|
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Maps")
|
||||||
TSoftObjectPtr<UDataTable> MapsTableAsset;
|
TSoftObjectPtr<UDataTable> MapsTableAsset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataAsset com os perfis de loading (etapas por contexto). Configure em
|
||||||
|
* DefaultGame.ini:
|
||||||
|
* [/Script/ZMMO.UIFrontEndFlowSubsystem]
|
||||||
|
* LoadingProfilesAsset=/Game/ZMMO/Data/UI/Loading/DA_LoadingProfiles.DA_LoadingProfiles
|
||||||
|
*/
|
||||||
|
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Loading")
|
||||||
|
TSoftObjectPtr<UZMMOLoadingProfilesDataAsset> LoadingProfilesAsset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataTable com dicas (rows = FZMMOLoadingTipRow). Opcional — vazio
|
||||||
|
* significa "sem dicas". Configure em DefaultGame.ini:
|
||||||
|
* [/Script/ZMMO.UIFrontEndFlowSubsystem]
|
||||||
|
* LoadingTipsAsset=/Game/ZMMO/Data/UI/Loading/DT_LoadingTips.DT_LoadingTips
|
||||||
|
*/
|
||||||
|
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Loading")
|
||||||
|
TSoftObjectPtr<UDataTable> LoadingTipsAsset;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void BindNetwork();
|
void BindNetwork();
|
||||||
void UnbindNetwork();
|
void UnbindNetwork();
|
||||||
@@ -170,8 +190,26 @@ private:
|
|||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleServerTravelRequested(const FString& MapName, const FString& MapPath);
|
void HandleServerTravelRequested(const FString& MapName, const FString& MapPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn do player local — fim da última etapa do loading EnteringWorld.
|
||||||
|
* Assinatura espelha FZeusOnPlayerSpawned (FiveParams).
|
||||||
|
*/
|
||||||
|
UFUNCTION()
|
||||||
|
void HandlePlayerSpawned(int32 EntityId, bool bIsLocal, FVector PosCm,
|
||||||
|
float YawDeg, int64 ServerTimeMs);
|
||||||
|
|
||||||
|
/** Disparado pela tela de loading quando todas as etapas viram Done. */
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleLoadingComplete();
|
||||||
|
|
||||||
void HandlePostLoadMap(UWorld* LoadedWorld);
|
void HandlePostLoadMap(UWorld* LoadedWorld);
|
||||||
|
|
||||||
|
/** Resolve+carrega o DA de perfis (sync). */
|
||||||
|
UZMMOLoadingProfilesDataAsset* GetLoadingProfiles();
|
||||||
|
|
||||||
|
/** Resolve+carrega o DT de dicas (sync). */
|
||||||
|
UDataTable* GetLoadingTips();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dívida técnica (D5): ServerHello traz ThemeId, mas o UZeusNetworkSubsystem
|
* Dívida técnica (D5): ServerHello traz ThemeId, mas o UZeusNetworkSubsystem
|
||||||
* ainda não expõe um delegate/getter dedicado. Quando expuser, ligar aqui
|
* ainda não expõe um delegate/getter dedicado. Quando expuser, ligar aqui
|
||||||
@@ -199,6 +237,18 @@ private:
|
|||||||
UPROPERTY(Transient)
|
UPROPERTY(Transient)
|
||||||
TObjectPtr<UUIFrontEndScreenSet> ScreenSet;
|
TObjectPtr<UUIFrontEndScreenSet> ScreenSet;
|
||||||
|
|
||||||
|
UPROPERTY(Transient)
|
||||||
|
TObjectPtr<UZMMOLoadingProfilesDataAsset> LoadingProfiles;
|
||||||
|
|
||||||
|
UPROPERTY(Transient)
|
||||||
|
TObjectPtr<UDataTable> LoadingTips;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tela de loading atualmente em Modal. Weak porque o stack do CommonUI
|
||||||
|
* é dono — se popar por outro caminho não queremos dangling.
|
||||||
|
*/
|
||||||
|
TWeakObjectPtr<UUILoadingScreen_Base> ActiveLoadingScreen;
|
||||||
|
|
||||||
bool bNetBound = false;
|
bool bNetBound = false;
|
||||||
bool bTravelingToWorld = false;
|
bool bTravelingToWorld = false;
|
||||||
FDelegateHandle PostLoadMapHandle;
|
FDelegateHandle PostLoadMapHandle;
|
||||||
|
|||||||
@@ -9,12 +9,11 @@
|
|||||||
void UUIServerCard_Base::NativePreConstruct()
|
void UUIServerCard_Base::NativePreConstruct()
|
||||||
{
|
{
|
||||||
Super::NativePreConstruct();
|
Super::NativePreConstruct();
|
||||||
// Reaplica tema do botao antes de qualquer SetIsEnabled — garante que o
|
// TODO(ui-system): reativar quando UUIButton_Base voltar a ter RefreshUIStyle.
|
||||||
// background da variante "Primary" pinta tanto em Normal quanto Disabled.
|
// if (Btn_Enter)
|
||||||
if (Btn_Enter)
|
// {
|
||||||
{
|
// Btn_Enter->RefreshUIStyle();
|
||||||
Btn_Enter->RefreshUIStyle();
|
// }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UUIServerCard_Base::NativeConstruct()
|
void UUIServerCard_Base::NativeConstruct()
|
||||||
@@ -25,10 +24,11 @@ void UUIServerCard_Base::NativeConstruct()
|
|||||||
Btn_Enter->OnClicked().AddUObject(this, &UUIServerCard_Base::HandleEnterClicked);
|
Btn_Enter->OnClicked().AddUObject(this, &UUIServerCard_Base::HandleEnterClicked);
|
||||||
bBound = true;
|
bBound = true;
|
||||||
}
|
}
|
||||||
if (Btn_Enter)
|
// TODO(ui-system): reativar quando UUIButton_Base voltar a ter RefreshUIStyle.
|
||||||
{
|
// if (Btn_Enter)
|
||||||
Btn_Enter->RefreshUIStyle();
|
// {
|
||||||
}
|
// Btn_Enter->RefreshUIStyle();
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
void UUIServerCard_Base::NativeDestruct()
|
void UUIServerCard_Base::NativeDestruct()
|
||||||
@@ -69,10 +69,11 @@ void UUIServerCard_Base::SetFromEntry(const FZMMOWorldEntry& InEntry)
|
|||||||
// o usuario pode entrar no Lobby pra ver a lista de personagens, criar/
|
// o usuario pode entrar no Lobby pra ver a lista de personagens, criar/
|
||||||
// deletar. O gate de "entrar no mundo" fica no proprio Lobby (botao
|
// deletar. O gate de "entrar no mundo" fica no proprio Lobby (botao
|
||||||
// "Entrar no Servidor"), que checa World.State antes do handoff.
|
// "Entrar no Servidor"), que checa World.State antes do handoff.
|
||||||
if (Btn_Enter)
|
// TODO(ui-system): reativar quando UUIButton_Base voltar a ter RefreshUIStyle.
|
||||||
{
|
// if (Btn_Enter)
|
||||||
Btn_Enter->RefreshUIStyle();
|
// {
|
||||||
}
|
// Btn_Enter->RefreshUIStyle();
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
void UUIServerCard_Base::HandleEnterClicked()
|
void UUIServerCard_Base::HandleEnterClicked()
|
||||||
|
|||||||
@@ -1,168 +0,0 @@
|
|||||||
#include "ZMMOStatusWindowWidget.h"
|
|
||||||
|
|
||||||
#include "CommonInputTypeEnum.h"
|
|
||||||
#include "Components/Button.h"
|
|
||||||
#include "Components/TextBlock.h"
|
|
||||||
#include "Engine/World.h"
|
|
||||||
#include "GameFramework/PlayerController.h"
|
|
||||||
#include "GameFramework/PlayerState.h"
|
|
||||||
#include "Internationalization/Text.h"
|
|
||||||
#include "ZMMO.h"
|
|
||||||
#include "ZMMOAttributeComponent.h"
|
|
||||||
#include "UI/InGame/UIInGameFlowSubsystem.h"
|
|
||||||
#include "UI/InGameTypes.h"
|
|
||||||
|
|
||||||
UZMMOStatusWindowWidget::UZMMOStatusWindowWidget(const FObjectInitializer& ObjectInitializer)
|
|
||||||
: Super(ObjectInitializer)
|
|
||||||
{
|
|
||||||
bIsBackHandler = true; // Esc/B fecha
|
|
||||||
bAutoActivate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
TOptional<FUIInputConfig> UZMMOStatusWindowWidget::GetDesiredInputConfig() const
|
|
||||||
{
|
|
||||||
// Modo Menu: cursor visivel, input flui pra UI (clicks nos botões),
|
|
||||||
// movimento bloqueado. Padrao pra menus modais.
|
|
||||||
return FUIInputConfig(ECommonInputMode::Menu, EMouseCaptureMode::NoCapture);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UZMMOStatusWindowWidget::NativeOnActivated()
|
|
||||||
{
|
|
||||||
Super::NativeOnActivated();
|
|
||||||
BindToLocalPlayer();
|
|
||||||
|
|
||||||
if (PlusBtn_Str) PlusBtn_Str->OnClicked.AddDynamic(this, &UZMMOStatusWindowWidget::OnPlusStr);
|
|
||||||
if (PlusBtn_Agi) PlusBtn_Agi->OnClicked.AddDynamic(this, &UZMMOStatusWindowWidget::OnPlusAgi);
|
|
||||||
if (PlusBtn_Vit) PlusBtn_Vit->OnClicked.AddDynamic(this, &UZMMOStatusWindowWidget::OnPlusVit);
|
|
||||||
if (PlusBtn_Int) PlusBtn_Int->OnClicked.AddDynamic(this, &UZMMOStatusWindowWidget::OnPlusInt);
|
|
||||||
if (PlusBtn_Dex) PlusBtn_Dex->OnClicked.AddDynamic(this, &UZMMOStatusWindowWidget::OnPlusDex);
|
|
||||||
if (PlusBtn_Luk) PlusBtn_Luk->OnClicked.AddDynamic(this, &UZMMOStatusWindowWidget::OnPlusLuk);
|
|
||||||
if (CloseBtn) CloseBtn->OnClicked.AddDynamic(this, &UZMMOStatusWindowWidget::OnCloseClicked);
|
|
||||||
|
|
||||||
UE_LOG(LogZMMO, Log, TEXT("ZMMOStatusWindow activated"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void UZMMOStatusWindowWidget::NativeOnDeactivated()
|
|
||||||
{
|
|
||||||
UnbindFromComponent();
|
|
||||||
|
|
||||||
if (PlusBtn_Str) PlusBtn_Str->OnClicked.RemoveDynamic(this, &UZMMOStatusWindowWidget::OnPlusStr);
|
|
||||||
if (PlusBtn_Agi) PlusBtn_Agi->OnClicked.RemoveDynamic(this, &UZMMOStatusWindowWidget::OnPlusAgi);
|
|
||||||
if (PlusBtn_Vit) PlusBtn_Vit->OnClicked.RemoveDynamic(this, &UZMMOStatusWindowWidget::OnPlusVit);
|
|
||||||
if (PlusBtn_Int) PlusBtn_Int->OnClicked.RemoveDynamic(this, &UZMMOStatusWindowWidget::OnPlusInt);
|
|
||||||
if (PlusBtn_Dex) PlusBtn_Dex->OnClicked.RemoveDynamic(this, &UZMMOStatusWindowWidget::OnPlusDex);
|
|
||||||
if (PlusBtn_Luk) PlusBtn_Luk->OnClicked.RemoveDynamic(this, &UZMMOStatusWindowWidget::OnPlusLuk);
|
|
||||||
if (CloseBtn) CloseBtn->OnClicked.RemoveDynamic(this, &UZMMOStatusWindowWidget::OnCloseClicked);
|
|
||||||
|
|
||||||
Super::NativeOnDeactivated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void UZMMOStatusWindowWidget::BindToLocalPlayer()
|
|
||||||
{
|
|
||||||
UWorld* World = GetWorld();
|
|
||||||
if (!World) { return; }
|
|
||||||
APlayerController* PC = World->GetFirstPlayerController();
|
|
||||||
if (!PC || !PC->PlayerState) { return; }
|
|
||||||
UZMMOAttributeComponent* Comp = PC->PlayerState->FindComponentByClass<UZMMOAttributeComponent>();
|
|
||||||
if (!Comp || Comp == BoundComponent.Get()) { return; }
|
|
||||||
|
|
||||||
UnbindFromComponent();
|
|
||||||
BoundComponent = Comp;
|
|
||||||
Comp->OnAttributesChanged.AddDynamic(this, &UZMMOStatusWindowWidget::HandleAttributesChanged);
|
|
||||||
Comp->OnStatAllocReply.AddDynamic(this, &UZMMOStatusWindowWidget::HandleStatAllocReply);
|
|
||||||
RefreshFromSnapshot(Comp->GetSnapshot());
|
|
||||||
}
|
|
||||||
|
|
||||||
void UZMMOStatusWindowWidget::UnbindFromComponent()
|
|
||||||
{
|
|
||||||
if (UZMMOAttributeComponent* Old = BoundComponent.Get())
|
|
||||||
{
|
|
||||||
Old->OnAttributesChanged.RemoveDynamic(this, &UZMMOStatusWindowWidget::HandleAttributesChanged);
|
|
||||||
Old->OnStatAllocReply.RemoveDynamic(this, &UZMMOStatusWindowWidget::HandleStatAllocReply);
|
|
||||||
}
|
|
||||||
BoundComponent.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void UZMMOStatusWindowWidget::HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot)
|
|
||||||
{
|
|
||||||
RefreshFromSnapshot(Snapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UZMMOStatusWindowWidget::HandleStatAllocReply(bool bAccepted, int32 Reason)
|
|
||||||
{
|
|
||||||
UE_LOG(LogZMMO, Log, TEXT("StatusWindow: alloc reply accepted=%d reason=%d"),
|
|
||||||
bAccepted ? 1 : 0, Reason);
|
|
||||||
// Snapshot subsequente vai disparar HandleAttributesChanged e atualizar UI.
|
|
||||||
// Aqui poderia popup de erro visual quando bAccepted=false (TODO polish).
|
|
||||||
}
|
|
||||||
|
|
||||||
void UZMMOStatusWindowWidget::RefreshFromSnapshot(const FZMMOAttributesSnapshot& S)
|
|
||||||
{
|
|
||||||
if (StrText) StrText->SetText(FText::FromString(FString::Printf(TEXT("STR %d"), S.Str)));
|
|
||||||
if (AgiText) AgiText->SetText(FText::FromString(FString::Printf(TEXT("AGI %d"), S.Agi)));
|
|
||||||
if (VitText) VitText->SetText(FText::FromString(FString::Printf(TEXT("VIT %d"), S.Vit)));
|
|
||||||
if (IntText) IntText->SetText(FText::FromString(FString::Printf(TEXT("INT %d"), S.Int)));
|
|
||||||
if (DexText) DexText->SetText(FText::FromString(FString::Printf(TEXT("DEX %d"), S.Dex)));
|
|
||||||
if (LukText) LukText->SetText(FText::FromString(FString::Printf(TEXT("LUK %d"), S.Luk)));
|
|
||||||
if (StatusPointText)
|
|
||||||
{
|
|
||||||
StatusPointText->SetText(FText::FromString(
|
|
||||||
FString::Printf(TEXT("Status Points: %d"), S.StatusPoint)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Derivados — formato RO "STAT base + equip" (ex.: "ATK 51 + 0").
|
|
||||||
// Servidor manda os dois separados; cliente NUNCA recalcula.
|
|
||||||
if (AtkText) AtkText->SetText(FText::FromString(FString::Printf(TEXT("ATK %d + %d"), S.AtkBase, S.AtkEquipBonus)));
|
|
||||||
if (MatkText) MatkText->SetText(FText::FromString(FString::Printf(TEXT("MATK %d + %d"), S.MatkBase, S.MatkEquipBonus)));
|
|
||||||
if (DefText) DefText->SetText(FText::FromString(FString::Printf(TEXT("DEF %d + %d"), S.DefBase, S.DefEquipBonus)));
|
|
||||||
if (MdefText) MdefText->SetText(FText::FromString(FString::Printf(TEXT("MDEF %d + %d"), S.MdefBase, S.MdefEquipBonus)));
|
|
||||||
if (HitText) HitText->SetText(FText::FromString(FString::Printf(TEXT("HIT %d + %d"), S.HitBase, S.HitEquipBonus)));
|
|
||||||
if (FleeText) FleeText->SetText(FText::FromString(FString::Printf(TEXT("FLEE %d + %d"), S.FleeBase, S.FleeEquipBonus)));
|
|
||||||
if (CritText)
|
|
||||||
{
|
|
||||||
// CRIT vem ×10 em ambas as camadas — soma X10 antes de converter pra
|
|
||||||
// decimal (10 = 1.0). Display: "CRIT base + equip" com 1 casa cada.
|
|
||||||
const int32 BaseInt = S.CritBaseX10 / 10;
|
|
||||||
const int32 BaseFrac = S.CritBaseX10 % 10;
|
|
||||||
const int32 EquipInt = S.CritEquipBonusX10 / 10;
|
|
||||||
const int32 EquipFrac = S.CritEquipBonusX10 % 10;
|
|
||||||
CritText->SetText(FText::FromString(FString::Printf(
|
|
||||||
TEXT("CRIT %d.%d + %d.%d"), BaseInt, BaseFrac, EquipInt, EquipFrac)));
|
|
||||||
}
|
|
||||||
// ASPD nao splita (calculo nao-linear server-side via aspd_base × stats).
|
|
||||||
if (AspdText) AspdText->SetText(FText::FromString(FString::Printf(TEXT("ASPD %d"), S.Aspd)));
|
|
||||||
// Habilita botões `+` somente se ha status_point disponível.
|
|
||||||
const bool bCanAlloc = S.StatusPoint > 0;
|
|
||||||
if (PlusBtn_Str) PlusBtn_Str->SetIsEnabled(bCanAlloc);
|
|
||||||
if (PlusBtn_Agi) PlusBtn_Agi->SetIsEnabled(bCanAlloc);
|
|
||||||
if (PlusBtn_Vit) PlusBtn_Vit->SetIsEnabled(bCanAlloc);
|
|
||||||
if (PlusBtn_Int) PlusBtn_Int->SetIsEnabled(bCanAlloc);
|
|
||||||
if (PlusBtn_Dex) PlusBtn_Dex->SetIsEnabled(bCanAlloc);
|
|
||||||
if (PlusBtn_Luk) PlusBtn_Luk->SetIsEnabled(bCanAlloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UZMMOStatusWindowWidget::RequestAlloc(int32 StatId)
|
|
||||||
{
|
|
||||||
if (UZMMOAttributeComponent* Comp = BoundComponent.Get())
|
|
||||||
{
|
|
||||||
Comp->RequestStatAlloc(StatId, /*Amount*/ 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void UZMMOStatusWindowWidget::OnPlusStr() { RequestAlloc(0); }
|
|
||||||
void UZMMOStatusWindowWidget::OnPlusAgi() { RequestAlloc(1); }
|
|
||||||
void UZMMOStatusWindowWidget::OnPlusVit() { RequestAlloc(2); }
|
|
||||||
void UZMMOStatusWindowWidget::OnPlusInt() { RequestAlloc(3); }
|
|
||||||
void UZMMOStatusWindowWidget::OnPlusDex() { RequestAlloc(4); }
|
|
||||||
void UZMMOStatusWindowWidget::OnPlusLuk() { RequestAlloc(5); }
|
|
||||||
|
|
||||||
void UZMMOStatusWindowWidget::OnCloseClicked()
|
|
||||||
{
|
|
||||||
if (UGameInstance* GI = GetGameInstance())
|
|
||||||
{
|
|
||||||
if (UUIInGameFlowSubsystem* Flow = GI->GetSubsystem<UUIInGameFlowSubsystem>())
|
|
||||||
{
|
|
||||||
Flow->SetState(EZMMOInGameUIState::Playing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "CommonActivatableWidget.h"
|
|
||||||
#include "CoreMinimal.h"
|
|
||||||
#include "ZMMOAttributeTypes.h"
|
|
||||||
#include "ZMMOStatusWindowWidget.generated.h"
|
|
||||||
|
|
||||||
class UButton;
|
|
||||||
class UTextBlock;
|
|
||||||
class UCommonButtonBase;
|
|
||||||
class UZMMOAttributeComponent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tela "Status" — atributos do jogador + alocacao via botões `+`.
|
|
||||||
*
|
|
||||||
* Modo Menu: cursor visível, movimento bloqueado, Esc fecha. Push em
|
|
||||||
* UI.Layer.GameMenu pelo `UUIInGameFlowSubsystem::ToggleScreen(StatusWindow)`.
|
|
||||||
* HUD continua visível em UI.Layer.Game por baixo.
|
|
||||||
*
|
|
||||||
* Composicao do WBP_StatusWindow (BindWidgetOptional pra permitir variantes):
|
|
||||||
* - Labels de stats primarios: StrText / AgiText / VitText / IntText / DexText / LukText
|
|
||||||
* - Botões +: PlusBtn_Str / PlusBtn_Agi / PlusBtn_Vit / PlusBtn_Int / PlusBtn_Dex / PlusBtn_Luk
|
|
||||||
* - Derivados (server-authoritative): AtkText / MatkText / DefText / MdefText /
|
|
||||||
* HitText / FleeText / CritText / AspdText
|
|
||||||
* - StatusPointText (pontos disponíveis pra alocar)
|
|
||||||
* - CloseBtn (fecha — fallback do Esc/back handler)
|
|
||||||
*
|
|
||||||
* Workflow:
|
|
||||||
* 1. NativeOnActivated: busca AttributeComponent (PlayerState do player local)
|
|
||||||
* 2. Subscreve OnAttributesChanged + OnStatAllocReply
|
|
||||||
* 3. Refresh inicial via snapshot atual
|
|
||||||
* 4. Clique no `+X` chama `Comp->RequestStatAlloc(X, 1)` → server valida
|
|
||||||
* 5. Server responde com S_ATTRIBUTE_STAT_ALLOC_OK + (se ok) snapshot novo
|
|
||||||
* 6. UI atualiza via delegate
|
|
||||||
*/
|
|
||||||
UCLASS(Abstract, Blueprintable, BlueprintType)
|
|
||||||
class ZMMO_API UZMMOStatusWindowWidget : public UCommonActivatableWidget
|
|
||||||
{
|
|
||||||
GENERATED_BODY()
|
|
||||||
|
|
||||||
public:
|
|
||||||
UZMMOStatusWindowWidget(const FObjectInitializer& ObjectInitializer);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void NativeOnActivated() override;
|
|
||||||
virtual void NativeOnDeactivated() override;
|
|
||||||
|
|
||||||
/// Modo Menu: cursor visível + bloqueia input de gameplay (movimento).
|
|
||||||
virtual TOptional<FUIInputConfig> GetDesiredInputConfig() const override;
|
|
||||||
|
|
||||||
/// Texto dos stats (label + valor). BindWidgetOptional pra permitir
|
|
||||||
/// WBPs custom (ex.: sem display de SP em variantes).
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UTextBlock* StrText = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UTextBlock* AgiText = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UTextBlock* VitText = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UTextBlock* IntText = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UTextBlock* DexText = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UTextBlock* LukText = nullptr;
|
|
||||||
|
|
||||||
/// Pontos restantes pra alocar + label "Status Points: N".
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UTextBlock* StatusPointText = nullptr;
|
|
||||||
|
|
||||||
/// Derivados (sempre calculados pelo servidor — cliente NUNCA recalcula).
|
|
||||||
/// Mostra `base + bonus` quando aplicavel (camadas equip/buff vem do server
|
|
||||||
/// pre-somadas no snapshot). BindWidgetOptional pra suportar HUDs minimalistas.
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* AtkText = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* MatkText = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* DefText = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* MdefText = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* HitText = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* FleeText = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* CritText = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") UTextBlock* AspdText = nullptr;
|
|
||||||
|
|
||||||
/// Botoes + por stat.
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UButton* PlusBtn_Str = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UButton* PlusBtn_Agi = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UButton* PlusBtn_Vit = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UButton* PlusBtn_Int = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UButton* PlusBtn_Dex = nullptr;
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UButton* PlusBtn_Luk = nullptr;
|
|
||||||
|
|
||||||
/// Botao Close (fallback do Esc/back).
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") UButton* CloseBtn = nullptr;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void BindToLocalPlayer();
|
|
||||||
void UnbindFromComponent();
|
|
||||||
void RefreshFromSnapshot(const FZMMOAttributesSnapshot& Snap);
|
|
||||||
|
|
||||||
UFUNCTION() void HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot);
|
|
||||||
UFUNCTION() void HandleStatAllocReply(bool bAccepted, int32 Reason);
|
|
||||||
|
|
||||||
UFUNCTION() void OnPlusStr();
|
|
||||||
UFUNCTION() void OnPlusAgi();
|
|
||||||
UFUNCTION() void OnPlusVit();
|
|
||||||
UFUNCTION() void OnPlusInt();
|
|
||||||
UFUNCTION() void OnPlusDex();
|
|
||||||
UFUNCTION() void OnPlusLuk();
|
|
||||||
UFUNCTION() void OnCloseClicked();
|
|
||||||
|
|
||||||
void RequestAlloc(int32 StatId);
|
|
||||||
|
|
||||||
UPROPERTY(Transient)
|
|
||||||
TWeakObjectPtr<UZMMOAttributeComponent> BoundComponent;
|
|
||||||
};
|
|
||||||
@@ -1,434 +1,28 @@
|
|||||||
#include "UIButton_Base.h"
|
#include "UIButton_Base.h"
|
||||||
|
|
||||||
#include "ZMMOThemeSubsystem.h"
|
#include "UI/UIStyleRow.h"
|
||||||
#include "Engine/GameInstance.h"
|
#include "UI/UIStyleTokens.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/DataTable.h"
|
||||||
#include "TimerManager.h"
|
|
||||||
#include "Components/Border.h"
|
|
||||||
#include "Styling/SlateBrush.h"
|
|
||||||
#include "Components/TextBlock.h"
|
|
||||||
#include "Components/Image.h"
|
|
||||||
#include "Components/SizeBox.h"
|
|
||||||
#include "Components/ScaleBox.h"
|
|
||||||
#include "Components/OverlaySlot.h"
|
|
||||||
#include "CommonTextBlock.h"
|
|
||||||
#include "CommonActionWidget.h"
|
|
||||||
|
|
||||||
namespace
|
const FUIStyle* UUIButton_Base::GetActiveUIStyle(FName ThemeId) const
|
||||||
{
|
{
|
||||||
/** Resolve o FUIStyle ativo; fallback p/ defaults (preview de designer). */
|
const UDataTable* DT = LoadObject<UDataTable>(nullptr,
|
||||||
const FUIStyle& ResolveActiveStyle(const UUserWidget* Widget, const FUIStyle& Fallback)
|
TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles"));
|
||||||
|
if (!DT)
|
||||||
{
|
{
|
||||||
if (Widget)
|
return nullptr;
|
||||||
{
|
|
||||||
if (const UGameInstance* GI = Widget->GetGameInstance())
|
|
||||||
{
|
|
||||||
if (const UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
|
|
||||||
{
|
|
||||||
return Theme->GetActiveUIStyle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Fallback; // sem GameInstance (design-time): Aurora Arcana default
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (const FUIStyleRow* Row = DT->FindRow<FUIStyleRow>(
|
||||||
|
ThemeId, TEXT("UUIButton_Base::GetActiveUIStyle"), false))
|
||||||
|
{
|
||||||
|
return &Row->Style;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
FUIStyleButtonVariant UUIButton_Base::ResolveVariant(const FUIStyleButton& Button) const
|
const FUIStyleButton* UUIButton_Base::GetActiveButtonStyle(FName ThemeId) const
|
||||||
{
|
{
|
||||||
// Compõe FLAT a partir dos 3 sub-mapas do tema (Backgrounds/Strokes/Fonts).
|
const FUIStyle* UIStyle = GetActiveUIStyle(ThemeId);
|
||||||
// Fallback inline → Defaults (constructor de FUIStyleButton popula
|
return UIStyle ? &UIStyle->Button : nullptr;
|
||||||
// Primary/Secondary/Danger/Ghost com defaults Aurora Arcana — GC-safe).
|
|
||||||
static const FUIStyleButton Defaults;
|
|
||||||
FUIStyleButtonVariant V;
|
|
||||||
|
|
||||||
const FUIStyleButtonBackground* BG = Button.Backgrounds.Find(Variant);
|
|
||||||
if (!BG) { BG = Defaults.Backgrounds.Find(Variant); }
|
|
||||||
if (!BG) { BG = Defaults.Backgrounds.Find(TEXT("Primary")); }
|
|
||||||
if (BG)
|
|
||||||
{
|
|
||||||
V.BgNormal = BG->BgNormal;
|
|
||||||
V.BgHover = BG->BgHover;
|
|
||||||
V.BgPressed = BG->BgPressed;
|
|
||||||
V.BgDisabled = BG->BgDisabled;
|
|
||||||
V.BackgroundTexture = BG->BackgroundTexture;
|
|
||||||
V.BackgroundMargin = BG->BackgroundMargin;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FUIStyleButtonStroke* ST = Button.Strokes.Find(Variant);
|
|
||||||
if (!ST) { ST = Defaults.Strokes.Find(Variant); }
|
|
||||||
if (!ST) { ST = Defaults.Strokes.Find(TEXT("Primary")); }
|
|
||||||
if (ST)
|
|
||||||
{
|
|
||||||
V.BorderNormal = ST->BorderNormal;
|
|
||||||
V.BorderHover = ST->BorderHover;
|
|
||||||
V.BorderWidth = ST->BorderWidth;
|
|
||||||
V.CornerRadius = ST->CornerRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FUIStyleButtonFont* F = Button.Fonts.Find(Variant);
|
|
||||||
if (!F) { F = Defaults.Fonts.Find(Variant); }
|
|
||||||
if (!F) { F = Defaults.Fonts.Find(TEXT("Primary")); }
|
|
||||||
if (F)
|
|
||||||
{
|
|
||||||
V.TextColor = F->TextColor;
|
|
||||||
V.TextStyle = F->TextStyle;
|
|
||||||
}
|
|
||||||
return V;
|
|
||||||
}
|
|
||||||
|
|
||||||
TArray<FString> UUIButton_Base::GetVariantOptions() const
|
|
||||||
{
|
|
||||||
TArray<FString> Options;
|
|
||||||
for (const TCHAR* N : { TEXT("Primary"), TEXT("Secondary"), TEXT("Danger"), TEXT("Ghost") })
|
|
||||||
{
|
|
||||||
Options.Add(N);
|
|
||||||
}
|
|
||||||
if (const UDataTable* DT = LoadObject<UDataTable>(nullptr,
|
|
||||||
TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles")))
|
|
||||||
{
|
|
||||||
if (const FUIStyleRow* Row = DT->FindRow<FUIStyleRow>(
|
|
||||||
FName(TEXT("Default")), TEXT("UIButtonVariantOptions"), false))
|
|
||||||
{
|
|
||||||
for (const TPair<FName, FUIStyleButtonBackground>& P : Row->Style.Button.Backgrounds) { Options.AddUnique(P.Key.ToString()); }
|
|
||||||
for (const TPair<FName, FUIStyleButtonStroke>& P : Row->Style.Button.Strokes) { Options.AddUnique(P.Key.ToString()); }
|
|
||||||
for (const TPair<FName, FUIStyleButtonFont>& P : Row->Style.Button.Fonts) { Options.AddUnique(P.Key.ToString()); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Options;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Hyper "Set Button Text" ----
|
|
||||||
void UUIButton_Base::SetButtonText(FText InText)
|
|
||||||
{
|
|
||||||
if (!ButtonText)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const FText Final = bUppercaseText
|
|
||||||
? FText::FromString(InText.ToString().ToUpper())
|
|
||||||
: InText;
|
|
||||||
ButtonText->SetText(Final);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Hyper "Set Number Text" ----
|
|
||||||
void UUIButton_Base::SetNumberText()
|
|
||||||
{
|
|
||||||
if (!Number_Text)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (bShowNumberText)
|
|
||||||
{
|
|
||||||
Number_Text->SetText(FText::AsNumber(NumberTextAmount));
|
|
||||||
Number_Text->SetVisibility(ESlateVisibility::Visible);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Number_Text->SetVisibility(ESlateVisibility::Hidden);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Hyper "Set Icon" ----
|
|
||||||
void UUIButton_Base::SetIcon()
|
|
||||||
{
|
|
||||||
if (bUseIconInsteadOfText)
|
|
||||||
{
|
|
||||||
if (Image_Icon)
|
|
||||||
{
|
|
||||||
if (IconImage)
|
|
||||||
{
|
|
||||||
Image_Icon->SetBrushResourceObject(IconImage);
|
|
||||||
}
|
|
||||||
Image_Icon->SetVisibility(ESlateVisibility::Visible);
|
|
||||||
}
|
|
||||||
if (ButtonText)
|
|
||||||
{
|
|
||||||
ButtonText->SetVisibility(ESlateVisibility::Collapsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (Image_Icon)
|
|
||||||
{
|
|
||||||
Image_Icon->SetVisibility(ESlateVisibility::Collapsed);
|
|
||||||
}
|
|
||||||
if (ButtonText)
|
|
||||||
{
|
|
||||||
ButtonText->SetVisibility(ESlateVisibility::Visible);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Hyper "Set Sizebox Master Dimmensions" ----
|
|
||||||
void UUIButton_Base::ApplySizeBox()
|
|
||||||
{
|
|
||||||
if (bDoNotOverrideWithSizebox || !SizeBox_Master)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SizeBox_Master->SetHeightOverride(InHeightOverride);
|
|
||||||
SizeBox_Master->SetWidthOverride(InWidthOverride);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Hyper "Set InputAction Style" ----
|
|
||||||
void UUIButton_Base::ApplyInputActionStyle()
|
|
||||||
{
|
|
||||||
// InputActionWidget é privado em UCommonButtonBase (bind interno). Pega por
|
|
||||||
// nome — o widget do WBP chama-se "InputActionWidget" (bind do CommonUI).
|
|
||||||
UCommonActionWidget* IAW = Cast<UCommonActionWidget>(
|
|
||||||
GetWidgetFromName(TEXT("InputActionWidget")));
|
|
||||||
if (!IAW)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bShowInputActionWidget)
|
|
||||||
{
|
|
||||||
IAW->SetVisibility(ESlateVisibility::Visible);
|
|
||||||
IAW->SetIsEnabled(true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
IAW->SetVisibility(ESlateVisibility::Collapsed);
|
|
||||||
IAW->SetIsEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UOverlaySlot* OS = Cast<UOverlaySlot>(IAW->Slot))
|
|
||||||
{
|
|
||||||
OS->SetPadding(InputPadding);
|
|
||||||
OS->SetHorizontalAlignment(InputHorzAlignment);
|
|
||||||
OS->SetVerticalAlignment(InputVertAlignment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Hyper "Initialize Button" (orquestrador) ----
|
|
||||||
void UUIButton_Base::InitializeButton()
|
|
||||||
{
|
|
||||||
SetButtonText(ButtonTextValue);
|
|
||||||
SetNumberText();
|
|
||||||
SetIcon();
|
|
||||||
ApplySizeBox();
|
|
||||||
RefreshUIStyle();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Hyper "Update Button Style" / "Set Brush" → nossa camada FUIStyle ----
|
|
||||||
void UUIButton_Base::RefreshUIStyle()
|
|
||||||
{
|
|
||||||
const FUIStyle Fallback; // defaults do struct = paleta Aurora Arcana
|
|
||||||
const FUIStyle& ActiveStyle = ResolveActiveStyle(this, Fallback);
|
|
||||||
const FUIStyleButtonVariant& VariantStyle = ResolveVariant(ActiveStyle.Button);
|
|
||||||
|
|
||||||
// Fundo + borda no estado Normal (hover/pressed/disabled via NativeOn*).
|
|
||||||
ApplyVisualState(EUIButtonVisual::Normal);
|
|
||||||
|
|
||||||
if (ButtonText)
|
|
||||||
{
|
|
||||||
// Tipografia: CommonTextStyle da variante (padrão Hyper/CommonUI).
|
|
||||||
if (VariantStyle.TextStyle)
|
|
||||||
{
|
|
||||||
ButtonText->SetStyle(VariantStyle.TextStyle);
|
|
||||||
}
|
|
||||||
// Cor: FUIStyle manda na cor por tema (sobrepõe o CommonTextStyle).
|
|
||||||
ButtonText->SetColorAndOpacity(FSlateColor(VariantStyle.TextColor));
|
|
||||||
|
|
||||||
// Alinhamento L/C/R dentro da largura do botão.
|
|
||||||
ButtonText->SetJustification(TextAlign);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Texto adapta ao tamanho do botão (ScaleToFit) ou usa tamanho fixo.
|
|
||||||
if (TextScaleBox)
|
|
||||||
{
|
|
||||||
TextScaleBox->SetStretch(bAutoSizeText ? EStretch::ScaleToFit : EStretch::None);
|
|
||||||
|
|
||||||
// Posiciona o bloco de texto L/C/R dentro do botão (slot do Overlay).
|
|
||||||
if (UOverlaySlot* OS = Cast<UOverlaySlot>(TextScaleBox->Slot))
|
|
||||||
{
|
|
||||||
EHorizontalAlignment H = HAlign_Center;
|
|
||||||
switch (TextAlign)
|
|
||||||
{
|
|
||||||
case ETextJustify::Left: H = HAlign_Left; break;
|
|
||||||
case ETextJustify::Right: H = HAlign_Right; break;
|
|
||||||
default: H = HAlign_Center; break;
|
|
||||||
}
|
|
||||||
OS->SetHorizontalAlignment(H);
|
|
||||||
OS->SetVerticalAlignment(VAlign_Center);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BP_ApplyUIStyle(ActiveStyle.Button, VariantStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UUIButton_Base::ApplyVisualState(EUIButtonVisual State)
|
|
||||||
{
|
|
||||||
if (!Background)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const FUIStyle Fallback;
|
|
||||||
const FUIStyle& AS = ResolveActiveStyle(this, Fallback);
|
|
||||||
const FUIStyleButtonVariant& V = ResolveVariant(AS.Button);
|
|
||||||
|
|
||||||
FLinearColor Fill, Outline;
|
|
||||||
switch (State)
|
|
||||||
{
|
|
||||||
case EUIButtonVisual::Hovered: Fill = V.BgHover; Outline = V.BorderHover; break;
|
|
||||||
case EUIButtonVisual::Pressed: Fill = V.BgPressed; Outline = V.BorderHover; break;
|
|
||||||
case EUIButtonVisual::Disabled: Fill = V.BgDisabled; Outline = V.BorderNormal; break;
|
|
||||||
default: Fill = V.BgNormal; Outline = V.BorderNormal; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
FSlateBrush Brush = Background->Background;
|
|
||||||
Brush.TintColor = FSlateColor(Fill);
|
|
||||||
if (bRoundedBackground)
|
|
||||||
{
|
|
||||||
// CornerRadius/BorderWidth agora vêm da variante composta (Stroke).
|
|
||||||
const float R = V.CornerRadius;
|
|
||||||
Brush.DrawAs = ESlateBrushDrawType::RoundedBox;
|
|
||||||
Brush.OutlineSettings = FSlateBrushOutlineSettings(
|
|
||||||
FVector4(R, R, R, R), FSlateColor(Outline), V.BorderWidth);
|
|
||||||
Brush.OutlineSettings.RoundingType = ESlateBrushRoundingType::FixedRadius;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Brush.DrawAs = ESlateBrushDrawType::Box;
|
|
||||||
Brush.OutlineSettings = FSlateBrushOutlineSettings(
|
|
||||||
FVector4(0, 0, 0, 0), FSlateColor(Outline), V.BorderWidth);
|
|
||||||
}
|
|
||||||
Background->SetBrush(Brush);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UUIButton_Base::NativeOnHovered()
|
|
||||||
{
|
|
||||||
Super::NativeOnHovered();
|
|
||||||
ApplyVisualState(EUIButtonVisual::Hovered);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UUIButton_Base::NativeOnUnhovered()
|
|
||||||
{
|
|
||||||
Super::NativeOnUnhovered();
|
|
||||||
ApplyVisualState(EUIButtonVisual::Normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UUIButton_Base::NativeOnPressed()
|
|
||||||
{
|
|
||||||
Super::NativeOnPressed();
|
|
||||||
ApplyVisualState(EUIButtonVisual::Pressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UUIButton_Base::NativeOnReleased()
|
|
||||||
{
|
|
||||||
Super::NativeOnReleased();
|
|
||||||
ApplyVisualState(IsHovered() ? EUIButtonVisual::Hovered : EUIButtonVisual::Normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UUIButton_Base::NativeOnEnabled()
|
|
||||||
{
|
|
||||||
Super::NativeOnEnabled();
|
|
||||||
ApplyVisualState(EUIButtonVisual::Normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UUIButton_Base::NativeOnDisabled()
|
|
||||||
{
|
|
||||||
Super::NativeOnDisabled();
|
|
||||||
ApplyVisualState(EUIButtonVisual::Disabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UUIButton_Base::NativePreConstruct()
|
|
||||||
{
|
|
||||||
Super::NativePreConstruct();
|
|
||||||
InitializeButton();
|
|
||||||
ApplyInputActionStyle();
|
|
||||||
}
|
|
||||||
|
|
||||||
void UUIButton_Base::NativeConstruct()
|
|
||||||
{
|
|
||||||
Super::NativeConstruct();
|
|
||||||
|
|
||||||
if (!bThemeBound)
|
|
||||||
{
|
|
||||||
if (const UGameInstance* GI = GetGameInstance())
|
|
||||||
{
|
|
||||||
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
|
|
||||||
{
|
|
||||||
Theme->OnThemeChanged.AddDynamic(this, &UUIButton_Base::HandleThemeChanged);
|
|
||||||
bThemeBound = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InitializeButton();
|
|
||||||
ApplyInputActionStyle();
|
|
||||||
|
|
||||||
if (bDeactivatedOnConstructWithTimeout && Timeout > 0.f)
|
|
||||||
{
|
|
||||||
StartDeactivateTimeout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void UUIButton_Base::NativeDestruct()
|
|
||||||
{
|
|
||||||
if (UWorld* World = GetWorld())
|
|
||||||
{
|
|
||||||
World->GetTimerManager().ClearTimer(InactivityTimer);
|
|
||||||
World->GetTimerManager().ClearTimer(TimerTickHandle);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bThemeBound)
|
|
||||||
{
|
|
||||||
if (const UGameInstance* GI = GetGameInstance())
|
|
||||||
{
|
|
||||||
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
|
|
||||||
{
|
|
||||||
Theme->OnThemeChanged.RemoveDynamic(this, &UUIButton_Base::HandleThemeChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bThemeBound = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Super::NativeDestruct();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Hyper "Deactivate Button On Timeout" ----
|
|
||||||
void UUIButton_Base::StartDeactivateTimeout()
|
|
||||||
{
|
|
||||||
UWorld* World = GetWorld();
|
|
||||||
if (!World)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetIsEnabled(false);
|
|
||||||
TimeRemaining = Timeout;
|
|
||||||
|
|
||||||
World->GetTimerManager().SetTimer(
|
|
||||||
InactivityTimer, this, &UUIButton_Base::HandleTimeoutFinished, Timeout, false);
|
|
||||||
|
|
||||||
if (TimeUpdateFrequency > 0.f)
|
|
||||||
{
|
|
||||||
World->GetTimerManager().SetTimer(
|
|
||||||
TimerTickHandle, this, &UUIButton_Base::HandleTimerTick,
|
|
||||||
TimeUpdateFrequency, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void UUIButton_Base::HandleTimeoutFinished()
|
|
||||||
{
|
|
||||||
if (UWorld* World = GetWorld())
|
|
||||||
{
|
|
||||||
World->GetTimerManager().ClearTimer(TimerTickHandle);
|
|
||||||
}
|
|
||||||
TimeRemaining = 0.f;
|
|
||||||
SetIsEnabled(true);
|
|
||||||
OnButtonActivatedByTimeout.Broadcast();
|
|
||||||
}
|
|
||||||
|
|
||||||
void UUIButton_Base::HandleTimerTick()
|
|
||||||
{
|
|
||||||
TimeRemaining = FMath::Max(0.f, TimeRemaining - TimeUpdateFrequency);
|
|
||||||
OnButtonTimerUpdated.Broadcast(TimeRemaining);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UUIButton_Base::HandleThemeChanged(FName /*NewThemeId*/)
|
|
||||||
{
|
|
||||||
RefreshUIStyle();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,220 +2,55 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "CommonButtonBase.h"
|
#include "CommonButtonBase.h"
|
||||||
#include "Layout/Margin.h"
|
|
||||||
#include "Types/SlateEnums.h"
|
|
||||||
#include "UI/UIStyleTypes.h"
|
|
||||||
#include "UI/UIStyleTokens.h"
|
|
||||||
#include "UIButton_Base.generated.h"
|
#include "UIButton_Base.generated.h"
|
||||||
|
|
||||||
class UBorder;
|
|
||||||
class UTextBlock;
|
|
||||||
class UImage;
|
|
||||||
class USizeBox;
|
class USizeBox;
|
||||||
class UScaleBox;
|
class UImage;
|
||||||
|
class UCanvasPanel;
|
||||||
class UCommonTextBlock;
|
class UCommonTextBlock;
|
||||||
class UCommonActionWidget;
|
class UTextBlock;
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnUIButtonActivatedByTimeout);
|
struct FUIStyle;
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnUIButtonTimerUpdated, float, TimeRemaining);
|
struct FUIStyleButton;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Botão base do ZMMO — porte 1:1 do UI_Button_Master do projeto Hyper para C++
|
* Botão base do ZMMO (CommonUI). Hoje expõe BindWidgetOptional dos widgets
|
||||||
* auto-configurável, trocando o Struct_Style_Button do Hyper pela camada de
|
* que o WBP UI_Button_Master_New consome, e helpers pra ler o estilo ativo
|
||||||
* tema C++ (FUIStyle via UZMMOThemeSubsystem::GetActiveUIStyle()).
|
* de DT_UI_Styles. A aplicação visual (pintar brushes/cores/fonte) entra
|
||||||
*
|
* depois — esta camada só prepara o acesso.
|
||||||
* Fundação: CommonUI (UCommonButtonBase) cuida de input/foco/click.
|
|
||||||
* Camada Abstract (padrão "_Abstract" do Hyper). WBP UI_Button_Base herda
|
|
||||||
* disto; UI_Button_Master herda do WBP. Os widgets visuais são opcionais
|
|
||||||
* (BindWidgetOptional) e nomeados como no Hyper.
|
|
||||||
*
|
|
||||||
* Auto-config: NativePreConstruct chama InitializeButton (text/number/icon/
|
|
||||||
* sizebox/estilo) + ApplyInputActionStyle. Reage à troca de tema.
|
|
||||||
*/
|
*/
|
||||||
UCLASS(Abstract, Blueprintable)
|
UCLASS(Abstract, Blueprintable)
|
||||||
class ZMMO_API UUIButton_Base : public UCommonButtonBase
|
class ZMMO_API UUIButton_Base : public UCommonButtonBase
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
|
||||||
// ---- Botão ----
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button")
|
|
||||||
EUIButtonShape ButtonTypeSelection = EUIButtonShape::Rectangle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Variante visual do botão (dropdown vem de FUIStyle.Button.Backgrounds/
|
|
||||||
* Strokes/Fonts no DT_UI_Styles + as 4 clássicas como fallback).
|
|
||||||
* Data-driven via FName, sem enum fixo (padrão Hyper-style).
|
|
||||||
*/
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button",
|
|
||||||
meta = (GetOptions = "GetVariantOptions"))
|
|
||||||
FName Variant = TEXT("Primary");
|
|
||||||
|
|
||||||
/** Opções do dropdown de Variant (data-driven do DT_UI_Styles). */
|
|
||||||
UFUNCTION()
|
|
||||||
TArray<FString> GetVariantOptions() const;
|
|
||||||
|
|
||||||
/** Hyper "Transform Policy = To Upper": força o texto em maiúsculas. */
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button")
|
|
||||||
bool bUppercaseText = true;
|
|
||||||
|
|
||||||
// ---- Text Style ----
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Text Style")
|
|
||||||
FText ButtonTextValue = FText::FromString(TEXT("Button Text"));
|
|
||||||
|
|
||||||
/** Alinhamento do texto dentro do botão (esquerda/centro/direita). */
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Text Style")
|
|
||||||
TEnumAsByte<ETextJustify::Type> TextAlign = ETextJustify::Center;
|
|
||||||
|
|
||||||
/** true = texto escala p/ caber no tamanho do botão (ScaleBox ScaleToFit);
|
|
||||||
* false = usa o tamanho fixo da fonte da variante. */
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Text Style")
|
|
||||||
bool bAutoSizeText = true;
|
|
||||||
|
|
||||||
/** Arredonda o Background (RoundedBox) e desenha a borda na cor da
|
|
||||||
* variante. Desligado = caixa reta. Raio/largura vêm de FUIStyleButton. */
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Button")
|
|
||||||
bool bRoundedBackground = true;
|
|
||||||
|
|
||||||
// ---- Number Text ----
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Number Text")
|
|
||||||
bool bShowNumberText = false;
|
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Number Text")
|
|
||||||
int32 NumberTextAmount = 0;
|
|
||||||
|
|
||||||
// ---- Icon ----
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Icon")
|
|
||||||
bool bUseIconInsteadOfText = false;
|
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Icon")
|
|
||||||
TObjectPtr<UObject> IconImage = nullptr;
|
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Icon")
|
|
||||||
EUIIconType IconTypeSelection = EUIIconType::None;
|
|
||||||
|
|
||||||
// ---- Size ----
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Size")
|
|
||||||
float InHeightOverride = 50.f;
|
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Size")
|
|
||||||
float InWidthOverride = 150.f;
|
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Size")
|
|
||||||
bool bDoNotOverrideWithSizebox = false;
|
|
||||||
|
|
||||||
// ---- Input Action Style ----
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input Action Style")
|
|
||||||
bool bShowInputActionWidget = false;
|
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input Action Style")
|
|
||||||
FMargin InputPadding = FMargin(0.f, 0.f, 0.f, -22.f);
|
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input Action Style")
|
|
||||||
TEnumAsByte<EHorizontalAlignment> InputHorzAlignment = HAlign_Center;
|
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input Action Style")
|
|
||||||
TEnumAsByte<EVerticalAlignment> InputVertAlignment = VAlign_Bottom;
|
|
||||||
|
|
||||||
// ---- Deactivate by Timer ----
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deactivate By Timer")
|
|
||||||
bool bDeactivatedOnConstructWithTimeout = false;
|
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deactivate By Timer")
|
|
||||||
float Timeout = 0.f;
|
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Timer")
|
|
||||||
float TimeUpdateFrequency = 0.05f;
|
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, Category = "Timer")
|
|
||||||
float TimeRemaining = 0.f;
|
|
||||||
|
|
||||||
UPROPERTY(BlueprintAssignable, Category = "Timer")
|
|
||||||
FOnUIButtonActivatedByTimeout OnButtonActivatedByTimeout;
|
|
||||||
|
|
||||||
UPROPERTY(BlueprintAssignable, Category = "Timer")
|
|
||||||
FOnUIButtonTimerUpdated OnButtonTimerUpdated;
|
|
||||||
|
|
||||||
// ---- API (porte 1:1 das funções do Hyper) ----
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "UI Button")
|
|
||||||
void SetButtonText(FText InText);
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "UI Button")
|
|
||||||
void SetNumberText();
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "UI Button")
|
|
||||||
void SetIcon();
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "UI Button")
|
|
||||||
void ApplySizeBox();
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "UI Button")
|
|
||||||
void ApplyInputActionStyle();
|
|
||||||
|
|
||||||
/** Orquestrador (Hyper "Initialize Button"): aplica tudo + estilo do tema. */
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "UI Button")
|
|
||||||
void InitializeButton();
|
|
||||||
|
|
||||||
/** Re-resolve os tokens do tema ativo e reaplica (Hyper "Update Button Style"). */
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "UI Style")
|
|
||||||
void RefreshUIStyle();
|
|
||||||
|
|
||||||
/** Estado visual do botão (mapeia nos tokens BgNormal/Hover/Pressed/Disabled). */
|
|
||||||
enum class EUIButtonVisual : uint8 { Normal, Hovered, Pressed, Disabled };
|
|
||||||
|
|
||||||
/** Pinta o Background (fill + borda) conforme o estado, re-resolvendo o tema. */
|
|
||||||
void ApplyVisualState(EUIButtonVisual State);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void NativePreConstruct() override;
|
// ---- Widgets BindWidgetOptional (espelham a árvore do UI_Button_Master_New) ----
|
||||||
virtual void NativeConstruct() override;
|
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||||
virtual void NativeDestruct() override;
|
TObjectPtr<USizeBox> SizeBox;
|
||||||
|
|
||||||
// Estados do UCommonButtonBase → repinta o Background pela variante.
|
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||||
virtual void NativeOnHovered() override;
|
TObjectPtr<UImage> ImgStoke;
|
||||||
virtual void NativeOnUnhovered() override;
|
|
||||||
virtual void NativeOnPressed() override;
|
|
||||||
virtual void NativeOnReleased() override;
|
|
||||||
virtual void NativeOnEnabled() override;
|
|
||||||
virtual void NativeOnDisabled() override;
|
|
||||||
|
|
||||||
/** Hook opcional: o WBP pode estender/sobrescrever a aplicação visual. */
|
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||||
UFUNCTION(BlueprintImplementableEvent, Category = "UI Style",
|
TObjectPtr<UCanvasPanel> CanvasPanel_Text;
|
||||||
meta = (DisplayName = "Apply UI Style"))
|
|
||||||
void BP_ApplyUIStyle(const FUIStyleButton& Button, const FUIStyleButtonVariant& VariantStyle);
|
|
||||||
|
|
||||||
FUIStyleButtonVariant ResolveVariant(const FUIStyleButton& Button) const;
|
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||||
|
|
||||||
// ---- Widgets visuais opcionais (nomes espelham o Hyper) ----
|
|
||||||
UPROPERTY(meta = (BindWidgetOptional))
|
|
||||||
TObjectPtr<USizeBox> SizeBox_Master;
|
|
||||||
|
|
||||||
UPROPERTY(meta = (BindWidgetOptional))
|
|
||||||
TObjectPtr<UCommonTextBlock> ButtonText;
|
TObjectPtr<UCommonTextBlock> ButtonText;
|
||||||
|
|
||||||
/** Envolve o ButtonText: escala o texto p/ caber no botão (bAutoSizeText). */
|
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||||
UPROPERTY(meta = (BindWidgetOptional))
|
|
||||||
TObjectPtr<UScaleBox> TextScaleBox;
|
|
||||||
|
|
||||||
UPROPERTY(meta = (BindWidgetOptional))
|
|
||||||
TObjectPtr<UTextBlock> Number_Text;
|
TObjectPtr<UTextBlock> Number_Text;
|
||||||
|
|
||||||
UPROPERTY(meta = (BindWidgetOptional))
|
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||||
TObjectPtr<UImage> Image_Icon;
|
TObjectPtr<UImage> ImgIcon;
|
||||||
|
|
||||||
/** Fundo temável (nossa adição sobre o Hyper, p/ FUIStyle). */
|
// ---- Acesso ao estilo ativo (DT_UI_Styles) — C++ only ----
|
||||||
UPROPERTY(meta = (BindWidgetOptional))
|
|
||||||
TObjectPtr<UBorder> Background;
|
|
||||||
|
|
||||||
private:
|
/** Carrega DT_UI_Styles e devolve o FUIStyle da row pedida (default "Default").
|
||||||
void StartDeactivateTimeout();
|
* Retorna nullptr se a DT ou a row não existirem. Ponteiro válido enquanto
|
||||||
void HandleTimeoutFinished();
|
* a DT estiver carregada — uso típico é leitura imediata, sem armazenar. */
|
||||||
void HandleTimerTick();
|
const FUIStyle* GetActiveUIStyle(FName ThemeId = TEXT("Default")) const;
|
||||||
|
|
||||||
UFUNCTION()
|
/** Atalho: devolve só o bloco Button do FUIStyle ativo. */
|
||||||
void HandleThemeChanged(FName NewThemeId);
|
const FUIStyleButton* GetActiveButtonStyle(FName ThemeId = TEXT("Default")) const;
|
||||||
|
|
||||||
bool bThemeBound = false;
|
|
||||||
FTimerHandle InactivityTimer;
|
|
||||||
FTimerHandle TimerTickHandle;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,83 +52,43 @@ void UUIPanel_Base::RefreshUIStyle()
|
|||||||
const FUIStyle AS = ResolvePanelStyle(this, Fallback);
|
const FUIStyle AS = ResolvePanelStyle(this, Fallback);
|
||||||
const FUIStylePanel& P = AS.Panel;
|
const FUIStylePanel& P = AS.Panel;
|
||||||
|
|
||||||
// ---- Modo TEXTURA (padrão Hyper): brush por EUIPanelTexture ----
|
// bUseTheme = true -> brushes vêm da DT (lookup pelas 3 chaves).
|
||||||
// Fiel ao UI_Panel_Master do Hyper: só faz SetBrush nos dois Borders
|
// bUseTheme = false -> brushes vêm dos 3 FSlateBrush em Panels (direto).
|
||||||
// (Find no mapa por chave). Sem padding/visibility — a arte da textura
|
|
||||||
// já traz a moldura; o conteúdo é injetado pelo WBP filho.
|
|
||||||
// Seed dos mapas da instância a partir do tema quando vazios — assim o
|
|
||||||
// Details mostra Panel Background/Outline preenchidos e editáveis (igual
|
|
||||||
// ao Hyper, cujo `Panels` é alimentado pelo estilo). Edições do designer
|
|
||||||
// têm prioridade; o tema (P) é fallback por chave.
|
|
||||||
if (Panels.PanelBackground.Num() == 0) { Panels.PanelBackground = P.PanelBackground; }
|
|
||||||
if (Panels.PanelOutline.Num() == 0) { Panels.PanelOutline = P.PanelOutline; }
|
|
||||||
|
|
||||||
const FSlateBrush* BgTex = nullptr;
|
static const FSlateBrush NoDrawBrush = []
|
||||||
if (PanelTexture != EUIPanelTexture::None)
|
|
||||||
{
|
{
|
||||||
BgTex = Panels.PanelBackground.Find(PanelTexture);
|
FSlateBrush B;
|
||||||
if (!BgTex) { BgTex = P.PanelBackground.Find(PanelTexture); }
|
B.DrawAs = ESlateBrushDrawType::NoDrawType;
|
||||||
}
|
return B;
|
||||||
if (BgTex)
|
}();
|
||||||
|
|
||||||
|
auto ResolveFromTheme = [](EUIPanelTexture Key,
|
||||||
|
const TMap<EUIPanelTexture, FSlateBrush>& Map) -> const FSlateBrush*
|
||||||
{
|
{
|
||||||
Background->SetBrush(*BgTex);
|
return (Key == EUIPanelTexture::None) ? nullptr : Map.Find(Key);
|
||||||
if (Outline)
|
};
|
||||||
{
|
|
||||||
// Hyper: Find sempre seguido de SetBrush. No Hyper a entrada de
|
const FSlateBrush* BgTex = bUseTheme
|
||||||
// outline ausente é um brush TRANSPARENTE (alpha 0) — não some
|
? ResolveFromTheme(BackgroundKey, P.PanelBackground)
|
||||||
// widget, só não desenha. Um FSlateBrush default desenha um
|
: &Panels.PanelBackground;
|
||||||
// retângulo BRANCO; o equivalente correto é DrawAs=NoDrawType.
|
Background->SetBrush(BgTex ? *BgTex : NoDrawBrush);
|
||||||
const FSlateBrush* OlTex = Panels.PanelOutline.Find(PanelTexture);
|
|
||||||
if (!OlTex) { OlTex = P.PanelOutline.Find(PanelTexture); }
|
|
||||||
static const FSlateBrush NoDrawBrush = []
|
|
||||||
{
|
|
||||||
FSlateBrush B;
|
|
||||||
B.DrawAs = ESlateBrushDrawType::NoDrawType;
|
|
||||||
return B;
|
|
||||||
}();
|
|
||||||
Outline->SetBrush(OlTex ? *OlTex : NoDrawBrush);
|
|
||||||
}
|
|
||||||
BP_ApplyPanelStyle(P);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Modo COR (RoundedBox) ----
|
|
||||||
if (Outline)
|
if (Outline)
|
||||||
{
|
{
|
||||||
Outline->SetVisibility(ESlateVisibility::Collapsed);
|
const FSlateBrush* OlTex = bUseTheme
|
||||||
|
? ResolveFromTheme(OutlineKey, P.PanelOutline)
|
||||||
|
: &Panels.PanelOutline;
|
||||||
|
Outline->SetBrush(OlTex ? *OlTex : NoDrawBrush);
|
||||||
}
|
}
|
||||||
|
|
||||||
FLinearColor Fill;
|
if (OutlineEffect)
|
||||||
FLinearColor OutlineColor = P.BorderColor;
|
|
||||||
switch (PanelType)
|
|
||||||
{
|
{
|
||||||
case EUIPanelType::Secondary: Fill = P.BgRaised; break;
|
const FSlateBrush* OeTex = bUseTheme
|
||||||
case EUIPanelType::Tertiary: Fill = P.BgSunken; break;
|
? ResolveFromTheme(OutlineEffectKey, P.PanelOutlineEffect)
|
||||||
case EUIPanelType::Card: Fill = P.CardSelectedBg; OutlineColor = P.CardSelectedBorder; break;
|
: &Panels.PanelOutlineEffect;
|
||||||
case EUIPanelType::Primary:
|
OutlineEffect->SetBrush(OeTex ? *OeTex : NoDrawBrush);
|
||||||
case EUIPanelType::None:
|
|
||||||
default: Fill = P.Bg; break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FSlateBrush Brush = Background->Background;
|
|
||||||
Brush.TintColor = FSlateColor(Fill);
|
|
||||||
if (bRoundedBackground)
|
|
||||||
{
|
|
||||||
const float R = bUseSmallPadding ? P.CornerRadiusSmall : P.CornerRadius;
|
|
||||||
Brush.DrawAs = ESlateBrushDrawType::RoundedBox;
|
|
||||||
Brush.OutlineSettings = FSlateBrushOutlineSettings(
|
|
||||||
FVector4(R, R, R, R), FSlateColor(OutlineColor), P.BorderWidth);
|
|
||||||
Brush.OutlineSettings.RoundingType = ESlateBrushRoundingType::FixedRadius;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Brush.DrawAs = ESlateBrushDrawType::Box;
|
|
||||||
Brush.OutlineSettings = FSlateBrushOutlineSettings(
|
|
||||||
FVector4(0, 0, 0, 0), FSlateColor(OutlineColor), P.BorderWidth);
|
|
||||||
}
|
|
||||||
Background->SetBrush(Brush);
|
|
||||||
Background->SetPadding(bUseSmallPadding ? P.PaddingSmall : P.Padding);
|
|
||||||
|
|
||||||
BP_ApplyPanelStyle(P);
|
BP_ApplyPanelStyle(P);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,34 +23,31 @@ class ZMMO_API UUIPanel_Base : public UCommonUserWidget
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/** Tipo do painel — escolhe Bg/BgRaised/BgSunken/Card de FUIStylePanel. */
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel")
|
|
||||||
EUIPanelType PanelType = EUIPanelType::Primary;
|
|
||||||
|
|
||||||
/** Arredonda o Background (RoundedBox) com a borda do tema. */
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel")
|
|
||||||
bool bRoundedBackground = true;
|
|
||||||
|
|
||||||
/** Usa o padding "pequeno" do tema (PaddingSmall) em vez de Padding. */
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel")
|
|
||||||
bool bUseSmallPadding = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Conjunto de textura (modo Hyper). Se != None E houver brush no mapa
|
* true = lê brushes da DT_UI_Styles (FUIStylePanel) — tema centralizado.
|
||||||
* FUIStylePanel.PanelBackground p/ esta chave, usa o painel TEXTURIZADO
|
* false = ignora a DT e usa apenas `Panels` (brushes manuais no Details).
|
||||||
* (brush bg + outline). Senão, cai no modo COR (RoundedBox acima).
|
|
||||||
*/
|
*/
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel")
|
||||||
EUIPanelTexture PanelTexture = EUIPanelTexture::None;
|
bool bUseTheme = true;
|
||||||
|
|
||||||
/**
|
// ---- Modo TEMA: escolhe a chave do mapa FUIStylePanel pra cada Border. ----
|
||||||
* Mapas de brush por instância — espelha o `Panels` do UI_Panel_Master
|
|
||||||
* do Hyper (Details mostra Panel Background / Panel Outline editáveis).
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel",
|
||||||
* Tem prioridade. Se não houver brush p/ a chave aqui, cai no tema
|
meta = (EditCondition = "bUseTheme", EditConditionHides))
|
||||||
* (DT_UI_Styles). Em NativePreConstruct é semeado a partir do tema
|
EUIPanelTexture BackgroundKey = EUIPanelTexture::None;
|
||||||
* quando vazio, então por padrão o tema manda — como no Hyper.
|
|
||||||
*/
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel",
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel")
|
meta = (EditCondition = "bUseTheme", EditConditionHides))
|
||||||
|
EUIPanelTexture OutlineKey = EUIPanelTexture::None;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel",
|
||||||
|
meta = (EditCondition = "bUseTheme", EditConditionHides))
|
||||||
|
EUIPanelTexture OutlineEffectKey = EUIPanelTexture::None;
|
||||||
|
|
||||||
|
// ---- Modo MANUAL: 3 brushes editáveis direto no Details. ----
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel",
|
||||||
|
meta = (EditCondition = "!bUseTheme", EditConditionHides))
|
||||||
FUIPanelBrushSet Panels;
|
FUIPanelBrushSet Panels;
|
||||||
|
|
||||||
/** Re-resolve os tokens do tema ativo e reaplica no Background. */
|
/** Re-resolve os tokens do tema ativo e reaplica no Background. */
|
||||||
@@ -75,6 +72,10 @@ protected:
|
|||||||
UPROPERTY(meta = (BindWidgetOptional))
|
UPROPERTY(meta = (BindWidgetOptional))
|
||||||
TObjectPtr<UBorder> Outline;
|
TObjectPtr<UBorder> Outline;
|
||||||
|
|
||||||
|
/** Efeito sobre o contorno (glow/embossing/etc.) — pintado acima do Outline. */
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional))
|
||||||
|
TObjectPtr<UBorder> OutlineEffect;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleThemeChanged(FName NewThemeId);
|
void HandleThemeChanged(FName NewThemeId);
|
||||||
|
|||||||
69
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_BonusRow.cpp
Normal file
69
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_BonusRow.cpp
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#include "UIPlayerStatus_BonusRow.h"
|
||||||
|
|
||||||
|
#include "Components/TextBlock.h"
|
||||||
|
#include "Internationalization/Text.h"
|
||||||
|
|
||||||
|
void UUIPlayerStatus_BonusRow::SetStatName(FText DisplayName)
|
||||||
|
{
|
||||||
|
if (Text_StatName) Text_StatName->SetText(DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_BonusRow::SetValues(int32 Base, int32 Bonus)
|
||||||
|
{
|
||||||
|
if (Text_BasePart) Text_BasePart->SetText(FText::AsNumber(Base));
|
||||||
|
|
||||||
|
const bool bShowBonus = (Bonus != 0);
|
||||||
|
SetBonusVisibility(bShowBonus);
|
||||||
|
if (bShowBonus)
|
||||||
|
{
|
||||||
|
if (Text_SplitOp) Text_SplitOp->SetText(FText::FromString(Bonus > 0 ? TEXT("+") : TEXT("-")));
|
||||||
|
if (Text_BonusPart) Text_BonusPart->SetText(FText::AsNumber(FMath::Abs(Bonus)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_BonusRow::SetRangeValues(int32 MinBase, int32 MaxBase, int32 Bonus)
|
||||||
|
{
|
||||||
|
if (Text_BasePart)
|
||||||
|
{
|
||||||
|
Text_BasePart->SetText(FText::FromString(FString::Printf(TEXT("%d~%d"), MinBase, MaxBase)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool bShowBonus = (Bonus != 0);
|
||||||
|
SetBonusVisibility(bShowBonus);
|
||||||
|
if (bShowBonus)
|
||||||
|
{
|
||||||
|
if (Text_SplitOp) Text_SplitOp->SetText(FText::FromString(Bonus > 0 ? TEXT("+") : TEXT("-")));
|
||||||
|
if (Text_BonusPart) Text_BonusPart->SetText(FText::AsNumber(FMath::Abs(Bonus)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_BonusRow::SetValuesX10(int32 BaseX10, int32 BonusX10)
|
||||||
|
{
|
||||||
|
auto FormatX10 = [](int32 ValX10) -> FString
|
||||||
|
{
|
||||||
|
const int32 Sign = ValX10 < 0 ? -1 : 1;
|
||||||
|
const int32 Abs = FMath::Abs(ValX10);
|
||||||
|
return FString::Printf(TEXT("%d.%d"), Sign * (Abs / 10), Abs % 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Text_BasePart) Text_BasePart->SetText(FText::FromString(FormatX10(BaseX10)));
|
||||||
|
|
||||||
|
const bool bShowBonus = (BonusX10 != 0);
|
||||||
|
SetBonusVisibility(bShowBonus);
|
||||||
|
if (bShowBonus)
|
||||||
|
{
|
||||||
|
if (Text_SplitOp) Text_SplitOp->SetText(FText::FromString(BonusX10 > 0 ? TEXT("+") : TEXT("-")));
|
||||||
|
if (Text_BonusPart)
|
||||||
|
{
|
||||||
|
const int32 Abs = FMath::Abs(BonusX10);
|
||||||
|
Text_BonusPart->SetText(FText::FromString(FString::Printf(TEXT("%d.%d"), Abs / 10, Abs % 10)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_BonusRow::SetBonusVisibility(bool bShow)
|
||||||
|
{
|
||||||
|
const ESlateVisibility Vis = bShow ? ESlateVisibility::Visible : ESlateVisibility::Collapsed;
|
||||||
|
if (Text_SplitOp) Text_SplitOp->SetVisibility(Vis);
|
||||||
|
if (Text_BonusPart) Text_BonusPart->SetVisibility(Vis);
|
||||||
|
}
|
||||||
46
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_BonusRow.h
Normal file
46
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_BonusRow.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "CommonUserWidget.h"
|
||||||
|
#include "UIPlayerStatus_BonusRow.generated.h"
|
||||||
|
|
||||||
|
class UTextBlock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linha de um stat derivado (ATK/DEF/HIT/etc.) na janela de Status.
|
||||||
|
* Layout: nome + valor base + (separador + bônus). Sem botão.
|
||||||
|
*
|
||||||
|
* Três modos de valor:
|
||||||
|
* - SetValues(base, bonus) "10 + 2"
|
||||||
|
* - SetRangeValues(min, max, bonus) "10~15 + 2" (MATK pre-renewal)
|
||||||
|
* - SetValuesX10(baseX10, bonusX10) "1.5 + 0.0" (CRIT armazenado ×10)
|
||||||
|
*
|
||||||
|
* Pareada com o WBP UI_PlayerStatus_BonusRow.
|
||||||
|
*/
|
||||||
|
UCLASS(Abstract, Blueprintable)
|
||||||
|
class ZMMO_API UUIPlayerStatus_BonusRow : public UCommonUserWidget
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Zeus|Status")
|
||||||
|
void SetStatName(FText DisplayName);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Zeus|Status")
|
||||||
|
void SetValues(int32 Base, int32 Bonus);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Zeus|Status")
|
||||||
|
void SetRangeValues(int32 MinBase, int32 MaxBase, int32 Bonus);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Zeus|Status")
|
||||||
|
void SetValuesX10(int32 BaseX10, int32 BonusX10);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UTextBlock> Text_StatName;
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UTextBlock> Text_BasePart;
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UTextBlock> Text_SplitOp;
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UTextBlock> Text_BonusPart;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetBonusVisibility(bool bShow);
|
||||||
|
};
|
||||||
73
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_StatRow.cpp
Normal file
73
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_StatRow.cpp
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#include "UIPlayerStatus_StatRow.h"
|
||||||
|
|
||||||
|
#include "Components/Button.h"
|
||||||
|
#include "Components/HorizontalBox.h"
|
||||||
|
#include "Components/TextBlock.h"
|
||||||
|
#include "Internationalization/Text.h"
|
||||||
|
|
||||||
|
void UUIPlayerStatus_StatRow::SetupStat(int32 InStatId, FText IconLetter, FText DisplayName)
|
||||||
|
{
|
||||||
|
StatId = InStatId;
|
||||||
|
if (Border_StatIconWrap_Label) Border_StatIconWrap_Label->SetText(IconLetter);
|
||||||
|
if (Text_StatName) Text_StatName->SetText(DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_StatRow::SetValues(int32 Base, int32 Bonus)
|
||||||
|
{
|
||||||
|
if (Text_StatBase) Text_StatBase->SetText(FText::AsNumber(Base));
|
||||||
|
|
||||||
|
const bool bShowBonus = (Bonus != 0);
|
||||||
|
const ESlateVisibility BonusVis = bShowBonus ? ESlateVisibility::Visible : ESlateVisibility::Collapsed;
|
||||||
|
|
||||||
|
if (Layout_BonusGroup)
|
||||||
|
{
|
||||||
|
Layout_BonusGroup->SetVisibility(BonusVis);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Text_Sign) Text_Sign->SetVisibility(BonusVis);
|
||||||
|
if (Text_StatBonus) Text_StatBonus->SetVisibility(BonusVis);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bShowBonus)
|
||||||
|
{
|
||||||
|
if (Text_Sign) Text_Sign->SetText(FText::FromString(Bonus > 0 ? TEXT("+") : TEXT("-")));
|
||||||
|
if (Text_StatBonus) Text_StatBonus->SetText(FText::AsNumber(FMath::Abs(Bonus)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_StatRow::SetPlusVisible(bool bVisible)
|
||||||
|
{
|
||||||
|
if (Button_PlusBtn)
|
||||||
|
{
|
||||||
|
Button_PlusBtn->SetVisibility(bVisible ? ESlateVisibility::Visible : ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_StatRow::SetPlusEnabled(bool bEnabled)
|
||||||
|
{
|
||||||
|
if (Button_PlusBtn) Button_PlusBtn->SetIsEnabled(bEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_StatRow::NativeOnInitialized()
|
||||||
|
{
|
||||||
|
Super::NativeOnInitialized();
|
||||||
|
if (Button_PlusBtn)
|
||||||
|
{
|
||||||
|
Button_PlusBtn->OnClicked.AddDynamic(this, &UUIPlayerStatus_StatRow::HandlePlusClicked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_StatRow::NativeDestruct()
|
||||||
|
{
|
||||||
|
if (Button_PlusBtn)
|
||||||
|
{
|
||||||
|
Button_PlusBtn->OnClicked.RemoveDynamic(this, &UUIPlayerStatus_StatRow::HandlePlusClicked);
|
||||||
|
}
|
||||||
|
Super::NativeDestruct();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_StatRow::HandlePlusClicked()
|
||||||
|
{
|
||||||
|
OnPlusClicked.Broadcast(StatId);
|
||||||
|
}
|
||||||
68
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_StatRow.h
Normal file
68
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_StatRow.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "CommonUserWidget.h"
|
||||||
|
#include "UIPlayerStatus_StatRow.generated.h"
|
||||||
|
|
||||||
|
class UButton;
|
||||||
|
class UHorizontalBox;
|
||||||
|
class UTextBlock;
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FZMMOOnStatRowPlusClicked, int32, StatId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linha de um stat primário (STR/AGI/VIT/INT/DEX/LUK) na janela de Status.
|
||||||
|
* Layout: ícone + nome + valor base + (sinal + bônus) + botão "+".
|
||||||
|
*
|
||||||
|
* Sem lógica de domínio — a janela (UUIPlayerStatus_Window) injeta o
|
||||||
|
* StatId e os valores via Setup/SetValues; o clique no "+" é re-emitido
|
||||||
|
* como OnPlusClicked(StatId).
|
||||||
|
*
|
||||||
|
* Pareada com o WBP UI_PlayerStatus_StatRow.
|
||||||
|
*/
|
||||||
|
UCLASS(Abstract, Blueprintable)
|
||||||
|
class ZMMO_API UUIPlayerStatus_StatRow : public UCommonUserWidget
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Setup único chamado pela window quando inicializa a linha. */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Zeus|Status")
|
||||||
|
void SetupStat(int32 InStatId, FText IconLetter, FText DisplayName);
|
||||||
|
|
||||||
|
/** Atualiza valores. Bonus=0 esconde a parte "+N". */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Zeus|Status")
|
||||||
|
void SetValues(int32 Base, int32 Bonus);
|
||||||
|
|
||||||
|
/** Mostra/esconde o botão "+". */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Zeus|Status")
|
||||||
|
void SetPlusVisible(bool bVisible);
|
||||||
|
|
||||||
|
/** Habilita/desabilita o botão "+" (independente da visibilidade). */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Zeus|Status")
|
||||||
|
void SetPlusEnabled(bool bEnabled);
|
||||||
|
|
||||||
|
/** Disparado quando o usuário clica em "+". */
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Zeus|Status")
|
||||||
|
FZMMOOnStatRowPlusClicked OnPlusClicked;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void NativeOnInitialized() override;
|
||||||
|
virtual void NativeDestruct() override;
|
||||||
|
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UTextBlock> Border_StatIconWrap_Label;
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UTextBlock> Text_StatName;
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UTextBlock> Text_StatBase;
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UTextBlock> Text_Sign;
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UTextBlock> Text_StatBonus;
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UButton> Button_PlusBtn;
|
||||||
|
|
||||||
|
/** HBox que envolve sinal+bonus; ocultado quando bonus = 0. Bind opcional —
|
||||||
|
* se o WBP não expuser, cai pra ocultar Text_Sign/Text_StatBonus individualmente. */
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UHorizontalBox> Layout_BonusGroup;
|
||||||
|
|
||||||
|
private:
|
||||||
|
UFUNCTION() void HandlePlusClicked();
|
||||||
|
|
||||||
|
int32 StatId = 0;
|
||||||
|
};
|
||||||
231
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.cpp
Normal file
231
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.cpp
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
#include "UIPlayerStatus_Window.h"
|
||||||
|
|
||||||
|
#include "CommonInputTypeEnum.h"
|
||||||
|
#include "Components/Button.h"
|
||||||
|
#include "Components/TextBlock.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "GameFramework/PlayerController.h"
|
||||||
|
#include "GameFramework/PlayerState.h"
|
||||||
|
#include "Internationalization/Text.h"
|
||||||
|
#include "ZMMO.h"
|
||||||
|
#include "ZMMOAttributeComponent.h"
|
||||||
|
#include "ZMMOJobDataAsset.h"
|
||||||
|
#include "ZMMOJobsSubsystem.h"
|
||||||
|
#include "ZMMOPlayerState.h"
|
||||||
|
#include "UI/InGame/UIInGameFlowSubsystem.h"
|
||||||
|
#include "UI/InGameTypes.h"
|
||||||
|
#include "UI/Widgets/UIPlayerStatus_BonusRow.h"
|
||||||
|
#include "UI/Widgets/UIPlayerStatus_StatRow.h"
|
||||||
|
|
||||||
|
UUIPlayerStatus_Window::UUIPlayerStatus_Window(const FObjectInitializer& ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
bIsBackHandler = true;
|
||||||
|
bAutoActivate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TOptional<FUIInputConfig> UUIPlayerStatus_Window::GetDesiredInputConfig() const
|
||||||
|
{
|
||||||
|
return FUIInputConfig(ECommonInputMode::Menu, EMouseCaptureMode::NoCapture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_Window::NativeOnActivated()
|
||||||
|
{
|
||||||
|
Super::NativeOnActivated();
|
||||||
|
|
||||||
|
BindRow(Row_Str, 0, TEXT("S"), TEXT("STR"));
|
||||||
|
BindRow(Row_Agi, 1, TEXT("A"), TEXT("AGI"));
|
||||||
|
BindRow(Row_Vit, 2, TEXT("V"), TEXT("VIT"));
|
||||||
|
BindRow(Row_Int, 3, TEXT("I"), TEXT("INT"));
|
||||||
|
BindRow(Row_Dex, 4, TEXT("D"), TEXT("DEX"));
|
||||||
|
BindRow(Row_Luk, 5, TEXT("L"), TEXT("LUK"));
|
||||||
|
|
||||||
|
if (Row_Atk) Row_Atk->SetStatName(FText::FromString(TEXT("ATK")));
|
||||||
|
if (Row_Matk) Row_Matk->SetStatName(FText::FromString(TEXT("MATK")));
|
||||||
|
if (Row_Def) Row_Def->SetStatName(FText::FromString(TEXT("DEF")));
|
||||||
|
if (Row_Mdef) Row_Mdef->SetStatName(FText::FromString(TEXT("MDEF")));
|
||||||
|
if (Row_Hit) Row_Hit->SetStatName(FText::FromString(TEXT("HIT")));
|
||||||
|
if (Row_Flee) Row_Flee->SetStatName(FText::FromString(TEXT("FLEE")));
|
||||||
|
if (Row_Crit) Row_Crit->SetStatName(FText::FromString(TEXT("CRIT")));
|
||||||
|
if (Row_Aspd) Row_Aspd->SetStatName(FText::FromString(TEXT("ASPD")));
|
||||||
|
|
||||||
|
if (TitleText) TitleText->SetText(FText::FromString(TEXT("Status")));
|
||||||
|
|
||||||
|
if (CloseBtn) CloseBtn->OnClicked.AddDynamic(this, &UUIPlayerStatus_Window::OnCloseClicked);
|
||||||
|
|
||||||
|
BindToLocalPlayer();
|
||||||
|
|
||||||
|
UE_LOG(LogZMMO, Log, TEXT("UIPlayerStatus_Window activated"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_Window::NativeOnDeactivated()
|
||||||
|
{
|
||||||
|
UnbindFromComponent();
|
||||||
|
|
||||||
|
auto UnbindRow = [this](UUIPlayerStatus_StatRow* Row)
|
||||||
|
{
|
||||||
|
if (Row) Row->OnPlusClicked.RemoveDynamic(this, &UUIPlayerStatus_Window::HandleRowPlusClicked);
|
||||||
|
};
|
||||||
|
UnbindRow(Row_Str); UnbindRow(Row_Agi); UnbindRow(Row_Vit);
|
||||||
|
UnbindRow(Row_Int); UnbindRow(Row_Dex); UnbindRow(Row_Luk);
|
||||||
|
|
||||||
|
if (CloseBtn) CloseBtn->OnClicked.RemoveDynamic(this, &UUIPlayerStatus_Window::OnCloseClicked);
|
||||||
|
|
||||||
|
Super::NativeOnDeactivated();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_Window::BindRow(UUIPlayerStatus_StatRow* Row, int32 StatId, const TCHAR* IconLetter, const TCHAR* DisplayName)
|
||||||
|
{
|
||||||
|
if (!Row) return;
|
||||||
|
Row->SetupStat(StatId, FText::FromString(IconLetter), FText::FromString(DisplayName));
|
||||||
|
Row->OnPlusClicked.AddDynamic(this, &UUIPlayerStatus_Window::HandleRowPlusClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_Window::BindToLocalPlayer()
|
||||||
|
{
|
||||||
|
UWorld* World = GetWorld();
|
||||||
|
if (!World) return;
|
||||||
|
APlayerController* PC = World->GetFirstPlayerController();
|
||||||
|
if (!PC || !PC->PlayerState) return;
|
||||||
|
|
||||||
|
// CharName vem do AZMMOPlayerState (setado pelo S_SPAWN_PLAYER no spawn
|
||||||
|
// local). Fallback pra APlayerState::GetPlayerName() se o cast falhar ou
|
||||||
|
// se CharName ainda nao chegou (race com handler que dispara o widget).
|
||||||
|
if (Text_CharName)
|
||||||
|
{
|
||||||
|
FString DisplayName = PC->PlayerState->GetPlayerName();
|
||||||
|
if (const AZMMOPlayerState* ZMMOPs = Cast<AZMMOPlayerState>(PC->PlayerState))
|
||||||
|
{
|
||||||
|
if (!ZMMOPs->CharName.IsEmpty())
|
||||||
|
{
|
||||||
|
DisplayName = ZMMOPs->CharName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text_CharName->SetText(FText::FromString(DisplayName));
|
||||||
|
}
|
||||||
|
|
||||||
|
UZMMOAttributeComponent* Comp = PC->PlayerState->FindComponentByClass<UZMMOAttributeComponent>();
|
||||||
|
if (!Comp || Comp == BoundComponent.Get()) return;
|
||||||
|
|
||||||
|
UnbindFromComponent();
|
||||||
|
BoundComponent = Comp;
|
||||||
|
Comp->OnAttributesChanged.AddDynamic(this, &UUIPlayerStatus_Window::HandleAttributesChanged);
|
||||||
|
Comp->OnStatAllocReply.AddDynamic(this, &UUIPlayerStatus_Window::HandleStatAllocReply);
|
||||||
|
RefreshFromSnapshot(Comp->GetSnapshot());
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_Window::UnbindFromComponent()
|
||||||
|
{
|
||||||
|
if (UZMMOAttributeComponent* Old = BoundComponent.Get())
|
||||||
|
{
|
||||||
|
Old->OnAttributesChanged.RemoveDynamic(this, &UUIPlayerStatus_Window::HandleAttributesChanged);
|
||||||
|
Old->OnStatAllocReply.RemoveDynamic(this, &UUIPlayerStatus_Window::HandleStatAllocReply);
|
||||||
|
}
|
||||||
|
BoundComponent.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_Window::HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot)
|
||||||
|
{
|
||||||
|
bPlusButtonsLocked = false;
|
||||||
|
RefreshFromSnapshot(Snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_Window::HandleStatAllocReply(bool bAccepted, int32 Reason)
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Log, TEXT("UIPlayerStatus_Window: alloc reply accepted=%d reason=%d"),
|
||||||
|
bAccepted ? 1 : 0, Reason);
|
||||||
|
|
||||||
|
bPlusButtonsLocked = false;
|
||||||
|
if (UZMMOAttributeComponent* Comp = BoundComponent.Get())
|
||||||
|
{
|
||||||
|
const FZMMOAttributesSnapshot& S = Comp->GetSnapshot();
|
||||||
|
SetAllPrimaryRowsPlusEnabled(S.StatusPoint > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_Window::HandleRowPlusClicked(int32 StatId)
|
||||||
|
{
|
||||||
|
if (bPlusButtonsLocked) return;
|
||||||
|
UZMMOAttributeComponent* Comp = BoundComponent.Get();
|
||||||
|
if (!Comp) return;
|
||||||
|
|
||||||
|
bPlusButtonsLocked = true;
|
||||||
|
SetAllPrimaryRowsPlusEnabled(false);
|
||||||
|
|
||||||
|
Comp->RequestStatAlloc(StatId, /*Amount*/ 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_Window::SetAllPrimaryRowsPlusEnabled(bool bEnabled)
|
||||||
|
{
|
||||||
|
auto Set = [bEnabled](UUIPlayerStatus_StatRow* Row)
|
||||||
|
{
|
||||||
|
if (Row) Row->SetPlusEnabled(bEnabled);
|
||||||
|
};
|
||||||
|
Set(Row_Str); Set(Row_Agi); Set(Row_Vit);
|
||||||
|
Set(Row_Int); Set(Row_Dex); Set(Row_Luk);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_Window::RefreshFromSnapshot(const FZMMOAttributesSnapshot& S)
|
||||||
|
{
|
||||||
|
// Primários: snapshot atual não splita base/bonus de primários (só derivados).
|
||||||
|
if (Row_Str) Row_Str->SetValues(S.Str, 0);
|
||||||
|
if (Row_Agi) Row_Agi->SetValues(S.Agi, 0);
|
||||||
|
if (Row_Vit) Row_Vit->SetValues(S.Vit, 0);
|
||||||
|
if (Row_Int) Row_Int->SetValues(S.Int, 0);
|
||||||
|
if (Row_Dex) Row_Dex->SetValues(S.Dex, 0);
|
||||||
|
if (Row_Luk) Row_Luk->SetValues(S.Luk, 0);
|
||||||
|
|
||||||
|
// Header (level/job).
|
||||||
|
if (Text_BaseLevel_Value) Text_BaseLevel_Value->SetText(FText::AsNumber(S.BaseLevel));
|
||||||
|
if (Text_JobLevel_Value) Text_JobLevel_Value->SetText(FText::AsNumber(S.JobLevel));
|
||||||
|
if (Text_Job)
|
||||||
|
{
|
||||||
|
FText JobName;
|
||||||
|
if (UGameInstance* GI = GetGameInstance())
|
||||||
|
{
|
||||||
|
if (const UZMMOJobsSubsystem* Jobs = GI->GetSubsystem<UZMMOJobsSubsystem>())
|
||||||
|
{
|
||||||
|
if (const UZMMOJobDataAsset* Data = Jobs->GetJobData(S.ClassId))
|
||||||
|
{
|
||||||
|
JobName = Data->DisplayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (JobName.IsEmpty())
|
||||||
|
{
|
||||||
|
JobName = FText::FromString(FString::Printf(TEXT("Job %d"), S.ClassId));
|
||||||
|
}
|
||||||
|
Text_Job->SetText(JobName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StatusPointText)
|
||||||
|
{
|
||||||
|
StatusPointText->SetText(FText::FromString(
|
||||||
|
FString::Printf(TEXT("Status Points: %d"), S.StatusPoint)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derivados (server-authoritative: base + equip).
|
||||||
|
if (Row_Atk) Row_Atk->SetValues(S.AtkBase, S.AtkEquipBonus);
|
||||||
|
if (Row_Matk) Row_Matk->SetRangeValues(S.MatkBaseMin, S.MatkBaseMax, S.MatkEquipBonus);
|
||||||
|
if (Row_Def) Row_Def->SetValues(S.DefBase, S.DefEquipBonus);
|
||||||
|
if (Row_Mdef) Row_Mdef->SetValues(S.MdefBase, S.MdefEquipBonus);
|
||||||
|
if (Row_Hit) Row_Hit->SetValues(S.HitBase, S.HitEquipBonus);
|
||||||
|
if (Row_Flee) Row_Flee->SetValues(S.FleeBase, S.FleeEquipBonus);
|
||||||
|
if (Row_Crit) Row_Crit->SetValuesX10(S.CritBaseX10, S.CritEquipBonusX10);
|
||||||
|
// ASPD não splita.
|
||||||
|
if (Row_Aspd) Row_Aspd->SetValues(S.Aspd, 0);
|
||||||
|
|
||||||
|
const bool bCanAlloc = !bPlusButtonsLocked && S.StatusPoint > 0;
|
||||||
|
SetAllPrimaryRowsPlusEnabled(bCanAlloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIPlayerStatus_Window::OnCloseClicked()
|
||||||
|
{
|
||||||
|
if (UGameInstance* GI = GetGameInstance())
|
||||||
|
{
|
||||||
|
if (UUIInGameFlowSubsystem* Flow = GI->GetSubsystem<UUIInGameFlowSubsystem>())
|
||||||
|
{
|
||||||
|
Flow->SetState(EZMMOInGameUIState::Playing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.h
Normal file
82
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.h
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "CommonActivatableWidget.h"
|
||||||
|
#include "ZMMOAttributeTypes.h"
|
||||||
|
#include "UIPlayerStatus_Window.generated.h"
|
||||||
|
|
||||||
|
class UButton;
|
||||||
|
class UTextBlock;
|
||||||
|
class UUIPlayerStatus_BonusRow;
|
||||||
|
class UUIPlayerStatus_StatRow;
|
||||||
|
class UZMMOAttributeComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Janela de Status do player — composta por instâncias dos sub-widgets
|
||||||
|
* UUIPlayerStatus_StatRow (6 primários) e UUIPlayerStatus_BonusRow (8 derivados).
|
||||||
|
*
|
||||||
|
* Consome o UZMMOAttributeComponent do PlayerState e a pipeline de
|
||||||
|
* C_STAT_ALLOC (RequestStatAlloc → server valida → OnStatAllocReply).
|
||||||
|
*
|
||||||
|
* Pareada com o WBP UI_PlayerStatus_Window. Os 14 rows são BindWidgetOptional
|
||||||
|
* pra suportar variantes (ex.: HUD compacto sem CRIT/ASPD).
|
||||||
|
*/
|
||||||
|
UCLASS(Abstract, Blueprintable, BlueprintType)
|
||||||
|
class ZMMO_API UUIPlayerStatus_Window : public UCommonActivatableWidget
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UUIPlayerStatus_Window(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void NativeOnActivated() override;
|
||||||
|
virtual void NativeOnDeactivated() override;
|
||||||
|
virtual TOptional<FUIInputConfig> GetDesiredInputConfig() const override;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr<UTextBlock> TitleText;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr<UTextBlock> StatusPointText;
|
||||||
|
|
||||||
|
// Header: identidade do char.
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Header") TObjectPtr<UTextBlock> Text_CharName;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Header") TObjectPtr<UTextBlock> Text_Job;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Header") TObjectPtr<UTextBlock> Text_BaseLevel_Value;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Header") TObjectPtr<UTextBlock> Text_JobLevel_Value;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr<UUIPlayerStatus_StatRow> Row_Str;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr<UUIPlayerStatus_StatRow> Row_Agi;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr<UUIPlayerStatus_StatRow> Row_Vit;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr<UUIPlayerStatus_StatRow> Row_Int;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr<UUIPlayerStatus_StatRow> Row_Dex;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr<UUIPlayerStatus_StatRow> Row_Luk;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr<UUIPlayerStatus_BonusRow> Row_Atk;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr<UUIPlayerStatus_BonusRow> Row_Matk;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr<UUIPlayerStatus_BonusRow> Row_Def;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr<UUIPlayerStatus_BonusRow> Row_Mdef;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr<UUIPlayerStatus_BonusRow> Row_Hit;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr<UUIPlayerStatus_BonusRow> Row_Flee;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr<UUIPlayerStatus_BonusRow> Row_Crit;
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Derived") TObjectPtr<UUIPlayerStatus_BonusRow> Row_Aspd;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr<UButton> CloseBtn;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void BindToLocalPlayer();
|
||||||
|
void UnbindFromComponent();
|
||||||
|
void RefreshFromSnapshot(const FZMMOAttributesSnapshot& Snap);
|
||||||
|
void SetAllPrimaryRowsPlusEnabled(bool bEnabled);
|
||||||
|
void BindRow(UUIPlayerStatus_StatRow* Row, int32 StatId, const TCHAR* IconLetter, const TCHAR* DisplayName);
|
||||||
|
|
||||||
|
UFUNCTION() void HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot);
|
||||||
|
UFUNCTION() void HandleStatAllocReply(bool bAccepted, int32 Reason);
|
||||||
|
UFUNCTION() void HandleRowPlusClicked(int32 StatId);
|
||||||
|
UFUNCTION() void OnCloseClicked();
|
||||||
|
|
||||||
|
UPROPERTY(Transient)
|
||||||
|
TWeakObjectPtr<UZMMOAttributeComponent> BoundComponent;
|
||||||
|
|
||||||
|
/** Trava local pra evitar spam-click: vira true ao clicar, false ao
|
||||||
|
* receber StatAllocReply (ou snapshot novo, o que vier primeiro). */
|
||||||
|
bool bPlusButtonsLocked = false;
|
||||||
|
};
|
||||||
@@ -37,7 +37,8 @@ namespace
|
|||||||
S.Money = P.Money;
|
S.Money = P.Money;
|
||||||
S.AtkBase = P.AtkBase;
|
S.AtkBase = P.AtkBase;
|
||||||
S.AtkEquipBonus = P.AtkEquipBonus;
|
S.AtkEquipBonus = P.AtkEquipBonus;
|
||||||
S.MatkBase = P.MatkBase;
|
S.MatkBaseMin = P.MatkBaseMin;
|
||||||
|
S.MatkBaseMax = P.MatkBaseMax;
|
||||||
S.MatkEquipBonus = P.MatkEquipBonus;
|
S.MatkEquipBonus = P.MatkEquipBonus;
|
||||||
S.DefBase = P.DefBase;
|
S.DefBase = P.DefBase;
|
||||||
S.DefEquipBonus = P.DefEquipBonus;
|
S.DefEquipBonus = P.DefEquipBonus;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Snapshot completo dos atributos do char vindo do server via
|
* Snapshot completo dos atributos do char vindo do server via
|
||||||
* S_ATTRIBUTE_SNAPSHOT_FULL (opcode 1200).
|
* S_ATTRIBUTE_SNAPSHOT_FULL (opcode 1500).
|
||||||
*
|
*
|
||||||
* Cliente NUNCA recalcula derivados — apenas exibe o que recebe. Os campos
|
* Cliente NUNCA recalcula derivados — apenas exibe o que recebe. Os campos
|
||||||
* HP/MaxHp/SP/MaxSp ja sao os "efetivos" (base + equip + buff somados no
|
* HP/MaxHp/SP/MaxSp ja sao os "efetivos" (base + equip + buff somados no
|
||||||
@@ -56,7 +56,10 @@ struct ZMMOATTRIBUTES_API FZMMOAttributesSnapshot
|
|||||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 AtkBase = 0;
|
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 AtkBase = 0;
|
||||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 AtkEquipBonus = 0;
|
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 AtkEquipBonus = 0;
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 MatkBase = 0;
|
/** MATK pre-renewal e' um range [min, max] (rathena status_base_matk_min/max).
|
||||||
|
* Display: "MATK min ~ max + equip". */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 MatkBaseMin = 0;
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 MatkBaseMax = 0;
|
||||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 MatkEquipBonus = 0;
|
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 MatkEquipBonus = 0;
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 DefBase = 0;
|
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 DefBase = 0;
|
||||||
|
|||||||
67
Source/ZMMOJobs/Private/ZMMOJobChangeNetworkHandler.cpp
Normal file
67
Source/ZMMOJobs/Private/ZMMOJobChangeNetworkHandler.cpp
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#include "ZMMOJobChangeNetworkHandler.h"
|
||||||
|
|
||||||
|
#include "Engine/GameInstance.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "ZeusNetworkSubsystem.h"
|
||||||
|
|
||||||
|
DEFINE_LOG_CATEGORY_STATIC(LogZMMOJobChange, Log, All);
|
||||||
|
|
||||||
|
bool UZMMOJobChangeNetworkHandler::ShouldCreateSubsystem(UObject* Outer) const
|
||||||
|
{
|
||||||
|
// Mesmo criterio do AttributeNetworkHandler — so' em mundos de gameplay
|
||||||
|
// (PIE / standalone in-game). Editor preview / front-end menu nao precisa.
|
||||||
|
const UWorld* World = Cast<UWorld>(Outer);
|
||||||
|
if (World == nullptr) { return false; }
|
||||||
|
return World->WorldType == EWorldType::Game
|
||||||
|
|| World->WorldType == EWorldType::PIE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOJobChangeNetworkHandler::Initialize(FSubsystemCollectionBase& Collection)
|
||||||
|
{
|
||||||
|
Super::Initialize(Collection);
|
||||||
|
|
||||||
|
if (UZeusNetworkSubsystem* Net = GetZeusNetSubsystem())
|
||||||
|
{
|
||||||
|
JobChangeResultHandle = Net->OnJobChangeResult.AddUObject(
|
||||||
|
this, &UZMMOJobChangeNetworkHandler::HandleJobChangeResult);
|
||||||
|
UE_LOG(LogZMMOJobChange, Log,
|
||||||
|
TEXT("[ZMMOJobChangeNetworkHandler] Bind OnJobChangeResult OK"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMOJobChange, Warning,
|
||||||
|
TEXT("[ZMMOJobChangeNetworkHandler] ZeusNetworkSubsystem indisponivel — bind pulado"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOJobChangeNetworkHandler::Deinitialize()
|
||||||
|
{
|
||||||
|
if (UZeusNetworkSubsystem* Net = GetZeusNetSubsystem())
|
||||||
|
{
|
||||||
|
if (JobChangeResultHandle.IsValid())
|
||||||
|
{
|
||||||
|
Net->OnJobChangeResult.Remove(JobChangeResultHandle);
|
||||||
|
JobChangeResultHandle.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OnJobChangeResult.Clear();
|
||||||
|
Super::Deinitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOJobChangeNetworkHandler::HandleJobChangeResult(
|
||||||
|
bool bAccepted, int32 Reason, int32 NewClassId)
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMOJobChange, Log,
|
||||||
|
TEXT("[ZMMOJobChangeNetworkHandler] S_JOB_CHANGE_RESULT accepted=%d reason=%d newClassId=%d"),
|
||||||
|
bAccepted ? 1 : 0, Reason, NewClassId);
|
||||||
|
OnJobChangeResult.Broadcast(bAccepted, Reason, NewClassId);
|
||||||
|
}
|
||||||
|
|
||||||
|
UZeusNetworkSubsystem* UZMMOJobChangeNetworkHandler::GetZeusNetSubsystem() const
|
||||||
|
{
|
||||||
|
const UWorld* World = GetWorld();
|
||||||
|
if (World == nullptr) { return nullptr; }
|
||||||
|
UGameInstance* GI = World->GetGameInstance();
|
||||||
|
if (GI == nullptr) { return nullptr; }
|
||||||
|
return GI->GetSubsystem<UZeusNetworkSubsystem>();
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
#include "ZMMOJobDataAsset.h"
|
#include "ZMMOJobDataAsset.h"
|
||||||
#include "ZMMOJobsSubsystem.h"
|
#include "ZMMOJobsSubsystem.h"
|
||||||
|
#include "ZeusNetworkSubsystem.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -52,3 +53,37 @@ bool UZMMOJobsLibrary::IsJobRegistered(const UObject* WorldContextObject, int32
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TArray<UZMMOJobDataAsset*> UZMMOJobsLibrary::GetJobsByTier(
|
||||||
|
const UObject* WorldContextObject, EZMMOJobTier Tier)
|
||||||
|
{
|
||||||
|
if (UZMMOJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
|
||||||
|
{
|
||||||
|
return Sub->GetJobsByTier(Tier);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<UZMMOJobDataAsset*> UZMMOJobsLibrary::GetEligibleNextJobs(
|
||||||
|
const UObject* WorldContextObject, int32 CurrentClassId)
|
||||||
|
{
|
||||||
|
if (UZMMOJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
|
||||||
|
{
|
||||||
|
return Sub->GetEligibleNextJobs(CurrentClassId);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOJobsLibrary::SendJobChangeRequest(
|
||||||
|
const UObject* WorldContextObject, int32 TargetClassId)
|
||||||
|
{
|
||||||
|
if (WorldContextObject == nullptr) { return; }
|
||||||
|
const UWorld* World = WorldContextObject->GetWorld();
|
||||||
|
if (World == nullptr) { return; }
|
||||||
|
UGameInstance* GI = World->GetGameInstance();
|
||||||
|
if (GI == nullptr) { return; }
|
||||||
|
if (UZeusNetworkSubsystem* Net = GI->GetSubsystem<UZeusNetworkSubsystem>())
|
||||||
|
{
|
||||||
|
Net->SendJobChangeRequest(TargetClassId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,3 +65,32 @@ UZMMOJobDataAsset* UZMMOJobsSubsystem::GetJobData(int32 ClassId) const
|
|||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TArray<UZMMOJobDataAsset*> UZMMOJobsSubsystem::GetJobsByTier(EZMMOJobTier Tier) const
|
||||||
|
{
|
||||||
|
TArray<UZMMOJobDataAsset*> Out;
|
||||||
|
Out.Reserve(CachedJobs.Num());
|
||||||
|
for (const TPair<int32, TObjectPtr<UZMMOJobDataAsset>>& Pair : CachedJobs)
|
||||||
|
{
|
||||||
|
if (Pair.Value && Pair.Value->Tier == Tier)
|
||||||
|
{
|
||||||
|
Out.Add(Pair.Value.Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Out;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<UZMMOJobDataAsset*> UZMMOJobsSubsystem::GetEligibleNextJobs(int32 CurrentClassId) const
|
||||||
|
{
|
||||||
|
TArray<UZMMOJobDataAsset*> Out;
|
||||||
|
Out.Reserve(4); // tipico: 2-6 destinos
|
||||||
|
for (const TPair<int32, TObjectPtr<UZMMOJobDataAsset>>& Pair : CachedJobs)
|
||||||
|
{
|
||||||
|
if (Pair.Value && Pair.Value->ParentClassId == CurrentClassId
|
||||||
|
&& Pair.Value->ClassId != CurrentClassId)
|
||||||
|
{
|
||||||
|
Out.Add(Pair.Value.Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Out;
|
||||||
|
}
|
||||||
|
|||||||
54
Source/ZMMOJobs/Public/ZMMOJobChangeNetworkHandler.h
Normal file
54
Source/ZMMOJobs/Public/ZMMOJobChangeNetworkHandler.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Subsystems/WorldSubsystem.h"
|
||||||
|
#include "ZMMOJobChangeNetworkHandler.generated.h"
|
||||||
|
|
||||||
|
class UZeusNetworkSubsystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multicast dinamico assignable em Blueprint. WBP de promocao binda no
|
||||||
|
* OnJobChangeResult pra reagir ao S_JOB_CHANGE_RESULT (1520) do server.
|
||||||
|
*
|
||||||
|
* Parametros:
|
||||||
|
* - bAccepted : true = promocao aplicada (S_ATTRIBUTE_SNAPSHOT_FULL
|
||||||
|
* chega logo apos com state novo).
|
||||||
|
* - Reason : EJobChangeRejectReason (None/NoSession/UnknownJob/
|
||||||
|
* NotParent/WrongTier/JobLevelTooLow/AlreadyAtTarget/
|
||||||
|
* JobsDbUnavailable). Significativo apenas se !bAccepted.
|
||||||
|
* - NewClassId : significativo apenas se bAccepted=true (redundante com
|
||||||
|
* snapshot que vem junto, mas util pra triggers/toasts).
|
||||||
|
*/
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FZMMOOnJobChangeResultBP,
|
||||||
|
bool, bAccepted, int32, Reason, int32, NewClassId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridge entre o `UZeusNetworkSubsystem` (do plugin ZeusNetwork) e a UI
|
||||||
|
* Blueprint (WBP de promocao). Mesmo pattern do `UZMMOAttributeNetworkHandler`
|
||||||
|
* — converte o delegate C++-only `FZeusOnJobChangeResult` num dynamic
|
||||||
|
* multicast (`FZMMOOnJobChangeResultBP`) assignavel em BP.
|
||||||
|
*
|
||||||
|
* Por que UWorldSubsystem? Player so' faz promocao quando ja' esta no
|
||||||
|
* WorldServer (com mundo carregado). No front-end / lobby, esse subsystem
|
||||||
|
* nao existe — economiza memoria.
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class ZMMOJOBS_API UZMMOJobChangeNetworkHandler : public UWorldSubsystem
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||||
|
virtual void Deinitialize() override;
|
||||||
|
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
|
||||||
|
|
||||||
|
/// Assignavel em BP: WBP_JobChangePanel.OnEvent_OnJobChangeResult.
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Zeus|Jobs")
|
||||||
|
FZMMOOnJobChangeResultBP OnJobChangeResult;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void HandleJobChangeResult(bool bAccepted, int32 Reason, int32 NewClassId);
|
||||||
|
UZeusNetworkSubsystem* GetZeusNetSubsystem() const;
|
||||||
|
|
||||||
|
FDelegateHandle JobChangeResultHandle;
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "Engine/DataAsset.h"
|
#include "Engine/DataAsset.h"
|
||||||
|
#include "ZMMOJobTypes.h"
|
||||||
#include "ZMMOJobDataAsset.generated.h"
|
#include "ZMMOJobDataAsset.generated.h"
|
||||||
|
|
||||||
class UTexture2D;
|
class UTexture2D;
|
||||||
@@ -39,11 +40,28 @@ public:
|
|||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
|
||||||
FString TechnicalName;
|
FString TechnicalName;
|
||||||
|
|
||||||
/// Nome localizado exibido na UI (pt-BR no projeto base). Ex: "Aprendiz".
|
/// Nome localizado exibido na UI (pt-BR no projeto base). Ex: "Novato".
|
||||||
/// Pode usar FText Localization tables pra multi-idioma futuro.
|
/// Pode usar FText Localization tables pra multi-idioma futuro.
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
|
||||||
FText DisplayName;
|
FText DisplayName;
|
||||||
|
|
||||||
|
/// Tier hierarquico — espelha `JobDef::tier` do server. UI usa pra agrupar
|
||||||
|
/// jobs (ex: lista de Vocacoes possiveis quando promovendo Novato).
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
|
||||||
|
EZMMOJobTier Tier = EZMMOJobTier::Beginner;
|
||||||
|
|
||||||
|
/// ClassId do parent na arvore de promocao. 0 = sem parent (Novato eh root).
|
||||||
|
/// Espelha `JobDef::parentJobId` do server. UI usa pra filtrar destinos
|
||||||
|
/// possiveis ao mostrar painel de promocao.
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
|
||||||
|
int32 ParentClassId = 0;
|
||||||
|
|
||||||
|
/// Lista de armas que o job pode equipar. Espelha `JobDef::allowedWeaponsMask`
|
||||||
|
/// do server (mas como TArray pra editor visual em vez de bitmask cru).
|
||||||
|
/// Inventory UI usa pra hint "nao pode equipar" + filtro de catalogo.
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
|
||||||
|
TArray<EZMMOWeaponType> AllowedWeapons;
|
||||||
|
|
||||||
/// Icone do job — usado em StatusWindow, CharacterCard, JobChangeUI.
|
/// Icone do job — usado em StatusWindow, CharacterCard, JobChangeUI.
|
||||||
/// Soft ptr: nao carrega ate alguem chamar LoadSynchronous() ou async load.
|
/// Soft ptr: nao carrega ate alguem chamar LoadSynchronous() ou async load.
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job|Assets")
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job|Assets")
|
||||||
|
|||||||
57
Source/ZMMOJobs/Public/ZMMOJobTypes.h
Normal file
57
Source/ZMMOJobs/Public/ZMMOJobTypes.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "ZMMOJobTypes.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tier hierarquico de um job. Espelha `EJobTier` do server em
|
||||||
|
* `Server/.../JobsSystem/Public/JobTypes.hpp`. Valores TEM que bater
|
||||||
|
* byte-a-byte com a enum do server porque o snapshot pode carregar
|
||||||
|
* tier como uint8 no futuro.
|
||||||
|
*
|
||||||
|
* Renascer (RO pre-renewal): char no Tier 2 (Specialization) que cumpre
|
||||||
|
* condicao volta pra Tier 3 (RebornBeginner) com bonus permanente,
|
||||||
|
* abrindo path paralelo RebornVocation → Mastery (coroa final).
|
||||||
|
*/
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EZMMOJobTier : uint8
|
||||||
|
{
|
||||||
|
Beginner = 0 UMETA(DisplayName = "Iniciante"),
|
||||||
|
Vocation = 1 UMETA(DisplayName = "Vocacao"),
|
||||||
|
Specialization = 2 UMETA(DisplayName = "Especializacao"),
|
||||||
|
RebornBeginner = 3 UMETA(DisplayName = "Iniciante Renascido"),
|
||||||
|
RebornVocation = 4 UMETA(DisplayName = "Vocacao Renascida"),
|
||||||
|
Mastery = 5 UMETA(DisplayName = "Mestria"),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tipos de arma. Espelha `EWeaponType` do server. Usado em `AllowedWeapons`
|
||||||
|
* do DA pra UI (filtrar items equipaveis) + validacao server-side futura
|
||||||
|
* quando InventorySystem entrar.
|
||||||
|
*
|
||||||
|
* Ordem TEM que bater com server porque o bitmask `allowedWeaponsMask` no
|
||||||
|
* JobDef usa bit N = EWeaponType(N).
|
||||||
|
*/
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EZMMOWeaponType : uint8
|
||||||
|
{
|
||||||
|
Fist = 0 UMETA(DisplayName = "Punho"),
|
||||||
|
Dagger = 1 UMETA(DisplayName = "Adaga"),
|
||||||
|
Sword1H = 2 UMETA(DisplayName = "Espada 1M"),
|
||||||
|
Sword2H = 3 UMETA(DisplayName = "Espada 2M"),
|
||||||
|
Spear1H = 4 UMETA(DisplayName = "Lanca 1M"),
|
||||||
|
Spear2H = 5 UMETA(DisplayName = "Lanca 2M"),
|
||||||
|
Axe1H = 6 UMETA(DisplayName = "Machado 1M"),
|
||||||
|
Axe2H = 7 UMETA(DisplayName = "Machado 2M"),
|
||||||
|
Mace1H = 8 UMETA(DisplayName = "Maca 1M"),
|
||||||
|
Mace2H = 9 UMETA(DisplayName = "Maca 2M"),
|
||||||
|
Staff1H = 10 UMETA(DisplayName = "Cajado 1M"),
|
||||||
|
Staff2H = 11 UMETA(DisplayName = "Cajado 2M"),
|
||||||
|
Bow = 12 UMETA(DisplayName = "Arco"),
|
||||||
|
Knuckle = 13 UMETA(DisplayName = "Soqueira"),
|
||||||
|
Musical = 14 UMETA(DisplayName = "Instrumento Musical"),
|
||||||
|
Whip = 15 UMETA(DisplayName = "Chicote"),
|
||||||
|
Book = 16 UMETA(DisplayName = "Livro"),
|
||||||
|
Katar = 17 UMETA(DisplayName = "Katar"),
|
||||||
|
Gun = 18 UMETA(DisplayName = "Arma de Fogo"),
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||||
|
#include "ZMMOJobTypes.h"
|
||||||
#include "ZMMOJobsLibrary.generated.h"
|
#include "ZMMOJobsLibrary.generated.h"
|
||||||
|
|
||||||
class UZMMOJobDataAsset;
|
class UZMMOJobDataAsset;
|
||||||
@@ -36,4 +37,26 @@ public:
|
|||||||
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs",
|
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs",
|
||||||
meta = (WorldContext = "WorldContextObject"))
|
meta = (WorldContext = "WorldContextObject"))
|
||||||
static bool IsJobRegistered(const UObject* WorldContextObject, int32 ClassId);
|
static bool IsJobRegistered(const UObject* WorldContextObject, int32 ClassId);
|
||||||
|
|
||||||
|
/// Lista jobs num tier (UI dropdown). Vazio se subsystem indisponivel.
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs",
|
||||||
|
meta = (WorldContext = "WorldContextObject"))
|
||||||
|
static TArray<UZMMOJobDataAsset*> GetJobsByTier(
|
||||||
|
const UObject* WorldContextObject, EZMMOJobTier Tier);
|
||||||
|
|
||||||
|
/// Destinos de promocao a partir do char atual. WBP de promocao consome
|
||||||
|
/// direto. Vazio se nao houver filhos (ex: char ja' esta em Tier 3
|
||||||
|
/// Mastery — Jobs.4 introduzira terminal de progressao).
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs",
|
||||||
|
meta = (WorldContext = "WorldContextObject"))
|
||||||
|
static TArray<UZMMOJobDataAsset*> GetEligibleNextJobs(
|
||||||
|
const UObject* WorldContextObject, int32 CurrentClassId);
|
||||||
|
|
||||||
|
/// Helper de send via ZeusNetworkSubsystem. Conveniencia pra WBP nao
|
||||||
|
/// precisar pegar o subsystem manualmente. Idempotente: rejeitado
|
||||||
|
/// silenciosamente se nao conectado.
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Zeus|Jobs",
|
||||||
|
meta = (WorldContext = "WorldContextObject"))
|
||||||
|
static void SendJobChangeRequest(
|
||||||
|
const UObject* WorldContextObject, int32 TargetClassId);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "Subsystems/GameInstanceSubsystem.h"
|
#include "Subsystems/GameInstanceSubsystem.h"
|
||||||
|
#include "ZMMOJobTypes.h"
|
||||||
#include "ZMMOJobsSubsystem.generated.h"
|
#include "ZMMOJobsSubsystem.generated.h"
|
||||||
|
|
||||||
class UZMMOJobDataAsset;
|
class UZMMOJobDataAsset;
|
||||||
@@ -41,6 +42,22 @@ public:
|
|||||||
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs")
|
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs")
|
||||||
int32 GetJobCount() const { return CachedJobs.Num(); }
|
int32 GetJobCount() const { return CachedJobs.Num(); }
|
||||||
|
|
||||||
|
/// Lista jobs num tier especifico (UI agrupa por tier — ex: dropdown
|
||||||
|
/// "Vocacoes possiveis"). Ordem do retorno e' a do AssetRegistry scan
|
||||||
|
/// — UI pode reordenar por ClassId/DisplayName se precisar.
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs")
|
||||||
|
TArray<UZMMOJobDataAsset*> GetJobsByTier(EZMMOJobTier Tier) const;
|
||||||
|
|
||||||
|
/// Destinos de promocao validos a partir do `CurrentClassId`. Filtra
|
||||||
|
/// pelos jobs que tem `ParentClassId == CurrentClassId`. WBP de
|
||||||
|
/// promocao consome direto pra montar botoes "Tornar-se X".
|
||||||
|
///
|
||||||
|
/// NOTA: condicao de elegibilidade real (jobLevel >= cap, quest, item)
|
||||||
|
/// e' do server — UI apenas LISTA opcoes possiveis pelo schema. Server
|
||||||
|
/// reponde rejeicao via S_JOB_CHANGE_RESULT (Jobs::EJobChangeRejectReason).
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs")
|
||||||
|
TArray<UZMMOJobDataAsset*> GetEligibleNextJobs(int32 CurrentClassId) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Scan AssetRegistry + popula CachedJobs. Sincronizo (V1) — chamado UMA
|
/// Scan AssetRegistry + popula CachedJobs. Sincronizo (V1) — chamado UMA
|
||||||
/// vez no Initialize. Pra 50 jobs eh aceitavel; se crescer pra 200+ pode
|
/// vez no Initialize. Pra 50 jobs eh aceitavel; se crescer pra 200+ pode
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ public class ZMMOJobs : ModuleRules
|
|||||||
});
|
});
|
||||||
|
|
||||||
PrivateDependencyModuleNames.AddRange(new string[] {
|
PrivateDependencyModuleNames.AddRange(new string[] {
|
||||||
"AssetRegistry" // FAssetRegistryModule pra scan de DA_Job_*
|
"AssetRegistry", // FAssetRegistryModule pra scan de DA_Job_*
|
||||||
|
"ZeusNetwork" // UZeusNetworkSubsystem (SendJobChangeRequest)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Espelho do `Server/.../JobsSystem/module.json` (modulo nucleo MMO).
|
// Espelho do `Server/.../JobsSystem/module.json` (modulo nucleo MMO).
|
||||||
|
|||||||
Reference in New Issue
Block a user