From de294f4075a446050cf3105e97d7d10ea455d7a0 Mon Sep 17 00:00:00 2001 From: Mateus Rodrigues Date: Sat, 13 Jun 2026 02:22:07 -0300 Subject: [PATCH] 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 --- Source/ZMMO/Game/Entity/ZeusCharacter.cpp | 23 ++++++++++------ Source/ZMMO/Game/Entity/ZeusPlayerProxy.cpp | 29 ++++++++++++++++++--- Source/ZMMO/Game/Entity/ZeusPlayerProxy.h | 9 +++++++ 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/Source/ZMMO/Game/Entity/ZeusCharacter.cpp b/Source/ZMMO/Game/Entity/ZeusCharacter.cpp index d4eb22a..1a31eb0 100644 --- a/Source/ZMMO/Game/Entity/ZeusCharacter.cpp +++ b/Source/ZMMO/Game/Entity/ZeusCharacter.cpp @@ -640,12 +640,19 @@ void AZeusCharacter::FlushInputAxisToServer(const float DeltaSeconds) ++InputSequence; const int32 ClientTimeMs = static_cast(FPlatformTime::Seconds() * 1000.0); - // ADR 0040: yaw da camara/controller para que o servidor consiga rotar o - // input (referencial-camara) para velocidade mundial. Fallback para o yaw - // do actor se nao houver controller (e.g. tela de loading). - const float ViewYawDeg = (GetController() - ? static_cast(GetController()->GetControlRotation().Yaw) - : static_cast(GetActorRotation().Yaw)); + // YAW DO CORPO (nao da mira): GetActorRotation().Yaw reflete o orient-to-movement + // do CMC (bOrientRotationToMovement=true, RotationRate=(0,500,0)) -> o corpo vira + // pra direcao do MOVIMENTO (WASD), suave a 500 deg/s, e o MOUSE nao gira o corpo + // (bUseControllerRotationYaw=false). O servidor usa este yaw direto e o proxy nos + // outros clientes o replica -> o proxy vira IDENTICO ao corpo do dono. + // + // (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(GetActorRotation().Yaw); // ADR 0041: cliente autoritativo da posicao XYZ + velocidade XY. O CMC ja // integrou o input local antes deste flush, portanto `GetActorLocation` e // `GetVelocity` reflectem o estado real que o servidor deve replicar para @@ -662,7 +669,7 @@ void AZeusCharacter::FlushInputAxisToServer(const float DeltaSeconds) bPendingJumpReleased, InputSequence, ClientTimeMs, - ViewYawDeg, + BodyYawDeg, PosCm, FVector2D(Vel.X, Vel.Y), bIsFalling, @@ -689,7 +696,7 @@ void AZeusCharacter::FlushInputAxisToServer(const float DeltaSeconds) bPendingJumpReleased, InputSequence, ClientTimeMs, - ViewYawDeg, + BodyYawDeg, PosCm, FVector2D(Vel.X, Vel.Y), bIsFalling, diff --git a/Source/ZMMO/Game/Entity/ZeusPlayerProxy.cpp b/Source/ZMMO/Game/Entity/ZeusPlayerProxy.cpp index f955685..5d8dcab 100644 --- a/Source/ZMMO/Game/Entity/ZeusPlayerProxy.cpp +++ b/Source/ZMMO/Game/Entity/ZeusPlayerProxy.cpp @@ -157,12 +157,32 @@ void AZeusPlayerProxy::Tick(const float DeltaSeconds) NewVel = FMath::Lerp(A.VelCmS, B.VelCmS, InterpAlpha); } - // Yaw deriva da velocidade interpolada: locomotion mantem face na direcao - // do movimento; em idle preserva o yaw atual para nao "snappar". + // ORIENTACAO DO CORPO = REPLICA do yaw autoritativo do servidor (Snapshot.YawDeg), + // 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(); - 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.Roll = 0.0f; @@ -303,6 +323,7 @@ void AZeusPlayerProxy::ApplyEntitySnapshot(const FZeusEntitySnapshot& Snapshot) FZeusProxySnapshot S; S.PosCm = Snapshot.PositionCm; S.VelCmS = Snapshot.VelocityCmS; + S.YawDeg = Snapshot.YawDeg; // yaw do CORPO do dono (orient-to-movement; mouse nao gira) S.bGrounded = Snapshot.bGrounded; S.ServerTimeMs = Snapshot.ServerTimeMs; diff --git a/Source/ZMMO/Game/Entity/ZeusPlayerProxy.h b/Source/ZMMO/Game/Entity/ZeusPlayerProxy.h index c8f91d2..ec72ead 100644 --- a/Source/ZMMO/Game/Entity/ZeusPlayerProxy.h +++ b/Source/ZMMO/Game/Entity/ZeusPlayerProxy.h @@ -42,6 +42,15 @@ struct FZeusProxySnapshot 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;