#include "ZMMOAttributeNetworkHandler.h" #include "Engine/GameInstance.h" #include "Engine/World.h" #include "GameFramework/GameStateBase.h" #include "GameFramework/PlayerController.h" #include "GameFramework/PlayerState.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.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 -> UZMMOAttributeComponent` via PlayerArray do GameState. // AttributeComponent agora vive no PlayerState (vide AZMMOPlayerState + // Component Registry em DefaultGame.ini). Itera apenas players (1 por // conexao), nao 1000+ atores — O(N_players). // // Cada UZMMOAttributeComponent carrega seu proprio EntityId (seed em // AZMMOPlayerCharacter::HandleLocalSpawnReady ou no primeiro snapshot). UZMMOAttributeComponent* FindComponentForEntity(UWorld* World, int32 EntityId) { if (!World || EntityId == 0) { return nullptr; } const AGameStateBase* GS = World->GetGameState(); if (!GS) { return nullptr; } for (APlayerState* PS : GS->PlayerArray) { if (!PS) { continue; } UZMMOAttributeComponent* Comp = PS->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); StatAllocReplyHandle = Net->OnStatAllocReply.AddUObject( this, &UZMMOAttributeNetworkHandler::HandleStatAllocReply); } } 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(); } if (StatAllocReplyHandle.IsValid()) { Net->OnStatAllocReply.Remove(StatAllocReplyHandle); StatAllocReplyHandle.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) { // 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; } UZMMOAttributeComponent* Candidate = PS->FindComponentByClass(); if (Candidate && Candidate->GetSnapshot().EntityId == 0) { Comp = Candidate; break; } } } } 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); } } void UZMMOAttributeNetworkHandler::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 (UZMMOAttributeComponent* Comp = FindLocalPlayerAttributeComponent()) { Comp->NotifyStatAllocReply(bAccepted, Reason); } } UZMMOAttributeComponent* UZMMOAttributeNetworkHandler::FindLocalPlayerAttributeComponent() const { UWorld* World = GetWorld(); if (!World) { return nullptr; } APlayerController* PC = World->GetFirstPlayerController(); if (!PC || !PC->PlayerState) { return nullptr; } return PC->PlayerState->FindComponentByClass(); }