10 Commits

Author SHA1 Message Date
84f552b4fa feat(gas): Kick V0 end-to-end -- IA_Kick + BP_GA_Kick + GC_Kick + rename Pawn + limpeza ThirdPerson
Cliente UE Kick V0 funcionando end-to-end (testado com 2 PIEs: ativador chuta
+ proxies dos peers veem montage via GameplayCue replicado).

ASSETS NOVOS:
- IA_Kick.uasset: Enhanced Input Action (Boolean) -- tecla F.
- IMC_Default: mapping F -> IA_Kick (trigger Pressed).
- BP_ZeusPlayerCharacter.uasset (em /Game/ZMMO/Core/Player/): renomeado a
  partir do BP_ThirdPersonCharacter; agora possui handler IA_Kick que chama
  RequestActivateAbilityByTag("Zeus.Ability.Melee.Kick") no UZeusGASComponent
  do PlayerState. ZeusGameMode aponta pra ele via FClassFinder.
- GC_Kick.uasset (em /Game/ZMMO/GAS/Cues/): GameplayCueNotify_Burst BP, parent
  AZeusGameplayCueNotify_Burst, tag Zeus.Cue.Melee.Kick. Override do hook
  OnZeusCueExecute (Lyra-style) -> Cast To Character (MyTarget) -> Play Anim
  Montage (AM_Kick_Montage). Quando server emite S_ABILITY_CUE, cliente
  spawna esse GC e roda a animacao no Mesh do proxy.

ASSETS MODIFICADOS:
- GA_Kick: conexoes PlayMontageAndWait -> EndAbility ajustadas. Antes TODAS
  as 5 saidas (OnCompleted/OnBlendedIn/OnBlendOut/OnInterrupted/OnCancelled)
  ligavam no EndAbility -- OnBlendedIn dispara ~100ms apos inicio (fim do
  blend in) -> EndAbility -> bStopWhenAbilityEnds=true -> montage para
  prematuro. Fix: so OnCompleted/OnInterrupted/OnCancelled. Hook BP novo
  OnZeusAbilityConfirmedByServer -> Print "Kick CONFIRMED by server".
- DT_Abilities: row Kick adicionada (Tag=Zeus.Ability.Melee.Kick, AbilityClass
  =GA_Kick_C, ActivationMontage=AM_Kick_Montage, ActivateCueTag=Zeus.Cue.Melee.Kick,
  Category=Melee, KeybindHint=F).
- BP_PlayerState: salvo apos GameMode apontar pra ele (mark dirty).
- ABP_Unarmed: deletado 1 Slot DefaultSlot duplicado (warning AnimBlueprintLog
  "node ja existe" + PlayMontage interno falhava em escolher slot).

LIMPEZA Content/ThirdPerson/:
- BP_ThirdPersonCharacter.uasset DELETED (renomeado pra BP_ZeusPlayerCharacter).
- BP_ThirdPersonGameMode.uasset DELETED (sem refs externas).
- BP_ThirdPersonPlayerController.uasset DELETED (so usado por BP_ThirdPersonGameMode).
- Menu.umap DELETED (sample UE template, sem refs).
- MI_ThirdPersonColWay.uasset mantido (90 refs em L_Test_UI -- mover pra
  /Game/ZMMO/Materials/ fica pra batch futuro).

Content/_DevOnly/ NAO incluido.

Tested in PIE 2 players: PIE0 aperta F -> chuta visualmente; proxy do PIE0
no PIE1 tambem chuta (montage replicado via GC). Cooldown 1.5s ativo.
Stamina cai 100 -> 95 a cada kick.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 03:56:28 -03:00
1308338953 feat(gas): GameMode aponta pra BPs (Pawn/PlayerState) + cleanup ZeusCharacter
ZeusGameMode.cpp:
- DefaultPawnClass: usa FClassFinder pra /Game/ZMMO/Core/Player/BP_ZeusPlayerCharacter
  (fallback AZeusCharacter). Sem isso, engine instancia C++ direto e perde
  toda customizacao BP (EnhancedInputAction events, AnimBP wire, componentes
  BP-added).
- PlayerStateClass: usa FClassFinder pra /Game/ZMMO/Core/Player/BP_PlayerState
  (fallback AZeusPlayerState). Mesmo motivo -- BP filho tem componentes (ex:
  UZeusGASComponent via ComponentClasses no DefaultGame.ini) e overrides do
  designer que classes C++ puras nao tem.
