Compare commits
24 Commits
feat/attri
...
e3aab64c8c
| Author | SHA1 | Date | |
|---|---|---|---|
| e3aab64c8c | |||
| b2c1153229 | |||
| 4e14f1feb6 | |||
| ede49c1777 | |||
| 484c8d5d4f | |||
| 65edf1081b | |||
| e1fca1ae8d | |||
| c9492a3910 | |||
| 88eb20eefa | |||
| 8843ac3d1c | |||
| 5d44647170 | |||
| 1794e5d418 | |||
| 7fa10b4cb9 | |||
| 079d05c117 | |||
| 90484876f0 | |||
| 87d9ca4dae | |||
| 037aed30ce | |||
| 9390ed9da9 | |||
| 28bfa9a567 | |||
| 6391372f00 | |||
| f832c1a4df | |||
| c3df705a7a | |||
| a354d9eefc | |||
| ca304a99dd |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -29,3 +29,6 @@ desktop.ini
|
||||
*.log
|
||||
*.pdb
|
||||
.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):
|
||||
> - [`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
|
||||
> de UI** (`UI/Themes/`), com tutorial §4.7, regra de "no hard ref a textura
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
## 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:
|
||||
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 |
|
||||
|---|---|---|
|
||||
| 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) |
|
||||
| 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 |
|
||||
| Casting de skill | WorldServer | NÃO persiste (efêmero) |
|
||||
| Resultado de skill (HP perdido, item dropado) | WorldServer | WorldServer → writeback |
|
||||
@@ -260,7 +260,7 @@ version: 1
|
||||
jobs:
|
||||
- id: 0
|
||||
name: Novice # nome técnico (inglês, estável)
|
||||
display_name_ptbr: Aprendiz
|
||||
display_name_ptbr: Novato
|
||||
parent_job: null
|
||||
max_base_level: 99
|
||||
max_job_level: 10
|
||||
@@ -322,7 +322,7 @@ jobs:
|
||||
|
||||
## 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 }
|
||||
@@ -342,7 +342,7 @@ CharServer:
|
||||
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)
|
||||
|
||||
@@ -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).
|
||||
|
||||
### 5.4 Job change (Aprendiz → Espadachim, etc.)
|
||||
### 5.4 Job change (Novato → Espadachim, etc.)
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
### 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`).
|
||||
- `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.
|
||||
- `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
|
||||
|
||||
@@ -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.**
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -311,11 +311,11 @@ ALTER TABLE characters
|
||||
**Notas:**
|
||||
- `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).
|
||||
- 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).
|
||||
- `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+)
|
||||
|
||||
|
||||
@@ -86,6 +86,15 @@ GameViewportClientClassName=/Script/CommonUI.CommonGameViewportClient
|
||||
+ActiveClassRedirects=(OldClassName="TP_ThirdPersonCharacter",NewClassName="ZMMOPlayerCharacter")
|
||||
+ActiveClassRedirects=(OldClassName="ZMMOCharacter",NewClassName="ZMMOPlayerCharacter")
|
||||
|
||||
[CoreRedirects]
|
||||
; Migração v1→v2 do FUIStylePanel: TMap<EUIPanelTexture,...> (Enum) → TMap<FName,...>.
|
||||
; Os campos *_DEPRECATED preservam a leitura do asset antigo; PostSerialize copia
|
||||
; pros novos ByName e após re-save os legacy ficam vazios. Os redirects podem
|
||||
; ser removidos quando todos os DTs do projeto tiverem sido re-salvos.
|
||||
+PropertyRedirects=(OldName="/Script/ZMMO.UIStylePanel.PanelBackground",NewName="/Script/ZMMO.UIStylePanel.PanelBackground_DEPRECATED")
|
||||
+PropertyRedirects=(OldName="/Script/ZMMO.UIStylePanel.PanelOutline",NewName="/Script/ZMMO.UIStylePanel.PanelOutline_DEPRECATED")
|
||||
+PropertyRedirects=(OldName="/Script/ZMMO.UIStylePanel.PanelOutlineEffect",NewName="/Script/ZMMO.UIStylePanel.PanelOutlineEffect_DEPRECATED")
|
||||
|
||||
[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings]
|
||||
bEnablePlugin=True
|
||||
bAllowNetworkConnection=True
|
||||
|
||||
@@ -35,6 +35,12 @@ ScreenSetAsset=/Game/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.DA_FrontEndScreenSet
|
||||
; cliente resolve `mapId -> ClientLevel/spawns` via FZMMOMapDef rows.
|
||||
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:
|
||||
; - ScreenSetAsset mapeia EZMMOInGameUIState -> WBP (Playing -> WBP_HUD,
|
||||
|
||||
Binary file not shown.
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/DT_UI_VitalsReadout.uasset
Normal file
BIN
Content/ZMMO/Data/UI/DT_UI_VitalsReadout.uasset
Normal file
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/UI/Fonts/JetBrains_Mono/JetBrains_Mono.uasset
Normal file
BIN
Content/ZMMO/UI/Fonts/JetBrains_Mono/JetBrains_Mono.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/UI/Fonts/Manrope/Manrope.uasset
Normal file
BIN
Content/ZMMO/UI/Fonts/Manrope/Manrope.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/Fonts/Manrope/static/Manrope-Bold.uasset
Normal file
BIN
Content/ZMMO/UI/Fonts/Manrope/static/Manrope-Bold.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/Fonts/Manrope/static/Manrope-ExtraBold.uasset
Normal file
BIN
Content/ZMMO/UI/Fonts/Manrope/static/Manrope-ExtraBold.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/Fonts/Manrope/static/Manrope-ExtraLight.uasset
Normal file
BIN
Content/ZMMO/UI/Fonts/Manrope/static/Manrope-ExtraLight.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/Fonts/Manrope/static/Manrope-Light.uasset
Normal file
BIN
Content/ZMMO/UI/Fonts/Manrope/static/Manrope-Light.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/Fonts/Manrope/static/Manrope-Medium.uasset
Normal file
BIN
Content/ZMMO/UI/Fonts/Manrope/static/Manrope-Medium.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/Fonts/Manrope/static/Manrope-Regular.uasset
Normal file
BIN
Content/ZMMO/UI/Fonts/Manrope/static/Manrope-Regular.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/Fonts/Manrope/static/Manrope-SemiBold.uasset
Normal file
BIN
Content/ZMMO/UI/Fonts/Manrope/static/Manrope-SemiBold.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/UI/HUD/WBP_PlayerVitalsPanel.uasset
Normal file
BIN
Content/ZMMO/UI/HUD/WBP_PlayerVitalsPanel.uasset
Normal file
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.
BIN
Content/ZMMO/UI/Shared/UI_ProgressBar_Master.uasset
Normal file
BIN
Content/ZMMO/UI/Shared/UI_ProgressBar_Master.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -27,6 +27,7 @@ enum class EZMMOInGameUIState : uint8
|
||||
None, ///< Antes do player local spawnar; UI in-game inativa.
|
||||
Playing, ///< HUD principal visivel (HP/SP/level/etc).
|
||||
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.
|
||||
SkillTree, ///< (futuro) Arvore de skills.
|
||||
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;
|
||||
};
|
||||
48
Source/ZMMO/Data/UI/UIStyleTokens.cpp
Normal file
48
Source/ZMMO/Data/UI/UIStyleTokens.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "UIStyleTokens.h"
|
||||
|
||||
#include "UObject/Class.h"
|
||||
#include "UObject/UnrealType.h"
|
||||
|
||||
void FUIStylePanel::PostSerialize(const FArchive& Ar)
|
||||
{
|
||||
// Migração v1→v2: TMap<EUIPanelTexture, ...> (legacy) → TMap<FName, ...> (atual).
|
||||
// Roda no PostLoad de cada FUIStyleRow; depois do primeiro re-save no editor
|
||||
// os campos *_DEPRECATED ficam vazios e este código vira no-op.
|
||||
if (!Ar.IsLoading())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const UEnum* EnumPtr = StaticEnum<EUIPanelTexture>();
|
||||
if (EnumPtr == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto MigrateOne = [EnumPtr](
|
||||
TMap<EUIPanelTexture, FSlateBrush>& Legacy,
|
||||
TMap<FName, FSlateBrush>& ByName)
|
||||
{
|
||||
if (Legacy.IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (const TPair<EUIPanelTexture, FSlateBrush>& Pair : Legacy)
|
||||
{
|
||||
if (Pair.Key == EUIPanelTexture::None)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const FName KeyName(*EnumPtr->GetAuthoredNameStringByValue(static_cast<int64>(Pair.Key)));
|
||||
if (!ByName.Contains(KeyName))
|
||||
{
|
||||
ByName.Add(KeyName, Pair.Value);
|
||||
}
|
||||
}
|
||||
Legacy.Reset();
|
||||
};
|
||||
|
||||
MigrateOne(PanelBackground_DEPRECATED, PanelBackgroundByName);
|
||||
MigrateOne(PanelOutline_DEPRECATED, PanelOutlineByName);
|
||||
MigrateOne(PanelOutlineEffect_DEPRECATED, PanelOutlineEffectByName);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "Fonts/SlateFontInfo.h"
|
||||
#include "Styling/SlateBrush.h"
|
||||
#include "Styling/SlateTypes.h"
|
||||
#include "Layout/Margin.h"
|
||||
#include "Templates/SubclassOf.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
|
||||
// chave (nome da variante: "Primary", "Secondary", "Danger", "Ghost", ou
|
||||
// customizada). UUIButton_Base.Variant é FName e busca em cada sub-mapa.
|
||||
// O struct FUIStyleButtonVariant (composto, FLAT) permanece com a forma
|
||||
// antiga — assim o hook BP_ApplyUIStyle dos WBP_Master continua funcional.
|
||||
// FUIStyleButton — padrão Hyper "Buttons": 7 sub-mapas independentes
|
||||
// indexados pela MESMA chave FName de variante (autor define livremente:
|
||||
// "Default", "Square", "Topbar", "Home_Main_Menu", ...). UUIButton_Base.Variant
|
||||
// é FName e busca o pedaço necessário em cada sub-mapa. ButtonStyle traz
|
||||
// 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)
|
||||
struct ZMMO_API FUIStyleButton
|
||||
{
|
||||
@@ -313,163 +210,138 @@ struct ZMMO_API FUIStyleButton
|
||||
|
||||
FUIStyleButton()
|
||||
{
|
||||
// ---- Backgrounds (Primary/Secondary/Danger/Ghost) ----
|
||||
FUIStyleButtonBackground PriBg;
|
||||
PriBg.BgNormal = FLinearColor(FColor(201, 167, 90)); // gold
|
||||
PriBg.BgHover = FLinearColor(FColor(231, 200, 115)); // gold-hi
|
||||
PriBg.BgPressed = FLinearColor(FColor(201, 167, 90));
|
||||
PriBg.BgDisabled = FLinearColor(FColor(201, 167, 90, 107));
|
||||
Backgrounds.Add(TEXT("Primary"), PriBg);
|
||||
// Defaults "no draw" pra não pintar branco quando o brush estiver vazio
|
||||
// (regra Hyper — ver feedback_slatebrush_empty_white). Autor preenche
|
||||
// no DT_UI_Styles.
|
||||
FButtonStyle DefaultStyle;
|
||||
DefaultStyle.Normal.DrawAs = ESlateBrushDrawType::NoDrawType;
|
||||
DefaultStyle.Hovered.DrawAs = ESlateBrushDrawType::NoDrawType;
|
||||
DefaultStyle.Pressed.DrawAs = ESlateBrushDrawType::NoDrawType;
|
||||
DefaultStyle.Disabled.DrawAs = ESlateBrushDrawType::NoDrawType;
|
||||
ButtonStyle.Add(TEXT("Default"), DefaultStyle);
|
||||
|
||||
FUIStyleButtonBackground SecBg;
|
||||
SecBg.BgNormal = FLinearColor(FColor(17, 24, 42)); // panel-2
|
||||
SecBg.BgHover = FLinearColor(FColor(22, 32, 58));
|
||||
SecBg.BgPressed = FLinearColor(FColor(17, 24, 42));
|
||||
Backgrounds.Add(TEXT("Secondary"), SecBg);
|
||||
FSlateBrush DefaultStroke;
|
||||
DefaultStroke.DrawAs = ESlateBrushDrawType::NoDrawType;
|
||||
ButtonStroke.Add(TEXT("Default"), DefaultStroke);
|
||||
|
||||
FUIStyleButtonBackground DngBg;
|
||||
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);
|
||||
ButtonFont.Add(TEXT("Default"), FSlateFontInfo());
|
||||
|
||||
FUIStyleButtonBackground GhoBg;
|
||||
GhoBg.BgNormal = FLinearColor::Transparent;
|
||||
GhoBg.BgHover = FLinearColor(FColor(22, 32, 58, 128));
|
||||
GhoBg.BgPressed = FLinearColor::Transparent;
|
||||
Backgrounds.Add(TEXT("Ghost"), GhoBg);
|
||||
ButtonTextColorRegular.Add(TEXT("Default"), FLinearColor::White);
|
||||
ButtonTextColorHovered.Add(TEXT("Default"), FLinearColor::White);
|
||||
ButtonTextColorSelected.Add(TEXT("Default"), FLinearColor::White);
|
||||
|
||||
// ---- Strokes ----
|
||||
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);
|
||||
ButtonIconColor.Add(TEXT("Default"), FLinearColor::White);
|
||||
}
|
||||
|
||||
/** 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")
|
||||
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")
|
||||
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")
|
||||
TMap<FName, FUIStyleButtonFont> Fonts;
|
||||
|
||||
// Globais comuns às variantes:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
|
||||
FMargin Padding = FMargin(22.f, 12.f);
|
||||
TMap<FName, FSlateFontInfo> ButtonFont;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
|
||||
float MinHeight = 48.f;
|
||||
TMap<FName, FLinearColor> ButtonTextColorRegular;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Button")
|
||||
float HoverScale = 1.02f;
|
||||
TMap<FName, FLinearColor> ButtonTextColorHovered;
|
||||
|
||||
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")
|
||||
TMap<EUIButtonShape, FVector2D> ShapeSizes;
|
||||
TMap<FName, FLinearColor> ButtonIconColor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Espelha 1:1 o Struct_UI_Style_Panels do Hyper: só os dois mapas de brush
|
||||
* (fundo + contorno) indexados por EUIPanelTexture. É o que o UI_Panel_Master
|
||||
* do Hyper expõe editável na instância (Details). EditAnywhere/ReadWrite para
|
||||
* o designer ajustar brush por widget; o tema (DT) só faz seed/fallback.
|
||||
* Override manual por instância (modo bUseTheme = false). 3 brushes diretos
|
||||
* pros 3 Borders do painel — designer edita o brush bruto no Details.
|
||||
* Default = NoDrawType pra não pintar branco quando vazio (regra Hyper).
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct ZMMO_API FUIPanelBrushSet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel|Texture")
|
||||
TMap<EUIPanelTexture, FSlateBrush> PanelBackground;
|
||||
FUIPanelBrushSet()
|
||||
{
|
||||
PanelBackground.DrawAs = ESlateBrushDrawType::NoDrawType;
|
||||
PanelOutline.DrawAs = ESlateBrushDrawType::NoDrawType;
|
||||
PanelOutlineEffect.DrawAs = ESlateBrushDrawType::NoDrawType;
|
||||
}
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel|Texture")
|
||||
TMap<EUIPanelTexture, FSlateBrush> PanelOutline;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel|Brush")
|
||||
FSlateBrush PanelBackground;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel|Brush")
|
||||
FSlateBrush PanelOutline;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel|Brush")
|
||||
FSlateBrush PanelOutlineEffect;
|
||||
};
|
||||
|
||||
/**
|
||||
* Espelha 1:1 o Struct_UI_Style_Panels do Hyper. 3 brushes por categoria
|
||||
* (FName livre): fundo, contorno e efeito sobre o contorno. Categorias são
|
||||
* editadas direto no DT_UI_Styles — UUIPanel_Base resolve via dropdown
|
||||
* dinâmico (meta=GetOptions).
|
||||
*
|
||||
* Migração: assets v1 tinham TMap<EUIPanelTexture, ...>; os campos
|
||||
* *_DEPRECATED preservam essas keys via PropertyRedirect no DefaultEngine.ini.
|
||||
* PostSerialize copia legacy→ByName no PostLoad — após re-save no editor,
|
||||
* o legacy fica vazio e o asset só carrega o ByName.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct ZMMO_API FUIStylePanel
|
||||
{
|
||||
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")
|
||||
TMap<EUIPanelTexture, FSlateBrush> PanelBackground;
|
||||
// ---- Novo (FName livre) -------------------------------------------
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel|Texture")
|
||||
TMap<EUIPanelTexture, FSlateBrush> PanelOutline;
|
||||
TMap<FName, FSlateBrush> PanelBackgroundByName;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel|Texture")
|
||||
TMap<FName, FSlateBrush> PanelOutlineByName;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Panel|Texture")
|
||||
TMap<FName, FSlateBrush> PanelOutlineEffectByName;
|
||||
|
||||
// ---- Legacy (carrega assets antigos via CoreRedirect) -------------
|
||||
//
|
||||
// Não aparecem no Details (DeprecatedProperty); persistem só pra leitura
|
||||
// do asset pré-migração. PostSerialize copia o conteúdo pros campos
|
||||
// ByName e zera os legacy.
|
||||
|
||||
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Use PanelBackgroundByName (FName)."))
|
||||
TMap<EUIPanelTexture, FSlateBrush> PanelBackground_DEPRECATED;
|
||||
|
||||
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Use PanelOutlineByName (FName)."))
|
||||
TMap<EUIPanelTexture, FSlateBrush> PanelOutline_DEPRECATED;
|
||||
|
||||
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Use PanelOutlineEffectByName (FName)."))
|
||||
TMap<EUIPanelTexture, FSlateBrush> PanelOutlineEffect_DEPRECATED;
|
||||
|
||||
/** Chamado automaticamente após Serialize. Migra dados legacy→ByName. */
|
||||
void PostSerialize(const FArchive& Ar);
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TStructOpsTypeTraits<FUIStylePanel> : public TStructOpsTypeTraitsBase2<FUIStylePanel>
|
||||
{
|
||||
enum
|
||||
{
|
||||
WithPostSerialize = true,
|
||||
};
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
@@ -502,29 +374,78 @@ struct ZMMO_API FUIStyleBorders
|
||||
float RuleSoftThickness = 1.f;
|
||||
};
|
||||
|
||||
/**
|
||||
* Variante completa de ProgressBar: materiais por layer (Fill/BG/Track/Edge/Shimmer)
|
||||
* + parametros visuais (DrawStyle, CornerRadius, Border) + toggles de efeito.
|
||||
*
|
||||
* Material nulo = nao sobrescreve o brush do WBP (usa o que esta atribuido la).
|
||||
* Aplicada pelo UUIProgressBar_Base quando bUseTheme=true via Variant FName.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct ZMMO_API FUIStyleProgressBarVariant
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// ---- Materiais (opcionais; nullptr = mantem brush do WBP) ----
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Materials")
|
||||
TObjectPtr<UMaterialInterface> FillMaterial;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Materials")
|
||||
TObjectPtr<UMaterialInterface> BackgroundMaterial;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Materials")
|
||||
TObjectPtr<UMaterialInterface> TrackGridMaterial;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Materials")
|
||||
TObjectPtr<UMaterialInterface> EdgeMaterial;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Materials")
|
||||
TObjectPtr<UMaterialInterface> ShimmerMaterial;
|
||||
|
||||
// ---- Style (forma + bordas) ----
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Style")
|
||||
EUIProgressBarStyle DrawStyle = EUIProgressBarStyle::Rounded;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Style",
|
||||
meta = (ClampMin = "0", ClampMax = "100"))
|
||||
float CornerRadius = 8.f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Style")
|
||||
bool bShowBorder = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Style")
|
||||
FLinearColor BorderColour = FLinearColor(0.f, 0.f, 0.f, 0.8f);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Style",
|
||||
meta = (ClampMin = "0", ClampMax = "10"))
|
||||
float BorderWidth = 1.f;
|
||||
|
||||
// ---- Toggles de efeito ----
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Effects")
|
||||
bool bShowTrackGrid = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Effects")
|
||||
bool bShowEdge = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar|Effects")
|
||||
bool bShowShimmer = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* ProgressBar — padrao multi-map Hyper-style. Variantes (HP/MP/XP/Cast/...) sao
|
||||
* editadas direto no DT_UI_Styles; cada UUIProgressBar_Base referencia uma key
|
||||
* FName (dropdown via meta=GetOptions resolve esse mapa em runtime+editor).
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct ZMMO_API FUIStyleProgressBar
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar")
|
||||
FLinearColor TrackColor = FLinearColor(FColor(14, 20, 36)); // Panel3
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar")
|
||||
FLinearColor BorderColor = FLinearColor(FColor(58, 75, 120, 140));
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar")
|
||||
float Height = 10.f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar")
|
||||
float CornerRadius = 6.f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar")
|
||||
float BorderWidth = 1.f;
|
||||
|
||||
/** Cor de preenchimento por tipo (Health/Mana/XP...). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|ProgressBar")
|
||||
TMap<EUIProgressBarType, FLinearColor> FillByType;
|
||||
TMap<FName, FUIStyleProgressBarVariant> Variants;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
|
||||
@@ -66,6 +66,7 @@ enum class EUIPanelTexture : uint8
|
||||
Rectangle_Vertical_03 UMETA(DisplayName = "Rectangle Vertical 03"),
|
||||
Rectangle_Vertical_04 UMETA(DisplayName = "Rectangle Vertical 04"),
|
||||
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")
|
||||
};
|
||||
|
||||
@@ -80,6 +81,16 @@ enum class EUIProgressBarType : uint8
|
||||
Generic UMETA(DisplayName = "Generic")
|
||||
};
|
||||
|
||||
/** Preset que controla Brush.DrawAs em todos os UImages da ProgressBar. */
|
||||
UENUM(BlueprintType)
|
||||
enum class EUIProgressBarStyle : uint8
|
||||
{
|
||||
/** DrawAs=Image. Sem mascara nos cantos. */
|
||||
Box UMETA(DisplayName = "Box"),
|
||||
/** DrawAs=RoundedBox. Cantos arredondados via Slate nativo (pixel-perfect). */
|
||||
Rounded UMETA(DisplayName = "Rounded"),
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EUIHeaderType : uint8
|
||||
{
|
||||
|
||||
@@ -104,6 +104,14 @@ void AZMMOPlayerController::SetupInputComponent()
|
||||
this, &AZMMOPlayerController::ToggleStatusWindow);
|
||||
Binding.bConsumeInput = true;
|
||||
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
|
||||
{
|
||||
return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls;
|
||||
|
||||
@@ -50,4 +50,7 @@ protected:
|
||||
|
||||
/** Hotkey Alt+A → toggle StatusWindow via UUIInGameFlowSubsystem. */
|
||||
void ToggleStatusWindow();
|
||||
|
||||
/** Hotkey J → toggle JobChangePanel (Jobs.1 — promocao de classe). */
|
||||
void ToggleJobChangePanel();
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "ZMMO.h"
|
||||
#include "ZMMOAttributeComponent.h"
|
||||
#include "ZMMOPlayerState.h"
|
||||
#include "ZMMOWorldSubsystem.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
#include "UI/FrontEnd/UIFrontEndFlowSubsystem.h"
|
||||
@@ -278,6 +279,7 @@ void AZMMOPlayerCharacter::BindZeusSpawnDelegate()
|
||||
}
|
||||
|
||||
ZeusNetwork->OnPlayerSpawned.AddDynamic(this, &AZMMOPlayerCharacter::HandleZeusPlayerSpawned);
|
||||
ZeusNetwork->OnCharInfoReceived.AddDynamic(this, &AZMMOPlayerCharacter::HandleZeusCharInfo);
|
||||
bSpawnDelegateBound = true;
|
||||
}
|
||||
|
||||
@@ -289,6 +291,7 @@ void AZMMOPlayerCharacter::UnbindZeusSpawnDelegate()
|
||||
return;
|
||||
}
|
||||
ZeusNetwork->OnPlayerSpawned.RemoveDynamic(this, &AZMMOPlayerCharacter::HandleZeusPlayerSpawned);
|
||||
ZeusNetwork->OnCharInfoReceived.RemoveDynamic(this, &AZMMOPlayerCharacter::HandleZeusCharInfo);
|
||||
bSpawnDelegateBound = false;
|
||||
}
|
||||
|
||||
@@ -307,6 +310,10 @@ void AZMMOPlayerCharacter::TryRegisterLocalEntityFromCachedSpawn()
|
||||
{
|
||||
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,
|
||||
@@ -349,25 +356,74 @@ void AZMMOPlayerCharacter::HandleLocalSpawnReady(const int32 InEntityId, const F
|
||||
}
|
||||
}
|
||||
|
||||
// Seed do EntityId no AttributeComponent do PlayerState. O componente
|
||||
// vive no AZMMOPlayerState via Component Registry — busca via PlayerState
|
||||
// (sobrevive ao despawn do Pawn). S_ATTRIBUTE_SNAPSHOT_FULL chega via
|
||||
// UDP e e' rouado pelo ZMMOAttributeNetworkHandler via lookup por
|
||||
// EntityId no PlayerArray do GameState.
|
||||
// Identidade publica (EntityId) + seed no AttributeComponent. CharName/Guild
|
||||
// vem separado via S_CHAR_INFO -> HandleZeusCharInfo. AttributeComponent vive
|
||||
// como subobject e recebe seed pra que o ZMMOAttributeNetworkHandler consiga
|
||||
// rotear o snapshot por EntityId desde o primeiro pacote.
|
||||
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>())
|
||||
{
|
||||
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
|
||||
// Pawn. AHUD::BeginPlay chama UUIInGameFlowSubsystem::StartInGame, que
|
||||
// pushea WBP_HUD em UI.Layer.Game. UZMMOHudWidget::NativeOnActivated auto-binda
|
||||
// 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)
|
||||
{
|
||||
ResolveZeusNetworkSubsystem();
|
||||
|
||||
@@ -133,12 +133,19 @@ private:
|
||||
/**
|
||||
* Memoriza `EntityId` autoritativo e regista o pawn no `UZMMOWorldSubsystem`
|
||||
* 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);
|
||||
|
||||
UFUNCTION()
|
||||
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()
|
||||
TObjectPtr<UZeusNetworkSubsystem> ZeusNetwork;
|
||||
|
||||
|
||||
@@ -57,7 +57,11 @@ void UZMMOGameInstance::BindZeusDelegates(UZeusNetworkSubsystem* Zeus)
|
||||
Zeus->OnConnected.AddDynamic(this, &UZMMOGameInstance::HandleZeusConnected);
|
||||
Zeus->OnConnectionFailed.AddDynamic(this, &UZMMOGameInstance::HandleZeusConnectionFailed);
|
||||
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)
|
||||
@@ -69,7 +73,7 @@ void UZMMOGameInstance::UnbindZeusDelegates(UZeusNetworkSubsystem* Zeus)
|
||||
Zeus->OnConnected.RemoveDynamic(this, &UZMMOGameInstance::HandleZeusConnected);
|
||||
Zeus->OnConnectionFailed.RemoveDynamic(this, &UZMMOGameInstance::HandleZeusConnectionFailed);
|
||||
Zeus->OnDisconnected.RemoveDynamic(this, &UZMMOGameInstance::HandleZeusDisconnected);
|
||||
Zeus->OnNetworkLog.RemoveDynamic(this, &UZMMOGameInstance::HandleZeusNetworkLog);
|
||||
// OnNetworkLog: nao foi assinado (ver BindZeusDelegates) — nada a remover.
|
||||
}
|
||||
|
||||
void UZMMOGameInstance::HandleZeusConnected()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "Misc/ConfigCacheIni.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "ZMMO.h"
|
||||
|
||||
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,
|
||||
int32 InBaseLevel, int32 InClassId)
|
||||
{
|
||||
@@ -80,3 +92,9 @@ void AZMMOPlayerState::SetPublicIdentity(int64 InEntityId, const FString& InChar
|
||||
BaseLevel = InBaseLevel;
|
||||
ClassId = InClassId;
|
||||
}
|
||||
|
||||
void AZMMOPlayerState::SetCharInfo(const FString& InCharName, const FString& InGuildName)
|
||||
{
|
||||
CharName = InCharName;
|
||||
GuildName = InGuildName;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Templates/SubclassOf.h"
|
||||
#include "ZMMOPlayerState.generated.h"
|
||||
|
||||
class FLifetimeProperty;
|
||||
class UActorComponent;
|
||||
|
||||
/**
|
||||
@@ -52,36 +53,55 @@ public:
|
||||
AZMMOPlayerState();
|
||||
|
||||
// === 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. */
|
||||
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Identity")
|
||||
UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Identity")
|
||||
int64 ZMMOEntityId = 0;
|
||||
|
||||
/** 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;
|
||||
|
||||
/** 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 ===
|
||||
|
||||
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress")
|
||||
UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress")
|
||||
int32 BaseLevel = 1;
|
||||
|
||||
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress")
|
||||
UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress")
|
||||
int32 ClassId = 0;
|
||||
|
||||
// === Guild (futuro) ===
|
||||
|
||||
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Guild")
|
||||
UPROPERTY(Replicated, VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Guild")
|
||||
FString GuildName;
|
||||
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
|
||||
// === Helpers ===
|
||||
|
||||
/// Atualizado quando S_ATTRIBUTE_SNAPSHOT_FULL chega — UZMMOHudWidget chama
|
||||
/// isso pra refletir os dados publicos do snapshot no PlayerState.
|
||||
/// Setado por AZMMOPlayerCharacter::HandleLocalSpawnReady (pose/EntityId)
|
||||
/// e atualizado por handlers de snapshot/level up para refletir dados
|
||||
/// publicos do char no PlayerState.
|
||||
UFUNCTION(BlueprintCallable, Category = "ZMMO|Identity")
|
||||
void SetPublicIdentity(int64 InEntityId, const FString& InCharId,
|
||||
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 <typename T>
|
||||
T* GetZMMOComponent() const
|
||||
|
||||
@@ -104,7 +104,10 @@ void UZMMOWorldSubsystem::HandlePlayerSpawned(const int32 EntityId, const bool b
|
||||
|
||||
if (RemoteEntities.Contains(EntityId))
|
||||
{
|
||||
// Ja existe um proxy: reactivar e re-snap.
|
||||
// Ja existe um proxy: tenta reactivar e re-snap. Se o weak ptr ficou
|
||||
// stale (actor coletado pelo GC, stream-out do World Partition, etc.),
|
||||
// remove a entry orfa e cai no caminho de spawn novo — evita SPAWN
|
||||
// silenciosamente ignorado depois de oscilacoes na AOI.
|
||||
if (AActor* Existing = RemoteEntities[EntityId].Get())
|
||||
{
|
||||
if (IZMMOEntityInterface* AsEntity = Cast<IZMMOEntityInterface>(Existing))
|
||||
@@ -112,8 +115,12 @@ void UZMMOWorldSubsystem::HandlePlayerSpawned(const int32 EntityId, const bool b
|
||||
AsEntity->SetEntityRelevant(true);
|
||||
}
|
||||
Existing->SetActorLocationAndRotation(PosCm, FRotator(0.0f, YawDeg, 0.0f));
|
||||
UE_LOG(LogZMMO, Verbose, TEXT("ZMMOWorldSubsystem: re-snap EntityId=%d"), EntityId);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
// weak ptr stale — limpa entry e cai pro spawn novo abaixo.
|
||||
RemoteEntities.Remove(EntityId);
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem: stale entry para EntityId=%d, respawnando"), EntityId);
|
||||
}
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
@@ -166,15 +173,23 @@ void UZMMOWorldSubsystem::HandlePlayerDespawned(const int32 EntityId)
|
||||
return;
|
||||
}
|
||||
|
||||
// Destruir o actor pra valer + remover do mapa. Antes, despawn apenas
|
||||
// fazia SetActorHiddenInGame(true) via SetEntityRelevant(false), o que
|
||||
// deixava o entry no mapa apontando pra um actor escondido. SPAWN
|
||||
// subsequente do mesmo entityId era detectado como "duplicate" e nada
|
||||
// acontecia — pawn ficava invisivel ate' o weak ptr ser GC'd. Bug
|
||||
// confirmado nos logs do AOI ao oscilar entrada/saida da ZI/ZD.
|
||||
if (AActor* Actor = Entry->Get())
|
||||
{
|
||||
if (IZMMOEntityInterface* AsEntity = Cast<IZMMOEntityInterface>(Actor))
|
||||
{
|
||||
AsEntity->SetEntityRelevant(false);
|
||||
}
|
||||
Actor->Destroy();
|
||||
}
|
||||
RemoteEntities.Remove(EntityId);
|
||||
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem: despawn EntityId=%d"), EntityId);
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem: despawn EntityId=%d (destroyed)"), EntityId);
|
||||
}
|
||||
|
||||
void UZMMOWorldSubsystem::HandlePlayerStateUpdate(const int32 EntityId, const int32 InputSeq,
|
||||
|
||||
131
Source/ZMMO/Game/UI/Common/UIDraggableWindow_Base.cpp
Normal file
131
Source/ZMMO/Game/UI/Common/UIDraggableWindow_Base.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include "UIDraggableWindow_Base.h"
|
||||
|
||||
#include "Components/Border.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/GameViewportClient.h"
|
||||
#include "Input/Reply.h"
|
||||
#include "UIWindowSaveGame.h"
|
||||
|
||||
void UUIDraggableWindow_Base::NativeOnActivated()
|
||||
{
|
||||
Super::NativeOnActivated();
|
||||
ApplySavedPosition();
|
||||
// Validacao usa translation + viewport size, ambos disponiveis ja' aqui
|
||||
// (sem precisar do CachedGeometry/layout pass).
|
||||
ValidatePositionInViewport();
|
||||
}
|
||||
|
||||
void UUIDraggableWindow_Base::ApplySavedPosition()
|
||||
{
|
||||
const FName Id = GetWindowId();
|
||||
if (Id.IsNone()) return;
|
||||
|
||||
UZMMOUISaveGame* Save = UZMMOUISaveGame::LoadOrCreate();
|
||||
FVector2D Pos;
|
||||
if (Save && Save->TryGetWindowPosition(Id, Pos))
|
||||
{
|
||||
SetRenderTranslation(Pos);
|
||||
}
|
||||
}
|
||||
|
||||
void UUIDraggableWindow_Base::PersistCurrentPosition()
|
||||
{
|
||||
const FName Id = GetWindowId();
|
||||
if (Id.IsNone()) return;
|
||||
|
||||
UZMMOUISaveGame* Save = UZMMOUISaveGame::LoadOrCreate();
|
||||
if (!Save) return;
|
||||
Save->SetWindowPosition(Id, GetRenderTransform().Translation);
|
||||
UZMMOUISaveGame::SaveNow(Save);
|
||||
}
|
||||
|
||||
bool UUIDraggableWindow_Base::IsDragHandleHit(const FPointerEvent& MouseEvent) const
|
||||
{
|
||||
if (!Border_DragHandle) return false;
|
||||
const FGeometry HandleGeom = Border_DragHandle->GetCachedGeometry();
|
||||
return HandleGeom.IsUnderLocation(MouseEvent.GetScreenSpacePosition());
|
||||
}
|
||||
|
||||
FReply UUIDraggableWindow_Base::NativeOnMouseButtonDown(const FGeometry& InGeometry,
|
||||
const FPointerEvent& InMouseEvent)
|
||||
{
|
||||
if (InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && IsDragHandleHit(InMouseEvent))
|
||||
{
|
||||
bMaybeDragging = true;
|
||||
bDragging = false;
|
||||
DragStartScreenPos = InMouseEvent.GetScreenSpacePosition();
|
||||
WidgetStartTranslation = GetRenderTransform().Translation;
|
||||
return FReply::Handled().CaptureMouse(TakeWidget());
|
||||
}
|
||||
return Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);
|
||||
}
|
||||
|
||||
FReply UUIDraggableWindow_Base::NativeOnMouseMove(const FGeometry& InGeometry,
|
||||
const FPointerEvent& InMouseEvent)
|
||||
{
|
||||
if (!bMaybeDragging && !bDragging)
|
||||
{
|
||||
return Super::NativeOnMouseMove(InGeometry, InMouseEvent);
|
||||
}
|
||||
|
||||
const FVector2D Delta = InMouseEvent.GetScreenSpacePosition() - DragStartScreenPos;
|
||||
if (!bDragging && Delta.SizeSquared() < DragStartThresholdPx * DragStartThresholdPx)
|
||||
{
|
||||
return FReply::Handled();
|
||||
}
|
||||
bDragging = true;
|
||||
SetRenderTranslation(WidgetStartTranslation + Delta);
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply UUIDraggableWindow_Base::NativeOnMouseButtonUp(const FGeometry& InGeometry,
|
||||
const FPointerEvent& InMouseEvent)
|
||||
{
|
||||
if (InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && (bMaybeDragging || bDragging))
|
||||
{
|
||||
const bool bWasRealDrag = bDragging;
|
||||
bMaybeDragging = false;
|
||||
bDragging = false;
|
||||
if (bWasRealDrag)
|
||||
{
|
||||
PersistCurrentPosition();
|
||||
}
|
||||
return FReply::Handled().ReleaseMouseCapture();
|
||||
}
|
||||
return Super::NativeOnMouseButtonUp(InGeometry, InMouseEvent);
|
||||
}
|
||||
|
||||
void UUIDraggableWindow_Base::NativeOnMouseLeave(const FPointerEvent& InMouseEvent)
|
||||
{
|
||||
Super::NativeOnMouseLeave(InMouseEvent);
|
||||
}
|
||||
|
||||
void UUIDraggableWindow_Base::ValidatePositionInViewport()
|
||||
{
|
||||
if (!GEngine || !GEngine->GameViewport) return;
|
||||
FVector2D ViewportSize;
|
||||
GEngine->GameViewport->GetViewportSize(ViewportSize);
|
||||
if (ViewportSize.IsNearlyZero()) return;
|
||||
|
||||
// RenderTransform.Translation esta em coordenadas de viewport (pixels),
|
||||
// independente de DPI/offset do editor — comparacao direta funciona em
|
||||
// PIE e em build standalone. Assume slot centralizado (default UMG
|
||||
// stack); meio-viewport e' o quanto a translation pode crescer antes
|
||||
// da janela sair completamente.
|
||||
const FVector2D Tr = GetRenderTransform().Translation;
|
||||
const float HalfX = ViewportSize.X * 0.5f;
|
||||
const float HalfY = ViewportSize.Y * 0.5f;
|
||||
|
||||
const bool bOutOfBounds =
|
||||
FMath::Abs(Tr.X) > HalfX - MinVisiblePixels ||
|
||||
FMath::Abs(Tr.Y) > HalfY - MinVisiblePixels;
|
||||
|
||||
UE_LOG(LogTemp, Verbose, TEXT("DraggableWindow::Validate translation=(%.0f,%.0f) viewport=(%.0f,%.0f) outOfBounds=%d"),
|
||||
Tr.X, Tr.Y, ViewportSize.X, ViewportSize.Y, bOutOfBounds ? 1 : 0);
|
||||
|
||||
if (bOutOfBounds)
|
||||
{
|
||||
SetRenderTranslation(FVector2D::ZeroVector);
|
||||
PersistCurrentPosition();
|
||||
}
|
||||
}
|
||||
90
Source/ZMMO/Game/UI/Common/UIDraggableWindow_Base.h
Normal file
90
Source/ZMMO/Game/UI/Common/UIDraggableWindow_Base.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonActivatableWidget.h"
|
||||
#include "UIDraggableWindow_Base.generated.h"
|
||||
|
||||
class UBorder;
|
||||
class UZMMOUISaveGame;
|
||||
|
||||
/**
|
||||
* Base de janelas in-game movel + persistente. Subclasse define WindowId
|
||||
* (FName estavel) via override de GetWindowId(); a Base cuida do drag
|
||||
* (RenderTransform.Translation) e da persistencia (UZMMOUISaveGame).
|
||||
*
|
||||
* Convencao do WBP:
|
||||
* - Bind opcional `Border_DragHandle` cobrindo a area arrastavel (header).
|
||||
* Sem ele, a janela nao captura drag (vira static).
|
||||
* - `Border_DragHandle->Visibility = Visible` (precisa receber HitTest).
|
||||
*
|
||||
* Por que RenderTransform e nao Slot.Position?
|
||||
* - Funciona em qualquer layout slot (Overlay, VBox, Canvas), nao so
|
||||
* CanvasPanelSlot.
|
||||
* - Nao polui o asset (translation e' transient/visual, nao layout-layout).
|
||||
*
|
||||
* Persistencia: na primeira ativacao, carrega a posicao salva do WindowId;
|
||||
* ao soltar o drag, grava e salva o slot.
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable)
|
||||
class ZMMO_API UUIDraggableWindow_Base : public UCommonActivatableWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Identificador estavel da janela (chave no save). Subclasse OBRIGADA
|
||||
* a sobrescrever — default NAME_None desativa persistencia.
|
||||
*/
|
||||
virtual FName GetWindowId() const { return NAME_None; }
|
||||
|
||||
protected:
|
||||
virtual void NativeOnActivated() override;
|
||||
virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
|
||||
virtual FReply NativeOnMouseMove(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
|
||||
virtual FReply NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
|
||||
virtual void NativeOnMouseLeave(const FPointerEvent& InMouseEvent) override;
|
||||
|
||||
/**
|
||||
* Define se o ponto sob o cursor pertence a area de drag. Default: usa
|
||||
* Border_DragHandle (se bindado). Subclass pode override pra usar outro
|
||||
* widget (ex.: Container_Header ja' existente) sem precisar adicionar
|
||||
* um Border dedicado no WBP.
|
||||
*/
|
||||
virtual bool IsDragHandleHit(const FPointerEvent& MouseEvent) const;
|
||||
|
||||
/** Aplica a posicao salva (se houver) como RenderTransform.Translation. */
|
||||
void ApplySavedPosition();
|
||||
|
||||
/** Grava a posicao atual no save e persiste no slot. */
|
||||
void PersistCurrentPosition();
|
||||
|
||||
/**
|
||||
* Se a janela ficou fora do viewport (ex.: usuario arrastou pra borda e
|
||||
* depois a resolucao mudou ou ele fechou e reabriu), reseta translation
|
||||
* pra (0,0). Chamado no proximo tick apos Activated — cached geometry
|
||||
* so' tem valor depois do primeiro layout pass.
|
||||
*/
|
||||
UFUNCTION()
|
||||
void ValidatePositionInViewport();
|
||||
|
||||
/** Quanto da janela precisa ficar visivel pra contar como "no viewport". */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Drag")
|
||||
float MinVisiblePixels = 50.f;
|
||||
|
||||
/**
|
||||
* Bind opcional do WBP. Apenas drags iniciados com o cursor SOBRE
|
||||
* este border sao aceitos — bloqueia drag por clicar nos rows/botoes.
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Drag")
|
||||
TObjectPtr<UBorder> Border_DragHandle;
|
||||
|
||||
/** Distancia minima do mouse pra confirmar "drag iniciado" (anti-jitter). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Drag")
|
||||
float DragStartThresholdPx = 4.f;
|
||||
|
||||
private:
|
||||
bool bMaybeDragging = false; ///< pressed mas ainda nao passou do threshold
|
||||
bool bDragging = false; ///< drag confirmado
|
||||
FVector2D DragStartScreenPos = FVector2D::ZeroVector;
|
||||
FVector2D WidgetStartTranslation = FVector2D::ZeroVector;
|
||||
};
|
||||
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();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user