#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; 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 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; /** 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(); };