- TODO: renomear BP_PlayerState -> BP_ZeusPlayerState (alinhar com convencao
  BP_ZeusPlayerCharacter); path do FClassFinder ja preparado pra renomear.

ZeusCharacter.cpp/h cleanup:
- Removida gambiarra anterior (PossessedBy + OnRep_PlayerState overrides +
  RefreshGASAvatar com FTimerHandle retry). Substituida pelo pattern
  event-driven em UZeusGASComponent::HandlePawnSet (commit server anterior).
- Includes desnecessarios (AbilitySystemComponent.h + ZeusAbilitySystemComponent.h)
  removidos.

ZMMO.uproject:
- GameplayAbilities adicionado em Plugins (UE 5.7 exige que modulo do jogo
  declare explicitamente plugins que ele consome -- antes vinha transitivamente
  do plugin ZeusGAS, gerava warning de build).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 03:56:00 -03:00
21d42d9846 chore(gas): sync GameplayTags do server (Stamina/Kick/Spell/Buff) + flags de debug GAS
ZeusServerTags.ini -- merge gerado por sync_tags.ps1 (14 tags novas vindo do
ZeusServer dump JSON via ZeusTool tags sync):
- Stamina:           Zeus.Attribute.Stamina + MaxStamina
- Kick:              Zeus.Ability.Melee + Zeus.Ability.Melee.Kick
                     Zeus.Effect.Cooldown.Kick + Zeus.Cooldown.Kick
                     Zeus.Cue.Melee + Zeus.Cue.Melee.Kick
- FireballRain rename: Zeus.Ability.Spell.* + Zeus.Effect.Cooldown.FireballRain
                       Zeus.Cooldown.FireballRain
- Buff reorg:        Zeus.Effect.Buff + Zeus.Effect.Buff.StrUp

Tag orfa removida manualmente: Zeus.Ability.Combat.Kick (Kick mudou
de Combat.* pra Melee.*; merge preservou a antiga, limpo aqui).

DefaultGame.ini: flags de debug do GAS pra Kick development:
- bUseDebugTargetFromHud=true   (showdebug abilitysystem aponta pra mira)
- AbilitySystem.LogAbilityTriggers=1
- AbilitySystem.DebugAbilityTags=1

