Authentic target acquisition LIVE: reticle slew + pick-ray lock + aimed zone damage (task #36)
The engine Reticle model (MUNGA/RETICLE.h [T0]) reconstructed end to end: - Mech::targetReticle is a real Reticle member bound to the TargetReticle attribute (0x1d), per the RP VTV analog (VTV.h targetReticle). - Crosshair slew: mouse -> client rect -> reticle coords (the pod stick free-aim channel's dev-box stand-in); BT_AIM="x y" pins it headless. LMB fires lasers / RMB missiles (alongside SPACE/CTRL). - Pick ray: the ACTIVE eye publishes pos + LookAtRH basis (BTSetAimCamera, L4VIDRND view-write site) + the render loop publishes proj._22; BTGetAimRay builds the world ray, Mech::PickRayHit slab-tests it against the collision template's ExtentBox via the engine's BoundingBox::HitBy (local frame; clips the Line at entry) -> world hull point. - Designation: the mech under the crosshair designates (sticky; re-hover refreshes; cleared when the target leaves the roster at burial); the entity target slots 0x37c/0x388/0x38c feed the whole weapon path. - Aimed fire: while HOT the impact point is the PICKED hull point -> the STEP-6 cylinder lookup resolves the zone under the crosshair (verified: center-aim -> head-band zone 13 dominant). Off-crosshair the sticky designation converges on center mass. - HUD: the aim group draws at the slewed position ([0x9a] translate, contained by push/pop); the designator ring tracks the target's projected point (subB9 hot / subB8 designated, BTProjectToReticle); edge arrows when off-screen/behind. - AUTHENTIC gating: no fire arc exists in the binary (FireWeapon fires whenever HasActiveTarget, part_013.c:7758) -> BT_FIRE_ARC is now an explicit OPT-IN presentation clamp; the hardwired gEnemyMech lock and the projectile path's gEnemyMech fallback are removed. - Fixed en route: every renderable rebuild stomped mCamera back to the chase eye (start-inside silently lost the cockpit camera; the aim feed exposed it). BTL4VideoRenderer::mViewInside persists the chosen view. Verified headless: BT_AIM="0 0" -> HOT lock, pick hits the hull face at exact range, aimed zones resolve; BT_AIM="0.8 0.3" -> no lock, zero damage, zero missile launches; kill chain completes to wreck + smoke. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
6988821525
commit
d78bde066d
@@ -72,6 +72,7 @@
|
||||
// AUTHENTIC GROUND MODEL ctor half (task #15): complete BoxedSolid type for the
|
||||
// collisionTemplate/collisionVolume extent reads + the template bottom lift.
|
||||
#include <BOXSOLID.hpp>
|
||||
#include <line.hpp> // Line/Ray -- the PickRayHit slab test (task #36)
|
||||
// STEP 6 cylinder hit-location table. dmgtable.hpp pulls in no subsystem
|
||||
// headers (only Plug + mechrecon + <vector>), so it is safe here -- unlike the
|
||||
// real subsystem headers, whose classes collide with mech.cpp's local stubs.
|
||||
@@ -435,6 +436,67 @@ Point3D
|
||||
return local;
|
||||
}
|
||||
|
||||
//
|
||||
// Ray pick against this mech (task #36 -- the engine Reticle's "pick point
|
||||
// intersection testing"): transform the world ray into the mech's local frame
|
||||
// (rotation part only for the direction), slab-test it against the collision
|
||||
// template's ExtentBox via the engine's own BoundingBox::HitBy (which clips
|
||||
// the Line's length to the entry distance), and return the world hit point.
|
||||
// The same collision volume the movement/collision code uses; the hit point
|
||||
// then drives the cylinder hit-location table at damage delivery (aimed
|
||||
// shots resolve to the zone under the crosshair).
|
||||
//
|
||||
Logical
|
||||
Mech::PickRayHit(const Point3D &start, const Vector3D &dir,
|
||||
Scalar max_range, Point3D *hit_world)
|
||||
{
|
||||
BoxedSolid *tmpl = GetCollisionTemplate();
|
||||
if (tmpl == 0)
|
||||
return False;
|
||||
|
||||
// world -> local: point via the full inverse, direction via the inverse
|
||||
// rotation (transform a second point and difference -- exact for the
|
||||
// orthonormal localToWorld, no scale).
|
||||
Point3D localStart;
|
||||
localStart.MultiplyByInverse(start, localToWorld);
|
||||
Point3D worldTip(start.x + dir.x, start.y + dir.y, start.z + dir.z);
|
||||
Point3D localTip;
|
||||
localTip.MultiplyByInverse(worldTip, localToWorld);
|
||||
Vector3D localDir(localTip.x - localStart.x,
|
||||
localTip.y - localStart.y,
|
||||
localTip.z - localStart.z);
|
||||
Scalar dlen = (Scalar)Sqrt(localDir.x*localDir.x + localDir.y*localDir.y
|
||||
+ localDir.z*localDir.z);
|
||||
if (dlen < 1e-6f)
|
||||
return False;
|
||||
localDir.x /= dlen; localDir.y /= dlen; localDir.z /= dlen;
|
||||
|
||||
BoundingBox box(*(const ExtentBox *)tmpl); // the template's extents
|
||||
Line ray(localStart, UnitVector(localDir.x, localDir.y, localDir.z), max_range);
|
||||
const Logical hitOK = box.HitBy(&ray); // clips ray.length to the entry
|
||||
if (BTEnvOn("BT_AIM_LOG", 0))
|
||||
{
|
||||
static int s_n = 0;
|
||||
if ((++s_n % 60) == 1)
|
||||
DEBUG_STREAM << "[pick] box=(" << tmpl->minX << ".." << tmpl->maxX
|
||||
<< ", " << tmpl->minY << ".." << tmpl->maxY
|
||||
<< ", " << tmpl->minZ << ".." << tmpl->maxZ
|
||||
<< ") lstart=(" << localStart.x << "," << localStart.y << "," << localStart.z
|
||||
<< ") ldir=(" << localDir.x << "," << localDir.y << "," << localDir.z
|
||||
<< ") hit=" << (int)hitOK << " len=" << ray.length << "\n" << std::flush;
|
||||
}
|
||||
if (!hitOK)
|
||||
return False;
|
||||
|
||||
if (hit_world != 0)
|
||||
{
|
||||
Point3D localHit;
|
||||
ray.Project(ray.length, &localHit); // start + length*dir (local)
|
||||
hit_world->Multiply(localHit, localToWorld); // local -> world
|
||||
}
|
||||
return True;
|
||||
}
|
||||
|
||||
//
|
||||
// The roster torso (sinkSourceSubsystem @0x438, ClassID 0xBC5) -- parity with
|
||||
// the binary's *(mech+0x438).
|
||||
@@ -532,7 +594,7 @@ const Mech::IndexEntry
|
||||
ATTRIBUTE_ENTRY(Mech, CurrentSpeed, legCycleSpeed), // 0x1a (existing @0x348)
|
||||
ATTRIBUTE_ENTRY(Mech, MaxRunSpeed, reverseStrideLength), // 0x1b (existing @0x34c = run/top speed)
|
||||
ATTRIBUTE_ENTRY(Mech, EyepointRotation, eyepointRotation), // 0x1c (real member -- the eye reads it per frame)
|
||||
ATTRIBUTE_ENTRY(Mech, TargetReticle, attrPad), // 0x1d
|
||||
ATTRIBUTE_ENTRY(Mech, TargetReticle, targetReticle), // 0x1d (real Reticle struct -- task #36)
|
||||
ATTRIBUTE_ENTRY(Mech, FootStep, attrPad), // 0x1e
|
||||
ATTRIBUTE_ENTRY(Mech, AnimationState, attrPad), // 0x1f
|
||||
ATTRIBUTE_ENTRY(Mech, ReplicantAnimationState, attrPad), // 0x20
|
||||
@@ -681,6 +743,12 @@ Mech::Mech(
|
||||
weaponRoster.Construct(0); // FUN_004a4df6(this+0x1ef)
|
||||
damageableSubsystems.Construct(0); // FUN_004a4e41(this+499)
|
||||
|
||||
// The target reticle (task #36): armed + pick-testing. The Reticle ctor
|
||||
// zeroes position/target; the pilot's slew + the pick feed it per frame.
|
||||
targetReticle.reticleState = Reticle::ReticleOn;
|
||||
targetReticle.pickPointingOn = True;
|
||||
targetReticle.reticleElementMask = Reticle::AllEnabledGroup;
|
||||
|
||||
for (int i = 0; i < 5; ++i) // FUN_0043ad4f x5
|
||||
{
|
||||
telemetryFilter[i].Initialize(15, 0.0f); // this[0x1f8..0x204]
|
||||
|
||||
Reference in New Issue
Block a user