Merge pull request 'feat: GameplayAbilitySystem (client) - GAS client + Admin Tools + AOI debug' (#5) from GameplayAbilitySystem into main

This commit was merged in pull request #5.
This commit is contained in:
2026-06-04 18:40:37 -03:00
58 changed files with 592 additions and 9 deletions

View File

@@ -71,6 +71,13 @@ ScreenSetAsset=/Game/ZMMO/UI/InGame/DA_InGameScreenSet.DA_InGameScreenSet
; (UWorldSubsystem) subscribe nos delegates do UZeusNetworkSubsystem.
+ComponentClasses=/Script/ZeusGAS.ZeusGASComponent
; === GAS Cue paths (Batch 2.5 — S_ABILITY_CUE multicast cosmetico) ===
; UGameplayCueManager scaneia esses paths no boot pra mapear FGameplayTag
; -> AGameplayCueNotify_Actor BP. UZeusGASComponent::DispatchAbilityCue chama
; ASC->ExecuteGameplayCueLocal(CueTag, params) -> manager resolve pelo path.
[/Script/GameplayAbilities.AbilitySystemGlobals]
+GameplayCueNotifyPaths="/Game/ZMMO/GAS/Cues"
[/Script/Engine.AssetManagerSettings]
-PrimaryAssetTypesToScan=(PrimaryAssetType="Map",AssetBaseClass=/Script/Engine.World,bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game/Maps")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))
-PrimaryAssetTypesToScan=(PrimaryAssetType="PrimaryAssetLabel",AssetBaseClass=/Script/Engine.PrimaryAssetLabel,bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -17,8 +17,13 @@
#include "Subsystems/WorldSubsystem.h"
#include "UObject/ConstructorHelpers.h"
#include "ZMMO.h"
#include "GameplayTagContainer.h"
#include "GameplayTagsManager.h"
#include "ZeusGASComponent.h"
#include "ZeusAOIComponent.h"
#include "ZeusPlayerState.h"
#include "Blueprint/UserWidget.h"
#include "GameFramework/PlayerController.h"
#include "ZeusWorldSubsystem.h"
#include "ZeusNetworkSubsystem.h"
#include "UI/FrontEnd/UIFrontEndFlowSubsystem.h"
@@ -55,6 +60,14 @@ AZeusCharacter::AZeusCharacter()
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
FollowCamera->bUsePawnControlRotation = false;
// AOI do cliente (+ overlays de debug). Implementa IZeusAOIDebugTarget; o
// Zeus Admin Tools acha este componente no pawn e dirige os overlays.
AOIComponent = CreateDefaultSubobject<UZeusAOIComponent>(TEXT("ZeusAOIComponent"));
// AdminPanelClass NAO e' resolvido aqui via FClassFinder: na criacao do CDO
// (load do modulo) o asset registry pode nao estar pronto e o static cacheia
// null pra sempre. Resolvido lazy em ToggleAdminPanel (LoadClass em runtime).
// AttributeComponent migrou pro PlayerState (AZeusPlayerState + Component
// Registry config-driven). Pawn fica leve — so' movement/input/camera.
@@ -101,11 +114,14 @@ AZeusCharacter::AZeusCharacter()
TEXT("/Game/Input/Actions/IA_MouseLook.IA_MouseLook"));
static ConstructorHelpers::FObjectFinder<UInputAction> JumpActionAsset(
TEXT("/Game/Input/Actions/IA_Jump.IA_Jump"));
static ConstructorHelpers::FObjectFinder<UInputAction> DashActionAsset(
TEXT("/Game/Input/Actions/IA_Dash.IA_Dash"));
if (MoveActionAsset.Succeeded()) { MoveAction = MoveActionAsset.Object; }
if (LookActionAsset.Succeeded()) { LookAction = LookActionAsset.Object; }
if (MouseLookActionAsset.Succeeded()) { MouseLookAction = MouseLookActionAsset.Object; }
if (JumpActionAsset.Succeeded()) { JumpAction = JumpActionAsset.Object; }
if (DashActionAsset.Succeeded()) { DashAction = DashActionAsset.Object; }
}
void AZeusCharacter::BeginPlay()
@@ -166,11 +182,71 @@ void AZeusCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCompo
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AZeusCharacter::Look);
EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &AZeusCharacter::Look);
if (DashAction)
{
EnhancedInputComponent->BindAction(DashAction, ETriggerEvent::Triggered, this, &AZeusCharacter::OnDashTriggered);
}
}
else
{
UE_LOG(LogZeusPlayer, Error, TEXT("'%s' Failed to find an Enhanced Input component."), *GetNameSafe(this));
}
// Debug: F8 abre/fecha o Zeus Admin Tools (AIO Debug). Legacy BindKey
// convive com Enhanced Input. Trocar a tecla aqui se conflitar.
PlayerInputComponent->BindKey(EKeys::F8, IE_Pressed, this, &AZeusCharacter::ToggleAdminPanel);
}
void AZeusCharacter::ToggleAdminPanel()
{
APlayerController* PC = Cast<APlayerController>(GetController());
if (!PC)
{
return;
}
// Aberto -> fecha + devolve input pro jogo.
if (AdminPanelInstance && AdminPanelInstance->IsInViewport())
{
AdminPanelInstance->RemoveFromParent();
PC->SetInputMode(FInputModeGameOnly());
PC->bShowMouseCursor = false;
return;
}
// Lazy load (runtime): no F8 o asset ja' esta carregavel; evita o pitfall do
// FClassFinder na criacao do CDO. Path do generated class (_C).
// A UI vive no CONTENT DO PLUGIN (mount /ZeusAdminTools/), nao no /Game do projeto:
// o plugin ZeusAdminTools e' autocontido (C++ + UI no repo Server).
if (!AdminPanelClass)
{
AdminPanelClass = LoadClass<UUserWidget>(nullptr,
TEXT("/ZeusAdminTools/UI/WBP_AdminToolsAIO.WBP_AdminToolsAIO_C"));
}
if (!AdminPanelClass)
{
UE_LOG(LogZeusPlayer, Warning, TEXT("[AdminPanel] AdminPanelClass nao resolvido (WBP_AdminToolsAIO ausente?)."));
return;
}
if (!AdminPanelInstance)
{
AdminPanelInstance = CreateWidget<UUserWidget>(PC, AdminPanelClass);
}
if (AdminPanelInstance)
{
AdminPanelInstance->AddToViewport(1000);
// UIOnly: trava a acao do jogo enquanto o menu esta aberto e garante que
// os cliques vao 100% pra UI (no GameAndUI o viewport rouba o clique).
// O mundo continua renderizando + os overlays de debug seguem desenhando.
FInputModeUIOnly Mode;
Mode.SetWidgetToFocus(AdminPanelInstance->TakeWidget());
Mode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
PC->SetInputMode(Mode);
PC->bShowMouseCursor = true;
}
}
void AZeusCharacter::Move(const FInputActionValue& Value)
@@ -203,6 +279,40 @@ void AZeusCharacter::OnJumpReleased()
DoJumpEnd();
}
void AZeusCharacter::OnDashTriggered()
{
// Resolve UZeusGASComponent do PlayerState. Component vive la' via
// AZeusPlayerState Component Registry (config-driven). Sem el, ASC ausente
// e RequestActivateAbilityByTag falha — log warning.
const APlayerState* PS = GetPlayerState();
if (!PS)
{
UE_LOG(LogZeusPlayer, Warning, TEXT("OnDashTriggered: PlayerState nullptr — ignorado"));
return;
}
UZeusGASComponent* Comp = PS->FindComponentByClass<UZeusGASComponent>();
if (!Comp)
{
UE_LOG(LogZeusPlayer, Warning, TEXT("OnDashTriggered: UZeusGASComponent nao achado"));
return;
}
// Tag canonica casa com row do DT + JSON do server. Hash FNV calculado
// dentro do componente (RequestActivateAbilityByTag).
const FGameplayTag DashTag = UGameplayTagsManager::Get().RequestGameplayTag(
FName(TEXT("Zeus.Ability.Movement.Dash")));
if (!DashTag.IsValid())
{
UE_LOG(LogZeusPlayer, Warning,
TEXT("OnDashTriggered: tag Zeus.Ability.Movement.Dash nao registrada no cliente"));
return;
}
const bool bSent = Comp->RequestActivateAbilityByTag(DashTag);
UE_LOG(LogZeusPlayer, Log, TEXT("OnDashTriggered -> RequestActivateAbilityByTag = %s"),
bSent ? TEXT("OK (C_ABILITY_TRY_ACTIVATE enviado)") : TEXT("FALHOU"));
}
void AZeusCharacter::DoMove(const float Right, const float Forward)
{
PendingMoveRight = FMath::Clamp(Right, -1.0f, 1.0f);
@@ -388,6 +498,17 @@ void AZeusCharacter::HandleZeusCharInfo(const int64 InEntityId, const FString& C
TEXT("AZeusCharacter: S_CHAR_INFO recebido EntityId=%lld name=%s guild=%s"),
InEntityId, *CharName, *GuildName);
// 2026-06-03 fix: server envia S_CHAR_INFO de TODOS os players (proprio +
// catch-up de proxies pre-existentes). Sem este filtro, o nome do ultimo
// proxy recebido sobrescrevia o do pawn local (ex: player Mateuus loga e
// ve "Olatudook" em si mesmo porque o catch-up do Olatudook chegou depois
// do S_CHAR_INFO proprio). Proxies remotos sao roteados pelo registry no
// UZeusWorldSubsystem (futuro nameplate por EntityId).
if (ZeusEntityId != 0 && InEntityId != ZeusEntityId)
{
return;
}
APlayerState* PS = GetPlayerState();
if (!PS)
{

View File

@@ -11,6 +11,8 @@ class USpringArmComponent;
class UCameraComponent;
class UInputAction;
class UZeusNetworkSubsystem;
class UZeusAOIComponent;
class UUserWidget;
struct FInputActionValue;
DECLARE_LOG_CATEGORY_EXTERN(LogZeusPlayer, Log, All);
@@ -46,6 +48,10 @@ class ZMMO_API AZeusCharacter : public ACharacter, public IZeusEntityInterface
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
UCameraComponent* FollowCamera;
/** AOI do cliente (+ overlays de debug dirigidos pelo Zeus Admin Tools). */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Zeus|AOI", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UZeusAOIComponent> AOIComponent;
// GAS Component vive no PlayerState (AZeusPlayerState + Component Registry).
// Acesso: GetPlayerState<AZeusPlayerState>()->GetZeusComponent<UZeusGASComponent>().
@@ -62,6 +68,16 @@ protected:
UPROPERTY(EditAnywhere, Category = "Input")
UInputAction* MouseLookAction;
/// IA_Dash — dispara C_ABILITY_TRY_ACTIVATE pra Zeus.Ability.Movement.Dash
/// via UZeusGASComponent. Bind padrao no IMC_Default: LeftShift -> Pressed.
UPROPERTY(EditAnywhere, Category = "Input")
UInputAction* DashAction;
/** Painel Zeus Admin Tools (AIO Debug), aberto/fechado por F8. Default =
* WBP_AdminToolsAIO (resolvido no construtor). */
UPROPERTY(EditAnywhere, Category = "Zeus|Admin")
TSubclassOf<UUserWidget> AdminPanelClass;
/** Cadencia de envio dos pacotes `C_INPUT_AXIS` (Hz). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Zeus|Networking", meta = (ClampMin = "1", ClampMax = "120"))
int32 InputSendRateHz = 30;
@@ -104,6 +120,14 @@ protected:
void OnJumpPressed();
void OnJumpReleased();
/// IA_Dash Triggered -> resolve UZeusGASComponent do PlayerState e chama
/// RequestActivateAbilityByTag("Zeus.Ability.Movement.Dash"). Server valida
/// + responde S_ABILITY_ACTIVATED (HP/SP atualizado, montage roda local).
void OnDashTriggered();
/** F8: cria/mostra ou esconde o painel Zeus Admin Tools (input mode UI + cursor). */
void ToggleAdminPanel();
public:
/** Wrappers expostos a Blueprint (UI / mobile / automation). */
UFUNCTION(BlueprintCallable, Category = "Input")
@@ -148,6 +172,10 @@ private:
UPROPERTY()
TObjectPtr<UZeusNetworkSubsystem> ZeusNetwork;
/** Instancia viva do painel admin (criada lazy no primeiro F8). */
UPROPERTY(Transient)
TObjectPtr<UUserWidget> AdminPanelInstance;
bool bSpawnDelegateBound = false;
/** Identidade autoritativa atribuida quando o servidor envia S_SPAWN_PLAYER local. */

View File

@@ -1,9 +1,13 @@
#include "ZeusPlayerState.h"
#include "AbilitySystemComponent.h"
#include "Components/ActorComponent.h"
#include "Misc/ConfigCacheIni.h"
#include "Net/UnrealNetwork.h"
#include "ZMMO.h"
#include "ZeusAbilitySystemComponent.h"
#include "ZeusAttributeSet.h"
#include "ZeusGASComponent.h"
AZeusPlayerState::AZeusPlayerState()
{
@@ -98,3 +102,32 @@ void AZeusPlayerState::SetCharInfo(const FString& InCharName, const FString& InG
CharName = InCharName;
GuildName = InGuildName;
}
UAbilitySystemComponent* AZeusPlayerState::GetAbilitySystemComponent() const
{
// ASC vive como subobject do UZeusGASComponent (que esta no Component
// Registry). UE5 GAS nodes BP ("Get Ability System Component", "Apply
// Gameplay Effect To Target", etc.) chamam isto via IAbilitySystemInterface.
// O retorno e o ponteiro upcastado pra UAbilitySystemComponent (base) —
// mas em runtime e' UZeusAbilitySystemComponent (cast tipado via
// GetZeusAbilitySystemComponent abaixo).
return GetZeusAbilitySystemComponent();
}
UZeusAbilitySystemComponent* AZeusPlayerState::GetZeusAbilitySystemComponent() const
{
if (UZeusGASComponent* GAS = GetZeusComponent<UZeusGASComponent>())
{
return GAS->AbilitySystemComponent.Get();
}
return nullptr;
}
UZeusAttributeSet* AZeusPlayerState::GetZeusAttributeSet() const
{
if (UZeusGASComponent* GAS = GetZeusComponent<UZeusGASComponent>())
{
return GAS->AttributeSet.Get();
}
return nullptr;
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include "AbilitySystemInterface.h"
#include "CoreMinimal.h"
#include "GameFramework/PlayerState.h"
#include "Templates/SubclassOf.h"
@@ -7,6 +8,8 @@
class FLifetimeProperty;
class UActorComponent;
class UZeusAbilitySystemComponent;
class UZeusAttributeSet;
/**
* PlayerState do MMO. Pattern UE5 idiomatico: o GameMode atribui
@@ -45,13 +48,36 @@ class UActorComponent;
* ou o helper `GetZeusComponent<T>()`.
*/
UCLASS(Config = Game, BlueprintType)
class ZMMO_API AZeusPlayerState : public APlayerState
class ZMMO_API AZeusPlayerState : public APlayerState, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
AZeusPlayerState();
// === IAbilitySystemInterface (UE5 GAS) ===
//
// Padrao Lyra: PlayerState implementa a interface; o ASC vive como
// subobject do UZeusGASComponent (Component Registry). Isso permite que
// qualquer Actor que receba este PlayerState resolva o ASC via cast
// pra IAbilitySystemInterface OU via node BP "Get Ability System
// Component" (que faz o mesmo internamente).
//
// Acesso BP: "Get Ability System Component" + pin do PlayerState.
// Acesso C++: `Cast<IAbilitySystemInterface>(PlayerState)->GetAbilitySystemComponent()`.
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
/// Versao tipada do ASC custom — retorna direto UZeusAbilitySystemComponent
/// (os 12 overrides que falam com o servidor custom via opcodes ZeusNetwork).
/// Prefira este sobre o getter da interface no BP — evita cast + expoe a API
/// do ASC custom (RequestActivateAbility, etc.).
UFUNCTION(BlueprintPure, Category = "Zeus|GAS")
UZeusAbilitySystemComponent* GetZeusAbilitySystemComponent() const;
/// Helper BP-friendly pro AttributeSet tipado (template C++ nao expoe pra BP).
UFUNCTION(BlueprintPure, Category = "Zeus|GAS")
UZeusAttributeSet* GetZeusAttributeSet() const;
// === Identidade publica ===
//
// Todos os campos publicos sao Replicated — hoje o cliente UE5 roda em

View File

@@ -0,0 +1,223 @@
// Copyright Zeus Server Engine. All rights reserved.
#include "ZeusAOIComponent.h"
#include "ZeusNetworkSubsystem.h" // plugin ZeusNetwork: OnDebugAoiInfo + SendDebugAoiRequest
#include "DrawDebugHelpers.h"
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "GameFramework/Actor.h"
// Console vars — espelham as flags do painel (overlay liga se flag OU CVar).
static TAutoConsoleVariable<int32> CVarShowAOI(
TEXT("zeus.debug.aoi"), 0, TEXT("Desenha a Zona de Interesse (esfera do raio AOI)."), ECVF_Default);
static TAutoConsoleVariable<int32> CVarShowDespawn(
TEXT("zeus.debug.despawn"), 0, TEXT("Desenha a Zona de Despawn (esfera externa do AOI)."), ECVF_Default);
static TAutoConsoleVariable<int32> CVarShowCell(
TEXT("zeus.debug.cell"), 0, TEXT("Desenha os bounds da celula (placeholder)."), ECVF_Default);
static TAutoConsoleVariable<int32> CVarShowProxy(
TEXT("zeus.debug.proxy"), 0, TEXT("Desenha labels de proxy (TODO)."), ECVF_Default);
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);
namespace
{
const FColor kColorInterest(93, 213, 255); // cyan — Zona de Interesse
const FColor kColorDespawn(255, 140, 90); // laranja — Zona de Despawn (externa)
const FColor kColorCell(232, 192, 96);
const FColor kColorHandoff(255, 155, 190);
}
UZeusAOIComponent::UZeusAOIComponent()
{
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.bStartWithTickEnabled = true;
SetIsReplicatedByDefault(false); // debug/cliente; gameplay AOI real entra depois
}
void UZeusAOIComponent::BeginPlay()
{
Super::BeginPlay();
// Bind do feed de config de AOI (servidor -> cliente). Multicast plain C++.
if (UWorld* World = GetWorld())
{
if (UGameInstance* GI = World->GetGameInstance())
{
if (UZeusNetworkSubsystem* Net = GI->GetSubsystem<UZeusNetworkSubsystem>())
{
AoiConfigHandle_ = Net->OnDebugAoiInfo.AddUObject(this, &UZeusAOIComponent::HandleAoiConfig);
}
}
}
// Best-effort: ja' pede a config (no-op se ainda nao conectado — o request
// e' re-disparado ao ligar um overlay em SetOverlayEnabled).
RequestAoiConfig();
}
void UZeusAOIComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (AoiConfigHandle_.IsValid())
{
if (UWorld* World = GetWorld())
{
if (UGameInstance* GI = World->GetGameInstance())
{
if (UZeusNetworkSubsystem* Net = GI->GetSubsystem<UZeusNetworkSubsystem>())
{
Net->OnDebugAoiInfo.Remove(AoiConfigHandle_);
}
}
}
AoiConfigHandle_.Reset();
}
Super::EndPlay(EndPlayReason);
}
void UZeusAOIComponent::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (AnyOverlayActive())
{
DrawOverlays();
}
}
void UZeusAOIComponent::RequestAoiConfig()
{
if (UWorld* World = GetWorld())
{
if (UGameInstance* GI = World->GetGameInstance())
{
if (UZeusNetworkSubsystem* Net = GI->GetSubsystem<UZeusNetworkSubsystem>())
{
Net->SendDebugAoiRequest(); // no-op se nao conectado
}
}
}
}
void UZeusAOIComponent::HandleAoiConfig(float InterestCm, float DespawnCm)
{
InterestRadiusCm = InterestCm;
DespawnRadiusCm = DespawnCm;
}
bool UZeusAOIComponent::ResolveOverlay(EZeusAOIOverlay Overlay) const
{
switch (Overlay)
{
case EZeusAOIOverlay::AOIRadius: return bShowAOIRadius || CVarShowAOI.GetValueOnGameThread() > 0;
case EZeusAOIOverlay::DespawnZone: return bShowDespawnZone || CVarShowDespawn.GetValueOnGameThread() > 0;
case EZeusAOIOverlay::CellBounds: return bShowCellBounds || CVarShowCell.GetValueOnGameThread() > 0;
case EZeusAOIOverlay::ProxyLabels: return bShowProxyLabels || CVarShowProxy.GetValueOnGameThread() > 0;
case EZeusAOIOverlay::HandoffLine: return bShowHandoffLine || CVarShowHandoff.GetValueOnGameThread() > 0;
case EZeusAOIOverlay::AuthDrift: return bShowAuthDrift || CVarShowDrift.GetValueOnGameThread() > 0;
default: return false;
}
}
bool UZeusAOIComponent::AnyOverlayActive() const
{
return ResolveOverlay(EZeusAOIOverlay::AOIRadius)
|| ResolveOverlay(EZeusAOIOverlay::DespawnZone)
|| ResolveOverlay(EZeusAOIOverlay::CellBounds)
|| ResolveOverlay(EZeusAOIOverlay::ProxyLabels)
|| ResolveOverlay(EZeusAOIOverlay::HandoffLine)
|| ResolveOverlay(EZeusAOIOverlay::AuthDrift);
}
void UZeusAOIComponent::DrawOverlays()
{
const AActor* Owner = GetOwner();
UWorld* World = GetWorld();
if (!Owner || !World) { return; }
const FVector Loc = Owner->GetActorLocation();
// Zona de Interesse (interna) — raio real do servidor. Sem o raio (==0),
// ainda nao chegou a config: nao desenha (loading).
if (ResolveOverlay(EZeusAOIOverlay::AOIRadius) && InterestRadiusCm > 0.0f)
{
DrawDebugSphere(World, Loc, InterestRadiusCm, 32, kColorInterest, false, -1.0f, 0, LineThickness);
}
// Zona de Despawn (externa, > interesse = histerese) — raio real do servidor.
if (ResolveOverlay(EZeusAOIOverlay::DespawnZone) && DespawnRadiusCm > 0.0f)
{
DrawDebugSphere(World, Loc, DespawnRadiusCm, 32, kColorDespawn, false, -1.0f, 0, LineThickness);
}
if (ResolveOverlay(EZeusAOIOverlay::CellBounds))
{
// PLACEHOLDER: box grande centrada no player. TODO bounds reais da cell.
DrawDebugBox(World, Loc, FVector(CellBoundsExtentCm, CellBoundsExtentCm, 2000.0f),
kColorCell, false, -1.0f, 0, LineThickness);
}
if (ResolveOverlay(EZeusAOIOverlay::HandoffLine))
{
// PLACEHOLDER: vetor pra frente. TODO apontar pro centro da cell vizinha.
const float Len = InterestRadiusCm > 0.0f ? InterestRadiusCm : 3000.0f;
const FVector End = Loc + Owner->GetActorForwardVector() * Len;
DrawDebugDirectionalArrow(World, Loc, End, 200.0f, kColorHandoff, false, -1.0f, 0, LineThickness);
}
// ProxyLabels / AuthDrift: TODO (precisam do registry de proxies/snapshots).
}
void UZeusAOIComponent::SetOverlayEnabled_Implementation(EZeusAOIOverlay Overlay, bool bEnabled)
{
switch (Overlay)
{
case EZeusAOIOverlay::AOIRadius: bShowAOIRadius = bEnabled; break;
case EZeusAOIOverlay::DespawnZone: bShowDespawnZone = bEnabled; break;
case EZeusAOIOverlay::CellBounds: bShowCellBounds = bEnabled; break;
case EZeusAOIOverlay::ProxyLabels: bShowProxyLabels = bEnabled; break;
case EZeusAOIOverlay::HandoffLine: bShowHandoffLine = bEnabled; break;
case EZeusAOIOverlay::AuthDrift: bShowAuthDrift = bEnabled; break;
default: break;
}
// Ao ligar uma zona de AOI sem ter os raios ainda, pede a config (loading).
const bool bAoiZone = (Overlay == EZeusAOIOverlay::AOIRadius || Overlay == EZeusAOIOverlay::DespawnZone);
if (bEnabled && bAoiZone && InterestRadiusCm <= 0.0f)
{
RequestAoiConfig();
}
}
void UZeusAOIComponent::SetAllOverlays_Implementation(bool bEnabled)
{
bShowAOIRadius = bShowDespawnZone = bShowCellBounds = bEnabled;
bShowProxyLabels = bShowHandoffLine = bShowAuthDrift = bEnabled;
if (bEnabled && InterestRadiusCm <= 0.0f)
{
RequestAoiConfig();
}
}
bool UZeusAOIComponent::IsOverlayEnabled_Implementation(EZeusAOIOverlay Overlay) const
{
// Reflete o estado real (flag OU CVar) p/ a UI mostrar o que esta' desenhando.
return ResolveOverlay(Overlay);
}
float UZeusAOIComponent::GetInterestRadiusCm_Implementation() const
{
return InterestRadiusCm; // 0 = config do servidor ainda nao chegou (loading)
}
float UZeusAOIComponent::GetDespawnRadiusCm_Implementation() const
{
return DespawnRadiusCm;
}
int32 UZeusAOIComponent::GetEnabledOverlayCount() const
{
return (bShowAOIRadius ? 1 : 0) + (bShowDespawnZone ? 1 : 0) + (bShowCellBounds ? 1 : 0)
+ (bShowProxyLabels ? 1 : 0) + (bShowHandoffLine ? 1 : 0) + (bShowAuthDrift ? 1 : 0);
}

View File

@@ -0,0 +1,81 @@
// Copyright Zeus Server Engine. All rights reserved.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "ZeusAOIDebugTarget.h" // plugin ZeusAdminToolsRuntime: interface + EZeusAOIOverlay
#include "ZeusAOIComponent.generated.h"
/**
* UZeusAOIComponent (cliente / jogo)
*
* Componente de gameplay do player local. Dominio principal: Area of Interest
* do cliente (proxies, despawn coordenado, interpolacao — ver
* [[project_aoi_client_component]]). AQUI, alem disso, expoe um "plus" de
* DEBUG: overlays via DrawDebug* dirigidos pelo painel admin (plugin) atraves
* de IZeusAOIDebugTarget, e/ou por console vars (zeus.debug.aoi etc.).
*
* As DUAS zonas do AOI (Interesse interna + Despawn externa = histerese) vem do
* SERVIDOR: ao ligar um overlay (ou no BeginPlay) o componente pede a config via
* C_DEBUG_AOI_REQUEST e recebe S_DEBUG_AOI_INFO (delegate OnDebugAoiInfo do
* UZeusNetworkSubsystem) com interestRadiusCm + despawnRadiusCm. Enquanto nao
* chega (raio == 0), nao desenha (loading).
*
* Vive na src do ZMMO (nao no plugin): e' um componente do JOGO. O plugin
* ZeusAdminTools apenas COMUNICA com ele via a interface — direcao da
* dependencia: ZMMO -> plugin (implementa a interface).
*/
UCLASS(ClassGroup = (Zeus), meta = (BlueprintSpawnableComponent), DisplayName = "Zeus AOI Component")
class ZMMO_API UZeusAOIComponent : public UActorComponent, public IZeusAOIDebugTarget
{
GENERATED_BODY()
public:
UZeusAOIComponent();
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction) override;
// === IZeusAOIDebugTarget (dirigido pelo painel admin) ===
virtual void SetOverlayEnabled_Implementation(EZeusAOIOverlay Overlay, bool bEnabled) override;
virtual void SetAllOverlays_Implementation(bool bEnabled) override;
virtual bool IsOverlayEnabled_Implementation(EZeusAOIOverlay Overlay) const override;
virtual float GetInterestRadiusCm_Implementation() const override;
virtual float GetDespawnRadiusCm_Implementation() const override;
// === Flags de debug por overlay ===
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|AOI Debug|Overlays") bool bShowAOIRadius = false; // Zona de Interesse
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|AOI Debug|Overlays") bool bShowDespawnZone = false; // Zona de Despawn
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|AOI Debug|Overlays") bool bShowCellBounds = false;
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;
// === Parametros ===
// Raios reais vem do servidor (S_DEBUG_AOI_INFO). 0 = ainda nao recebido (loading).
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Zeus|AOI Debug|Params") float InterestRadiusCm = 0.0f;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Zeus|AOI Debug|Params") float DespawnRadiusCm = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|AOI Debug|Params") float CellBoundsExtentCm = 50000.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|AOI Debug|Params") float LineThickness = 2.0f;
UFUNCTION(BlueprintPure, Category = "Zeus|AOI Debug")
int32 GetEnabledOverlayCount() const;
private:
void DrawOverlays();
bool ResolveOverlay(EZeusAOIOverlay Overlay) const;
bool AnyOverlayActive() const;
/** Pede a config de AOI ao servidor (C_DEBUG_AOI_REQUEST). No-op se ja' temos
* os raios ou se nao houver UZeusNetworkSubsystem conectado. */
void RequestAoiConfig();
/** Bind de UZeusNetworkSubsystem::OnDebugAoiInfo: armazena os raios reais. */
void HandleAoiConfig(float InterestCm, float DespawnCm);
/** Handle do bind do OnDebugAoiInfo (pra remover no EndPlay). */
FDelegateHandle AoiConfigHandle_;
};

