#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 It(World); It; ++It) { AActor* Actor = *It; if (!Actor) { continue; } UZMMOAttributeComponent* Comp = Actor->FindComponentByClass(); 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(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(); } 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 It(World); It; ++It) { AActor* Actor = *It; if (!Actor) { continue; } Comp = Actor->FindComponentByClass(); 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); } }