Compare commits
19 Commits
9422522d6d
...
GASCombatS
| Author | SHA1 | Date | |
|---|---|---|---|
| 432741af52 | |||
| 84f552b4fa | |||
| 1308338953 | |||
| 21d42d9846 | |||
| 2895889e2b | |||
| 1114ddbb11 | |||
| 9a430dabc3 | |||
| a3f2fa9c06 | |||
| 1f2441bde7 | |||
| 216e580548 | |||
| de294f4075 | |||
| 4d737829f5 | |||
| 341e6895c9 | |||
| 5f4c88637f | |||
| 77d52a703b | |||
| 9f5ccd3a05 | |||
| c0f6d32d95 | |||
| f21d059b67 | |||
| e87b2cec6c |
@@ -91,3 +91,12 @@ bShouldAcquireMissingChunksOnLoad=False
|
|||||||
bShouldWarnAboutInvalidAssets=True
|
bShouldWarnAboutInvalidAssets=True
|
||||||
MetaDataTagsForAssetRegistry=()
|
MetaDataTagsForAssetRegistry=()
|
||||||
|
|
||||||
|
|
||||||
|
[/Script/GameplayAbilities.AbilitySystemGlobals]
|
||||||
|
bUseDebugTargetFromHud=true
|
||||||
|
|
||||||
|
[ConsoleVariables]
|
||||||
|
; mostra cooldowns, ativações, falhas
|
||||||
|
AbilitySystem.LogAbilityTriggers=1
|
||||||
|
; mostra effects aplicados/removidos com magnitude
|
||||||
|
AbilitySystem.DebugAbilityTags=1
|
||||||
19
Config/DefaultZeusV1.ini
Normal file
19
Config/DefaultZeusV1.ini
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
; DefaultZeusV1.ini
|
||||||
|
; Config inicial pra UZeusNetworkingClientSubsystem (P9-6/P9-7 plugin).
|
||||||
|
; Editar via UE Project Settings -> Plugins -> Zeus V1 OU editar este arquivo
|
||||||
|
; e relancar o editor.
|
||||||
|
;
|
||||||
|
; PORT 7777: ZeusGateway. NodeService popula routes via charId lookup quando
|
||||||
|
; cliente conecta (validado: handshake routed charId=7 -> SP_01 cell 0). Fluxo
|
||||||
|
; padrao de producao -- UE conecta no Gateway, Gateway resolve cell ownership
|
||||||
|
; via lease em Valkey + encaminha pro ZS correto.
|
||||||
|
;
|
||||||
|
; ALTERNATIVO (debug only -- bug aberto pendente investigacao):
|
||||||
|
; ServerPort=9001 conecta DIRETO em SP_01 mas hoje pacote UE nao chega no
|
||||||
|
; server (FUdpSocketBuilder + Windows multi-NIC loopback issue). Use 7777.
|
||||||
|
|
||||||
|
[/Script/ZeusNetwork.ZeusNetworkingClientSubsystem]
|
||||||
|
ServerHost=127.0.0.1
|
||||||
|
ServerPort=7777
|
||||||
|
bUseZeusNetworkingV1=True
|
||||||
|
bAutoConnectOnStart=True
|
||||||
114
Config/Tags/ZeusServerTags.ini
Normal file
114
Config/Tags/ZeusServerTags.ini
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
; Gerado por ZeusTool tags sync -- tags do ZeusServer p/ o cliente UE.
|
||||||
|
; NAO editar a mao: regenere com `ZeusTool tags sync <json> <ini>`.
|
||||||
|
[/Script/GameplayTags.GameplayTagsList]
|
||||||
|
GameplayTagList=(Tag="GameplayCue",DevComment="")
|
||||||
|
GameplayTagList=(Tag="GameplayCue.Zeus",DevComment="")
|
||||||
|
GameplayTagList=(Tag="GameplayCue.Zeus.Melee",DevComment="")
|
||||||
|
GameplayTagList=(Tag="GameplayCue.Zeus.Melee.Kick",DevComment="")
|
||||||
|
GameplayTagList=(Tag="GameplayCue.Zeus.Combat",DevComment="Categoria root de cues compartilhados por TODO melee (reutilizaveis)")
|
||||||
|
GameplayTagList=(Tag="GameplayCue.Zeus.Combat.Hit",DevComment="Familia de cues do impacto de hit")
|
||||||
|
GameplayTagList=(Tag="GameplayCue.Zeus.Combat.Hit.Impact",DevComment="Broadcast AOI - SFX impact + VFX sparks na location (todos ouvem)")
|
||||||
|
GameplayTagList=(Tag="GameplayCue.Zeus.Combat.Hit.React",DevComment="Broadcast AOI - AnimMontage de hit react no target (todos veem)")
|
||||||
|
GameplayTagList=(Tag="GameplayCue.Zeus.Combat.Hit.Self",DevComment="Unicast - feedback privado do alvo (camera shake, hit flash, rumble)")
|
||||||
|
GameplayTagList=(Tag="GameplayCue.Zeus.Melee.Kick.Target",DevComment="")
|
||||||
|
GameplayTagList=(Tag="GameplayCue.Zeus.Movement",DevComment="")
|
||||||
|
GameplayTagList=(Tag="GameplayCue.Zeus.Movement.Dash",DevComment="")
|
||||||
|
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="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.Atk",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.BaseExp",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.BaseLevel",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.Crit",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.Def",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.Dex",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.Flee",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.Hit",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.Hp",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.HpRegenRate",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.Int",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.JobExp",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.JobLevel",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.Luk",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.Matk",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.MatkMax",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.MatkMin",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.MaxHp",DevComment="")
|
||||||
|
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.SpRegenRate",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.Stamina",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.StaminaRegenRate",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.StatusPoint",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.Str",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Attribute.Vit",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Combat",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Combat.Blocking",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Combat.Dodging",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Combat.InCombat",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Combat.LockOn",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Combo",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Combo.Slot1",DevComment="")
|
||||||
|
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.Damage",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Damage.Earth",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Damage.Fire",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Damage.Holy",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Damage.Magical",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Damage.Physical",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Damage.Poison",DevComment="")
|
||||||
|
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.Effect.Damage",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Effect.Damage.Kick",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.State",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.State.NoHpRegen",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.State.NoRegen",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.State.NoSpRegen",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.State.NoStaminaRegen",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.Buff",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.CC",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.CC.Disarmed",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.CC.Feared",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.CC.Frozen",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.CC.KnockedDown",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.CC.Rooted",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.CC.Silenced",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.CC.Stunned",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.Dead",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.Debuff",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.Invulnerable",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.Regen",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.Regen.Blocked",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.Regen.Boosted",DevComment="")
|
||||||
|
GameplayTagList=(Tag="Zeus.Status.Regen.Poisoned",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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/Input/Actions/IA_Kick.uasset
Normal file
BIN
Content/Input/Actions/IA_Kick.uasset
Normal file
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.
BIN
Content/ZMMO/Core/Player/BP_ZeusPlayerCharacter.uasset
Normal file
BIN
Content/ZMMO/Core/Player/BP_ZeusPlayerCharacter.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/GAS/Abilities/Kick/Animations/AS_Kick.uasset
Normal file
BIN
Content/ZMMO/GAS/Abilities/Kick/Animations/AS_Kick.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/GAS/Abilities/Kick/GA_Kick.uasset
Normal file
BIN
Content/ZMMO/GAS/Abilities/Kick/GA_Kick.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/GAS/Cues/Camera/CS_HitReact.uasset
Normal file
BIN
Content/ZMMO/GAS/Cues/Camera/CS_HitReact.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/GAS/Cues/GC_Combat_Hit_Impact.uasset
Normal file
BIN
Content/ZMMO/GAS/Cues/GC_Combat_Hit_Impact.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/GAS/Cues/GC_Combat_Hit_React.uasset
Normal file
BIN
Content/ZMMO/GAS/Cues/GC_Combat_Hit_React.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/GAS/Cues/GC_Combat_Hit_Self.uasset
Normal file
BIN
Content/ZMMO/GAS/Cues/GC_Combat_Hit_Self.uasset
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/GAS/Cues/GC_Kick.uasset
Normal file
BIN
Content/ZMMO/GAS/Cues/GC_Kick.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/GAS/Cues/HitReact/AM_HitReact_Heavy.uasset
Normal file
BIN
Content/ZMMO/GAS/Cues/HitReact/AM_HitReact_Heavy.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/GAS/Cues/HitReact/AM_HitReact_Light.uasset
Normal file
BIN
Content/ZMMO/GAS/Cues/HitReact/AM_HitReact_Light.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/GAS/Cues/HitReact/AM_HitReact_Medium.uasset
Normal file
BIN
Content/ZMMO/GAS/Cues/HitReact/AM_HitReact_Medium.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/GAS/Cues/HitReact/Animations/AS_HitReact.uasset
Normal file
BIN
Content/ZMMO/GAS/Cues/HitReact/Animations/AS_HitReact.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/Sounds/Combat/SW_Punch_Cartoon.uasset
Normal file
BIN
Content/ZMMO/Sounds/Combat/SW_Punch_Cartoon.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright Zeus Server Engine. All rights reserved.
|
||||||
|
|
||||||
|
#include "ZeusAnimNotifyState_BodyHitWindow.h"
|
||||||
|
|
||||||
|
#include "Components/SkeletalMeshComponent.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "Game/Entity/ZeusCharacter.h"
|
||||||
|
|
||||||
|
void UZeusAnimNotifyState_BodyHitWindow::NotifyBegin(USkeletalMeshComponent* MeshComp,
|
||||||
|
UAnimSequenceBase* /*Animation*/, float /*TotalDuration*/,
|
||||||
|
const FAnimNotifyEventReference& /*EventReference*/)
|
||||||
|
{
|
||||||
|
if (!MeshComp) { return; }
|
||||||
|
if (AZeusCharacter* Char = Cast<AZeusCharacter>(MeshComp->GetOwner()))
|
||||||
|
{
|
||||||
|
Char->SetHitColliderEnabled(Source, /*bEnabled*/ true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZeusAnimNotifyState_BodyHitWindow::NotifyEnd(USkeletalMeshComponent* MeshComp,
|
||||||
|
UAnimSequenceBase* /*Animation*/, const FAnimNotifyEventReference& /*EventReference*/)
|
||||||
|
{
|
||||||
|
if (!MeshComp) { return; }
|
||||||
|
if (AZeusCharacter* Char = Cast<AZeusCharacter>(MeshComp->GetOwner()))
|
||||||
|
{
|
||||||
|
Char->SetHitColliderEnabled(Source, /*bEnabled*/ false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
FString UZeusAnimNotifyState_BodyHitWindow::GetNotifyName_Implementation() const
|
||||||
|
{
|
||||||
|
const TCHAR* SourceLabel;
|
||||||
|
switch (Source)
|
||||||
|
{
|
||||||
|
case EZeusHitSource::FootR: SourceLabel = TEXT("FootR"); break;
|
||||||
|
case EZeusHitSource::FootL: SourceLabel = TEXT("FootL"); break;
|
||||||
|
case EZeusHitSource::HandR: SourceLabel = TEXT("HandR"); break;
|
||||||
|
case EZeusHitSource::HandL: SourceLabel = TEXT("HandL"); break;
|
||||||
|
case EZeusHitSource::WeaponR: SourceLabel = TEXT("WeaponR"); break;
|
||||||
|
case EZeusHitSource::WeaponL: SourceLabel = TEXT("WeaponL"); break;
|
||||||
|
default: SourceLabel = TEXT("?"); break;
|
||||||
|
}
|
||||||
|
return FString::Printf(TEXT("Zeus Body Hit (%s)"), SourceLabel);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
74
Source/ZMMO/Game/Combat/ZeusAnimNotifyState_BodyHitWindow.h
Normal file
74
Source/ZMMO/Game/Combat/ZeusAnimNotifyState_BodyHitWindow.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// Copyright Zeus Server Engine. All rights reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Animation/AnimNotifies/AnimNotifyState.h"
|
||||||
|
#include "Game/Entity/ZeusCharacter.h" // EZeusHitSource
|
||||||
|
#include "ZeusAnimNotifyState_BodyHitWindow.generated.h"
|
||||||
|
|
||||||
|
class USkeletalMeshComponent;
|
||||||
|
class UAnimSequenceBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AnimNotifyState que liga o HitCollider de uma parte do corpo durante a sua
|
||||||
|
* janela no montage. NotifyBegin liga + NotifyEnd desliga -- define o HIT
|
||||||
|
* WINDOW do golpe melee/arma branca.
|
||||||
|
*
|
||||||
|
* Generaliza para qualquer parte do corpo via EZeusHitSource:
|
||||||
|
* - FootR / FootL -> chute (capsule attachado em foot_*_Socket)
|
||||||
|
* - HandR / HandL -> soco (capsule em HandGrip_*)
|
||||||
|
* - WeaponR / WeaponL -> arma branca (capsule em weapon_*)
|
||||||
|
*
|
||||||
|
* === Como usar ===
|
||||||
|
*
|
||||||
|
* No editor de um AnimMontage (ex: AM_Kick_Montage):
|
||||||
|
* 1. Encontrar o frame onde o membro comeca o movimento de impacto.
|
||||||
|
* 2. Right-click na track de notifies -> Add Notify State -> Zeus Body Hit Window.
|
||||||
|
* 3. Definir Source = FootR (ou HandR pra soco, WeaponR pra arma, etc).
|
||||||
|
* 4. Arrastar o range pra cobrir o arco completo do golpe (ex: frame 8 a 18
|
||||||
|
* em 30fps = ~330ms de janela).
|
||||||
|
*
|
||||||
|
* Pq AnimNotifyState e nao AnimNotify pontual:
|
||||||
|
* - AnimNotify dispara em UM frame -> nao da' pra detectar entradas no
|
||||||
|
* capsule durante a janela.
|
||||||
|
* - AnimNotifyState tem NotifyBegin/NotifyTick/NotifyEnd. Pra "ligar
|
||||||
|
* collider de frame X ate Y", e' o pattern correto.
|
||||||
|
*
|
||||||
|
* === Replicacao ===
|
||||||
|
*
|
||||||
|
* Roda LOCAL em cada cliente. Cliente do ATIVADOR liga capsule local + reporta
|
||||||
|
* overlap via opcode MELEE_HIT_REPORT pro server. Server valida e aplica dano +
|
||||||
|
* dispatcha cue Kick.Target pra todos no AOI do alvo.
|
||||||
|
*
|
||||||
|
* Proxies de outros clientes tocam o montage via GameplayCue replicado (visual)
|
||||||
|
* mas o capsule deles roda no PROXY que e' Pawn local de outra coisa -- nao
|
||||||
|
* importa, o reportador autoritativo eh quem ativou a ability.
|
||||||
|
*/
|
||||||
|
UCLASS(meta = (DisplayName = "Zeus Body Hit Window"))
|
||||||
|
class ZMMO_API UZeusAnimNotifyState_BodyHitWindow : public UAnimNotifyState
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Qual parte do corpo (capsule) tem o hit ligado durante a janela.
|
||||||
|
/// Default = FootR (chute direito e' o golpe mais comum em montages
|
||||||
|
/// melee unarmed). Trocar pra HandR pra soco, WeaponR pra arma, etc.
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Combat")
|
||||||
|
EZeusHitSource Source = EZeusHitSource::FootR;
|
||||||
|
|
||||||
|
// === AnimNotifyState overrides ===
|
||||||
|
|
||||||
|
virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
|
||||||
|
float TotalDuration, const FAnimNotifyEventReference& EventReference) override;
|
||||||
|
|
||||||
|
virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
|
||||||
|
const FAnimNotifyEventReference& EventReference) override;
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
/// Mostra "Zeus Body Hit (FootR/HandL/WeaponR/...)" no editor de montage em
|
||||||
|
/// vez do nome generico da classe -- ajuda a identificar qual parte sem
|
||||||
|
/// abrir details.
|
||||||
|
virtual FString GetNotifyName_Implementation() const override;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
#include "GameFramework/PlayerController.h"
|
#include "GameFramework/PlayerController.h"
|
||||||
#include "ZeusWorldSubsystem.h"
|
#include "ZeusWorldSubsystem.h"
|
||||||
#include "ZeusNetworkSubsystem.h"
|
#include "ZeusNetworkSubsystem.h"
|
||||||
|
#include "ZeusNetworkingClientSubsystem.h"
|
||||||
#include "UI/FrontEnd/UIFrontEndFlowSubsystem.h"
|
#include "UI/FrontEnd/UIFrontEndFlowSubsystem.h"
|
||||||
|
|
||||||
DEFINE_LOG_CATEGORY(LogZeusPlayer);
|
DEFINE_LOG_CATEGORY(LogZeusPlayer);
|
||||||
@@ -172,6 +173,12 @@ void AZeusCharacter::Tick(const float DeltaSeconds)
|
|||||||
|
|
||||||
void AZeusCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
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))
|
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
|
||||||
{
|
{
|
||||||
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &AZeusCharacter::OnJumpPressed);
|
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &AZeusCharacter::OnJumpPressed);
|
||||||
@@ -362,7 +369,7 @@ void AZeusCharacter::SetEntityRelevant(bool /*bRelevant*/)
|
|||||||
|
|
||||||
void AZeusCharacter::ResolveZeusNetworkSubsystem()
|
void AZeusCharacter::ResolveZeusNetworkSubsystem()
|
||||||
{
|
{
|
||||||
if (ZeusNetwork)
|
if (ZeusNetwork && NetClient)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -373,7 +380,14 @@ void AZeusCharacter::ResolveZeusNetworkSubsystem()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZeusNetwork = GI->GetSubsystem<UZeusNetworkSubsystem>();
|
if (!ZeusNetwork)
|
||||||
|
{
|
||||||
|
ZeusNetwork = GI->GetSubsystem<UZeusNetworkSubsystem>();
|
||||||
|
}
|
||||||
|
if (!NetClient)
|
||||||
|
{
|
||||||
|
NetClient = GI->GetSubsystem<UZeusNetworkingClientSubsystem>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AZeusCharacter::BindZeusSpawnDelegate()
|
void AZeusCharacter::BindZeusSpawnDelegate()
|
||||||
@@ -382,49 +396,71 @@ void AZeusCharacter::BindZeusSpawnDelegate()
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!ZeusNetwork)
|
if (!NetClient && !ZeusNetwork)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZeusNetwork->OnPlayerSpawned.AddDynamic(this, &AZeusCharacter::HandleZeusPlayerSpawned);
|
// Self-entity (entityId do proprio char) vem do sistema de rede novo.
|
||||||
ZeusNetwork->OnCharInfoReceived.AddDynamic(this, &AZeusCharacter::HandleZeusCharInfo);
|
if (NetClient)
|
||||||
|
{
|
||||||
|
NetClient->OnSelfEntityAssigned.AddDynamic(this, &AZeusCharacter::HandleSelfEntityAssigned);
|
||||||
|
}
|
||||||
|
// CHAR_INFO (nome/guild) ainda nao tem equivalente no sistema novo -> legacy.
|
||||||
|
if (ZeusNetwork)
|
||||||
|
{
|
||||||
|
ZeusNetwork->OnCharInfoReceived.AddDynamic(this, &AZeusCharacter::HandleZeusCharInfo);
|
||||||
|
}
|
||||||
bSpawnDelegateBound = true;
|
bSpawnDelegateBound = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AZeusCharacter::UnbindZeusSpawnDelegate()
|
void AZeusCharacter::UnbindZeusSpawnDelegate()
|
||||||
{
|
{
|
||||||
if (!bSpawnDelegateBound || !ZeusNetwork)
|
if (!bSpawnDelegateBound)
|
||||||
{
|
{
|
||||||
bSpawnDelegateBound = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ZeusNetwork->OnPlayerSpawned.RemoveDynamic(this, &AZeusCharacter::HandleZeusPlayerSpawned);
|
if (NetClient)
|
||||||
ZeusNetwork->OnCharInfoReceived.RemoveDynamic(this, &AZeusCharacter::HandleZeusCharInfo);
|
{
|
||||||
|
NetClient->OnSelfEntityAssigned.RemoveDynamic(this, &AZeusCharacter::HandleSelfEntityAssigned);
|
||||||
|
}
|
||||||
|
if (ZeusNetwork)
|
||||||
|
{
|
||||||
|
ZeusNetwork->OnCharInfoReceived.RemoveDynamic(this, &AZeusCharacter::HandleZeusCharInfo);
|
||||||
|
}
|
||||||
bSpawnDelegateBound = false;
|
bSpawnDelegateBound = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AZeusCharacter::TryRegisterLocalEntityFromCachedSpawn()
|
void AZeusCharacter::TryRegisterLocalEntityFromCachedSpawn()
|
||||||
{
|
{
|
||||||
if (!ZeusNetwork)
|
// Self-entity vem do sistema de rede novo (ENT_SELF). O subsystem e'
|
||||||
|
// per-GameInstance e sobrevive ao OpenLevel, entao o entityId pode ja' ter
|
||||||
|
// chegado antes deste pawn existir -- puxa o cache agora. Caso ainda nao
|
||||||
|
// tenha chegado, o bind em OnSelfEntityAssigned cobre quando chegar.
|
||||||
|
// A pos/yaw vem do proprio pawn (ja posicionado via PendingSpawnPose do DB);
|
||||||
|
// o sistema novo so' carrega o entityId.
|
||||||
|
if (NetClient)
|
||||||
{
|
{
|
||||||
return;
|
const int64 SelfId = NetClient->GetLocalEntityId();
|
||||||
|
if (SelfId != 0)
|
||||||
|
{
|
||||||
|
HandleLocalSpawnReady(SelfId, GetActorLocation(), GetActorRotation().Yaw, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int64 CachedEntityId = 0;
|
// Race fix CHAR_INFO (ainda legacy): o S_CHAR_INFO pode ter chegado antes do
|
||||||
FVector CachedPosCm = FVector::ZeroVector;
|
// pawn existir; aplica o ultimo nome/guild cacheado.
|
||||||
float CachedYawDeg = 0.0f;
|
|
||||||
int64 CachedServerTimeMs = 0;
|
|
||||||
if (ZeusNetwork->TryGetLastLocalSpawn(CachedEntityId, CachedPosCm, CachedYawDeg, CachedServerTimeMs))
|
|
||||||
{
|
|
||||||
HandleLocalSpawnReady(CachedEntityId, CachedPosCm, CachedYawDeg, CachedServerTimeMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Race fix: o S_CHAR_INFO chega ANTES de S_SPAWN_PLAYER mas o pawn pode
|
|
||||||
// nao existir ainda (OpenLevel em andamento). Aplica o ultimo cacheado.
|
|
||||||
TryApplyCachedCharInfo();
|
TryApplyCachedCharInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AZeusCharacter::HandleSelfEntityAssigned(const int64 InEntityId)
|
||||||
|
{
|
||||||
|
// Sistema novo informou o entityId do proprio char (ENT_SELF). Reusa o mesmo
|
||||||
|
// caminho do spawn local legacy (seta ZeusEntityId + RegisterLocalEntity).
|
||||||
|
// pos/yaw vem do pawn (ja posicionado); o sistema novo so' traz o entityId.
|
||||||
|
HandleLocalSpawnReady(InEntityId, GetActorLocation(), GetActorRotation().Yaw, 0);
|
||||||
|
}
|
||||||
|
|
||||||
void AZeusCharacter::HandleZeusPlayerSpawned(const int64 InEntityId, const bool bIsLocal,
|
void AZeusCharacter::HandleZeusPlayerSpawned(const int64 InEntityId, const bool bIsLocal,
|
||||||
const FVector PosCm, const float YawDeg, const int64 ServerTimeMs)
|
const FVector PosCm, const float YawDeg, const int64 ServerTimeMs)
|
||||||
{
|
{
|
||||||
@@ -548,7 +584,10 @@ void AZeusCharacter::TryApplyCachedCharInfo()
|
|||||||
void AZeusCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
void AZeusCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
||||||
{
|
{
|
||||||
ResolveZeusNetworkSubsystem();
|
ResolveZeusNetworkSubsystem();
|
||||||
if (!ZeusNetwork || !ZeusNetwork->IsConnected())
|
// Sistema de rede novo (canonico) tem prioridade; legacy e' fallback.
|
||||||
|
const bool bV1 = (NetClient
|
||||||
|
&& NetClient->GetConnectionState() == EZeusV1ConnectionState::Accepted);
|
||||||
|
if (!bV1 && (!ZeusNetwork || !ZeusNetwork->IsConnected()))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -557,7 +596,19 @@ void AZeusCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
|||||||
SendAccumulatorSec += DeltaSeconds;
|
SendAccumulatorSec += DeltaSeconds;
|
||||||
TimeSinceLastSendSec += DeltaSeconds;
|
TimeSinceLastSendSec += DeltaSeconds;
|
||||||
|
|
||||||
const bool bMovingInput = !FMath::IsNearlyZero(PendingMoveForward) || !FMath::IsNearlyZero(PendingMoveRight);
|
// Movimento p/ efeitos de rate NAO e' so' o input (W/A/S/D): o CMC do dono
|
||||||
|
// continua desacelerando (braking) por ~0.3s depois de soltar a tecla. Se
|
||||||
|
// pararmos de enviar no rate rapido assim que o input zera, o servidor fica
|
||||||
|
// congelado na ultima `simpleVelCmS` alta e o proxy remoto extrapola ~1m alem
|
||||||
|
// (NewPos = Last.Pos + Last.Vel*ExtrapSec) ate o heartbeat corrigir -> overshoot
|
||||||
|
// + snap-back ("velocidade grande que demora a zerar" + jitter). Tratar a
|
||||||
|
// velocidade residual como movimento mantem o envio a InputSendRateHz ate o
|
||||||
|
// dono realmente parar, alimentando o proxy com a curva de desaceleracao.
|
||||||
|
const bool bHasInputAxis = !FMath::IsNearlyZero(PendingMoveForward) || !FMath::IsNearlyZero(PendingMoveRight);
|
||||||
|
const FVector OwnerVelNow = GetVelocity();
|
||||||
|
constexpr float kResidualSpeedSqCmS = 25.0f * 25.0f; // abaixo de 25 cm/s tratamos como parado
|
||||||
|
const bool bHasResidualVel = OwnerVelNow.SizeSquared() > kResidualSpeedSqCmS;
|
||||||
|
const bool bMovingInput = bHasInputAxis || bHasResidualVel;
|
||||||
const bool bJumpEvent = bPendingJumpPressed || bPendingJumpReleased;
|
const bool bJumpEvent = bPendingJumpPressed || bPendingJumpReleased;
|
||||||
const bool bRateReached = SendAccumulatorSec >= SendInterval;
|
const bool bRateReached = SendAccumulatorSec >= SendInterval;
|
||||||
const bool bHeartbeatReached = TimeSinceLastSendSec >= HeartbeatIntervalSec;
|
const bool bHeartbeatReached = TimeSinceLastSendSec >= HeartbeatIntervalSec;
|
||||||
@@ -571,6 +622,23 @@ void AZeusCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
|||||||
const bool bFallingChanged = (bIsFalling != bPreviousFalling);
|
const bool bFallingChanged = (bIsFalling != bPreviousFalling);
|
||||||
|
|
||||||
const bool bShouldSend = bJumpEvent || bFallingChanged || (bMovingInput && bRateReached) || (!bMovingInput && bHeartbeatReached);
|
const bool bShouldSend = bJumpEvent || bFallingChanged || (bMovingInput && bRateReached) || (!bMovingInput && bHeartbeatReached);
|
||||||
|
// DEBUG-INPUT (2026-06-12): log ~1x/s COM o PIE. Remover apos achar o gap.
|
||||||
|
{
|
||||||
|
static float DbgInputAcc = 0.0f;
|
||||||
|
DbgInputAcc += DeltaSeconds;
|
||||||
|
if (DbgInputAcc >= 1.0f)
|
||||||
|
{
|
||||||
|
DbgInputAcc = 0.0f;
|
||||||
|
const int32 PieId = (GetWorld() && GetWorld()->GetOutermost())
|
||||||
|
? GetWorld()->GetOutermost()->GetPIEInstanceID() : -1;
|
||||||
|
UE_LOG(LogZeusPlayer, Warning,
|
||||||
|
TEXT("[PIE%d][InputDbg] bV1=%d state=%d moving=%d rate=%d should=%d hbReached=%d fwd=%.2f right=%.2f"),
|
||||||
|
PieId, bV1 ? 1 : 0,
|
||||||
|
NetClient ? static_cast<int32>(NetClient->GetConnectionState()) : -99,
|
||||||
|
bMovingInput ? 1 : 0, bRateReached ? 1 : 0, bShouldSend ? 1 : 0,
|
||||||
|
bHeartbeatReached ? 1 : 0, PendingMoveForward, PendingMoveRight);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!bShouldSend)
|
if (!bShouldSend)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -578,30 +646,68 @@ void AZeusCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
|||||||
|
|
||||||
++InputSequence;
|
++InputSequence;
|
||||||
const int32 ClientTimeMs = static_cast<int32>(FPlatformTime::Seconds() * 1000.0);
|
const int32 ClientTimeMs = static_cast<int32>(FPlatformTime::Seconds() * 1000.0);
|
||||||
// ADR 0040: yaw da camara/controller para que o servidor consiga rotar o
|
// YAW DO CORPO (nao da mira): GetActorRotation().Yaw reflete o orient-to-movement
|
||||||
// input (referencial-camara) para velocidade mundial. Fallback para o yaw
|
// do CMC (bOrientRotationToMovement=true, RotationRate=(0,500,0)) -> o corpo vira
|
||||||
// do actor se nao houver controller (e.g. tela de loading).
|
// pra direcao do MOVIMENTO (WASD), suave a 500 deg/s, e o MOUSE nao gira o corpo
|
||||||
const float ViewYawDeg = (GetController()
|
// (bUseControllerRotationYaw=false). O servidor usa este yaw direto e o proxy nos
|
||||||
? static_cast<float>(GetController()->GetControlRotation().Yaw)
|
// outros clientes o replica -> o proxy vira IDENTICO ao corpo do dono.
|
||||||
: static_cast<float>(GetActorRotation().Yaw));
|
//
|
||||||
|
// (Historico bug 2026-06-13: antes mandava GetControlRotation().Yaw -- a MIRA --
|
||||||
|
// e o servidor fazia atan2(vel) INSTANTANEO quando movia (proxy "virava demais"
|
||||||
|
// 45 graus) e usava a mira quando parado (proxy seguia o mouse). ADR 0040: o
|
||||||
|
// servidor NAO rotaciona mais o input por este yaw (ADR 0041 manda pos+vel
|
||||||
|
// mundiais), entao trocar a mira pelo corpo e' seguro. Quando precisar da MIRA
|
||||||
|
// real (aim/combate), adicionar um campo aimYawDeg dedicado -- nao reusar este.)
|
||||||
|
const float BodyYawDeg = static_cast<float>(GetActorRotation().Yaw);
|
||||||
// ADR 0041: cliente autoritativo da posicao XYZ + velocidade XY. O CMC ja
|
// ADR 0041: cliente autoritativo da posicao XYZ + velocidade XY. O CMC ja
|
||||||
// integrou o input local antes deste flush, portanto `GetActorLocation` e
|
// integrou o input local antes deste flush, portanto `GetActorLocation` e
|
||||||
// `GetVelocity` reflectem o estado real que o servidor deve replicar para
|
// `GetVelocity` reflectem o estado real que o servidor deve replicar para
|
||||||
// os outros clientes (sujeito ao clamp anti-cheat).
|
// os outros clientes (sujeito ao clamp anti-cheat).
|
||||||
const FVector PosCm = GetActorLocation();
|
const FVector PosCm = GetActorLocation();
|
||||||
const FVector Vel = GetVelocity();
|
const FVector Vel = GetVelocity();
|
||||||
ZeusNetwork->SendInputAxis(
|
if (bV1)
|
||||||
PendingMoveForward,
|
{
|
||||||
PendingMoveRight,
|
// Sistema novo: INPUT 6077 com a pose absoluta (ADR 0040/0041).
|
||||||
bPendingJumpPressed,
|
const bool bSent = NetClient->EmitInput(
|
||||||
bPendingJumpReleased,
|
PendingMoveForward,
|
||||||
InputSequence,
|
PendingMoveRight,
|
||||||
ClientTimeMs,
|
bPendingJumpPressed,
|
||||||
ViewYawDeg,
|
bPendingJumpReleased,
|
||||||
PosCm,
|
InputSequence,
|
||||||
FVector2D(Vel.X, Vel.Y),
|
ClientTimeMs,
|
||||||
bIsFalling,
|
BodyYawDeg,
|
||||||
static_cast<float>(Vel.Z));
|
PosCm,
|
||||||
|
FVector2D(Vel.X, Vel.Y),
|
||||||
|
bIsFalling,
|
||||||
|
static_cast<float>(Vel.Z));
|
||||||
|
// DEBUG-INPUT (2026-06-12): confirma chamada + retorno do SendPacket.
|
||||||
|
static float DbgEmitAcc = 0.0f;
|
||||||
|
DbgEmitAcc += DeltaSeconds;
|
||||||
|
if (DbgEmitAcc >= 1.0f)
|
||||||
|
{
|
||||||
|
DbgEmitAcc = 0.0f;
|
||||||
|
const int32 PieId = (GetWorld() && GetWorld()->GetOutermost())
|
||||||
|
? GetWorld()->GetOutermost()->GetPIEInstanceID() : -1;
|
||||||
|
UE_LOG(LogZeusPlayer, Warning,
|
||||||
|
TEXT("[PIE%d][InputDbg] EmitInput seq=%d sent=%d pos=%s"),
|
||||||
|
PieId, InputSequence, bSent ? 1 : 0, *PosCm.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ZeusNetwork)
|
||||||
|
{
|
||||||
|
ZeusNetwork->SendInputAxis(
|
||||||
|
PendingMoveForward,
|
||||||
|
PendingMoveRight,
|
||||||
|
bPendingJumpPressed,
|
||||||
|
bPendingJumpReleased,
|
||||||
|
InputSequence,
|
||||||
|
ClientTimeMs,
|
||||||
|
BodyYawDeg,
|
||||||
|
PosCm,
|
||||||
|
FVector2D(Vel.X, Vel.Y),
|
||||||
|
bIsFalling,
|
||||||
|
static_cast<float>(Vel.Z));
|
||||||
|
}
|
||||||
|
|
||||||
SendAccumulatorSec = 0.0f;
|
SendAccumulatorSec = 0.0f;
|
||||||
TimeSinceLastSendSec = 0.0f;
|
TimeSinceLastSendSec = 0.0f;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class USpringArmComponent;
|
|||||||
class UCameraComponent;
|
class UCameraComponent;
|
||||||
class UInputAction;
|
class UInputAction;
|
||||||
class UZeusNetworkSubsystem;
|
class UZeusNetworkSubsystem;
|
||||||
|
class UZeusNetworkingClientSubsystem;
|
||||||
class UZeusAOIComponent;
|
class UZeusAOIComponent;
|
||||||
class UUserWidget;
|
class UUserWidget;
|
||||||
struct FInputActionValue;
|
struct FInputActionValue;
|
||||||
@@ -164,6 +165,11 @@ private:
|
|||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleZeusPlayerSpawned(int64 InEntityId, bool bIsLocal, FVector PosCm, float YawDeg, int64 ServerTimeMs);
|
void HandleZeusPlayerSpawned(int64 InEntityId, bool bIsLocal, FVector PosCm, float YawDeg, int64 ServerTimeMs);
|
||||||
|
|
||||||
|
// Self-entity do sistema de rede novo (ENT_SELF): o cliente aprende o
|
||||||
|
// proprio entityId. Substitui o caminho legacy OnPlayerSpawned(bIsLocal=true).
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleSelfEntityAssigned(int64 InEntityId);
|
||||||
|
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleZeusCharInfo(int64 InEntityId, const FString& CharName, const FString& GuildName);
|
void HandleZeusCharInfo(int64 InEntityId, const FString& CharName, const FString& GuildName);
|
||||||
|
|
||||||
@@ -172,6 +178,10 @@ private:
|
|||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TObjectPtr<UZeusNetworkSubsystem> ZeusNetwork;
|
TObjectPtr<UZeusNetworkSubsystem> ZeusNetwork;
|
||||||
|
|
||||||
|
// Sistema de rede novo (canonico): fonte do self-entity (ENT_SELF).
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UZeusNetworkingClientSubsystem> NetClient;
|
||||||
|
|
||||||
/** Instancia viva do painel admin (criada lazy no primeiro F8). */
|
/** Instancia viva do painel admin (criada lazy no primeiro F8). */
|
||||||
UPROPERTY(Transient)
|
UPROPERTY(Transient)
|
||||||
TObjectPtr<UUserWidget> AdminPanelInstance;
|
TObjectPtr<UUserWidget> AdminPanelInstance;
|
||||||
|
|||||||
@@ -132,11 +132,18 @@ void AZeusPlayerProxy::Tick(const float DeltaSeconds)
|
|||||||
// conhecida, clampada por `MaxExtrapolationSeconds` para nao deixar
|
// conhecida, clampada por `MaxExtrapolationSeconds` para nao deixar
|
||||||
// o proxy fugir se a rede ficar muda.
|
// o proxy fugir se a rede ficar muda.
|
||||||
const FZeusProxySnapshot& Last = SnapshotBuffer.Last();
|
const FZeusProxySnapshot& Last = SnapshotBuffer.Last();
|
||||||
const float ExtrapSec = FMath::Clamp(
|
const float RawExtrapSec =
|
||||||
static_cast<float>(RenderMs - Last.ServerTimeMs) / 1000.0f,
|
static_cast<float>(RenderMs - Last.ServerTimeMs) / 1000.0f;
|
||||||
0.0f, MaxExtrapolationSeconds);
|
const float ExtrapSec = FMath::Clamp(RawExtrapSec, 0.0f, MaxExtrapolationSeconds);
|
||||||
NewPos = Last.PosCm + Last.VelCmS * ExtrapSec;
|
NewPos = Last.PosCm + Last.VelCmS * ExtrapSec;
|
||||||
NewVel = Last.VelCmS;
|
// STARVATION (buffer=1 por muito tempo -- ex: 1o login sem DELTA_BATCH por
|
||||||
|
// segundos): a extrapolacao satura no clamp e a POSE congela, mas manter a
|
||||||
|
// velocidade faz o AnimBP rodar locomocao PARADO ("andar no lugar"/moonwalk
|
||||||
|
// = o "jitter" reportado). Se ja passamos bem do clamp, zera a vel visual
|
||||||
|
// pra o proxy ir pra Idle ate' os deltas voltarem (em vez de animar
|
||||||
|
// andando travado). Quando os deltas chegam, o ramo de interpolacao retoma.
|
||||||
|
const bool bStarved = RawExtrapSec > (MaxExtrapolationSeconds + 0.15f);
|
||||||
|
NewVel = bStarved ? FVector::ZeroVector : Last.VelCmS;
|
||||||
bExtrapolating = ExtrapSec > KINDA_SMALL_NUMBER;
|
bExtrapolating = ExtrapSec > KINDA_SMALL_NUMBER;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -150,12 +157,32 @@ void AZeusPlayerProxy::Tick(const float DeltaSeconds)
|
|||||||
NewVel = FMath::Lerp(A.VelCmS, B.VelCmS, InterpAlpha);
|
NewVel = FMath::Lerp(A.VelCmS, B.VelCmS, InterpAlpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yaw deriva da velocidade interpolada: locomotion mantem face na direcao
|
// ORIENTACAO DO CORPO = REPLICA do yaw autoritativo do servidor (Snapshot.YawDeg),
|
||||||
// do movimento; em idle preserva o yaw atual para nao "snappar".
|
// interpolado shortest-path entre os MESMOS A/B da posicao. Esse yaw agora carrega
|
||||||
|
// o yaw do CORPO do dono (orient-to-movement do CMC: vira pra direcao do MOVIMENTO
|
||||||
|
// /WASD, suave a 500 deg/s; parado mantem; o MOUSE nao gira o corpo) -- o cliente
|
||||||
|
// dono manda GetActorRotation().Yaw e o servidor repassa direto. Por isso o proxy
|
||||||
|
// so' REPLICA: vira identico ao corpo do dono, sem re-derivar nada.
|
||||||
|
//
|
||||||
|
// NAO derivamos de Atan2(velocidade): isso era a fonte do bug 2026-06-13 ("virava
|
||||||
|
// demais 45 graus") -- a direcao da velocidade salta, o yaw do corpo nao. A
|
||||||
|
// suavizacao correta ja' aconteceu no CMC do dono; aqui so' interpolamos amostras.
|
||||||
|
//
|
||||||
|
// FUTURO -- Motion Matching/aim: quando "a mira dita a locomocao" (strafe/aim
|
||||||
|
// offset), o servidor passara a mandar um aimYawDeg separado e o AnimBP usara a
|
||||||
|
// velocidade relativa a este yaw pra escolher fwd/back/left/right. Por ORA, 1 yaw.
|
||||||
FRotator NewRot = GetActorRotation();
|
FRotator NewRot = GetActorRotation();
|
||||||
if (FVector(NewVel.X, NewVel.Y, 0.0f).SizeSquared() > 1.0f)
|
if (IdxB == INDEX_NONE)
|
||||||
{
|
{
|
||||||
NewRot.Yaw = FMath::RadiansToDegrees(FMath::Atan2(NewVel.Y, NewVel.X));
|
NewRot.Yaw = SnapshotBuffer.Last().YawDeg; // extrapolando: segura o ultimo yaw
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const float YawA = SnapshotBuffer[IdxB - 1].YawDeg;
|
||||||
|
const float YawB = SnapshotBuffer[IdxB].YawDeg;
|
||||||
|
// FindDeltaAngleDegrees -> menor arco (-180..180); evita girar "o lado longo".
|
||||||
|
const float DeltaYaw = FMath::FindDeltaAngleDegrees(YawA, YawB);
|
||||||
|
NewRot.Yaw = YawA + DeltaYaw * InterpAlpha;
|
||||||
}
|
}
|
||||||
NewRot.Pitch = 0.0f;
|
NewRot.Pitch = 0.0f;
|
||||||
NewRot.Roll = 0.0f;
|
NewRot.Roll = 0.0f;
|
||||||
@@ -207,10 +234,26 @@ void AZeusPlayerProxy::Tick(const float DeltaSeconds)
|
|||||||
LastDiagLogSec = NowSec;
|
LastDiagLogSec = NowSec;
|
||||||
const int64 NewestMs = SnapshotBuffer.Last().ServerTimeMs;
|
const int64 NewestMs = SnapshotBuffer.Last().ServerTimeMs;
|
||||||
const int64 RenderLagMs = ServerNowMs - NewestMs;
|
const int64 RenderLagMs = ServerNowMs - NewestMs;
|
||||||
UE_LOG(LogZMMO, Verbose,
|
// === DIAG PERMANENTE -- jitter por FOME DE DELTAS ========================
|
||||||
TEXT("ZeusPlayerProxy[%lld] buffer=%d renderLagMs=%lld interpAlpha=%.2f extrap=%d delayMs=%.0f"),
|
// Bug investigado/resolvido 2026-06-13 (ver memoria
|
||||||
EntityId, SnapshotBuffer.Num(), RenderLagMs, InterpAlpha,
|
// project_proxy_delta_starvation_jitter). Este Warning FICA DE PROPOSITO
|
||||||
bExtrapolating ? 1 : 0, InterpolationDelayMs);
|
// (decisao do dono: NAO rebaixar p/ Verbose nem remover) -- e' a sonda pra
|
||||||
|
// cacar regressao do jitter de proxy. Como ler:
|
||||||
|
// secsSinceSnap = ha quanto tempo o proxy NAO recebe snapshot. Sobe = FOME
|
||||||
|
// (player remoto parado -> ComputeDelta suprime no server -> sem DELTA_BATCH).
|
||||||
|
// newestMs = serverTimeMs do ultimo snapshot; CONGELA na fome (deveria avancar).
|
||||||
|
// renderLagMs = ServerNowMs - newestMs; cresce 1:1 com o wall-clock na fome.
|
||||||
|
// speed=0 na fome = a mitigacao (zera a vel visual -> Idle, sem "moonwalk") agindo.
|
||||||
|
// Ao o player voltar a se mover sai "buffer STALE ... reset+reseed" (anti-stale)
|
||||||
|
// e renderLagMs/secsSinceSnap voltam a ~0 (snap limpo, sem salto/jitter).
|
||||||
|
// =========================================================================
|
||||||
|
const double SecsSinceSnap =
|
||||||
|
(LastSnapshotRecvSec > 0.0) ? (NowSec - LastSnapshotRecvSec) : -1.0;
|
||||||
|
UE_LOG(LogZMMO, Warning,
|
||||||
|
TEXT("ZeusPlayerProxy[%lld] buffer=%d renderLagMs=%lld newestMs=%lld secsSinceSnap=%.1f interpAlpha=%.2f extrap=%d speed=%.0f"),
|
||||||
|
EntityId, SnapshotBuffer.Num(), RenderLagMs, NewestMs, SecsSinceSnap, InterpAlpha,
|
||||||
|
bExtrapolating ? 1 : 0,
|
||||||
|
FVector(VisualVelocity.X, VisualVelocity.Y, 0.0f).Size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,18 +265,65 @@ void AZeusPlayerProxy::SetZeusIdentity(const int64 InEntityId, const EZeusEntity
|
|||||||
|
|
||||||
void AZeusPlayerProxy::ApplyEntitySnapshot(const FZeusEntitySnapshot& Snapshot)
|
void AZeusPlayerProxy::ApplyEntitySnapshot(const FZeusEntitySnapshot& Snapshot)
|
||||||
{
|
{
|
||||||
// Estabiliza o offset de relogio na primeira amostra. Subsequente nao
|
LastSnapshotRecvSec = FPlatformTime::Seconds(); // DIAG: marca chegada (mede a fome no Tick)
|
||||||
// re-sincroniza para evitar saltos visuais; um esquema mais sofisticado
|
|
||||||
// (ema do offset) cabe quando tivermos ping/RTT estabilizado por sessao.
|
// 2026-06-06 H3 fix — disciplina o ServerClockOffsetMs via EMA com clamp.
|
||||||
|
// Antes: offset congelado na 1a amostra. Em cross-server handoff a fonte
|
||||||
|
// do ServerTimeMs muda (publisher antigo -> novo) e o offset fica
|
||||||
|
// permanentemente skewed, alimentando tremor pos-handoff e drift cumulativo
|
||||||
|
// de clock cliente vs server. Agora cada amostra recalcula candidato e
|
||||||
|
// suaviza (alpha=0.02, ~50 amostras pra convergir) com clamp max 2ms/snapshot
|
||||||
|
// pra evitar salto visual durante transicao.
|
||||||
|
const int64 LocalNowMs = static_cast<int64>(FPlatformTime::Seconds() * 1000.0);
|
||||||
|
const int64 OffsetCandidate = LocalNowMs - Snapshot.ServerTimeMs;
|
||||||
|
|
||||||
|
// Anti-stale (2026-06-13): se este snapshot esta MUITO a frente do mais novo
|
||||||
|
// do buffer, o proxy ficou "mudo" por segundos -- player remoto parado ->
|
||||||
|
// ComputeDelta suprime -> sem DELTA_BATCH novo -> renderLagMs disparou pra
|
||||||
|
// dezenas de segundos. Manter os snapshots velhos faz a interpolacao usar um
|
||||||
|
// span gigante (pose de ~100s atras -> agora) quando o player volta a se mover,
|
||||||
|
// causando rasteio/salto (o jitter reportado). Descarta o buffer obsoleto +
|
||||||
|
// re-bootstrapa o relogio pra a interpolacao recomecar limpa neste keyframe.
|
||||||
|
if (SnapshotBuffer.Num() > 0
|
||||||
|
&& (Snapshot.ServerTimeMs - SnapshotBuffer.Last().ServerTimeMs) > 1000)
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Warning,
|
||||||
|
TEXT("ZeusPlayerProxy[%lld] buffer STALE gap=%lldms -> reset+reseed"),
|
||||||
|
EntityId, Snapshot.ServerTimeMs - SnapshotBuffer.Last().ServerTimeMs);
|
||||||
|
SnapshotBuffer.Reset();
|
||||||
|
ServerClockOffsetMs = OffsetCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
if (SnapshotBuffer.Num() == 0)
|
if (SnapshotBuffer.Num() == 0)
|
||||||
{
|
{
|
||||||
const int64 LocalNowMs = static_cast<int64>(FPlatformTime::Seconds() * 1000.0);
|
// Primeira amostra: bootstrap direto (nao tem baseline pra suavizar).
|
||||||
ServerClockOffsetMs = LocalNowMs - Snapshot.ServerTimeMs;
|
ServerClockOffsetMs = OffsetCandidate;
|
||||||
|
// DIAG (jitter 1o login): confirma o seed do relogio. serverTimeMs deve ser
|
||||||
|
// != 0 e o offset razoavel; se vier 0 aqui, o keyframe/catch-up corrompeu o
|
||||||
|
// bootstrap (hipotese descartada, mas o log fecha a questao em campo).
|
||||||
|
UE_LOG(LogZMMO, Warning,
|
||||||
|
TEXT("ZeusPlayerProxy[%lld] SEED clock serverTimeMs=%lld localNowMs=%lld offset=%lld"),
|
||||||
|
EntityId, Snapshot.ServerTimeMs, LocalNowMs, ServerClockOffsetMs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const double Alpha = 0.02;
|
||||||
|
const int64 RawTarget = static_cast<int64>(
|
||||||
|
static_cast<double>(ServerClockOffsetMs) * (1.0 - Alpha)
|
||||||
|
+ static_cast<double>(OffsetCandidate) * Alpha);
|
||||||
|
// Clamp max 2ms por amostra — em 30Hz isso converge a 60ms/s, mais que
|
||||||
|
// o suficiente pra absorver drift normal sem causar tremor visual.
|
||||||
|
const int64 Delta = RawTarget - ServerClockOffsetMs;
|
||||||
|
const int64 ClampedDelta = (Delta > 2) ? 2
|
||||||
|
: (Delta < -2) ? -2
|
||||||
|
: Delta;
|
||||||
|
ServerClockOffsetMs += ClampedDelta;
|
||||||
}
|
}
|
||||||
|
|
||||||
FZeusProxySnapshot S;
|
FZeusProxySnapshot S;
|
||||||
S.PosCm = Snapshot.PositionCm;
|
S.PosCm = Snapshot.PositionCm;
|
||||||
S.VelCmS = Snapshot.VelocityCmS;
|
S.VelCmS = Snapshot.VelocityCmS;
|
||||||
|
S.YawDeg = Snapshot.YawDeg; // yaw do CORPO do dono (orient-to-movement; mouse nao gira)
|
||||||
S.bGrounded = Snapshot.bGrounded;
|
S.bGrounded = Snapshot.bGrounded;
|
||||||
S.ServerTimeMs = Snapshot.ServerTimeMs;
|
S.ServerTimeMs = Snapshot.ServerTimeMs;
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,15 @@ struct FZeusProxySnapshot
|
|||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FVector VelCmS = FVector::ZeroVector;
|
FVector VelCmS = FVector::ZeroVector;
|
||||||
|
|
||||||
|
/** Yaw autoritativo do servidor (graus) = o MESMO yaw que o player LOCAL aplicou
|
||||||
|
* ao virar. O proxy replica EXATAMENTE a viradinha do local (apertou D = mesma
|
||||||
|
* virada nos dois). Sem armazenar isto, o proxy derivava o yaw da velocidade
|
||||||
|
* (Atan2) e "virava totalmente pra direcao do movimento" (bug 2026-06-13).
|
||||||
|
* NAO confundir com "mira dita a locomocao" (strafe / Motion Matching) -- isso
|
||||||
|
* esta' DESABILITADO por ora (ver AVISO em ZeusPlayerProxy.cpp::Tick). */
|
||||||
|
UPROPERTY()
|
||||||
|
float YawDeg = 0.0f;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
bool bGrounded = true;
|
bool bGrounded = true;
|
||||||
|
|
||||||
@@ -140,6 +149,12 @@ protected:
|
|||||||
*/
|
*/
|
||||||
int64 ServerClockOffsetMs = 0;
|
int64 ServerClockOffsetMs = 0;
|
||||||
|
|
||||||
|
/** DIAG (2026-06-13): instante (s, monotonic) do ultimo snapshot recebido em
|
||||||
|
* ApplyEntitySnapshot. Usado no log do Tick pra medir "secsSinceSnap" = quanto
|
||||||
|
* tempo o proxy ficou MUDO (sem delta). secsSinceSnap grande == fome (player
|
||||||
|
* remoto parado -> ComputeDelta suprime). 0 = nenhum snapshot ainda. */
|
||||||
|
double LastSnapshotRecvSec = 0.0;
|
||||||
|
|
||||||
/** Aceleracao derivada (com smoothing) escrita no CMC custom para alimentar
|
/** Aceleracao derivada (com smoothing) escrita no CMC custom para alimentar
|
||||||
* o AnimBP (`ShouldMove`/`Jump_Start` exigem Acceleration!=0). */
|
* o AnimBP (`ShouldMove`/`Jump_Start` exigem Acceleration!=0). */
|
||||||
FVector LastDerivedAccelerationCmS2 = FVector::ZeroVector;
|
FVector LastDerivedAccelerationCmS2 = FVector::ZeroVector;
|
||||||
|
|||||||
@@ -5,10 +5,47 @@
|
|||||||
#include "ZeusPlayerController.h"
|
#include "ZeusPlayerController.h"
|
||||||
#include "ZeusPlayerState.h"
|
#include "ZeusPlayerState.h"
|
||||||
|
|
||||||
|
#include "UObject/ConstructorHelpers.h"
|
||||||
|
|
||||||
AZeusGameMode::AZeusGameMode()
|
AZeusGameMode::AZeusGameMode()
|
||||||
{
|
{
|
||||||
DefaultPawnClass = AZeusCharacter::StaticClass();
|
// 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();
|
PlayerControllerClass = AZeusPlayerController::StaticClass();
|
||||||
PlayerStateClass = AZeusPlayerState::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();
|
HUDClass = AZeusHUD::StaticClass();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
#include "ZeusAOIComponent.h"
|
#include "ZeusAOIComponent.h"
|
||||||
|
|
||||||
#include "ZeusNetworkSubsystem.h" // plugin ZeusNetwork: OnDebugAoiInfo + SendDebugAoiRequest
|
#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"
|
#include "DrawDebugHelpers.h"
|
||||||
#include "Engine/GameInstance.h"
|
#include "Engine/GameInstance.h"
|
||||||
@@ -22,6 +24,8 @@ static TAutoConsoleVariable<int32> CVarShowHandoff(
|
|||||||
TEXT("zeus.debug.handoff"), 0, TEXT("Desenha a linha de handoff (placeholder)."), ECVF_Default);
|
TEXT("zeus.debug.handoff"), 0, TEXT("Desenha a linha de handoff (placeholder)."), ECVF_Default);
|
||||||
static TAutoConsoleVariable<int32> CVarShowDrift(
|
static TAutoConsoleVariable<int32> CVarShowDrift(
|
||||||
TEXT("zeus.debug.drift"), 0, TEXT("Desenha o drift autoritativo (TODO)."), ECVF_Default);
|
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
|
namespace
|
||||||
{
|
{
|
||||||
@@ -29,6 +33,14 @@ namespace
|
|||||||
const FColor kColorDespawn(255, 140, 90); // laranja — Zona de Despawn (externa)
|
const FColor kColorDespawn(255, 140, 90); // laranja — Zona de Despawn (externa)
|
||||||
const FColor kColorCell(232, 192, 96);
|
const FColor kColorCell(232, 192, 96);
|
||||||
const FColor kColorHandoff(255, 155, 190);
|
const FColor kColorHandoff(255, 155, 190);
|
||||||
|
|
||||||
|
// Resolucao das esferas de overlay. 32 segments deixavam a "bolha" facetada
|
||||||
|
// (poucas linhas) num raio de 60-80m; 48 deixa o circulo bem mais denso/legivel.
|
||||||
|
constexpr int32 kSphereSegments = 48;
|
||||||
|
// Clamp visual defensivo: se o raio vier absurdo (config corrompida), nao desenha
|
||||||
|
// uma esfera gigante que engole o mapa. 500m cobre qualquer AOI real (max=800m,
|
||||||
|
// mas overlay > 500m ja' nao ajuda a leitura). So' afeta o desenho, nao o gameplay.
|
||||||
|
constexpr float kMaxOverlayRadiusCm = 50000.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
UZeusAOIComponent::UZeusAOIComponent()
|
UZeusAOIComponent::UZeusAOIComponent()
|
||||||
@@ -42,14 +54,25 @@ void UZeusAOIComponent::BeginPlay()
|
|||||||
{
|
{
|
||||||
Super::BeginPlay();
|
Super::BeginPlay();
|
||||||
|
|
||||||
// Bind do feed de config de AOI (servidor -> cliente). Multicast plain C++.
|
// Bind do feed de config de AOI (servidor -> cliente) pelo transporte V1
|
||||||
|
// canonico. O legacy (UZeusNetworkSubsystem) esta desativado no V1 -> usava
|
||||||
|
// SendDebugAoiRequest que logava "ignorado: nao conectado" e o overlay nunca
|
||||||
|
// recebia a config. Agora via UZeusNetworkingClientSubsystem (OnDebugAoiInfo 6161).
|
||||||
if (UWorld* World = GetWorld())
|
if (UWorld* World = GetWorld())
|
||||||
{
|
{
|
||||||
if (UGameInstance* GI = World->GetGameInstance())
|
if (UGameInstance* GI = World->GetGameInstance())
|
||||||
{
|
{
|
||||||
if (UZeusNetworkSubsystem* Net = GI->GetSubsystem<UZeusNetworkSubsystem>())
|
if (UZeusNetworkingClientSubsystem* NetC = GI->GetSubsystem<UZeusNetworkingClientSubsystem>())
|
||||||
{
|
{
|
||||||
AoiConfigHandle_ = Net->OnDebugAoiInfo.AddUObject(this, &UZeusAOIComponent::HandleAoiConfig);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,19 +84,31 @@ void UZeusAOIComponent::BeginPlay()
|
|||||||
|
|
||||||
void UZeusAOIComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
void UZeusAOIComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||||
{
|
{
|
||||||
if (AoiConfigHandle_.IsValid())
|
if (AoiConfigHandle_.IsValid() || FrontierConfigHandle_.IsValid())
|
||||||
{
|
{
|
||||||
if (UWorld* World = GetWorld())
|
if (UWorld* World = GetWorld())
|
||||||
{
|
{
|
||||||
if (UGameInstance* GI = World->GetGameInstance())
|
if (UGameInstance* GI = World->GetGameInstance())
|
||||||
{
|
{
|
||||||
if (UZeusNetworkSubsystem* Net = GI->GetSubsystem<UZeusNetworkSubsystem>())
|
if (UZeusNetworkingClientSubsystem* NetC = GI->GetSubsystem<UZeusNetworkingClientSubsystem>())
|
||||||
{
|
{
|
||||||
Net->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();
|
AoiConfigHandle_.Reset();
|
||||||
|
FrontierConfigHandle_.Reset();
|
||||||
|
}
|
||||||
|
VisibleEntityIds_.Empty();
|
||||||
|
if (FrontierActor_)
|
||||||
|
{
|
||||||
|
FrontierActor_->Destroy();
|
||||||
|
FrontierActor_ = nullptr;
|
||||||
}
|
}
|
||||||
Super::EndPlay(EndPlayReason);
|
Super::EndPlay(EndPlayReason);
|
||||||
}
|
}
|
||||||
@@ -83,6 +118,26 @@ void UZeusAOIComponent::TickComponent(float DeltaTime, ELevelTick TickType,
|
|||||||
{
|
{
|
||||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
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())
|
if (AnyOverlayActive())
|
||||||
{
|
{
|
||||||
DrawOverlays();
|
DrawOverlays();
|
||||||
@@ -95,9 +150,9 @@ void UZeusAOIComponent::RequestAoiConfig()
|
|||||||
{
|
{
|
||||||
if (UGameInstance* GI = World->GetGameInstance())
|
if (UGameInstance* GI = World->GetGameInstance())
|
||||||
{
|
{
|
||||||
if (UZeusNetworkSubsystem* Net = GI->GetSubsystem<UZeusNetworkSubsystem>())
|
if (UZeusNetworkingClientSubsystem* NetC = GI->GetSubsystem<UZeusNetworkingClientSubsystem>())
|
||||||
{
|
{
|
||||||
Net->SendDebugAoiRequest(); // no-op se nao conectado
|
NetC->EmitDebugAoiRequest(); // V1: no-op se ainda nao Accepted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,6 +162,70 @@ void UZeusAOIComponent::HandleAoiConfig(float InterestCm, float DespawnCm)
|
|||||||
{
|
{
|
||||||
InterestRadiusCm = InterestCm;
|
InterestRadiusCm = InterestCm;
|
||||||
DespawnRadiusCm = DespawnCm;
|
DespawnRadiusCm = DespawnCm;
|
||||||
|
// K4: log AOI cfg recebido do server (paridade com Net.AOI server-side
|
||||||
|
// "config_applied ZI=... ZD=..."). Cold path (1 por sessao).
|
||||||
|
UE_LOG(LogZeusAOI, Display,
|
||||||
|
TEXT("AOI cfg recv interestCm=%.0f (%.0fm) despawnCm=%.0f (%.0fm)"),
|
||||||
|
InterestCm, InterestCm / 100.0f,
|
||||||
|
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
|
bool UZeusAOIComponent::ResolveOverlay(EZeusAOIOverlay Overlay) const
|
||||||
@@ -119,6 +238,7 @@ bool UZeusAOIComponent::ResolveOverlay(EZeusAOIOverlay Overlay) const
|
|||||||
case EZeusAOIOverlay::ProxyLabels: return bShowProxyLabels || CVarShowProxy.GetValueOnGameThread() > 0;
|
case EZeusAOIOverlay::ProxyLabels: return bShowProxyLabels || CVarShowProxy.GetValueOnGameThread() > 0;
|
||||||
case EZeusAOIOverlay::HandoffLine: return bShowHandoffLine || CVarShowHandoff.GetValueOnGameThread() > 0;
|
case EZeusAOIOverlay::HandoffLine: return bShowHandoffLine || CVarShowHandoff.GetValueOnGameThread() > 0;
|
||||||
case EZeusAOIOverlay::AuthDrift: return bShowAuthDrift || CVarShowDrift.GetValueOnGameThread() > 0;
|
case EZeusAOIOverlay::AuthDrift: return bShowAuthDrift || CVarShowDrift.GetValueOnGameThread() > 0;
|
||||||
|
case EZeusAOIOverlay::FrontierCells: return bShowFrontier || CVarShowFrontier.GetValueOnGameThread() > 0;
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,7 +250,8 @@ bool UZeusAOIComponent::AnyOverlayActive() const
|
|||||||
|| ResolveOverlay(EZeusAOIOverlay::CellBounds)
|
|| ResolveOverlay(EZeusAOIOverlay::CellBounds)
|
||||||
|| ResolveOverlay(EZeusAOIOverlay::ProxyLabels)
|
|| ResolveOverlay(EZeusAOIOverlay::ProxyLabels)
|
||||||
|| ResolveOverlay(EZeusAOIOverlay::HandoffLine)
|
|| ResolveOverlay(EZeusAOIOverlay::HandoffLine)
|
||||||
|| ResolveOverlay(EZeusAOIOverlay::AuthDrift);
|
|| ResolveOverlay(EZeusAOIOverlay::AuthDrift)
|
||||||
|
|| ResolveOverlay(EZeusAOIOverlay::FrontierCells);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UZeusAOIComponent::DrawOverlays()
|
void UZeusAOIComponent::DrawOverlays()
|
||||||
@@ -145,12 +266,14 @@ void UZeusAOIComponent::DrawOverlays()
|
|||||||
// ainda nao chegou a config: nao desenha (loading).
|
// ainda nao chegou a config: nao desenha (loading).
|
||||||
if (ResolveOverlay(EZeusAOIOverlay::AOIRadius) && InterestRadiusCm > 0.0f)
|
if (ResolveOverlay(EZeusAOIOverlay::AOIRadius) && InterestRadiusCm > 0.0f)
|
||||||
{
|
{
|
||||||
DrawDebugSphere(World, Loc, InterestRadiusCm, 32, kColorInterest, false, -1.0f, 0, LineThickness);
|
const float R = FMath::Min(InterestRadiusCm, kMaxOverlayRadiusCm);
|
||||||
|
DrawDebugSphere(World, Loc, R, kSphereSegments, kColorInterest, false, -1.0f, 0, LineThickness);
|
||||||
}
|
}
|
||||||
// Zona de Despawn (externa, > interesse = histerese) — raio real do servidor.
|
// Zona de Despawn (externa, > interesse = histerese) — raio real do servidor.
|
||||||
if (ResolveOverlay(EZeusAOIOverlay::DespawnZone) && DespawnRadiusCm > 0.0f)
|
if (ResolveOverlay(EZeusAOIOverlay::DespawnZone) && DespawnRadiusCm > 0.0f)
|
||||||
{
|
{
|
||||||
DrawDebugSphere(World, Loc, DespawnRadiusCm, 32, kColorDespawn, false, -1.0f, 0, LineThickness);
|
const float R = FMath::Min(DespawnRadiusCm, kMaxOverlayRadiusCm);
|
||||||
|
DrawDebugSphere(World, Loc, R, kSphereSegments, kColorDespawn, false, -1.0f, 0, LineThickness);
|
||||||
}
|
}
|
||||||
if (ResolveOverlay(EZeusAOIOverlay::CellBounds))
|
if (ResolveOverlay(EZeusAOIOverlay::CellBounds))
|
||||||
{
|
{
|
||||||
@@ -178,6 +301,7 @@ void UZeusAOIComponent::SetOverlayEnabled_Implementation(EZeusAOIOverlay Overlay
|
|||||||
case EZeusAOIOverlay::ProxyLabels: bShowProxyLabels = bEnabled; break;
|
case EZeusAOIOverlay::ProxyLabels: bShowProxyLabels = bEnabled; break;
|
||||||
case EZeusAOIOverlay::HandoffLine: bShowHandoffLine = bEnabled; break;
|
case EZeusAOIOverlay::HandoffLine: bShowHandoffLine = bEnabled; break;
|
||||||
case EZeusAOIOverlay::AuthDrift: bShowAuthDrift = bEnabled; break;
|
case EZeusAOIOverlay::AuthDrift: bShowAuthDrift = bEnabled; break;
|
||||||
|
case EZeusAOIOverlay::FrontierCells: bShowFrontier = bEnabled; break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,17 +311,27 @@ void UZeusAOIComponent::SetOverlayEnabled_Implementation(EZeusAOIOverlay Overlay
|
|||||||
{
|
{
|
||||||
RequestAoiConfig();
|
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)
|
void UZeusAOIComponent::SetAllOverlays_Implementation(bool bEnabled)
|
||||||
{
|
{
|
||||||
bShowAOIRadius = bShowDespawnZone = bShowCellBounds = bEnabled;
|
bShowAOIRadius = bShowDespawnZone = bShowCellBounds = bEnabled;
|
||||||
bShowProxyLabels = bShowHandoffLine = bShowAuthDrift = bEnabled;
|
bShowProxyLabels = bShowHandoffLine = bShowAuthDrift = bEnabled;
|
||||||
|
bShowFrontier = bEnabled;
|
||||||
|
|
||||||
if (bEnabled && InterestRadiusCm <= 0.0f)
|
if (bEnabled && InterestRadiusCm <= 0.0f)
|
||||||
{
|
{
|
||||||
RequestAoiConfig();
|
RequestAoiConfig();
|
||||||
}
|
}
|
||||||
|
if (bEnabled) { RequestFrontierConfig(); }
|
||||||
|
UpdateFrontierOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UZeusAOIComponent::IsOverlayEnabled_Implementation(EZeusAOIOverlay Overlay) const
|
bool UZeusAOIComponent::IsOverlayEnabled_Implementation(EZeusAOIOverlay Overlay) const
|
||||||
@@ -219,5 +353,67 @@ float UZeusAOIComponent::GetDespawnRadiusCm_Implementation() const
|
|||||||
int32 UZeusAOIComponent::GetEnabledOverlayCount() const
|
int32 UZeusAOIComponent::GetEnabledOverlayCount() const
|
||||||
{
|
{
|
||||||
return (bShowAOIRadius ? 1 : 0) + (bShowDespawnZone ? 1 : 0) + (bShowCellBounds ? 1 : 0)
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,20 @@
|
|||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "Components/ActorComponent.h"
|
#include "Components/ActorComponent.h"
|
||||||
#include "ZeusAOIDebugTarget.h" // plugin ZeusAdminToolsRuntime: interface + EZeusAOIOverlay
|
#include "ZeusAOIDebugTarget.h" // plugin ZeusAdminToolsRuntime: interface + EZeusAOIOverlay
|
||||||
|
#include "ZeusV1Protocol.h" // ZeusV1::FDebugFrontierInfo (overlay de fronteira)
|
||||||
#include "ZeusAOIComponent.generated.h"
|
#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)
|
* 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 bShowProxyLabels = false;
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|AOI Debug|Overlays") bool bShowHandoffLine = false;
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|AOI Debug|Overlays") bool bShowHandoffLine = false;
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|AOI Debug|Overlays") bool bShowAuthDrift = 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 ===
|
// === Parametros ===
|
||||||
// Raios reais vem do servidor (S_DEBUG_AOI_INFO). 0 = ainda nao recebido (loading).
|
// 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")
|
UFUNCTION(BlueprintPure, Category = "Zeus|AOI Debug")
|
||||||
int32 GetEnabledOverlayCount() const;
|
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:
|
private:
|
||||||
void DrawOverlays();
|
void DrawOverlays();
|
||||||
bool ResolveOverlay(EZeusAOIOverlay Overlay) const;
|
bool ResolveOverlay(EZeusAOIOverlay Overlay) const;
|
||||||
@@ -76,6 +107,43 @@ private:
|
|||||||
/** Bind de UZeusNetworkSubsystem::OnDebugAoiInfo: armazena os raios reais. */
|
/** Bind de UZeusNetworkSubsystem::OnDebugAoiInfo: armazena os raios reais. */
|
||||||
void HandleAoiConfig(float InterestCm, float DespawnCm);
|
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). */
|
/** Handle do bind do OnDebugAoiInfo (pra remover no EndPlay). */
|
||||||
FDelegateHandle AoiConfigHandle_;
|
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;
|
||||||
};
|
};
|
||||||
|
|||||||
218
Source/ZMMO/Game/Network/ZeusFrontierOverlayActor.cpp
Normal file
218
Source/ZMMO/Game/Network/ZeusFrontierOverlayActor.cpp
Normal 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();
|
||||||
|
}
|
||||||
70
Source/ZMMO/Game/Network/ZeusFrontierOverlayActor.h
Normal file
70
Source/ZMMO/Game/Network/ZeusFrontierOverlayActor.h
Normal 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();
|
||||||
|
};
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "ZeusPlayerProxy.h"
|
#include "ZeusPlayerProxy.h"
|
||||||
#include "ZeusPlayerState.h"
|
#include "ZeusPlayerState.h"
|
||||||
#include "ZeusNetworkSubsystem.h"
|
#include "ZeusNetworkSubsystem.h"
|
||||||
|
#include "ZeusNetworkingClientSubsystem.h"
|
||||||
|
|
||||||
void UZeusWorldSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
void UZeusWorldSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||||
{
|
{
|
||||||
@@ -32,45 +33,59 @@ void UZeusWorldSubsystem::OnWorldBeginPlay(UWorld& InWorld)
|
|||||||
{
|
{
|
||||||
Super::OnWorldBeginPlay(InWorld);
|
Super::OnWorldBeginPlay(InWorld);
|
||||||
|
|
||||||
if (UZeusNetworkSubsystem* ZeusNet = ResolveZeusNetworkSubsystem())
|
// Sistema de rede novo (canonico) -- substitui o ZeusNetworkSubsystem legacy
|
||||||
|
// para spawn/despawn/movimento de proxies. O legacy fica so' com o que ainda
|
||||||
|
// nao tem equivalente (CHAR_INFO/nome, tratado em AZeusCharacter).
|
||||||
|
if (UZeusNetworkingClientSubsystem* Net = ResolveNetClientSubsystem())
|
||||||
{
|
{
|
||||||
ZeusNet->OnPlayerSpawned.AddDynamic(this, &UZeusWorldSubsystem::HandlePlayerSpawned);
|
Net->OnEntitySpawned.AddDynamic(this, &UZeusWorldSubsystem::OnNetEntitySpawned);
|
||||||
ZeusNet->OnPlayerDespawned.AddDynamic(this, &UZeusWorldSubsystem::HandlePlayerDespawned);
|
Net->OnEntityDespawned.AddDynamic(this, &UZeusWorldSubsystem::OnNetEntityDespawned);
|
||||||
ZeusNet->OnPlayerStateUpdate.AddDynamic(this, &UZeusWorldSubsystem::HandlePlayerStateUpdate);
|
Net->OnEntityDeltaApplied.AddDynamic(this, &UZeusWorldSubsystem::OnNetEntityDelta);
|
||||||
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem bound to ZeusNetworkSubsystem delegates."));
|
Net->OnSelfEntityAssigned.AddDynamic(this, &UZeusWorldSubsystem::OnNetSelfEntityAssigned);
|
||||||
|
Net->OnCharInfo.AddDynamic(this, &UZeusWorldSubsystem::OnNetCharInfo);
|
||||||
|
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem bound to network client subsystem delegates."));
|
||||||
|
|
||||||
// Catch-up race fix (Fase 3): broadcasts de proxies remotos chegam
|
// O subsystem de rede e' per-GameInstance e sobrevive ao OpenLevel, entao
|
||||||
// enquanto o cliente novo ainda esta em OpenLevel. UZeusNetworkSubsystem
|
// o self-entity e os ENT_SPAWN podem ter chegado ANTES deste bind. Puxa o
|
||||||
// cacheia em PendingRemoteSpawnsByEntity. Aplicamos aqui — mundo ja
|
// self cacheado + faz replay dos proxies ja conhecidos (anti-race).
|
||||||
// esta pronto (GameMode ativo, WorldPartition cells inicializadas).
|
if (const int64 CachedSelf = Net->GetLocalEntityId())
|
||||||
|
{
|
||||||
|
OnNetSelfEntityAssigned(CachedSelf);
|
||||||
|
}
|
||||||
int32 ReplayCount = 0;
|
int32 ReplayCount = 0;
|
||||||
ZeusNet->ForEachPendingRemoteSpawn(
|
Net->ForEachRemoteEntity(
|
||||||
[this, &ReplayCount](const int64 EntityId, const FVector PosCm, const float YawDeg, const int64 ServerTimeMs)
|
[this, &ReplayCount](int64 EntityId, FVector PosCm, float YawDeg)
|
||||||
{
|
{
|
||||||
HandlePlayerSpawned(EntityId, /*bIsLocal=*/false, PosCm, YawDeg, ServerTimeMs);
|
// Catch-up local: ForEachRemoteEntity nao expoe vel/serverTimeMs ->
|
||||||
|
// ZeroVector + grounded + ts=0 (seed nao roda; o 1o delta ancora o relogio).
|
||||||
|
OnNetEntitySpawned(EntityId, /*Kind=*/1 /*Player*/, PosCm, YawDeg,
|
||||||
|
FVector::ZeroVector, /*bGrounded=*/true, /*ServerTimeMs=*/0);
|
||||||
++ReplayCount;
|
++ReplayCount;
|
||||||
});
|
});
|
||||||
if (ReplayCount > 0)
|
if (ReplayCount > 0)
|
||||||
{
|
{
|
||||||
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: replay de %d proxy(ies) cacheados."), ReplayCount);
|
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: replay de %d proxy(ies) ja conhecidos."), ReplayCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UE_LOG(LogZMMO, Warning, TEXT("ZeusWorldSubsystem: ZeusNetworkSubsystem not found at OnWorldBeginPlay."));
|
UE_LOG(LogZMMO, Warning, TEXT("ZeusWorldSubsystem: network client subsystem not found at OnWorldBeginPlay."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UZeusWorldSubsystem::Deinitialize()
|
void UZeusWorldSubsystem::Deinitialize()
|
||||||
{
|
{
|
||||||
if (UZeusNetworkSubsystem* ZeusNet = ResolveZeusNetworkSubsystem())
|
if (UZeusNetworkingClientSubsystem* Net = ResolveNetClientSubsystem())
|
||||||
{
|
{
|
||||||
ZeusNet->OnPlayerSpawned.RemoveDynamic(this, &UZeusWorldSubsystem::HandlePlayerSpawned);
|
Net->OnEntitySpawned.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetEntitySpawned);
|
||||||
ZeusNet->OnPlayerDespawned.RemoveDynamic(this, &UZeusWorldSubsystem::HandlePlayerDespawned);
|
Net->OnEntityDespawned.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetEntityDespawned);
|
||||||
ZeusNet->OnPlayerStateUpdate.RemoveDynamic(this, &UZeusWorldSubsystem::HandlePlayerStateUpdate);
|
Net->OnEntityDeltaApplied.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetEntityDelta);
|
||||||
|
Net->OnSelfEntityAssigned.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetSelfEntityAssigned);
|
||||||
|
Net->OnCharInfo.RemoveDynamic(this, &UZeusWorldSubsystem::OnNetCharInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoteEntities.Reset();
|
RemoteEntities.Reset();
|
||||||
|
PendingCharInfo.Reset();
|
||||||
LocalEntityId = 0;
|
LocalEntityId = 0;
|
||||||
Super::Deinitialize();
|
Super::Deinitialize();
|
||||||
}
|
}
|
||||||
@@ -83,7 +98,29 @@ void UZeusWorldSubsystem::RegisterLocalEntity(const int64 EntityId, AActor* Loca
|
|||||||
}
|
}
|
||||||
|
|
||||||
LocalEntityId = EntityId;
|
LocalEntityId = EntityId;
|
||||||
|
|
||||||
|
// Caso de borda: se o ENT_SPAWN do proprio chegou antes do self-entity, ja
|
||||||
|
// existe um proxy-fantasma com esta chave. Destroi antes de registrar o pawn
|
||||||
|
// real, senao o RemoteEntities.Add sobrescreve a entry e o fantasma fica
|
||||||
|
// orfao no mundo (sem ninguem pra despawna-lo).
|
||||||
|
if (TWeakObjectPtr<AActor>* Existing = RemoteEntities.Find(EntityId))
|
||||||
|
{
|
||||||
|
if (AActor* Ghost = Existing->Get())
|
||||||
|
{
|
||||||
|
if (Ghost != LocalActor && Ghost->IsA(AZeusPlayerProxy::StaticClass()))
|
||||||
|
{
|
||||||
|
HandlePlayerDespawned(EntityId);
|
||||||
|
UE_LOG(LogZMMO, Log,
|
||||||
|
TEXT("ZeusWorldSubsystem: ghost proxy do proprio limpo no RegisterLocalEntity EntityId=%lld"),
|
||||||
|
EntityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RemoteEntities.Add(EntityId, TWeakObjectPtr<AActor>(LocalActor));
|
RemoteEntities.Add(EntityId, TWeakObjectPtr<AActor>(LocalActor));
|
||||||
|
// V1-CHARINFO: o nome do proprio char pode ter chegado antes do pawn local
|
||||||
|
// existir (CHAR_INFO vem logo apos ENT_SELF); aplica agora.
|
||||||
|
FlushPendingCharInfo(EntityId);
|
||||||
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: local entity registered EntityId=%lld actor=%s"),
|
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: local entity registered EntityId=%lld actor=%s"),
|
||||||
EntityId, *GetNameSafe(LocalActor));
|
EntityId, *GetNameSafe(LocalActor));
|
||||||
}
|
}
|
||||||
@@ -96,6 +133,21 @@ void UZeusWorldSubsystem::HandlePlayerSpawned(const int64 EntityId, const bool b
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// P10-FIX-LEGACY (workflow wcyeqgrad finding): server pode reenviar
|
||||||
|
// S_SPAWN_PLAYER com bIsLocal=false do PROPRIO entityId via AOI re-emit
|
||||||
|
// (catch-up cross-server, SetOnEnter callback recomputando InterestSet,
|
||||||
|
// handoff em flight). Sem guard, o segundo spawn cria proxy fantasma do
|
||||||
|
// proprio player; GAS/HUD passa a rotear via proxy fantasma e o pawn real
|
||||||
|
// perde vinculo. Sintoma observado: player ve 2 pawns sobrepostos, ou
|
||||||
|
// outros players nao veem o autoritativo.
|
||||||
|
if (LocalEntityId != 0 && EntityId == LocalEntityId)
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Verbose,
|
||||||
|
TEXT("ZeusWorldSubsystem: ignoring S_SPAWN_PLAYER for own LocalEntityId=%lld (bIsLocal=%d)"),
|
||||||
|
LocalEntityId, bIsLocal ? 1 : 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (bIsLocal)
|
if (bIsLocal)
|
||||||
{
|
{
|
||||||
// O proprio AZeusCharacter cuida da sua identidade quando recebe o
|
// O proprio AZeusCharacter cuida da sua identidade quando recebe o
|
||||||
@@ -212,10 +264,63 @@ void UZeusWorldSubsystem::HandlePlayerSpawned(const int64 EntityId, const bool b
|
|||||||
}
|
}
|
||||||
|
|
||||||
RemoteEntities.Add(EntityId, TWeakObjectPtr<AActor>(SpawnedActor));
|
RemoteEntities.Add(EntityId, TWeakObjectPtr<AActor>(SpawnedActor));
|
||||||
|
// V1-CHARINFO: o nome chega ANTES do ator (server emite ENT_CHAR_INFO antes
|
||||||
|
// do ENT_SPAWN); aplica agora que o proxy + PlayerState ja existem.
|
||||||
|
FlushPendingCharInfo(EntityId);
|
||||||
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: spawned remote EntityId=%d at (%s) yaw=%.1f t=%lld"),
|
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: spawned remote EntityId=%d at (%s) yaw=%.1f t=%lld"),
|
||||||
EntityId, *PosCm.ToString(), YawDeg, ServerTimeMs);
|
EntityId, *PosCm.ToString(), YawDeg, ServerTimeMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UZeusWorldSubsystem::OnNetCharInfo(const int64 EntityId, const FString& CharName, const FString& GuildName)
|
||||||
|
{
|
||||||
|
if (EntityId == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Aplica direto se o ator/PlayerState ja existe; senao guarda pra aplicar no spawn.
|
||||||
|
if (!ApplyCharInfoToEntity(EntityId, CharName, GuildName))
|
||||||
|
{
|
||||||
|
PendingCharInfo.Add(EntityId, FZeusPendingCharInfo{ CharName, GuildName });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UZeusWorldSubsystem::ApplyCharInfoToEntity(const int64 EntityId, const FString& CharName, const FString& GuildName)
|
||||||
|
{
|
||||||
|
const TWeakObjectPtr<AActor>* Found = RemoteEntities.Find(EntityId);
|
||||||
|
if (!Found || !Found->IsValid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
APawn* Pawn = Cast<APawn>(Found->Get());
|
||||||
|
if (!Pawn)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
AZeusPlayerState* PS = Pawn->GetPlayerState<AZeusPlayerState>();
|
||||||
|
if (!PS)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PS->SetCharInfo(CharName, GuildName);
|
||||||
|
UE_LOG(LogZMMO, Log,
|
||||||
|
TEXT("ZeusWorldSubsystem: CHAR_INFO aplicado EntityId=%lld name='%s' guild='%s'"),
|
||||||
|
EntityId, *CharName, *GuildName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZeusWorldSubsystem::FlushPendingCharInfo(const int64 EntityId)
|
||||||
|
{
|
||||||
|
const FZeusPendingCharInfo* Pending = PendingCharInfo.Find(EntityId);
|
||||||
|
if (!Pending)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ApplyCharInfoToEntity(EntityId, Pending->CharName, Pending->GuildName))
|
||||||
|
{
|
||||||
|
PendingCharInfo.Remove(EntityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void UZeusWorldSubsystem::HandlePlayerDespawned(const int64 EntityId)
|
void UZeusWorldSubsystem::HandlePlayerDespawned(const int64 EntityId)
|
||||||
{
|
{
|
||||||
const TWeakObjectPtr<AActor>* Entry = RemoteEntities.Find(EntityId);
|
const TWeakObjectPtr<AActor>* Entry = RemoteEntities.Find(EntityId);
|
||||||
@@ -348,6 +453,108 @@ void UZeusWorldSubsystem::HandlePlayerStateUpdate(const int64 EntityId, const in
|
|||||||
AsEntity->ApplyEntitySnapshot(Snapshot);
|
AsEntity->ApplyEntitySnapshot(Snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UZeusWorldSubsystem::OnNetEntitySpawned(int64 EntityId, int32 /*Kind*/, FVector PosCm, float YawDeg,
|
||||||
|
FVector VelCmS, bool bGrounded, int64 ServerTimeMs)
|
||||||
|
{
|
||||||
|
// O sistema novo nao manda bIsLocal: derivamos do LocalEntityId (setado por
|
||||||
|
// ENT_SELF). Se ainda for 0 e este ENT_SPAWN for do proprio char, o
|
||||||
|
// OnNetSelfEntityAssigned destruira o fantasma quando o ENT_SELF chegar.
|
||||||
|
const bool bIsLocal = (LocalEntityId != 0 && EntityId == LocalEntityId);
|
||||||
|
HandlePlayerSpawned(EntityId, bIsLocal, PosCm, YawDeg, ServerTimeMs);
|
||||||
|
|
||||||
|
// V1-SPAWN-KEYFRAME(-TS): o ENT_SPAWN carrega vel + grounded + serverTimeMs. Aplica
|
||||||
|
// esse keyframe INICIAL no proxy recem-criado pelo mesmo caminho do delta (semeia o
|
||||||
|
// SnapshotBuffer + ANCORA o ServerClockOffsetMs com o tempo CERTO). Antes o seed usava
|
||||||
|
// ServerTimeMs=0 -> bootstrap do relogio de interpolacao errado -> proxy preso/flutuando
|
||||||
|
// ate' o EMA convergir. Agora com o serverTimeMs real o proxy interpola desde o frame 1.
|
||||||
|
if (!bIsLocal && ServerTimeMs > 0)
|
||||||
|
{
|
||||||
|
HandlePlayerStateUpdate(EntityId, /*InputSeq=*/0, PosCm, VelCmS, bGrounded, ServerTimeMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZeusWorldSubsystem::OnNetEntityDespawned(int64 EntityId, int32 /*Reason*/)
|
||||||
|
{
|
||||||
|
// NUNCA despawnar o proprio char local via rede (ex: ENT_DESPAWN do proprio
|
||||||
|
// disparado por timeout do server). Destruir o pawn local quebraria o jogo
|
||||||
|
// do dono. O proprio so' sai quando o cliente realmente desconecta.
|
||||||
|
if (LocalEntityId != 0 && EntityId == LocalEntityId)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HandlePlayerDespawned(EntityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZeusWorldSubsystem::OnNetEntityDelta(int64 EntityId, FVector PosCm, FVector VelCmS,
|
||||||
|
float YawDeg, bool bGrounded, int64 ServerTimeMs)
|
||||||
|
{
|
||||||
|
if (EntityId == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (LocalEntityId != 0 && EntityId == LocalEntityId)
|
||||||
|
{
|
||||||
|
return; // movimento do proprio char e' local; ignora snapshot autoritativo
|
||||||
|
}
|
||||||
|
|
||||||
|
const TWeakObjectPtr<AActor>* Entry = RemoteEntities.Find(EntityId);
|
||||||
|
if (!Entry)
|
||||||
|
{
|
||||||
|
return; // spawn chega em breve via ENT_SPAWN
|
||||||
|
}
|
||||||
|
AActor* Actor = Entry->Get();
|
||||||
|
if (!Actor)
|
||||||
|
{
|
||||||
|
RemoteEntities.Remove(EntityId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IZeusEntityInterface* AsEntity = Cast<IZeusEntityInterface>(Actor);
|
||||||
|
if (!AsEntity)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FZeusEntitySnapshot Snapshot;
|
||||||
|
Snapshot.EntityId = EntityId;
|
||||||
|
Snapshot.EntityType = AsEntity->GetZeusEntityType();
|
||||||
|
Snapshot.PositionCm = PosCm;
|
||||||
|
Snapshot.YawDeg = YawDeg;
|
||||||
|
// V1-REPL-FULL (2026-06-12): velocidade + grounded + serverTimeMs -> o proxy
|
||||||
|
// anima (AnimBP ShouldMove via Acceleration!=0 derivada da vel), cola no chao
|
||||||
|
// (MOVE_Walking quando grounded) e interpola com a timeline autoritativa.
|
||||||
|
Snapshot.VelocityCmS = VelCmS;
|
||||||
|
Snapshot.bGrounded = bGrounded;
|
||||||
|
Snapshot.ServerTimeMs = ServerTimeMs;
|
||||||
|
AsEntity->ApplyEntitySnapshot(Snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZeusWorldSubsystem::OnNetSelfEntityAssigned(int64 EntityId)
|
||||||
|
{
|
||||||
|
if (EntityId == 0 || LocalEntityId == EntityId)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LocalEntityId = EntityId;
|
||||||
|
UE_LOG(LogZMMO, Log, TEXT("ZeusWorldSubsystem: self entity assigned EntityId=%lld"), EntityId);
|
||||||
|
|
||||||
|
// Re-filtragem tardia: se o ENT_SPAWN do proprio chegou ANTES do ENT_SELF,
|
||||||
|
// ja existe um proxy-fantasma do proprio char. Destroi agora -- mas NUNCA o
|
||||||
|
// pawn local real (o RegisterLocalEntity do AZeusCharacter registra o pawn
|
||||||
|
// com a mesma chave; so destruimos se a entry for um AZeusPlayerProxy).
|
||||||
|
if (TWeakObjectPtr<AActor>* Entry = RemoteEntities.Find(EntityId))
|
||||||
|
{
|
||||||
|
if (AActor* Ghost = Entry->Get())
|
||||||
|
{
|
||||||
|
if (Ghost->IsA(AZeusPlayerProxy::StaticClass()))
|
||||||
|
{
|
||||||
|
HandlePlayerDespawned(EntityId);
|
||||||
|
UE_LOG(LogZMMO, Log,
|
||||||
|
TEXT("ZeusWorldSubsystem: ghost proxy do proprio destruido EntityId=%lld"), EntityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UClass* UZeusWorldSubsystem::ResolveActorClass(const EZeusEntityType EntityType) const
|
UClass* UZeusWorldSubsystem::ResolveActorClass(const EZeusEntityType EntityType) const
|
||||||
{
|
{
|
||||||
if (const TSubclassOf<AActor>* Found = RemoteEntityClasses.Find(EntityType))
|
if (const TSubclassOf<AActor>* Found = RemoteEntityClasses.Find(EntityType))
|
||||||
@@ -374,3 +581,18 @@ UZeusNetworkSubsystem* UZeusWorldSubsystem::ResolveZeusNetworkSubsystem() const
|
|||||||
}
|
}
|
||||||
return GI->GetSubsystem<UZeusNetworkSubsystem>();
|
return GI->GetSubsystem<UZeusNetworkSubsystem>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UZeusNetworkingClientSubsystem* UZeusWorldSubsystem::ResolveNetClientSubsystem() const
|
||||||
|
{
|
||||||
|
const UWorld* World = GetWorld();
|
||||||
|
if (!World)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
UGameInstance* GI = World->GetGameInstance();
|
||||||
|
if (!GI)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return GI->GetSubsystem<UZeusNetworkingClientSubsystem>();
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class AActor;
|
|||||||
class AZeusCharacter;
|
class AZeusCharacter;
|
||||||
class AZeusPlayerProxy;
|
class AZeusPlayerProxy;
|
||||||
class UZeusNetworkSubsystem;
|
class UZeusNetworkSubsystem;
|
||||||
|
class UZeusNetworkingClientSubsystem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UZeusWorldSubsystem
|
* UZeusWorldSubsystem
|
||||||
@@ -80,8 +81,39 @@ private:
|
|||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandlePlayerStateUpdate(int64 EntityId, int32 InputSeq, FVector PosCm, FVector VelCmS, bool bGrounded, int64 ServerTimeMs);
|
void HandlePlayerStateUpdate(int64 EntityId, int32 InputSeq, FVector PosCm, FVector VelCmS, bool bGrounded, int64 ServerTimeMs);
|
||||||
|
|
||||||
|
// Sistema de rede novo (canonico) -- recebe spawn/despawn/delta + self-entity.
|
||||||
|
// Adaptam a assinatura nova para os handlers de spawn/despawn/delta acima.
|
||||||
|
UFUNCTION()
|
||||||
|
void OnNetEntitySpawned(int64 EntityId, int32 Kind, FVector PosCm, float YawDeg,
|
||||||
|
FVector VelCmS, bool bGrounded, int64 ServerTimeMs);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnNetEntityDespawned(int64 EntityId, int32 Reason);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnNetEntityDelta(int64 EntityId, FVector PosCm, FVector VelCmS, float YawDeg,
|
||||||
|
bool bGrounded, int64 ServerTimeMs);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnNetSelfEntityAssigned(int64 EntityId);
|
||||||
|
|
||||||
|
// V1-CHARINFO: nome/guild do char (opcode ENT_CHAR_INFO 6045). Chega ANTES
|
||||||
|
// do ENT_SPAWN do proxy / do RegisterLocalEntity do self, entao se o ator
|
||||||
|
// ainda nao existe o dado fica em PendingCharInfo e e' aplicado no spawn.
|
||||||
|
UFUNCTION()
|
||||||
|
void OnNetCharInfo(int64 EntityId, const FString& CharName, const FString& GuildName);
|
||||||
|
|
||||||
UClass* ResolveActorClass(EZeusEntityType EntityType) const;
|
UClass* ResolveActorClass(EZeusEntityType EntityType) const;
|
||||||
UZeusNetworkSubsystem* ResolveZeusNetworkSubsystem() const;
|
UZeusNetworkSubsystem* ResolveZeusNetworkSubsystem() const;
|
||||||
|
UZeusNetworkingClientSubsystem* ResolveNetClientSubsystem() const;
|
||||||
|
|
||||||
|
// Aplica nome/guild no AZeusPlayerState do EntityId (self ou proxy). Retorna
|
||||||
|
// false se o ator/PlayerState ainda nao existe (caller deve cachear).
|
||||||
|
bool ApplyCharInfoToEntity(int64 EntityId, const FString& CharName, const FString& GuildName);
|
||||||
|
|
||||||
|
// Aplica (e consome) o char info pendente de EntityId, se houver. Chamado
|
||||||
|
// quando o ator passa a existir (spawn de proxy / RegisterLocalEntity).
|
||||||
|
void FlushPendingCharInfo(int64 EntityId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registry de proxies remotos. Chave = `EntityId` autoritativo do servidor.
|
* Registry de proxies remotos. Chave = `EntityId` autoritativo do servidor.
|
||||||
@@ -93,4 +125,12 @@ private:
|
|||||||
|
|
||||||
/** Cached id do jogador local, para ignorar snapshots dele (cliente solto). */
|
/** Cached id do jogador local, para ignorar snapshots dele (cliente solto). */
|
||||||
int64 LocalEntityId = 0;
|
int64 LocalEntityId = 0;
|
||||||
|
|
||||||
|
/** V1-CHARINFO: nome/guild recebido antes do ator existir. Chave = EntityId. */
|
||||||
|
struct FZeusPendingCharInfo
|
||||||
|
{
|
||||||
|
FString CharName;
|
||||||
|
FString GuildName;
|
||||||
|
};
|
||||||
|
TMap<int64, FZeusPendingCharInfo> PendingCharInfo;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "ZeusGameInstance.h"
|
#include "ZeusGameInstance.h"
|
||||||
#include "ZeusThemeSubsystem.h"
|
#include "ZeusThemeSubsystem.h"
|
||||||
#include "ZeusNetworkSubsystem.h"
|
#include "ZeusNetworkSubsystem.h"
|
||||||
|
#include "ZeusNetworkingClientSubsystem.h" // V1 canonico (sinal de spawn local)
|
||||||
#include "ZeusCharServerSubsystem.h"
|
#include "ZeusCharServerSubsystem.h"
|
||||||
#include "CommonActivatableWidget.h"
|
#include "CommonActivatableWidget.h"
|
||||||
#include "Engine/DataTable.h"
|
#include "Engine/DataTable.h"
|
||||||
@@ -265,6 +266,12 @@ UZeusNetworkSubsystem* UUIFrontEndFlowSubsystem::GetZeusNetwork() const
|
|||||||
return GI ? GI->GetSubsystem<UZeusNetworkSubsystem>() : nullptr;
|
return GI ? GI->GetSubsystem<UZeusNetworkSubsystem>() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UZeusNetworkingClientSubsystem* UUIFrontEndFlowSubsystem::GetNetClient() const
|
||||||
|
{
|
||||||
|
const UGameInstance* GI = GetGameInstance();
|
||||||
|
return GI ? GI->GetSubsystem<UZeusNetworkingClientSubsystem>() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
UZeusCharServerSubsystem* UUIFrontEndFlowSubsystem::GetCharServer() const
|
UZeusCharServerSubsystem* UUIFrontEndFlowSubsystem::GetCharServer() const
|
||||||
{
|
{
|
||||||
const UGameInstance* GI = GetGameInstance();
|
const UGameInstance* GI = GetGameInstance();
|
||||||
@@ -314,11 +321,20 @@ void UUIFrontEndFlowSubsystem::BindNetwork()
|
|||||||
Char->OnConnectionFailed.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed);
|
Char->OnConnectionFailed.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed);
|
||||||
Char->OnDisconnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleCharDisconnected);
|
Char->OnDisconnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleCharDisconnected);
|
||||||
}
|
}
|
||||||
// UDP (world server) só interessa para o handoff de travel.
|
// UDP (world server): o travel (TRAVEL_TO_MAP) ainda passa pelo objeto legacy
|
||||||
|
// como PONTE (HandleTravelToMap V1 reusa OnServerTravelRequested) -- isso some
|
||||||
|
// na Fase D quando o travel ganhar delegate proprio no V1.
|
||||||
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
||||||
{
|
{
|
||||||
Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
||||||
Zeus->OnPlayerSpawned.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned);
|
}
|
||||||
|
// V1 CANONICO: o spawn do player local chega via ENT_SELF (o cliente aprende
|
||||||
|
// o proprio entityId) -> OnSelfEntityAssigned. O legacy OnPlayerSpawned NAO
|
||||||
|
// dispara mais com V1, entao a etapa "Spawn" do loading ficava eterna
|
||||||
|
// (loading travado). Liga a etapa ao sinal V1.
|
||||||
|
if (UZeusNetworkingClientSubsystem* Net = GetNetClient())
|
||||||
|
{
|
||||||
|
Net->OnSelfEntityAssigned.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleV1LocalSpawned);
|
||||||
}
|
}
|
||||||
bNetBound = true;
|
bNetBound = true;
|
||||||
}
|
}
|
||||||
@@ -338,7 +354,10 @@ void UUIFrontEndFlowSubsystem::UnbindNetwork()
|
|||||||
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
||||||
{
|
{
|
||||||
Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
||||||
Zeus->OnPlayerSpawned.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned);
|
}
|
||||||
|
if (UZeusNetworkingClientSubsystem* Net = GetNetClient())
|
||||||
|
{
|
||||||
|
Net->OnSelfEntityAssigned.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleV1LocalSpawned);
|
||||||
}
|
}
|
||||||
bNetBound = false;
|
bNetBound = false;
|
||||||
}
|
}
|
||||||
@@ -505,6 +524,13 @@ void UUIFrontEndFlowSubsystem::HandlePlayerSpawned(int64 /*EntityId*/, bool bIsL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UUIFrontEndFlowSubsystem::HandleV1LocalSpawned(int64 EntityId)
|
||||||
|
{
|
||||||
|
// ENT_SELF e' SEMPRE o proprio char (o cliente aprendendo seu entityId), entao
|
||||||
|
// bIsLocal=true. Reusa a logica de marcar a etapa "Spawn" + memoizacao anti-race.
|
||||||
|
HandlePlayerSpawned(EntityId, /*bIsLocal=*/true, FVector::ZeroVector, 0.0f, 0);
|
||||||
|
}
|
||||||
|
|
||||||
void UUIFrontEndFlowSubsystem::HandleLoadingComplete()
|
void UUIFrontEndFlowSubsystem::HandleLoadingComplete()
|
||||||
{
|
{
|
||||||
if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
|
if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class UUIManagerSubsystem;
|
|||||||
class UUILoadingScreen_Base;
|
class UUILoadingScreen_Base;
|
||||||
class UZeusLoadingProfilesDataAsset;
|
class UZeusLoadingProfilesDataAsset;
|
||||||
class UZeusNetworkSubsystem;
|
class UZeusNetworkSubsystem;
|
||||||
|
class UZeusNetworkingClientSubsystem;
|
||||||
class UZeusCharServerSubsystem;
|
class UZeusCharServerSubsystem;
|
||||||
struct FZeusMapDef;
|
struct FZeusMapDef;
|
||||||
|
|
||||||
@@ -171,6 +172,7 @@ private:
|
|||||||
void BindNetwork();
|
void BindNetwork();
|
||||||
void UnbindNetwork();
|
void UnbindNetwork();
|
||||||
UZeusNetworkSubsystem* GetZeusNetwork() const;
|
UZeusNetworkSubsystem* GetZeusNetwork() const;
|
||||||
|
UZeusNetworkingClientSubsystem* GetNetClient() const; // V1 canonico
|
||||||
UZeusCharServerSubsystem* GetCharServer() const;
|
UZeusCharServerSubsystem* GetCharServer() const;
|
||||||
UUIManagerSubsystem* GetUIManager() const;
|
UUIManagerSubsystem* GetUIManager() const;
|
||||||
UUIFrontEndScreenSet* GetScreenSet();
|
UUIFrontEndScreenSet* GetScreenSet();
|
||||||
@@ -198,6 +200,15 @@ private:
|
|||||||
void HandlePlayerSpawned(int64 EntityId, bool bIsLocal, FVector PosCm,
|
void HandlePlayerSpawned(int64 EntityId, bool bIsLocal, FVector PosCm,
|
||||||
float YawDeg, int64 ServerTimeMs);
|
float YawDeg, int64 ServerTimeMs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V1 canonico: o spawn do player local chega via ENT_SELF (o cliente
|
||||||
|
* aprende o proprio entityId) -> OnSelfEntityAssigned. Marca a etapa
|
||||||
|
* "Spawn" do loading no fluxo V1 (o legacy OnPlayerSpawned nao dispara
|
||||||
|
* mais). Adapta a assinatura OneParam pra logica de HandlePlayerSpawned.
|
||||||
|
*/
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleV1LocalSpawned(int64 EntityId);
|
||||||
|
|
||||||
/** Disparado pela tela de loading quando todas as etapas viram Done. */
|
/** Disparado pela tela de loading quando todas as etapas viram Done. */
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleLoadingComplete();
|
void HandleLoadingComplete();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "WireHelpers.h"
|
#include "WireHelpers.h"
|
||||||
#include "ZeusCharServerSubsystem.h"
|
#include "ZeusCharServerSubsystem.h"
|
||||||
#include "ZeusNetworkSubsystem.h"
|
#include "ZeusNetworkSubsystem.h"
|
||||||
|
#include "ZeusNetworkingClientSubsystem.h" // B4: V1 ConnectWithTicket
|
||||||
#include "CommonTextBlock.h"
|
#include "CommonTextBlock.h"
|
||||||
#include "Components/PanelWidget.h"
|
#include "Components/PanelWidget.h"
|
||||||
#include "Components/WidgetSwitcher.h"
|
#include "Components/WidgetSwitcher.h"
|
||||||
@@ -384,13 +385,29 @@ void UUIUserLobbyScreen_Base::HandleCharSelectOk(const TArray<uint8>& Payload)
|
|||||||
// `S_CONNECT_OK` (sucesso) ou `S_CONNECT_REJECT` (ticket invalido/expirado).
|
// `S_CONNECT_OK` (sucesso) ou `S_CONNECT_REJECT` (ticket invalido/expirado).
|
||||||
// Disparamos PRIMEIRO o connect (nao-bloqueante), depois o OpenLevel —
|
// Disparamos PRIMEIRO o connect (nao-bloqueante), depois o OpenLevel —
|
||||||
// ZeusNetworkSubsystem e per-GameInstance e persiste cross-travel.
|
// ZeusNetworkSubsystem e per-GameInstance e persiste cross-travel.
|
||||||
if (UZeusNetworkSubsystem* ZeusNet = GI->GetSubsystem<UZeusNetworkSubsystem>())
|
// B4 (2026-06-11): se V1 canonico, roteia ConnectWithTicket pro V1 subsystem.
|
||||||
|
// V1 envia CONN_HELLO_CLIENT (6000) com handoffTicket embutido — server
|
||||||
|
// valida via Valkey GETDEL antes do CHALLENGE (HandshakeProcessor::OnHelloClient).
|
||||||
|
const UZeusNetworkingClientSubsystem* V1Cdo = GetDefault<UZeusNetworkingClientSubsystem>();
|
||||||
|
const bool bUseV1 = V1Cdo && V1Cdo->bUseZeusNetworkingV1;
|
||||||
|
if (bUseV1)
|
||||||
|
{
|
||||||
|
if (UZeusNetworkingClientSubsystem* V1 = GI->GetSubsystem<UZeusNetworkingClientSubsystem>())
|
||||||
|
{
|
||||||
|
V1->ConnectWithTicket(GwHost, GwPort, HandoffToken);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Error, TEXT("Lobby: V1 subsystem ausente — handoff abortado"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (UZeusNetworkSubsystem* ZeusNet = GI->GetSubsystem<UZeusNetworkSubsystem>())
|
||||||
{
|
{
|
||||||
ZeusNet->ConnectToZeusServerWithTicket(GwHost, GwPort, HandoffToken);
|
ZeusNet->ConnectToZeusServerWithTicket(GwHost, GwPort, HandoffToken);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UE_LOG(LogZMMO, Error, TEXT("Lobby: ZeusNetworkSubsystem ausente — handoff abortado"));
|
UE_LOG(LogZMMO, Error, TEXT("Lobby: nenhum ZeusNetworkSubsystem disponivel — handoff abortado"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fase 4: lookup mapId no DT_Maps e dispara OpenLevel pro level certo.
|
// Fase 4: lookup mapId no DT_Maps e dispara OpenLevel pro level certo.
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public class ZMMO : ModuleRules
|
|||||||
"CommonInput",
|
"CommonInput",
|
||||||
"GameplayTags",
|
"GameplayTags",
|
||||||
"GameplayAbilities", // GAS: UAttributeSet, FGameplayAttributeData
|
"GameplayAbilities", // GAS: UAttributeSet, FGameplayAttributeData
|
||||||
|
"ProceduralMeshComponent", // paredes do overlay de fronteira (AZeusFrontierOverlayActor)
|
||||||
"ZeusNetwork",
|
"ZeusNetwork",
|
||||||
"ZeusGAS", // UZeusGASComponent, UZeusAttributeSet, FZeusAttributesSnapshot
|
"ZeusGAS", // UZeusGASComponent, UZeusAttributeSet, FZeusAttributesSnapshot
|
||||||
"ZeusJobs",
|
"ZeusJobs",
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
#include "ZMMO.h"
|
#include "ZMMO.h"
|
||||||
|
#include "ZMMONetLog.h"
|
||||||
#include "Modules/ModuleManager.h"
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, ZMMO, "ZMMO" );
|
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, ZMMO, "ZMMO" );
|
||||||
|
|
||||||
DEFINE_LOG_CATEGORY(LogZMMO)
|
DEFINE_LOG_CATEGORY(LogZMMO)
|
||||||
|
|
||||||
|
// Batch 2.5 — categorias vivem no modulo ZMMO (nao no plugin) por causa
|
||||||
|
// do adaptive non-unity build do UBT (ver ZMMONetLog.h).
|
||||||
|
DEFINE_LOG_CATEGORY(LogZeusHandoff)
|
||||||
|
DEFINE_LOG_CATEGORY(LogZeusAOI)
|
||||||
|
DEFINE_LOG_CATEGORY(LogZeusProxy)
|
||||||
|
DEFINE_LOG_CATEGORY(LogZeusHaloTransition)
|
||||||
24
Source/ZMMO/ZMMONetLog.h
Normal file
24
Source/ZMMO/ZMMONetLog.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// ZMMONetLog.h — categorias UE_LOG do CLIENTE ZMMO (modulo Game), nao do plugin.
|
||||||
|
//
|
||||||
|
// Por que aqui e nao no plugin ZeusNetwork?
|
||||||
|
// UBT usa `git status` em Clients/ZMMO/ pra adaptive non-unity build. O plugin
|
||||||
|
// ZeusNetwork mora em Server/Plugins/Unreal/ZeusNetwork/ (OUTRO repo git),
|
||||||
|
// entao UBT nao enxerga mudancas la e pula o rebuild do plugin. Resultado:
|
||||||
|
// adicionar DECLARE_LOG_CATEGORY_EXTERN no header do plugin nao funciona
|
||||||
|
// sem rebuild manual do plugin (Build.bat). Solucao: categorias que vivem
|
||||||
|
// no proprio CLIENTE moram aqui — UBT enxerga e linka direto.
|
||||||
|
//
|
||||||
|
// Setup -logcmds="LogZeusHandoff Verbose, LogZeusAOI Verbose, LogZeusProxy Verbose"
|
||||||
|
// pra filtrar Output Log seletivamente.
|
||||||
|
//
|
||||||
|
// DEFINE_LOG_CATEGORY correspondente em ZMMO.cpp.
|
||||||
|
// =============================================================================
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
|
||||||
|
DECLARE_LOG_CATEGORY_EXTERN(LogZeusHandoff, Log, All);
|
||||||
|
DECLARE_LOG_CATEGORY_EXTERN(LogZeusAOI, Log, All);
|
||||||
|
DECLARE_LOG_CATEGORY_EXTERN(LogZeusProxy, Verbose, All);
|
||||||
|
DECLARE_LOG_CATEGORY_EXTERN(LogZeusHaloTransition, Log, All);
|
||||||
@@ -76,6 +76,10 @@
|
|||||||
{
|
{
|
||||||
"Name": "CommonUI",
|
"Name": "CommonUI",
|
||||||
"Enabled": true
|
"Enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "GameplayAbilities",
|
||||||
|
"Enabled": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"AdditionalPluginDirectories": [
|
"AdditionalPluginDirectories": [
|
||||||
|
|||||||
Reference in New Issue
Block a user