feat: adicionar projeto ZClientMMO (Source, Content, Config, Plugins)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-07 01:33:28 -03:00
parent 3e4e9ac30d
commit ae4b954ab0
861 changed files with 10089 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class ZClientMMOTarget : TargetRules
{
public ZClientMMOTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V6;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
ExtraModuleNames.Add("ZClientMMO");
}
}

View File

@@ -0,0 +1,278 @@
#include "PlayerCharacter.h"
#include "EnhancedInputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "ZeusNetworkSubsystem.h"
#include "ZeusInputBridgeComponent.h"
#include "ZeusProxyMovementComponent.h"
#include "ZClientMMO.h"
#include "GameFramework/SpringArmComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Animation/AnimInstance.h"
#include "Misc/App.h"
#include "UObject/UnrealType.h"
APlayerCharacter::APlayerCharacter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UZeusProxyMovementComponent>(ACharacter::CharacterMovementComponentName))
{
PrimaryActorTick.bCanEverTick = true;
InputBridge = CreateDefaultSubobject<UZeusInputBridgeComponent>(TEXT("ZeusInputBridge"));
}
void APlayerCharacter::EnsureExpectedAnimBinding(const bool bForceReinit)
{
USkeletalMeshComponent* MeshComponent = GetMesh();
if (MeshComponent == nullptr)
{
return;
}
static const TCHAR* AnimClassPath =
TEXT("/Script/Engine.AnimBlueprintGeneratedClass'/Game/Characters/Mannequins/Anims/Unarmed/ABP_Unarmed.ABP_Unarmed_C'");
UClass* ExpectedAnimClass = StaticLoadClass(UAnimInstance::StaticClass(), nullptr, AnimClassPath);
if (ExpectedAnimClass == nullptr)
{
UE_LOG(LogZClientMMO, Error, TEXT("Anim binding failed: could not load AnimClass path=%s"), AnimClassPath);
return;
}
const bool bNeedsFix =
MeshComponent->GetAnimationMode() != EAnimationMode::AnimationBlueprint ||
MeshComponent->GetAnimClass() != ExpectedAnimClass ||
MeshComponent->GetAnimInstance() == nullptr;
if (!bNeedsFix && !bForceReinit)
{
RegisterMeshTickAfterInputBridge();
return;
}
MeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
MeshComponent->SetAnimInstanceClass(ExpectedAnimClass);
MeshComponent->InitAnim(true);
if (IsLocallyControlled() && !bAnimTickPrerequisiteAdded)
{
AddTickPrerequisiteComponent(MeshComponent);
bAnimTickPrerequisiteAdded = true;
}
UE_LOG(LogZClientMMO, Log, TEXT("Anim binding ok: mesh=%s mode=%d animClass=%s animInstance=%s"),
*GetNameSafe(MeshComponent->GetSkeletalMeshAsset()),
static_cast<int32>(MeshComponent->GetAnimationMode()),
*GetNameSafe(MeshComponent->GetAnimClass()),
*GetNameSafe(MeshComponent->GetAnimInstance()));
RegisterMeshTickAfterInputBridge();
}
void APlayerCharacter::RegisterMeshTickAfterInputBridge()
{
if (!IsLocallyControlled() || bMeshWaitsForBridgeTickPrerequisite)
{
return;
}
if (InputBridge == nullptr)
{
return;
}
USkeletalMeshComponent* MeshComponent = GetMesh();
if (MeshComponent == nullptr)
{
return;
}
MeshComponent->AddTickPrerequisiteComponent(InputBridge);
bMeshWaitsForBridgeTickPrerequisite = true;
UE_LOG(LogZClientMMO, Log, TEXT("Anim tick order: skeletal mesh waits for ZeusInputBridge (CMC refresh before AnimBP)."));
}
void APlayerCharacter::BeginPlay()
{
Super::BeginPlay();
EnsureExpectedAnimBinding(true);
const UZeusNetworkSubsystem* Zeus = UZeusNetworkSubsystem::GetZeusNetworkSubsystem(GetWorld());
const bool bIsConnectedToZeus = Zeus != nullptr && Zeus->IsConnected();
// Se estiver ligado ao servidor Zeus, mantemos caminho autoritativo por snapshot.
// Se nao estiver ligado, habilitamos fallback local para permitir locomocao offline.
if (UCharacterMovementComponent* CMC = GetCharacterMovement())
{
// Online: mantemos CMC ativo para o AnimBP ler Velocity/MovementMode vindos do snapshot.
CMC->SetMovementMode(MOVE_Walking);
}
if (USpringArmComponent* LocalCameraBoom = GetCameraBoom())
{
LocalCameraBoom->bEnableCameraLag = true;
LocalCameraBoom->CameraLagSpeed = bIsConnectedToZeus ? 14.0f : 18.0f;
LocalCameraBoom->CameraLagMaxDistance = bIsConnectedToZeus ? 70.0f : 40.0f;
}
if (IsLocallyControlled() && !bAnimTickPrerequisiteAdded)
{
if (USkeletalMeshComponent* MeshComponent = GetMesh())
{
AddTickPrerequisiteComponent(MeshComponent);
bAnimTickPrerequisiteAdded = true;
}
}
RegisterMeshTickAfterInputBridge();
}
void APlayerCharacter::Tick(const float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
if (!IsLocallyControlled())
{
return;
}
ApplyShouldMoveToAnimInstanceAfterMesh();
const double NowSeconds = FApp::GetCurrentTime();
if ((NowSeconds - LastAnimLifecycleAuditTimeSeconds) >= 1.0)
{
LastAnimLifecycleAuditTimeSeconds = NowSeconds;
EnsureExpectedAnimBinding(false);
}
}
void APlayerCharacter::ApplyShouldMoveToAnimInstanceAfterMesh()
{
USkeletalMeshComponent* MeshComponent = GetMesh();
if (MeshComponent == nullptr)
{
return;
}
UAnimInstance* AnimInstance = MeshComponent->GetAnimInstance();
if (AnimInstance == nullptr)
{
return;
}
const UZeusNetworkSubsystem* Zeus = UZeusNetworkSubsystem::GetZeusNetworkSubsystem(GetWorld());
const bool bIsConnectedToZeus = Zeus != nullptr && Zeus->IsConnected();
bool bIsFalling = false;
if (bIsConnectedToZeus && InputBridge != nullptr)
{
FVector AuthoritativeVel = FVector::ZeroVector;
FVector AuthoritativeAccel = FVector::ZeroVector;
EMovementMode AuthoritativeMode = MOVE_Walking;
if (InputBridge->TryGetAuthoritativeKinematics(AuthoritativeVel, AuthoritativeAccel, AuthoritativeMode))
{
bIsFalling = (AuthoritativeMode == MOVE_Falling);
}
else if (const UCharacterMovementComponent* CMC = GetCharacterMovement())
{
bIsFalling = CMC->IsFalling();
}
}
else if (const UCharacterMovementComponent* CMC = GetCharacterMovement())
{
bIsFalling = CMC->IsFalling();
}
const FVector VelForAnim = GetVelocity();
const float Speed2D = FVector2D(VelForAnim.X, VelForAnim.Y).Size();
static constexpr float ShouldMoveSpeedThresholdCmS = 15.0f;
const bool bShouldMove = (Speed2D > ShouldMoveSpeedThresholdCmS) && !bIsFalling;
FBoolProperty* BoolProp = CastField<FBoolProperty>(AnimInstance->GetClass()->FindPropertyByName(FName(TEXT("ShouldMove"))));
if (BoolProp == nullptr)
{
BoolProp = CastField<FBoolProperty>(AnimInstance->GetClass()->FindPropertyByName(FName(TEXT("bShouldMove"))));
}
if (BoolProp == nullptr)
{
return;
}
bool& bStored = *BoolProp->ContainerPtrToValuePtr<bool>(AnimInstance);
bStored = bShouldMove;
}
void APlayerCharacter::DoMove(const float Right, const float Forward)
{
if (InputBridge != nullptr)
{
InputBridge->SetMovementInput(Forward, Right);
}
const UZeusNetworkSubsystem* Zeus = UZeusNetworkSubsystem::GetZeusNetworkSubsystem(GetWorld());
const bool bIsConnectedToZeus = Zeus != nullptr && Zeus->IsConnected();
if (bIsConnectedToZeus)
{
// Online: input vai para o servidor; movimento local fica desativado.
return;
}
// Offline: usa o CharacterMovement normal do template.
if (UCharacterMovementComponent* CMC = GetCharacterMovement())
{
if (CMC->MovementMode == MOVE_None)
{
CMC->SetMovementMode(MOVE_Walking);
}
}
Super::DoMove(Right, Forward);
}
void APlayerCharacter::DoJumpStart()
{
if (InputBridge != nullptr)
{
InputBridge->RegisterJumpPressed();
}
const UZeusNetworkSubsystem* Zeus = UZeusNetworkSubsystem::GetZeusNetworkSubsystem(GetWorld());
const bool bIsConnectedToZeus = Zeus != nullptr && Zeus->IsConnected();
if (!bIsConnectedToZeus)
{
Super::DoJumpStart();
}
}
void APlayerCharacter::DoJumpEnd()
{
if (InputBridge != nullptr)
{
InputBridge->RegisterJumpReleased();
}
const UZeusNetworkSubsystem* Zeus = UZeusNetworkSubsystem::GetZeusNetworkSubsystem(GetWorld());
const bool bIsConnectedToZeus = Zeus != nullptr && Zeus->IsConnected();
if (!bIsConnectedToZeus)
{
Super::DoJumpEnd();
}
}
FVector APlayerCharacter::GetVelocity() const
{
const UZeusNetworkSubsystem* Zeus = UZeusNetworkSubsystem::GetZeusNetworkSubsystem(GetWorld());
const bool bIsConnectedToZeus = Zeus != nullptr && Zeus->IsConnected();
if (bIsConnectedToZeus && InputBridge != nullptr)
{
FVector AuthoritativeVelocity = FVector::ZeroVector;
FVector AuthoritativeAcceleration = FVector::ZeroVector;
EMovementMode AuthoritativeMode = MOVE_Walking;
if (InputBridge->TryGetAuthoritativeKinematics(
AuthoritativeVelocity,
AuthoritativeAcceleration,
AuthoritativeMode))
{
return AuthoritativeVelocity;
}
}
return Super::GetVelocity();
}
void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// O template liga o JumpAction a ACharacter::Jump/StopJumping. Aqui
// adicionamos handlers para tambem registar press/release no InputBridge.
if (UEnhancedInputComponent* EIC = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
EIC->BindAction(JumpAction, ETriggerEvent::Started, this, &APlayerCharacter::DoJumpStart);
EIC->BindAction(JumpAction, ETriggerEvent::Completed, this, &APlayerCharacter::DoJumpEnd);
}
}

