feat(zeus-gas): M7 - migrate HUD/Status to UZeusGASComponent + delete legacy
- ZeusHudWidget: porta de UZeusAttributeComponent legacy pra UZeusGASComponent
(delegate OnSnapshotAppliedRaw via AddUObject; FZeusAttributesSnapshot
construido pelo helper estatico FromPayload do plugin ZeusGAS). Logs
diagnosticos no bind.
- UIPlayerStatus_Window: mesmo porte (snapshot raw + StatAllocReply). Lock
de spam-click preservado.
- ZeusHudHpSpWidget: movido do plugin ZeusAttributes (deletado) pra ZMMO
/Game/UI/InGame/. API preservada (WBP filhos nao precisam mudar).
- ZeusCharacter: tira o dual-seed legacy; so' seedea UZeusGASComponent
via FindComponentByClass no HandleLocalSpawnReady.
- ZeusPlayerState/ZeusCharacter: comentarios atualizados pra refletir o
UZeusGASComponent como dono unico.
- ZMMO.Build.cs: remove dep "ZeusAttributes", adiciona "ZeusGAS" +
"GameplayAbilities".
- ZMMO.uproject: remove module "ZeusAttributes" (delete completo).
- DefaultGame.ini: remove linha legacy comentada do Component Registry.
- DefaultEngine.ini: adiciona [CoreRedirects] pra:
* ZMMOJobs module + ZMMOJobDataAsset class (Package+Class) — 7 DA_Job_*
* ZMMO.ZMMOMapDef struct (DT_Maps row)
* ZMMO.ZMMOLoadingTipRow struct (DT_LoadingTips row)
* ZMMO.ZMMOLoadingProfile/StepDef structs + EZMMOLoadingContext/StepStatus
enums (DA_LoadingProfiles)
- DELETE: Source/ZeusAttributes/ inteiro (5 .cpp + 5 .h + Build.cs +
module.json).
Pegadinha aprendida: ActiveClassRedirects so' renomeia classe (sem
package); CoreRedirects (ClassRedirects/StructRedirects/EnumRedirects/
PackageRedirects) e' obrigatorio quando muda modulo OU quando o asset
referencia USTRUCT/UENUM. Tipo errado nao da erro mas tambem nao funciona.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -104,7 +104,6 @@ GameViewportClientClassName=/Script/CommonUI.CommonGameViewportClient
|
||||
+ActiveClassRedirects=(OldClassName="ZMMOHudWidget",NewClassName="ZeusHudWidget")
|
||||
+ActiveClassRedirects=(OldClassName="ZMMOHudPlayerVitalsWidget",NewClassName="ZeusHudPlayerVitalsWidget")
|
||||
+ActiveClassRedirects=(OldClassName="ZMMOLoadingProfilesDataAsset",NewClassName="ZeusLoadingProfilesDataAsset")
|
||||
|
||||
[CoreRedirects]
|
||||
; Migração v1→v2 do FUIStylePanel: TMap<EUIPanelTexture,...> (Enum) → TMap<FName,...>.
|
||||
; Os campos *_DEPRECATED preservam a leitura do asset antigo; PostSerialize copia
|
||||
@@ -114,6 +113,27 @@ GameViewportClientClassName=/Script/CommonUI.CommonGameViewportClient
|
||||
+PropertyRedirects=(OldName="/Script/ZMMO.UIStylePanel.PanelOutline",NewName="/Script/ZMMO.UIStylePanel.PanelOutline_DEPRECATED")
|
||||
+PropertyRedirects=(OldName="/Script/ZMMO.UIStylePanel.PanelOutlineEffect",NewName="/Script/ZMMO.UIStylePanel.PanelOutlineEffect_DEPRECATED")
|
||||
|
||||
; --- M7: Modulo cliente "ZMMOJobs" foi renomeado pra "ZeusJobs" + classe
|
||||
; UZMMOJobDataAsset -> UZeusJobDataAsset. Os 7 DA_Job_* em /Game/ZMMO/Data/Jobs/
|
||||
; foram salvos apontando pra /Script/ZMMOJobs.ZMMOJobDataAsset (modulo + classe
|
||||
; antigos). ActiveClassRedirects so' troca o NOME da classe, nao o package;
|
||||
; precisamos de CoreRedirects com path completo + PackageRedirect.
|
||||
+PackageRedirects=(OldName="/Script/ZMMOJobs",NewName="/Script/ZeusJobs")
|
||||
+ClassRedirects=(OldName="/Script/ZMMOJobs.ZMMOJobDataAsset",NewName="/Script/ZeusJobs.ZeusJobDataAsset")
|
||||
|
||||
; --- M7: USTRUCTs renomeadas (mesmo modulo ZMMO). DTs salvos com Row Structure
|
||||
; apontando pro nome antigo (ZMMOMapDef, ZMMOLoadingTipRow). USTRUCT usa
|
||||
; +StructRedirects (nao +ClassRedirects).
|
||||
+StructRedirects=(OldName="/Script/ZMMO.ZMMOMapDef",NewName="/Script/ZMMO.ZeusMapDef")
|
||||
+StructRedirects=(OldName="/Script/ZMMO.ZMMOLoadingTipRow",NewName="/Script/ZMMO.ZeusLoadingTipRow")
|
||||
; DA_LoadingProfiles salvo com TMap<EZMMOLoadingContext, FZMMOLoadingProfile>;
|
||||
; FZMMOLoadingProfile contem TArray<FZMMOLoadingStepDef>. Precisa de 3 redirects
|
||||
; (1 enum + 2 struct) pra carregar sem warnings nem perda de dados.
|
||||
+EnumRedirects=(OldName="/Script/ZMMO.EZMMOLoadingContext",NewName="/Script/ZMMO.EZeusLoadingContext")
|
||||
+EnumRedirects=(OldName="/Script/ZMMO.EZMMOLoadingStepStatus",NewName="/Script/ZMMO.EZeusLoadingStepStatus")
|
||||
+StructRedirects=(OldName="/Script/ZMMO.ZMMOLoadingProfile",NewName="/Script/ZMMO.ZeusLoadingProfile")
|
||||
+StructRedirects=(OldName="/Script/ZMMO.ZMMOLoadingStepDef",NewName="/Script/ZMMO.ZeusLoadingStepDef")
|
||||
|
||||
[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings]
|
||||
bEnablePlugin=True
|
||||
bAllowNetworkConnection=True
|
||||
|
||||
@@ -65,7 +65,11 @@ ScreenSetAsset=/Game/ZMMO/UI/InGame/DA_InGameScreenSet.DA_InGameScreenSet
|
||||
; -----------------------------------------------------------------------------
|
||||
|
||||
[/Script/ZMMO.ZeusPlayerState]
|
||||
+ComponentClasses=/Script/ZeusAttributes.ZeusAttributeComponent
|
||||
; UZeusGASComponent (plugin ZeusGAS) — dono unico dos atributos do char (M7).
|
||||
; Owns UZeusAbilitySystemComponent + UZeusAttributeSet (26 atributos
|
||||
; espelhados do server CharAttributeSet). Bridge via UZeusGASNetworkHandler
|
||||
; (UWorldSubsystem) subscribe nos delegates do UZeusNetworkSubsystem.
|
||||
+ComponentClasses=/Script/ZeusGAS.ZeusGASComponent
|
||||
|
||||
[/Script/Engine.AssetManagerSettings]
|
||||
-PrimaryAssetTypesToScan=(PrimaryAssetType="Map",AssetBaseClass=/Script/Engine.World,bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game/Maps")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include "Subsystems/WorldSubsystem.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "ZMMO.h"
|
||||
#include "ZeusAttributeComponent.h"
|
||||
#include "ZeusGASComponent.h"
|
||||
#include "ZeusPlayerState.h"
|
||||
#include "ZeusWorldSubsystem.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
@@ -355,10 +355,11 @@ void AZeusCharacter::HandleLocalSpawnReady(const int64 InEntityId, const FVector
|
||||
}
|
||||
}
|
||||
|
||||
// Identidade publica (EntityId) + seed no AttributeComponent. CharName/Guild
|
||||
// vem separado via S_CHAR_INFO -> HandleZeusCharInfo. AttributeComponent vive
|
||||
// como subobject e recebe seed pra que o ZeusAttributeNetworkHandler consiga
|
||||
// rotear o snapshot por EntityId desde o primeiro pacote.
|
||||
// Identidade publica (EntityId) + seed no GAS Component. CharName/Guild
|
||||
// vem separado via S_CHAR_INFO -> HandleZeusCharInfo. UZeusGASComponent
|
||||
// vive como subobject do PlayerState (Component Registry) e recebe seed
|
||||
// pra que o UZeusGASNetworkHandler consiga rotear o snapshot por
|
||||
// EntityId desde o primeiro pacote.
|
||||
if (APlayerState* PS = GetPlayerState())
|
||||
{
|
||||
if (AZeusPlayerState* ZeusPs = Cast<AZeusPlayerState>(PS))
|
||||
@@ -366,9 +367,9 @@ void AZeusCharacter::HandleLocalSpawnReady(const int64 InEntityId, const FVector
|
||||
ZeusPs->SetPublicIdentity(InEntityId, /*CharId*/ FString(),
|
||||
/*BaseLevel*/ 1, /*ClassId*/ 0);
|
||||
}
|
||||
if (UZeusAttributeComponent* AttrComp = PS->FindComponentByClass<UZeusAttributeComponent>())
|
||||
if (UZeusGASComponent* GASComp = PS->FindComponentByClass<UZeusGASComponent>())
|
||||
{
|
||||
AttrComp->SeedEntityId(InEntityId);
|
||||
GASComp->SeedEntityId(InEntityId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +379,7 @@ void AZeusCharacter::HandleLocalSpawnReady(const int64 InEntityId, const FVector
|
||||
// UI in-game e' responsabilidade do AZeusHUD (GameMode.HUDClass), nao do
|
||||
// Pawn. AHUD::BeginPlay chama UUIInGameFlowSubsystem::StartInGame, que
|
||||
// pushea WBP_HUD em UI.Layer.Game. UZeusHudWidget::NativeOnActivated auto-binda
|
||||
// no UZeusAttributeComponent via PlayerState.
|
||||
// no UZeusGASComponent via PlayerState.
|
||||
}
|
||||
|
||||
void AZeusCharacter::HandleZeusCharInfo(const int64 InEntityId, const FString& CharName, const FString& GuildName)
|
||||
|
||||
@@ -46,8 +46,8 @@ class ZMMO_API AZeusCharacter : public ACharacter, public IZeusEntityInterface
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
|
||||
UCameraComponent* FollowCamera;
|
||||
|
||||
// AttributeComponent vive no PlayerState (AZeusPlayerState + Component Registry).
|
||||
// Acesso: GetPlayerState<AZeusPlayerState>()->GetZeusComponent<UZeusAttributeComponent>().
|
||||
// GAS Component vive no PlayerState (AZeusPlayerState + Component Registry).
|
||||
// Acesso: GetPlayerState<AZeusPlayerState>()->GetZeusComponent<UZeusGASComponent>().
|
||||
|
||||
protected:
|
||||
UPROPERTY(EditAnywhere, Category = "Input")
|
||||
|
||||
@@ -28,11 +28,11 @@ class UActorComponent;
|
||||
*
|
||||
* ## Component Registry (Open-Closed)
|
||||
*
|
||||
* Cada modulo (ZeusAttributes, ZeusInventory, ZeusSkills futuros) declara
|
||||
* Cada modulo (ZeusGAS, ZeusInventory, ZeusSkills futuros) declara
|
||||
* sua component class em DefaultGame.ini:
|
||||
*
|
||||
* [/Script/ZMMO.ZeusPlayerState]
|
||||
* +ComponentClasses=/Script/ZeusAttributes.ZeusAttributeComponent
|
||||
* +ComponentClasses=/Script/ZeusGAS.ZeusGASComponent
|
||||
* +ComponentClasses=/Script/ZeusInventory.ZeusInventoryComponent
|
||||
* +ComponentClasses=/Script/ZeusSkills.ZeusSkillComponent
|
||||
*
|
||||
@@ -41,7 +41,7 @@ class UActorComponent;
|
||||
* modulos abertos pra extensao — adicionar Inventory NAO requer
|
||||
* recompilar/editar AZeusPlayerState.
|
||||
*
|
||||
* Acesso: `PlayerState->FindComponentByClass<UZeusAttributeComponent>()`
|
||||
* Acesso: `PlayerState->FindComponentByClass<UZeusGASComponent>()`
|
||||
* ou o helper `GetZeusComponent<T>()`.
|
||||
*/
|
||||
UCLASS(Config = Game, BlueprintType)
|
||||
@@ -102,7 +102,7 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Zeus|Identity")
|
||||
void SetCharInfo(const FString& InCharName, const FString& InGuildName);
|
||||
|
||||
/// Template helper: PS->GetZeusComponent<UZeusAttributeComponent>()
|
||||
/// Template helper: PS->GetZeusComponent<UZeusGASComponent>()
|
||||
template <typename T>
|
||||
T* GetZeusComponent() const
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "CoreMinimal.h"
|
||||
#include "ZeusAttributeTypes.h"
|
||||
#include "ZeusAttributesSnapshot.h"
|
||||
#include "ZeusHudHpSpWidget.generated.h"
|
||||
|
||||
class UProgressBar;
|
||||
@@ -10,27 +10,17 @@ class UTextBlock;
|
||||
|
||||
/**
|
||||
* Sub-widget do HUD: barras de HP/SP + label de level. Sem auto-bind no
|
||||
* AttributeComponent — o parent (`UZeusHudWidget`) propaga snapshots via
|
||||
* `ApplySnapshot` / `ApplyHpSpDelta` quando o player local recebe updates.
|
||||
* GAS Component — o parent (`UZeusHudWidget`) propaga snapshots via
|
||||
* `ApplySnapshot` / `ApplyHpSp` quando o player local recebe updates.
|
||||
*
|
||||
* Composicao do HUD (ver `UZeusHudWidget`):
|
||||
* WBP_HUD (UZeusHudWidget root)
|
||||
* |-- HpSpBar (UZeusHudHpSpWidget — 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 (UZeusHudWidget)
|
||||
* faz binding com AttributeComponent e propaga deltas.
|
||||
* Migrado de ZeusAttributes plugin pra ZMMO em M7 (delecao do plugin
|
||||
* legacy). API preservada — WBP que herda dele nao precisa mudar.
|
||||
*
|
||||
* Convencao `meta=(BindWidget)`: o WBP filho deve ter widgets com nomes
|
||||
* exatos (HpBar, SpBar). `BindWidgetOptional` permite omitir.
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable, BlueprintType)
|
||||
class ZEUSATTRIBUTES_API UZeusHudHpSpWidget : public UUserWidget
|
||||
class ZMMO_API UZeusHudHpSpWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
@@ -70,7 +60,7 @@ protected:
|
||||
UTextBlock* LevelText = nullptr;
|
||||
|
||||
private:
|
||||
/// Ultimo snapshot recebido (cache pra ApplyHpSp manter MaxHp/MaxSp). */
|
||||
/// Ultimo snapshot recebido (cache pra ApplyHpSp manter MaxHp/MaxSp).
|
||||
UPROPERTY(Transient)
|
||||
FZeusAttributesSnapshot LastSnapshot;
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "CommonUserWidget.h"
|
||||
#include "CoreMinimal.h"
|
||||
#include "ZeusAttributeTypes.h"
|
||||
#include "ZeusAttributesSnapshot.h"
|
||||
#include "ZeusHudPlayerVitalsWidget.generated.h"
|
||||
|
||||
class UUIProgressBar_Base;
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
#include "GameFramework/PlayerState.h"
|
||||
#include "TimerManager.h"
|
||||
#include "ZMMO.h"
|
||||
#include "ZeusAttributeComponent.h"
|
||||
#include "ZeusAttributesPayload.h" // FZeusAttributesPayload (ZeusNetwork)
|
||||
#include "ZeusGASComponent.h" // UZeusGASComponent (ZeusGAS)
|
||||
#include "ZeusHudHpSpWidget.h"
|
||||
#include "ZeusHudPlayerVitalsWidget.h"
|
||||
#include "ZeusJobsLibrary.h"
|
||||
@@ -68,40 +69,61 @@ void UZeusHudWidget::BindToLocalPlayer()
|
||||
if (!PS)
|
||||
{
|
||||
// PlayerState pode nao existir ainda — race com GameMode setup.
|
||||
// AZeusCharacter::HandleLocalSpawnReady chama BindToAttributeComponent
|
||||
// manualmente como fallback (seed do EntityId).
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZeusHudWidget: PlayerState ausente no activate (race fix via HandleLocalSpawnReady esperado)"));
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZeusHudWidget: PlayerState ausente no activate (esperado se ativou antes do replicar)"));
|
||||
return;
|
||||
}
|
||||
UZeusGASComponent* Comp = PS->FindComponentByClass<UZeusGASComponent>();
|
||||
if (!Comp)
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning, TEXT("ZeusHudWidget::BindToLocalPlayer: UZeusGASComponent NAO encontrado em PS=%s"),
|
||||
*PS->GetName());
|
||||
return;
|
||||
}
|
||||
UZeusAttributeComponent* Comp = PS->FindComponentByClass<UZeusAttributeComponent>();
|
||||
if (!Comp) { return; }
|
||||
if (Comp == BoundComponent.Get()) { return; } // idempotente
|
||||
|
||||
UnbindFromComponent();
|
||||
BoundComponent = Comp;
|
||||
|
||||
Comp->OnAttributesChanged.AddDynamic(this, &UZeusHudWidget::HandleAttributesChanged);
|
||||
Comp->OnHpSpChanged.AddDynamic(this, &UZeusHudWidget::HandleHpSpChanged);
|
||||
// Raw e' non-dynamic (FZeusAttributesPayload nao e' BlueprintType) — AddUObject.
|
||||
SnapshotRawHandle = Comp->OnSnapshotAppliedRaw.AddUObject(this, &UZeusHudWidget::HandleSnapshotRaw);
|
||||
Comp->OnHpSpUpdated.AddDynamic(this, &UZeusHudWidget::HandleHpSpUpdated);
|
||||
Comp->OnLevelUp.AddDynamic(this, &UZeusHudWidget::HandleLevelUp);
|
||||
|
||||
// Refresh imediato — caso o snapshot ja' tenha chegado.
|
||||
HandleAttributesChanged(Comp->GetSnapshot());
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZeusHudWidget::BindToLocalPlayer: bound to %s (HasSnapshot=%d, PlayerVitals=%s, HpSpBar=%s)"),
|
||||
*Comp->GetName(), Comp->HasSnapshot() ? 1 : 0,
|
||||
PlayerVitals ? *PlayerVitals->GetName() : TEXT("nullptr"),
|
||||
HpSpBar ? *HpSpBar->GetName() : TEXT("nullptr"));
|
||||
|
||||
// Refresh imediato — caso o snapshot ja' tenha chegado antes do bind.
|
||||
if (Comp->HasSnapshot())
|
||||
{
|
||||
HandleSnapshotRaw(Comp->GetLastSnapshot());
|
||||
}
|
||||
}
|
||||
|
||||
void UZeusHudWidget::UnbindFromComponent()
|
||||
{
|
||||
if (UZeusAttributeComponent* Old = BoundComponent.Get())
|
||||
if (UZeusGASComponent* Old = BoundComponent.Get())
|
||||
{
|
||||
Old->OnAttributesChanged.RemoveDynamic(this, &UZeusHudWidget::HandleAttributesChanged);
|
||||
Old->OnHpSpChanged.RemoveDynamic(this, &UZeusHudWidget::HandleHpSpChanged);
|
||||
if (SnapshotRawHandle.IsValid())
|
||||
{
|
||||
Old->OnSnapshotAppliedRaw.Remove(SnapshotRawHandle);
|
||||
SnapshotRawHandle.Reset();
|
||||
}
|
||||
Old->OnHpSpUpdated.RemoveDynamic(this, &UZeusHudWidget::HandleHpSpUpdated);
|
||||
Old->OnLevelUp.RemoveDynamic(this, &UZeusHudWidget::HandleLevelUp);
|
||||
}
|
||||
BoundComponent.Reset();
|
||||
}
|
||||
|
||||
void UZeusHudWidget::HandleAttributesChanged(const FZeusAttributesSnapshot& Snapshot)
|
||||
void UZeusHudWidget::HandleSnapshotRaw(const FZeusAttributesPayload& Payload)
|
||||
{
|
||||
// Propaga snapshot aos sub-widgets que ja existem.
|
||||
const FZeusAttributesSnapshot Snapshot = FZeusAttributesSnapshot::FromPayload(Payload);
|
||||
|
||||
UE_LOG(LogZMMO, Log, TEXT("ZeusHudWidget::HandleSnapshotRaw: entity=%lld level=%d hp=%d/%d (PlayerVitals=%s)"),
|
||||
Snapshot.EntityId, Snapshot.BaseLevel, Snapshot.Hp, Snapshot.MaxHp,
|
||||
PlayerVitals ? TEXT("ok") : TEXT("NULLPTR"));
|
||||
|
||||
if (HpSpBar)
|
||||
{
|
||||
HpSpBar->ApplySnapshot(Snapshot);
|
||||
@@ -110,9 +132,8 @@ void UZeusHudWidget::HandleAttributesChanged(const FZeusAttributesSnapshot& Snap
|
||||
{
|
||||
PlayerVitals->ApplySnapshot(Snapshot);
|
||||
|
||||
// Identidade: nome vem do PlayerState (não está no snapshot); job vem
|
||||
// do JobsSubsystem resolvendo ClassId -> display name.
|
||||
if (UZeusAttributeComponent* Comp = BoundComponent.Get())
|
||||
// Identidade: nome vem do PlayerState; job vem do JobsSubsystem.
|
||||
if (UZeusGASComponent* Comp = BoundComponent.Get())
|
||||
{
|
||||
if (const AZeusPlayerState* PS = Cast<AZeusPlayerState>(Comp->GetOwner()))
|
||||
{
|
||||
@@ -125,19 +146,21 @@ void UZeusHudWidget::HandleAttributesChanged(const FZeusAttributesSnapshot& Snap
|
||||
// Futuros: Buffs->RefreshIcons, etc.
|
||||
}
|
||||
|
||||
void UZeusHudWidget::HandleHpSpChanged(int32 Hp, int32 Sp)
|
||||
void UZeusHudWidget::HandleHpSpUpdated(float Hp, float Sp)
|
||||
{
|
||||
if (UZeusAttributeComponent* Comp = BoundComponent.Get())
|
||||
UZeusGASComponent* Comp = BoundComponent.Get();
|
||||
if (!Comp || !Comp->HasSnapshot()) { return; }
|
||||
|
||||
const FZeusAttributesPayload& P = Comp->GetLastSnapshot();
|
||||
const int32 HpInt = static_cast<int32>(Hp);
|
||||
const int32 SpInt = static_cast<int32>(Sp);
|
||||
if (HpSpBar)
|
||||
{
|
||||
const FZeusAttributesSnapshot& Snap = Comp->GetSnapshot();
|
||||
if (HpSpBar)
|
||||
{
|
||||
HpSpBar->ApplyHpSp(Hp, Snap.MaxHp, Sp, Snap.MaxSp);
|
||||
}
|
||||
if (PlayerVitals)
|
||||
{
|
||||
PlayerVitals->ApplyHpSp(Hp, Snap.MaxHp, Sp, Snap.MaxSp);
|
||||
}
|
||||
HpSpBar->ApplyHpSp(HpInt, P.MaxHp, SpInt, P.MaxSp);
|
||||
}
|
||||
if (PlayerVitals)
|
||||
{
|
||||
PlayerVitals->ApplyHpSp(HpInt, P.MaxHp, SpInt, P.MaxSp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
#include "CommonActivatableWidget.h"
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/TimerHandle.h"
|
||||
#include "ZeusAttributeTypes.h"
|
||||
#include "ZeusAttributesSnapshot.h"
|
||||
#include "ZeusHudWidget.generated.h"
|
||||
|
||||
class UTextBlock;
|
||||
class UWidget;
|
||||
class UZeusAttributeComponent;
|
||||
class UZeusGASComponent;
|
||||
class UZeusHudHpSpWidget;
|
||||
class UZeusHudPlayerVitalsWidget;
|
||||
struct FZeusAttributesPayload;
|
||||
|
||||
/**
|
||||
* Root da HUD de gameplay. UCommonActivatableWidget pushed pelo
|
||||
@@ -18,7 +19,7 @@ class UZeusHudPlayerVitalsWidget;
|
||||
*
|
||||
* **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 ZeusAttributes, Minimap de ZeusMap futuro,
|
||||
* (HpSpBar local do ZMMO, Minimap de ZeusMap futuro,
|
||||
* Chat de ZeusChat futuro, etc.).
|
||||
*
|
||||
* Padrao **composite**: o WBP_HUD (filho) declara as PECAS visuais como
|
||||
@@ -32,7 +33,7 @@ class UZeusHudPlayerVitalsWidget;
|
||||
*
|
||||
* Composicao prevista (sub-widgets — todos `BindWidgetOptional` pra
|
||||
* permitir GameTypes sem modulos opcionais — ex.: Survival sem stats RO):
|
||||
* - HpSpBar (UZeusHudHpSpWidget — sub-modulo ZeusAttributes) — impl
|
||||
* - HpSpBar (UZeusHudHpSpWidget — local UI/InGame/) — impl
|
||||
* - PlayerInfo (futuro — nome/job/level/exp bar)
|
||||
* - Minimap (futuro)
|
||||
* - QuickBar (futuro — hotkeys de skills)
|
||||
@@ -70,7 +71,7 @@ protected:
|
||||
*/
|
||||
virtual TOptional<FUIInputConfig> GetDesiredInputConfig() const override;
|
||||
|
||||
/// Sub-widget HP/SP/level (sub-modulo ZeusAttributes). BindWidgetOptional
|
||||
/// Sub-widget HP/SP/level (local UI/InGame/). BindWidgetOptional
|
||||
/// — WBP_HUD pode omitir se o GameType nao usa Attributes (Survival, etc.).
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD")
|
||||
UZeusHudHpSpWidget* HpSpBar = nullptr;
|
||||
@@ -106,25 +107,31 @@ protected:
|
||||
// UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional)) UZeusHudTargetInfoWidget* TargetInfo = nullptr;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Zeus|HUD")
|
||||
UZeusAttributeComponent* GetBoundComponent() const { return BoundComponent.Get(); }
|
||||
UZeusGASComponent* GetBoundComponent() const { return BoundComponent.Get(); }
|
||||
|
||||
private:
|
||||
/// Tenta achar o UZeusAttributeComponent do player local + subscrever
|
||||
/// nos delegates. Idempotente.
|
||||
/// Tenta achar o UZeusGASComponent do player local + subscrever nos
|
||||
/// delegates. Idempotente.
|
||||
void BindToLocalPlayer();
|
||||
void UnbindFromComponent();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleAttributesChanged(const FZeusAttributesSnapshot& Snapshot);
|
||||
/// Listener C++ no payload bruto do UZeusGASComponent (nao e' UFUNCTION
|
||||
/// porque FZeusAttributesPayload nao e' BlueprintType — delegate multicast
|
||||
/// nativo).
|
||||
void HandleSnapshotRaw(const FZeusAttributesPayload& Payload);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleHpSpChanged(int32 Hp, int32 Sp);
|
||||
void HandleHpSpUpdated(float Hp, float Sp);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleLevelUp(int32 NewBaseLevel, int32 StatusPointDelta);
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TWeakObjectPtr<UZeusAttributeComponent> BoundComponent;
|
||||
TWeakObjectPtr<UZeusGASComponent> BoundComponent;
|
||||
|
||||
/// Handle do listener nao-dynamic em OnSnapshotAppliedRaw — guardar pra remover
|
||||
/// em UnbindFromComponent (AddUObject nao auto-cleanup como AddDynamic).
|
||||
FDelegateHandle SnapshotRawHandle;
|
||||
|
||||
void ShowLevelUpToast(int32 NewBaseLevel);
|
||||
void HideLevelUpToast();
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
#include "GameFramework/PlayerState.h"
|
||||
#include "Internationalization/Text.h"
|
||||
#include "ZMMO.h"
|
||||
#include "ZeusAttributeComponent.h"
|
||||
#include "ZeusAttributesPayload.h" // FZeusAttributesPayload (ZeusNetwork)
|
||||
#include "ZeusGASComponent.h" // UZeusGASComponent (ZeusGAS)
|
||||
#include "ZeusJobDataAsset.h"
|
||||
#include "ZeusJobsSubsystem.h"
|
||||
#include "ZeusPlayerState.h"
|
||||
@@ -123,30 +124,40 @@ void UUIPlayerStatus_Window::BindToLocalPlayer()
|
||||
Text_CharName->SetText(FText::FromString(DisplayName));
|
||||
}
|
||||
|
||||
UZeusAttributeComponent* Comp = PC->PlayerState->FindComponentByClass<UZeusAttributeComponent>();
|
||||
UZeusGASComponent* Comp = PC->PlayerState->FindComponentByClass<UZeusGASComponent>();
|
||||
if (!Comp || Comp == BoundComponent.Get()) return;
|
||||
|
||||
UnbindFromComponent();
|
||||
BoundComponent = Comp;
|
||||
Comp->OnAttributesChanged.AddDynamic(this, &UUIPlayerStatus_Window::HandleAttributesChanged);
|
||||
// Raw e' non-dynamic (FZeusAttributesPayload nao e' BlueprintType) — AddUObject.
|
||||
SnapshotRawHandle = Comp->OnSnapshotAppliedRaw.AddUObject(this, &UUIPlayerStatus_Window::HandleSnapshotRaw);
|
||||
Comp->OnStatAllocReply.AddDynamic(this, &UUIPlayerStatus_Window::HandleStatAllocReply);
|
||||
RefreshFromSnapshot(Comp->GetSnapshot());
|
||||
|
||||
// Refresh imediato — caso o snapshot ja' tenha chegado antes do bind.
|
||||
if (Comp->HasSnapshot())
|
||||
{
|
||||
HandleSnapshotRaw(Comp->GetLastSnapshot());
|
||||
}
|
||||
}
|
||||
|
||||
void UUIPlayerStatus_Window::UnbindFromComponent()
|
||||
{
|
||||
if (UZeusAttributeComponent* Old = BoundComponent.Get())
|
||||
if (UZeusGASComponent* Old = BoundComponent.Get())
|
||||
{
|
||||
Old->OnAttributesChanged.RemoveDynamic(this, &UUIPlayerStatus_Window::HandleAttributesChanged);
|
||||
if (SnapshotRawHandle.IsValid())
|
||||
{
|
||||
Old->OnSnapshotAppliedRaw.Remove(SnapshotRawHandle);
|
||||
SnapshotRawHandle.Reset();
|
||||
}
|
||||
Old->OnStatAllocReply.RemoveDynamic(this, &UUIPlayerStatus_Window::HandleStatAllocReply);
|
||||
}
|
||||
BoundComponent.Reset();
|
||||
}
|
||||
|
||||
void UUIPlayerStatus_Window::HandleAttributesChanged(const FZeusAttributesSnapshot& Snapshot)
|
||||
void UUIPlayerStatus_Window::HandleSnapshotRaw(const FZeusAttributesPayload& Payload)
|
||||
{
|
||||
bPlusButtonsLocked = false;
|
||||
RefreshFromSnapshot(Snapshot);
|
||||
RefreshFromSnapshot(FZeusAttributesSnapshot::FromPayload(Payload));
|
||||
}
|
||||
|
||||
void UUIPlayerStatus_Window::HandleStatAllocReply(bool bAccepted, int32 Reason)
|
||||
@@ -155,17 +166,20 @@ void UUIPlayerStatus_Window::HandleStatAllocReply(bool bAccepted, int32 Reason)
|
||||
bAccepted ? 1 : 0, Reason);
|
||||
|
||||
bPlusButtonsLocked = false;
|
||||
if (UZeusAttributeComponent* Comp = BoundComponent.Get())
|
||||
if (UZeusGASComponent* Comp = BoundComponent.Get())
|
||||
{
|
||||
const FZeusAttributesSnapshot& S = Comp->GetSnapshot();
|
||||
SetAllPrimaryRowsPlusEnabled(S.StatusPoint > 0);
|
||||
if (Comp->HasSnapshot())
|
||||
{
|
||||
const FZeusAttributesPayload& P = Comp->GetLastSnapshot();
|
||||
SetAllPrimaryRowsPlusEnabled(P.StatusPoint > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UUIPlayerStatus_Window::HandleRowPlusClicked(int32 StatId)
|
||||
{
|
||||
if (bPlusButtonsLocked) return;
|
||||
UZeusAttributeComponent* Comp = BoundComponent.Get();
|
||||
UZeusGASComponent* Comp = BoundComponent.Get();
|
||||
if (!Comp) return;
|
||||
|
||||
bPlusButtonsLocked = true;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UI/Common/UIDraggableWindow_Base.h"
|
||||
#include "ZeusAttributeTypes.h"
|
||||
#include "ZeusAttributesSnapshot.h"
|
||||
#include "UIPlayerStatus_Window.generated.h"
|
||||
|
||||
class UButton;
|
||||
@@ -10,7 +10,8 @@ class UTextBlock;
|
||||
class UHorizontalBox;
|
||||
class UUIPlayerStatus_BonusRow;
|
||||
class UUIPlayerStatus_StatRow;
|
||||
class UZeusAttributeComponent;
|
||||
class UZeusGASComponent;
|
||||
struct FZeusAttributesPayload;
|
||||
|
||||
/**
|
||||
* Janela de Status do player — composta por instâncias dos sub-widgets
|
||||
@@ -98,13 +99,21 @@ private:
|
||||
void SetAllPrimaryRowsPlusEnabled(bool bEnabled);
|
||||
void BindRow(UUIPlayerStatus_StatRow* Row, int32 StatId, const TCHAR* IconLetter, const TCHAR* DisplayName);
|
||||
|
||||
UFUNCTION() void HandleAttributesChanged(const FZeusAttributesSnapshot& Snapshot);
|
||||
/// Listener C++ no payload bruto (FZeusAttributesPayload nao e' BlueprintType
|
||||
/// — delegate multicast nativo). Converte pra FZeusAttributesSnapshot e
|
||||
/// chama RefreshFromSnapshot.
|
||||
void HandleSnapshotRaw(const FZeusAttributesPayload& Payload);
|
||||
|
||||
UFUNCTION() void HandleStatAllocReply(bool bAccepted, int32 Reason);
|
||||
UFUNCTION() void HandleRowPlusClicked(int32 StatId);
|
||||
UFUNCTION() void HandleCloseClicked();
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TWeakObjectPtr<UZeusAttributeComponent> BoundComponent;
|
||||
TWeakObjectPtr<UZeusGASComponent> BoundComponent;
|
||||
|
||||
/// Handle do listener nao-dynamic em OnSnapshotAppliedRaw — guardar pra remover
|
||||
/// em UnbindFromComponent (AddUObject nao auto-cleanup como AddDynamic).
|
||||
FDelegateHandle SnapshotRawHandle;
|
||||
|
||||
/** Trava local pra evitar spam-click: vira true ao clicar, false ao
|
||||
* receber StatAllocReply (ou snapshot novo, o que vier primeiro). */
|
||||
|
||||
@@ -18,8 +18,9 @@ public class ZMMO : ModuleRules
|
||||
"CommonUI",
|
||||
"CommonInput",
|
||||
"GameplayTags",
|
||||
"GameplayAbilities", // GAS: UAttributeSet, FGameplayAttributeData
|
||||
"ZeusNetwork",
|
||||
"ZeusAttributes",
|
||||
"ZeusGAS", // UZeusGASComponent, UZeusAttributeSet, FZeusAttributesSnapshot
|
||||
"ZeusJobs"
|
||||
});
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
#include "ZeusAttributeComponent.h"
|
||||
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
|
||||
UZeusAttributeComponent::UZeusAttributeComponent()
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
}
|
||||
|
||||
void UZeusAttributeComponent::ApplySnapshot(const FZeusAttributesSnapshot& 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 UZeusAttributeComponent::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 UZeusAttributeComponent::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 UZeusAttributeComponent::NotifyStatAllocReply(bool bAccepted, int32 Reason)
|
||||
{
|
||||
OnStatAllocReply.Broadcast(bAccepted, Reason);
|
||||
}
|
||||
|
||||
void UZeusAttributeComponent::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);
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
#include "ZeusAttributeNetworkHandler.h"
|
||||
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/GameStateBase.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "GameFramework/PlayerState.h"
|
||||
#include "ZeusAttributeComponent.h"
|
||||
#include "ZeusAttributeTypes.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Helper privado — converte o payload binario do plugin pra USTRUCT
|
||||
// Blueprint que o componente exibe.
|
||||
FZeusAttributesSnapshot ToSnapshot(const FZeusAttributesPayload& P)
|
||||
{
|
||||
FZeusAttributesSnapshot S;
|
||||
S.EntityId = P.EntityId;
|
||||
S.ClassId = P.ClassId;
|
||||
S.BaseLevel = P.BaseLevel;
|
||||
S.BaseExp = P.BaseExp;
|
||||
S.BaseExpToNext = P.BaseExpToNext;
|
||||
S.JobLevel = P.JobLevel;
|
||||
S.JobExp = P.JobExp;
|
||||
S.JobExpToNext = P.JobExpToNext;
|
||||
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.MatkBaseMin = P.MatkBaseMin;
|
||||
S.MatkBaseMax = P.MatkBaseMax;
|
||||
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 -> UZeusAttributeComponent` via PlayerArray do GameState.
|
||||
// AttributeComponent agora vive no PlayerState (vide AZeusPlayerState +
|
||||
// Component Registry em DefaultGame.ini). Itera apenas players (1 por
|
||||
// conexao), nao 1000+ atores — O(N_players).
|
||||
//
|
||||
// Cada UZeusAttributeComponent carrega seu proprio EntityId (seed em
|
||||
// AZeusCharacter::HandleLocalSpawnReady ou no primeiro snapshot).
|
||||
UZeusAttributeComponent* FindComponentForEntity(UWorld* World, int64 EntityId) // PR-HANDOFF-007 int64
|
||||
{
|
||||
if (!World || EntityId == 0) { return nullptr; }
|
||||
const AGameStateBase* GS = World->GetGameState();
|
||||
if (!GS) { return nullptr; }
|
||||
for (APlayerState* PS : GS->PlayerArray)
|
||||
{
|
||||
if (!PS) { continue; }
|
||||
UZeusAttributeComponent* Comp = PS->FindComponentByClass<UZeusAttributeComponent>();
|
||||
if (!Comp) { continue; }
|
||||
if (Comp->GetSnapshot().EntityId == EntityId)
|
||||
{
|
||||
return Comp;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool UZeusAttributeNetworkHandler::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 UZeusAttributeNetworkHandler::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
|
||||
if (UZeusNetworkSubsystem* Net = GetZeusNetSubsystem())
|
||||
{
|
||||
SnapshotFullHandle = Net->OnAttributeSnapshotFull.AddUObject(
|
||||
this, &UZeusAttributeNetworkHandler::HandleAttributeSnapshotFull);
|
||||
HpSpUpdateHandle = Net->OnHpSpUpdate.AddUObject(
|
||||
this, &UZeusAttributeNetworkHandler::HandleHpSpUpdate);
|
||||
LevelUpHandle = Net->OnLevelUp.AddUObject(
|
||||
this, &UZeusAttributeNetworkHandler::HandleLevelUp);
|
||||
StatAllocReplyHandle = Net->OnStatAllocReply.AddUObject(
|
||||
this, &UZeusAttributeNetworkHandler::HandleStatAllocReply);
|
||||
}
|
||||
}
|
||||
|
||||
void UZeusAttributeNetworkHandler::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* UZeusAttributeNetworkHandler::GetZeusNetSubsystem() const
|
||||
{
|
||||
const UWorld* World = GetWorld();
|
||||
if (!World) { return nullptr; }
|
||||
UGameInstance* GI = World->GetGameInstance();
|
||||
if (!GI) { return nullptr; }
|
||||
return GI->GetSubsystem<UZeusNetworkSubsystem>();
|
||||
}
|
||||
|
||||
void UZeusAttributeNetworkHandler::HandleAttributeSnapshotFull(const FZeusAttributesPayload& Payload)
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (!World) { return; }
|
||||
UZeusAttributeComponent* 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; }
|
||||
UZeusAttributeComponent* Candidate = PS->FindComponentByClass<UZeusAttributeComponent>();
|
||||
if (Candidate && Candidate->GetSnapshot().EntityId == 0)
|
||||
{
|
||||
Comp = Candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Comp)
|
||||
{
|
||||
Comp->ApplySnapshot(ToSnapshot(Payload));
|
||||
}
|
||||
}
|
||||
|
||||
void UZeusAttributeNetworkHandler::HandleHpSpUpdate(int64 EntityId, int32 Hp, int32 Sp)
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (!World) { return; }
|
||||
UZeusAttributeComponent* Comp = FindComponentForEntity(World, EntityId);
|
||||
if (Comp)
|
||||
{
|
||||
Comp->ApplyHpSpUpdate(Hp, Sp);
|
||||
}
|
||||
}
|
||||
|
||||
void UZeusAttributeNetworkHandler::HandleLevelUp(int64 EntityId, int32 NewBaseLevel, int32 StatusPointDelta)
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (!World) { return; }
|
||||
UZeusAttributeComponent* Comp = FindComponentForEntity(World, EntityId);
|
||||
if (Comp)
|
||||
{
|
||||
Comp->NotifyLevelUp(NewBaseLevel, StatusPointDelta);
|
||||
}
|
||||
}
|
||||
|
||||
void UZeusAttributeNetworkHandler::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 (UZeusAttributeComponent* Comp = FindLocalPlayerAttributeComponent())
|
||||
{
|
||||
Comp->NotifyStatAllocReply(bAccepted, Reason);
|
||||
}
|
||||
}
|
||||
|
||||
UZeusAttributeComponent* UZeusAttributeNetworkHandler::FindLocalPlayerAttributeComponent() const
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (!World) { return nullptr; }
|
||||
APlayerController* PC = World->GetFirstPlayerController();
|
||||
if (!PC || !PC->PlayerState) { return nullptr; }
|
||||
return PC->PlayerState->FindComponentByClass<UZeusAttributeComponent>();
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#include "ZeusAttributesModule.h"
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
void FZeusAttributesModule::StartupModule()
|
||||
{
|
||||
}
|
||||
|
||||
void FZeusAttributesModule::ShutdownModule()
|
||||
{
|
||||
}
|
||||
|
||||
IMPLEMENT_MODULE(FZeusAttributesModule, ZeusAttributes)
|
||||
@@ -1,105 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "ZeusAttributeTypes.h"
|
||||
#include "ZeusAttributeComponent.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FZeusOnAttributesChanged, const FZeusAttributesSnapshot&, Snapshot);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FZeusOnHpSpChanged, int32, Hp, int32, Sp);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FZeusOnLevelUp, 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(FZeusOnStatAllocReply, 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: `UZeusAttributeNetworkHandler`
|
||||
* (UWorldSubsystem) liga em `UZeusNetworkSubsystem::OnAttributeSnapshotFull`,
|
||||
* resolve `EntityId -> AActor*` via `UZeusWorldSubsystem` e chama
|
||||
* `ApplySnapshot` no componente do ator.
|
||||
*/
|
||||
UCLASS(ClassGroup=(Zeus), meta=(BlueprintSpawnableComponent),
|
||||
hidecategories=(Activation, Collision, Cooking, Object, Replication))
|
||||
class ZEUSATTRIBUTES_API UZeusAttributeComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UZeusAttributeComponent();
|
||||
|
||||
/// Aplica snapshot full recebido do servidor. Substitui `Current` e
|
||||
/// dispara `OnAttributesChanged` + `OnHpSpChanged` (se hp/sp mudaram).
|
||||
UFUNCTION(BlueprintCallable, Category = "Zeus|Attributes")
|
||||
void ApplySnapshot(const FZeusAttributesSnapshot& 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
|
||||
/// `AZeusCharacter` 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(int64 InEntityId) { Current.EntityId = InEntityId; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Zeus|Attributes")
|
||||
const FZeusAttributesSnapshot& 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")
|
||||
FZeusOnAttributesChanged OnAttributesChanged;
|
||||
|
||||
/** HP ou SP mudou (efêmero ou via snapshot). Para barras animadas. */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Zeus|Attributes")
|
||||
FZeusOnHpSpChanged OnHpSpChanged;
|
||||
|
||||
/** Subiu de nível. Para efeitos visuais ("LEVEL UP!" toast). */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Zeus|Attributes")
|
||||
FZeusOnLevelUp OnLevelUp;
|
||||
|
||||
/** Resposta ao C_STAT_ALLOC (aceito ou rejeitado). UI reage. */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Zeus|Attributes")
|
||||
FZeusOnStatAllocReply OnStatAllocReply;
|
||||
|
||||
protected:
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes")
|
||||
FZeusAttributesSnapshot Current;
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/WorldSubsystem.h"
|
||||
#include "ZeusAttributesPayload.h"
|
||||
#include "ZeusAttributeNetworkHandler.generated.h"
|
||||
|
||||
class UZeusNetworkSubsystem;
|
||||
|
||||
/**
|
||||
* Ponte entre o `UZeusNetworkSubsystem` (GameInstanceSubsystem do plugin
|
||||
* ZeusNetwork) e os `UZeusAttributeComponent` ligados aos atores do mundo.
|
||||
*
|
||||
* Por que UWorldSubsystem (e nao GameInstanceSubsystem)?
|
||||
* - O registry de atores (`UZeusWorldSubsystem::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 ->
|
||||
* FZeusAttributesSnapshot` (USTRUCT Blueprint) acontece aqui.
|
||||
*/
|
||||
UCLASS()
|
||||
class ZEUSATTRIBUTES_API UZeusAttributeNetworkHandler : 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(int64 EntityId, int32 Hp, int32 Sp);
|
||||
void HandleLevelUp(int64 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 UZeusAttributeComponent* FindLocalPlayerAttributeComponent() const;
|
||||
|
||||
FDelegateHandle SnapshotFullHandle;
|
||||
FDelegateHandle HpSpUpdateHandle;
|
||||
FDelegateHandle LevelUpHandle;
|
||||
FDelegateHandle StatAllocReplyHandle;
|
||||
};
|
||||
@@ -1,88 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "ZeusAttributeTypes.generated.h"
|
||||
|
||||
/**
|
||||
* Snapshot completo dos atributos do char vindo do server via
|
||||
* S_ATTRIBUTE_SNAPSHOT_FULL (opcode 1500).
|
||||
*
|
||||
* Cliente NUNCA recalcula derivados — apenas exibe o que recebe. Os campos
|
||||
* HP/MaxHp/SP/MaxSp ja sao os "efetivos" (base + equip + buff somados no
|
||||
* servidor); o cliente nao ve as 3 camadas separadas.
|
||||
*
|
||||
* Espelha `FZeusAttributesPayload` do plugin ZeusNetwork + a serializacao
|
||||
* em `Server/.../AttributeService.cpp::WriteSnapshotPayload`.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct ZEUSATTRIBUTES_API FZeusAttributesSnapshot
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// === Identidade + classe ===
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int64 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;
|
||||
/** EXP pra subir o proximo base level (server resolve via JobsDatabase).
|
||||
* 0 = ja' em cap -> barra cheia. % = BaseExp / BaseExpToNext. */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int64 BaseExpToNext = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int32 JobLevel = 1;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int64 JobExp = 0;
|
||||
/** EXP pra subir o proximo job level. 0 = ja' em cap -> barra cheia. */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes") int64 JobExpToNext = 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;
|
||||
|
||||
/** MATK pre-renewal e' um range [min, max] (rathena status_base_matk_min/max).
|
||||
* Display: "MATK min ~ max + equip". */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 MatkBaseMin = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 MatkBaseMax = 0;
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 MatkEquipBonus = 0;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Zeus|Attributes|Derived") int32 DefBase = 0;
|
||||
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;
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
#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 FZeusAttributesModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class ZeusAttributes : ModuleRules
|
||||
{
|
||||
public ZeusAttributes(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
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "ZeusAttributes",
|
||||
"gameType": "MMO",
|
||||
"version": "0.1.0",
|
||||
"side": "client",
|
||||
"dependencies": {
|
||||
"engine": ["Core", "CoreUObject", "Engine", "UMG", "Slate", "CommonUI", "CommonInput"],
|
||||
"plugins": ["ZeusNetwork"],
|
||||
"modules": []
|
||||
},
|
||||
"publicHeaders": [
|
||||
"ZeusAttributesModule.h",
|
||||
"ZeusAttributeTypes.h",
|
||||
"ZeusAttributeComponent.h",
|
||||
"ZeusAttributeNetworkHandler.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."
|
||||
}
|
||||
@@ -9,11 +9,6 @@
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default"
|
||||
},
|
||||
{
|
||||
"Name": "ZeusAttributes",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "PreDefault"
|
||||
},
|
||||
{
|
||||
"Name": "ZeusJobs",
|
||||
"Type": "Runtime",
|
||||
|
||||
Reference in New Issue
Block a user