View File

@@ -2,10 +2,13 @@
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "GameFramework/GameStateBase.h"
#include "ZMMO.h"
#include "ZeusEntity.h"
#include "ZeusEntityInterface.h"
#include "ZeusGASComponent.h"
#include "ZeusPlayerProxy.h"
#include "ZeusPlayerState.h"
#include "ZeusNetworkSubsystem.h"
void UZeusWorldSubsystem::Initialize(FSubsystemCollectionBase& Collection)
@@ -160,6 +163,54 @@ void UZeusWorldSubsystem::HandlePlayerSpawned(const int64 EntityId, const bool b
AsEntity->SetEntityRelevant(true);
}
// === PlayerState pra proxies (Batch 2.5+) ===
//
// Sem PlayerState, o proxy fica fora do GameState->PlayerArray. Daqui pra
// cima, qualquer sistema que itera PlayerArray (UZeusGASNetworkHandler::
// FindGASComponentForEntity, HUD, chat, friend list) NAO consegue achar o
// proxy -> fallback ao local player -> efeito apareceria no char errado
// (bug do cue do Dash). Criar PS replicado-fake permite que o pipeline GAS
// trate proxies igual a local players.
//
// PlayerState tem o UZeusGASComponent via DefaultGame.ini Component Registry,
// entao GASComp e' auto-instanciado. So' precisamos seed o EntityId.
if (APawn* ProxyPawn = Cast<APawn>(SpawnedActor))
{
FActorSpawnParameters PSParams;
PSParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
PSParams.Owner = ProxyPawn;
AZeusPlayerState* ProxyPS = World->SpawnActor<AZeusPlayerState>(
AZeusPlayerState::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator, PSParams);
if (ProxyPS)
{
ProxyPawn->SetPlayerState(ProxyPS); // tambem chama ProxyPS->SetPawn(ProxyPawn)
// Add ao PlayerArray do GameState (replicacao UE faria isso automatico,
// mas no nosso pipeline custom o PS local-fake precisa ser inserido).
if (AGameStateBase* GS = World->GetGameState())
{
GS->AddPlayerState(ProxyPS);
}
// Seed EntityId no GASComp do PS (criado via Component Registry).
// FindGASComponentForEntity vai resolver via PS->FindComponentByClass.
if (UZeusGASComponent* GASComp = ProxyPS->FindComponentByClass<UZeusGASComponent>())
{
GASComp->SeedEntityId(EntityId);
}
UE_LOG(LogZMMO, Log,
TEXT("ZeusWorldSubsystem: PlayerState criado p/ proxy EntityId=%lld PS=%s"),
EntityId, *ProxyPS->GetName());
}
else
{
UE_LOG(LogZMMO, Warning,
TEXT("ZeusWorldSubsystem: SpawnActor<AZeusPlayerState> falhou pra proxy EntityId=%lld"),
EntityId);
}
}
RemoteEntities.Add(EntityId, TWeakObjectPtr<AActor>(SpawnedActor));
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: spawned remote EntityId=%d at (%s) yaw=%.1f t=%lld"),
EntityId, *PosCm.ToString(), YawDeg, ServerTimeMs);
@@ -185,6 +236,22 @@ void UZeusWorldSubsystem::HandlePlayerDespawned(const int64 EntityId)
{
AsEntity->SetEntityRelevant(false);
}
// Destrui PS proxy linkado (Batch 2.5+). Sem isso o GameState->PlayerArray
// fica com PS orfa apos o proxy ser destruido.
if (APawn* Pawn = Cast<APawn>(Actor))
{
if (APlayerState* PS = Pawn->GetPlayerState())
{
if (UWorld* World = GetWorld())
{
if (AGameStateBase* GS = World->GetGameState())
{
GS->RemovePlayerState(PS);
}
}
PS->Destroy();
}
}
Actor->Destroy();
}
RemoteEntities.Remove(EntityId);