View File

@@ -0,0 +1,51 @@
#pragma once
#include "CoreMinimal.h"
#include "ZClientMMOCharacter.h"
#include "PlayerCharacter.generated.h"
class UZeusInputBridgeComponent;
/**
* APlayerCharacter — pawn local controlavel pelo cliente Zeus.
*
* Evolui o template `AZClientMMOCharacter` (camera + spring arm + skeletal mesh)
* adicionando um `UZeusInputBridgeComponent` que envia C_INPUT_AXIS para o
* servidor e aplica os snapshots S_PLAYER_STATE.
*
* O servidor e' autoritativo: o `CharacterMovementComponent` local fica
* desactivado em pawns possessivos para evitar dupla simulacao. Movimento
* visual vem dos snapshots (futuras Partes 7+ adicionam predicao real).
*/
UCLASS(Blueprintable, BlueprintType)
class ZCLIENTMMO_API APlayerCharacter : public AZClientMMOCharacter
{
GENERATED_BODY()
public:
APlayerCharacter(const FObjectInitializer& ObjectInitializer);
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Zeus|Player")
UZeusInputBridgeComponent* InputBridge;
protected:
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
public:
virtual void DoMove(float Right, float Forward) override;
virtual void DoJumpStart() override;
virtual void DoJumpEnd() override;
virtual FVector GetVelocity() const override;
private:
double LastAnimLifecycleAuditTimeSeconds = 0.0;
bool bAnimTickPrerequisiteAdded = false;
bool bMeshWaitsForBridgeTickPrerequisite = false;
void EnsureExpectedAnimBinding(bool bForceReinit);
/** Garante tick do bridge antes do skeletal mesh para o AnimBP ler CMC ja refrescado pelo snapshot. */
void RegisterMeshTickAfterInputBridge();
/** ABP_Unarmed exige aceleracao != 0 para ShouldMove; online isso falha. Sobrescreve apos o tick do mesh. */
void ApplyShouldMoveToAnimInstanceAfterMesh();
};

View File

