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:
2026-05-23 04:00:32 -03:00
parent 0e96956a17
commit 7d83dd800f
16 changed files with 777 additions and 1 deletions

View 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;
};