feat(attributes): cliente Fase 1 — sub-modulo ZMMOAttributes + HUD HP/SP
Espelho do AttributeSystem do servidor (Game/MMO/Modules/AttributeSystem).
Sub-modulo C++ Source/ZMMOAttributes/ (Runtime, LoadingPhase=PreDefault)
isolando o cliente do AttributeSystem em pasta propria — pavimentando o
caminho para futuros Inventory/Skills/Combat virem como sub-modulos peer.
Componentes:
- FZMMOAttributesSnapshot (USTRUCT Blueprint, espelha
FZeusAttributesPayload do plugin ZeusNetwork)
- UZMMOAttributeComponent (UActorComponent ligado ao player):
ApplySnapshot / ApplyHpSpUpdate / NotifyLevelUp + 3 delegates
dinamicos (OnAttributesChanged / OnHpSpChanged / OnLevelUp) +
helpers GetHpRatio / GetSpRatio
- UZMMOAttributeNetworkHandler (UWorldSubsystem): ponte
UZeusNetworkSubsystem -> componente via lookup por EntityId
- UZMMOHudHpSpWidget (UUserWidget base): BindWidget (HpBar, SpBar) +
BindWidgetOptional (HpText, SpText, LevelText). NativeConstruct
binda nos delegates do componente; NativeDestruct desliga.
OnSnapshotApplied / OnHpSpDelta (BlueprintNativeEvent) com impl
C++ atualizando progress bars e textos.
Integracao no PlayerCharacter:
- CreateDefaultSubobject<UZMMOAttributeComponent> no ctor
- HudHpSpWidgetClass (TSubclassOf) resolvido via
ConstructorHelpers::FClassFinder em /Game/ZMMO/UI/HUD/WBP_HUD_HpSpBar
- HandleLocalSpawnReady seed do EntityId no componente + spawn do HUD
(CreateWidget + AddToViewport + BindToAttributeComponent)
- EndPlay remove widget do parent
WBP_HUD_HpSpBar (UMG, criado via MCP):
- Overlay root + SizeBox (320x120) + VerticalBox
- 5 widgets BindWidget: LevelText, HpBar (vermelho), HpText,
SpBar (azul), SpText
- Herda de UZMMOHudHpSpWidget
Verificacao (smoke test):
- PIE login -> server envia S_ATTRIBUTE_SNAPSHOT_FULL
- Cliente recebe via OnAttributeSnapshotFull -> handler roteia pra
componente do entity -> ApplySnapshot dispara OnAttributesChanged ->
widget atualiza ProgressBars + TextBlocks
- HUD aparece no canto inferior esquerdo da tela com valores
correspondentes ao DB
Refs:
- Plano: C:/Users/mateu/.claude/plans/temos-uma-lab-input-nested-diffie.md
- Server companion commit: feat(attributes): AttributeSystem modular
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
86
Source/ZMMOAttributes/Public/ZMMOAttributeComponent.h
Normal file
86
Source/ZMMOAttributes/Public/ZMMOAttributeComponent.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/// 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;
|
||||
|
||||
protected:
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes")
|
||||
FZMMOAttributesSnapshot Current;
|
||||
};
|
||||
45
Source/ZMMOAttributes/Public/ZMMOAttributeNetworkHandler.h
Normal file
45
Source/ZMMOAttributes/Public/ZMMOAttributeNetworkHandler.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#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);
|
||||
|
||||
UZeusNetworkSubsystem* GetZeusNetSubsystem() const;
|
||||
|
||||
FDelegateHandle SnapshotFullHandle;
|
||||
FDelegateHandle HpSpUpdateHandle;
|
||||
FDelegateHandle LevelUpHandle;
|
||||
};
|
||||
63
Source/ZMMOAttributes/Public/ZMMOAttributeTypes.h
Normal file
63
Source/ZMMOAttributes/Public/ZMMOAttributeTypes.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#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 (efetivos; servidor sempre recalcula) ===
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 Atk = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 Matk = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 Def = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 Mdef = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 Hit = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 Flee = 0;
|
||||
/** Critico × 10 internamente (10 = 1.0). Divida por 10 para exibir. */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 CritX10 = 0;
|
||||
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;
|
||||
};
|
||||
85
Source/ZMMOAttributes/Public/ZMMOHudHpSpWidget.h
Normal file
85
Source/ZMMOAttributes/Public/ZMMOHudHpSpWidget.h
Normal file
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "CoreMinimal.h"
|
||||
#include "ZMMOAttributeTypes.h"
|
||||
#include "ZMMOHudHpSpWidget.generated.h"
|
||||
|
||||
class UProgressBar;
|
||||
class UTextBlock;
|
||||
class UZMMOAttributeComponent;
|
||||
|
||||
/**
|
||||
* Widget base do HUD de atributos (HP/SP/level). Spawnada por
|
||||
* `AZMMOPlayerCharacter::BeginPlay` quando o pawn local virou autoritativo.
|
||||
*
|
||||
* Layout (cor, textos, ancoras) vem do WBP filho via UMG.
|
||||
* Comportamento (binding com AttributeComponent + refresh on delegate)
|
||||
* vive aqui em C++ — permite que outros modulos consumam esta API
|
||||
* sem precisar abrir o WBP. Sub-classe via Blueprint apenas para skin
|
||||
* visual e disposicao dos sub-widgets.
|
||||
*
|
||||
* Convencao `meta=(BindWidget)`: o WBP filho DEVE ter widgets com os
|
||||
* mesmos nomes (HpBar, SpBar). `BindWidgetOptional` permite ao WBP
|
||||
* omitir o widget (ex.: HUD compacto sem texto numerico).
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable, BlueprintType)
|
||||
class ZMMOATTRIBUTES_API UZMMOHudHpSpWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Liga o widget a um `UZMMOAttributeComponent`. Subscreve aos delegates
|
||||
* e faz um refresh imediato com o snapshot corrente. Idempotente —
|
||||
* chamar com outro componente desliga do anterior.
|
||||
* Se `InComponent` for nullptr, desliga sem religar.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Zeus|HUD")
|
||||
void BindToAttributeComponent(UZMMOAttributeComponent* InComponent);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Zeus|HUD")
|
||||
UZMMOAttributeComponent* GetBoundComponent() const { return BoundComponent.Get(); }
|
||||
|
||||
protected:
|
||||
virtual void NativeDestruct() override;
|
||||
|
||||
/**
|
||||
* Hook BlueprintNativeEvent para skin visual reagir a um snapshot novo
|
||||
* (cores, animacao de level up, etc.). Implementacao 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);
|
||||
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "Zeus|HUD")
|
||||
void OnHpSpDelta(int32 Hp, int32 Sp);
|
||||
virtual void OnHpSpDelta_Implementation(int32 Hp, int32 Sp);
|
||||
|
||||
// === 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:
|
||||
UFUNCTION()
|
||||
void HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleHpSpChanged(int32 Hp, int32 Sp);
|
||||
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UZMMOAttributeComponent> BoundComponent;
|
||||
};
|
||||
Reference in New Issue
Block a user