@@ -0,0 +1,344 @@
#include "ZeusInputBridgeComponent.h"
#include "PlayerCharacter.h"
#include "ZeusProxyMovementComponent.h"
#include "ZeusNetworkSubsystem.h"
#include "GameFramework/Controller.h"
#include "GameFramework/CharacterMovementComponent.h"
UZeusInputBridgeComponent::UZeusInputBridgeComponent()
{
PrimaryComponentTick.bCanEverTick = true;
}
void UZeusInputBridgeComponent::BeginPlay()
{
Super::BeginPlay();
if (UZeusNetworkSubsystem* Zeus = GetZeus())
{
Zeus->OnPlayerSpawned.AddDynamic(this, &UZeusInputBridgeComponent::HandlePlayerSpawned);
Zeus->OnPlayerStateUpdate.AddDynamic(this, &UZeusInputBridgeComponent::HandlePlayerStateUpdate);
Zeus->OnPlayerDespawned.AddDynamic(this, &UZeusInputBridgeComponent::HandlePlayerDespawned);
int32 CachedEntityId = 0;
FVector CachedPos = FVector::ZeroVector;
float CachedYaw = 0.0f;
int64 CachedSpawnServerTimeMs = 0;
if (Zeus->TryGetLastLocalSpawn(CachedEntityId, CachedPos, CachedYaw, CachedSpawnServerTimeMs))
{
HandlePlayerSpawned(CachedEntityId, true, CachedPos, CachedYaw, CachedSpawnServerTimeMs);
int32 CachedInputSeq = 0;
FVector CachedVel = FVector::ZeroVector;
bool bCachedGrounded = false;
int64 CachedStateServerTimeMs = 0;
if (Zeus->TryGetLastPlayerState(
CachedEntityId,
CachedInputSeq,
CachedPos,
CachedVel,
bCachedGrounded,
CachedStateServerTimeMs))
{
HandlePlayerStateUpdate(
CachedEntityId,
CachedInputSeq,
CachedPos,
CachedVel,
bCachedGrounded,
CachedStateServerTimeMs);
}
}
}
}
void UZeusInputBridgeComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (UZeusNetworkSubsystem* Zeus = GetZeus())
{
Zeus->OnPlayerSpawned.RemoveDynamic(this, &UZeusInputBridgeComponent::HandlePlayerSpawned);
Zeus->OnPlayerStateUpdate.RemoveDynamic(this, &UZeusInputBridgeComponent::HandlePlayerStateUpdate);
Zeus->OnPlayerDespawned.RemoveDynamic(this, &UZeusInputBridgeComponent::HandlePlayerDespawned);
}
Super::EndPlay(EndPlayReason);
}
UZeusNetworkSubsystem* UZeusInputBridgeComponent::GetZeus() const
{
return UZeusNetworkSubsystem::GetZeusNetworkSubsystem(GetWorld());
}
APlayerCharacter* UZeusInputBridgeComponent::GetOwnerCharacter() const
{
return Cast<APlayerCharacter>(GetOwner());
}
void UZeusInputBridgeComponent::RefreshAuthoritativeAnimationDrivers(APlayerCharacter* Owner) const
{
if (Owner == nullptr || !bHasSnapshotKinematics)
{
return;
}
UCharacterMovementComponent* CMC = Owner->GetCharacterMovement();
if (CMC == nullptr)
{
return;
}
CMC->Velocity = LastSnapshotVelocityCmS;
if (CMC->MovementMode != LastSnapshotMovementMode)
{
CMC->SetMovementMode(LastSnapshotMovementMode);
}
if (UZeusProxyMovementComponent* ProxyMovement = Cast<UZeusProxyMovementComponent>(CMC))
{
ProxyMovement->SetZeusExternalAcceleration(LastDerivedAccelerationCmS2);
}
}
bool UZeusInputBridgeComponent::TryGetAuthoritativeKinematics(
FVector& OutVelocityCmS,
FVector& OutDerivedAccelerationCmS2,
EMovementMode& OutMovementMode) const
{
if (!bHasSnapshotKinematics)
{
return false;
}
OutVelocityCmS = LastSnapshotVelocityCmS;
OutDerivedAccelerationCmS2 = LastDerivedAccelerationCmS2;
OutMovementMode = LastSnapshotMovementMode;
return true;
}
void UZeusInputBridgeComponent::SetMovementInput(const float Forward, const float Right)
{
CurrentForward = FMath::Clamp(Forward, -1.0f, 1.0f);
CurrentRight = FMath::Clamp(Right, -1.0f, 1.0f);
}
void UZeusInputBridgeComponent::RegisterJumpPressed()
{
bJumpPressedThisFrame = true;
}
void UZeusInputBridgeComponent::RegisterJumpReleased()
{
bJumpReleasedThisFrame = true;
}
void UZeusInputBridgeComponent::TickComponent(
const float DeltaTime,
const ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
UZeusNetworkSubsystem* Zeus = GetZeus();
if (Zeus == nullptr || !Zeus->IsConnected())
{
return;
}
if (APlayerCharacter* OwnerCharacter = GetOwnerCharacter())
{
// Proxy-style (V1): reescreve drivers por tick entre snapshots.
RefreshAuthoritativeAnimationDrivers(OwnerCharacter);
}
SendAccumulator += DeltaTime;
if (SendAccumulator < SendIntervalSeconds && !bJumpPressedThisFrame && !bJumpReleasedThisFrame)
{
return;
}
SendAccumulator = 0.0f;
const APlayerCharacter* Owner = GetOwnerCharacter();
float ForwardToSend = CurrentForward;
float RightToSend = CurrentRight;
// Se temos controlador, projetar o input no plano horizontal usando o yaw do controlador,
// para que o "forward" do servidor corresponda ao que o cliente ve em camera.
if (Owner != nullptr)
{
if (const AController* Ctrl = Owner->GetController())
{
const FRotator Rot = Ctrl->GetControlRotation();
const float YawRad = FMath::DegreesToRadians(Rot.Yaw);
const float Cy = FMath::Cos(YawRad);
const float Sy = FMath::Sin(YawRad);
// X = forward, Y = right (Zeus convention). UE usa X=forward,Y=right tambem.
const float OutF = ForwardToSend * Cy - RightToSend * Sy;
const float OutR = ForwardToSend * Sy + RightToSend * Cy;
ForwardToSend = OutF;
RightToSend = OutR;
const float LenSq = ForwardToSend * ForwardToSend + RightToSend * RightToSend;
if (LenSq > 1.0f)
{
const float InvLen = FMath::InvSqrt(LenSq);
ForwardToSend *= InvLen;
RightToSend *= InvLen;
}
}
}
const int32 ClientTimeMs = static_cast<int32>(
FMath::FloorToInt32(static_cast<float>(FPlatformTime::Seconds() * 1000.0)));
++InputSeq;
Zeus->SendInputAxis(
ForwardToSend,
RightToSend,
bJumpPressedThisFrame,
bJumpReleasedThisFrame,
InputSeq,
ClientTimeMs);
bJumpPressedThisFrame = false;
bJumpReleasedThisFrame = false;
}
void UZeusInputBridgeComponent::HandlePlayerSpawned(
const int32 EntityId,
const bool bIsLocal,
const FVector PosCm,
const float YawDeg,
const int64 ServerTimeMs)
{
(void)ServerTimeMs;
if (!bIsLocal)
{
return;
}
APlayerCharacter* Owner = GetOwnerCharacter();
if (Owner == nullptr)
{
return;
}
LocalEntityId = EntityId;
bSpawnedLocal = true;
LastAppliedServerInputSeq = -1;
bHasLastServerPos = false;
LastServerPosCm = PosCm;
bHasSnapshotKinematics = false;
LastSnapshotVelocityCmS = FVector::ZeroVector;
LastDerivedAccelerationCmS2 = FVector::ZeroVector;
LastSnapshotMovementMode = MOVE_Walking;
LastSnapshotServerTimeMs = ServerTimeMs;
Owner->SetActorLocation(PosCm, /*bSweep*/ false);
Owner->SetActorRotation(FRotator(0.0f, YawDeg, 0.0f));
CurrentForward = 0.0f;
CurrentRight = 0.0f;
bJumpPressedThisFrame = false;
bJumpReleasedThisFrame = false;
}
void UZeusInputBridgeComponent::HandlePlayerStateUpdate(
const int32 EntityId,
const int32 InInputSeq,
const FVector PosCm,
const FVector VelCmS,
const bool bGrounded,
const int64 ServerTimeMs)
{
if (!bSpawnedLocal || EntityId != LocalEntityId)
{
return;
}
APlayerCharacter* Owner = GetOwnerCharacter();
if (Owner == nullptr)
{
return;
}
if (InInputSeq > 0 && LastAppliedServerInputSeq >= InInputSeq)
{
return;
}
if (InInputSeq > 0)
{
LastAppliedServerInputSeq = InInputSeq;
}
const EMovementMode DesiredMode = bGrounded ? MOVE_Walking : MOVE_Falling;
const FVector PrevVelocity = LastSnapshotVelocityCmS;
const int64 PrevServerTimeMs = LastSnapshotServerTimeMs;
const double DeltaSnapshotSec = (bHasSnapshotKinematics && PrevServerTimeMs > 0)
? static_cast<double>(ServerTimeMs - PrevServerTimeMs) / 1000.0
: 0.0;
FVector DerivedAccel = FVector::ZeroVector;
if (DeltaSnapshotSec > KINDA_SMALL_NUMBER)
{
DerivedAccel = (VelCmS - PrevVelocity) / static_cast<float>(DeltaSnapshotSec);
DerivedAccel = DerivedAccel.GetClampedToMaxSize(8000.0f);
}
const float AccelSmoothingAlpha = 0.35f;
LastDerivedAccelerationCmS2 = bHasSnapshotKinematics
? FMath::Lerp(LastDerivedAccelerationCmS2, DerivedAccel, AccelSmoothingAlpha)
: DerivedAccel;
const FVector2D Vel2D(VelCmS.X, VelCmS.Y);
const float Speed2D = Vel2D.Size();
const FVector2D Accel2D(LastDerivedAccelerationCmS2.X, LastDerivedAccelerationCmS2.Y);
const float AccelMag2D = Accel2D.Size();
const bool bApplyVisualAccelFallback =
DesiredMode == MOVE_Walking &&
Speed2D >= AnimationMinSpeedForAccelFallbackCmS &&
AccelMag2D < AnimationMinAccelFallbackCmS2;
if (bApplyVisualAccelFallback)
{
const FVector2D VelDir = Vel2D.GetSafeNormal();
const FVector2D FallbackAccel2D = VelDir * AnimationMinAccelFallbackCmS2;
LastDerivedAccelerationCmS2.X = FallbackAccel2D.X;
LastDerivedAccelerationCmS2.Y = FallbackAccel2D.Y;
LastDerivedAccelerationCmS2.Z = 0.0f;
}
LastSnapshotVelocityCmS = VelCmS;
LastSnapshotMovementMode = DesiredMode;
LastSnapshotServerTimeMs = ServerTimeMs;
bHasSnapshotKinematics = true;
bHasLastServerPos = true;
LastServerPosCm = PosCm;
RefreshAuthoritativeAnimationDrivers(Owner);
const FVector Cur = Owner->GetActorLocation();
const FVector Delta = PosCm - Cur;
const FVector2D DeltaXY(Delta.X, Delta.Y);
const float DeltaZAbs = FMath::Abs(Delta.Z);
if (DeltaXY.Size() < SnapshotMinHorizontalCorrectionCm && DeltaZAbs < SnapshotMinVerticalCorrectionCm)
{
return;
}
const float DistSq = Delta.SizeSquared();
const bool bUseTeleport = DistSq > TeleportThresholdCm * TeleportThresholdCm;
if (bUseTeleport)
{
Owner->SetActorLocation(PosCm, /*bSweep*/ false);
}
else
{
const FVector Lerped = FMath::Lerp(Cur, PosCm, FMath::Clamp(SnapshotLerpAlpha, 0.0f, 1.0f));
Owner->SetActorLocation(Lerped, /*bSweep*/ false);
}
}
void UZeusInputBridgeComponent::HandlePlayerDespawned(const int32 EntityId)
{
if (EntityId == LocalEntityId)
{
if (APlayerCharacter* Owner = GetOwnerCharacter())
{
if (UCharacterMovementComponent* CMC = Owner->GetCharacterMovement())
{
CMC->Velocity = FVector::ZeroVector;
CMC->SetMovementMode(MOVE_Walking);
}
}
bSpawnedLocal = false;
LocalEntityId = 0;
LastAppliedServerInputSeq = -1;
bHasLastServerPos = false;
LastServerPosCm = FVector::ZeroVector;
bHasSnapshotKinematics = false;
LastSnapshotVelocityCmS = FVector::ZeroVector;
LastDerivedAccelerationCmS2 = FVector::ZeroVector;
LastSnapshotMovementMode = MOVE_Walking;
LastSnapshotServerTimeMs = 0;
}
}

