diff --git a/Source/ZMMO/Game/Network/ZeusAOIComponent.cpp b/Source/ZMMO/Game/Network/ZeusAOIComponent.cpp index 3886ea0..0b4c95a 100644 --- a/Source/ZMMO/Game/Network/ZeusAOIComponent.cpp +++ b/Source/ZMMO/Game/Network/ZeusAOIComponent.cpp @@ -3,6 +3,7 @@ #include "ZeusAOIComponent.h" #include "ZeusNetworkingClientSubsystem.h" // V1 canonico: OnDebugAoiInfo (6161) + EmitDebugAoiRequest (6160) +#include "ZeusFrontierOverlayActor.h" // ator que desenha as paredes de fronteira #include "ZMMONetLog.h" // Batch 2.5: LogZeusAOI (categoria do modulo ZMMO) #include "DrawDebugHelpers.h" @@ -23,6 +24,8 @@ static TAutoConsoleVariable CVarShowHandoff( TEXT("zeus.debug.handoff"), 0, TEXT("Desenha a linha de handoff (placeholder)."), ECVF_Default); static TAutoConsoleVariable CVarShowDrift( TEXT("zeus.debug.drift"), 0, TEXT("Desenha o drift autoritativo (TODO)."), ECVF_Default); +static TAutoConsoleVariable CVarShowFrontier( + TEXT("zeus.debug.frontier"), 0, TEXT("Desenha as paredes das fronteiras do mesh (own<->neighbor)."), ECVF_Default); namespace { @@ -62,6 +65,7 @@ void UZeusAOIComponent::BeginPlay() if (UZeusNetworkingClientSubsystem* NetC = GI->GetSubsystem()) { AoiConfigHandle_ = NetC->OnDebugAoiInfo.AddUObject(this, &UZeusAOIComponent::HandleAoiConfig); + FrontierConfigHandle_ = NetC->OnDebugFrontierInfo.AddUObject(this, &UZeusAOIComponent::HandleFrontierConfig); } } } @@ -73,7 +77,7 @@ void UZeusAOIComponent::BeginPlay() void UZeusAOIComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) { - if (AoiConfigHandle_.IsValid()) + if (AoiConfigHandle_.IsValid() || FrontierConfigHandle_.IsValid()) { if (UWorld* World = GetWorld()) { @@ -81,11 +85,18 @@ void UZeusAOIComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) { if (UZeusNetworkingClientSubsystem* NetC = GI->GetSubsystem()) { - NetC->OnDebugAoiInfo.Remove(AoiConfigHandle_); + if (AoiConfigHandle_.IsValid()) { NetC->OnDebugAoiInfo.Remove(AoiConfigHandle_); } + if (FrontierConfigHandle_.IsValid()) { NetC->OnDebugFrontierInfo.Remove(FrontierConfigHandle_); } } } } AoiConfigHandle_.Reset(); + FrontierConfigHandle_.Reset(); + } + if (FrontierActor_) + { + FrontierActor_->Destroy(); + FrontierActor_ = nullptr; } Super::EndPlay(EndPlayReason); } @@ -95,6 +106,26 @@ void UZeusAOIComponent::TickComponent(float DeltaTime, ELevelTick TickType, { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + // Frontier: sincroniza o overlay com o estado atual. Pega o liga/desliga via + // CVar zeus.debug.frontier (que nao passa por SetOverlayEnabled) e garante que + // ao desligar as paredes sejam REMOVIDAS (UpdateFrontierOverlay -> ClearOverlay). + if (ResolveOverlay(EZeusAOIOverlay::FrontierCells) != bFrontierApplied_) + { + UpdateFrontierOverlay(); + } + + // Re-pede o frontier periodicamente enquanto ligado -> o overlay reflete + // split/merge da topologia dinamica (debug client-side, baixa freq, so' ON). + if (ResolveOverlay(EZeusAOIOverlay::FrontierCells)) + { + FrontierRepollAccumSec_ += DeltaTime; + if (FrontierRepollAccumSec_ >= FrontierRepollIntervalSec) + { + FrontierRepollAccumSec_ = 0.0f; + RequestFrontierConfig(); + } + } + if (AnyOverlayActive()) { DrawOverlays(); @@ -127,6 +158,64 @@ void UZeusAOIComponent::HandleAoiConfig(float InterestCm, float DespawnCm) DespawnCm, DespawnCm / 100.0f); } +void UZeusAOIComponent::RequestFrontierConfig() +{ + if (UWorld* World = GetWorld()) + { + if (UGameInstance* GI = World->GetGameInstance()) + { + if (UZeusNetworkingClientSubsystem* NetC = GI->GetSubsystem()) + { + NetC->EmitDebugFrontierRequest(); // V1: no-op se ainda nao Accepted + } + } + } +} + +void UZeusAOIComponent::HandleFrontierConfig(const ZeusV1::FDebugFrontierInfo& Info) +{ + FrontierInfo_ = Info; + bFrontierReceived_ = true; + UE_LOG(LogZeusAOI, Display, + TEXT("Frontier cfg recv own=%d neighbors=%d"), + Info.OwnedCells.Num(), Info.NeighborCells.Num()); + UpdateFrontierOverlay(); +} + +AZeusFrontierOverlayActor* UZeusAOIComponent::EnsureFrontierActor() +{ + if (FrontierActor_) { return FrontierActor_; } + UWorld* World = GetWorld(); + if (!World) { return nullptr; } + FActorSpawnParameters Params; + Params.Owner = GetOwner(); + Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + FrontierActor_ = World->SpawnActor( + AZeusFrontierOverlayActor::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator, Params); + return FrontierActor_; +} + +void UZeusAOIComponent::UpdateFrontierOverlay() +{ + const bool bOn = ResolveOverlay(EZeusAOIOverlay::FrontierCells); + bFrontierApplied_ = bOn; + if (!bOn) + { + if (FrontierActor_) { FrontierActor_->ClearOverlay(); } + return; + } + if (!bFrontierReceived_) + { + // Ainda nao temos as cells; pede e espera o 6163 (HandleFrontierConfig re-chama). + RequestFrontierConfig(); + return; + } + AZeusFrontierOverlayActor* Actor = EnsureFrontierActor(); + if (!Actor) { return; } + const float AnchorZ = GetOwner() ? GetOwner()->GetActorLocation().Z : 0.0f; + Actor->RebuildFromFrontier(FrontierInfo_, AnchorZ); +} + bool UZeusAOIComponent::ResolveOverlay(EZeusAOIOverlay Overlay) const { switch (Overlay) @@ -137,6 +226,7 @@ bool UZeusAOIComponent::ResolveOverlay(EZeusAOIOverlay Overlay) const case EZeusAOIOverlay::ProxyLabels: return bShowProxyLabels || CVarShowProxy.GetValueOnGameThread() > 0; case EZeusAOIOverlay::HandoffLine: return bShowHandoffLine || CVarShowHandoff.GetValueOnGameThread() > 0; case EZeusAOIOverlay::AuthDrift: return bShowAuthDrift || CVarShowDrift.GetValueOnGameThread() > 0; + case EZeusAOIOverlay::FrontierCells: return bShowFrontier || CVarShowFrontier.GetValueOnGameThread() > 0; default: return false; } } @@ -148,7 +238,8 @@ bool UZeusAOIComponent::AnyOverlayActive() const || ResolveOverlay(EZeusAOIOverlay::CellBounds) || ResolveOverlay(EZeusAOIOverlay::ProxyLabels) || ResolveOverlay(EZeusAOIOverlay::HandoffLine) - || ResolveOverlay(EZeusAOIOverlay::AuthDrift); + || ResolveOverlay(EZeusAOIOverlay::AuthDrift) + || ResolveOverlay(EZeusAOIOverlay::FrontierCells); } void UZeusAOIComponent::DrawOverlays() @@ -198,6 +289,7 @@ void UZeusAOIComponent::SetOverlayEnabled_Implementation(EZeusAOIOverlay Overlay case EZeusAOIOverlay::ProxyLabels: bShowProxyLabels = bEnabled; break; case EZeusAOIOverlay::HandoffLine: bShowHandoffLine = bEnabled; break; case EZeusAOIOverlay::AuthDrift: bShowAuthDrift = bEnabled; break; + case EZeusAOIOverlay::FrontierCells: bShowFrontier = bEnabled; break; default: break; } @@ -207,17 +299,27 @@ void UZeusAOIComponent::SetOverlayEnabled_Implementation(EZeusAOIOverlay Overlay { RequestAoiConfig(); } + + // Frontier: ao ligar pede as cells (6162) e reconstroi; ao desligar limpa as paredes. + if (Overlay == EZeusAOIOverlay::FrontierCells) + { + if (bEnabled) { RequestFrontierConfig(); } + UpdateFrontierOverlay(); + } } void UZeusAOIComponent::SetAllOverlays_Implementation(bool bEnabled) { bShowAOIRadius = bShowDespawnZone = bShowCellBounds = bEnabled; bShowProxyLabels = bShowHandoffLine = bShowAuthDrift = bEnabled; + bShowFrontier = bEnabled; if (bEnabled && InterestRadiusCm <= 0.0f) { RequestAoiConfig(); } + if (bEnabled) { RequestFrontierConfig(); } + UpdateFrontierOverlay(); } bool UZeusAOIComponent::IsOverlayEnabled_Implementation(EZeusAOIOverlay Overlay) const @@ -239,5 +341,6 @@ float UZeusAOIComponent::GetDespawnRadiusCm_Implementation() const int32 UZeusAOIComponent::GetEnabledOverlayCount() const { return (bShowAOIRadius ? 1 : 0) + (bShowDespawnZone ? 1 : 0) + (bShowCellBounds ? 1 : 0) - + (bShowProxyLabels ? 1 : 0) + (bShowHandoffLine ? 1 : 0) + (bShowAuthDrift ? 1 : 0); + + (bShowProxyLabels ? 1 : 0) + (bShowHandoffLine ? 1 : 0) + (bShowAuthDrift ? 1 : 0) + + (bShowFrontier ? 1 : 0); } diff --git a/Source/ZMMO/Game/Network/ZeusAOIComponent.h b/Source/ZMMO/Game/Network/ZeusAOIComponent.h index e91bab6..a24a5a9 100644 --- a/Source/ZMMO/Game/Network/ZeusAOIComponent.h +++ b/Source/ZMMO/Game/Network/ZeusAOIComponent.h @@ -5,8 +5,11 @@ #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "ZeusAOIDebugTarget.h" // plugin ZeusAdminToolsRuntime: interface + EZeusAOIOverlay +#include "ZeusV1Protocol.h" // ZeusV1::FDebugFrontierInfo (overlay de fronteira) #include "ZeusAOIComponent.generated.h" +class AZeusFrontierOverlayActor; + /** * UZeusAOIComponent (cliente / jogo) * @@ -53,6 +56,9 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|AOI Debug|Overlays") bool bShowProxyLabels = false; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|AOI Debug|Overlays") bool bShowHandoffLine = false; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|AOI Debug|Overlays") bool bShowAuthDrift = false; + // Fronteiras do mesh: paredes own<->neighbor com label "Cell ", renderizadas + // por AZeusFrontierOverlayActor a partir do DEBUG_FRONTIER_INFO (6163). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|AOI Debug|Overlays") bool bShowFrontier = false; // === Parametros === // Raios reais vem do servidor (S_DEBUG_AOI_INFO). 0 = ainda nao recebido (loading). @@ -76,6 +82,30 @@ private: /** Bind de UZeusNetworkSubsystem::OnDebugAoiInfo: armazena os raios reais. */ void HandleAoiConfig(float InterestCm, float DespawnCm); + /** Pede o frontier do mesh ao servidor (DEBUG_FRONTIER_REQUEST 6162). */ + void RequestFrontierConfig(); + /** Bind de OnDebugFrontierInfo: guarda as cells e (se overlay on) reconstroi as paredes. */ + void HandleFrontierConfig(const ZeusV1::FDebugFrontierInfo& Info); + /** Spawna (lazy) o ator que desenha as paredes + labels. */ + AZeusFrontierOverlayActor* EnsureFrontierActor(); + /** Liga / reconstroi / limpa o overlay de fronteira conforme bShowFrontier + dados. */ + void UpdateFrontierOverlay(); + /** Handle do bind do OnDebugAoiInfo (pra remover no EndPlay). */ FDelegateHandle AoiConfigHandle_; + + /** Handle do bind do OnDebugFrontierInfo (frontier overlay). */ + FDelegateHandle FrontierConfigHandle_; + /** Ultimo frontier recebido do server (own cells + neighbors). */ + ZeusV1::FDebugFrontierInfo FrontierInfo_; + bool bFrontierReceived_ = false; + /** Estado do overlay JA aplicado (paredes desenhadas). O TickComponent compara + * com ResolveOverlay p/ pegar liga/desliga via CVar tambem (nao so' o toggle). */ + bool bFrontierApplied_ = false; + /** Re-poll periodico do frontier enquanto ligado -> overlay reflete split/merge + * da topologia dinamica (o 6163 traz o estado atual do topology:current). */ + float FrontierRepollIntervalSec = 2.0f; + float FrontierRepollAccumSec_ = 0.0f; + /** Ator que desenha as paredes + labels (spawn lazy ao ligar o overlay). */ + UPROPERTY() TObjectPtr FrontierActor_ = nullptr; }; diff --git a/Source/ZMMO/Game/Network/ZeusFrontierOverlayActor.cpp b/Source/ZMMO/Game/Network/ZeusFrontierOverlayActor.cpp new file mode 100644 index 0000000..5027431 --- /dev/null +++ b/Source/ZMMO/Game/Network/ZeusFrontierOverlayActor.cpp @@ -0,0 +1,205 @@ +// Copyright Zeus Server Engine. All rights reserved. + +#include "ZeusFrontierOverlayActor.h" + +#include "ZMMONetLog.h" // LogZeusAOI (categoria do modulo ZMMO) + +#include "ProceduralMeshComponent.h" +#include "Materials/MaterialInterface.h" +#include "Engine/World.h" +#include "DrawDebugHelpers.h" +#include "UObject/ConstructorHelpers.h" + +namespace +{ + const TCHAR* kWallMaterialPath = + TEXT("/Game/ZMMO/Materials/VFX/Portal/GridBoundary/Instancia/MI_VFX_Portal_GridBoundary_01.MI_VFX_Portal_GridBoundary_01"); + + const FColor kLabelAlive(93, 213, 255); // ciano -- server online + const FColor kLabelDead(255, 90, 90); // vermelho -- server offline / cell vazia + + // Offset do cellId de filha de split (espelha CHILD_CELLID_OFFSET do server). + constexpr uint32 kChildCellIdOffset = 1000000u; + + // Decodifica o cellId int na notacao de quadtree: raiz -> "0".."3"; filha -> + // "," (ex "0,2"); neto -> "0,2,1" (recursivo). Espelha + // childCellId = offset + pai*4 + quadrante do SplitCoordinator. + FString DecodeCellPath(uint32 CellId) + { + if (CellId < kChildCellIdOffset) + { + return FString::Printf(TEXT("%u"), CellId); + } + const uint32 Rel = CellId - kChildCellIdOffset; + return DecodeCellPath(Rel / 4u) + FString::Printf(TEXT(",%u"), Rel % 4u); + } + + // Aresta compartilhada entre dois retangulos axis-aligned (no plano XY). + bool SharedEdge(float aMinX, float aMinY, float aMaxX, float aMaxY, + float bMinX, float bMinY, float bMaxX, float bMaxY, + float Eps, FVector2D& OutA, FVector2D& OutB) + { + const float yLo = FMath::Max(aMinY, bMinY), yHi = FMath::Min(aMaxY, bMaxY); + const float xLo = FMath::Max(aMinX, bMinX), xHi = FMath::Min(aMaxX, bMaxX); + + if (FMath::Abs(aMaxX - bMinX) <= Eps && yHi > yLo) { OutA = {aMaxX, yLo}; OutB = {aMaxX, yHi}; return true; } + if (FMath::Abs(aMinX - bMaxX) <= Eps && yHi > yLo) { OutA = {aMinX, yLo}; OutB = {aMinX, yHi}; return true; } + if (FMath::Abs(aMaxY - bMinY) <= Eps && xHi > xLo) { OutA = {xLo, aMaxY}; OutB = {xHi, aMaxY}; return true; } + if (FMath::Abs(aMinY - bMaxY) <= Eps && xHi > xLo) { OutA = {xLo, aMinY}; OutB = {xHi, aMinY}; return true; } + return false; + } +} + +AZeusFrontierOverlayActor::AZeusFrontierOverlayActor() +{ + PrimaryActorTick.bCanEverTick = true; + PrimaryActorTick.bStartWithTickEnabled = true; + + WallMesh = CreateDefaultSubobject(TEXT("WallMesh")); + SetRootComponent(WallMesh); + WallMesh->bUseAsyncCooking = false; + WallMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); + WallMesh->SetCastShadow(false); + + static ConstructorHelpers::FObjectFinder MatFinder(kWallMaterialPath); + if (MatFinder.Succeeded()) + { + WallMaterial = MatFinder.Object; + } +} + +void AZeusFrontierOverlayActor::RebuildFromFrontier(const ZeusV1::FDebugFrontierInfo& Info, float AnchorZCm) +{ + if (!WallMesh) { return; } + AnchorZ_ = AnchorZCm; + + // 1) Junta own + neighbors numa lista unica de cells (grid inteiro). Dedup por cellId. + Cells_.Reset(); + auto AddCell = [this](uint32 CellId, int32 MinX, int32 MinY, int32 MaxX, int32 MaxY, + const FString& InOwner, bool bIsDead) + { + for (const FCellView& Ex : Cells_) { if (Ex.CellId == CellId) { return; } } + FCellView V; + V.CellId = CellId; + V.MinX = static_cast(MinX); V.MinY = static_cast(MinY); + V.MaxX = static_cast(MaxX); V.MaxY = static_cast(MaxY); + V.Owner = InOwner; V.bDead = bIsDead; + Cells_.Add(MoveTemp(V)); + }; + for (const ZeusV1::FFrontierOwnCell& C : Info.OwnedCells) + { + AddCell(C.CellId, C.MinXCm, C.MinYCm, C.MaxXCm, C.MaxYCm, C.OwnerName, false); + } + for (const ZeusV1::FFrontierNeighborCell& Nb : Info.NeighborCells) + { + AddCell(Nb.CellId, Nb.MinXCm, Nb.MinYCm, Nb.MaxXCm, Nb.MaxYCm, Nb.OwnerName, Nb.bDead); + } + + // 2) Paredes: 1 quad por aresta compartilhada entre QUALQUER par (i dedup). + const float BaseZ = AnchorZCm - WallHeightCm * 0.5f; + const float TopZ = AnchorZCm + WallHeightCm * 0.5f; + + TArray Verts; + TArray Tris; + TArray Normals; + TArray UVs; + TArray Tangents; + TArray Colors; + + for (int32 i = 0; i < Cells_.Num(); ++i) + { + for (int32 j = i + 1; j < Cells_.Num(); ++j) + { + const FCellView& A = Cells_[i]; + const FCellView& B = Cells_[j]; + FVector2D P0, P1; + if (!SharedEdge(A.MinX, A.MinY, A.MaxX, A.MaxY, + B.MinX, B.MinY, B.MaxX, B.MaxY, EdgeEpsilonCm, P0, P1)) + { + continue; + } + + const int32 Base = Verts.Num(); + Verts.Add(FVector(P0.X, P0.Y, BaseZ)); + Verts.Add(FVector(P1.X, P1.Y, BaseZ)); + Verts.Add(FVector(P1.X, P1.Y, TopZ)); + Verts.Add(FVector(P0.X, P0.Y, TopZ)); + + const FVector2D Dir2D = (P1 - P0).GetSafeNormal(); + const FVector N(-Dir2D.Y, Dir2D.X, 0.f); + Normals.Add(N); Normals.Add(N); Normals.Add(N); Normals.Add(N); + + UVs.Add(FVector2D(0.f, 1.f)); + UVs.Add(FVector2D(1.f, 1.f)); + UVs.Add(FVector2D(1.f, 0.f)); + UVs.Add(FVector2D(0.f, 0.f)); + + const FProcMeshTangent Tan(FVector(Dir2D.X, Dir2D.Y, 0.f), false); + Tangents.Add(Tan); Tangents.Add(Tan); Tangents.Add(Tan); Tangents.Add(Tan); + + const FLinearColor C = (A.bDead || B.bDead) ? FLinearColor(kLabelDead) : FLinearColor(kLabelAlive); + Colors.Add(C); Colors.Add(C); Colors.Add(C); Colors.Add(C); + + Tris.Add(Base + 0); Tris.Add(Base + 1); Tris.Add(Base + 2); + Tris.Add(Base + 0); Tris.Add(Base + 2); Tris.Add(Base + 3); + Tris.Add(Base + 0); Tris.Add(Base + 2); Tris.Add(Base + 1); + Tris.Add(Base + 0); Tris.Add(Base + 3); Tris.Add(Base + 2); + } + } + + WallMesh->ClearAllMeshSections(); + WallMesh->SetVisibility(true); + if (Verts.Num() > 0) + { + WallMesh->CreateMeshSection_LinearColor( + /*SectionIndex=*/0, Verts, Tris, Normals, UVs, Colors, Tangents, /*bCreateCollision=*/false); + if (WallMaterial) { WallMesh->SetMaterial(0, WallMaterial); } + } + bHasOverlay_ = (Cells_.Num() > 0); + + UE_LOG(LogZeusAOI, Display, + TEXT("Frontier overlay rebuilt: cells=%d walls=%d anchorZ=%.0f%s"), + Cells_.Num(), Verts.Num() / 4, AnchorZCm, + WallMaterial ? TEXT("") : TEXT(" [WARN: material nula]")); +} + +void AZeusFrontierOverlayActor::ClearOverlay() +{ + if (WallMesh) + { + WallMesh->ClearAllMeshSections(); + WallMesh->SetVisibility(false); + } + Cells_.Reset(); + bHasOverlay_ = false; +} + +void AZeusFrontierOverlayActor::DrawCellLabels() +{ + if (!bHasOverlay_ || Cells_.Num() == 0) { return; } + UWorld* World = GetWorld(); + if (!World) { return; } + + for (const FCellView& C : Cells_) + { + const float Cx = (C.MinX + C.MaxX) * 0.5f; + const float Cy = (C.MinY + C.MaxY) * 0.5f; + const FVector Pos(Cx, Cy, AnchorZ_ + LabelHeightCm); + + // "Cell 0" / "Cell 0,2" (notacao da quadtree) + nome do server dono. + FString Txt = FString::Printf(TEXT("Cell %s"), *DecodeCellPath(C.CellId)); + if (!C.Owner.IsEmpty()) { Txt += TEXT("\n") + C.Owner; } + const FColor Col = C.bDead ? kLabelDead : kLabelAlive; + + // DrawDebugString: billboard de tela (visivel de qualquer lado, nao gira, + // tamanho de tela constante). Re-desenhado a cada frame (Duration 0). + DrawDebugString(World, Pos, Txt, /*TestBaseActor=*/nullptr, Col, + /*Duration=*/0.0f, /*bDrawShadow=*/true, LabelFontScale); + } +} + +void AZeusFrontierOverlayActor::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + DrawCellLabels(); +} diff --git a/Source/ZMMO/Game/Network/ZeusFrontierOverlayActor.h b/Source/ZMMO/Game/Network/ZeusFrontierOverlayActor.h new file mode 100644 index 0000000..77fcaef --- /dev/null +++ b/Source/ZMMO/Game/Network/ZeusFrontierOverlayActor.h @@ -0,0 +1,70 @@ +// Copyright Zeus Server Engine. All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "ZeusV1Protocol.h" // ZeusV1::FDebugFrontierInfo (plugin ZeusNetwork) +#include "ZeusFrontierOverlayActor.generated.h" + +class UProceduralMeshComponent; +class UMaterialInterface; + +/** + * AZeusFrontierOverlayActor (cliente / debug) + * + * Desenha no MUNDO as "paredes" de TODAS as fronteiras do grid de server-meshing + * (todas as arestas compartilhadas entre quaisquer duas cells, com dedup), usando + * a material de portal MI_VFX_Portal_GridBoundary_01. As cells vem do + * DEBUG_FRONTIER_INFO (6163), que reflete a topologia DINAMICA (split/merge). + * + * Label: no CENTRO de cada cell, um texto via DrawDebugString (billboard de tela, + * visivel de qualquer lado, nao gira). Mostra a notacao da cell: + * raiz -> "Cell 0", "Cell 1", ... + * split -> "Cell 0,0", "Cell 0,1", ... (pai,quadrante) -- reflete a quadtree. + * + o nome do servidor dono. NAO e' replicado: visual no cliente local. + */ +UCLASS() +class ZMMO_API AZeusFrontierOverlayActor : public AActor +{ + GENERATED_BODY() + +public: + AZeusFrontierOverlayActor(); + + virtual void Tick(float DeltaSeconds) override; + + /** (Re)constroi as paredes do grid + guarda as cells pros labels. + * AnchorZCm ancora a altura das paredes/labels (tipicamente o Z do player). */ + void RebuildFromFrontier(const ZeusV1::FDebugFrontierInfo& Info, float AnchorZCm); + + /** Limpa a geometria (overlay desligado). */ + void ClearOverlay(); + + // === Parametros das paredes === + UPROPERTY(EditAnywhere, Category = "Zeus|Frontier") float WallHeightCm = 12000.0f; // 120m + UPROPERTY(EditAnywhere, Category = "Zeus|Frontier") float EdgeEpsilonCm = 4.0f; // tolerancia "compartilha aresta" + + // === Label "Cell X,Y" no centro de cada cell (DrawDebugString) === + UPROPERTY(EditAnywhere, Category = "Zeus|Frontier|Label") float LabelFontScale = 1.4f; // tamanho na tela (screen-space) + UPROPERTY(EditAnywhere, Category = "Zeus|Frontier|Label") float LabelHeightCm = 250.0f; // altura do label acima do anchor + +private: + UPROPERTY() UProceduralMeshComponent* WallMesh = nullptr; + UPROPERTY() UMaterialInterface* WallMaterial = nullptr; + + /// Snapshot leve das cells (own + neighbors) pros labels. + struct FCellView + { + uint32 CellId = 0; + float MinX = 0.f, MinY = 0.f, MaxX = 0.f, MaxY = 0.f; + FString Owner; // "Server1" (ou vazio) + bool bDead = false; + }; + TArray Cells_; + float AnchorZ_ = 0.0f; + bool bHasOverlay_ = false; + + /// Desenha "Cell X,Y\n" no centro de cada cell (chamado no Tick). + void DrawCellLabels(); +}; diff --git a/Source/ZMMO/ZMMO.Build.cs b/Source/ZMMO/ZMMO.Build.cs index 322e700..5095e10 100644 --- a/Source/ZMMO/ZMMO.Build.cs +++ b/Source/ZMMO/ZMMO.Build.cs @@ -19,6 +19,7 @@ public class ZMMO : ModuleRules "CommonInput", "GameplayTags", "GameplayAbilities", // GAS: UAttributeSet, FGameplayAttributeData + "ProceduralMeshComponent", // paredes do overlay de fronteira (AZeusFrontierOverlayActor) "ZeusNetwork", "ZeusGAS", // UZeusGASComponent, UZeusAttributeSet, FZeusAttributesSnapshot "ZeusJobs",