From 9eafcd27fb799dc189531c494b5316b7aa839a68 Mon Sep 17 00:00:00 2001 From: vottozi Date: Tue, 3 Mar 2026 19:42:08 +0600 Subject: [PATCH] Fix lead indicator prediction & align projectile lifetime (client/server) --- server/server.cpp | 2 +- src/Space.cpp | 114 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 102 insertions(+), 14 deletions(-) diff --git a/server/server.cpp b/server/server.cpp index e7fa469..dd1a900 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -423,7 +423,7 @@ private: float len = worldForward.norm(); if (len > 1e-6f) worldForward /= len; pr.vel = worldForward * velocity; - pr.lifeMs = 5000.0f; + pr.lifeMs = 15000.0f; g_projectiles.push_back(pr); std::cout << "Server: Created projectile from player " << id_ diff --git a/src/Space.cpp b/src/Space.cpp index 3639912..9ec49f2 100644 --- a/src/Space.cpp +++ b/src/Space.cpp @@ -25,6 +25,13 @@ #include "network/LocalClient.h" #endif +// --- TEMP DEBUG SHOT LOG --- +#define DEBUG_SHOTLOG 1 +#if DEBUG_SHOTLOG +static uint64_t g_lastShotLogMs = 0; +static uint64_t g_lastAimLogMs = 0; +#endif + namespace ZL { @@ -1143,40 +1150,56 @@ namespace ZL Vector3f shooterPos = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0f, 0.9f - 6.0f, 5.0f }; // скорость цели в мире (вектор) - Vector3f shooterVel = ForwardFromRotation(Environment::shipState.rotation) * Environment::shipState.velocity; + // Vector3f shooterVel = ForwardFromRotation(Environment::shipState.rotation) * Environment::shipState.velocity; + + float shooterSpeed = std::abs(Environment::shipState.velocity); + // В нашей физике линейная скорость корабля всегда направлена по его forward (-Z). + // Когда игрок "наводится" на lead, forward (и скорость) становятся сонаправлены с выстрелом, + // поэтому эффективная скорость снаряда в мире ≈ muzzle + shipSpeed. + const float effectiveProjectileSpeed = projectileSpeed + shooterSpeed; + Vector3f shooterVel = Vector3f::Zero(); // скорость уже учтена в effectiveProjectileSpeed Vector3f targetVel = ForwardFromRotation(st.rotation) * st.velocity; + // --- ВАЖНО: у нас remote state берется на now_ms - CLIENT_DELAY + // Значит shipWorld - это позиция ~0.5 сек назад. + // Для корректного lead нужно предсказать положение цели на "сейчас". + const float clientDelaySec = (float)CLIENT_DELAY / 1000.0f; + Vector3f targetPosNow = shipWorld + targetVel * clientDelaySec; + const float minTargetSpeed = 0.5f; // подобрать (в твоих единицах) bool targetMoving = (targetVel.norm() > minTargetSpeed); // альфа круга float leadAlpha = targetMoving ? 1.0f : 0.5f; - Vector3f leadWorld = shipWorld; + // Vector3f leadWorld = shipWorld; + Vector3f leadWorld = targetPosNow; bool haveLead = false; - // чтобы круг не улетал далеко: максимум 4 секунды (подстроить под игру) - float distToTarget = (Environment::shipState.position - shipWorld).norm(); - float maxLeadTime = std::clamp((distToTarget / projectileSpeed) * 1.2f, 0.05f, 4.0f); + // Дистанцию лучше считать от реальной точки вылета + float distToTarget = (shooterPos - targetPosNow).norm(); + // Максимальное время перехвата ограничиваем жизнью пули + const float projectileLifeSec = (float)PROJECTILE_LIFE / 1000.0f; + float maxLeadTime = std::clamp((distToTarget / effectiveProjectileSpeed) * 1.25f, 0.01f, projectileLifeSec * 0.98f); if (!targetMoving) { // Цель стоит: рисуем lead прямо на ней, но полупрозрачный - leadWorld = shipWorld; + leadWorld = targetPosNow; haveLead = true; } else { float tLead = 0.0f; // 1) Пытаемся “правильное” решение перехвата - bool ok = SolveLeadInterceptTime(shooterPos, shooterVel, shipWorld, targetVel, projectileSpeed, tLead); + bool ok = SolveLeadInterceptTime(shooterPos, shooterVel, targetPosNow, targetVel, effectiveProjectileSpeed, tLead); // 2) Если решения нет / оно плохое — fallback (чтобы круг не пропадал при пролёте "вбок") // Это ключевое изменение: lead всегда будет. if (!ok || !(tLead > 0.0f) || tLead > maxLeadTime) { - tLead = std::clamp(distToTarget / projectileSpeed, 0.05f, maxLeadTime); + tLead = std::clamp(distToTarget / effectiveProjectileSpeed, 0.05f, maxLeadTime); } - leadWorld = shipWorld + targetVel * tLead; + leadWorld = targetPosNow + targetVel * tLead; haveLead = true; } @@ -1421,7 +1444,8 @@ namespace ZL firePressed = false; if (now_ms - lastProjectileFireTime >= static_cast(projectileCooldownMs)) { lastProjectileFireTime = now_ms; - const float projectileSpeed = 250.0f; + // const float projectileSpeed = 250.0f; + const float projectileSpeed = PROJECTILE_VELOCITY; this->fireProjectiles(); @@ -1429,7 +1453,71 @@ namespace ZL Eigen::Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized(); Eigen::Vector3f centerPos = Environment::shipState.position + - Environment::shipState.rotation * Vector3f{ 0, 0.9f, 5.0f }; + Environment::shipState.rotation * Vector3f{ 0, 0.9f - 6.0f, 5.0f }; + +#if DEBUG_SHOTLOG + // лог не чаще чем раз в 250мс, чтобы не заспамить консоль + if (now_ms - g_lastShotLogMs > 250) { + g_lastShotLogMs = now_ms; + + // 1) кого мы сейчас таргетим (как в HUD) + int tid = pickTargetId(); + if (tid >= 0 && remotePlayerStates.count(tid)) { + const ClientState& st = remotePlayerStates.at(tid); + + // shooterPos ДОЛЖЕН совпадать с тем, что используется в lead (HUD) + Vector3f shooterPos = Environment::shipState.position + + Environment::shipState.rotation * Vector3f{ 0.0f, 0.9f - 6.0f, 5.0f }; + + Vector3f targetPos = st.position; + Vector3f targetVel = ForwardFromRotation(st.rotation) * st.velocity; + + const float clientDelaySec = (float)CLIENT_DELAY / 1000.0f; + Vector3f targetPosNow = targetPos + targetVel * clientDelaySec; + + float shooterSpeed = std::abs(Environment::shipState.velocity); + float effSpeed = PROJECTILE_VELOCITY + shooterSpeed; + + float dist = (shooterPos - targetPosNow).norm(); + float projectileLifeSec = (float)PROJECTILE_LIFE / 1000.0f; + float tStraight = dist / max(1e-3f, effSpeed); + + // попытка solve lead + float tLead = 0.0f; + bool ok = SolveLeadInterceptTime(shooterPos, Vector3f::Zero(), targetPosNow, targetVel, effSpeed, tLead); + + // clamp как у HUD + float maxLeadTime = std::clamp(tStraight * 1.25f, 0.01f, projectileLifeSec * 0.98f); + if (!ok || !(tLead > 0.0f) || tLead > maxLeadTime) { + tLead = std::clamp(tStraight, 0.05f, maxLeadTime); + } + + Vector3f leadWorld = targetPosNow + targetVel * tLead; + + std::cout + << "\n[SHOTLOG] now_ms=" << now_ms + << " tid=" << tid + << " dist=" << dist + << " tStraight=" << tStraight + << " tLead=" << tLead + << " lifeSec=" << projectileLifeSec + << " effSpeed=" << effSpeed + << " shipSpeed=" << shooterSpeed + << "\n shooterPos=(" << shooterPos.x() << "," << shooterPos.y() << "," << shooterPos.z() << ")" + << "\n centerPos (sent)=(" << centerPos.x() << "," << centerPos.y() << "," << centerPos.z() << ")" + << "\n targetPos(raw)=(" << targetPos.x() << "," << targetPos.y() << "," << targetPos.z() << ")" + << "\n targetPosNow =(" << targetPosNow.x() << "," << targetPosNow.y() << "," << targetPosNow.z() << ")" + << "\n targetVel=(" << targetVel.x() << "," << targetVel.y() << "," << targetVel.z() << ")" + << "\n leadWorld=(" << leadWorld.x() << "," << leadWorld.y() << "," << leadWorld.z() << ")" + << "\n okSolve=" << (ok ? "true" : "false") + << "\n WILL_REACH=" << ((tStraight <= projectileLifeSec) ? "YES" : "NO (life too short)") + << std::endl; + } + else { + std::cout << "\n[SHOTLOG] now_ms=" << now_ms << " no target\n"; + } + } +#endif Eigen::Quaternionf q(Environment::shipState.rotation); float speedToSend = projectileSpeed + Environment::shipState.velocity; @@ -1743,8 +1831,8 @@ namespace ZL const float size = 0.5f; for (const auto& pi : pending) { const std::vector localOffsets = { - Vector3f{ -1.5f, 0.9f, 5.0f }, - Vector3f{ 1.5f, 0.9f, 5.0f } + Vector3f{ -1.5f, 0.9f - 6.0f, 5.0f }, + Vector3f{ 1.5f, 0.9f - 6.0f, 5.0f } }; Vector3f localForward = { 0, 0, -1 };