View File

@@ -0,0 +1,106 @@
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "ZeusInputBridgeComponent.generated.h"
class UZeusNetworkSubsystem;
class APlayerCharacter;
/**
* UZeusInputBridgeComponent — liga `APlayerCharacter` ao `UZeusNetworkSubsystem`.
*
* Responsabilidades:
* - Tick: ler axis correntes (via `SetMovementInput`) e flags de jump;
* chamar `Zeus->SendInputAxis(...)` no intervalo configurado.
* - Subscrever `OnPlayerSpawned` (apenas se `bIsLocal=true`): aplica
* `SetActorLocation(PosCm)` ao owner e zera input acumulado.
* - Subscrever `OnPlayerStateUpdate`: aplica posicao do snapshot. Nesta fase
* sem predicao — `SetActorLocation(NewPos)` directo (sweep=false). Se a
* distancia for grande (>50cm) faz teleport, senao lerp suave.
* - Subscrever `OnPlayerDespawned`: limpa estado local de snapshot/input.
*/
UCLASS(ClassGroup = (Zeus), meta = (BlueprintSpawnableComponent))
class ZCLIENTMMO_API UZeusInputBridgeComponent : public UActorComponent
{
GENERATED_BODY()
public:
UZeusInputBridgeComponent();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Input")
float SendIntervalSeconds = 1.0f / 30.0f;
/** Distancia (cm) acima da qual fazemos teleport em vez de lerp. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Input")
float TeleportThresholdCm = 120.0f;
/** Factor de lerp [0..1] aplicado por snapshot (default 0.4). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Input")
float SnapshotLerpAlpha = 0.6f;
/** Ignora micro-correcao horizontal para reduzir jitter visual. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Input")
float SnapshotMinHorizontalCorrectionCm = 3.0f;
/** Ignora micro-correcao vertical para reduzir jitter visual. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Input")
float SnapshotMinVerticalCorrectionCm = 6.0f;
/** Velocidade horizontal minima para ativar fallback de aceleracao visual. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Animation")
float AnimationMinSpeedForAccelFallbackCmS = 120.0f;
/** Aceleracao horizontal minima usada para manter locomotion blend estavel. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Animation")
float AnimationMinAccelFallbackCmS2 = 300.0f;
UFUNCTION(BlueprintCallable, Category = "Zeus|Input")
void SetMovementInput(float Forward, float Right);
UFUNCTION(BlueprintCallable, Category = "Zeus|Input")
void RegisterJumpPressed();
UFUNCTION(BlueprintCallable, Category = "Zeus|Input")
void RegisterJumpReleased();
bool TryGetAuthoritativeKinematics(FVector& OutVelocityCmS, FVector& OutDerivedAccelerationCmS2, EMovementMode& OutMovementMode) const;
protected:
virtual void BeginPlay() override;
virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
private:
void RefreshAuthoritativeAnimationDrivers(APlayerCharacter* Owner) const;
UFUNCTION()
void HandlePlayerSpawned(int32 EntityId, bool bIsLocal, FVector PosCm, float YawDeg, int64 ServerTimeMs);
UFUNCTION()
void HandlePlayerStateUpdate(int32 EntityId, int32 InputSeq, FVector PosCm, FVector VelCmS, bool bGrounded, int64 ServerTimeMs);
UFUNCTION()
void HandlePlayerDespawned(int32 EntityId);
UZeusNetworkSubsystem* GetZeus() const;
APlayerCharacter* GetOwnerCharacter() const;
float CurrentForward = 0.0f;
float CurrentRight = 0.0f;
bool bJumpPressedThisFrame = false;
bool bJumpReleasedThisFrame = false;
float SendAccumulator = 0.0f;
int32 InputSeq = 0;
int32 LocalEntityId = 0;
bool bSpawnedLocal = false;
int32 LastAppliedServerInputSeq = -1;
bool bHasLastServerPos = false;
FVector LastServerPosCm = FVector::ZeroVector;
bool bHasSnapshotKinematics = false;
FVector LastSnapshotVelocityCmS = FVector::ZeroVector;
FVector LastDerivedAccelerationCmS2 = FVector::ZeroVector;
EMovementMode LastSnapshotMovementMode = MOVE_Walking;
int64 LastSnapshotServerTimeMs = 0;
};

View File

@@ -0,0 +1 @@
#include "ZeusProxyMovementComponent.h"

View File

@@ -0,0 +1,25 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "ZeusProxyMovementComponent.generated.h"
/**
* Componente de movimento para proxies Zeus.
*
* Expõe escrita de aceleração externa para manter o AnimBP em locomotion
* quando o movimento vem de snapshot autoritativo (sem NetDriver da UE).
*/
UCLASS(ClassGroup = (Zeus), meta = (BlueprintSpawnableComponent))
class ZCLIENTMMO_API UZeusProxyMovementComponent : public UCharacterMovementComponent
{
GENERATED_BODY()
public:
/** Define aceleração externa consumida pelo CMC/AnimBP. */
void SetZeusExternalAcceleration(const FVector& InAccelerationCmS2)
{
Acceleration = InAccelerationCmS2;
}
};

View File

@@ -0,0 +1,54 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class ZClientMMO : ModuleRules
{
public ZClientMMO(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"CoreUObject",
"Engine",
"InputCore",
"EnhancedInput",
"AIModule",
"StateTreeModule",
"GameplayStateTreeModule",
"UMG",
"Slate"
});
PrivateDependencyModuleNames.AddRange(new string[] {
"ZeusNetwork"
});
PublicIncludePaths.AddRange(new string[] {
"ZClientMMO",
"ZClientMMO/Game/Player",
"ZClientMMO/Variant_Platforming",
"ZClientMMO/Variant_Platforming/Animation",
"ZClientMMO/Variant_Combat",
"ZClientMMO/Variant_Combat/AI",
"ZClientMMO/Variant_Combat/Animation",
"ZClientMMO/Variant_Combat/Gameplay",
"ZClientMMO/Variant_Combat/Interfaces",
"ZClientMMO/Variant_Combat/UI",
"ZClientMMO/Variant_SideScrolling",
"ZClientMMO/Variant_SideScrolling/AI",
"ZClientMMO/Variant_SideScrolling/Gameplay",
"ZClientMMO/Variant_SideScrolling/Interfaces",
"ZClientMMO/Variant_SideScrolling/UI"
});
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}

