diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 107fbd1..2f1ba3b 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -1916,6 +1916,19 @@ static void // query every frame, FUN_0042291c, and DETONATES on geometry). Ray the // flight step against the terrain/cave solids -- a lofted round in a // low cavern bursts on the CEILING instead of punching through it. + // (#175) TWO queries, earliest hit wins: the visual-heightfield march + // (BTGroundRayHit) alone let rounds tunnel through everything that + // blocks the WALK but is not heightfield -- the arena1-garage class + // (open-questions: the visual mesh is not the collision truth), and + // the field case that filed the ticket (LRM duel through a mound). + // The STATIC COLLISION SOLID TREE (WorldStructurePick -> + // Mover::FindStaticSolidHitBy) is the same static-world tail of the + // engine collider FUN_0042291c swept, so the round now detonates on + // exactly the geometry that stops a walking mech. A struck solid's + // OWNING entity takes the direct dispatch (binary missile contact + // @004be078 dispatches at the struck entity -- trucks/props are + // missile-killable); plain terrain owners ignore it. + // BT_WORLDHIT=0 restores the heightfield-only behavior (A/B lever). { extern bool BTGroundRayHit(float,float,float, float,float,float, float, float*,float*,float*); @@ -1926,8 +1939,47 @@ static void Vector3D rd; rd.x = p.vel.x/p.speed; rd.y = p.vel.y/p.speed; rd.z = p.vel.z/p.speed; float hx, hy, hz; + int worldHit = 0; // 1 = heightfield, 2 = static solid + Entity *structOwner = 0; if (BTGroundRayHit(prev.x, prev.y, prev.z, rd.x, rd.y, rd.z, step + 1.0f, &hx, &hy, &hz)) + worldHit = 1; + { + static int s_wh = -1; + if (s_wh < 0) + { + const char *wv = getenv("BT_WORLDHIT"); + s_wh = (wv != 0 && *wv == '0') ? 0 : 1; + } + if (s_wh && p.shooter != 0 && BTIsRegisteredMech(p.shooter)) + { + Point3D sHit; + Entity *sOwn = 0; + Vector3D sd(rd.x, rd.y, rd.z); + if (((Mech *)p.shooter)->WorldStructurePick(prev, sd, + step + 1.0f, &sHit, &sOwn)) + { + // earlier than the heightfield hit (or the only hit)? + int take = 1; + if (worldHit == 1) + { + const float gd2 = (hx-prev.x)*(hx-prev.x) + + (hy-prev.y)*(hy-prev.y) + (hz-prev.z)*(hz-prev.z); + const float sd2 = (float)((sHit.x-prev.x)*(sHit.x-prev.x) + + (sHit.y-prev.y)*(sHit.y-prev.y) + + (sHit.z-prev.z)*(sHit.z-prev.z)); + take = (sd2 < gd2); + } + if (take) + { + worldHit = 2; + structOwner = sOwn; + hx = (float)sHit.x; hy = (float)sHit.y; hz = (float)sHit.z; + } + } + } + } + if (worldHit != 0) { // burst on the rock: the round's own DETONATION (the binary // missile detonates on ANY geometry, @004bef78) + a tight @@ -1955,12 +2007,40 @@ static void BTApplySplashDamage(p.shooter, p.weaponSubsys, hp, 0, sdmg); } } + // (#175) a struck STRUCTURE's owning entity takes the direct + // dispatch, exactly as the binary missile contact does + // (@004be078: zone=-1 TakeDamageMessage straight at the + // struck entity, no class test) -- missiles kill trucks/ + // props. Plain terrain owners have no zones and ignore it. + int ownerDispatched = 0; + if (worldHit == 2 && structOwner != 0 + && !BTIsRegisteredMech(structOwner) + && structOwner->damageZoneCount > 0 + && p.damage > 0.0f) + { + ownerDispatched = 1; + Damage wdmg; + wdmg.damageType = (Enumeration)p.damageType; + wdmg.damageAmount = p.damage; + wdmg.burstCount = 1; + wdmg.impactPoint.x = hx; wdmg.impactPoint.y = hy; + wdmg.impactPoint.z = hz; + Entity::TakeDamageMessage take_damage( + Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage), + (p.shooter != 0) ? p.shooter->GetEntityID() : EntityID::Null, + -1 /*icon handler maps -1 -> zone 0*/, wdmg); + structOwner->Dispatch(&take_damage); + } extern void BTPfxTrailPuff(int, float, float, float, float, float, float, int); for (int pf = 0; pf < 4; ++pf) BTPfxTrailPuff(0, hx, hy, hz, -rd.x, -rd.y, -rd.z, 2); if (getenv("BT_PROJ_LOG")) - DEBUG_STREAM << "[projectile] WORLD burst at(" << hx << "," - << hy << "," << hz << ")" << std::endl; + DEBUG_STREAM << "[projectile] WORLD burst (" + << ((worldHit == 2) ? "struct" : "terrain") + << (ownerDispatched ? " owner-dispatched" : "") + << ") at(" << hx << "," << hy << "," << hz << ")" + << " dmg=" << p.damage << " lead=" << p.salvoLead + << std::endl; p.active = 0; continue; }