feat(client): AttributeSystem (Fase 1-3) + ZMMOJobs + UI in-game #3
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.
@@ -13,9 +13,12 @@
|
|||||||
#include "GameFramework/SpringArmComponent.h"
|
#include "GameFramework/SpringArmComponent.h"
|
||||||
#include "InputAction.h"
|
#include "InputAction.h"
|
||||||
#include "InputActionValue.h"
|
#include "InputActionValue.h"
|
||||||
|
#include "Blueprint/UserWidget.h"
|
||||||
#include "Subsystems/WorldSubsystem.h"
|
#include "Subsystems/WorldSubsystem.h"
|
||||||
#include "UObject/ConstructorHelpers.h"
|
#include "UObject/ConstructorHelpers.h"
|
||||||
#include "ZMMO.h"
|
#include "ZMMO.h"
|
||||||
|
#include "ZMMOAttributeComponent.h"
|
||||||
|
#include "ZMMOHudHpSpWidget.h"
|
||||||
#include "ZMMOWorldSubsystem.h"
|
#include "ZMMOWorldSubsystem.h"
|
||||||
#include "ZeusNetworkSubsystem.h"
|
#include "ZeusNetworkSubsystem.h"
|
||||||
#include "UI/FrontEnd/UIFrontEndFlowSubsystem.h"
|
#include "UI/FrontEnd/UIFrontEndFlowSubsystem.h"
|
||||||
@@ -52,6 +55,13 @@ AZMMOPlayerCharacter::AZMMOPlayerCharacter()
|
|||||||
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
|
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
|
||||||
FollowCamera->bUsePawnControlRotation = false;
|
FollowCamera->bUsePawnControlRotation = false;
|
||||||
|
|
||||||
|
// AttributeSystem cliente (sub-modulo ZMMOAttributes). Estado authoritative
|
||||||
|
// vive no servidor (Game/MMO/Modules/AttributeSystem); este componente
|
||||||
|
// apenas armazena o ultimo snapshot recebido e expoe delegates pro HUD.
|
||||||
|
// Roteamento server -> componente via UZMMOAttributeNetworkHandler
|
||||||
|
// (UWorldSubsystem em ZMMOAttributes/Private/).
|
||||||
|
AttributeComponent = CreateDefaultSubobject<UZMMOAttributeComponent>(TEXT("AttributeComponent"));
|
||||||
|
|
||||||
// Defaults visuais (mesh + AnimBP) — alinhados ao ZClientMMO. Permitem ao
|
// Defaults visuais (mesh + AnimBP) — alinhados ao ZClientMMO. Permitem ao
|
||||||
// motor spawnar AZMMOPlayerCharacter directamente como DefaultPawnClass do
|
// motor spawnar AZMMOPlayerCharacter directamente como DefaultPawnClass do
|
||||||
// AZMMOGameMode sem exigir um BP filho. Um BP filho continua opcional para
|
// AZMMOGameMode sem exigir um BP filho. Um BP filho continua opcional para
|
||||||
@@ -101,6 +111,20 @@ AZMMOPlayerCharacter::AZMMOPlayerCharacter()
|
|||||||
if (LookActionAsset.Succeeded()) { LookAction = LookActionAsset.Object; }
|
if (LookActionAsset.Succeeded()) { LookAction = LookActionAsset.Object; }
|
||||||
if (MouseLookActionAsset.Succeeded()) { MouseLookAction = MouseLookActionAsset.Object; }
|
if (MouseLookActionAsset.Succeeded()) { MouseLookAction = MouseLookActionAsset.Object; }
|
||||||
if (JumpActionAsset.Succeeded()) { JumpAction = JumpActionAsset.Object; }
|
if (JumpActionAsset.Succeeded()) { JumpAction = JumpActionAsset.Object; }
|
||||||
|
|
||||||
|
// HUD default — WBP_HUD_HpSpBar herda de UZMMOHudHpSpWidget. BP filho do
|
||||||
|
// player character pode sobrescrever via EditAnywhere.
|
||||||
|
static ConstructorHelpers::FClassFinder<UZMMOHudHpSpWidget> HudClassFinder(
|
||||||
|
TEXT("/Game/ZMMO/UI/HUD/WBP_HUD_HpSpBar"));
|
||||||
|
if (HudClassFinder.Succeeded())
|
||||||
|
{
|
||||||
|
HudHpSpWidgetClass = HudClassFinder.Class;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMOPlayer, Warning,
|
||||||
|
TEXT("WBP_HUD_HpSpBar not found at /Game/ZMMO/UI/HUD/ — HUD desactivado"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AZMMOPlayerCharacter::BeginPlay()
|
void AZMMOPlayerCharacter::BeginPlay()
|
||||||
@@ -140,6 +164,11 @@ void AZMMOPlayerCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|||||||
{
|
{
|
||||||
UnbindZeusSpawnDelegate();
|
UnbindZeusSpawnDelegate();
|
||||||
ZeusNetwork = nullptr;
|
ZeusNetwork = nullptr;
|
||||||
|
if (HudHpSpWidget)
|
||||||
|
{
|
||||||
|
HudHpSpWidget->RemoveFromParent();
|
||||||
|
HudHpSpWidget = nullptr;
|
||||||
|
}
|
||||||
Super::EndPlay(EndPlayReason);
|
Super::EndPlay(EndPlayReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,6 +372,50 @@ void AZMMOPlayerCharacter::HandleLocalSpawnReady(const int32 InEntityId, const F
|
|||||||
WorldSubsystem->RegisterLocalEntity(EntityIdAsInt64, this);
|
WorldSubsystem->RegisterLocalEntity(EntityIdAsInt64, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Seed do EntityId no AttributeComponent: o S_ATTRIBUTE_SNAPSHOT_FULL ja
|
||||||
|
// pode estar em transito (server envia logo apos S_SPAWN_PLAYER) — sem
|
||||||
|
// este seed o NetworkHandler nao consegue achar o componente certo via
|
||||||
|
// lookup por EntityId.
|
||||||
|
if (AttributeComponent)
|
||||||
|
{
|
||||||
|
AttributeComponent->SeedEntityId(InEntityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn do HUD de atributos (HP/SP/level). Apenas player local (este
|
||||||
|
// metodo so' roda quando bIsLocal=true em HandleZeusPlayerSpawned).
|
||||||
|
UE_LOG(LogZMMOPlayer, Warning,
|
||||||
|
TEXT("HUD spawn check: HudClass=%s AttrComp=%s HudPrev=%s Controller=%s"),
|
||||||
|
HudHpSpWidgetClass ? *HudHpSpWidgetClass->GetName() : TEXT("NULL"),
|
||||||
|
AttributeComponent ? TEXT("OK") : TEXT("NULL"),
|
||||||
|
HudHpSpWidget ? TEXT("ALREADY_EXISTS") : TEXT("none"),
|
||||||
|
GetController() ? *GetController()->GetName() : TEXT("NULL"));
|
||||||
|
|
||||||
|
if (HudHpSpWidgetClass && AttributeComponent && !HudHpSpWidget)
|
||||||
|
{
|
||||||
|
APlayerController* PC = Cast<APlayerController>(GetController());
|
||||||
|
if (!PC)
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMOPlayer, Warning, TEXT("HUD spawn: GetController() retornou null/nao-PC. Adiando."));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HudHpSpWidget = CreateWidget<UZMMOHudHpSpWidget>(PC, HudHpSpWidgetClass);
|
||||||
|
if (!HudHpSpWidget)
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMOPlayer, Error, TEXT("HUD spawn: CreateWidget retornou NULL para classe %s"),
|
||||||
|
*HudHpSpWidgetClass->GetName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HudHpSpWidget->AddToViewport(/*ZOrder=*/10);
|
||||||
|
HudHpSpWidget->BindToAttributeComponent(AttributeComponent);
|
||||||
|
UE_LOG(LogZMMOPlayer, Warning,
|
||||||
|
TEXT("HUD spawn: widget criado e adicionado ao viewport (%s)"),
|
||||||
|
*HudHpSpWidget->GetName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AZMMOPlayerCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
void AZMMOPlayerCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ class USpringArmComponent;
|
|||||||
class UCameraComponent;
|
class UCameraComponent;
|
||||||
class UInputAction;
|
class UInputAction;
|
||||||
class UZeusNetworkSubsystem;
|
class UZeusNetworkSubsystem;
|
||||||
|
class UZMMOAttributeComponent;
|
||||||
|
class UZMMOHudHpSpWidget;
|
||||||
struct FInputActionValue;
|
struct FInputActionValue;
|
||||||
|
|
||||||
DECLARE_LOG_CATEGORY_EXTERN(LogZMMOPlayer, Log, All);
|
DECLARE_LOG_CATEGORY_EXTERN(LogZMMOPlayer, Log, All);
|
||||||
@@ -47,6 +49,11 @@ class ZMMO_API AZMMOPlayerCharacter : public ACharacter, public IZMMOEntityInter
|
|||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
|
||||||
UCameraComponent* FollowCamera;
|
UCameraComponent* FollowCamera;
|
||||||
|
|
||||||
|
/** Atributos do MMO (HP/SP/STR/etc). Estado authoritative no server;
|
||||||
|
* cliente apenas exibe via UI bindando em `OnAttributesChanged`. */
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
|
||||||
|
UZMMOAttributeComponent* AttributeComponent;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
UPROPERTY(EditAnywhere, Category = "Input")
|
UPROPERTY(EditAnywhere, Category = "Input")
|
||||||
UInputAction* JumpAction;
|
UInputAction* JumpAction;
|
||||||
@@ -73,6 +80,12 @@ protected:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ZMMO|Networking", meta = (ClampMin = "0.05", ClampMax = "5.0"))
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ZMMO|Networking", meta = (ClampMin = "0.05", ClampMax = "5.0"))
|
||||||
float HeartbeatIntervalSec = 0.2f;
|
float HeartbeatIntervalSec = 0.2f;
|
||||||
|
|
||||||
|
/** Classe do HUD de HP/SP. Spawnada quando o pawn local recebe S_SPAWN_PLAYER.
|
||||||
|
* Default = WBP_HUD_HpSpBar que herda de UZMMOHudHpSpWidget (criado em Fase 1
|
||||||
|
* via MCP em /Game/ZMMO/UI/HUD/). Deixar null desativa o HUD. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ZMMO|UI")
|
||||||
|
TSubclassOf<UZMMOHudHpSpWidget> HudHpSpWidgetClass;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AZMMOPlayerCharacter();
|
AZMMOPlayerCharacter();
|
||||||
|
|
||||||
@@ -89,6 +102,7 @@ public:
|
|||||||
|
|
||||||
FORCEINLINE USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
|
FORCEINLINE USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
|
||||||
FORCEINLINE UCameraComponent* GetFollowCamera() const { return FollowCamera; }
|
FORCEINLINE UCameraComponent* GetFollowCamera() const { return FollowCamera; }
|
||||||
|
FORCEINLINE UZMMOAttributeComponent* GetAttributeComponent() const { return AttributeComponent; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void BeginPlay() override;
|
virtual void BeginPlay() override;
|
||||||
@@ -159,4 +173,9 @@ private:
|
|||||||
float SendAccumulatorSec = 0.0f;
|
float SendAccumulatorSec = 0.0f;
|
||||||
float TimeSinceLastSendSec = 0.0f;
|
float TimeSinceLastSendSec = 0.0f;
|
||||||
int32 InputSequence = 0;
|
int32 InputSequence = 0;
|
||||||
|
|
||||||
|
/** Instancia do HUD spawnada no spawn local. nullptr ate o spawn ou se
|
||||||
|
* HudHpSpWidgetClass nao foi setado. */
|
||||||
|
UPROPERTY(Transient)
|
||||||
|
TObjectPtr<UZMMOHudHpSpWidget> HudHpSpWidget;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ public class ZMMO : ModuleRules
|
|||||||
"CommonUI",
|
"CommonUI",
|
||||||
"CommonInput",
|
"CommonInput",
|
||||||
"GameplayTags",
|
"GameplayTags",
|
||||||
"ZeusNetwork"
|
"ZeusNetwork",
|
||||||
|
"ZMMOAttributes"
|
||||||
});
|
});
|
||||||
|
|
||||||
PrivateDependencyModuleNames.AddRange(new string[] {
|
PrivateDependencyModuleNames.AddRange(new string[] {
|
||||||
|
|||||||
36
Source/ZMMOAttributes/Private/ZMMOAttributeComponent.cpp
Normal file
36
Source/ZMMOAttributes/Private/ZMMOAttributeComponent.cpp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#include "ZMMOAttributeComponent.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);
|
||||||
|
}
|
||||||
182
Source/ZMMOAttributes/Private/ZMMOAttributeNetworkHandler.cpp
Normal file
182
Source/ZMMOAttributes/Private/ZMMOAttributeNetworkHandler.cpp
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#include "ZMMOAttributeNetworkHandler.h"
|
||||||
|
|
||||||
|
#include "Engine/GameInstance.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "EngineUtils.h" // TActorIterator
|
||||||
|
#include "GameFramework/Actor.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.Atk = P.Atk;
|
||||||
|
S.Matk = P.Matk;
|
||||||
|
S.Def = P.Def;
|
||||||
|
S.Mdef = P.Mdef;
|
||||||
|
S.Hit = P.Hit;
|
||||||
|
S.Flee = P.Flee;
|
||||||
|
S.CritX10 = P.CritX10;
|
||||||
|
S.Aspd = P.Aspd;
|
||||||
|
return S;
|
||||||
|
}
|
||||||
|
|
||||||
|
// V1 — resolve por EntityId varrendo atores do mundo procurando um que
|
||||||
|
// tenha `UZMMOAttributeComponent`. Funciona para PIE com 1-2 players +
|
||||||
|
// proxies. Otimizacao futura: usar `UZMMOWorldSubsystem::GetActorByEntityId`
|
||||||
|
// (precisa expor) ou cache local de `EntityId -> UAttributeComponent*`.
|
||||||
|
//
|
||||||
|
// Convencao: `IZMMOEntityInterface::GetZMMOEntityId()` retorna o EntityId
|
||||||
|
// autoritativo do servidor. Nao depender da interface aqui para manter
|
||||||
|
// ZMMOAttributes sem `#include` do modulo ZMMO core — varremos `Tags`
|
||||||
|
// alternativamente, mas o caminho mais simples e' a propria component
|
||||||
|
// keys de `EntityId` no proprio componente (TODO Fase 2: armazenar
|
||||||
|
// EntityId no componente quando snapshot chega).
|
||||||
|
UZMMOAttributeComponent* FindComponentForEntity(UWorld* World, int32 EntityId)
|
||||||
|
{
|
||||||
|
if (!World || EntityId == 0) { return nullptr; }
|
||||||
|
for (TActorIterator<AActor> It(World); It; ++It)
|
||||||
|
{
|
||||||
|
AActor* Actor = *It;
|
||||||
|
if (!Actor) { continue; }
|
||||||
|
UZMMOAttributeComponent* Comp = Actor->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
// V1 fallback: aplica ao primeiro UZMMOAttributeComponent encontrado
|
||||||
|
// (player local). Isso resolve o caso do primeiro snapshot chegar
|
||||||
|
// antes do `EntityId` estar registrado no componente (que so' acontece
|
||||||
|
// apos o primeiro snapshot — chicken-and-egg). Apos o primeiro apply,
|
||||||
|
// `Current.EntityId` ja' bate e o caminho normal funciona.
|
||||||
|
for (TActorIterator<AActor> It(World); It; ++It)
|
||||||
|
{
|
||||||
|
AActor* Actor = *It;
|
||||||
|
if (!Actor) { continue; }
|
||||||
|
Comp = Actor->FindComponentByClass<UZMMOAttributeComponent>();
|
||||||
|
if (Comp && Comp->GetSnapshot().EntityId == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Comp = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
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)
|
||||||
101
Source/ZMMOAttributes/Private/ZMMOHudHpSpWidget.cpp
Normal file
101
Source/ZMMOAttributes/Private/ZMMOHudHpSpWidget.cpp
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#include "ZMMOHudHpSpWidget.h"
|
||||||
|
|
||||||
|
#include "Components/ProgressBar.h"
|
||||||
|
#include "Components/TextBlock.h"
|
||||||
|
#include "Internationalization/Text.h"
|
||||||
|
#include "ZMMOAttributeComponent.h"
|
||||||
|
|
||||||
|
void UZMMOHudHpSpWidget::BindToAttributeComponent(UZMMOAttributeComponent* InComponent)
|
||||||
|
{
|
||||||
|
if (UZMMOAttributeComponent* Old = BoundComponent.Get())
|
||||||
|
{
|
||||||
|
Old->OnAttributesChanged.RemoveDynamic(this, &UZMMOHudHpSpWidget::HandleAttributesChanged);
|
||||||
|
Old->OnHpSpChanged.RemoveDynamic(this, &UZMMOHudHpSpWidget::HandleHpSpChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundComponent = InComponent;
|
||||||
|
|
||||||
|
if (InComponent)
|
||||||
|
{
|
||||||
|
InComponent->OnAttributesChanged.AddDynamic(this, &UZMMOHudHpSpWidget::HandleAttributesChanged);
|
||||||
|
InComponent->OnHpSpChanged.AddDynamic(this, &UZMMOHudHpSpWidget::HandleHpSpChanged);
|
||||||
|
// Refresh imediato — caso o primeiro snapshot tenha chegado antes
|
||||||
|
// do widget existir.
|
||||||
|
HandleAttributesChanged(InComponent->GetSnapshot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOHudHpSpWidget::NativeDestruct()
|
||||||
|
{
|
||||||
|
if (UZMMOAttributeComponent* Old = BoundComponent.Get())
|
||||||
|
{
|
||||||
|
Old->OnAttributesChanged.RemoveDynamic(this, &UZMMOHudHpSpWidget::HandleAttributesChanged);
|
||||||
|
Old->OnHpSpChanged.RemoveDynamic(this, &UZMMOHudHpSpWidget::HandleHpSpChanged);
|
||||||
|
}
|
||||||
|
BoundComponent.Reset();
|
||||||
|
Super::NativeDestruct();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOHudHpSpWidget::HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot)
|
||||||
|
{
|
||||||
|
OnSnapshotApplied(Snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOHudHpSpWidget::HandleHpSpChanged(int32 Hp, int32 Sp)
|
||||||
|
{
|
||||||
|
OnHpSpDelta(Hp, Sp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOHudHpSpWidget::OnSnapshotApplied_Implementation(const FZMMOAttributesSnapshot& Snapshot)
|
||||||
|
{
|
||||||
|
if (HpBar)
|
||||||
|
{
|
||||||
|
HpBar->SetPercent(Snapshot.MaxHp > 0
|
||||||
|
? static_cast<float>(Snapshot.Hp) / static_cast<float>(Snapshot.MaxHp) : 0.f);
|
||||||
|
}
|
||||||
|
if (SpBar)
|
||||||
|
{
|
||||||
|
SpBar->SetPercent(Snapshot.MaxSp > 0
|
||||||
|
? static_cast<float>(Snapshot.Sp) / static_cast<float>(Snapshot.MaxSp) : 0.f);
|
||||||
|
}
|
||||||
|
if (HpText)
|
||||||
|
{
|
||||||
|
HpText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), Snapshot.Hp, Snapshot.MaxHp)));
|
||||||
|
}
|
||||||
|
if (SpText)
|
||||||
|
{
|
||||||
|
SpText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), Snapshot.Sp, Snapshot.MaxSp)));
|
||||||
|
}
|
||||||
|
if (LevelText)
|
||||||
|
{
|
||||||
|
LevelText->SetText(FText::FromString(FString::Printf(TEXT("Lv %d"), Snapshot.BaseLevel)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOHudHpSpWidget::OnHpSpDelta_Implementation(int32 Hp, int32 Sp)
|
||||||
|
{
|
||||||
|
// Atualiza apenas os campos vitais (evita refresh redundante de
|
||||||
|
// stats/level que nao mudaram).
|
||||||
|
if (UZMMOAttributeComponent* Comp = BoundComponent.Get())
|
||||||
|
{
|
||||||
|
const FZMMOAttributesSnapshot& Snap = Comp->GetSnapshot();
|
||||||
|
if (HpBar)
|
||||||
|
{
|
||||||
|
HpBar->SetPercent(Snap.MaxHp > 0
|
||||||
|
? static_cast<float>(Hp) / static_cast<float>(Snap.MaxHp) : 0.f);
|
||||||
|
}
|
||||||
|
if (SpBar)
|
||||||
|
{
|
||||||
|
SpBar->SetPercent(Snap.MaxSp > 0
|
||||||
|
? static_cast<float>(Sp) / static_cast<float>(Snap.MaxSp) : 0.f);
|
||||||
|
}
|
||||||
|
if (HpText)
|
||||||
|
{
|
||||||
|
HpText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), Hp, Snap.MaxHp)));
|
||||||
|
}
|
||||||
|
if (SpText)
|
||||||
|
{
|
||||||
|
SpText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), Sp, Snap.MaxSp)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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;
|
||||||
|
};
|
||||||
29
Source/ZMMOAttributes/ZMMOAttributes.Build.cs
Normal file
29
Source/ZMMOAttributes/ZMMOAttributes.Build.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class ZMMOAttributes : ModuleRules
|
||||||
|
{
|
||||||
|
public ZMMOAttributes(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(new string[] {
|
||||||
|
"Core",
|
||||||
|
"CoreUObject",
|
||||||
|
"Engine",
|
||||||
|
"UMG",
|
||||||
|
"Slate",
|
||||||
|
"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"],
|
||||||
|
"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."
|
||||||
|
}
|
||||||
@@ -8,6 +8,11 @@
|
|||||||
"Name": "ZMMO",
|
"Name": "ZMMO",
|
||||||
"Type": "Runtime",
|
"Type": "Runtime",
|
||||||
"LoadingPhase": "Default"
|
"LoadingPhase": "Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "ZMMOAttributes",
|
||||||
|
"Type": "Runtime",
|
||||||
|
"LoadingPhase": "PreDefault"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Plugins": [
|
"Plugins": [
|
||||||
|
|||||||
Reference in New Issue
Block a user