View File

@@ -0,0 +1,8 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ZClientMMO.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, ZClientMMO, "ZClientMMO" );
DEFINE_LOG_CATEGORY(LogZClientMMO)

View File

@@ -0,0 +1,8 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
/** Main log category used across the project */
DECLARE_LOG_CATEGORY_EXTERN(LogZClientMMO, Log, All);

View File

@@ -0,0 +1,197 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ZClientMMOCharacter.h"
#include "Animation/AnimInstance.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "EnhancedInputComponent.h"
#include "Engine/SkeletalMesh.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"
#include "InputAction.h"
#include "InputActionValue.h"
#include "UObject/ConstructorHelpers.h"
#include "ZClientMMO.h"
AZClientMMOCharacter::AZClientMMOCharacter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
GetCapsuleComponent()->InitCapsuleSize(42.0f, 96.0f);
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
UCharacterMovementComponent* MovementComponent = GetCharacterMovement();
MovementComponent->bOrientRotationToMovement = true;
MovementComponent->RotationRate = FRotator(0.0f, 500.0f, 0.0f);
MovementComponent->JumpZVelocity = 500.0f;
MovementComponent->AirControl = 0.35f;
MovementComponent->MaxWalkSpeed = 500.0f;
MovementComponent->MinAnalogWalkSpeed = 20.0f;
MovementComponent->BrakingDecelerationWalking = 2000.0f;
MovementComponent->BrakingDecelerationFalling = 1500.0f;
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 400.0f;
CameraBoom->bUsePawnControlRotation = true;
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
FollowCamera->bUsePawnControlRotation = false;
if (USkeletalMeshComponent* MeshComponent = GetMesh())
{
static ConstructorHelpers::FObjectFinder<USkeletalMesh> QuinnMesh(
TEXT("/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple.SKM_Quinn_Simple"));
static ConstructorHelpers::FClassFinder<UAnimInstance> QuinnAnimBp(
TEXT("/Game/Characters/Mannequins/Anims/Unarmed/ABP_Unarmed"));
if (QuinnMesh.Succeeded())
{
MeshComponent->SetSkeletalMesh(QuinnMesh.Object);
MeshComponent->SetRelativeLocation(FVector(0.0f, 0.0f, -90.0f));
MeshComponent->SetRelativeRotation(FRotator(0.0f, -90.0f, 0.0f));
}
else
{
UE_LOG(LogZClientMMO, Warning, TEXT("Default mesh SKM_Quinn_Simple not found for AZClientMMOCharacter."));
}
if (QuinnAnimBp.Succeeded())
{
MeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
MeshComponent->SetAnimInstanceClass(QuinnAnimBp.Class);
}
else
{
UE_LOG(LogZClientMMO, Warning, TEXT("Default anim blueprint ABP_Unarmed not found for AZClientMMOCharacter."));
}
}
static ConstructorHelpers::FObjectFinder<UInputAction> MoveActionAsset(
TEXT("/Game/Input/Actions/IA_Move.IA_Move"));
static ConstructorHelpers::FObjectFinder<UInputAction> LookActionAsset(
TEXT("/Game/Input/Actions/IA_Look.IA_Look"));
static ConstructorHelpers::FObjectFinder<UInputAction> MouseLookActionAsset(
TEXT("/Game/Input/Actions/IA_MouseLook.IA_MouseLook"));
static ConstructorHelpers::FObjectFinder<UInputAction> JumpActionAsset(
TEXT("/Game/Input/Actions/IA_Jump.IA_Jump"));
if (MoveActionAsset.Succeeded())
{
MoveAction = MoveActionAsset.Object;
}
if (LookActionAsset.Succeeded())
{
LookAction = LookActionAsset.Object;
}
if (MouseLookActionAsset.Succeeded())
{
MouseLookAction = MouseLookActionAsset.Object;
}
if (JumpActionAsset.Succeeded())
{
JumpAction = JumpActionAsset.Object;
}
}
void AZClientMMOCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
if (JumpAction != nullptr)
{
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
}
if (MoveAction != nullptr)
{
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AZClientMMOCharacter::Move);
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Completed, this, &AZClientMMOCharacter::MoveCompleted);
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Canceled, this, &AZClientMMOCharacter::MoveCompleted);
}
if (MouseLookAction != nullptr)
{
EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &AZClientMMOCharacter::Look);
}
if (LookAction != nullptr)
{
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AZClientMMOCharacter::Look);
}
}
else
{
UE_LOG(LogZClientMMO, Error, TEXT("'%s' failed to find an Enhanced Input component."), *GetNameSafe(this));
}
}
void AZClientMMOCharacter::Move(const FInputActionValue& Value)
{
// input is a Vector2D
FVector2D MovementVector = Value.Get<FVector2D>();
// route the input
DoMove(MovementVector.X, MovementVector.Y);
}
void AZClientMMOCharacter::MoveCompleted(const FInputActionValue& Value)
{
(void)Value;
// Garante release explícito para evitar "input preso" no pipeline autoritativo.
DoMove(0.0f, 0.0f);
}
void AZClientMMOCharacter::Look(const FInputActionValue& Value)
{
// input is a Vector2D
FVector2D LookAxisVector = Value.Get<FVector2D>();
// route the input
DoLook(LookAxisVector.X, LookAxisVector.Y);
}
void AZClientMMOCharacter::DoMove(float Right, float Forward)
{
if (GetController() != nullptr)
{
// find out which way is forward
const FRotator Rotation = GetController()->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// get forward vector
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
// get right vector
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
// add movement
AddMovementInput(ForwardDirection, Forward);
AddMovementInput(RightDirection, Right);
}
}
void AZClientMMOCharacter::DoLook(float Yaw, float Pitch)
{
if (GetController() != nullptr)
{
// add yaw and pitch input to controller
AddControllerYawInput(Yaw);
AddControllerPitchInput(Pitch);
}
}
void AZClientMMOCharacter::DoJumpStart()
{
// signal the character to jump
Jump();
}
void AZClientMMOCharacter::DoJumpEnd()
{
// signal the character to stop jumping
StopJumping();
}

