Merge pull request 'feat(client): AttributeSystem (Fase 1-3) + ZMMOJobs + UI in-game' (#3) from feat/attribute-system-mmo into main
Reviewed-on: #3
This commit was merged in pull request #3.
This commit is contained in:
@@ -34,3 +34,26 @@ ScreenSetAsset=/Game/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.DA_FrontEndScreenSet
|
||||
; server (Server/ZeusServerEngine/Config/DataTables/maps_config.json) — o
|
||||
; cliente resolve `mapId -> ClientLevel/spawns` via FZMMOMapDef rows.
|
||||
MapsTableAsset=/Game/ZMMO/Data/World/DT_Maps.DT_Maps
|
||||
|
||||
; -----------------------------------------------------------------------------
|
||||
; UI in-game (PR 19+). Espelho do front-end pattern:
|
||||
; - ScreenSetAsset mapeia EZMMOInGameUIState -> WBP (Playing -> WBP_HUD,
|
||||
; StatusWindow -> WBP_StatusWindow, etc.)
|
||||
; - Subsystem orquestra: AZMMOHUD::BeginPlay chama StartInGame que vai pra
|
||||
; Playing.
|
||||
; Asset criado via MCP em /Game/ZMMO/UI/InGame/DA_InGameScreenSet.
|
||||
; -----------------------------------------------------------------------------
|
||||
[/Script/ZMMO.UIInGameFlowSubsystem]
|
||||
ScreenSetAsset=/Game/ZMMO/UI/InGame/DA_InGameScreenSet.DA_InGameScreenSet
|
||||
|
||||
; -----------------------------------------------------------------------------
|
||||
; PlayerState Component Registry (PR 19+). Cada módulo MMO registra seus
|
||||
; UActorComponent aqui. O AZMMOPlayerState lê esta lista no construtor e
|
||||
; instancia cada classe como subobject — pattern Open-Closed: adicionar
|
||||
; Inventory/Skills/Guild = mais uma linha aqui, sem editar AZMMOPlayerState.
|
||||
;
|
||||
; Convenção: usa o path /Script/<Module>.<ClassName> (sem o "U" do prefixo
|
||||
; C++; UClass resolve pelo nome curto).
|
||||
; -----------------------------------------------------------------------------
|
||||
[/Script/ZMMO.ZMMOPlayerState]
|
||||
+ComponentClasses=/Script/ZMMOAttributes.ZMMOAttributeComponent
|
||||
|
||||
BIN
Content/ZMMO/Data/Jobs/DA_Job_Novice.uasset
Normal file
BIN
Content/ZMMO/Data/Jobs/DA_Job_Novice.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/HUD/WBP_HUD.uasset
Normal file
BIN
Content/ZMMO/UI/HUD/WBP_HUD.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/HUD/WBP_HUD_HpSpBar.uasset
Normal file
BIN
Content/ZMMO/UI/HUD/WBP_HUD_HpSpBar.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/HUD/WBP_StatusWindow.uasset
Normal file
BIN
Content/ZMMO/UI/HUD/WBP_StatusWindow.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/InGame/DA_InGameScreenSet.uasset
Normal file
BIN
Content/ZMMO/UI/InGame/DA_InGameScreenSet.uasset
Normal file
Binary file not shown.
34
Source/ZMMO/Data/UI/InGameTypes.h
Normal file
34
Source/ZMMO/Data/UI/InGameTypes.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "InGameTypes.generated.h"
|
||||
|
||||
/**
|
||||
* Estados da UI in-game. Dirigidos pelo UUIInGameFlowSubsystem; cada estado
|
||||
* resolve uma tela (UCommonActivatableWidget) via DA_InGameScreenSet.
|
||||
*
|
||||
* Espelha simetricamente EZMMOFrontEndState (Data/UI/FrontEndTypes.h):
|
||||
* - Front-end usa estados pra Login/ServerSelect/Lobby (UI.Layer.Menu)
|
||||
* - In-game usa estados pra HUD/Status/Inventory/Skills (UI.Layer.Game + GameMenu)
|
||||
*
|
||||
* Convencao de camadas (resolvida pelo subsystem):
|
||||
* - Playing -> UI.Layer.Game (HUD principal)
|
||||
* - StatusWindow / Inventory /
|
||||
* SkillTree / EscapeMenu -> UI.Layer.GameMenu (sobreposicoes)
|
||||
* - Loading -> UI.Layer.Modal (overlay critico)
|
||||
*
|
||||
* `Playing` e' o estado base — quando in-game, sempre ha um HUD no
|
||||
* Layer.Game. Abrir Status/Inventory NAO substitui o HUD; empilha em
|
||||
* Layer.GameMenu por cima dele.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
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).
|
||||
Inventory, ///< (futuro) Bag/equipamentos.
|
||||
SkillTree, ///< (futuro) Arvore de skills.
|
||||
EscapeMenu, ///< (futuro) Pausa, settings, logout.
|
||||
Loading ///< Overlay de loading generico (transitorio).
|
||||
};
|
||||
@@ -1,12 +1,17 @@
|
||||
#include "ZMMOPlayerController.h"
|
||||
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "Components/InputComponent.h"
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "Framework/Commands/InputChord.h"
|
||||
#include "InputMappingContext.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "Widgets/Input/SVirtualJoystick.h"
|
||||
#include "ZMMO.h"
|
||||
#include "UI/InGame/UIInGameFlowSubsystem.h"
|
||||
#include "UI/InGameTypes.h"
|
||||
|
||||
AZMMOPlayerController::AZMMOPlayerController()
|
||||
{
|
||||
@@ -86,6 +91,31 @@ void AZMMOPlayerController::SetupInputComponent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hotkey global Alt+A -> toggle StatusWindow. Uso InputComponent legacy
|
||||
// (BindKey com FInputChord) pra nao depender de assets InputAction.
|
||||
// Migrar pra Enhanced Input (IA_OpenStatus, IA_OpenInventory, etc.) quando
|
||||
// vier outro menu (Inventory I, SkillTree K).
|
||||
if (InputComponent)
|
||||
{
|
||||
FInputKeyBinding& Binding = InputComponent->BindKey(
|
||||
FInputChord(EKeys::A, /*bShift*/ false, /*bCtrl*/ false, /*bAlt*/ true, /*bCmd*/ false),
|
||||
IE_Pressed,
|
||||
this, &AZMMOPlayerController::ToggleStatusWindow);
|
||||
Binding.bConsumeInput = true;
|
||||
Binding.bExecuteWhenPaused = true; // dispara mesmo com StatusWindow aberto (CommonUI menu mode)
|
||||
}
|
||||
}
|
||||
|
||||
void AZMMOPlayerController::ToggleStatusWindow()
|
||||
{
|
||||
if (UGameInstance* GI = GetGameInstance())
|
||||
{
|
||||
if (UUIInGameFlowSubsystem* Flow = GI->GetSubsystem<UUIInGameFlowSubsystem>())
|
||||
{
|
||||
Flow->ToggleScreen(EZMMOInGameUIState::StatusWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AZMMOPlayerController::ShouldUseTouchControls() const
|
||||
|
||||
@@ -47,4 +47,7 @@ protected:
|
||||
virtual void SetupInputComponent() override;
|
||||
|
||||
bool ShouldUseTouchControls() const;
|
||||
|
||||
/** Hotkey Alt+A → toggle StatusWindow via UUIInGameFlowSubsystem. */
|
||||
void ToggleStatusWindow();
|
||||
};
|
||||
|
||||
@@ -13,9 +13,11 @@
|
||||
#include "GameFramework/SpringArmComponent.h"
|
||||
#include "InputAction.h"
|
||||
#include "InputActionValue.h"
|
||||
#include "GameFramework/PlayerState.h"
|
||||
#include "Subsystems/WorldSubsystem.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "ZMMO.h"
|
||||
#include "ZMMOAttributeComponent.h"
|
||||
#include "ZMMOWorldSubsystem.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
#include "UI/FrontEnd/UIFrontEndFlowSubsystem.h"
|
||||
@@ -52,6 +54,9 @@ AZMMOPlayerCharacter::AZMMOPlayerCharacter()
|
||||
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
|
||||
FollowCamera->bUsePawnControlRotation = false;
|
||||
|
||||
// AttributeComponent migrou pro PlayerState (AZMMOPlayerState + Component
|
||||
// Registry config-driven). Pawn fica leve — so' movement/input/camera.
|
||||
|
||||
// Defaults visuais (mesh + AnimBP) — alinhados ao ZClientMMO. Permitem ao
|
||||
// motor spawnar AZMMOPlayerCharacter directamente como DefaultPawnClass do
|
||||
// AZMMOGameMode sem exigir um BP filho. Um BP filho continua opcional para
|
||||
@@ -343,6 +348,24 @@ void AZMMOPlayerCharacter::HandleLocalSpawnReady(const int32 InEntityId, const F
|
||||
WorldSubsystem->RegisterLocalEntity(EntityIdAsInt64, this);
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (APlayerState* PS = GetPlayerState())
|
||||
{
|
||||
if (UZMMOAttributeComponent* AttrComp = PS->FindComponentByClass<UZMMOAttributeComponent>())
|
||||
{
|
||||
AttrComp->SeedEntityId(InEntityId);
|
||||
}
|
||||
}
|
||||
|
||||
// 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::FlushInputAxisToServer(const float DeltaSeconds)
|
||||
|
||||
@@ -47,6 +47,9 @@ class ZMMO_API AZMMOPlayerCharacter : public ACharacter, public IZMMOEntityInter
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
|
||||
UCameraComponent* FollowCamera;
|
||||
|
||||
// AttributeComponent vive no PlayerState (AZMMOPlayerState + Component Registry).
|
||||
// Acesso: GetPlayerState<AZMMOPlayerState>()->GetZMMOComponent<UZMMOAttributeComponent>().
|
||||
|
||||
protected:
|
||||
UPROPERTY(EditAnywhere, Category = "Input")
|
||||
UInputAction* JumpAction;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
#include "ZMMOGameMode.h"
|
||||
|
||||
#include "ZMMOHUD.h"
|
||||
#include "ZMMOPlayerCharacter.h"
|
||||
#include "ZMMOPlayerController.h"
|
||||
#include "ZMMOPlayerState.h"
|
||||
|
||||
AZMMOGameMode::AZMMOGameMode()
|
||||
{
|
||||
DefaultPawnClass = AZMMOPlayerCharacter::StaticClass();
|
||||
PlayerControllerClass = AZMMOPlayerController::StaticClass();
|
||||
PlayerStateClass = AZMMOPlayerState::StaticClass();
|
||||
HUDClass = AZMMOHUD::StaticClass();
|
||||
}
|
||||
|
||||
40
Source/ZMMO/Game/Modes/ZMMOHUD.cpp
Normal file
40
Source/ZMMO/Game/Modes/ZMMOHUD.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "ZMMOHUD.h"
|
||||
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "ZMMO.h"
|
||||
#include "UI/InGame/UIInGameFlowSubsystem.h"
|
||||
|
||||
AZMMOHUD::AZMMOHUD()
|
||||
{
|
||||
// AHUD nao precisa tick — toda atualizacao vem via UMG (delegates +
|
||||
// CommonUI). PrimaryActorTick default e' off; mantemos.
|
||||
}
|
||||
|
||||
void AZMMOHUD::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// UI in-game assume responsabilidade aqui. Subsystem orquestra:
|
||||
// resolve DA_InGameScreenSet[Playing] -> WBP_HUD, pushea em UI.Layer.Game.
|
||||
if (const UGameInstance* GI = GetGameInstance())
|
||||
{
|
||||
if (UUIInGameFlowSubsystem* Flow = GI->GetSubsystem<UUIInGameFlowSubsystem>())
|
||||
{
|
||||
Flow->StartInGame();
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning,
|
||||
TEXT("AZMMOHUD: UUIInGameFlowSubsystem nao encontrado — HUD nao sera spawnado"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AZMMOHUD::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
// UUIInGameFlowSubsystem persiste no GameInstance entre OpenLevel — nao
|
||||
// limpamos UI in-game aqui (o front-end flow vai tomar conta na proxima
|
||||
// transicao de estado). Cleanup do widget atual acontece no
|
||||
// NativeOnDeactivated quando o stack for esvaziado pelo flow.
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
36
Source/ZMMO/Game/Modes/ZMMOHUD.h
Normal file
36
Source/ZMMO/Game/Modes/ZMMOHUD.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/HUD.h"
|
||||
#include "ZMMOHUD.generated.h"
|
||||
|
||||
/**
|
||||
* HUD do MMO. Pattern UE5 idiomatico: o GameMode atribui `HUDClass`, o
|
||||
* engine cria 1 instancia por PlayerController automaticamente.
|
||||
*
|
||||
* Responsabilidade unica: orquestrar o lifecycle da UI in-game. Em
|
||||
* BeginPlay, pede ao `UUIInGameFlowSubsystem` pra entrar em `Playing`
|
||||
* (que pushea WBP_HUD em UI.Layer.Game). Em EndPlay/destrucao, o flow
|
||||
* subsystem cuida do cleanup.
|
||||
*
|
||||
* Separacao de responsabilidades:
|
||||
* - AZMMOPlayerCharacter (Pawn) — movimento, input, AttributeComponent
|
||||
* - AZMMOPlayerController — input mapping, mouse
|
||||
* - AZMMOHUD — UI lifecycle
|
||||
* - AZMMOGameMode — wires tudo via class slots
|
||||
*
|
||||
* NAO desenha nada via canvas (AHUD::DrawHUD). UI inteira vive em UMG +
|
||||
* CommonUI (UI.Layer.Game / GameMenu / Modal).
|
||||
*/
|
||||
UCLASS()
|
||||
class ZMMO_API AZMMOHUD : public AHUD
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
AZMMOHUD();
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
};
|
||||
82
Source/ZMMO/Game/Modes/ZMMOPlayerState.cpp
Normal file
82
Source/ZMMO/Game/Modes/ZMMOPlayerState.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "ZMMOPlayerState.h"
|
||||
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "Misc/ConfigCacheIni.h"
|
||||
#include "ZMMO.h"
|
||||
|
||||
AZMMOPlayerState::AZMMOPlayerState()
|
||||
{
|
||||
// Component Registry: instancia cada classe declarada em
|
||||
// DefaultGame.ini::[/Script/ZMMO.ZMMOPlayerState]+ComponentClasses=...
|
||||
//
|
||||
// Le manualmente via GConfig para evitar timing do UPROPERTY(Config),
|
||||
// que requer LoadConfig() ja ter rodado no CDO antes da instance ser
|
||||
// construida. Pra propriedades em arrays config com `+` syntax, lendo
|
||||
// direto e mais previsivel.
|
||||
if (ComponentClasses.Num() == 0 && GConfig)
|
||||
{
|
||||
TArray<FString> ClassPaths;
|
||||
GConfig->GetArray(TEXT("/Script/ZMMO.ZMMOPlayerState"),
|
||||
TEXT("ComponentClasses"),
|
||||
ClassPaths,
|
||||
GGameIni);
|
||||
for (const FString& Path : ClassPaths)
|
||||
{
|
||||
if (UClass* C = LoadClass<UActorComponent>(nullptr, *Path))
|
||||
{
|
||||
ComponentClasses.Add(C);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning,
|
||||
TEXT("ZMMOPlayerState: ComponentClasses path nao resolveu: %s"), *Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogZMMO, Log,
|
||||
TEXT("AZMMOPlayerState ctor: %d componente(s) no registry (instance=%s)"),
|
||||
ComponentClasses.Num(), *GetName());
|
||||
|
||||
int32 SubobjectIndex = 0;
|
||||
for (const TSubclassOf<UActorComponent>& CompClassRef : ComponentClasses)
|
||||
{
|
||||
UClass* CompClass = CompClassRef.Get();
|
||||
if (!CompClass)
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning,
|
||||
TEXT("ZMMOPlayerState: ComponentClass null em ComponentClasses[%d]"),
|
||||
SubobjectIndex);
|
||||
++SubobjectIndex;
|
||||
continue;
|
||||
}
|
||||
const FName CompName = MakeUniqueObjectName(this, CompClass,
|
||||
FName(*FString::Printf(TEXT("ZMMOComp_%d_%s"), SubobjectIndex, *CompClass->GetName())));
|
||||
// Overload runtime de CreateDefaultSubobject retorna UObject* (sem template) —
|
||||
// precisa cast para UActorComponent.
|
||||
UActorComponent* Comp = Cast<UActorComponent>(CreateDefaultSubobject(CompName, CompClass, CompClass,
|
||||
/*bIsRequired*/ false, /*bIsTransient*/ false));
|
||||
if (Comp)
|
||||
{
|
||||
UE_LOG(LogZMMO, Log,
|
||||
TEXT("ZMMOPlayerState: componente registrado [%d] %s"),
|
||||
SubobjectIndex, *CompClass->GetName());
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning,
|
||||
TEXT("ZMMOPlayerState: CreateDefaultSubobject falhou para %s"),
|
||||
*CompClass->GetName());
|
||||
}
|
||||
++SubobjectIndex;
|
||||
}
|
||||
}
|
||||
|
||||
void AZMMOPlayerState::SetPublicIdentity(int64 InEntityId, const FString& InCharId,
|
||||
int32 InBaseLevel, int32 InClassId)
|
||||
{
|
||||
ZMMOEntityId = InEntityId;
|
||||
CharId = InCharId;
|
||||
BaseLevel = InBaseLevel;
|
||||
ClassId = InClassId;
|
||||
}
|
||||
104
Source/ZMMO/Game/Modes/ZMMOPlayerState.h
Normal file
104
Source/ZMMO/Game/Modes/ZMMOPlayerState.h
Normal file
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/PlayerState.h"
|
||||
#include "Templates/SubclassOf.h"
|
||||
#include "ZMMOPlayerState.generated.h"
|
||||
|
||||
class UActorComponent;
|
||||
|
||||
/**
|
||||
* PlayerState do MMO. Pattern UE5 idiomatico: o GameMode atribui
|
||||
* `PlayerStateClass`, engine cria 1 instancia por PlayerController.
|
||||
*
|
||||
* Responsabilidade — estado **publico** do jogador (visivel pra outros):
|
||||
* - Identidade: charId, entityId, name, guild
|
||||
* - Progressao publica: baseLevel, classId
|
||||
* - Estado: alive/dead, in-combat (futuro)
|
||||
*
|
||||
* Dados PRIVADOS (HP/SP atuais, stats brutos, inventory, hotbar) vivem
|
||||
* em componentes anexados via Component Registry (vide abaixo).
|
||||
*
|
||||
* Por que componentes no PlayerState e nao no Pawn:
|
||||
* - **Sobrevive ao despawn** (morte/respawn — Pawn destrui, PlayerState
|
||||
* mantem). Padrao MMO classico (rathena, TrinityCore).
|
||||
* - **Spectator-friendly**: dados acessiveis sem ter pawn.
|
||||
* - **1:1 com player** (Pawn pode trocar — vehicle, mount, possession).
|
||||
*
|
||||
* ## Component Registry (Open-Closed)
|
||||
*
|
||||
* Cada modulo (ZMMOAttributes, ZMMOInventory, ZMMOSkills futuros) declara
|
||||
* sua component class em DefaultGame.ini:
|
||||
*
|
||||
* [/Script/ZMMO.ZMMOPlayerState]
|
||||
* +ComponentClasses=/Script/ZMMOAttributes.ZMMOAttributeComponent
|
||||
* +ComponentClasses=/Script/ZMMOInventory.ZMMOInventoryComponent
|
||||
* +ComponentClasses=/Script/ZMMOSkills.ZMMOSkillComponent
|
||||
*
|
||||
* O construtor de AZMMOPlayerState le essa lista (Config) e instancia
|
||||
* cada classe como subobject. PlayerState fica fechado pra modificacao,
|
||||
* modulos abertos pra extensao — adicionar Inventory NAO requer
|
||||
* recompilar/editar AZMMOPlayerState.
|
||||
*
|
||||
* Acesso: `PlayerState->FindComponentByClass<UZMMOAttributeComponent>()`
|
||||
* ou o helper `GetZMMOComponent<T>()`.
|
||||
*/
|
||||
UCLASS(Config = Game, BlueprintType)
|
||||
class ZMMO_API AZMMOPlayerState : public APlayerState
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
AZMMOPlayerState();
|
||||
|
||||
// === Identidade publica ===
|
||||
|
||||
/** EntityId autoritativo do server. 0 ate o spawn local confirmar. */
|
||||
UPROPERTY(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")
|
||||
FString CharId;
|
||||
|
||||
// === Progressao publica ===
|
||||
|
||||
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress")
|
||||
int32 BaseLevel = 1;
|
||||
|
||||
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress")
|
||||
int32 ClassId = 0;
|
||||
|
||||
// === Guild (futuro) ===
|
||||
|
||||
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Guild")
|
||||
FString GuildName;
|
||||
|
||||
// === Helpers ===
|
||||
|
||||
/// Atualizado quando S_ATTRIBUTE_SNAPSHOT_FULL chega — UZMMOHudWidget chama
|
||||
/// isso pra refletir os dados publicos do snapshot no PlayerState.
|
||||
UFUNCTION(BlueprintCallable, Category = "ZMMO|Identity")
|
||||
void SetPublicIdentity(int64 InEntityId, const FString& InCharId,
|
||||
int32 InBaseLevel, int32 InClassId);
|
||||
|
||||
/// Template helper: PS->GetZMMOComponent<UZMMOAttributeComponent>()
|
||||
template <typename T>
|
||||
T* GetZMMOComponent() const
|
||||
{
|
||||
return Cast<T>(FindComponentByClass(T::StaticClass()));
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Component Registry — config-driven. Modulos registram suas classes
|
||||
* via DefaultGame.ini com prefix `+`. Construtor instancia cada uma
|
||||
* como CreateDefaultSubobject.
|
||||
*
|
||||
* TSubclassOf (nao TSoftClassPtr): config UE resolve classes nativas
|
||||
* diretamente. Modulos C++ precisam estar carregados antes do CDO ser
|
||||
* instanciado — garantido por LoadingPhase=PreDefault no .uproject.
|
||||
*/
|
||||
UPROPERTY(Config, EditDefaultsOnly, Category = "ZMMO|Components")
|
||||
TArray<TSubclassOf<UActorComponent>> ComponentClasses;
|
||||
};
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Engine/World.h"
|
||||
#include "TimerManager.h"
|
||||
#include "UI/Widgets/UIButton_Base.h"
|
||||
#include "ZMMOJobsLibrary.h"
|
||||
|
||||
void UUICharCard_Base::NativeConstruct()
|
||||
{
|
||||
@@ -48,7 +49,7 @@ void UUICharCard_Base::SetFromSummary(const FZMMOCharSummary& InSummary)
|
||||
}
|
||||
if (Text_Class)
|
||||
{
|
||||
Text_Class->SetText(FText::FromString(FString::Printf(TEXT("Classe %d"), Summary.ClassId)));
|
||||
Text_Class->SetText(UZMMOJobsLibrary::GetJobDisplayName(this, Summary.ClassId));
|
||||
}
|
||||
|
||||
const ESlateVisibility ActionVis = bDeleteScheduled ? ESlateVisibility::Collapsed : ESlateVisibility::Visible;
|
||||
|
||||
@@ -41,18 +41,7 @@ void UUIFrontEndFlowSubsystem::Deinitialize()
|
||||
|
||||
void UUIFrontEndFlowSubsystem::StartFrontEnd()
|
||||
{
|
||||
UUIFrontEndScreenSet* SS = GetScreenSet();
|
||||
|
||||
if (UUIManagerSubsystem* Mgr = GetUIManager())
|
||||
{
|
||||
TSubclassOf<UUIPrimaryGameLayout_Base> LayoutClass;
|
||||
if (SS && !SS->RootLayoutClass.IsNull())
|
||||
{
|
||||
LayoutClass = SS->RootLayoutClass.LoadSynchronous();
|
||||
}
|
||||
Mgr->CreateAndAddRootLayout(LayoutClass);
|
||||
}
|
||||
|
||||
EnsureRootLayout();
|
||||
BindNetwork();
|
||||
|
||||
SetState(EZMMOFrontEndState::Boot);
|
||||
@@ -69,6 +58,26 @@ void UUIFrontEndFlowSubsystem::StartFrontEnd()
|
||||
}
|
||||
}
|
||||
|
||||
void UUIFrontEndFlowSubsystem::EnsureRootLayout()
|
||||
{
|
||||
UUIManagerSubsystem* Mgr = GetUIManager();
|
||||
if (!Mgr) { return; }
|
||||
if (Mgr->IsRootLayoutReady())
|
||||
{
|
||||
return; // ja' no viewport
|
||||
}
|
||||
UUIFrontEndScreenSet* SS = GetScreenSet();
|
||||
if (!SS || SS->RootLayoutClass.IsNull())
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning,
|
||||
TEXT("FrontEndFlow::EnsureRootLayout: RootLayoutClass ausente em ScreenSet"));
|
||||
return;
|
||||
}
|
||||
TSubclassOf<UUIPrimaryGameLayout_Base> LayoutClass = SS->RootLayoutClass.LoadSynchronous();
|
||||
Mgr->CreateAndAddRootLayout(LayoutClass);
|
||||
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow::EnsureRootLayout: RootLayout recriado"));
|
||||
}
|
||||
|
||||
void UUIFrontEndFlowSubsystem::SetState(EZMMOFrontEndState NewState)
|
||||
{
|
||||
if (NewState == CurrentState)
|
||||
|
||||
@@ -46,6 +46,18 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
|
||||
void StartFrontEnd();
|
||||
|
||||
/**
|
||||
* Garante que o root layout (UUIPrimaryGameLayout_Base) esta no viewport.
|
||||
* Chamado pelo UIInGameFlowSubsystem::StartInGame apos OpenLevel — o
|
||||
* `OpenLevel` invalida widgets no viewport ("InvalidateAllWidgets") e o
|
||||
* RootLayout fica orfao. Idempotente: CreateAndAddRootLayout do
|
||||
* UIManagerSubsystem checa `IsInViewport` antes de recriar.
|
||||
*
|
||||
* C++ puro (sem UFUNCTION) — Live Coding nao registra UFUNCTIONs novas;
|
||||
* consumidores externos chamam direto via C++ (UIInGameFlowSubsystem).
|
||||
*/
|
||||
void EnsureRootLayout();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
|
||||
void SetState(EZMMOFrontEndState NewState);
|
||||
|
||||
|
||||
210
Source/ZMMO/Game/UI/InGame/UIInGameFlowSubsystem.cpp
Normal file
210
Source/ZMMO/Game/UI/InGame/UIInGameFlowSubsystem.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
#include "UIInGameFlowSubsystem.h"
|
||||
|
||||
#include "CommonActivatableWidget.h" // Necessario pra TSoftClassPtr<UCommonActivatableWidget>::Get()
|
||||
#include "Engine/AssetManager.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/StreamableManager.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "ZMMO.h"
|
||||
#include "Data/UI/UILayerTags.h"
|
||||
#include "FrontEnd/UIFrontEndFlowSubsystem.h"
|
||||
#include "FrontEnd/UIManagerSubsystem.h"
|
||||
#include "InGame/UIInGameScreenSet.h"
|
||||
|
||||
void UUIInGameFlowSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
UE_LOG(LogZMMO, Log, TEXT("UIInGameFlowSubsystem initialized"));
|
||||
}
|
||||
|
||||
void UUIInGameFlowSubsystem::Deinitialize()
|
||||
{
|
||||
ScreenSet = nullptr;
|
||||
CurrentState = EZMMOInGameUIState::None;
|
||||
Super::Deinitialize();
|
||||
}
|
||||
|
||||
void UUIInGameFlowSubsystem::StartInGame()
|
||||
{
|
||||
if (CurrentState != EZMMOInGameUIState::None)
|
||||
{
|
||||
// Idempotente: chamadas extras (ex.: respawn) sao no-op se ja in-world.
|
||||
UE_LOG(LogZMMO, Verbose,
|
||||
TEXT("InGameFlow: StartInGame ignorado (ja em estado %s)"),
|
||||
*UEnum::GetValueAsString(CurrentState));
|
||||
return;
|
||||
}
|
||||
|
||||
// CRITICAL: OpenLevel invalida widgets no viewport ("InvalidateAllWidgets"),
|
||||
// orfanando o RootLayout (UUIPrimaryGameLayout_Base) que foi criado pelo
|
||||
// FrontEndFlow no boot. Sem RootLayout no viewport, PushScreenToLayer
|
||||
// silenciosamente adiciona widgets ao stack do RootLayout — mas o stack
|
||||
// nao e' visivel porque o parent (RootLayout) nao esta no viewport.
|
||||
//
|
||||
// Re-cria via EnsureRootLayout (idempotente) ANTES de qualquer push.
|
||||
if (UGameInstance* GI = GetGameInstance())
|
||||
{
|
||||
if (UUIFrontEndFlowSubsystem* FE = GI->GetSubsystem<UUIFrontEndFlowSubsystem>())
|
||||
{
|
||||
FE->EnsureRootLayout();
|
||||
}
|
||||
}
|
||||
|
||||
// Limpa qualquer residuo do front-end (Menu/Modal podem ter "Loading" ou
|
||||
// dialogs orfaos do handoff). Layer.Game vai receber o HUD novo.
|
||||
if (UUIManagerSubsystem* Mgr = GetUIManager())
|
||||
{
|
||||
Mgr->ClearLayer(ZMMOUITags::UI_Layer_Menu.GetTag());
|
||||
Mgr->ClearLayer(ZMMOUITags::UI_Layer_Modal.GetTag());
|
||||
Mgr->ClearLayer(ZMMOUITags::UI_Layer_GameMenu.GetTag());
|
||||
}
|
||||
|
||||
SetState(EZMMOInGameUIState::Playing);
|
||||
}
|
||||
|
||||
void UUIInGameFlowSubsystem::SetState(EZMMOInGameUIState NewState)
|
||||
{
|
||||
if (NewState == CurrentState)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const EZMMOInGameUIState OldState = CurrentState;
|
||||
CurrentState = NewState;
|
||||
|
||||
UE_LOG(LogZMMO, Log, TEXT("InGameFlow: state %s -> %s"),
|
||||
*UEnum::GetValueAsString(OldState),
|
||||
*UEnum::GetValueAsString(NewState));
|
||||
|
||||
// Decisao de stack: ao voltar pra `Playing`, fechamos quaisquer overlays
|
||||
// (Status/Inventory/Menu) limpando o Layer.GameMenu — HUD em Layer.Game
|
||||
// permanece. Para qualquer outro estado, mantemos HUD e empilhamos no
|
||||
// layer correspondente.
|
||||
if (UUIManagerSubsystem* Mgr = GetUIManager())
|
||||
{
|
||||
const bool bGoingToPlaying = (NewState == EZMMOInGameUIState::Playing);
|
||||
if (bGoingToPlaying)
|
||||
{
|
||||
Mgr->ClearLayer(ZMMOUITags::UI_Layer_GameMenu.GetTag());
|
||||
}
|
||||
}
|
||||
|
||||
ResolveAndPushScreen(NewState);
|
||||
OnStateChanged.Broadcast(NewState);
|
||||
}
|
||||
|
||||
void UUIInGameFlowSubsystem::ToggleScreen(EZMMOInGameUIState Screen)
|
||||
{
|
||||
if (Screen == EZMMOInGameUIState::Playing || Screen == EZMMOInGameUIState::None)
|
||||
{
|
||||
// Toggle para Playing nao faz sentido (estado base). Use SetState direto.
|
||||
return;
|
||||
}
|
||||
if (CurrentState == Screen)
|
||||
{
|
||||
SetState(EZMMOInGameUIState::Playing);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetState(Screen);
|
||||
}
|
||||
}
|
||||
|
||||
UUIInGameScreenSet* UUIInGameFlowSubsystem::GetScreenSet()
|
||||
{
|
||||
if (ScreenSet) { return ScreenSet; }
|
||||
if (ScreenSetAsset.IsNull())
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning,
|
||||
TEXT("InGameFlow: ScreenSetAsset nao configurado em DefaultGame.ini "
|
||||
"[/Script/ZMMO.UIInGameFlowSubsystem] ScreenSetAsset=..."));
|
||||
return nullptr;
|
||||
}
|
||||
// Sync load — DA e' pequeno (so' soft refs); PIE/dev OK.
|
||||
ScreenSet = ScreenSetAsset.LoadSynchronous();
|
||||
return ScreenSet;
|
||||
}
|
||||
|
||||
UUIManagerSubsystem* UUIInGameFlowSubsystem::GetUIManager() const
|
||||
{
|
||||
if (const UGameInstance* GI = GetGameInstance())
|
||||
{
|
||||
if (ULocalPlayer* LP = GI->GetFirstGamePlayer())
|
||||
{
|
||||
return LP->GetSubsystem<UUIManagerSubsystem>();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FGameplayTag UUIInGameFlowSubsystem::LayerForState(EZMMOInGameUIState State) const
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
case EZMMOInGameUIState::Playing:
|
||||
return ZMMOUITags::UI_Layer_Game.GetTag();
|
||||
case EZMMOInGameUIState::Loading:
|
||||
return ZMMOUITags::UI_Layer_Modal.GetTag();
|
||||
case EZMMOInGameUIState::StatusWindow:
|
||||
case EZMMOInGameUIState::Inventory:
|
||||
case EZMMOInGameUIState::SkillTree:
|
||||
case EZMMOInGameUIState::EscapeMenu:
|
||||
default:
|
||||
return ZMMOUITags::UI_Layer_GameMenu.GetTag();
|
||||
}
|
||||
}
|
||||
|
||||
void UUIInGameFlowSubsystem::ResolveAndPushScreen(EZMMOInGameUIState State)
|
||||
{
|
||||
if (State == EZMMOInGameUIState::None) { return; }
|
||||
|
||||
UUIInGameScreenSet* SS = GetScreenSet();
|
||||
if (!SS)
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning,
|
||||
TEXT("InGameFlow: ScreenSet ausente; tela de %s ignorada"),
|
||||
*UEnum::GetValueAsString(State));
|
||||
return;
|
||||
}
|
||||
|
||||
const TSoftClassPtr<UCommonActivatableWidget> Soft = SS->GetScreenForState(State);
|
||||
if (Soft.IsNull())
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning,
|
||||
TEXT("InGameFlow: Screen for state %s not configured em DA_InGameScreenSet (verificar StateScreens)"),
|
||||
*UEnum::GetValueAsString(State));
|
||||
return;
|
||||
}
|
||||
|
||||
UUIManagerSubsystem* Mgr = GetUIManager();
|
||||
if (!Mgr)
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning, TEXT("InGameFlow: UIManagerSubsystem ausente"));
|
||||
return;
|
||||
}
|
||||
|
||||
const FGameplayTag Layer = LayerForState(State);
|
||||
const FSoftObjectPath Path = Soft.ToSoftObjectPath();
|
||||
|
||||
UAssetManager::GetStreamableManager().RequestAsyncLoad(
|
||||
Path,
|
||||
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer, State]()
|
||||
{
|
||||
UUIManagerSubsystem* M = GetUIManager();
|
||||
if (!M) { return; }
|
||||
UClass* Cls = Soft.Get();
|
||||
if (!Cls)
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning,
|
||||
TEXT("InGameFlow: async load falhou pra %s"),
|
||||
*UEnum::GetValueAsString(State));
|
||||
return;
|
||||
}
|
||||
UCommonActivatableWidget* W = M->PushScreenToLayer(Layer, Cls);
|
||||
UE_LOG(LogZMMO, Log,
|
||||
TEXT("InGameFlow: tela %s pushed em %s (widget=%s)"),
|
||||
*UEnum::GetValueAsString(State), *Layer.ToString(),
|
||||
W ? *W->GetName() : TEXT("null"));
|
||||
}));
|
||||
}
|
||||
97
Source/ZMMO/Game/UI/InGame/UIInGameFlowSubsystem.h
Normal file
97
Source/ZMMO/Game/UI/InGame/UIInGameFlowSubsystem.h
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "UI/InGameTypes.h"
|
||||
#include "UIInGameFlowSubsystem.generated.h"
|
||||
|
||||
class UUIInGameScreenSet;
|
||||
class UUIManagerSubsystem;
|
||||
class UCommonActivatableWidget;
|
||||
struct FGameplayTag;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnZMMOInGameStateChanged, EZMMOInGameUIState, NewState);
|
||||
|
||||
/**
|
||||
* Orquestrador da UI in-game. Espelho simetrico do UUIFrontEndFlowSubsystem
|
||||
* (Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h) — o front-end gerencia
|
||||
* Boot/Login/ServerSelect/Lobby; este subsystem gerencia
|
||||
* HUD/StatusWindow/Inventory/SkillTree/EscapeMenu in-world.
|
||||
*
|
||||
* UGameInstanceSubsystem pra persistir entre OpenLevel (futuro: trocar
|
||||
* de mapa in-game preserva estado da UI). Inicializa em modo `None`;
|
||||
* caller (`AZMMOPlayerCharacter::HandleLocalSpawnReady`) chama
|
||||
* `StartInGame()` ao confirmar spawn local, que vai pra `Playing`.
|
||||
*
|
||||
* Convencao de layer (resolvida internamente):
|
||||
* - Playing -> UI.Layer.Game
|
||||
* - StatusWindow/Inventory/
|
||||
* SkillTree/EscapeMenu -> UI.Layer.GameMenu
|
||||
* - Loading -> UI.Layer.Modal
|
||||
*
|
||||
* Adicionar nova tela in-game = 3 passos:
|
||||
* 1. Adicionar entry em `EZMMOInGameUIState`
|
||||
* 2. Adicionar entry no DA_InGameScreenSet (estado -> WBP)
|
||||
* 3. (se for em layer custom) adicionar caso em `LayerForState`
|
||||
* Subsystem nao precisa de mudancas.
|
||||
*/
|
||||
UCLASS(Config = Game)
|
||||
class ZMMO_API UUIInGameFlowSubsystem : public UGameInstanceSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void Deinitialize() override;
|
||||
|
||||
/**
|
||||
* Ativa a UI in-game. Chamado pelo `AZMMOPlayerCharacter::HandleLocalSpawnReady`
|
||||
* quando o servidor confirma o spawn local. Garante root layout, limpa
|
||||
* layers do front-end (Menu/Modal residuais), e vai pra `Playing` (HUD).
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "InGame|UI")
|
||||
void StartInGame();
|
||||
|
||||
/** Define o estado top-level. Resolve a tela do DA + push no layer correto. */
|
||||
UFUNCTION(BlueprintCallable, Category = "InGame|UI")
|
||||
void SetState(EZMMOInGameUIState NewState);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "InGame|UI")
|
||||
EZMMOInGameUIState GetCurrentState() const { return CurrentState; }
|
||||
|
||||
/**
|
||||
* Atalho UX: alterna entre `Playing` e a tela passada. Util pra teclas
|
||||
* de toggle (Alt+A abre/fecha StatusWindow, I abre/fecha Inventory).
|
||||
* Se ja' estiver em `Screen`, volta pra Playing; senao vai pra `Screen`.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "InGame|UI")
|
||||
void ToggleScreen(EZMMOInGameUIState Screen);
|
||||
|
||||
/** Disparado a cada SetState bem-sucedido. */
|
||||
UPROPERTY(BlueprintAssignable, Category = "InGame|UI")
|
||||
FOnZMMOInGameStateChanged OnStateChanged;
|
||||
|
||||
protected:
|
||||
/** DA com o mapa estado->tela. Config em DefaultGame.ini:
|
||||
* [/Script/ZMMO.UIInGameFlowSubsystem]
|
||||
* ScreenSetAsset=/Game/ZMMO/UI/InGame/DA_InGameScreenSet.DA_InGameScreenSet
|
||||
*/
|
||||
UPROPERTY(Config, EditDefaultsOnly, Category = "InGame|UI")
|
||||
TSoftObjectPtr<UUIInGameScreenSet> ScreenSetAsset;
|
||||
|
||||
private:
|
||||
UUIInGameScreenSet* GetScreenSet();
|
||||
UUIManagerSubsystem* GetUIManager() const;
|
||||
|
||||
/** Retorna a layer tag onde a tela do estado deve ir. */
|
||||
FGameplayTag LayerForState(EZMMOInGameUIState State) const;
|
||||
|
||||
/** Resolve a soft class + push no layer apropriado (async load). */
|
||||
void ResolveAndPushScreen(EZMMOInGameUIState State);
|
||||
|
||||
UPROPERTY(Transient)
|
||||
EZMMOInGameUIState CurrentState = EZMMOInGameUIState::None;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UUIInGameScreenSet> ScreenSet;
|
||||
};
|
||||
7
Source/ZMMO/Game/UI/InGame/UIInGameScreenSet.cpp
Normal file
7
Source/ZMMO/Game/UI/InGame/UIInGameScreenSet.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "UIInGameScreenSet.h"
|
||||
|
||||
TSoftClassPtr<UCommonActivatableWidget> UUIInGameScreenSet::GetScreenForState(EZMMOInGameUIState State) const
|
||||
{
|
||||
const TSoftClassPtr<UCommonActivatableWidget>* Found = StateScreens.Find(State);
|
||||
return Found ? *Found : TSoftClassPtr<UCommonActivatableWidget>();
|
||||
}
|
||||
35
Source/ZMMO/Game/UI/InGame/UIInGameScreenSet.h
Normal file
35
Source/ZMMO/Game/UI/InGame/UIInGameScreenSet.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "Templates/SubclassOf.h"
|
||||
#include "UI/InGameTypes.h"
|
||||
#include "UIInGameScreenSet.generated.h"
|
||||
|
||||
class UCommonActivatableWidget;
|
||||
|
||||
/**
|
||||
* Mapa data-driven estado -> tela in-game. Espelho de UUIFrontEndScreenSet
|
||||
* (Game/UI/FrontEnd/UIFrontEndScreenSet.h) — mesma estrategia: as classes
|
||||
* concretas das telas (WBP) vivem no DA, nao em codigo. Subsystem orquestra.
|
||||
*
|
||||
* Asset concreto: DA_InGameScreenSet em Content/ZMMO/UI/InGame/
|
||||
* (criado via MCP no editor).
|
||||
*
|
||||
* Adicionar nova tela = adicionar entry em EZMMOInGameUIState + entry no DA
|
||||
* + WBP herdando UCommonActivatableWidget. Subsystem nao muda.
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class ZMMO_API UUIInGameScreenSet : public UDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Tela por estado in-game. */
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "InGame")
|
||||
TMap<EZMMOInGameUIState, TSoftClassPtr<UCommonActivatableWidget>> StateScreens;
|
||||
|
||||
/** Soft class da tela do estado (vazio se nao configurado). */
|
||||
UFUNCTION(BlueprintCallable, Category = "InGame")
|
||||
TSoftClassPtr<UCommonActivatableWidget> GetScreenForState(EZMMOInGameUIState State) const;
|
||||
};
|
||||
158
Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.cpp
Normal file
158
Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
#include "ZMMOHudWidget.h"
|
||||
|
||||
#include "Components/TextBlock.h"
|
||||
#include "Components/Widget.h"
|
||||
#include "CommonInputTypeEnum.h" // ECommonInputMode
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "GameFramework/PlayerState.h"
|
||||
#include "TimerManager.h"
|
||||
#include "ZMMO.h"
|
||||
#include "ZMMOAttributeComponent.h"
|
||||
#include "ZMMOHudHpSpWidget.h"
|
||||
|
||||
UZMMOHudWidget::UZMMOHudWidget(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
// HUD nao bloqueia "back" (Esc / B no controller). Menus reais (StatusWindow,
|
||||
// EscapeMenu) sim — eles setam bIsBackHandler=true.
|
||||
bIsBackHandler = false;
|
||||
// HUD nao reativa stack ao virar topo (e' sempre fundo). Garante que o
|
||||
// UCommonActivatableWidgetStack do Layer.Game nao force activate/deactivate.
|
||||
bAutoActivate = true;
|
||||
}
|
||||
|
||||
TOptional<FUIInputConfig> UZMMOHudWidget::GetDesiredInputConfig() const
|
||||
{
|
||||
// Modo Game: input flui pro PlayerController/Pawn, mouse capturado (3a pessoa).
|
||||
// Sem isso, CommonUI assume Menu config (mostra cursor + bloqueia movimento).
|
||||
return FUIInputConfig(ECommonInputMode::Game, EMouseCaptureMode::CapturePermanently);
|
||||
}
|
||||
|
||||
void UZMMOHudWidget::NativeOnActivated()
|
||||
{
|
||||
Super::NativeOnActivated();
|
||||
// HUD nao intercepta cliques — mouse passa direto pro Pawn (camera, etc.).
|
||||
// Sub-widgets que precisam de click (futuro: hotkeys) podem override.
|
||||
SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||
// Toast comeca escondido — so' aparece em HandleLevelUp.
|
||||
HideLevelUpToast();
|
||||
BindToLocalPlayer();
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZMMOHudWidget activated (HpSpBar=%s)"),
|
||||
HpSpBar ? *HpSpBar->GetName() : TEXT("none"));
|
||||
}
|
||||
|
||||
void UZMMOHudWidget::NativeOnDeactivated()
|
||||
{
|
||||
UnbindFromComponent();
|
||||
Super::NativeOnDeactivated();
|
||||
}
|
||||
|
||||
void UZMMOHudWidget::NativeDestruct()
|
||||
{
|
||||
UnbindFromComponent();
|
||||
Super::NativeDestruct();
|
||||
}
|
||||
|
||||
void UZMMOHudWidget::BindToLocalPlayer()
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (!World) { return; }
|
||||
|
||||
APlayerController* PC = World->GetFirstPlayerController();
|
||||
if (!PC) { return; }
|
||||
APlayerState* PS = PC->PlayerState;
|
||||
if (!PS)
|
||||
{
|
||||
// PlayerState pode nao existir ainda — race com GameMode setup.
|
||||
// AZMMOPlayerCharacter::HandleLocalSpawnReady chama BindToAttributeComponent
|
||||
// manualmente como fallback (seed do EntityId).
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZMMOHudWidget: PlayerState ausente no activate (race fix via HandleLocalSpawnReady esperado)"));
|
||||
return;
|
||||
}
|
||||
UZMMOAttributeComponent* Comp = PS->FindComponentByClass<UZMMOAttributeComponent>();
|
||||
if (!Comp) { return; }
|
||||
if (Comp == BoundComponent.Get()) { return; } // idempotente
|
||||
|
||||
UnbindFromComponent();
|
||||
BoundComponent = Comp;
|
||||
|
||||
Comp->OnAttributesChanged.AddDynamic(this, &UZMMOHudWidget::HandleAttributesChanged);
|
||||
Comp->OnHpSpChanged.AddDynamic(this, &UZMMOHudWidget::HandleHpSpChanged);
|
||||
Comp->OnLevelUp.AddDynamic(this, &UZMMOHudWidget::HandleLevelUp);
|
||||
|
||||
// Refresh imediato — caso o snapshot ja' tenha chegado.
|
||||
HandleAttributesChanged(Comp->GetSnapshot());
|
||||
}
|
||||
|
||||
void UZMMOHudWidget::UnbindFromComponent()
|
||||
{
|
||||
if (UZMMOAttributeComponent* Old = BoundComponent.Get())
|
||||
{
|
||||
Old->OnAttributesChanged.RemoveDynamic(this, &UZMMOHudWidget::HandleAttributesChanged);
|
||||
Old->OnHpSpChanged.RemoveDynamic(this, &UZMMOHudWidget::HandleHpSpChanged);
|
||||
Old->OnLevelUp.RemoveDynamic(this, &UZMMOHudWidget::HandleLevelUp);
|
||||
}
|
||||
BoundComponent.Reset();
|
||||
}
|
||||
|
||||
void UZMMOHudWidget::HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot)
|
||||
{
|
||||
// Propaga snapshot aos sub-widgets que ja existem. Em V1 so' HpSpBar.
|
||||
if (HpSpBar)
|
||||
{
|
||||
HpSpBar->ApplySnapshot(Snapshot);
|
||||
}
|
||||
// Futuros: PlayerInfo->ApplySnapshot, Buffs->RefreshIcons, etc.
|
||||
}
|
||||
|
||||
void UZMMOHudWidget::HandleHpSpChanged(int32 Hp, int32 Sp)
|
||||
{
|
||||
if (UZMMOAttributeComponent* Comp = BoundComponent.Get())
|
||||
{
|
||||
const FZMMOAttributesSnapshot& Snap = Comp->GetSnapshot();
|
||||
if (HpSpBar)
|
||||
{
|
||||
HpSpBar->ApplyHpSp(Hp, Snap.MaxHp, Sp, Snap.MaxSp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UZMMOHudWidget::HandleLevelUp(int32 NewBaseLevel, int32 StatusPointDelta)
|
||||
{
|
||||
// Snapshot subsequente atualiza HpSpBar (level/maxhp/maxsp). Aqui mostra
|
||||
// toast visual instantaneo. StatusPointDelta nao usado no texto (UI mostra
|
||||
// no StatusWindow). Polish futuro: SFX + particle aura.
|
||||
UE_LOG(LogZMMO, Log, TEXT("[HUD] LEVEL UP! newLevel=%d +sp=%d"), NewBaseLevel, StatusPointDelta);
|
||||
ShowLevelUpToast(NewBaseLevel);
|
||||
}
|
||||
|
||||
void UZMMOHudWidget::ShowLevelUpToast(int32 NewBaseLevel)
|
||||
{
|
||||
if (LevelUpToastText)
|
||||
{
|
||||
LevelUpToastText->SetText(FText::FromString(
|
||||
FString::Printf(TEXT("LEVEL UP! Lv %d"), NewBaseLevel)));
|
||||
}
|
||||
if (LevelUpToastContainer)
|
||||
{
|
||||
LevelUpToastContainer->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||
}
|
||||
|
||||
// Cancela timer anterior (caso level up duplo em sequencia) + agenda hide.
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
World->GetTimerManager().ClearTimer(LevelUpToastHideTimer);
|
||||
World->GetTimerManager().SetTimer(LevelUpToastHideTimer,
|
||||
FTimerDelegate::CreateUObject(this, &UZMMOHudWidget::HideLevelUpToast),
|
||||
LevelUpToastDurationSec, /*loop*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
void UZMMOHudWidget::HideLevelUpToast()
|
||||
{
|
||||
if (LevelUpToastContainer)
|
||||
{
|
||||
LevelUpToastContainer->SetVisibility(ESlateVisibility::Collapsed);
|
||||
}
|
||||
}
|
||||
126
Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.h
Normal file
126
Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include "CommonActivatableWidget.h"
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/TimerHandle.h"
|
||||
#include "ZMMOAttributeTypes.h"
|
||||
#include "ZMMOHudWidget.generated.h"
|
||||
|
||||
class UTextBlock;
|
||||
class UWidget;
|
||||
class UZMMOAttributeComponent;
|
||||
class UZMMOHudHpSpWidget;
|
||||
|
||||
/**
|
||||
* Root da HUD de gameplay. UCommonActivatableWidget pushed pelo
|
||||
* UUIInGameFlowSubsystem em UI.Layer.Game quando o estado vira `Playing`.
|
||||
*
|
||||
* **Vive em ZMMO core** (UI/InGame/) porque o HUD root e' generico pra
|
||||
* qualquer GameType — sub-widgets vem dos modulos especializados
|
||||
* (HpSpBar do sub-modulo ZMMOAttributes, Minimap de ZMMOMap futuro,
|
||||
* Chat de ZMMOChat futuro, etc.).
|
||||
*
|
||||
* Padrao **composite**: o WBP_HUD (filho) declara as PECAS visuais como
|
||||
* sub-widgets BindWidgetOptional, e este C++ orquestra:
|
||||
* 1. Em NativeOnActivated: auto-binda no UZMMOAttributeComponent do
|
||||
* player local (vive no PlayerState via Component Registry)
|
||||
* 2. Subscreve aos delegates do componente (OnAttributesChanged,
|
||||
* OnHpSpChanged, OnLevelUp)
|
||||
* 3. Propaga os deltas pros sub-widgets via metodos publicos
|
||||
* (HpSpBar->ApplySnapshot, HpSpBar->ApplyHpSp, etc.)
|
||||
*
|
||||
* Composicao prevista (sub-widgets — todos `BindWidgetOptional` pra
|
||||
* permitir GameTypes sem modulos opcionais — ex.: Survival sem stats RO):
|
||||
* - HpSpBar (UZMMOHudHpSpWidget — sub-modulo ZMMOAttributes) — impl
|
||||
* - PlayerInfo (futuro — nome/job/level/exp bar)
|
||||
* - Minimap (futuro)
|
||||
* - QuickBar (futuro — hotkeys de skills)
|
||||
* - Buffs (futuro — icones SC ativos)
|
||||
* - Chat (futuro)
|
||||
* - TargetInfo (futuro — HP/SP/nome do mob/player targetado)
|
||||
*
|
||||
* Adicionar novo sub-widget = adicionar campo BindWidgetOptional aqui +
|
||||
* setup no NativeOnActivated/handlers + adicionar no WBP_HUD.
|
||||
*
|
||||
* Pra mudar o LAYOUT visual ou skin do HUD: edita o WBP_HUD (filho)
|
||||
* no Designer — sem tocar este C++.
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable, BlueprintType)
|
||||
class ZMMO_API UZMMOHudWidget : public UCommonActivatableWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UZMMOHudWidget(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
protected:
|
||||
virtual void NativeOnActivated() override;
|
||||
virtual void NativeOnDeactivated() override;
|
||||
virtual void NativeDestruct() override;
|
||||
|
||||
/**
|
||||
* HUD passa input direto pro Pawn — sem capturar mouse/teclado. Override
|
||||
* retorna `TOptional<FUIInputConfig>(unset)`. Sem isso, UCommonActivatableWidget
|
||||
* default usa Menu config (`SetInputMode_GameAndUI` + mouse cursor), o que
|
||||
* desativa input de gameplay.
|
||||
*
|
||||
* Menus (StatusWindow, Inventory, etc.) override e' diferente — eles
|
||||
* retornam GameAndMenu/Menu pra mostrar cursor + bloquear movement.
|
||||
*/
|
||||
virtual TOptional<FUIInputConfig> GetDesiredInputConfig() const override;
|
||||
|
||||
/// Sub-widget HP/SP/level (sub-modulo ZMMOAttributes). BindWidgetOptional
|
||||
/// — WBP_HUD pode omitir se o GameType nao usa Attributes (Survival, etc.).
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD")
|
||||
UZMMOHudHpSpWidget* HpSpBar = nullptr;
|
||||
|
||||
/// Container do toast "LEVEL UP!" (Fase 3). Hidden por default; em
|
||||
/// `HandleLevelUp` vira visivel por `LevelUpToastDurationSec` segundos
|
||||
/// (ver `Show/HideLevelUpToast`). WBP_HUD coloca um Border + Image + TextBlock
|
||||
/// dentro deste container; este C++ controla apenas o lifecycle visivel/hidden.
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD|LevelUp")
|
||||
UWidget* LevelUpToastContainer = nullptr;
|
||||
|
||||
/// Texto exibido no toast — formato "LEVEL UP! Lv X" (X = novo level).
|
||||
/// Tambem BindWidgetOptional; WBP_HUD pode usar texto estatico no design.
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD|LevelUp")
|
||||
UTextBlock* LevelUpToastText = nullptr;
|
||||
|
||||
/// Duracao do toast em segundos. Configuravel no BP filho.
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Zeus|HUD|LevelUp")
|
||||
float LevelUpToastDurationSec = 2.5f;
|
||||
|
||||
// === Futuros sub-widgets ===
|
||||
// UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional)) UZMMOHudPlayerInfoWidget* PlayerInfo = nullptr;
|
||||
// UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional)) UZMMOHudMinimapWidget* Minimap = nullptr;
|
||||
// UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional)) UZMMOHudQuickBarWidget* QuickBar = nullptr;
|
||||
// UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional)) UZMMOHudBuffsWidget* Buffs = nullptr;
|
||||
// UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional)) UZMMOHudChatWidget* Chat = nullptr;
|
||||
// UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional)) UZMMOHudTargetInfoWidget* TargetInfo = nullptr;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Zeus|HUD")
|
||||
UZMMOAttributeComponent* GetBoundComponent() const { return BoundComponent.Get(); }
|
||||
|
||||
private:
|
||||
/// Tenta achar o UZMMOAttributeComponent do player local + subscrever
|
||||
/// nos delegates. Idempotente.
|
||||
void BindToLocalPlayer();
|
||||
void UnbindFromComponent();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleHpSpChanged(int32 Hp, int32 Sp);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleLevelUp(int32 NewBaseLevel, int32 StatusPointDelta);
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TWeakObjectPtr<UZMMOAttributeComponent> BoundComponent;
|
||||
|
||||
void ShowLevelUpToast(int32 NewBaseLevel);
|
||||
void HideLevelUpToast();
|
||||
|
||||
FTimerHandle LevelUpToastHideTimer;
|
||||
};
|
||||
168
Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.cpp
Normal file
168
Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
106
Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.h
Normal file
106
Source/ZMMO/Game/UI/InGame/ZMMOStatusWindowWidget.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#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;
|
||||
};
|
||||
@@ -18,7 +18,9 @@ public class ZMMO : ModuleRules
|
||||
"CommonUI",
|
||||
"CommonInput",
|
||||
"GameplayTags",
|
||||
"ZeusNetwork"
|
||||
"ZeusNetwork",
|
||||
"ZMMOAttributes",
|
||||
"ZMMOJobs"
|
||||
});
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] {
|
||||
|
||||
55
Source/ZMMOAttributes/Private/ZMMOAttributeComponent.cpp
Normal file
55
Source/ZMMOAttributes/Private/ZMMOAttributeComponent.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "ZMMOAttributeComponent.h"
|
||||
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
|
||||
UZMMOAttributeComponent::UZMMOAttributeComponent()
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
}
|
||||
|
||||
void UZMMOAttributeComponent::ApplySnapshot(const FZMMOAttributesSnapshot& InSnapshot)
|
||||
{
|
||||
const int32 OldHp = Current.Hp;
|
||||
const int32 OldSp = Current.Sp;
|
||||
Current = InSnapshot;
|
||||
OnAttributesChanged.Broadcast(Current);
|
||||
if (OldHp != Current.Hp || OldSp != Current.Sp)
|
||||
{
|
||||
OnHpSpChanged.Broadcast(Current.Hp, Current.Sp);
|
||||
}
|
||||
}
|
||||
|
||||
void UZMMOAttributeComponent::ApplyHpSpUpdate(int32 NewHp, int32 NewSp)
|
||||
{
|
||||
if (Current.Hp == NewHp && Current.Sp == NewSp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Current.Hp = NewHp;
|
||||
Current.Sp = NewSp;
|
||||
OnHpSpChanged.Broadcast(Current.Hp, Current.Sp);
|
||||
}
|
||||
|
||||
void UZMMOAttributeComponent::NotifyLevelUp(int32 NewBaseLevel, int32 StatusPointDelta)
|
||||
{
|
||||
// O snapshot subsequente vai trazer todos os outros campos atualizados —
|
||||
// aqui apenas dispara o delegate para efeitos visuais imediatos.
|
||||
OnLevelUp.Broadcast(NewBaseLevel, StatusPointDelta);
|
||||
}
|
||||
|
||||
void UZMMOAttributeComponent::NotifyStatAllocReply(bool bAccepted, int32 Reason)
|
||||
{
|
||||
OnStatAllocReply.Broadcast(bAccepted, Reason);
|
||||
}
|
||||
|
||||
void UZMMOAttributeComponent::RequestStatAlloc(int32 StatId, int32 Amount)
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (!World) { return; }
|
||||
UGameInstance* GI = World->GetGameInstance();
|
||||
if (!GI) { return; }
|
||||
UZeusNetworkSubsystem* Net = GI->GetSubsystem<UZeusNetworkSubsystem>();
|
||||
if (!Net) { return; }
|
||||
Net->SendStatAlloc(StatId, Amount);
|
||||
}
|
||||
215
Source/ZMMOAttributes/Private/ZMMOAttributeNetworkHandler.cpp
Normal file
215
Source/ZMMOAttributes/Private/ZMMOAttributeNetworkHandler.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
#include "ZMMOAttributeNetworkHandler.h"
|
||||
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/GameStateBase.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "GameFramework/PlayerState.h"
|
||||
#include "ZMMOAttributeComponent.h"
|
||||
#include "ZMMOAttributeTypes.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Helper privado — converte o payload binario do plugin pra USTRUCT
|
||||
// Blueprint que o componente exibe.
|
||||
FZMMOAttributesSnapshot ToSnapshot(const FZeusAttributesPayload& P)
|
||||
{
|
||||
FZMMOAttributesSnapshot S;
|
||||
S.EntityId = P.EntityId;
|
||||
S.ClassId = P.ClassId;
|
||||
S.BaseLevel = P.BaseLevel;
|
||||
S.BaseExp = P.BaseExp;
|
||||
S.JobLevel = P.JobLevel;
|
||||
S.JobExp = P.JobExp;
|
||||
S.Str = P.Str;
|
||||
S.Agi = P.Agi;
|
||||
S.Vit = P.Vit;
|
||||
S.Int = P.Int;
|
||||
S.Dex = P.Dex;
|
||||
S.Luk = P.Luk;
|
||||
S.StatusPoint = P.StatusPoint;
|
||||
S.SkillPoint = P.SkillPoint;
|
||||
S.Hp = P.Hp;
|
||||
S.MaxHp = P.MaxHp;
|
||||
S.Sp = P.Sp;
|
||||
S.MaxSp = P.MaxSp;
|
||||
S.Money = P.Money;
|
||||
S.AtkBase = P.AtkBase;
|
||||
S.AtkEquipBonus = P.AtkEquipBonus;
|
||||
S.MatkBase = P.MatkBase;
|
||||
S.MatkEquipBonus = P.MatkEquipBonus;
|
||||
S.DefBase = P.DefBase;
|
||||
S.DefEquipBonus = P.DefEquipBonus;
|
||||
S.MdefBase = P.MdefBase;
|
||||
S.MdefEquipBonus = P.MdefEquipBonus;
|
||||
S.HitBase = P.HitBase;
|
||||
S.HitEquipBonus = P.HitEquipBonus;
|
||||
S.FleeBase = P.FleeBase;
|
||||
S.FleeEquipBonus = P.FleeEquipBonus;
|
||||
S.CritBaseX10 = P.CritBaseX10;
|
||||
S.CritEquipBonusX10 = P.CritEquipBonusX10;
|
||||
S.Aspd = P.Aspd;
|
||||
return S;
|
||||
}
|
||||
|
||||
// Resolve `EntityId -> UZMMOAttributeComponent` via PlayerArray do GameState.
|
||||
// AttributeComponent agora vive no PlayerState (vide AZMMOPlayerState +
|
||||
// Component Registry em DefaultGame.ini). Itera apenas players (1 por
|
||||
// conexao), nao 1000+ atores — O(N_players).
|
||||
//
|
||||
// Cada UZMMOAttributeComponent carrega seu proprio EntityId (seed em
|
||||
// AZMMOPlayerCharacter::HandleLocalSpawnReady ou no primeiro snapshot).
|
||||
UZMMOAttributeComponent* FindComponentForEntity(UWorld* World, int32 EntityId)
|
||||
{
|
||||
if (!World || EntityId == 0) { return nullptr; }
|
||||
const AGameStateBase* GS = World->GetGameState();
|
||||
if (!GS) { return nullptr; }
|
||||
for (APlayerState* PS : GS->PlayerArray)
|
||||
{
|
||||
if (!PS) { continue; }
|
||||
UZMMOAttributeComponent* Comp = PS->FindComponentByClass<UZMMOAttributeComponent>();
|
||||
if (!Comp) { continue; }
|
||||
if (Comp->GetSnapshot().EntityId == EntityId)
|
||||
{
|
||||
return Comp;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool UZMMOAttributeNetworkHandler::ShouldCreateSubsystem(UObject* Outer) const
|
||||
{
|
||||
// Cria apenas para mundos de gameplay (PIE/Game), nao para editor preview.
|
||||
UWorld* World = Cast<UWorld>(Outer);
|
||||
if (!World) { return false; }
|
||||
return World->WorldType == EWorldType::Game || World->WorldType == EWorldType::PIE;
|
||||
}
|
||||
|
||||
void UZMMOAttributeNetworkHandler::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
|
||||
if (UZeusNetworkSubsystem* Net = GetZeusNetSubsystem())
|
||||
{
|
||||
SnapshotFullHandle = Net->OnAttributeSnapshotFull.AddUObject(
|
||||
this, &UZMMOAttributeNetworkHandler::HandleAttributeSnapshotFull);
|
||||
HpSpUpdateHandle = Net->OnHpSpUpdate.AddUObject(
|
||||
this, &UZMMOAttributeNetworkHandler::HandleHpSpUpdate);
|
||||
LevelUpHandle = Net->OnLevelUp.AddUObject(
|
||||
this, &UZMMOAttributeNetworkHandler::HandleLevelUp);
|
||||
StatAllocReplyHandle = Net->OnStatAllocReply.AddUObject(
|
||||
this, &UZMMOAttributeNetworkHandler::HandleStatAllocReply);
|
||||
}
|
||||
}
|
||||
|
||||
void UZMMOAttributeNetworkHandler::Deinitialize()
|
||||
{
|
||||
if (UZeusNetworkSubsystem* Net = GetZeusNetSubsystem())
|
||||
{
|
||||
if (SnapshotFullHandle.IsValid())
|
||||
{
|
||||
Net->OnAttributeSnapshotFull.Remove(SnapshotFullHandle);
|
||||
SnapshotFullHandle.Reset();
|
||||
}
|
||||
if (HpSpUpdateHandle.IsValid())
|
||||
{
|
||||
Net->OnHpSpUpdate.Remove(HpSpUpdateHandle);
|
||||
HpSpUpdateHandle.Reset();
|
||||
}
|
||||
if (LevelUpHandle.IsValid())
|
||||
{
|
||||
Net->OnLevelUp.Remove(LevelUpHandle);
|
||||
LevelUpHandle.Reset();
|
||||
}
|
||||
if (StatAllocReplyHandle.IsValid())
|
||||
{
|
||||
Net->OnStatAllocReply.Remove(StatAllocReplyHandle);
|
||||
StatAllocReplyHandle.Reset();
|
||||
}
|
||||
}
|
||||
Super::Deinitialize();
|
||||
}
|
||||
|
||||
UZeusNetworkSubsystem* UZMMOAttributeNetworkHandler::GetZeusNetSubsystem() const
|
||||
{
|
||||
const UWorld* World = GetWorld();
|
||||
if (!World) { return nullptr; }
|
||||
UGameInstance* GI = World->GetGameInstance();
|
||||
if (!GI) { return nullptr; }
|
||||
return GI->GetSubsystem<UZeusNetworkSubsystem>();
|
||||
}
|
||||
|
||||
void UZMMOAttributeNetworkHandler::HandleAttributeSnapshotFull(const FZeusAttributesPayload& Payload)
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (!World) { return; }
|
||||
UZMMOAttributeComponent* Comp = FindComponentForEntity(World, Payload.EntityId);
|
||||
if (!Comp)
|
||||
{
|
||||
// Fallback chicken-and-egg: primeiro snapshot chega antes do EntityId
|
||||
// estar seeded no componente. Aplica ao primeiro componente do
|
||||
// PlayerArray que ainda tem EntityId=0. Apos o primeiro apply, o
|
||||
// caminho normal por lookup funciona.
|
||||
const AGameStateBase* GS = World->GetGameState();
|
||||
if (GS)
|
||||
{
|
||||
for (APlayerState* PS : GS->PlayerArray)
|
||||
{
|
||||
if (!PS) { continue; }
|
||||
UZMMOAttributeComponent* Candidate = PS->FindComponentByClass<UZMMOAttributeComponent>();
|
||||
if (Candidate && Candidate->GetSnapshot().EntityId == 0)
|
||||
{
|
||||
Comp = Candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Comp)
|
||||
{
|
||||
Comp->ApplySnapshot(ToSnapshot(Payload));
|
||||
}
|
||||
}
|
||||
|
||||
void UZMMOAttributeNetworkHandler::HandleHpSpUpdate(int32 EntityId, int32 Hp, int32 Sp)
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (!World) { return; }
|
||||
UZMMOAttributeComponent* Comp = FindComponentForEntity(World, EntityId);
|
||||
if (Comp)
|
||||
{
|
||||
Comp->ApplyHpSpUpdate(Hp, Sp);
|
||||
}
|
||||
}
|
||||
|
||||
void UZMMOAttributeNetworkHandler::HandleLevelUp(int32 EntityId, int32 NewBaseLevel, int32 StatusPointDelta)
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (!World) { return; }
|
||||
UZMMOAttributeComponent* Comp = FindComponentForEntity(World, EntityId);
|
||||
if (Comp)
|
||||
{
|
||||
Comp->NotifyLevelUp(NewBaseLevel, StatusPointDelta);
|
||||
}
|
||||
}
|
||||
|
||||
void UZMMOAttributeNetworkHandler::HandleStatAllocReply(bool bAccepted, int32 Reason)
|
||||
{
|
||||
// S_ATTRIBUTE_STAT_ALLOC_OK nao traz EntityId no payload — sempre do
|
||||
// player local que fez o request. Busca o AttributeComponent via PC.
|
||||
if (UZMMOAttributeComponent* Comp = FindLocalPlayerAttributeComponent())
|
||||
{
|
||||
Comp->NotifyStatAllocReply(bAccepted, Reason);
|
||||
}
|
||||
}
|
||||
|
||||
UZMMOAttributeComponent* UZMMOAttributeNetworkHandler::FindLocalPlayerAttributeComponent() const
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (!World) { return nullptr; }
|
||||
APlayerController* PC = World->GetFirstPlayerController();
|
||||
if (!PC || !PC->PlayerState) { return nullptr; }
|
||||
return PC->PlayerState->FindComponentByClass<UZMMOAttributeComponent>();
|
||||
}
|
||||
13
Source/ZMMOAttributes/Private/ZMMOAttributesModule.cpp
Normal file
13
Source/ZMMOAttributes/Private/ZMMOAttributesModule.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "ZMMOAttributesModule.h"
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
void FZMMOAttributesModule::StartupModule()
|
||||
{
|
||||
}
|
||||
|
||||
void FZMMOAttributesModule::ShutdownModule()
|
||||
{
|
||||
}
|
||||
|
||||
IMPLEMENT_MODULE(FZMMOAttributesModule, ZMMOAttributes)
|
||||
45
Source/ZMMOAttributes/Private/ZMMOHudHpSpWidget.cpp
Normal file
45
Source/ZMMOAttributes/Private/ZMMOHudHpSpWidget.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "ZMMOHudHpSpWidget.h"
|
||||
|
||||
#include "Components/ProgressBar.h"
|
||||
#include "Components/TextBlock.h"
|
||||
#include "Internationalization/Text.h"
|
||||
|
||||
void UZMMOHudHpSpWidget::ApplySnapshot(const FZMMOAttributesSnapshot& Snapshot)
|
||||
{
|
||||
LastSnapshot = Snapshot;
|
||||
OnSnapshotApplied(Snapshot);
|
||||
}
|
||||
|
||||
void UZMMOHudHpSpWidget::ApplyHpSp(int32 Hp, int32 MaxHp, int32 Sp, int32 MaxSp)
|
||||
{
|
||||
LastSnapshot.Hp = Hp;
|
||||
LastSnapshot.MaxHp = MaxHp;
|
||||
LastSnapshot.Sp = Sp;
|
||||
LastSnapshot.MaxSp = MaxSp;
|
||||
|
||||
if (HpBar)
|
||||
{
|
||||
HpBar->SetPercent(MaxHp > 0 ? static_cast<float>(Hp) / static_cast<float>(MaxHp) : 0.f);
|
||||
}
|
||||
if (SpBar)
|
||||
{
|
||||
SpBar->SetPercent(MaxSp > 0 ? static_cast<float>(Sp) / static_cast<float>(MaxSp) : 0.f);
|
||||
}
|
||||
if (HpText)
|
||||
{
|
||||
HpText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), Hp, MaxHp)));
|
||||
}
|
||||
if (SpText)
|
||||
{
|
||||
SpText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), Sp, MaxSp)));
|
||||
}
|
||||
}
|
||||
|
||||
void UZMMOHudHpSpWidget::OnSnapshotApplied_Implementation(const FZMMOAttributesSnapshot& Snapshot)
|
||||
{
|
||||
ApplyHpSp(Snapshot.Hp, Snapshot.MaxHp, Snapshot.Sp, Snapshot.MaxSp);
|
||||
if (LevelText)
|
||||
{
|
||||
LevelText->SetText(FText::FromString(FString::Printf(TEXT("Lv %d"), Snapshot.BaseLevel)));
|
||||
}
|
||||
}
|
||||
105
Source/ZMMOAttributes/Public/ZMMOAttributeComponent.h
Normal file
105
Source/ZMMOAttributes/Public/ZMMOAttributeComponent.h
Normal file
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "ZMMOAttributeTypes.h"
|
||||
#include "ZMMOAttributeComponent.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FZMMOOnAttributesChanged, const FZMMOAttributesSnapshot&, Snapshot);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FZMMOOnHpSpChanged, int32, Hp, int32, Sp);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FZMMOOnLevelUp, int32, NewBaseLevel, int32, StatusPointDelta);
|
||||
|
||||
/// Resposta server ao C_STAT_ALLOC. Reason e' EAllocRejectReason (None=0,
|
||||
/// InvalidStat=1, InvalidAmount=2, NotEnoughPoints=3, StatCapped=4, NoSession=5).
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FZMMOOnStatAllocReply, bool, bAccepted, int32, Reason);
|
||||
|
||||
/**
|
||||
* Componente de atributos do MMO ligado ao ator que o representa em-jogo
|
||||
* (player local, proxies remotos, NPCs futuros).
|
||||
*
|
||||
* Estado authoritative vive no servidor (CharRuntimeStatus em
|
||||
* Game/MMO/Modules/AttributeSystem). Cliente apenas armazena o ultimo
|
||||
* snapshot recebido e expoe delegates para UI/HUD reagir.
|
||||
*
|
||||
* Roteamento server → componente: `UZMMOAttributeNetworkHandler`
|
||||
* (UWorldSubsystem) liga em `UZeusNetworkSubsystem::OnAttributeSnapshotFull`,
|
||||
* resolve `EntityId -> AActor*` via `UZMMOWorldSubsystem` e chama
|
||||
* `ApplySnapshot` no componente do ator.
|
||||
*/
|
||||
UCLASS(ClassGroup=(ZMMO), meta=(BlueprintSpawnableComponent),
|
||||
hidecategories=(Activation, Collision, Cooking, Object, Replication))
|
||||
class ZMMOATTRIBUTES_API UZMMOAttributeComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UZMMOAttributeComponent();
|
||||
|
||||
/// Aplica snapshot full recebido do servidor. Substitui `Current` e
|
||||
/// dispara `OnAttributesChanged` + `OnHpSpChanged` (se hp/sp mudaram).
|
||||
UFUNCTION(BlueprintCallable, Category = "Zeus|Attributes")
|
||||
void ApplySnapshot(const FZMMOAttributesSnapshot& InSnapshot);
|
||||
|
||||
/// Aplica apenas delta de HP/SP (sem mexer em outros campos).
|
||||
UFUNCTION(BlueprintCallable, Category = "Zeus|Attributes")
|
||||
void ApplyHpSpUpdate(int32 NewHp, int32 NewSp);
|
||||
|
||||
/// Notifica level up — o snapshot subsequente atualiza demais campos.
|
||||
UFUNCTION(BlueprintCallable, Category = "Zeus|Attributes")
|
||||
void NotifyLevelUp(int32 NewBaseLevel, int32 StatusPointDelta);
|
||||
|
||||
/// Notifica resposta server ao C_STAT_ALLOC. UI pode reagir (toast/SFX
|
||||
/// quando aceito, erro vermelho quando rejeitado).
|
||||
UFUNCTION(BlueprintCallable, Category = "Zeus|Attributes")
|
||||
void NotifyStatAllocReply(bool bAccepted, int32 Reason);
|
||||
|
||||
/// Envia C_STAT_ALLOC pro server. Wrapper conveniente — busca o
|
||||
/// UZeusNetworkSubsystem via GameInstance + chama SendStatAlloc.
|
||||
/// `StatId`: 0=STR, 1=AGI, 2=VIT, 3=INT, 4=DEX, 5=LUK. `Amount`: 1..10.
|
||||
UFUNCTION(BlueprintCallable, Category = "Zeus|Attributes")
|
||||
void RequestStatAlloc(int32 StatId, int32 Amount = 1);
|
||||
|
||||
/// Seed do EntityId vindo de S_SPAWN_PLAYER local. Chamado pelo
|
||||
/// `AZMMOPlayerCharacter` antes do primeiro S_ATTRIBUTE_SNAPSHOT_FULL
|
||||
/// chegar, garantindo que o NetworkHandler consiga rotear via lookup
|
||||
/// por EntityId desde o inicio.
|
||||
UFUNCTION(BlueprintCallable, Category = "Zeus|Attributes")
|
||||
void SeedEntityId(int32 InEntityId) { Current.EntityId = InEntityId; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Zeus|Attributes")
|
||||
const FZMMOAttributesSnapshot& GetSnapshot() const { return Current; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Zeus|Attributes")
|
||||
float GetHpRatio() const
|
||||
{
|
||||
return Current.MaxHp > 0 ? static_cast<float>(Current.Hp) / static_cast<float>(Current.MaxHp) : 0.f;
|
||||
}
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Zeus|Attributes")
|
||||
float GetSpRatio() const
|
||||
{
|
||||
return Current.MaxSp > 0 ? static_cast<float>(Current.Sp) / static_cast<float>(Current.MaxSp) : 0.f;
|
||||
}
|
||||
|
||||
// === Delegates Blueprint ===
|
||||
|
||||
/** Snapshot novo aplicado. Use isto na UI para refresh geral. */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Zeus|Attributes")
|
||||
FZMMOOnAttributesChanged OnAttributesChanged;
|
||||
|
||||
/** HP ou SP mudou (efêmero ou via snapshot). Para barras animadas. */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Zeus|Attributes")
|
||||
FZMMOOnHpSpChanged OnHpSpChanged;
|
||||
|
||||
/** Subiu de nível. Para efeitos visuais ("LEVEL UP!" toast). */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Zeus|Attributes")
|
||||
FZMMOOnLevelUp OnLevelUp;
|
||||
|
||||
/** Resposta ao C_STAT_ALLOC (aceito ou rejeitado). UI reage. */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Zeus|Attributes")
|
||||
FZMMOOnStatAllocReply OnStatAllocReply;
|
||||
|
||||
protected:
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes")
|
||||
FZMMOAttributesSnapshot Current;
|
||||
};
|
||||
51
Source/ZMMOAttributes/Public/ZMMOAttributeNetworkHandler.h
Normal file
51
Source/ZMMOAttributes/Public/ZMMOAttributeNetworkHandler.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/WorldSubsystem.h"
|
||||
#include "ZeusAttributesPayload.h"
|
||||
#include "ZMMOAttributeNetworkHandler.generated.h"
|
||||
|
||||
class UZeusNetworkSubsystem;
|
||||
|
||||
/**
|
||||
* Ponte entre o `UZeusNetworkSubsystem` (GameInstanceSubsystem do plugin
|
||||
* ZeusNetwork) e os `UZMMOAttributeComponent` ligados aos atores do mundo.
|
||||
*
|
||||
* Por que UWorldSubsystem (e nao GameInstanceSubsystem)?
|
||||
* - O registry de atores (`UZMMOWorldSubsystem::GetActorByEntityId`) e'
|
||||
* World-scoped, reset a cada OpenLevel.
|
||||
* - World subsystem so existe enquanto ha mundo carregado, o que e' exato
|
||||
* quando precisamos rotear snapshots (no front-end nao ha pawn).
|
||||
*
|
||||
* Lifecycle: `Initialize` binda nos 3 delegates do ZeusNetworkSubsystem;
|
||||
* `Deinitialize` unbinda. Conversao `FZeusAttributesPayload ->
|
||||
* FZMMOAttributesSnapshot` (USTRUCT Blueprint) acontece aqui.
|
||||
*/
|
||||
UCLASS()
|
||||
class ZMMOATTRIBUTES_API UZMMOAttributeNetworkHandler : public UWorldSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void Deinitialize() override;
|
||||
|
||||
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
|
||||
|
||||
private:
|
||||
void HandleAttributeSnapshotFull(const FZeusAttributesPayload& Payload);
|
||||
void HandleHpSpUpdate(int32 EntityId, int32 Hp, int32 Sp);
|
||||
void HandleLevelUp(int32 EntityId, int32 NewBaseLevel, int32 StatusPointDelta);
|
||||
void HandleStatAllocReply(bool bAccepted, int32 Reason);
|
||||
|
||||
UZeusNetworkSubsystem* GetZeusNetSubsystem() const;
|
||||
|
||||
/// Acha o AttributeComponent do player local (Pawn->PlayerState).
|
||||
/// Usado pro StatAllocReply (sem EntityId no payload).
|
||||
class UZMMOAttributeComponent* FindLocalPlayerAttributeComponent() const;
|
||||
|
||||
FDelegateHandle SnapshotFullHandle;
|
||||
FDelegateHandle HpSpUpdateHandle;
|
||||
FDelegateHandle LevelUpHandle;
|
||||
FDelegateHandle StatAllocReplyHandle;
|
||||
};
|
||||
80
Source/ZMMOAttributes/Public/ZMMOAttributeTypes.h
Normal file
80
Source/ZMMOAttributes/Public/ZMMOAttributeTypes.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "ZMMOAttributeTypes.generated.h"
|
||||
|
||||
/**
|
||||
* Snapshot completo dos atributos do char vindo do server via
|
||||
* S_ATTRIBUTE_SNAPSHOT_FULL (opcode 1200).
|
||||
*
|
||||
* Cliente NUNCA recalcula derivados — apenas exibe o que recebe. Os campos
|
||||
* HP/MaxHp/SP/MaxSp ja sao os "efetivos" (base + equip + buff somados no
|
||||
* servidor); o cliente nao ve as 3 camadas separadas.
|
||||
*
|
||||
* Espelha `FZeusAttributesPayload` do plugin ZeusNetwork + a serializacao
|
||||
* em `Server/.../AttributeService.cpp::WriteSnapshotPayload`.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct ZMMOATTRIBUTES_API FZMMOAttributesSnapshot
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// === Identidade + classe ===
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int32 EntityId = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int32 ClassId = 0;
|
||||
|
||||
// === Progressao ===
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int32 BaseLevel = 1;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int64 BaseExp = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int32 JobLevel = 1;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int64 JobExp = 0;
|
||||
|
||||
// === Stats primarios (Camada 1 base; efetivos vao em derivados) ===
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Stats") int32 Str = 1;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Stats") int32 Agi = 1;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Stats") int32 Vit = 1;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Stats") int32 Int = 1;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Stats") int32 Dex = 1;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Stats") int32 Luk = 1;
|
||||
|
||||
// === Pontos nao gastos ===
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int32 StatusPoint = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int32 SkillPoint = 0;
|
||||
|
||||
// === Pool efetivo (camadas 1+2+3 somadas no servidor) ===
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int32 Hp = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int32 MaxHp = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int32 Sp = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int32 MaxSp = 0;
|
||||
|
||||
// === Moeda ===
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int64 Money = 0;
|
||||
|
||||
// === Derivados split em pares (base, equipBonus) ===
|
||||
// Padrao RO: display mostra "ATK 51 + 0" (base + equip). Cliente NUNCA
|
||||
// recalcula — servidor envia ambos prontos no S_ATTRIBUTE_SNAPSHOT_FULL.
|
||||
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;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 MatkEquipBonus = 0;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 DefBase = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 DefEquipBonus = 0;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 MdefBase = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 MdefEquipBonus = 0;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 HitBase = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 HitEquipBonus = 0;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 FleeBase = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 FleeEquipBonus = 0;
|
||||
|
||||
/** Critico × 10 internamente (10 = 1.0). Divida por 10 para exibir. */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 CritBaseX10 = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 CritEquipBonusX10 = 0;
|
||||
|
||||
/** ASPD nao splita — calculo nao-linear via aspd_base × stats. Display direto. */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 Aspd = 0;
|
||||
};
|
||||
19
Source/ZMMOAttributes/Public/ZMMOAttributesModule.h
Normal file
19
Source/ZMMOAttributes/Public/ZMMOAttributesModule.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleInterface.h"
|
||||
|
||||
/**
|
||||
* Sub-modulo cliente do AttributeSystem.
|
||||
*
|
||||
* Espelho de `Server/ZeusServerEngine/Game/MMO/Modules/AttributeSystem/`.
|
||||
* Roda como modulo Runtime carregado em `PreDefault` para que o
|
||||
* `UAttributeComponent` esteja registrado antes do `ZMMO` instanciar o
|
||||
* player character.
|
||||
*/
|
||||
class FZMMOAttributesModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
76
Source/ZMMOAttributes/Public/ZMMOHudHpSpWidget.h
Normal file
76
Source/ZMMOAttributes/Public/ZMMOHudHpSpWidget.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "CoreMinimal.h"
|
||||
#include "ZMMOAttributeTypes.h"
|
||||
#include "ZMMOHudHpSpWidget.generated.h"
|
||||
|
||||
class UProgressBar;
|
||||
class UTextBlock;
|
||||
|
||||
/**
|
||||
* Sub-widget do HUD: barras de HP/SP + label de level. Sem auto-bind no
|
||||
* AttributeComponent — o parent (`UZMMOHudWidget`) propaga snapshots via
|
||||
* `ApplySnapshot` / `ApplyHpSpDelta` quando o player local recebe updates.
|
||||
*
|
||||
* Composicao do HUD (ver `UZMMOHudWidget`):
|
||||
* WBP_HUD (UZMMOHudWidget root)
|
||||
* |-- HpSpBar (UZMMOHudHpSpWidget — este)
|
||||
* |-- PlayerInfo (futuro — nome/job/level)
|
||||
* |-- Minimap (futuro)
|
||||
* |-- QuickBar (futuro — hotkeys)
|
||||
* |-- Buffs (futuro — icones SC)
|
||||
* |-- Chat (futuro)
|
||||
* |-- TargetInfo (futuro — mob/player targetado)
|
||||
*
|
||||
* Cada sub-widget tem responsabilidade unica (SRP). O parent (UZMMOHudWidget)
|
||||
* faz binding com AttributeComponent e propaga deltas.
|
||||
*
|
||||
* Convencao `meta=(BindWidget)`: o WBP filho deve ter widgets com nomes
|
||||
* exatos (HpBar, SpBar). `BindWidgetOptional` permite omitir.
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable, BlueprintType)
|
||||
class ZMMOATTRIBUTES_API UZMMOHudHpSpWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Atualiza todos os campos a partir do snapshot. Chamado pelo UZMMOHudWidget. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Zeus|HUD")
|
||||
void ApplySnapshot(const FZMMOAttributesSnapshot& Snapshot);
|
||||
|
||||
/** Atualiza apenas HP/SP (sem mexer no level/text). Otimizacao do tick. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Zeus|HUD")
|
||||
void ApplyHpSp(int32 Hp, int32 MaxHp, int32 Sp, int32 MaxSp);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Hook BlueprintNativeEvent para skin visual reagir a um snapshot novo
|
||||
* (cores, animacao de level up, etc.). Impl default em C++ apenas
|
||||
* atualiza progress bars + textos via BindWidget/BindWidgetOptional.
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "Zeus|HUD")
|
||||
void OnSnapshotApplied(const FZMMOAttributesSnapshot& Snapshot);
|
||||
virtual void OnSnapshotApplied_Implementation(const FZMMOAttributesSnapshot& Snapshot);
|
||||
|
||||
// === BindWidget — devem existir no WBP com EXATAMENTE estes nomes ===
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidget), Category = "Zeus|HUD")
|
||||
UProgressBar* HpBar = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidget), Category = "Zeus|HUD")
|
||||
UProgressBar* SpBar = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD")
|
||||
UTextBlock* HpText = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD")
|
||||
UTextBlock* SpText = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD")
|
||||
UTextBlock* LevelText = nullptr;
|
||||
|
||||
private:
|
||||
/// Ultimo snapshot recebido (cache pra ApplyHpSp manter MaxHp/MaxSp). */
|
||||
UPROPERTY(Transient)
|
||||
FZMMOAttributesSnapshot LastSnapshot;
|
||||
};
|
||||
31
Source/ZMMOAttributes/ZMMOAttributes.Build.cs
Normal file
31
Source/ZMMOAttributes/ZMMOAttributes.Build.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class ZMMOAttributes : ModuleRules
|
||||
{
|
||||
public ZMMOAttributes(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[] {
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"UMG",
|
||||
"Slate",
|
||||
"CommonUI",
|
||||
"CommonInput",
|
||||
"ZeusNetwork"
|
||||
});
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] {
|
||||
"SlateCore"
|
||||
});
|
||||
|
||||
// Dependencias entre modulos (vide
|
||||
// Server/ZeusServerEngine/Game/MMO/Modules/AttributeSystem/module.json
|
||||
// — espelho do manifest no cliente):
|
||||
// - engine: Core, CoreUObject, Engine
|
||||
// - modules: [] (este e' modulo de base do MMO)
|
||||
// - plugin ZeusNetwork: para opcodes + delegates
|
||||
}
|
||||
}
|
||||
19
Source/ZMMOAttributes/module.json
Normal file
19
Source/ZMMOAttributes/module.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "ZMMOAttributes",
|
||||
"gameType": "MMO",
|
||||
"version": "0.1.0",
|
||||
"side": "client",
|
||||
"dependencies": {
|
||||
"engine": ["Core", "CoreUObject", "Engine", "UMG", "Slate", "CommonUI", "CommonInput"],
|
||||
"plugins": ["ZeusNetwork"],
|
||||
"modules": []
|
||||
},
|
||||
"publicHeaders": [
|
||||
"ZMMOAttributesModule.h",
|
||||
"ZMMOAttributeTypes.h",
|
||||
"ZMMOAttributeComponent.h",
|
||||
"ZMMOAttributeNetworkHandler.h"
|
||||
],
|
||||
"loadOrder": 100,
|
||||
"description": "Cliente do AttributeSystem do MMO. UActorComponent + UWorldSubsystem que recebe S_ATTRIBUTE_SNAPSHOT_FULL/HP_SP_UPDATE/LEVEL_UP do ZeusNetworkSubsystem e expõe pra UI/HUD."
|
||||
}
|
||||
5
Source/ZMMOJobs/Private/ZMMOJobDataAsset.cpp
Normal file
5
Source/ZMMOJobs/Private/ZMMOJobDataAsset.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#include "ZMMOJobDataAsset.h"
|
||||
|
||||
// Implementacao vazia — todas as props sao UPROPERTY EditDefaultsOnly e
|
||||
// vivem inline no .uasset. Override de GetPrimaryAssetId() esta no header
|
||||
// (constexpr-friendly).
|
||||
54
Source/ZMMOJobs/Private/ZMMOJobsLibrary.cpp
Normal file
54
Source/ZMMOJobs/Private/ZMMOJobsLibrary.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "ZMMOJobsLibrary.h"
|
||||
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/World.h"
|
||||
#include "ZMMOJobDataAsset.h"
|
||||
#include "ZMMOJobsSubsystem.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
/// Resolve Subsystem via WorldContextObject. Retorna nullptr em contextos
|
||||
/// invalidos (CDO em editor, world em destruicao).
|
||||
UZMMOJobsSubsystem* ResolveSubsystem(const UObject* WorldContextObject)
|
||||
{
|
||||
if (WorldContextObject == nullptr) { return nullptr; }
|
||||
const UWorld* World = WorldContextObject->GetWorld();
|
||||
if (World == nullptr) { return nullptr; }
|
||||
UGameInstance* GI = World->GetGameInstance();
|
||||
if (GI == nullptr) { return nullptr; }
|
||||
return GI->GetSubsystem<UZMMOJobsSubsystem>();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
FText UZMMOJobsLibrary::GetJobDisplayName(const UObject* WorldContextObject, int32 ClassId)
|
||||
{
|
||||
if (UZMMOJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
|
||||
{
|
||||
if (UZMMOJobDataAsset* Job = Sub->GetJobData(ClassId))
|
||||
{
|
||||
return Job->DisplayName.IsEmpty()
|
||||
? FText::FromString(Job->TechnicalName)
|
||||
: Job->DisplayName;
|
||||
}
|
||||
}
|
||||
// Fallback: nao crasha UI se ClassId desconhecido.
|
||||
return FText::FromString(FString::Printf(TEXT("Classe %d"), ClassId));
|
||||
}
|
||||
|
||||
UZMMOJobDataAsset* UZMMOJobsLibrary::GetJobData(const UObject* WorldContextObject, int32 ClassId)
|
||||
{
|
||||
if (UZMMOJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
|
||||
{
|
||||
return Sub->GetJobData(ClassId);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UZMMOJobsLibrary::IsJobRegistered(const UObject* WorldContextObject, int32 ClassId)
|
||||
{
|
||||
if (UZMMOJobsSubsystem* Sub = ResolveSubsystem(WorldContextObject))
|
||||
{
|
||||
return Sub->GetJobData(ClassId) != nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
13
Source/ZMMOJobs/Private/ZMMOJobsModule.cpp
Normal file
13
Source/ZMMOJobs/Private/ZMMOJobsModule.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "ZMMOJobsModule.h"
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
void FZMMOJobsModule::StartupModule()
|
||||
{
|
||||
}
|
||||
|
||||
void FZMMOJobsModule::ShutdownModule()
|
||||
{
|
||||
}
|
||||
|
||||
IMPLEMENT_MODULE(FZMMOJobsModule, ZMMOJobs)
|
||||
67
Source/ZMMOJobs/Private/ZMMOJobsSubsystem.cpp
Normal file
67
Source/ZMMOJobs/Private/ZMMOJobsSubsystem.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "ZMMOJobsSubsystem.h"
|
||||
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "ZMMOJobDataAsset.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogZMMOJobs, Log, All);
|
||||
|
||||
void UZMMOJobsSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
LoadJobsFromAssetRegistry();
|
||||
UE_LOG(LogZMMOJobs, Log, TEXT("[ZMMOJobsSubsystem] Initialize: %d job(s) carregados"), CachedJobs.Num());
|
||||
}
|
||||
|
||||
void UZMMOJobsSubsystem::Deinitialize()
|
||||
{
|
||||
CachedJobs.Empty();
|
||||
Super::Deinitialize();
|
||||
}
|
||||
|
||||
void UZMMOJobsSubsystem::LoadJobsFromAssetRegistry()
|
||||
{
|
||||
FAssetRegistryModule& AssetRegistryModule =
|
||||
FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
||||
IAssetRegistry& AR = AssetRegistryModule.Get();
|
||||
|
||||
// Editor pode chamar antes de scan inicial. Standalone/PIE scan ja' rodou.
|
||||
// SearchAllAssets(false) = nao-blocking; assume cache populado em runtime.
|
||||
#if WITH_EDITOR
|
||||
AR.SearchAllAssets(true); // bloqueia editor; ok em Initialize
|
||||
#endif
|
||||
|
||||
TArray<FAssetData> Assets;
|
||||
const FTopLevelAssetPath ClassPath(UZMMOJobDataAsset::StaticClass()->GetClassPathName());
|
||||
AR.GetAssetsByClass(ClassPath, Assets, /*bSearchSubClasses*/ true);
|
||||
|
||||
for (const FAssetData& AssetData : Assets)
|
||||
{
|
||||
UZMMOJobDataAsset* Job = Cast<UZMMOJobDataAsset>(AssetData.GetAsset());
|
||||
if (Job == nullptr) { continue; }
|
||||
|
||||
if (CachedJobs.Contains(Job->ClassId))
|
||||
{
|
||||
UE_LOG(LogZMMOJobs, Warning,
|
||||
TEXT("[ZMMOJobsSubsystem] Duplicate ClassId=%d (existing=%s, new=%s) — ignorando segundo"),
|
||||
Job->ClassId,
|
||||
*CachedJobs[Job->ClassId]->GetName(),
|
||||
*Job->GetName());
|
||||
continue;
|
||||
}
|
||||
|
||||
CachedJobs.Add(Job->ClassId, Job);
|
||||
UE_LOG(LogZMMOJobs, Verbose,
|
||||
TEXT("[ZMMOJobsSubsystem] Cached classId=%d name=%s display=%s"),
|
||||
Job->ClassId, *Job->TechnicalName, *Job->DisplayName.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
UZMMOJobDataAsset* UZMMOJobsSubsystem::GetJobData(int32 ClassId) const
|
||||
{
|
||||
if (const TObjectPtr<UZMMOJobDataAsset>* Found = CachedJobs.Find(ClassId))
|
||||
{
|
||||
return Found->Get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
75
Source/ZMMOJobs/Public/ZMMOJobDataAsset.h
Normal file
75
Source/ZMMOJobs/Public/ZMMOJobDataAsset.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "ZMMOJobDataAsset.generated.h"
|
||||
|
||||
class UTexture2D;
|
||||
class USoundBase;
|
||||
class UAnimInstance;
|
||||
class UAnimMontage;
|
||||
|
||||
/**
|
||||
* Dados visuais/UI de UM job (classe). 1 .uasset por job em
|
||||
* `Content/ZMMO/Data/Jobs/DA_Job_<Nome>.uasset`.
|
||||
*
|
||||
* ClassId eh a CHAVE — bate com `classId` do snapshot do server (rathena
|
||||
* pc.hpp `job_class`, ex: Novice=0, Swordsman=1, Mage=2, ...).
|
||||
*
|
||||
* Soft pointers (TSoftObjectPtr) sao usados pra assets pesados (textures,
|
||||
* sounds) — lazy-loaded quando precisar (level up, hover icon). DataAsset
|
||||
* em si fica em memoria depois do scan do Subsystem.
|
||||
*
|
||||
* Async pointers (TSubclassOf) sao sync — anim BP precisa estar carregada
|
||||
* quando o player spawna.
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class ZMMOJOBS_API UZMMOJobDataAsset : public UPrimaryDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/// Chave estavel (bate com `Config/Jobs/<Name>.json:Id` no server e com
|
||||
/// `FZMMOAttributesSnapshot::ClassId` recebido do server).
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
|
||||
int32 ClassId = 0;
|
||||
|
||||
/// Nome tecnico estavel (en-US). Ex: "Novice", "Swordsman". Aparece em
|
||||
/// logs e telemetria — NUNCA exibido pro player.
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
|
||||
FString TechnicalName;
|
||||
|
||||
/// Nome localizado exibido na UI (pt-BR no projeto base). Ex: "Aprendiz".
|
||||
/// Pode usar FText Localization tables pra multi-idioma futuro.
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job")
|
||||
FText DisplayName;
|
||||
|
||||
/// 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")
|
||||
TSoftObjectPtr<UTexture2D> JobIcon;
|
||||
|
||||
/// AnimInstance class override (opcional). Quando nao-null, AnimBP
|
||||
/// padrao do PlayerCharacter eh substituido por esta classe no spawn
|
||||
/// (jobs com move set diferente — Monge bate, Assassin furtivo, etc.).
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job|Anim")
|
||||
TSubclassOf<UAnimInstance> DefaultAnimInstanceClass;
|
||||
|
||||
/// Montage de "job change" (animacao de transformacao). Tocada quando
|
||||
/// player promove de Novice → 1a classe. V1 nao implementado.
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job|Anim")
|
||||
TSoftObjectPtr<UAnimMontage> JobChangeMontage;
|
||||
|
||||
/// Som de level up override. Quando null, HUD usa som generico do projeto.
|
||||
/// Soft ptr: lazy load no primeiro level up.
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Zeus|Job|Audio")
|
||||
TSoftObjectPtr<USoundBase> LevelUpSoundCue;
|
||||
|
||||
/// PrimaryAssetType pro AssetManager (precisa pra GetPrimaryAssetIdList).
|
||||
/// Hardcoded "ZMMOJob" — registry classifica todos os DAs deste tipo.
|
||||
virtual FPrimaryAssetId GetPrimaryAssetId() const override
|
||||
{
|
||||
return FPrimaryAssetId(FPrimaryAssetType(TEXT("ZMMOJob")),
|
||||
*FString::Printf(TEXT("Job_%d_%s"), ClassId, *TechnicalName));
|
||||
}
|
||||
};
|
||||
39
Source/ZMMOJobs/Public/ZMMOJobsLibrary.h
Normal file
39
Source/ZMMOJobs/Public/ZMMOJobsLibrary.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "ZMMOJobsLibrary.generated.h"
|
||||
|
||||
class UZMMOJobDataAsset;
|
||||
|
||||
/**
|
||||
* Helpers Blueprint pra UI/HUD acessar dados de job sem precisar pegar o
|
||||
* Subsystem manualmente. Todos usam WorldContextObject pra resolver
|
||||
* GameInstance → Subsystem internamente.
|
||||
*
|
||||
* Padrao de uso em UMG:
|
||||
* - Text binding: `JobsLibrary::GetJobDisplayName(Self, AttrComp.Current.ClassId)`
|
||||
* - Image binding: `JobsLibrary::GetJobIcon(Self, ClassId).LoadSynchronous()`
|
||||
*/
|
||||
UCLASS()
|
||||
class ZMMOJOBS_API UZMMOJobsLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/// Nome localizado do job. Fallback "Classe N" se ClassId nao tem DA
|
||||
/// correspondente (job nao registrado no Content/ZMMO/Data/Jobs/).
|
||||
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs",
|
||||
meta = (WorldContext = "WorldContextObject"))
|
||||
static FText GetJobDisplayName(const UObject* WorldContextObject, int32 ClassId);
|
||||
|
||||
/// DataAsset completo (pode ser nullptr — caller checa).
|
||||
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs",
|
||||
meta = (WorldContext = "WorldContextObject"))
|
||||
static UZMMOJobDataAsset* GetJobData(const UObject* WorldContextObject, int32 ClassId);
|
||||
|
||||
/// Helper de UI: retorna true se ClassId tem DA registrado.
|
||||
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs",
|
||||
meta = (WorldContext = "WorldContextObject"))
|
||||
static bool IsJobRegistered(const UObject* WorldContextObject, int32 ClassId);
|
||||
};
|
||||
23
Source/ZMMOJobs/Public/ZMMOJobsModule.h
Normal file
23
Source/ZMMOJobs/Public/ZMMOJobsModule.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleInterface.h"
|
||||
|
||||
/**
|
||||
* Sub-modulo cliente do JobsSystem.
|
||||
*
|
||||
* Espelho de `Server/ZeusServerEngine/Game/MMO/Modules/JobsSystem/`. Roda
|
||||
* como modulo Runtime em LoadingPhase PreDefault para que o
|
||||
* `UZMMOJobsSubsystem` esteja registrado antes do ZMMO core spawnar a HUD.
|
||||
*
|
||||
* Responsabilidades:
|
||||
* - Dados visuais por job: DisplayName localizado, JobIcon, anim override
|
||||
* - Lookup classId → UZMMOJobDataAsset via Subsystem
|
||||
* - Library Blueprint pra HUD/UI consumir
|
||||
*/
|
||||
class FZMMOJobsModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
52
Source/ZMMOJobs/Public/ZMMOJobsSubsystem.h
Normal file
52
Source/ZMMOJobs/Public/ZMMOJobsSubsystem.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "ZMMOJobsSubsystem.generated.h"
|
||||
|
||||
class UZMMOJobDataAsset;
|
||||
|
||||
/**
|
||||
* Cache central de UZMMOJobDataAsset por ClassId. Vive como GameInstance
|
||||
* Subsystem — startup automatico quando GameInstance inicializa (PIE,
|
||||
* standalone, packaged). Sobrevive a map transitions.
|
||||
*
|
||||
* Initialize:
|
||||
* 1. Carrega via AssetRegistry: scan de UZMMOJobDataAsset em todo Content
|
||||
* 2. Cacheia em TMap<int32, UZMMOJobDataAsset*> por ClassId
|
||||
* 3. Log: quantos jobs encontrou
|
||||
*
|
||||
* Uso:
|
||||
* - C++: GameInstance->GetSubsystem<UZMMOJobsSubsystem>()->GetJobData(0)
|
||||
* - BP: UZMMOJobsLibrary::GetJobData(WorldContext, 0)
|
||||
*
|
||||
* Fase 7+ (Mod support): SCANEAR PrimaryAssetType="ZMMOJob" do AssetManager
|
||||
* permite mods adicionarem DAs proprios sem mexer no source.
|
||||
*/
|
||||
UCLASS()
|
||||
class ZMMOJOBS_API UZMMOJobsSubsystem : public UGameInstanceSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void Deinitialize() override;
|
||||
|
||||
/// Lookup por classId. Retorna nullptr se ClassId nao tem DA correspondente
|
||||
/// (caller usa Library com fallback FText).
|
||||
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs")
|
||||
UZMMOJobDataAsset* GetJobData(int32 ClassId) const;
|
||||
|
||||
/// Total de jobs cacheados (diagnostico).
|
||||
UFUNCTION(BlueprintPure, Category = "Zeus|Jobs")
|
||||
int32 GetJobCount() const { return CachedJobs.Num(); }
|
||||
|
||||
private:
|
||||
/// Scan AssetRegistry + popula CachedJobs. Sincronizo (V1) — chamado UMA
|
||||
/// vez no Initialize. Pra 50 jobs eh aceitavel; se crescer pra 200+ pode
|
||||
/// virar async no futuro.
|
||||
void LoadJobsFromAssetRegistry();
|
||||
|
||||
UPROPERTY()
|
||||
TMap<int32, TObjectPtr<UZMMOJobDataAsset>> CachedJobs;
|
||||
};
|
||||
26
Source/ZMMOJobs/ZMMOJobs.Build.cs
Normal file
26
Source/ZMMOJobs/ZMMOJobs.Build.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class ZMMOJobs : ModuleRules
|
||||
{
|
||||
public ZMMOJobs(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[] {
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine"
|
||||
});
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] {
|
||||
"AssetRegistry" // FAssetRegistryModule pra scan de DA_Job_*
|
||||
});
|
||||
|
||||
// Espelho do `Server/.../JobsSystem/module.json` (modulo nucleo MMO).
|
||||
// Cliente roda em LoadingPhase PreDefault — Subsystem inicializa
|
||||
// ANTES de o ZMMO core criar a player character / HUD.
|
||||
//
|
||||
// Sem dependencia em ZMMOAttributes: server envia classId no snapshot,
|
||||
// cliente faz lookup local. Mantem o peer-module pattern.
|
||||
}
|
||||
}
|
||||
18
Source/ZMMOJobs/module.json
Normal file
18
Source/ZMMOJobs/module.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "ZMMOJobs",
|
||||
"gameType": "MMO",
|
||||
"version": "0.1.0",
|
||||
"side": "client",
|
||||
"dependencies": {
|
||||
"engine": ["Core", "CoreUObject", "Engine", "AssetRegistry"],
|
||||
"modules": []
|
||||
},
|
||||
"publicHeaders": [
|
||||
"ZMMOJobsModule.h",
|
||||
"ZMMOJobDataAsset.h",
|
||||
"ZMMOJobsSubsystem.h",
|
||||
"ZMMOJobsLibrary.h"
|
||||
],
|
||||
"loadingPhase": "PreDefault",
|
||||
"description": "Sub-modulo cliente do GameType MMO — dados visuais/UI por job (DisplayName localizado, ícone, anim override, som de level up). Espelha JobsSystem do server mas focado em apresentacao. NAO depende de ZMMOAttributes — server envia classId no snapshot, cliente faz lookup local. Pattern peer-module estabelecido no PR #9."
|
||||
}
|
||||
@@ -8,6 +8,16 @@
|
||||
"Name": "ZMMO",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default"
|
||||
},
|
||||
{
|
||||
"Name": "ZMMOAttributes",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "PreDefault"
|
||||
},
|
||||
{
|
||||
"Name": "ZMMOJobs",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "PreDefault"
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
|
||||
Reference in New Issue
Block a user