Lembrete: GameplayTags.ini so' eh lido no boot do editor -- reiniciar UE
pra registrar as tags novas (BP_GA_Kick depende de Zeus.Ability.Melee.Kick).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 01:37:45 -03:00
2895889e2b feat(gas): rename BP_GA_* -> GA_* + DT_Abilities seed + wire no ThirdPersonCharacter
Convencao de nome: abilities passam de "BP_GA_<Nome>" pra "GA_<Nome>"
(remove o "BP_" redundante -- ja sabemos que GA_ e' Blueprint).

- BP_GA_Dash.uasset -> GA_Dash.uasset
- BP_GA_Kick.uasset -> GA_Kick.uasset (pasta Kick/ mantida)
- DT_Abilities: linha do Kick atualizada pra apontar pra GA_Kick
- BP_ThirdPersonCharacter: ability Kick ligada (input/grant)

Doc HOWTO ja' reflete a nova convencao (ver server commit).
Content/_DevOnly/ NAO incluido.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 00:50:09 -03:00
1114ddbb11 feat(gas): seed inicial do GASCombatSystem -- ABP_Unarmed + Kick ability
- ABP_Unarmed.uasset: AnimBP unarmed (modificado) - base de animacao p/ combate sem arma
- BP_GA_Kick.uasset: GameplayAbility blueprint do Kick
- AS_Kick.uasset: animation sequence do golpe
- AM_Kick_Montage.uasset: montage que a ability dispara

Primeiro asset GAS authored cliente-side -- pre-requisito pra Fase 2 (M9 cues + M10 AbilityTask cliente).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 00:29:10 -03:00
9a430dabc3 feat(aoi): UZeusAOIComponent ganha visibility tracking + delegates BP (1.4 do roteiro PvP)
Estende o componente (que ja existia como debug overlay) com:
- TSet<int64> VisibleEntityIds_ espelhando ENT_SPAWN/ENT_DESPAWN do V1
- Delegates BlueprintAssignable: OnEntityEnteredAOI(EntityId) + OnEntityExitedAOI(EntityId, Reason)
- BlueprintPure: GetVisibleEntityCount, GetVisibleEntityIds, IsEntityVisible, GetSelfEntityId
- Bind/unbind dos delegates DYNAMIC do UZeusNetworkingClientSubsystem (AddDynamic/RemoveDynamic)
  no BeginPlay/EndPlay
- Self-entity filtrada via OnSelfEntityAssigned -- proprio char nao trackeia

Sem histerese client-side (server ja filtra com margem ~2048cm; se aparecer
flapping visual, adicionar depois). Memoria: project_aoi_client_component.

NAO testado in-game.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 00:23:51 -03:00
a3f2fa9c06 feat(client): overlay de fronteira -- ajustes + materiais e external actor
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 22:44:11 -03:00
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
35 changed files with 609 additions and 6 deletions

View File

@@ -91,3 +91,12 @@ bShouldAcquireMissingChunksOnLoad=False
bShouldWarnAboutInvalidAssets=True
MetaDataTagsForAssetRegistry=()
[/Script/GameplayAbilities.AbilitySystemGlobals]
bUseDebugTargetFromHud=true
[ConsoleVariables]
; mostra cooldowns, ativações, falhas
AbilitySystem.LogAbilityTriggers=1
; mostra effects aplicados/removidos com magnitude
AbilitySystem.DebugAbilityTags=1

View File

@@ -5,9 +5,13 @@ GameplayTagList=(Tag="Zeus",DevComment="")
GameplayTagList=(Tag="Zeus.Ability",DevComment="")
GameplayTagList=(Tag="Zeus.Ability.Casting",DevComment="")
GameplayTagList=(Tag="Zeus.Ability.Channeling",DevComment="")
GameplayTagList=(Tag="Zeus.Ability.Melee",DevComment="")
GameplayTagList=(Tag="Zeus.Ability.Melee.Kick",DevComment="")
GameplayTagList=(Tag="Zeus.Ability.Movement",DevComment="")
GameplayTagList=(Tag="Zeus.Ability.Movement.Dash",DevComment="")
GameplayTagList=(Tag="Zeus.Ability.Recovery",DevComment="")
GameplayTagList=(Tag="Zeus.Ability.Spell",DevComment="")
GameplayTagList=(Tag="Zeus.Ability.Spell.FireballRain",DevComment="")
GameplayTagList=(Tag="Zeus.Attribute",DevComment="")
GameplayTagList=(Tag="Zeus.Attribute.Agi",DevComment="")
GameplayTagList=(Tag="Zeus.Attribute.Aspd",DevComment="")
@@ -32,6 +36,7 @@ GameplayTagList=(Tag="Zeus.Attribute.MaxSp",DevComment="")
GameplayTagList=(Tag="Zeus.Attribute.Mdef",DevComment="")
GameplayTagList=(Tag="Zeus.Attribute.SkillPoint",DevComment="")
GameplayTagList=(Tag="Zeus.Attribute.Sp",DevComment="")
GameplayTagList=(Tag="Zeus.Attribute.Stamina",DevComment="")
GameplayTagList=(Tag="Zeus.Attribute.StatusPoint",DevComment="")
GameplayTagList=(Tag="Zeus.Attribute.Str",DevComment="")
GameplayTagList=(Tag="Zeus.Attribute.Vit",DevComment="")
@@ -46,7 +51,10 @@ GameplayTagList=(Tag="Zeus.Combo.Slot2",DevComment="")
GameplayTagList=(Tag="Zeus.Combo.Slot3",DevComment="")
GameplayTagList=(Tag="Zeus.Combo.Window",DevComment="")
GameplayTagList=(Tag="Zeus.Cooldown",DevComment="")
GameplayTagList=(Tag="Zeus.Cooldown.FireballRain",DevComment="")
GameplayTagList=(Tag="Zeus.Cue",DevComment="")
GameplayTagList=(Tag="Zeus.Cue.Melee",DevComment="")
GameplayTagList=(Tag="Zeus.Cue.Melee.Kick",DevComment="")
GameplayTagList=(Tag="Zeus.Cue.Movement",DevComment="")
GameplayTagList=(Tag="Zeus.Cue.Movement.Dash",DevComment="")
GameplayTagList=(Tag="Zeus.Damage",DevComment="")
@@ -60,10 +68,16 @@ GameplayTagList=(Tag="Zeus.Damage.Shadow",DevComment="")
GameplayTagList=(Tag="Zeus.Damage.Water",DevComment="")
GameplayTagList=(Tag="Zeus.Damage.Wind",DevComment="")
GameplayTagList=(Tag="Zeus.Effect",DevComment="")
GameplayTagList=(Tag="Zeus.Effect.Buff",DevComment="")
GameplayTagList=(Tag="Zeus.Effect.Buff.StrUp",DevComment="")
GameplayTagList=(Tag="Zeus.Effect.Cooldown",DevComment="")
GameplayTagList=(Tag="Zeus.Effect.Cooldown.Dash",DevComment="")
GameplayTagList=(Tag="Zeus.Effect.Cooldown.FireballRain",DevComment="")
GameplayTagList=(Tag="Zeus.Effect.Cooldown.Kick",DevComment="")
GameplayTagList=(Tag="Zeus.Effect.Cost",DevComment="")
GameplayTagList=(Tag="Zeus.Effect.Cost.Sp_10",DevComment="")
GameplayTagList=(Tag="Zeus.Effect.Cost.Sp_50",DevComment="")
GameplayTagList=(Tag="Zeus.Effect.Cost.Stamina_5",DevComment="")
GameplayTagList=(Tag="Zeus.Status",DevComment="")
GameplayTagList=(Tag="Zeus.Status.Buff",DevComment="")
GameplayTagList=(Tag="Zeus.Status.CC",DevComment="")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -173,6 +173,12 @@ void AZeusCharacter::Tick(const float DeltaSeconds)
void AZeusCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
// IMPORTANTE: chamar Super:: pra que BPs filhos consigam auto-bindar
// nodes EnhancedInputAction no Event Graph (ex: GA_Kick / IA_Kick no
// BP_ThirdPersonCharacter). Sem isso, bindings em BP ficam orfaos --
// so' bindings C++ (Move/Look/Jump/Dash abaixo) recebem input.
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &AZeusCharacter::OnJumpPressed);