View File

@@ -0,0 +1,94 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Logging/LogMacros.h"
#include "ZClientMMOCharacter.generated.h"
class USpringArmComponent;
class UCameraComponent;
class UInputAction;
struct FInputActionValue;
DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);
/** Character base do template third-person usado pelo cliente. */
UCLASS(Abstract)
class AZClientMMOCharacter : public ACharacter
{
GENERATED_BODY()
/** Camera boom positioning the camera behind the character */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
USpringArmComponent* CameraBoom;
/** Follow camera */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
UCameraComponent* FollowCamera;
protected:
/** Jump Input Action */
UPROPERTY(EditAnywhere, Category="Input")
UInputAction* JumpAction;
/** Move Input Action */
UPROPERTY(EditAnywhere, Category="Input")
UInputAction* MoveAction;
/** Look Input Action */
UPROPERTY(EditAnywhere, Category="Input")
UInputAction* LookAction;
/** Mouse Look Input Action */
UPROPERTY(EditAnywhere, Category="Input")
UInputAction* MouseLookAction;
public:
/** Constructor */
AZClientMMOCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
protected:
/** Initialize input action bindings */
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
protected:
/** Called for movement input */
void Move(const FInputActionValue& Value);
void MoveCompleted(const FInputActionValue& Value);
/** Called for looking input */
void Look(const FInputActionValue& Value);
public:
/** Handles move inputs from either controls or UI interfaces */
UFUNCTION(BlueprintCallable, Category="Input")
virtual void DoMove(float Right, float Forward);
/** Handles look inputs from either controls or UI interfaces */
UFUNCTION(BlueprintCallable, Category="Input")
virtual void DoLook(float Yaw, float Pitch);
/** Handles jump pressed inputs from either controls or UI interfaces */
UFUNCTION(BlueprintCallable, Category="Input")
virtual void DoJumpStart();
/** Handles jump pressed inputs from either controls or UI interfaces */
UFUNCTION(BlueprintCallable, Category="Input")
virtual void DoJumpEnd();
public:
/** Returns CameraBoom subobject **/
FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
/** Returns FollowCamera subobject **/
FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
};

View File

