Files
ZMMO/Source/ZMMO/Game/Entity/ZeusPlayerProxy.h
Mateus Rodrigues de294f4075 fix(yaw): proxy replica o yaw do CORPO do dono (orient-to-movement), nao a mira
[YAW-BODY-ORIENT] commit conjunto cliente + servidor.

Sintoma: ao apertar D o proxy virava ~45 graus de uma vez, em vez de virar
igual ao player local (suave, so um pouco), e parado seguia o mouse.

Raiz: a fonte de verdade do yaw estava errada. O servidor derivava
simpleYawDeg de atan2(velocidade) INSTANTANEO ao mover (snap pra direcao do
movimento) e da mira (ControlRotation) ao parar -- nenhum dos dois e o yaw
do corpo do dono.

Correcao ponta-a-ponta:
- Cliente: envia GetActorRotation().Yaw (yaw do CORPO; o CMC ja orienta pra
  direcao do movimento a 500 deg/s e bUseControllerRotationYaw=false faz o
  MOUSE nao girar o corpo) no lugar de GetControlRotation().Yaw.
- Servidor: usa o yaw recebido DIRETO em simpleYawDeg (remove atan2
  instantaneo + mira-no-idle).
- Proxy: replica Snapshot.YawDeg interpolado shortest-path (remove a
  derivacao por Atan2 da velocidade).

Resultado: o proxy vira identico ao corpo do dono e o mouse nao gira o corpo.
Futuro (aim/combate/Motion Matching): adicionar um campo aimYawDeg dedicado em
vez de reusar este.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:22:07 -03:00

180 lines
7.1 KiB
C++

