Compare commits
16 Commits
feat/attri
...
88eb20eefa
| Author | SHA1 | Date | |
|---|---|---|---|
| 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/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.
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.
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
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();
|
||||
}
|
||||
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;
|
||||
};
|
||||
43
Source/ZMMO/Game/UI/Common/UIWindowSaveGame.cpp
Normal file
43
Source/ZMMO/Game/UI/Common/UIWindowSaveGame.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "UIWindowSaveGame.h"
|
||||
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
|
||||
const FString UZMMOUISaveGame::SlotName = TEXT("UIPrefs");
|
||||
|
||||
UZMMOUISaveGame* UZMMOUISaveGame::LoadOrCreate()
|
||||
{
|
||||
if (UGameplayStatics::DoesSaveGameExist(SlotName, UserIndex))
|
||||
{
|
||||
if (UZMMOUISaveGame* Loaded = Cast<UZMMOUISaveGame>(
|
||||
UGameplayStatics::LoadGameFromSlot(SlotName, UserIndex)))
|
||||
{
|
||||
return Loaded;
|
||||
}
|
||||
}
|
||||
return Cast<UZMMOUISaveGame>(
|
||||
UGameplayStatics::CreateSaveGameObject(UZMMOUISaveGame::StaticClass()));
|
||||
}
|
||||
|
||||
void UZMMOUISaveGame::SaveNow(UZMMOUISaveGame* Save)
|
||||
{
|
||||
if (Save)
|
||||
{
|
||||
UGameplayStatics::SaveGameToSlot(Save, SlotName, UserIndex);
|
||||
}
|
||||
}
|
||||
|
||||
bool UZMMOUISaveGame::TryGetWindowPosition(FName WindowId, FVector2D& OutPos) const
|
||||
{
|
||||
if (const FVector2D* Found = WindowPositions.Find(WindowId))
|
||||
{
|
||||
OutPos = *Found;
|
||||
return true;
|
||||
}
|
||||
OutPos = FVector2D::ZeroVector;
|
||||
return false;
|
||||
}
|
||||
|
||||
void UZMMOUISaveGame::SetWindowPosition(FName WindowId, FVector2D Pos)
|
||||
{
|
||||
WindowPositions.Add(WindowId, Pos);
|
||||
}
|
||||
46
Source/ZMMO/Game/UI/Common/UIWindowSaveGame.h
Normal file
46
Source/ZMMO/Game/UI/Common/UIWindowSaveGame.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/SaveGame.h"
|
||||
#include "UIWindowSaveGame.generated.h"
|
||||
|
||||
/**
|
||||
* Persistencia de prefs de UI por janela (posicao, e futuramente tamanho,
|
||||
* estado expandido, etc). Usado por UUIDraggableWindow_Base — toda janela
|
||||
* movel registra uma chave (FName WindowId) e grava aqui ao soltar o drag.
|
||||
*
|
||||
* Arquivo: [Project]/Saved/SaveGames/UIPrefs_0.sav (binary blob via
|
||||
* UE serializer). Mesmo padrao do [[ZMMOLoginSaveGame]].
|
||||
*
|
||||
* Helpers static abaixo encapsulam load-or-create do slot e Get/Set/Save
|
||||
* pra que a Base nao precise repetir a cerimonia.
|
||||
*/
|
||||
UCLASS()
|
||||
class ZMMO_API UZMMOUISaveGame : public USaveGame
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Posicao normalizada (translation absoluta em pixels da viewport) por janela. */
|
||||
UPROPERTY()
|
||||
TMap<FName, FVector2D> WindowPositions;
|
||||
|
||||
static const FString SlotName;
|
||||
static constexpr int32 UserIndex = 0;
|
||||
|
||||
/**
|
||||
* Carrega o save existente ou cria um novo (nao salva ainda).
|
||||
* Sempre retorna instancia valida.
|
||||
*/
|
||||
static UZMMOUISaveGame* LoadOrCreate();
|
||||
|
||||
/** Salva o slot atual (sincrono — chamado on-mouse-up, custo baixo). */
|
||||
static void SaveNow(UZMMOUISaveGame* Save);
|
||||
|
||||
/** Get com default — se WindowId nao foi gravado retorna FVector2D::ZeroVector. */
|
||||
UFUNCTION(BlueprintCallable, Category = "UI|Prefs")
|
||||
bool TryGetWindowPosition(FName WindowId, FVector2D& OutPos) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "UI|Prefs")
|
||||
void SetWindowPosition(FName WindowId, FVector2D Pos);
|
||||
};
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "UIFrontEndScreenSet.h"
|
||||
#include "UIManagerSubsystem.h"
|
||||
#include "UIPrimaryGameLayout_Base.h"
|
||||
#include "UI/Common/UILoadingProfilesDataAsset.h"
|
||||
#include "UI/Common/UILoadingScreen_Base.h"
|
||||
#include "UI/UILayerTags.h"
|
||||
#include "ZMMOGameInstance.h"
|
||||
#include "ZMMOThemeSubsystem.h"
|
||||
@@ -212,13 +214,37 @@ void UUIFrontEndFlowSubsystem::ResolveAndPushScreen(EZMMOFrontEndState State)
|
||||
const FSoftObjectPath Path = Soft.ToSoftObjectPath();
|
||||
UAssetManager::GetStreamableManager().RequestAsyncLoad(
|
||||
Path,
|
||||
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer]()
|
||||
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer, State]()
|
||||
{
|
||||
if (UUIManagerSubsystem* M = GetUIManager())
|
||||
{
|
||||
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"));
|
||||
|
||||
// Drena steps que dispararam ANTES do widget existir
|
||||
// (HandlePostLoadMap/HandlePlayerSpawned podem ter
|
||||
// rodado durante o RequestAsyncLoad). Sem isso o
|
||||
// loading fica eterno se o servidor for rápido demais.
|
||||
for (const FName StepId : PendingLoadingSteps_)
|
||||
{
|
||||
Loading->MarkStepDone(StepId);
|
||||
}
|
||||
PendingLoadingSteps_.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -283,6 +309,7 @@ void UUIFrontEndFlowSubsystem::BindNetwork()
|
||||
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
||||
{
|
||||
Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
||||
Zeus->OnPlayerSpawned.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned);
|
||||
}
|
||||
bNetBound = true;
|
||||
}
|
||||
@@ -302,6 +329,7 @@ void UUIFrontEndFlowSubsystem::UnbindNetwork()
|
||||
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
||||
{
|
||||
Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
||||
Zeus->OnPlayerSpawned.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned);
|
||||
}
|
||||
bNetBound = false;
|
||||
}
|
||||
@@ -390,11 +418,77 @@ void UUIFrontEndFlowSubsystem::HandleServerTravelRequested(const FString& MapNam
|
||||
|
||||
void UUIFrontEndFlowSubsystem::HandlePostLoadMap(UWorld* /*LoadedWorld*/)
|
||||
{
|
||||
if (!bTravelingToWorld)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Race fix: widget ainda em RequestAsyncLoad; memoiza pra drenar quando
|
||||
// a tela for criada (ver lambda em ResolveAndPushScreen). Não fallback
|
||||
// pra InWorld — esperamos a tela existir e o Spawn chegar.
|
||||
PendingLoadingSteps_.Add(TEXT("MapLoaded"));
|
||||
}
|
||||
|
||||
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"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Race fix: spawn pode chegar ANTES do widget existir se o servidor for
|
||||
// rápido (TryFinalizeEnterWorld) e o RequestAsyncLoad lento. Memoiza
|
||||
// pra drenar no callback do async load.
|
||||
if (bTravelingToWorld)
|
||||
{
|
||||
PendingLoadingSteps_.Add(TEXT("Spawn"));
|
||||
}
|
||||
}
|
||||
|
||||
void UUIFrontEndFlowSubsystem::HandleLoadingComplete()
|
||||
{
|
||||
if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
|
||||
{
|
||||
Loading->OnLoadingComplete.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleLoadingComplete);
|
||||
}
|
||||
ActiveLoadingScreen.Reset();
|
||||
PendingLoadingSteps_.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)
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
class UUIFrontEndScreenSet;
|
||||
class UUIManagerSubsystem;
|
||||
class UUILoadingScreen_Base;
|
||||
class UZMMOLoadingProfilesDataAsset;
|
||||
class UZeusNetworkSubsystem;
|
||||
class UZeusCharServerSubsystem;
|
||||
struct FZMMOMapDef;
|
||||
@@ -147,6 +149,24 @@ protected:
|
||||
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Maps")
|
||||
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:
|
||||
void BindNetwork();
|
||||
void UnbindNetwork();
|
||||
@@ -170,8 +190,26 @@ private:
|
||||
UFUNCTION()
|
||||
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);
|
||||
|
||||
/** 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
|
||||
* ainda não expõe um delegate/getter dedicado. Quando expuser, ligar aqui
|
||||
@@ -199,6 +237,27 @@ private:
|
||||
UPROPERTY(Transient)
|
||||
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;
|
||||
|
||||
/**
|
||||
* Race fix: o widget de loading é criado via RequestAsyncLoad (assíncrono).
|
||||
* Se o servidor for rápido (TryFinalizeEnterWorld), HandlePostLoadMap e/ou
|
||||
* HandlePlayerSpawned podem disparar ANTES de ActiveLoadingScreen existir
|
||||
* — nesse caso o MarkStepDone vira no-op e o loading fica eterno.
|
||||
* Memoizamos os StepIds aqui e drenamos quando a tela for criada.
|
||||
*/
|
||||
TSet<FName> PendingLoadingSteps_;
|
||||
|
||||
bool bNetBound = false;
|
||||
bool bTravelingToWorld = false;
|
||||
FDelegateHandle PostLoadMapHandle;
|
||||
|
||||
@@ -9,12 +9,11 @@
|
||||
void UUIServerCard_Base::NativePreConstruct()
|
||||
{
|
||||
Super::NativePreConstruct();
|
||||
// Reaplica tema do botao antes de qualquer SetIsEnabled — garante que o
|
||||
// background da variante "Primary" pinta tanto em Normal quanto Disabled.
|
||||
if (Btn_Enter)
|
||||
{
|
||||
Btn_Enter->RefreshUIStyle();
|
||||
}
|
||||
// TODO(ui-system): reativar quando UUIButton_Base voltar a ter RefreshUIStyle.
|
||||
// if (Btn_Enter)
|
||||
// {
|
||||
// Btn_Enter->RefreshUIStyle();
|
||||
// }
|
||||
}
|
||||
|
||||
void UUIServerCard_Base::NativeConstruct()
|
||||
@@ -25,10 +24,11 @@ void UUIServerCard_Base::NativeConstruct()
|
||||
Btn_Enter->OnClicked().AddUObject(this, &UUIServerCard_Base::HandleEnterClicked);
|
||||
bBound = true;
|
||||
}
|
||||
if (Btn_Enter)
|
||||
{
|
||||
Btn_Enter->RefreshUIStyle();
|
||||
}
|
||||
// TODO(ui-system): reativar quando UUIButton_Base voltar a ter RefreshUIStyle.
|
||||
// if (Btn_Enter)
|
||||
// {
|
||||
// Btn_Enter->RefreshUIStyle();
|
||||
// }
|
||||
}
|
||||
|
||||
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/
|
||||
// deletar. O gate de "entrar no mundo" fica no proprio Lobby (botao
|
||||
// "Entrar no Servidor"), que checa World.State antes do handoff.
|
||||
if (Btn_Enter)
|
||||
{
|
||||
Btn_Enter->RefreshUIStyle();
|
||||
}
|
||||
// TODO(ui-system): reativar quando UUIButton_Base voltar a ter RefreshUIStyle.
|
||||
// if (Btn_Enter)
|
||||
// {
|
||||
// Btn_Enter->RefreshUIStyle();
|
||||
// }
|
||||
}
|
||||
|
||||
void UUIServerCard_Base::HandleEnterClicked()
|
||||
|
||||
@@ -81,16 +81,26 @@ void UUIInGameFlowSubsystem::SetState(EZMMOInGameUIState NewState)
|
||||
// (Status/Inventory/Menu) limpando o Layer.GameMenu — HUD em Layer.Game
|
||||
// permanece. Para qualquer outro estado, mantemos HUD e empilhamos no
|
||||
// layer correspondente.
|
||||
const bool bGoingToPlaying = (NewState == EZMMOInGameUIState::Playing);
|
||||
if (UUIManagerSubsystem* Mgr = GetUIManager())
|
||||
{
|
||||
const bool bGoingToPlaying = (NewState == EZMMOInGameUIState::Playing);
|
||||
if (bGoingToPlaying)
|
||||
{
|
||||
Mgr->ClearLayer(ZMMOUITags::UI_Layer_GameMenu.GetTag());
|
||||
}
|
||||
}
|
||||
|
||||
// Skip re-push do HUD quando ja' estavamos in-game (Old != None significa
|
||||
// que StartInGame ja' rodou e o HUD esta no Layer.Game). Senao, cada vez
|
||||
// que o usuario fechar StatusWindow/Inventory/etc, o HUD seria recriado
|
||||
// — flicker em todos os textos (HP/SP/etc) por causa do Construct novo.
|
||||
const bool bSkipRepush = bGoingToPlaying
|
||||
&& OldState != EZMMOInGameUIState::None
|
||||
&& OldState != EZMMOInGameUIState::Playing;
|
||||
if (!bSkipRepush)
|
||||
{
|
||||
ResolveAndPushScreen(NewState);
|
||||
}
|
||||
OnStateChanged.Broadcast(NewState);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 "ZMMOThemeSubsystem.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/World.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"
|
||||
#include "UI/UIStyleRow.h"
|
||||
#include "UI/UIStyleTokens.h"
|
||||
#include "Engine/DataTable.h"
|
||||
|
||||
namespace
|
||||
const FUIStyle* UUIButton_Base::GetActiveUIStyle(FName ThemeId) const
|
||||
{
|
||||
/** Resolve o FUIStyle ativo; fallback p/ defaults (preview de designer). */
|
||||
const FUIStyle& ResolveActiveStyle(const UUserWidget* Widget, const FUIStyle& Fallback)
|
||||
const UDataTable* DT = LoadObject<UDataTable>(nullptr,
|
||||
TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles"));
|
||||
if (!DT)
|
||||
{
|
||||
if (Widget)
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
FUIStyleButtonVariant UUIButton_Base::ResolveVariant(const FUIStyleButton& Button) const
|
||||
{
|
||||
// Compõe FLAT a partir dos 3 sub-mapas do tema (Backgrounds/Strokes/Fonts).
|
||||
// Fallback inline → Defaults (constructor de FUIStyleButton popula
|
||||
// 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;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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))
|
||||
ThemeId, TEXT("UUIButton_Base::GetActiveUIStyle"), 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 &Row->Style;
|
||||
}
|
||||
}
|
||||
return Options;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ---- Hyper "Set Button Text" ----
|
||||
void UUIButton_Base::SetButtonText(FText InText)
|
||||
const FUIStyleButton* UUIButton_Base::GetActiveButtonStyle(FName ThemeId) const
|
||||
{
|
||||
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();
|
||||
const FUIStyle* UIStyle = GetActiveUIStyle(ThemeId);
|
||||
return UIStyle ? &UIStyle->Button : nullptr;
|
||||
}
|
||||
|
||||
@@ -2,220 +2,55 @@
|
||||
|
||||
#include "CoreMinimal.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"
|
||||
|
||||
class UBorder;
|
||||
class UTextBlock;
|
||||
class UImage;
|
||||
class USizeBox;
|
||||
class UScaleBox;
|
||||
class UImage;
|
||||
class UCanvasPanel;
|
||||
class UCommonTextBlock;
|
||||
class UCommonActionWidget;
|
||||
class UTextBlock;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnUIButtonActivatedByTimeout);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnUIButtonTimerUpdated, float, TimeRemaining);
|
||||
struct FUIStyle;
|
||||
struct FUIStyleButton;
|
||||
|
||||
/**
|
||||
* Botão base do ZMMO — porte 1:1 do UI_Button_Master do projeto Hyper para C++
|
||||
* auto-configurável, trocando o Struct_Style_Button do Hyper pela camada de
|
||||
* tema C++ (FUIStyle via UZMMOThemeSubsystem::GetActiveUIStyle()).
|
||||
*
|
||||
* 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.
|
||||
* Botão base do ZMMO (CommonUI). Hoje expõe BindWidgetOptional dos widgets
|
||||
* que o WBP UI_Button_Master_New consome, e helpers pra ler o estilo ativo
|
||||
* de DT_UI_Styles. A aplicação visual (pintar brushes/cores/fonte) entra
|
||||
* depois — esta camada só prepara o acesso.
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable)
|
||||
class ZMMO_API UUIButton_Base : public UCommonButtonBase
|
||||
{
|
||||
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:
|
||||
virtual void NativePreConstruct() override;
|
||||
virtual void NativeConstruct() override;
|
||||
virtual void NativeDestruct() override;
|
||||
// ---- Widgets BindWidgetOptional (espelham a árvore do UI_Button_Master_New) ----
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<USizeBox> SizeBox;
|
||||
|
||||
// Estados do UCommonButtonBase → repinta o Background pela variante.
|
||||
virtual void NativeOnHovered() override;
|
||||
virtual void NativeOnUnhovered() override;
|
||||
virtual void NativeOnPressed() override;
|
||||
virtual void NativeOnReleased() override;
|
||||
virtual void NativeOnEnabled() override;
|
||||
virtual void NativeOnDisabled() override;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<UImage> ImgStoke;
|
||||
|
||||
/** Hook opcional: o WBP pode estender/sobrescrever a aplicação visual. */
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "UI Style",
|
||||
meta = (DisplayName = "Apply UI Style"))
|
||||
void BP_ApplyUIStyle(const FUIStyleButton& Button, const FUIStyleButtonVariant& VariantStyle);
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<UCanvasPanel> CanvasPanel_Text;
|
||||
|
||||
FUIStyleButtonVariant ResolveVariant(const FUIStyleButton& Button) const;
|
||||
|
||||
// ---- Widgets visuais opcionais (nomes espelham o Hyper) ----
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<USizeBox> SizeBox_Master;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<UCommonTextBlock> ButtonText;
|
||||
|
||||
/** Envolve o ButtonText: escala o texto p/ caber no botão (bAutoSizeText). */
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UScaleBox> TextScaleBox;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<UTextBlock> Number_Text;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UImage> Image_Icon;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "UI Button", meta = (BindWidgetOptional))
|
||||
TObjectPtr<UImage> ImgIcon;
|
||||
|
||||
/** Fundo temável (nossa adição sobre o Hyper, p/ FUIStyle). */
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UBorder> Background;
|
||||
// ---- Acesso ao estilo ativo (DT_UI_Styles) — C++ only ----
|
||||
|
||||
private:
|
||||
void StartDeactivateTimeout();
|
||||
void HandleTimeoutFinished();
|
||||
void HandleTimerTick();
|
||||
/** Carrega DT_UI_Styles e devolve o FUIStyle da row pedida (default "Default").
|
||||
* Retorna nullptr se a DT ou a row não existirem. Ponteiro válido enquanto
|
||||
* a DT estiver carregada — uso típico é leitura imediata, sem armazenar. */
|
||||
const FUIStyle* GetActiveUIStyle(FName ThemeId = TEXT("Default")) const;
|
||||
|
||||
UFUNCTION()
|
||||
void HandleThemeChanged(FName NewThemeId);
|
||||
|
||||
bool bThemeBound = false;
|
||||
FTimerHandle InactivityTimer;
|
||||
FTimerHandle TimerTickHandle;
|
||||
/** Atalho: devolve só o bloco Button do FUIStyle ativo. */
|
||||
const FUIStyleButton* GetActiveButtonStyle(FName ThemeId = TEXT("Default")) const;
|
||||
};
|
||||
|
||||
@@ -52,83 +52,43 @@ void UUIPanel_Base::RefreshUIStyle()
|
||||
const FUIStyle AS = ResolvePanelStyle(this, Fallback);
|
||||
const FUIStylePanel& P = AS.Panel;
|
||||
|
||||
// ---- Modo TEXTURA (padrão Hyper): brush por EUIPanelTexture ----
|
||||
// Fiel ao UI_Panel_Master do Hyper: só faz SetBrush nos dois Borders
|
||||
// (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; }
|
||||
// bUseTheme = true -> brushes vêm da DT (lookup pelas 3 chaves).
|
||||
// bUseTheme = false -> brushes vêm dos 3 FSlateBrush em Panels (direto).
|
||||
|
||||
const FSlateBrush* BgTex = nullptr;
|
||||
if (PanelTexture != EUIPanelTexture::None)
|
||||
{
|
||||
BgTex = Panels.PanelBackground.Find(PanelTexture);
|
||||
if (!BgTex) { BgTex = P.PanelBackground.Find(PanelTexture); }
|
||||
}
|
||||
if (BgTex)
|
||||
{
|
||||
Background->SetBrush(*BgTex);
|
||||
if (Outline)
|
||||
{
|
||||
// Hyper: Find sempre seguido de SetBrush. No Hyper a entrada de
|
||||
// outline ausente é um brush TRANSPARENTE (alpha 0) — não some
|
||||
// widget, só não desenha. Um FSlateBrush default desenha um
|
||||
// retângulo BRANCO; o equivalente correto é DrawAs=NoDrawType.
|
||||
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) ----
|
||||
auto ResolveFromTheme = [](FName Key,
|
||||
const TMap<FName, FSlateBrush>& Map) -> const FSlateBrush*
|
||||
{
|
||||
return Key.IsNone() ? nullptr : Map.Find(Key);
|
||||
};
|
||||
|
||||
const FSlateBrush* BgTex = bUseTheme
|
||||
? ResolveFromTheme(BackgroundKey, P.PanelBackgroundByName)
|
||||
: &Panels.PanelBackground;
|
||||
Background->SetBrush(BgTex ? *BgTex : NoDrawBrush);
|
||||
|
||||
if (Outline)
|
||||
{
|
||||
Outline->SetVisibility(ESlateVisibility::Collapsed);
|
||||
const FSlateBrush* OlTex = bUseTheme
|
||||
? ResolveFromTheme(OutlineKey, P.PanelOutlineByName)
|
||||
: &Panels.PanelOutline;
|
||||
Outline->SetBrush(OlTex ? *OlTex : NoDrawBrush);
|
||||
}
|
||||
|
||||
FLinearColor Fill;
|
||||
FLinearColor OutlineColor = P.BorderColor;
|
||||
switch (PanelType)
|
||||
if (OutlineEffect)
|
||||
{
|
||||
case EUIPanelType::Secondary: Fill = P.BgRaised; break;
|
||||
case EUIPanelType::Tertiary: Fill = P.BgSunken; break;
|
||||
case EUIPanelType::Card: Fill = P.CardSelectedBg; OutlineColor = P.CardSelectedBorder; break;
|
||||
case EUIPanelType::Primary:
|
||||
case EUIPanelType::None:
|
||||
default: Fill = P.Bg; break;
|
||||
const FSlateBrush* OeTex = bUseTheme
|
||||
? ResolveFromTheme(OutlineEffectKey, P.PanelOutlineEffectByName)
|
||||
: &Panels.PanelOutlineEffect;
|
||||
OutlineEffect->SetBrush(OeTex ? *OeTex : NoDrawBrush);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -176,3 +136,59 @@ void UUIPanel_Base::HandleThemeChanged(FName /*NewThemeId*/)
|
||||
{
|
||||
RefreshUIStyle();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
enum class EPanelBrushSlot : uint8 { Background, Outline, OutlineEffect };
|
||||
|
||||
TArray<FString> CollectPanelOptions(EPanelBrushSlot Slot)
|
||||
{
|
||||
TArray<FString> Options;
|
||||
Options.Add(FString()); // entry vazia = "sem brush" (NoDrawType)
|
||||
|
||||
const UDataTable* DT = LoadObject<UDataTable>(nullptr,
|
||||
TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles"));
|
||||
if (!DT)
|
||||
{
|
||||
return Options;
|
||||
}
|
||||
const FUIStyleRow* Row = DT->FindRow<FUIStyleRow>(
|
||||
FName(TEXT("Default")), TEXT("UIPanelGetOptions"), false);
|
||||
if (!Row)
|
||||
{
|
||||
return Options;
|
||||
}
|
||||
|
||||
const TMap<FName, FSlateBrush>* Map = nullptr;
|
||||
switch (Slot)
|
||||
{
|
||||
case EPanelBrushSlot::Background: Map = &Row->Style.Panel.PanelBackgroundByName; break;
|
||||
case EPanelBrushSlot::Outline: Map = &Row->Style.Panel.PanelOutlineByName; break;
|
||||
case EPanelBrushSlot::OutlineEffect: Map = &Row->Style.Panel.PanelOutlineEffectByName; break;
|
||||
}
|
||||
if (Map)
|
||||
{
|
||||
for (const TPair<FName, FSlateBrush>& Pair : *Map)
|
||||
{
|
||||
Options.Add(Pair.Key.ToString());
|
||||
}
|
||||
Options.Sort();
|
||||
}
|
||||
return Options;
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FString> UUIPanel_Base::GetPanelBackgroundOptions() const
|
||||
{
|
||||
return CollectPanelOptions(EPanelBrushSlot::Background);
|
||||
}
|
||||
|
||||
TArray<FString> UUIPanel_Base::GetPanelOutlineOptions() const
|
||||
{
|
||||
return CollectPanelOptions(EPanelBrushSlot::Outline);
|
||||
}
|
||||
|
||||
TArray<FString> UUIPanel_Base::GetPanelOutlineEffectOptions() const
|
||||
{
|
||||
return CollectPanelOptions(EPanelBrushSlot::OutlineEffect);
|
||||
}
|
||||
|
||||
@@ -23,40 +23,60 @@ class ZMMO_API UUIPanel_Base : public UCommonUserWidget
|
||||
GENERATED_BODY()
|
||||
|
||||
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
|
||||
* FUIStylePanel.PanelBackground p/ esta chave, usa o painel TEXTURIZADO
|
||||
* (brush bg + outline). Senão, cai no modo COR (RoundedBox acima).
|
||||
* true = lê brushes da DT_UI_Styles (FUIStylePanel) — tema centralizado.
|
||||
* false = ignora a DT e usa apenas `Panels` (brushes manuais no Details).
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel")
|
||||
EUIPanelTexture PanelTexture = EUIPanelTexture::None;
|
||||
bool bUseTheme = true;
|
||||
|
||||
/**
|
||||
* Mapas de brush por instância — espelha o `Panels` do UI_Panel_Master
|
||||
* do Hyper (Details mostra Panel Background / Panel Outline editáveis).
|
||||
* Tem prioridade. Se não houver brush p/ a chave aqui, cai no tema
|
||||
* (DT_UI_Styles). Em NativePreConstruct é semeado a partir do tema
|
||||
* quando vazio, então por padrão o tema manda — como no Hyper.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel")
|
||||
// ---- Modo TEMA: chave FName do TMap FUIStylePanel pra cada Border. ----
|
||||
//
|
||||
// GetOptions popula dropdown lendo DT_UI_Styles row "Default" — adicione
|
||||
// uma entry em FUIStylePanel.PanelBackgroundByName no DT e a categoria
|
||||
// aparece aqui sem recompilar C++. Vazio = sem brush (NoDrawType).
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel",
|
||||
meta = (EditCondition = "bUseTheme", EditConditionHides,
|
||||
GetOptions = "GetPanelBackgroundOptions"))
|
||||
FName BackgroundKey;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel",
|
||||
meta = (EditCondition = "bUseTheme", EditConditionHides,
|
||||
GetOptions = "GetPanelOutlineOptions"))
|
||||
FName OutlineKey;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel",
|
||||
meta = (EditCondition = "bUseTheme", EditConditionHides,
|
||||
GetOptions = "GetPanelOutlineEffectOptions"))
|
||||
FName OutlineEffectKey;
|
||||
|
||||
// ---- Modo MANUAL: 3 brushes editáveis direto no Details. ----
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Panel",
|
||||
meta = (EditCondition = "!bUseTheme", EditConditionHides))
|
||||
FUIPanelBrushSet Panels;
|
||||
|
||||
/** Re-resolve os tokens do tema ativo e reaplica no Background. */
|
||||
UFUNCTION(BlueprintCallable, Category = "UI Style")
|
||||
void RefreshUIStyle();
|
||||
|
||||
/**
|
||||
* Populadores do dropdown (meta=GetOptions) das chaves de textura.
|
||||
* Lêem DT_UI_Styles row "Default" e retornam as FName de cada sub-mapa
|
||||
* do FUIStylePanel. Editar o DT no editor reflete aqui sem recompilar.
|
||||
* São métodos de instância porque o UE precisa de UFUNCTION reflexável;
|
||||
* o conteúdo não usa estado da instância (poderia ser estático).
|
||||
*/
|
||||
UFUNCTION()
|
||||
TArray<FString> GetPanelBackgroundOptions() const;
|
||||
|
||||
UFUNCTION()
|
||||
TArray<FString> GetPanelOutlineOptions() const;
|
||||
|
||||
UFUNCTION()
|
||||
TArray<FString> GetPanelOutlineEffectOptions() const;
|
||||
|
||||
protected:
|
||||
virtual void NativePreConstruct() override;
|
||||
virtual void NativeConstruct() override;
|
||||
@@ -75,6 +95,10 @@ protected:
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UBorder> Outline;
|
||||
|
||||
/** Efeito sobre o contorno (glow/embossing/etc.) — pintado acima do Outline. */
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UBorder> OutlineEffect;
|
||||
|
||||
private:
|
||||
UFUNCTION()
|
||||
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;
|
||||
};
|
||||
280
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.cpp
Normal file
280
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.cpp
Normal file
@@ -0,0 +1,280 @@
|
||||
#include "UIPlayerStatus_Window.h"
|
||||
|
||||
#include "CommonInputTypeEnum.h"
|
||||
#include "Components/Button.h"
|
||||
#include "Components/HorizontalBox.h"
|
||||
#include "Components/TextBlock.h"
|
||||
#include "InputCoreTypes.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 exige DefaultBackAction configurada no CommonUISettings;
|
||||
// projeto ainda não tem DT de input actions, então registrar o handler
|
||||
// poluía o log com "Cannot create action binding". Fechamento da janela
|
||||
// vai por Alt+A (NativeOnKeyDown) e Button_Close.
|
||||
bAutoActivate = true;
|
||||
// Focusable + DesiredFocusTarget=self → recebe NativeOnKeyDown (Alt+A).
|
||||
SetIsFocusable(true);
|
||||
}
|
||||
|
||||
UWidget* UUIPlayerStatus_Window::NativeGetDesiredFocusTarget() const
|
||||
{
|
||||
return const_cast<UUIPlayerStatus_Window*>(this);
|
||||
}
|
||||
|
||||
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 (Button_Close) Button_Close->OnClicked.AddDynamic(this, &UUIPlayerStatus_Window::HandleCloseClicked);
|
||||
|
||||
// Background_Panel default é SELF_HIT_TEST_INVISIBLE; precisa Visible
|
||||
// pra capturar clicks em areas vazias do modal e bubbla pra Window.
|
||||
if (Background_Panel)
|
||||
{
|
||||
Background_Panel->SetVisibility(ESlateVisibility::Visible);
|
||||
}
|
||||
|
||||
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 (Button_Close) Button_Close->OnClicked.RemoveDynamic(this, &UUIPlayerStatus_Window::HandleCloseClicked);
|
||||
|
||||
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::HandleCloseClicked()
|
||||
{
|
||||
if (UGameInstance* GI = GetGameInstance())
|
||||
{
|
||||
if (UUIInGameFlowSubsystem* Flow = GI->GetSubsystem<UUIInGameFlowSubsystem>())
|
||||
{
|
||||
Flow->SetState(EZMMOInGameUIState::Playing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FReply UUIPlayerStatus_Window::NativeOnKeyDown(const FGeometry& Geom, const FKeyEvent& KeyEvent)
|
||||
{
|
||||
// Alt+A em modo Menu — PlayerController nao recebe (CommonUI absorve).
|
||||
// Handle aqui pra completar o toggle.
|
||||
if (KeyEvent.GetKey() == EKeys::A && KeyEvent.IsAltDown())
|
||||
{
|
||||
if (UGameInstance* GI = GetGameInstance())
|
||||
{
|
||||
if (UUIInGameFlowSubsystem* Flow = GI->GetSubsystem<UUIInGameFlowSubsystem>())
|
||||
{
|
||||
Flow->SetState(EZMMOInGameUIState::Playing);
|
||||
return FReply::Handled();
|
||||
}
|
||||
}
|
||||
}
|
||||
return Super::NativeOnKeyDown(Geom, KeyEvent);
|
||||
}
|
||||
|
||||
bool UUIPlayerStatus_Window::IsDragHandleHit(const FPointerEvent& MouseEvent) const
|
||||
{
|
||||
// Drag em qualquer parte do modal EXCETO Container_Stats (rows + botões +STR).
|
||||
// Botão close consome o evento antes (UButton.OnClicked) e não bubbla aqui.
|
||||
const FVector2D Pos = MouseEvent.GetScreenSpacePosition();
|
||||
if (Container_Stats && Container_Stats->GetCachedGeometry().IsUnderLocation(Pos))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
112
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.h
Normal file
112
Source/ZMMO/Game/UI/Widgets/UIPlayerStatus_Window.h
Normal file
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UI/Common/UIDraggableWindow_Base.h"
|
||||
#include "ZMMOAttributeTypes.h"
|
||||
#include "UIPlayerStatus_Window.generated.h"
|
||||
|
||||
class UButton;
|
||||
class UTextBlock;
|
||||
class UHorizontalBox;
|
||||
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 UUIDraggableWindow_Base
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UUIPlayerStatus_Window(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
/** Chave de persistencia no UZMMOUISaveGame. Estavel — nao renomear. */
|
||||
virtual FName GetWindowId() const override { return TEXT("PlayerStatus"); }
|
||||
|
||||
protected:
|
||||
virtual void NativeOnActivated() override;
|
||||
virtual void NativeOnDeactivated() override;
|
||||
virtual TOptional<FUIInputConfig> GetDesiredInputConfig() const override;
|
||||
|
||||
/** Diz ao stack pra mover foco pra ca' (precisa pra NativeOnKeyDown ser chamado). */
|
||||
virtual UWidget* NativeGetDesiredFocusTarget() const override;
|
||||
|
||||
/** Atalho Alt+A — fecha a janela em modo Menu (PC controller nao recebe). */
|
||||
virtual FReply NativeOnKeyDown(const FGeometry& Geom, const FKeyEvent& KeyEvent) override;
|
||||
|
||||
/** Drag area = Container_Header (header inteiro). Header_DragHandle no WBP nao necessario. */
|
||||
virtual bool IsDragHandleHit(const FPointerEvent& MouseEvent) const override;
|
||||
|
||||
/** Header do WBP (HorizontalBox raiz da identidade) — usado como drag area. */
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status|Header") TObjectPtr<UHorizontalBox> Container_Header;
|
||||
|
||||
/**
|
||||
* Zona EXCLUÍDA do drag — é onde ficam os rows interativos (+STR etc.).
|
||||
* Clicar aqui não deve iniciar drag mesmo em area "vazia" entre rows.
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr<UWidget> Container_Stats;
|
||||
|
||||
/**
|
||||
* Panel de fundo (UIPanel_Base). Setado pra Visible em NativeOnActivated
|
||||
* pra capturar clicks em areas vazias do modal — sem isso, drag só funciona
|
||||
* onde algum filho é Visible (header/footer).
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr<UWidget> Background_Panel;
|
||||
|
||||
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;
|
||||
|
||||
/** Botao de fechar na header — UButton simples (placeholder, estilizar depois). */
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|Status") TObjectPtr<UButton> Button_Close;
|
||||
|
||||
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 HandleCloseClicked();
|
||||
|
||||
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;
|
||||
};
|
||||
586
Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.cpp
Normal file
586
Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.cpp
Normal file
@@ -0,0 +1,586 @@
|
||||
#include "UIProgressBar_Base.h"
|
||||
|
||||
#include "Components/Image.h"
|
||||
#include "Components/OverlaySlot.h"
|
||||
#include "Materials/MaterialInstanceDynamic.h"
|
||||
#include "Materials/MaterialInterface.h"
|
||||
#include "Styling/SlateBrush.h"
|
||||
#include "Styling/SlateTypes.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr float MinAnimSeconds = 0.001f;
|
||||
|
||||
// Fill material params
|
||||
static const FName P_PrimaryLevel("PrimaryLevel");
|
||||
static const FName P_SecondaryLevel("SecondaryLevel");
|
||||
static const FName P_PrimaryColour("PrimaryColour");
|
||||
static const FName P_PrimaryColourBottom("PrimaryColourBottom");
|
||||
static const FName P_SecondaryColour("SecondaryColour");
|
||||
static const FName P_SecondaryColourBottom("SecondaryColourBottom");
|
||||
static const FName P_EmptyColour("EmptyColour");
|
||||
static const FName P_EnableGradient("EnableGradient");
|
||||
static const FName P_GradientSplit("GradientSplit");
|
||||
|
||||
// BG material params
|
||||
static const FName P_BackgroundColour("BackgroundColour");
|
||||
|
||||
// TrackGrid material params
|
||||
static const FName P_GridColour("GridColour");
|
||||
static const FName P_GridDivisions("GridDivisions");
|
||||
static const FName P_GridLineThickness("GridLineThickness");
|
||||
|
||||
// Shimmer material params
|
||||
static const FName P_ShimmerColour("ShimmerColour");
|
||||
static const FName P_ShimmerWidth("ShimmerWidth");
|
||||
static const FName P_ShimmerSpeed("ShimmerSpeed");
|
||||
static const FName P_ShimmerSoftness("ShimmerSoftness");
|
||||
|
||||
// Edge material params
|
||||
static const FName P_EdgeColour("EdgeColour");
|
||||
static const FName P_EdgeWidth("EdgeWidth");
|
||||
static const FName P_EdgeGlowWidth("EdgeGlowWidth");
|
||||
static const FName P_EdgeGlowIntensity("EdgeGlowIntensity");
|
||||
|
||||
FORCEINLINE float EaseOutCubic(float Alpha)
|
||||
{
|
||||
return 1.f - FMath::Pow(1.f - Alpha, 3.f);
|
||||
}
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::NativePreConstruct()
|
||||
{
|
||||
Super::NativePreConstruct();
|
||||
|
||||
CurrentPrimaryLevel = InitialPrimaryLevel;
|
||||
TargetPrimaryLevel = InitialPrimaryLevel;
|
||||
PrimaryStart = InitialPrimaryLevel;
|
||||
PrimaryElapsed = 0.f;
|
||||
bAnimatingPrimary = false;
|
||||
|
||||
CurrentSecondaryLevel = InitialSecondaryLevel;
|
||||
TargetSecondaryLevel = InitialSecondaryLevel;
|
||||
SecondaryStart = InitialSecondaryLevel;
|
||||
SecondaryElapsed = 0.f;
|
||||
bAnimatingSecondary = false;
|
||||
|
||||
ApplyBrushStyle();
|
||||
EnsureMIDs();
|
||||
ApplyMaterialOverrides();
|
||||
ApplyPrimaryToMID(CurrentPrimaryLevel);
|
||||
ApplySecondaryToMID(CurrentSecondaryLevel);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
ApplyBrushStyle();
|
||||
EnsureMIDs();
|
||||
ApplyMaterialOverrides();
|
||||
ApplyPrimaryToMID(CurrentPrimaryLevel);
|
||||
ApplySecondaryToMID(CurrentSecondaryLevel);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
|
||||
{
|
||||
Super::NativeTick(MyGeometry, InDeltaTime);
|
||||
|
||||
if (!bAnimatingPrimary && !bAnimatingSecondary)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (bAnimatingPrimary)
|
||||
{
|
||||
PrimaryElapsed += InDeltaTime;
|
||||
const float Duration = FMath::Max(PrimaryAnimationSeconds, MinAnimSeconds);
|
||||
const float Alpha = FMath::Clamp(PrimaryElapsed / Duration, 0.f, 1.f);
|
||||
CurrentPrimaryLevel = FMath::Lerp(PrimaryStart, TargetPrimaryLevel, EaseOutCubic(Alpha));
|
||||
ApplyPrimaryToMID(CurrentPrimaryLevel);
|
||||
if (Alpha >= 1.f)
|
||||
{
|
||||
CurrentPrimaryLevel = TargetPrimaryLevel;
|
||||
ApplyPrimaryToMID(CurrentPrimaryLevel);
|
||||
bAnimatingPrimary = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (bAnimatingSecondary)
|
||||
{
|
||||
SecondaryElapsed += InDeltaTime;
|
||||
const float Duration = FMath::Max(SecondaryAnimationSeconds, MinAnimSeconds);
|
||||
const float Alpha = FMath::Clamp(SecondaryElapsed / Duration, 0.f, 1.f);
|
||||
CurrentSecondaryLevel = FMath::Lerp(SecondaryStart, TargetSecondaryLevel, EaseOutCubic(Alpha));
|
||||
ApplySecondaryToMID(CurrentSecondaryLevel);
|
||||
if (Alpha >= 1.f)
|
||||
{
|
||||
CurrentSecondaryLevel = TargetSecondaryLevel;
|
||||
ApplySecondaryToMID(CurrentSecondaryLevel);
|
||||
bAnimatingSecondary = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetTargetPrimaryLevel(float Primary01)
|
||||
{
|
||||
const float Clamped = FMath::Clamp(Primary01, 0.f, 1.f);
|
||||
if (PrimaryAnimationSeconds <= MinAnimSeconds)
|
||||
{
|
||||
SetPrimaryLevelImmediate(Clamped);
|
||||
return;
|
||||
}
|
||||
if (FMath::IsNearlyEqual(Clamped, CurrentPrimaryLevel, KINDA_SMALL_NUMBER) &&
|
||||
FMath::IsNearlyEqual(Clamped, TargetPrimaryLevel, KINDA_SMALL_NUMBER))
|
||||
{
|
||||
return;
|
||||
}
|
||||
TargetPrimaryLevel = Clamped;
|
||||
PrimaryStart = CurrentPrimaryLevel;
|
||||
PrimaryElapsed = 0.f;
|
||||
bAnimatingPrimary = true;
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetPrimaryLevelImmediate(float Primary01)
|
||||
{
|
||||
const float Clamped = FMath::Clamp(Primary01, 0.f, 1.f);
|
||||
CurrentPrimaryLevel = Clamped;
|
||||
TargetPrimaryLevel = Clamped;
|
||||
PrimaryStart = Clamped;
|
||||
PrimaryElapsed = 0.f;
|
||||
bAnimatingPrimary = false;
|
||||
ApplyPrimaryToMID(CurrentPrimaryLevel);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetTargetSecondaryLevel(float Sec01)
|
||||
{
|
||||
const float Clamped = FMath::Clamp(Sec01, 0.f, 1.f);
|
||||
if (SecondaryAnimationSeconds <= MinAnimSeconds)
|
||||
{
|
||||
SetSecondaryLevelImmediate(Clamped);
|
||||
return;
|
||||
}
|
||||
if (FMath::IsNearlyEqual(Clamped, CurrentSecondaryLevel, KINDA_SMALL_NUMBER) &&
|
||||
FMath::IsNearlyEqual(Clamped, TargetSecondaryLevel, KINDA_SMALL_NUMBER))
|
||||
{
|
||||
return;
|
||||
}
|
||||
TargetSecondaryLevel = Clamped;
|
||||
SecondaryStart = CurrentSecondaryLevel;
|
||||
SecondaryElapsed = 0.f;
|
||||
bAnimatingSecondary = true;
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetSecondaryLevelImmediate(float Sec01)
|
||||
{
|
||||
const float Clamped = FMath::Clamp(Sec01, 0.f, 1.f);
|
||||
CurrentSecondaryLevel = Clamped;
|
||||
TargetSecondaryLevel = Clamped;
|
||||
SecondaryStart = Clamped;
|
||||
SecondaryElapsed = 0.f;
|
||||
bAnimatingSecondary = false;
|
||||
ApplySecondaryToMID(CurrentSecondaryLevel);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetDrawStyle(EUIProgressBarStyle InStyle)
|
||||
{
|
||||
DrawStyle = InStyle;
|
||||
ApplyBrushStyle();
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetCornerRadius(float RadiusPixels)
|
||||
{
|
||||
CornerRadius = FMath::Clamp(RadiusPixels, 0.f, 100.f);
|
||||
ApplyBrushStyle();
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetBorderVisible(bool bVisible)
|
||||
{
|
||||
bShowBorder = bVisible;
|
||||
ApplyBrushStyle();
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetBorderColour(FLinearColor Color)
|
||||
{
|
||||
BorderColour = Color;
|
||||
bShowBorder = true;
|
||||
ApplyBrushStyle();
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetBorderWidth(float WidthPixels)
|
||||
{
|
||||
BorderWidth = FMath::Clamp(WidthPixels, 0.f, 10.f);
|
||||
ApplyBrushStyle();
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetPrimaryColour(FLinearColor Color)
|
||||
{
|
||||
PrimaryColour = Color;
|
||||
bOverridePrimaryColour = true;
|
||||
if (FillMID) FillMID->SetVectorParameterValue(P_PrimaryColour, Color);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetPrimaryColourBottom(FLinearColor Color)
|
||||
{
|
||||
PrimaryColourBottom = Color;
|
||||
bOverridePrimaryColourBottom = true;
|
||||
if (FillMID) FillMID->SetVectorParameterValue(P_PrimaryColourBottom, Color);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetSecondaryColour(FLinearColor Color)
|
||||
{
|
||||
SecondaryColour = Color;
|
||||
bOverrideSecondaryColour = true;
|
||||
if (FillMID) FillMID->SetVectorParameterValue(P_SecondaryColour, Color);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetSecondaryColourBottom(FLinearColor Color)
|
||||
{
|
||||
SecondaryColourBottom = Color;
|
||||
bOverrideSecondaryColourBottom = true;
|
||||
if (FillMID) FillMID->SetVectorParameterValue(P_SecondaryColourBottom, Color);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetEmptyColour(FLinearColor Color)
|
||||
{
|
||||
EmptyColour = Color;
|
||||
bOverrideEmptyColour = true;
|
||||
if (FillMID) FillMID->SetVectorParameterValue(P_EmptyColour, Color);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetBackgroundColour(FLinearColor Color)
|
||||
{
|
||||
BackgroundColour = Color;
|
||||
bOverrideBackgroundColour = true;
|
||||
if (BackgroundMID) BackgroundMID->SetVectorParameterValue(P_BackgroundColour, Color);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetTrackGridVisible(bool bVisible)
|
||||
{
|
||||
bShowTrackGrid = bVisible;
|
||||
if (TrackGridImage)
|
||||
{
|
||||
TrackGridImage->SetVisibility(bVisible ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetGridColour(FLinearColor Color)
|
||||
{
|
||||
GridColour = Color;
|
||||
bOverrideGridColour = true;
|
||||
if (TrackGridMID) TrackGridMID->SetVectorParameterValue(P_GridColour, Color);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetGridDivisions(float Divisions)
|
||||
{
|
||||
GridDivisions = FMath::Clamp(Divisions, 2.f, 50.f);
|
||||
bOverrideGridDivisions = true;
|
||||
if (TrackGridMID) TrackGridMID->SetScalarParameterValue(P_GridDivisions, GridDivisions);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetGridLineThickness(float Thickness)
|
||||
{
|
||||
GridLineThickness = FMath::Clamp(Thickness, 0.001f, 0.2f);
|
||||
bOverrideGridLineThickness = true;
|
||||
if (TrackGridMID) TrackGridMID->SetScalarParameterValue(P_GridLineThickness, GridLineThickness);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetEdgeVisible(bool bVisible)
|
||||
{
|
||||
bShowEdge = bVisible;
|
||||
UpdateEdgeVisibility();
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetEdgeColour(FLinearColor Color)
|
||||
{
|
||||
EdgeColour = Color;
|
||||
bOverrideEdgeColour = true;
|
||||
if (EdgeMID) EdgeMID->SetVectorParameterValue(P_EdgeColour, Color);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetEdgeWidth(float Width01)
|
||||
{
|
||||
EdgeWidth = FMath::Clamp(Width01, 0.001f, 0.1f);
|
||||
bOverrideEdgeWidth = true;
|
||||
if (EdgeMID) EdgeMID->SetScalarParameterValue(P_EdgeWidth, EdgeWidth);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetEdgeGlowWidth(float GlowWidth01)
|
||||
{
|
||||
EdgeGlowWidth = FMath::Clamp(GlowWidth01, 0.f, 0.2f);
|
||||
bOverrideEdgeGlowWidth = true;
|
||||
if (EdgeMID) EdgeMID->SetScalarParameterValue(P_EdgeGlowWidth, EdgeGlowWidth);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetEdgeGlowIntensity(float Intensity01)
|
||||
{
|
||||
EdgeGlowIntensity = FMath::Clamp(Intensity01, 0.f, 1.f);
|
||||
bOverrideEdgeGlowIntensity = true;
|
||||
if (EdgeMID) EdgeMID->SetScalarParameterValue(P_EdgeGlowIntensity, EdgeGlowIntensity);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetShimmerVisible(bool bVisible)
|
||||
{
|
||||
bShowShimmer = bVisible;
|
||||
if (ShimmerImage)
|
||||
{
|
||||
ShimmerImage->SetVisibility(bVisible ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetShimmerColour(FLinearColor Color)
|
||||
{
|
||||
ShimmerColour = Color;
|
||||
bOverrideShimmerColour = true;
|
||||
if (ShimmerMID) ShimmerMID->SetVectorParameterValue(P_ShimmerColour, Color);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetShimmerWidth(float Width01)
|
||||
{
|
||||
ShimmerWidth = FMath::Clamp(Width01, 0.05f, 1.f);
|
||||
bOverrideShimmerWidth = true;
|
||||
if (ShimmerMID) ShimmerMID->SetScalarParameterValue(P_ShimmerWidth, ShimmerWidth);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetShimmerSpeed(float Speed)
|
||||
{
|
||||
ShimmerSpeed = FMath::Clamp(Speed, 0.01f, 5.f);
|
||||
bOverrideShimmerSpeed = true;
|
||||
if (ShimmerMID) ShimmerMID->SetScalarParameterValue(P_ShimmerSpeed, ShimmerSpeed);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetShimmerSoftness(float Softness)
|
||||
{
|
||||
ShimmerSoftness = FMath::Clamp(Softness, 0.5f, 8.f);
|
||||
bOverrideShimmerSoftness = true;
|
||||
if (ShimmerMID) ShimmerMID->SetScalarParameterValue(P_ShimmerSoftness, ShimmerSoftness);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetGradientEnabled(bool bEnabled)
|
||||
{
|
||||
bEnableGradient = bEnabled;
|
||||
bOverrideEnableGradient = true;
|
||||
if (FillMID) FillMID->SetScalarParameterValue(P_EnableGradient, bEnabled ? 1.f : 0.f);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::SetGradientSplit(float Split01)
|
||||
{
|
||||
GradientSplit = FMath::Clamp(Split01, 0.f, 1.f);
|
||||
bOverrideGradientSplit = true;
|
||||
if (FillMID) FillMID->SetScalarParameterValue(P_GradientSplit, GradientSplit);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::ShowHealPreview(float TargetPct, FLinearColor PreviewColor)
|
||||
{
|
||||
SetSecondaryColour(PreviewColor);
|
||||
SetTargetSecondaryLevel(TargetPct);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::ShowDamageLag(float OldPct, float NewPct, FLinearColor LagColor)
|
||||
{
|
||||
SetSecondaryColour(LagColor);
|
||||
SetSecondaryLevelImmediate(OldPct);
|
||||
SetPrimaryLevelImmediate(NewPct);
|
||||
SetTargetSecondaryLevel(NewPct);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::ShowBuffer(float BufferPct, FLinearColor BufferColor)
|
||||
{
|
||||
SetSecondaryColour(BufferColor);
|
||||
SetTargetSecondaryLevel(FMath::Clamp(CurrentPrimaryLevel + BufferPct, 0.f, 1.f));
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::ClearSecondary()
|
||||
{
|
||||
SetTargetSecondaryLevel(0.f);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::EnsureMIDs()
|
||||
{
|
||||
if (FillImage && !FillMID)
|
||||
{
|
||||
if (UMaterialInterface* Mat = Cast<UMaterialInterface>(FillImage->GetBrush().GetResourceObject()))
|
||||
{
|
||||
FillMID = UMaterialInstanceDynamic::Create(Mat, this);
|
||||
FillImage->SetBrushFromMaterial(FillMID);
|
||||
// SetBrushFromMaterial reseta DrawAs e OutlineSettings — re-aplica.
|
||||
ApplyBrushStyle();
|
||||
}
|
||||
}
|
||||
if (BackgroundImage && !BackgroundMID)
|
||||
{
|
||||
if (UMaterialInterface* Mat = Cast<UMaterialInterface>(BackgroundImage->GetBrush().GetResourceObject()))
|
||||
{
|
||||
BackgroundMID = UMaterialInstanceDynamic::Create(Mat, this);
|
||||
BackgroundImage->SetBrushFromMaterial(BackgroundMID);
|
||||
ApplyBrushStyle();
|
||||
}
|
||||
}
|
||||
if (TrackGridImage && !TrackGridMID)
|
||||
{
|
||||
if (UMaterialInterface* Mat = Cast<UMaterialInterface>(TrackGridImage->GetBrush().GetResourceObject()))
|
||||
{
|
||||
TrackGridMID = UMaterialInstanceDynamic::Create(Mat, this);
|
||||
TrackGridImage->SetBrushFromMaterial(TrackGridMID);
|
||||
ApplyBrushStyle();
|
||||
}
|
||||
}
|
||||
if (ShimmerImage && !ShimmerMID)
|
||||
{
|
||||
if (UMaterialInterface* Mat = Cast<UMaterialInterface>(ShimmerImage->GetBrush().GetResourceObject()))
|
||||
{
|
||||
ShimmerMID = UMaterialInstanceDynamic::Create(Mat, this);
|
||||
ShimmerImage->SetBrushFromMaterial(ShimmerMID);
|
||||
ApplyBrushStyle();
|
||||
}
|
||||
}
|
||||
if (EdgeImage && !EdgeMID)
|
||||
{
|
||||
if (UMaterialInterface* Mat = Cast<UMaterialInterface>(EdgeImage->GetBrush().GetResourceObject()))
|
||||
{
|
||||
EdgeMID = UMaterialInstanceDynamic::Create(Mat, this);
|
||||
EdgeImage->SetBrushFromMaterial(EdgeMID);
|
||||
ApplyBrushStyle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::ApplyMaterialOverrides()
|
||||
{
|
||||
if (FillMID)
|
||||
{
|
||||
if (bOverridePrimaryColour) FillMID->SetVectorParameterValue(P_PrimaryColour, PrimaryColour);
|
||||
if (bOverridePrimaryColourBottom) FillMID->SetVectorParameterValue(P_PrimaryColourBottom, PrimaryColourBottom);
|
||||
if (bOverrideSecondaryColour) FillMID->SetVectorParameterValue(P_SecondaryColour, SecondaryColour);
|
||||
if (bOverrideSecondaryColourBottom) FillMID->SetVectorParameterValue(P_SecondaryColourBottom, SecondaryColourBottom);
|
||||
if (bOverrideEmptyColour) FillMID->SetVectorParameterValue(P_EmptyColour, EmptyColour);
|
||||
if (bOverrideEnableGradient) FillMID->SetScalarParameterValue(P_EnableGradient, bEnableGradient ? 1.f : 0.f);
|
||||
if (bOverrideGradientSplit) FillMID->SetScalarParameterValue(P_GradientSplit, GradientSplit);
|
||||
}
|
||||
if (BackgroundMID)
|
||||
{
|
||||
if (bOverrideBackgroundColour) BackgroundMID->SetVectorParameterValue(P_BackgroundColour, BackgroundColour);
|
||||
}
|
||||
if (TrackGridMID)
|
||||
{
|
||||
if (bOverrideGridColour) TrackGridMID->SetVectorParameterValue(P_GridColour, GridColour);
|
||||
if (bOverrideGridDivisions) TrackGridMID->SetScalarParameterValue(P_GridDivisions, GridDivisions);
|
||||
if (bOverrideGridLineThickness) TrackGridMID->SetScalarParameterValue(P_GridLineThickness, GridLineThickness);
|
||||
}
|
||||
|
||||
if (ShimmerMID)
|
||||
{
|
||||
if (bOverrideShimmerColour) ShimmerMID->SetVectorParameterValue(P_ShimmerColour, ShimmerColour);
|
||||
if (bOverrideShimmerWidth) ShimmerMID->SetScalarParameterValue(P_ShimmerWidth, ShimmerWidth);
|
||||
if (bOverrideShimmerSpeed) ShimmerMID->SetScalarParameterValue(P_ShimmerSpeed, ShimmerSpeed);
|
||||
if (bOverrideShimmerSoftness) ShimmerMID->SetScalarParameterValue(P_ShimmerSoftness, ShimmerSoftness);
|
||||
}
|
||||
if (EdgeMID)
|
||||
{
|
||||
if (bOverrideEdgeColour) EdgeMID->SetVectorParameterValue(P_EdgeColour, EdgeColour);
|
||||
if (bOverrideEdgeWidth) EdgeMID->SetScalarParameterValue(P_EdgeWidth, EdgeWidth);
|
||||
if (bOverrideEdgeGlowWidth) EdgeMID->SetScalarParameterValue(P_EdgeGlowWidth, EdgeGlowWidth);
|
||||
if (bOverrideEdgeGlowIntensity) EdgeMID->SetScalarParameterValue(P_EdgeGlowIntensity, EdgeGlowIntensity);
|
||||
}
|
||||
|
||||
// Visibilidade dos widgets opcionais
|
||||
if (TrackGridImage)
|
||||
{
|
||||
TrackGridImage->SetVisibility(bShowTrackGrid ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed);
|
||||
}
|
||||
UpdateEdgeVisibility();
|
||||
if (ShimmerImage)
|
||||
{
|
||||
ShimmerImage->SetVisibility(bShowShimmer ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::ApplyBrushStyle()
|
||||
{
|
||||
const float InsetForShimmer = bShowBorder ? FMath::Max(BorderWidth, 0.f) : 0.f;
|
||||
|
||||
auto Apply = [this](UImage* Img, bool bAllowBorder, float RadiusInset)
|
||||
{
|
||||
if (!Img)
|
||||
{
|
||||
return;
|
||||
}
|
||||
FSlateBrush B = Img->GetBrush();
|
||||
B.DrawAs = (DrawStyle == EUIProgressBarStyle::Rounded)
|
||||
? ESlateBrushDrawType::RoundedBox
|
||||
: ESlateBrushDrawType::Image;
|
||||
|
||||
FSlateBrushOutlineSettings Outline = B.OutlineSettings;
|
||||
const float R = FMath::Max(CornerRadius - RadiusInset, 0.f);
|
||||
Outline.CornerRadii = FVector4(R, R, R, R);
|
||||
Outline.RoundingType = ESlateBrushRoundingType::FixedRadius;
|
||||
|
||||
if (bAllowBorder && bShowBorder)
|
||||
{
|
||||
Outline.Color = FSlateColor(BorderColour);
|
||||
Outline.Width = FMath::Max(BorderWidth, 0.f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Outline.Color = FSlateColor(FLinearColor::Transparent);
|
||||
Outline.Width = 0.f;
|
||||
}
|
||||
B.OutlineSettings = Outline;
|
||||
|
||||
Img->SetBrush(B);
|
||||
};
|
||||
|
||||
// Borda só no BG. Shimmer ganha inset = BorderWidth (padding no slot + corner reduzido)
|
||||
// pra não passar por cima do outline do BG. Demais widgets sem inset.
|
||||
Apply(BackgroundImage, /*bAllowBorder=*/true, 0.f);
|
||||
Apply(TrackGridImage, /*bAllowBorder=*/false, 0.f);
|
||||
Apply(FillImage, /*bAllowBorder=*/false, 0.f);
|
||||
Apply(EdgeImage, /*bAllowBorder=*/false, 0.f);
|
||||
Apply(ShimmerImage, /*bAllowBorder=*/false, InsetForShimmer);
|
||||
|
||||
if (ShimmerImage)
|
||||
{
|
||||
if (UOverlaySlot* OS = Cast<UOverlaySlot>(ShimmerImage->Slot))
|
||||
{
|
||||
OS->SetPadding(FMargin(InsetForShimmer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::ApplyPrimaryToMID(float Value)
|
||||
{
|
||||
if (FillMID)
|
||||
{
|
||||
FillMID->SetScalarParameterValue(P_PrimaryLevel, Value);
|
||||
}
|
||||
// Edge acompanha o nível Primary (posição da linha = PrimaryLevel)
|
||||
if (EdgeMID)
|
||||
{
|
||||
EdgeMID->SetScalarParameterValue(P_PrimaryLevel, Value);
|
||||
}
|
||||
UpdateEdgeVisibility();
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::UpdateEdgeVisibility()
|
||||
{
|
||||
if (!EdgeImage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!bShowEdge)
|
||||
{
|
||||
EdgeImage->SetVisibility(ESlateVisibility::Collapsed);
|
||||
return;
|
||||
}
|
||||
// Auto-hide quando Primary está nos extremos (0 ou 1) — Edge encostaria na borda.
|
||||
const bool bAtExtreme = (CurrentPrimaryLevel <= 0.f) || (CurrentPrimaryLevel >= 1.f);
|
||||
EdgeImage->SetVisibility(bAtExtreme ? ESlateVisibility::Collapsed : ESlateVisibility::HitTestInvisible);
|
||||
}
|
||||
|
||||
void UUIProgressBar_Base::ApplySecondaryToMID(float Value)
|
||||
{
|
||||
if (FillMID)
|
||||
{
|
||||
FillMID->SetScalarParameterValue(P_SecondaryLevel, Value);
|
||||
}
|
||||
}
|
||||
438
Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.h
Normal file
438
Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.h
Normal file
@@ -0,0 +1,438 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonUserWidget.h"
|
||||
#include "UIProgressBar_Base.generated.h"
|
||||
|
||||
class UImage;
|
||||
class UMaterialInstanceDynamic;
|
||||
|
||||
/** Preset que controla Brush.DrawAs (Image=Box, RoundedBox=Rounded) em ambos os widgets. */
|
||||
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"),
|
||||
};
|
||||
|
||||
/**
|
||||
* ProgressBar do ZMMO. Wrappa um UImage (FillImage) + UImage de background
|
||||
* (BackgroundImage) com material custom. O arredondamento dos cantos usa
|
||||
* o RoundedBox nativo do Slate (Brush.DrawAs + OutlineSettings.CornerRadii)
|
||||
* pra ficar pixel-perfect e independente do aspect ratio.
|
||||
*
|
||||
* Modelo assimetrico de niveis: SecondaryLevel so aparece visualmente se
|
||||
* for MAIOR que PrimaryLevel — representa heal preview, damage trail,
|
||||
* buffer empilhado em cima da barra principal.
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable)
|
||||
class ZMMO_API UUIProgressBar_Base : public UCommonUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// === Levels ===
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar",
|
||||
meta = (ClampMin = "0", ClampMax = "1"))
|
||||
float InitialPrimaryLevel = 1.f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar",
|
||||
meta = (ClampMin = "0", ClampMax = "1"))
|
||||
float InitialSecondaryLevel = 0.f;
|
||||
|
||||
// === Animation ===
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Animation",
|
||||
meta = (ClampMin = "0", ClampMax = "5"))
|
||||
float PrimaryAnimationSeconds = 0.25f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Animation",
|
||||
meta = (ClampMin = "0", ClampMax = "5"))
|
||||
float SecondaryAnimationSeconds = 0.8f;
|
||||
|
||||
// === Draw Style ===
|
||||
|
||||
/** Box=DrawAs Image (sem corner), Rounded=DrawAs RoundedBox (cantos arredondados). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Style")
|
||||
EUIProgressBarStyle DrawStyle = EUIProgressBarStyle::Box;
|
||||
|
||||
/**
|
||||
* Raio dos cantos em PIXELS. Aplicado quando DrawStyle = Rounded.
|
||||
* Se RoundingType = HalfHeightRadius (default do Slate), o Slate clampeia
|
||||
* automaticamente em metade da altura — ou seja, valor maior que isso vira pill.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Style",
|
||||
meta = (ClampMin = "0", ClampMax = "100"))
|
||||
float CornerRadius = 8.f;
|
||||
|
||||
/** Cor do outline em volta do Background (Slate OutlineSettings.Color). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Style",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bShowBorder = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Style",
|
||||
meta = (EditCondition = "bShowBorder"))
|
||||
FLinearColor BorderColour = FLinearColor(0.f, 0.f, 0.f, 0.8f);
|
||||
|
||||
/** Espessura do outline em pixels. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Style",
|
||||
meta = (EditCondition = "bShowBorder", ClampMin = "0", ClampMax = "10"))
|
||||
float BorderWidth = 1.f;
|
||||
|
||||
// === Color overrides (Fill material) ===
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverridePrimaryColour = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors",
|
||||
meta = (EditCondition = "bOverridePrimaryColour"))
|
||||
FLinearColor PrimaryColour = FLinearColor(0.957f, 0.769f, 0.353f, 1.f);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverridePrimaryColourBottom = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors",
|
||||
meta = (EditCondition = "bOverridePrimaryColourBottom"))
|
||||
FLinearColor PrimaryColourBottom = FLinearColor(0.478f, 0.282f, 0.094f, 1.f);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideSecondaryColour = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors",
|
||||
meta = (EditCondition = "bOverrideSecondaryColour"))
|
||||
FLinearColor SecondaryColour = FLinearColor(0.246201f, 0.002428f, 0.002125f, 1.f);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideSecondaryColourBottom = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors",
|
||||
meta = (EditCondition = "bOverrideSecondaryColourBottom"))
|
||||
FLinearColor SecondaryColourBottom = FLinearColor(0.123f, 0.001f, 0.001f, 1.f);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideEmptyColour = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors",
|
||||
meta = (EditCondition = "bOverrideEmptyColour"))
|
||||
FLinearColor EmptyColour = FLinearColor(0.f, 0.f, 0.f, 0.6f);
|
||||
|
||||
// === Background override ===
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Background",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideBackgroundColour = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Background",
|
||||
meta = (EditCondition = "bOverrideBackgroundColour"))
|
||||
FLinearColor BackgroundColour = FLinearColor(0.f, 0.f, 0.f, 0.6f);
|
||||
|
||||
// === Track Grid (efeito de listras verticais sobre o BG) ===
|
||||
|
||||
/** Liga/desliga a renderização do TrackGridImage. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|TrackGrid")
|
||||
bool bShowTrackGrid = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|TrackGrid",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideGridColour = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|TrackGrid",
|
||||
meta = (EditCondition = "bOverrideGridColour"))
|
||||
FLinearColor GridColour = FLinearColor(1.f, 1.f, 1.f, 0.05f);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|TrackGrid",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideGridDivisions = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|TrackGrid",
|
||||
meta = (EditCondition = "bOverrideGridDivisions", ClampMin = "2", ClampMax = "50"))
|
||||
float GridDivisions = 10.f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|TrackGrid",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideGridLineThickness = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|TrackGrid",
|
||||
meta = (EditCondition = "bOverrideGridLineThickness", ClampMin = "0.001", ClampMax = "0.2"))
|
||||
float GridLineThickness = 0.02f;
|
||||
|
||||
// === Edge (linha vertical no fim do Primary) ===
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Edge")
|
||||
bool bShowEdge = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Edge",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideEdgeColour = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Edge",
|
||||
meta = (EditCondition = "bOverrideEdgeColour"))
|
||||
FLinearColor EdgeColour = FLinearColor(1.f, 0.94f, 0.78f, 0.85f);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Edge",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideEdgeWidth = false;
|
||||
|
||||
/** Largura da linha em UV (fração da barra). 0.005 ≈ 2px em barra de 400px. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Edge",
|
||||
meta = (EditCondition = "bOverrideEdgeWidth", ClampMin = "0.001", ClampMax = "0.1"))
|
||||
float EdgeWidth = 0.005f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Edge",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideEdgeGlowWidth = false;
|
||||
|
||||
/** Largura do halo lateral em UV. 0 = sem glow. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Edge",
|
||||
meta = (EditCondition = "bOverrideEdgeGlowWidth", ClampMin = "0", ClampMax = "0.2"))
|
||||
float EdgeGlowWidth = 0.02f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Edge",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideEdgeGlowIntensity = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Edge",
|
||||
meta = (EditCondition = "bOverrideEdgeGlowIntensity", ClampMin = "0", ClampMax = "1"))
|
||||
float EdgeGlowIntensity = 0.5f;
|
||||
|
||||
// === Shimmer (faixa de luz animada por cima do Fill) ===
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Shimmer")
|
||||
bool bShowShimmer = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Shimmer",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideShimmerColour = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Shimmer",
|
||||
meta = (EditCondition = "bOverrideShimmerColour"))
|
||||
FLinearColor ShimmerColour = FLinearColor(1.f, 0.94f, 0.78f, 0.35f);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Shimmer",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideShimmerWidth = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Shimmer",
|
||||
meta = (EditCondition = "bOverrideShimmerWidth", ClampMin = "0.05", ClampMax = "1.0"))
|
||||
float ShimmerWidth = 0.2f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Shimmer",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideShimmerSpeed = false;
|
||||
|
||||
/** Ciclos por segundo. 0.4545 = ciclo de 2.2s (default CSS). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Shimmer",
|
||||
meta = (EditCondition = "bOverrideShimmerSpeed", ClampMin = "0.01", ClampMax = "5.0"))
|
||||
float ShimmerSpeed = 0.4545f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Shimmer",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideShimmerSoftness = false;
|
||||
|
||||
/** Expoente da bell curve. Maior = pico mais fino. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Shimmer",
|
||||
meta = (EditCondition = "bOverrideShimmerSoftness", ClampMin = "0.5", ClampMax = "8.0"))
|
||||
float ShimmerSoftness = 2.f;
|
||||
|
||||
// === Gradient overrides ===
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Gradient",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideEnableGradient = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Gradient",
|
||||
meta = (EditCondition = "bOverrideEnableGradient"))
|
||||
bool bEnableGradient = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Gradient",
|
||||
meta = (InlineEditConditionToggle))
|
||||
bool bOverrideGradientSplit = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Gradient",
|
||||
meta = (EditCondition = "bOverrideGradientSplit", ClampMin = "0", ClampMax = "1"))
|
||||
float GradientSplit = 0.5f;
|
||||
|
||||
// === Primary API ===
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar")
|
||||
void SetTargetPrimaryLevel(float Primary01);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar")
|
||||
void SetPrimaryLevelImmediate(float Primary01);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "ProgressBar")
|
||||
float GetCurrentPrimaryLevel() const { return CurrentPrimaryLevel; }
|
||||
|
||||
// === Secondary API ===
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar")
|
||||
void SetTargetSecondaryLevel(float Sec01);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar")
|
||||
void SetSecondaryLevelImmediate(float Sec01);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "ProgressBar")
|
||||
float GetCurrentSecondaryLevel() const { return CurrentSecondaryLevel; }
|
||||
|
||||
// === Style API ===
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Style")
|
||||
void SetDrawStyle(EUIProgressBarStyle InStyle);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Style")
|
||||
void SetCornerRadius(float RadiusPixels);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Style")
|
||||
void SetBorderVisible(bool bVisible);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Style")
|
||||
void SetBorderColour(FLinearColor Color);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Style")
|
||||
void SetBorderWidth(float WidthPixels);
|
||||
|
||||
// === Color setters (Fill MID) ===
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors")
|
||||
void SetPrimaryColour(FLinearColor Color);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors")
|
||||
void SetPrimaryColourBottom(FLinearColor Color);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors")
|
||||
void SetSecondaryColour(FLinearColor Color);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors")
|
||||
void SetSecondaryColourBottom(FLinearColor Color);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors")
|
||||
void SetEmptyColour(FLinearColor Color);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Background")
|
||||
void SetBackgroundColour(FLinearColor Color);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|TrackGrid")
|
||||
void SetTrackGridVisible(bool bVisible);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|TrackGrid")
|
||||
void SetGridColour(FLinearColor Color);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|TrackGrid")
|
||||
void SetGridDivisions(float Divisions);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|TrackGrid")
|
||||
void SetGridLineThickness(float Thickness);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Edge")
|
||||
void SetEdgeVisible(bool bVisible);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Edge")
|
||||
void SetEdgeColour(FLinearColor Color);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Edge")
|
||||
void SetEdgeWidth(float Width01);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Edge")
|
||||
void SetEdgeGlowWidth(float GlowWidth01);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Edge")
|
||||
void SetEdgeGlowIntensity(float Intensity01);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Shimmer")
|
||||
void SetShimmerVisible(bool bVisible);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Shimmer")
|
||||
void SetShimmerColour(FLinearColor Color);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Shimmer")
|
||||
void SetShimmerWidth(float Width01);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Shimmer")
|
||||
void SetShimmerSpeed(float Speed);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Shimmer")
|
||||
void SetShimmerSoftness(float Softness);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Gradient")
|
||||
void SetGradientEnabled(bool bEnabled);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Gradient")
|
||||
void SetGradientSplit(float Split01);
|
||||
|
||||
// === Wrappers semânticos ===
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Effects")
|
||||
void ShowHealPreview(float TargetPct, FLinearColor PreviewColor);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Effects")
|
||||
void ShowDamageLag(float OldPct, float NewPct, FLinearColor LagColor);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Effects")
|
||||
void ShowBuffer(float BufferPct, FLinearColor BufferColor);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "ProgressBar|Effects")
|
||||
void ClearSecondary();
|
||||
|
||||
protected:
|
||||
virtual void NativePreConstruct() override;
|
||||
virtual void NativeConstruct() override;
|
||||
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UImage> FillImage;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UImage> BackgroundImage;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UImage> TrackGridImage;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UImage> EdgeImage;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UImage> ShimmerImage;
|
||||
|
||||
private:
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UMaterialInstanceDynamic> FillMID;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UMaterialInstanceDynamic> BackgroundMID;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UMaterialInstanceDynamic> TrackGridMID;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UMaterialInstanceDynamic> EdgeMID;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UMaterialInstanceDynamic> ShimmerMID;
|
||||
|
||||
void EnsureMIDs();
|
||||
void ApplyPrimaryToMID(float Value);
|
||||
void ApplySecondaryToMID(float Value);
|
||||
void ApplyMaterialOverrides();
|
||||
void ApplyBrushStyle();
|
||||
void UpdateEdgeVisibility();
|
||||
|
||||
float CurrentPrimaryLevel = 1.f;
|
||||
float TargetPrimaryLevel = 1.f;
|
||||
float PrimaryStart = 1.f;
|
||||
float PrimaryElapsed = 0.f;
|
||||
bool bAnimatingPrimary = false;
|
||||
|
||||
float CurrentSecondaryLevel = 0.f;
|
||||
float TargetSecondaryLevel = 0.f;
|
||||
float SecondaryStart = 0.f;
|
||||
float SecondaryElapsed = 0.f;
|
||||
bool bAnimatingSecondary = false;
|
||||
};
|
||||
@@ -37,7 +37,8 @@ namespace
|
||||
S.Money = P.Money;
|
||||
S.AtkBase = P.AtkBase;
|
||||
S.AtkEquipBonus = P.AtkEquipBonus;
|
||||
S.MatkBase = P.MatkBase;
|
||||
S.MatkBaseMin = P.MatkBaseMin;
|
||||
S.MatkBaseMax = P.MatkBaseMax;
|
||||
S.MatkEquipBonus = P.MatkEquipBonus;
|
||||
S.DefBase = P.DefBase;
|
||||
S.DefEquipBonus = P.DefEquipBonus;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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 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 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 "ZMMOJobDataAsset.h"
|
||||
#include "ZMMOJobsSubsystem.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -52,3 +53,37 @@ bool UZMMOJobsLibrary::IsJobRegistered(const UObject* WorldContextObject, int32
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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 "Engine/DataAsset.h"
|
||||
#include "ZMMOJobTypes.h"
|
||||
#include "ZMMOJobDataAsset.generated.h"
|
||||
|
||||
class UTexture2D;
|
||||
@@ -39,11 +40,28 @@ public:
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
|
||||
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.
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
|
||||
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.
|
||||
/// Soft ptr: nao carrega ate alguem chamar LoadSynchronous() ou async load.
|
||||
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 "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "ZMMOJobTypes.h"
|
||||
#include "ZMMOJobsLibrary.generated.h"
|
||||
|
||||
class UZMMOJobDataAsset;
|
||||
@@ -36,4 +37,26 @@ public:
|
||||
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs",
|
||||
meta = (WorldContext = "WorldContextObject"))
|
||||
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 "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "ZMMOJobTypes.h"
|
||||
#include "ZMMOJobsSubsystem.generated.h"
|
||||
|
||||
class UZMMOJobDataAsset;
|
||||
@@ -41,6 +42,22 @@ public:
|
||||
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs")
|
||||
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:
|
||||
/// Scan AssetRegistry + popula CachedJobs. Sincronizo (V1) — chamado UMA
|
||||
/// 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[] {
|
||||
"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).
|
||||
|
||||
Reference in New Issue
Block a user