3 Commits

Author SHA1 Message Date
1f2441bde7 Merge pull request 'Consolidar GameplayAbilitySystem na main (baseline cliente tudo funcionando 2026-06-16)' (#6) from GameplayAbilitySystem into main
Reviewed-on: #6
2026-06-16 00:26:52 -03:00
216e580548 Overlay de fronteira (cliente): paredes do grid + labels Cell X,Y
- AZeusFrontierOverlayActor: paredes (ProceduralMesh, material de portal) em
  todas as fronteiras do grid; label DrawDebugString no centro de cada cell com
  notacao de quadtree ("Cell 0" / "Cell 0,2" pos-split) + nome do servidor.
- UZeusAOIComponent: liga via toggle "Fronteiras (Mesh)" / CVar zeus.debug.frontier,
  spawna/atualiza o ator, re-pede o 6163 a cada 2s (reflete split/merge).
- ZMMO.Build.cs: + ProceduralMeshComponent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:51:42 -03:00
9422522d6d Merge pull request 'feat: GameplayAbilitySystem (client) - GAS client + Admin Tools + AOI debug' (#5) from GameplayAbilitySystem into main 2026-06-04 18:40:37 -03:00
5 changed files with 413 additions and 4 deletions

View File

@@ -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<int32> CVarShowHandoff(
TEXT("zeus.debug.handoff"), 0, TEXT("Desenha a linha de handoff (placeholder)."), ECVF_Default);
static TAutoConsoleVariable<int32> CVarShowDrift(
TEXT("zeus.debug.drift"), 0, TEXT("Desenha o drift autoritativo (TODO)."), ECVF_Default);
static TAutoConsoleVariable<int32> 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<UZeusNetworkingClientSubsystem>())
{
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<UZeusNetworkingClientSubsystem>())
{
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<UZeusNetworkingClientSubsystem>())
{
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>(
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);
}

View File

@@ -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 <id>", 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<AZeusFrontierOverlayActor> FrontierActor_ = nullptr;
};

View File

@@ -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 ->
// "<pai>,<quadrante>" (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<UProceduralMeshComponent>(TEXT("WallMesh"));
SetRootComponent(WallMesh);
WallMesh->bUseAsyncCooking = false;
WallMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
WallMesh->SetCastShadow(false);
static ConstructorHelpers::FObjectFinder<UMaterialInterface> 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<float>(MinX); V.MinY = static_cast<float>(MinY);
V.MaxX = static_cast<float>(MaxX); V.MaxY = static_cast<float>(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<j -> dedup).
const float BaseZ = AnchorZCm - WallHeightCm * 0.5f;
const float TopZ = AnchorZCm + WallHeightCm * 0.5f;
TArray<FVector> Verts;
TArray<int32> Tris;
TArray<FVector> Normals;
TArray<FVector2D> UVs;
TArray<FProcMeshTangent> Tangents;
TArray<FLinearColor> 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();
}

View File

@@ -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<FCellView> Cells_;
float AnchorZ_ = 0.0f;
bool bHasOverlay_ = false;
/// Desenha "Cell X,Y\n<server>" no centro de cada cell (chamado no Tick).
void DrawCellLabels();
};

View File

@@ -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",