#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "ZeusEntityInterface.h"
#include "ZeusEntityTypes.h"
#include "ZeusPlayerProxy.generated.h"
/**
* AZeusPlayerProxy
*
* Proxy visual para jogadores remotos (e, no futuro, mobs com locomotion
* por capsula) cuja posicao vem 100% do servidor via snapshots
* (`UZeusNetworkSubsystem::OnPlayerStateUpdate`).
*
* Diferencas chave vs. `AZeusCharacter` (jogador local):
* - SEM `EnhancedInput`, sem envio de `C_INPUT_AXIS`.
* - SEM buffer de pending inputs nem reconciliacao.
* - Posicao aplicada por snapshot (`ApplyEntitySnapshot`); a `ACharacter`
* continua a existir para preservar capsula + skeletal mesh + AnimBP,
* mas o CMC fica em modo "kinematic-ish" (nao integra fisica).
*
* Animacao no AnimBP do Quinn (`ABP_Unarmed`): a formula `ShouldMove` exige
* `Acceleration != 0`. Como proxies nao tem input, adoptamos a estrategia
* de CMC custom (`UZeusProxyMovementComponent`) com escrita externa de
* `Acceleration`, alimentada pela derivada da velocidade entre snapshots.
* Tick do proxy roda **antes** do mesh tick para que Velocity+Acceleration
* estejam populados quando o `NativeUpdateAnimation` do AnimBP os ler.
*/
/**
* Snapshot bruto guardado no buffer de interpolacao. Mantido como struct privada
* ao .h porque so o proxy (e debugging local) precisa olhar para ele.
*/
USTRUCT()
struct FZeusProxySnapshot
{
GENERATED_BODY()
UPROPERTY()
FVector PosCm = FVector::ZeroVector;
UPROPERTY()
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()
bool bGrounded = true;
/** Timestamp do servidor que produziu o snapshot (ms desde Unix epoch). */
UPROPERTY()
int64 ServerTimeMs = 0;
};
UCLASS(Blueprintable, BlueprintType)
class ZMMO_API AZeusPlayerProxy : public ACharacter, public IZeusEntityInterface
{
GENERATED_BODY()
public:
AZeusPlayerProxy(const FObjectInitializer& ObjectInitializer);
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
// --- IZeusEntityInterface ---
virtual int64 GetZeusEntityId() const override { return EntityId; }
virtual EZeusEntityType GetZeusEntityType() const override { return EntityType; }
virtual void ApplyEntitySnapshot(const FZeusEntitySnapshot& Snapshot) override;
virtual void SetEntityRelevant(bool bRelevant) override;
UFUNCTION(BlueprintCallable, Category = "Zeus|Entity")
void SetZeusIdentity(int64 InEntityId, EZeusEntityType InEntityType);
protected:
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "Zeus|Entity")
int64 EntityId = 0;
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "Zeus|Entity")
EZeusEntityType EntityType = EZeusEntityType::Player;
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "Zeus|Entity")
bool bIsZeusRelevant = true;
/** True apos o primeiro snapshot ser aplicado — antes disso o ator pode ficar oculto. */
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "Zeus|Entity")
bool bHasFirstSnapshot = false;
/**
* Ultima posicao **autoritativa** recebida do servidor (cm, mundo). Difere
* de `InterpolatedPosCm` (visual) — separar os dois e essencial para
* comparar drift autoritativo vs visual via debug overlay/logs.
*/
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "Zeus|Entity")
FVector AuthoritativePosCm = FVector::ZeroVector;
/** Posicao **visual** atual (resultado da interpolacao do tick). */
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "Zeus|Entity")
FVector InterpolatedPosCm = FVector::ZeroVector;
/** Compat: alias para `AuthoritativePosCm` (preserva binding de Blueprint, se houver). */
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "Zeus|Entity")
FVector LastSnapshotPosition = FVector::ZeroVector;
/**
* Velocidade visual derivada para o AnimBP. Espelha a velocidade
* *interpolada* (nao o snapshot bruto) e e re-imposta no `CMC->Velocity`
* em cada `Tick` para manter os drivers de animacao alimentados entre
* updates de rede.
*/
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "Zeus|Entity")
FVector VisualVelocity = FVector::ZeroVector;
/**
* Atraso de interpolacao em ms. Render-time = wall_now - InterpolationDelayMs,
* expresso no dominio do tempo do servidor. Padrao 100 ms (~3 snapshots a 30 Hz)
* cobre 1 perda + 1 jitter sem extrapolar.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Network",
meta = (ClampMin = "0.0", ClampMax = "500.0"))
double InterpolationDelayMs = 100.0;
/** Tamanho maximo do buffer (em snapshots). Cobre ~270 ms a 30 Hz. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Network",
meta = (ClampMin = "2", ClampMax = "32"))
int32 SnapshotBufferCapacity = 8;
/** Limite duro de extrapolacao (s) quando nao ha snapshot futuro. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Zeus|Network",
meta = (ClampMin = "0.0", ClampMax = "0.5"))
float MaxExtrapolationSeconds = 0.1f;
/** Buffer ordenado por `ServerTimeMs` ascendente. */
UPROPERTY()
TArray<FZeusProxySnapshot> SnapshotBuffer;
/**
* Offset entre o relogio local (`FPlatformTime::Seconds * 1000`) e o
* relogio do servidor (`ServerTimeMs` do snapshot). Estimado pela primeira
* amostra como `local_now_ms - server_time_ms`. Subtraindo este offset do
* relogio local obtemos o tempo do servidor estimado.
*/
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
* o AnimBP (`ShouldMove`/`Jump_Start` exigem Acceleration!=0). */
FVector LastDerivedAccelerationCmS2 = FVector::ZeroVector;
/** Velocidade visual do tick anterior (para a derivada `Acceleration = dV/dt`). */
FVector PreviousVisualVelocity = FVector::ZeroVector;
bool bHasPreviousSnapshot = false;
/** Ultimo log de diagnostico (s, monotonic). Log emitido 1x/s no Tick. */
double LastDiagLogSec = 0.0;
private:
/** Reaplica `CMC->Velocity` (com clamp de Z em walking) e injeta a
* aceleracao derivada no CMC custom. Chamado em `Tick` (pre-mesh) e em
* `ApplyEntitySnapshot` (para refletir a chegada de novo snapshot
* imediatamente). */
void RefreshAnimationDrivers();
/** Liga/desliga colisao da capsula consoante a relevancia + visual ready. */
void ApplyCollisionPolicy();
};