@@ -0,0 +1,169 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ZClientMMOGameInstance.h"
#include "ZeusNetworkSubsystem.h"
#include "ZClientMMO.h"
#include "Engine/World.h"
#include "Kismet/GameplayStatics.h"
UZClientMMOGameInstance::UZClientMMOGameInstance()
{
ZeusServerHost = TEXT("127.0.0.1");
}
void UZClientMMOGameInstance::Init()
{
Super::Init();
if (!bZeusAutoConnectOnInit)
{
return;
}
UZeusNetworkSubsystem* Zeus = GetSubsystem<UZeusNetworkSubsystem>();
if (!Zeus)
{
UE_LOG(LogZClientMMO, Error, TEXT("ZeusNetworkSubsystem não encontrado na Game Instance. O plugin ZeusNetwork está activo?"));
return;
}
BindZeusDelegates(Zeus);
FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &UZClientMMOGameInstance::HandlePostLoadMap);
if (bZeusUseDeveloperSettingsEndpoint)
{
Zeus->ConnectToDefaultZeusServer();
}
else
{
Zeus->ConnectToZeusServer(ZeusServerHost, ZeusServerPort);
}
}
void UZClientMMOGameInstance::Shutdown()
{
if (UZeusNetworkSubsystem* Zeus = GetSubsystem<UZeusNetworkSubsystem>())
{
UnbindZeusDelegates(Zeus);
Zeus->DisconnectFromZeusServer();
}
FCoreUObjectDelegates::PostLoadMapWithWorld.RemoveAll(this);
Super::Shutdown();
}
void UZClientMMOGameInstance::BindZeusDelegates(UZeusNetworkSubsystem* Zeus)
{
if (!Zeus)
{
return;
}
Zeus->OnConnected.AddDynamic(this, &UZClientMMOGameInstance::HandleZeusConnected);
Zeus->OnConnectionFailed.AddDynamic(this, &UZClientMMOGameInstance::HandleZeusConnectionFailed);
Zeus->OnDisconnected.AddDynamic(this, &UZClientMMOGameInstance::HandleZeusDisconnected);
Zeus->OnPingUpdated.AddDynamic(this, &UZClientMMOGameInstance::HandleZeusPingUpdated);
Zeus->OnNetworkLog.AddDynamic(this, &UZClientMMOGameInstance::HandleZeusNetworkLog);
Zeus->OnServerTravelRequested.AddDynamic(this, &UZClientMMOGameInstance::HandleZeusServerTravelRequested);
}
void UZClientMMOGameInstance::UnbindZeusDelegates(UZeusNetworkSubsystem* Zeus)
{
if (!Zeus)
{
return;
}
Zeus->OnConnected.RemoveDynamic(this, &UZClientMMOGameInstance::HandleZeusConnected);
Zeus->OnConnectionFailed.RemoveDynamic(this, &UZClientMMOGameInstance::HandleZeusConnectionFailed);
Zeus->OnDisconnected.RemoveDynamic(this, &UZClientMMOGameInstance::HandleZeusDisconnected);
Zeus->OnPingUpdated.RemoveDynamic(this, &UZClientMMOGameInstance::HandleZeusPingUpdated);
Zeus->OnNetworkLog.RemoveDynamic(this, &UZClientMMOGameInstance::HandleZeusNetworkLog);
Zeus->OnServerTravelRequested.RemoveDynamic(this, &UZClientMMOGameInstance::HandleZeusServerTravelRequested);
}
void UZClientMMOGameInstance::HandleZeusConnected()
{
UE_LOG(LogZClientMMO, Log, TEXT("[Zeus] Ligado ao servidor."));
}
void UZClientMMOGameInstance::HandleZeusConnectionFailed(FString Reason)
{
UE_LOG(LogZClientMMO, Warning, TEXT("[Zeus] Falha de ligação: %s"), *Reason);
}
void UZClientMMOGameInstance::HandleZeusDisconnected()
{
bTravelInFlight = false;
LastTravelTarget.Empty();
UE_LOG(LogZClientMMO, Log, TEXT("[Zeus] Desligado."));
}
void UZClientMMOGameInstance::HandleZeusPingUpdated(float RttMs)
{
UE_LOG(LogZClientMMO, Verbose, TEXT("[Zeus] RTT %.2f ms"), RttMs);
}
void UZClientMMOGameInstance::HandleZeusNetworkLog(FString Message)
{
UE_LOG(LogZClientMMO, Log, TEXT("[Zeus] %s"), *Message);
}
void UZClientMMOGameInstance::HandleZeusServerTravelRequested(const FString& MapName, const FString& MapPath)
{
const FString Target = MapPath.IsEmpty() ? MapName : MapPath;
if (Target.IsEmpty())
{
UE_LOG(LogZClientMMO, Warning, TEXT("[Zeus] S_TRAVEL_TO_MAP recebido sem destino (mapName e mapPath vazios). A ignorar."));
return;
}
if (bTravelInFlight && LastTravelTarget.Equals(Target, ESearchCase::IgnoreCase))
{
UE_LOG(LogZClientMMO, Verbose, TEXT("[Zeus] Travel repetido para %s ignorado (em progresso)."), *Target);
return;
}
UWorld* CurrentWorld = GetWorld();
if (CurrentWorld != nullptr)
{
FString CurrentName = CurrentWorld->GetMapName();
CurrentName.RemoveFromStart(TEXT("UEDPIE_0_"));
if (!CurrentName.IsEmpty() && (Target.EndsWith(CurrentName) || Target.EndsWith(TEXT("/") + CurrentName)))
{
bTravelInFlight = false;
LastTravelTarget = Target;
UE_LOG(LogZClientMMO, Verbose, TEXT("[Zeus] Ja estamos no mapa %s; OpenLevel ignorado."), *CurrentName);
return;
}
}
bTravelInFlight = true;
LastTravelTarget = Target;
UE_LOG(LogZClientMMO, Log, TEXT("[Zeus] OpenLevel solicitado pelo servidor: name=%s path=%s"), *MapName, *MapPath);
UGameplayStatics::OpenLevel(this, FName(*Target));
}
void UZClientMMOGameInstance::HandlePostLoadMap(UWorld* LoadedWorld)
{
if (LoadedWorld == nullptr)
{
return;
}
FString LoadedMapName = LoadedWorld->GetMapName();
LoadedMapName.RemoveFromStart(TEXT("UEDPIE_0_"));
if (bTravelInFlight)
{
if (!LastTravelTarget.IsEmpty() &&
(LastTravelTarget.EndsWith(LoadedMapName) || LastTravelTarget.EndsWith(TEXT("/") + LoadedMapName)))
{
bTravelInFlight = false;
UE_LOG(LogZClientMMO, Log, TEXT("[Zeus] Travel concluido para mapa %s."), *LoadedMapName);
return;
}
UE_LOG(LogZClientMMO, Verbose, TEXT("[Zeus] PostLoadMap=%s, aguardando target=%s"), *LoadedMapName, *LastTravelTarget);
}
}

View File