View File

@@ -5,10 +5,47 @@
#include "ZeusPlayerController.h"
#include "ZeusPlayerState.h"
#include "UObject/ConstructorHelpers.h"
AZeusGameMode::AZeusGameMode()
{
// DefaultPawnClass: usar o BP filho (BP_ThirdPersonCharacter) em vez do
// C++ puro. BPs filhos so' sao instanciados se o pawn possessed for da
// classe BP -- caso contrario o engine cria a classe C++ direta e perde
// TUDO que o designer fez no BP (EnhancedInputAction events, AnimBP wire,
// hooks de ability, componentes adicionados via editor).
//
// Fallback pra AZeusCharacter pura se o asset BP nao existir (asset
// renomeado/deletado por engano -- nao deve acontecer em prod).
static ConstructorHelpers::FClassFinder<APawn> DefaultPawnBP(
TEXT("/Game/ZMMO/Core/Player/BP_ZeusPlayerCharacter"));
if (DefaultPawnBP.Succeeded())
{
DefaultPawnClass = DefaultPawnBP.Class;
}
else
{
DefaultPawnClass = AZeusCharacter::StaticClass();
}
PlayerControllerClass = AZeusPlayerController::StaticClass();
// PlayerStateClass: usar BP filho pelo mesmo motivo do DefaultPawnClass --
// componentes BP-added (ex: UZeusGASComponent listado em DefaultGame.ini
// ComponentClasses), eventos no Event Graph e overrides do designer so'
// existem na classe BP. C++ direto (AZeusPlayerState) perde tudo isso.
// TODO: renomear o asset pra BP_ZeusPlayerState pra alinhar com a convencao
// (BP_ZeusPlayerCharacter) -- quando renomear, atualizar o path aqui.
static ConstructorHelpers::FClassFinder<APlayerState> PlayerStateBP(
TEXT("/Game/ZMMO/Core/Player/BP_PlayerState"));
if (PlayerStateBP.Succeeded())
{
PlayerStateClass = PlayerStateBP.Class;
}
else
{
PlayerStateClass = AZeusPlayerState::StaticClass();
}
HUDClass = AZeusHUD::StaticClass();
}

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,14 @@ void UZeusAOIComponent::BeginPlay()
if (UZeusNetworkingClientSubsystem* NetC = GI->GetSubsystem<UZeusNetworkingClientSubsystem>())
{
AoiConfigHandle_ = NetC->OnDebugAoiInfo.AddUObject(this, &UZeusAOIComponent::HandleAoiConfig);
FrontierConfigHandle_ = NetC->OnDebugFrontierInfo.AddUObject(this, &UZeusAOIComponent::HandleFrontierConfig);
// 1.4 -- visibility tracking. Delegates DYNAMIC do subsystem V1
// precisam AddDynamic + UFUNCTION nas handlers (assim como no
// ZeusWorldSubsystem que ja faz spawn/despawn de proxies).
NetC->OnEntitySpawned.AddDynamic(this, &UZeusAOIComponent::HandleEntitySpawnedV1);
NetC->OnEntityDespawned.AddDynamic(this, &UZeusAOIComponent::HandleEntityDespawnedV1);
NetC->OnSelfEntityAssigned.AddDynamic(this, &UZeusAOIComponent::HandleSelfEntityAssignedV1);
}
}
}
@@ -73,7 +84,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 +92,23 @@ 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_); }
// 1.4 -- unbind dos delegates DYNAMIC de tracking.
NetC->OnEntitySpawned.RemoveDynamic(this, &UZeusAOIComponent::HandleEntitySpawnedV1);
NetC->OnEntityDespawned.RemoveDynamic(this, &UZeusAOIComponent::HandleEntityDespawnedV1);
NetC->OnSelfEntityAssigned.RemoveDynamic(this, &UZeusAOIComponent::HandleSelfEntityAssignedV1);
}
}
}
AoiConfigHandle_.Reset();
FrontierConfigHandle_.Reset();
}
VisibleEntityIds_.Empty();
if (FrontierActor_)
{
FrontierActor_->Destroy();
FrontierActor_ = nullptr;
}
Super::EndPlay(EndPlayReason);
}
@@ -95,6 +118,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 +170,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 +238,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 +250,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 +301,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 +311,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 +353,67 @@ 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);
}
// ===========================================================================
// Gameplay AOI: visibility tracking (1.4 do roteiro PvP)
// ===========================================================================
//
// Espelha ENT_SPAWN/ENT_DESPAWN do subsystem V1 num TSet local. Fires os
// delegates BlueprintAssignable pra game systems escutarem. Self-entity
// filtrada (o proprio char nao entra no tracking nem fires).
void UZeusAOIComponent::HandleSelfEntityAssignedV1(int64 EntityId)
{
SelfEntityId_ = EntityId;
// Se ja' havia "spawnado" antes do SELF chegar (race possivel), remove agora.
if (EntityId != 0 && VisibleEntityIds_.Contains(EntityId))
{
VisibleEntityIds_.Remove(EntityId);
OnEntityExitedAOI.Broadcast(EntityId, /*Reason=*/0);
}
}
void UZeusAOIComponent::HandleEntitySpawnedV1(int64 EntityId, int32 /*Kind*/,
FVector /*PosCm*/, float /*YawDeg*/,
FVector /*VelCmS*/, bool /*bGrounded*/,
int64 /*ServerTimeMs*/)
{
if (EntityId == 0) { return; }
if (EntityId == SelfEntityId_) { return; } // nunca trackear self
bool bAdded = false;
VisibleEntityIds_.Add(EntityId, &bAdded);
if (bAdded)
{
OnEntityEnteredAOI.Broadcast(EntityId);
}
// Re-spawn (entity ja' visivel) = no-op silencioso. Pode acontecer em
// keyframe pos-handoff cross-server.
}
void UZeusAOIComponent::HandleEntityDespawnedV1(int64 EntityId, int32 Reason)
{
if (EntityId == 0) { return; }
if (VisibleEntityIds_.Remove(EntityId) > 0)
{
OnEntityExitedAOI.Broadcast(EntityId, Reason);
}
}
int32 UZeusAOIComponent::GetVisibleEntityCount() const
{
return VisibleEntityIds_.Num();
}
TArray<int64> UZeusAOIComponent::GetVisibleEntityIds() const
{
return VisibleEntityIds_.Array();
}
bool UZeusAOIComponent::IsEntityVisible(int64 EntityId) const
{
return VisibleEntityIds_.Contains(EntityId);
}

