Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a430dabc3 | |||
| a3f2fa9c06 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -66,6 +66,13 @@ void UZeusAOIComponent::BeginPlay()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,12 +94,17 @@ void UZeusAOIComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
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();
|
||||
@@ -344,3 +356,64 @@ int32 UZeusAOIComponent::GetEnabledOverlayCount() const
|
||||
+ (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);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,15 @@
|
||||
|
||||
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)
|
||||
*
|
||||
@@ -70,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;
|
||||
@@ -108,4 +133,17 @@ private:
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -18,20 +18,33 @@ namespace
|
||||
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.
|
||||
// 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)
|
||||
{
|
||||
if (CellId < kChildCellIdOffset)
|
||||
constexpr uint32 kFlagBit = 1u << 30;
|
||||
if ((CellId & kFlagBit) == 0u)
|
||||
{
|
||||
return FString::Printf(TEXT("%u"), CellId);
|
||||
return FString::Printf(TEXT("%u"), CellId); // raiz pura
|
||||
}
|
||||
const uint32 Rel = CellId - kChildCellIdOffset;
|
||||
return DecodeCellPath(Rel / 4u) + FString::Printf(TEXT(",%u"), Rel % 4u);
|
||||
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).
|
||||
|
||||
Reference in New Issue
Block a user