From c850ffa26cedbf34531e8ef7cae8873249a386da Mon Sep 17 00:00:00 2001 From: arcattack Date: Tue, 7 Jul 2026 14:55:53 -0500 Subject: [PATCH] WAVE 7 Phase B polish: missiles launch from the mech's missile ports + look like missiles Two issues the user hit driving the Mad Cat: (1) projectiles appeared to launch from the mech's FEET -- MechWeapon::GetMuzzlePoint falls back to the mech origin (localOrigin) when the weapon's mount segment (this+0xdc) doesn't resolve, so the passed muzzle was at ground level; (2) they rendered as a thin laser-like line, not a missile. Fix: BTPushProjectile now takes the SHOOTER mech and resolves the real launch port by NAME (sitermissleport / sitelmissleport, alternating L/R for a salvo look; then torso ports, then gun ports; then a raised fallback) -- the same segment-name -> world-transform mechanism the visible laser beams use for the gun ports. The tracer is now a 3-part missile look (dim smoke trail behind + orange body + hot flame tip) instead of a single thin beam. FireWeapon (both ProjectileWeapon and MissileLauncher) passes owner as the shooter. Co-Authored-By: Claude Opus 4.8 (1M context) --- game/reconstructed/mech4.cpp | 52 +++++++++++++++++++++++++++++---- game/reconstructed/mislanch.cpp | 6 ++-- game/reconstructed/projweap.cpp | 6 ++-- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 9b1f1b4..0357ee8 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -717,12 +717,45 @@ static BTProjectile gProjectiles[64]; extern void BTPushBeam(float,float,float, float,float,float, unsigned, float, float); // Called from ProjectileWeapon / MissileLauncher::FireWeapon (via the extern below) -// with the live muzzle, the owner's locked target entity + point, the launch speed -// (|muzzleVelocity|), and the weapon's per-shot damage. +// with the live muzzle, the SHOOTER mech (to resolve the real launch port), the owner's +// locked target entity + point, the launch speed (|muzzleVelocity|), and per-shot damage. void - BTPushProjectile(const Point3D &muzzle, void *target, const Point3D &targetPos, + BTPushProjectile(const Point3D &muzzle, void *shooter, void *target, const Point3D &targetPos, Scalar speed, Scalar damage) { + // The weapon's GetMuzzlePoint falls back to the mech ORIGIN (feet) when its mount + // segment doesn't resolve, so a projectile appeared to launch from the mech's feet. + // Resolve the real launch port by NAME off the shooter (same mechanism the visible + // laser beams use for the gun ports), alternating the left/right missile ports for a + // natural salvo look; fall back to a raised (torso-height) muzzle, then the passed one. + Point3D mz = muzzle; + if (shooter != 0) + { + Mech *sm = (Mech *)shooter; + static const char *const kPorts[] = + { "sitermissleport", "sitelmissleport", "siterutorsoport", "sitelutorsoport", + "siterugunport", "sitelugunport" }; + static int s_portRot = 0; + EntitySegment *seg = 0; + int n = (int)(sizeof(kPorts)/sizeof(kPorts[0])); + for (int k = 0; k < n && seg == 0; ++k) + { + s_portRot = (s_portRot + 1) % n; + seg = sm->GetSegment(CString(kPorts[s_portRot])); + } + if (seg != 0) + { + AffineMatrix mw; + mw.Multiply(seg->GetSegmentToEntity(), sm->localToWorld); // port -> world + mz = mw; // W_Axis translation + } + else + { + mz = sm->localOrigin.linearPosition; // raise to torso height + mz.y += 12.0f; + } + } + // Bring-up: the mech's own target slot (owner+0x388) isn't populated (the visible // fire path targets the gEnemyMech global directly), so fall back to it here. Point3D tpos = targetPos; @@ -732,7 +765,7 @@ void tpos = ((Mech *)gEnemyMech)->localOrigin.linearPosition; } Vector3D d; - d.x = tpos.x - muzzle.x; d.y = tpos.y - muzzle.y; d.z = tpos.z - muzzle.z; + d.x = tpos.x - mz.x; d.y = tpos.y - mz.y; d.z = tpos.z - mz.z; Scalar len = (Scalar)sqrtf(d.x*d.x + d.y*d.y + d.z*d.z); if (getenv("BT_PROJ_LOG")) DEBUG_STREAM << "[projectile] PUSH target=" << (void*)target << " enemy=" << (void*)gEnemyMech @@ -742,7 +775,7 @@ void { if (gProjectiles[i].active) continue; BTProjectile &p = gProjectiles[i]; - p.pos = muzzle; + p.pos = mz; // resolved launch port p.dir.x = d.x/len; p.dir.y = d.y/len; p.dir.z = d.z/len; p.speed = (speed > 1.0f) ? speed : 120.0f; // |launchVelocity|; sane fallback p.traveled = 0.0f; @@ -767,7 +800,14 @@ static void Scalar step = p.speed * dt; p.pos.x += p.dir.x*step; p.pos.y += p.dir.y*step; p.pos.z += p.dir.z*step; p.traveled += step; - BTPushBeam(prev.x, prev.y, prev.z, p.pos.x, p.pos.y, p.pos.z, 0x00FFB030u, 0.18f, 1.4f); // warm tracer + // Missile look: a short smoky exhaust trail (behind) + a bright warm body + a + // hot flame tip, so it reads as a projectile rather than a thin laser line. + Vector3D bd = p.dir; + Point3D tail; tail.x = prev.x - bd.x*8.0f; tail.y = prev.y - bd.y*8.0f; tail.z = prev.z - bd.z*8.0f; + Point3D tip; tip.x = p.pos.x + bd.x*2.0f; tip.y = p.pos.y + bd.y*2.0f; tip.z = p.pos.z + bd.z*2.0f; + BTPushBeam(tail.x, tail.y, tail.z, prev.x, prev.y, prev.z, 0x00804020u, 0.20f, 3.2f); // dim smoke trail + BTPushBeam(prev.x, prev.y, prev.z, p.pos.x, p.pos.y, p.pos.z, 0x00FF6010u, 0.15f, 2.6f); // orange body + BTPushBeam(p.pos.x, p.pos.y, p.pos.z, tip.x, tip.y, tip.z, 0x00FFF0C0u, 0.15f, 1.4f); // hot flame tip Scalar dx = p.targetPos.x - p.pos.x, dy = p.targetPos.y - p.pos.y, dz = p.targetPos.z - p.pos.z; if (dx*dx + dy*dy + dz*dz < (10.0f*10.0f) || p.traveled >= p.range) diff --git a/game/reconstructed/mislanch.cpp b/game/reconstructed/mislanch.cpp index 9c0e8b7..94b3346 100644 --- a/game/reconstructed/mislanch.cpp +++ b/game/reconstructed/mislanch.cpp @@ -68,8 +68,8 @@ // WAVE 7 Phase B -- the port-side flying-projectile service (defined in mech4.cpp, a // complete-Mech TU with the render + damage path). A fired missile becomes a tracked // projectile that flies to the target + delivers this weapon's damage on impact. -extern void BTPushProjectile(const Point3D &muzzle, void *target, const Point3D &targetPos, - Scalar speed, Scalar damage); +extern void BTPushProjectile(const Point3D &muzzle, void *shooter, void *target, + const Point3D &targetPos, Scalar speed, Scalar damage); //########################################################################### //########################################################################### @@ -246,7 +246,7 @@ void MissileLauncher::FireWeapon() // slot isn't populated (bring-up); missileCount missiles per salvo. int nmiss = (missileCount > 0 && missileCount < 40) ? missileCount : 1; for (int i = 0; i < nmiss; ++i) - BTPushProjectile(muzzle, target, targetPos, speed, damageData.damageAmount); + BTPushProjectile(muzzle, o, target, targetPos, speed, damageData.damageAmount); simulationFlags |= 0x1; // replication-dirty Check_Fpu(); diff --git a/game/reconstructed/projweap.cpp b/game/reconstructed/projweap.cpp index 555a940..986982b 100644 --- a/game/reconstructed/projweap.cpp +++ b/game/reconstructed/projweap.cpp @@ -88,8 +88,8 @@ #include // WAVE 7 Phase B -- the port-side flying-projectile service (defined in mech4.cpp). -extern void BTPushProjectile(const Point3D &muzzle, void *target, const Point3D &targetPos, - Scalar speed, Scalar damage); +extern void BTPushProjectile(const Point3D &muzzle, void *shooter, void *target, + const Point3D &targetPos, Scalar speed, Scalar damage); //############################################################################# @@ -624,7 +624,7 @@ void + launchVelocity.z*launchVelocity.z); // Always launch -- BTPushProjectile falls back to gEnemyMech when the owner's target // slot isn't populated (bring-up). - BTPushProjectile(muzzle, target, targetPos, speed, damageData.damageAmount); + BTPushProjectile(muzzle, o, target, targetPos, speed, damageData.damageAmount); simulationFlags |= 0x1; // replication-dirty Check_Fpu();