View File

@@ -47,7 +47,7 @@ namespace
static const FName P_EdgeGlowWidth("EdgeGlowWidth");
static const FName P_EdgeGlowIntensity("EdgeGlowIntensity");
FORCEINLINE float EaseOutCubic(float Alpha)
FORCEINLINE float EaseOutCubicPB(float Alpha)
{
return 1.f - FMath::Pow(1.f - Alpha, 3.f);
}
@@ -195,7 +195,7 @@ void UUIProgressBar_Base::NativeTick(const FGeometry& MyGeometry, float InDeltaT
PrimaryElapsed += InDeltaTime;
const float Duration = FMath::Max(PrimaryAnimationSeconds, MinAnimSeconds);
const float Alpha = FMath::Clamp(PrimaryElapsed / Duration, 0.f, 1.f);
CurrentPrimaryLevel = FMath::Lerp(PrimaryStart, TargetPrimaryLevel, EaseOutCubic(Alpha));
CurrentPrimaryLevel = FMath::Lerp(PrimaryStart, TargetPrimaryLevel, EaseOutCubicPB(Alpha));
ApplyPrimaryToMID(CurrentPrimaryLevel);
if (Alpha >= 1.f)
{
@@ -210,7 +210,7 @@ void UUIProgressBar_Base::NativeTick(const FGeometry& MyGeometry, float InDeltaT
SecondaryElapsed += InDeltaTime;
const float Duration = FMath::Max(SecondaryAnimationSeconds, MinAnimSeconds);
const float Alpha = FMath::Clamp(SecondaryElapsed / Duration, 0.f, 1.f);
CurrentSecondaryLevel = FMath::Lerp(SecondaryStart, TargetSecondaryLevel, EaseOutCubic(Alpha));
CurrentSecondaryLevel = FMath::Lerp(SecondaryStart, TargetSecondaryLevel, EaseOutCubicPB(Alpha));
ApplySecondaryToMID(CurrentSecondaryLevel);
if (Alpha >= 1.f)
{

View File

@@ -21,7 +21,8 @@ public class ZMMO : ModuleRules
"GameplayAbilities", // GAS: UAttributeSet, FGameplayAttributeData
"ZeusNetwork",
"ZeusGAS", // UZeusGASComponent, UZeusAttributeSet, FZeusAttributesSnapshot
"ZeusJobs"
"ZeusJobs",
"ZeusAdminToolsRuntime" // IZeusAOIDebugTarget + EZeusAOIOverlay (admin panel dirige o componente)
});
PrivateDependencyModuleNames.AddRange(new string[] {

View File

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