@@ -0,0 +1,65 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "ZClientMMOGameInstance.generated.h"
/** GameInstance do cliente com bootstrap de conexao Zeus e travel solicitado pelo servidor. */
UCLASS(Blueprintable, BlueprintType)
class UZClientMMOGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
UZClientMMOGameInstance();
/** Chamado pelo motor após criar subsistemas da Game Instance (via Super::Init). A lógica Zeus corre aqui, não em ReceiveInit. */
virtual void Init() override;
/** Remove delegados e pede desligamento limpo ao subsistema Zeus. */
virtual void Shutdown() override;
protected:
/** Se verdadeiro, chama ConnectToDefaultZeusServer() (Developer Settings ZeusNetwork) em vez de Host/Port abaixo. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Zeus|Connection")
bool bZeusUseDeveloperSettingsEndpoint = false;
/** Host UDP do servidor Zeus (ignorado se bZeusUseDeveloperSettingsEndpoint). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Zeus|Connection")
FString ZeusServerHost;
/** Porta UDP (ignorada se bZeusUseDeveloperSettingsEndpoint). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Zeus|Connection")
int32 ZeusServerPort = 27777;
/** Ligar e registar delegados automaticamente no fim de Init. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Zeus|Connection")
bool bZeusAutoConnectOnInit = true;
private:
void BindZeusDelegates(class UZeusNetworkSubsystem* Zeus);
void UnbindZeusDelegates(class UZeusNetworkSubsystem* Zeus);
void HandlePostLoadMap(class UWorld* LoadedWorld);
bool bTravelInFlight = false;
FString LastTravelTarget;
UFUNCTION()
void HandleZeusConnected();
UFUNCTION()
void HandleZeusConnectionFailed(FString Reason);
UFUNCTION()
void HandleZeusDisconnected();
UFUNCTION()
void HandleZeusPingUpdated(float RttMs);
UFUNCTION()
void HandleZeusNetworkLog(FString Message);
UFUNCTION()
void HandleZeusServerTravelRequested(const FString& MapName, const FString& MapPath);
};

View File

@@ -0,0 +1,13 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ZClientMMOGameMode.h"
#include "Game/Player/PlayerCharacter.h"
#include "ZClientMMOPlayerController.h"
AZClientMMOGameMode::AZClientMMOGameMode()
{
// Mantemos o pawn especializado (APlayerCharacter) e controller padrão do cliente.
DefaultPawnClass = APlayerCharacter::StaticClass();
PlayerControllerClass = AZClientMMOPlayerController::StaticClass();
}

View File

@@ -0,0 +1,18 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "ZClientMMOGameMode.generated.h"
/** GameMode base do cliente MMO. */
UCLASS(Blueprintable, BlueprintType)
class AZClientMMOGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
/** Constructor */
AZClientMMOGameMode();
};

View File

@@ -0,0 +1,83 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ZClientMMOPlayerController.h"
#include "EnhancedInputSubsystems.h"
#include "Blueprint/UserWidget.h"
#include "Engine/LocalPlayer.h"
#include "InputMappingContext.h"
#include "UObject/ConstructorHelpers.h"
#include "ZClientMMO.h"
#include "Widgets/Input/SVirtualJoystick.h"
AZClientMMOPlayerController::AZClientMMOPlayerController()
{
static ConstructorHelpers::FObjectFinder<UInputMappingContext> DefaultImc(
TEXT("/Game/Input/IMC_Default.IMC_Default"));
static ConstructorHelpers::FObjectFinder<UInputMappingContext> MouseLookImc(
TEXT("/Game/Input/IMC_MouseLook.IMC_MouseLook"));
if (DefaultImc.Succeeded())
{
DefaultMappingContexts.Add(DefaultImc.Object);
}
if (MouseLookImc.Succeeded())
{
MobileExcludedMappingContexts.Add(MouseLookImc.Object);
}
}
void AZClientMMOPlayerController::BeginPlay()
{
Super::BeginPlay();
// only spawn touch controls on local player controllers
if (ShouldUseTouchControls() && IsLocalPlayerController())
{
// spawn the mobile controls widget
MobileControlsWidget = CreateWidget<UUserWidget>(this, MobileControlsWidgetClass);
if (MobileControlsWidget)
{
// add the controls to the player screen
MobileControlsWidget->AddToPlayerScreen(0);
} else {
UE_LOG(LogZClientMMO, Error, TEXT("Could not spawn mobile controls widget."));
}
}
}
void AZClientMMOPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
if (IsLocalPlayerController())
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
{
for (UInputMappingContext* CurrentContext : DefaultMappingContexts)
{
Subsystem->AddMappingContext(CurrentContext, 0);
}
// only add these IMCs if we're not using mobile touch input
if (!ShouldUseTouchControls())
{
for (UInputMappingContext* CurrentContext : MobileExcludedMappingContexts)
{
Subsystem->AddMappingContext(CurrentContext, 0);
}
}
}
}
}
bool AZClientMMOPlayerController::ShouldUseTouchControls() const
{
// are we on a mobile platform? Should we force touch?
return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls;
}

View File

@@ -0,0 +1,51 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "ZClientMMOPlayerController.generated.h"
class UInputMappingContext;
class UUserWidget;
/** PlayerController do cliente com setup de IMCs do template. */
UCLASS()
class AZClientMMOPlayerController : public APlayerController
{
GENERATED_BODY()
public:
AZClientMMOPlayerController();
protected:
/** Input Mapping Contexts base. */
UPROPERTY(EditAnywhere, Category = "Input|Input Mappings")
TArray<UInputMappingContext*> DefaultMappingContexts;
/** Input Mapping Contexts excluidos quando touch controls estao ativos. */
UPROPERTY(EditAnywhere, Category = "Input|Input Mappings")
TArray<UInputMappingContext*> MobileExcludedMappingContexts;
/** Mobile controls widget to spawn */
UPROPERTY(EditAnywhere, Category="Input|Touch Controls")
TSubclassOf<UUserWidget> MobileControlsWidgetClass;
/** Pointer to the mobile controls widget */
UPROPERTY()
TObjectPtr<UUserWidget> MobileControlsWidget;
/** If true, the player will use UMG touch controls even if not playing on mobile platforms */
UPROPERTY(EditAnywhere, Config, Category = "Input|Touch Controls")
bool bForceTouchControls = false;
/** Gameplay initialization */
virtual void BeginPlay() override;
/** Input mapping context setup */
virtual void SetupInputComponent() override;
/** Returns true if the player should use UMG touch controls */
bool ShouldUseTouchControls() const;
};

View File

@@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class ZClientMMOEditorTarget : TargetRules
{
public ZClientMMOEditorTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Editor;
DefaultBuildSettings = BuildSettingsVersion.V6;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
ExtraModuleNames.Add("ZClientMMO");
}
}