View File

@@ -5,8 +5,20 @@
#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;
// Delegates BlueprintAssignable pra game systems reagirem a entrada/saida de
// outras entidades na AOI do player local. Disparados quando ENT_SPAWN /
// ENT_DESPAWN chega do servidor (V1 canonico). Self-entity filtrada -- o
// proprio char NAO fires aqui.
// - Entered: 1 param (EntityId)
// - Exited: 2 params (EntityId, Reason). Reason vem do payload ENT_DESPAWN.
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FZeusAOIEntityEnter, int64, EntityId);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FZeusAOIEntityExit, int64, EntityId, int32, Reason);
/**
* UZeusAOIComponent (cliente / jogo)
*
@@ -53,6 +65,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).
@@ -64,6 +79,22 @@ public:
UFUNCTION(BlueprintPure, Category = "Zeus|AOI Debug")
int32 GetEnabledOverlayCount() const;
// === Gameplay AOI: visibility tracking (1.4 do roteiro PvP) ============
//
// Espelha quem o servidor mandou ENT_SPAWN/ENT_DESPAWN pro player local.
// Cliente NAO faz histérese própria -- server já filtra com
// margem de ~2048cm ([[project_aoi_crossserver_hysteresis]]). Game systems
// (HUD, audio, ability target filtering) escutam estes delegates pra
// reagir a entrada/saída. Self-entity filtrada (o próprio char
// não fires nem entra no tracking).
UPROPERTY(BlueprintAssignable, Category = "Zeus|AOI") FZeusAOIEntityEnter OnEntityEnteredAOI;
UPROPERTY(BlueprintAssignable, Category = "Zeus|AOI") FZeusAOIEntityExit OnEntityExitedAOI;
UFUNCTION(BlueprintPure, Category = "Zeus|AOI") int32 GetVisibleEntityCount() const;
UFUNCTION(BlueprintPure, Category = "Zeus|AOI") TArray<int64> GetVisibleEntityIds() const;
UFUNCTION(BlueprintPure, Category = "Zeus|AOI") bool IsEntityVisible(int64 EntityId) const;
UFUNCTION(BlueprintPure, Category = "Zeus|AOI") int64 GetSelfEntityId() const { return SelfEntityId_; }
private:
void DrawOverlays();
bool ResolveOverlay(EZeusAOIOverlay Overlay) const;
@@ -76,6 +107,43 @@ 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;
// === Visibility tracking (1.4) ==========================================
UFUNCTION() void HandleEntitySpawnedV1(int64 EntityId, int32 Kind, FVector PosCm,
float YawDeg, FVector VelCmS, bool bGrounded,
int64 ServerTimeMs);
UFUNCTION() void HandleEntityDespawnedV1(int64 EntityId, int32 Reason);
UFUNCTION() void HandleSelfEntityAssignedV1(int64 EntityId);
/** EntityIds atualmente visíveis (espelho do server). TSet pra O(1) check.
* Não inclui self. Limpo no EndPlay. */
TSet<int64> VisibleEntityIds_;
/** EntityId do próprio char (recebido via ENT_SELF). 0 = ainda não atribuído. */
int64 SelfEntityId_ = 0;
};

View File

@@ -0,0 +1,218 @@
// 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
// Decodifica o cellId na notacao "raiz fora, splits no array" da k-d tree
// binaria: raiz pura -> "0"; derivada -> "0,[0,1]". Espelha o layout
// bit-packed de Server/ZeusServices/Shared/types/src/cell-id.ts:
// bit 30 = flag (1 = cell derivada de split)
// bits 29..26 = depth (profundidade de split)
// bits 25..15 = rootId (cell raiz do baseGrid)
// bits 14..0 = path (1 bit por nivel, MSB-first)
FString DecodeCellPath(uint32 CellId)
{
constexpr uint32 kFlagBit = 1u << 30;
if ((CellId & kFlagBit) == 0u)
{
return FString::Printf(TEXT("%u"), CellId); // raiz pura
}
const uint32 Depth = (CellId >> 26) & 0xFu;
const uint32 Root = (CellId >> 15) & 0x7FFu;
const uint32 Packed = CellId & 0x7FFFu;
FString Path;
for (uint32 i = 0; i < Depth; ++i)
{
if (i > 0)
{
Path += TEXT(",");
}
Path += ((Packed >> (14u - i)) & 1u) ? TEXT("1") : TEXT("0");
}
return FString::Printf(TEXT("%u,[%s]"), Root, *Path);
}
// 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",

View File

@@ -76,6 +76,10 @@
{
"Name": "CommonUI",
"Enabled": true
},
{
"Name": "GameplayAbilities",
"Enabled": true
}
],
"AdditionalPluginDirectories": [