diff --git a/game/reconstructed/emitter.cpp b/game/reconstructed/emitter.cpp index 4569014..aa0d31d 100644 --- a/game/reconstructed/emitter.cpp +++ b/game/reconstructed/emitter.cpp @@ -390,7 +390,7 @@ void case 2: // Loaded -- ready; fire on the trigger's rising edge if (fireEdge) { - if (useConfiguredPip && HasActiveTarget()) // this+0x3E0 (Loaded->Firing gate) && entity+0x388 + if (viewFireEnable && HasActiveTarget()) // this+0x3E0 (Loaded->Firing gate: enabled in the CURRENT look view) && entity+0x388 { weaponAlarm.SetLevel(0); // -> Firing FireWeapon(); // (*vtable+0x48)() @@ -833,8 +833,8 @@ Emitter::Emitter( } // (E4 removed) the former `firingArmed = 1` wrote this+0x3e8 == MechWeapon::recoil, - // NOT the Loaded->Firing gate: the binary gate reads useConfiguredPip@0x3E0 (set by - // the MechWeapon ctor from usesExternalModel), so no separate arming is needed. + // NOT the Loaded->Firing gate: the binary gate reads viewFireEnable@0x3E0 (set by + // the MechWeapon ctor / the look-state commit), so no separate arming is needed. weaponAlarm.SetLevel(3); // Loading beamEndpoint = Point3D(0.0f, 0.0f, 0.0f); // 0x460 <- (0,0,0) beamFlag = 0; // 0x46c diff --git a/game/reconstructed/emitter.hpp b/game/reconstructed/emitter.hpp index be61119..8559302 100644 --- a/game/reconstructed/emitter.hpp +++ b/game/reconstructed/emitter.hpp @@ -308,7 +308,7 @@ class NotationFile; // * beamHitPoint/beamImpact/beamImpactScalar -> binary writes into the inherited // Damage damageData (0x3C8/0x3B0/0x3AC); the recon assignments were dead -> deleted // * beamColor -> never read (WriteUpdateRecord builds colour from targetEntity) - // * firingArmed -> the Loaded->Firing gate reads inherited useConfiguredPip@0x3E0 + // * firingArmed -> the Loaded->Firing gate reads inherited viewFireEnable@0x3E0 // * energyRampTime -> binary writes the base slot voltageScale@0x310; recon computes a local // * beamLengthRatio -> is beamScale.z@0x434 int seekVoltageIndex; // @0x3F0 (param_1[0xfc]) current charge-curve index diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index 441ad76..6ed9e9c 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -731,7 +731,7 @@ const Mech::IndexEntry ATTRIBUTE_ENTRY(Mech, RadarRange, radarRange), // 0x2f (radar scale) ATTRIBUTE_ENTRY(Mech, RadarLinearPosition, radarLinearPosition), // 0x30 (Point3D* -> localOrigin) ATTRIBUTE_ENTRY(Mech, RadarAngularPosition, radarAngularPosition),// 0x31 (Quaternion* -> localOrigin) - ATTRIBUTE_ENTRY(Mech, RearFiring, attrPad), // 0x32 + ATTRIBUTE_ENTRY(Mech, RearFiring, rearFiring), // 0x32 (task #68: binary id 50 @0x410) ATTRIBUTE_ENTRY(Mech, RequestDuckAnimation, attrPad), // 0x33 ATTRIBUTE_ENTRY(Mech, UnstablePercentage, unstablePercentage), // 0x34 (task #66: the instability alarm; binary id 52 @0x3F0) ATTRIBUTE_ENTRY(Mech, SuperStop, attrPad), // 0x35 @@ -853,7 +853,7 @@ Mech::Mech( mechNameFilter.Initialize(); // FUN_00435a7c(this+0xdb) masterAlarm = AlarmIndicator(0x21); // FUN_0041b9ec(this+0xe7,0x21) - stateFlags = 0; // Wword(0xff) + rearFiring = 0; // (task #68) ORed from the weapons below // (F7 correction) the binary's 0x400 = FLT_MAX init is DistanceToMissile's // "no missile" far default (attr id 56), NOT a maxSpeed -- the old member // is retired; distanceToMissile (init below) owns the slot's meaning. @@ -1329,11 +1329,17 @@ Mech::Mech( } for (int id = 2; id < subsystemCount; ++id) // weapon roster (0x511830 = MechWeapon) { - SubProxy *s = (SubProxy *)subsystemArray[id]; - if (s != 0 && s->IsDerivedFrom(0x511830)) + // (task #68) REAL derivation test via the mechweap bridge -- the old + // SubProxy::IsDerivedFrom stub returned 0, so this loop never ran + // (empty weaponRoster, no capability OR). + extern int BTWeaponIsRearFiring(Subsystem *sub); // -1 = not a weapon + int rf = BTWeaponIsRearFiring(subsystemArray[id]); + if (rf >= 0) { - weaponRoster.Add((Subsystem *)s); - stateFlags |= s->capabilityFlags; // |= *(s+0x334) -> Wword(0x104) + weaponRoster.Add(subsystemArray[id]); + // mech.RearFiring |= weapon.rearFiring (binary ctor + // @part_012.c:10220: mech[0x104] |= *(weapon+0x334)) + rearFiring |= rf; } } for (int id = 2; id < subsystemCount; ++id) // damageable roster @@ -1403,11 +1409,12 @@ Mech::Mech( unstableHighVelocityEffect = model->unstableHighVelocityEffect; unstableStopedTurnEffect = model->unstableStopedTurnEffect; - // Angle fields converted from degrees to radians. - Wword(0x159) = (int)(model->lookLeftAngle * DegreesToRadians); // +0x50 - Wword(0x15a) = (int)(model->lookRightAngle * DegreesToRadians); // +0x54 - Wword(0x15b) = (int)(model->lookFrontAngle * DegreesToRadians); // +0x58 - Wword(0x15c) = (int)(model->lookBackAngle * DegreesToRadians); // +0x5c + // Angle fields converted from degrees to radians. (task #68: REAL members + // now -- the look-state commit consumes them; were Wword scratch parks.) + lookLeftAngle = model->lookLeftAngle * DegreesToRadians; // +0x50 -> @0x564 + lookRightAngle = model->lookRightAngle * DegreesToRadians; // +0x54 -> @0x568 + lookFrontAngle = model->lookFrontAngle * DegreesToRadians; // +0x58 -> @0x56C + lookBackAngle = model->lookBackAngle * DegreesToRadians; // +0x5c -> @0x570 // The update-record deadbands (task #3; the struct is now the verified // 200-byte overlay, so the named fields ARE the raw offsets). updateTurnAngleDeadband = model->updateTurnDegreeDiffrence * DegreesToRadians; // @0x770 diff --git a/game/reconstructed/mech.hpp b/game/reconstructed/mech.hpp index 7d27edb..291393a 100644 --- a/game/reconstructed/mech.hpp +++ b/game/reconstructed/mech.hpp @@ -299,6 +299,7 @@ struct ShotDescriptor { friend class Mech__DamageZone; // per-zone damage code reads the roster / segment table friend struct MechBaseLayoutCheck; // base-region layout locks (mech4.cpp; P3 STEP-6 audit) + friend void BTCommitLookState(void *, int); // task #68: the look-state commit (mech4.cpp) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Shared Data Support // @@ -726,7 +727,19 @@ protected: // (F7) `Scalar maxSpeed @0x400` was a MISREAD -- the binary attribute // table names 0x400 DistanceToMissile (id 56; FLT_MAX = far default); // the member is retired in favor of distanceToMissile below. - int stateFlags; // @0x410 this[0x104] (binary attr table: RearFiring, id 50) + // (task #68) REAR-FIRING (binary attr id 50 @0x410; the old name was + // `stateFlags`): OR of every mounted weapon's rearFiring flag -- "this + // mech carries a rear arsenal". The look-back view arms those weapons. + int rearFiring; // @0x410 this[0x104] + // (task #68) the authored look-view angles (model record +0x50..0x5c, + // ctor converts degrees->radians; binary @0x564..0x570 -- previously + // parked in the Wword scratch bank). Consumed by the look-state + // commit: side views yaw by these, look-back = yaw pi + lookBackAngle + // pitch, look-down pitches by lookFrontAngle. + Scalar lookLeftAngle; // @0x564 + Scalar lookRightAngle; // @0x568 + Scalar lookFrontAngle; // @0x56C + Scalar lookBackAngle; // @0x570 int throttleState; // @0x4a4 this[0x129] = 2 (initial) int heatLevel; // @0x518 this[0x146] int heatCapacity; // @0x51c this[0x147] = 0.6 * heatLevel diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 3e4b965..9c25618 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -651,6 +651,7 @@ static int gBTPPCKey = 0; static int gBTMissileKey = 0; static int gBTPinkyKey = 0; // key '4' = the pod's 4th fire button (Pinky 0x45) int gBTModeCycle = 0; // 'M' edge: cycle the control mode (mapper consumes) +int gBTLookBehind = 0; // 'V' held: the pod's rear-view button (task #68) float gBTTwistAxis = 0.0f; // Q/E torso-twist deflection (assisted-mode stick X) int gBTTorsoRecenter = 0; // 'X' edge: pulse the authentic torso recenter (mapper consumes) static int gBTConfigKey = 0; // task #6: HOLD 'G' = the weapon-configure button @@ -2606,6 +2607,10 @@ void const int mNow = focused && (pAsync('M') & dn) ? 1 : 0; if (mNow && !sPrevM) gBTModeCycle = 1; // edge -> one cycle sPrevM = mNow; + // (task #68) 'V' HELD = look behind (the pod's rear-view + // button; releases back to the forward view). Rear-mounted + // weapons (blackhawk/owens back racks) fire only in it. + gBTLookBehind = focused && (pAsync('V') & dn) ? 1 : 0; const int tw = (focused && (pAsync('E') & dn) ? 1 : 0) - (focused && (pAsync('Q') & dn) ? 1 : 0); static float sTwist = 0.0f; @@ -6148,3 +6153,80 @@ void BTReportIncomingMissile(Entity *target, Scalar range) ((Mech *)target)->ReportIncomingMissile(range); } } + +//########################################################################### +// Rear-fire / look-view bridges (task #68) +// +// The binary's controls mapper (part_013.c:396-459) runs a five-state LOOK +// machine off the pod's look buttons: on a state change it re-aims the +// eyepoint (mech+0x360 = EyepointRotation, consumed by DPLEyeRenderable) +// and re-arms each weapon's view-fire enable (+0x3E0): FORWARD view enables +// the non-rear weapons, LOOK-BACK enables the REAR-mounted ones (+0x334, +// set from the mount segment's 'b' marker -- sitelbgunport/siterbgunport), +// side/down views disable all fire. Complete-Mech/Weapon TU bridges; the +// mapper calls BTCommitLookState on each state change. +//########################################################################### +int BTWeaponMountIsRear(void *ownerMech, int segIndex) +{ + Mech *m = (Mech *)ownerMech; + if (m == 0) + return 0; + EntitySegment *seg = m->GetSegment(segIndex); + if (seg == 0) + return 0; + const char *segname = seg->GetName(); // CString -> const char* + if (segname == 0) + return 0; + // the binary marker @0x511aa2 is the single character "b" (the back + // gun-port site names are the only port names containing it) + int rear = (strstr(segname, "b") != 0) ? 1 : 0; + if (getenv("BT_PROJ_LOG")) { static int s_rn=0; if (s_rn++<40) + DEBUG_STREAM << "[rearfire] segIndex=" << segIndex << " name='" + << segname << "' rear=" << rear << "\n" << std::flush; } + return rear; +} + +void BTCommitLookState(void *mech_v, int look_state) +{ + Mech *m = (Mech *)mech_v; + if (m == 0) + return; + + // eyepoint per state (binary case switch: yaw = the authored side angles, + // look-back = yaw pi (0x40490fdb) + lookBackAngle pitch, look-down = + // lookFrontAngle pitch) + Scalar pitch = 0.0f, yaw = 0.0f; + switch (look_state) + { + case 1: /*LookLeft*/ yaw = m->lookLeftAngle; break; + case 2: /*LookRight*/ yaw = m->lookRightAngle; break; + case 3: /*LookBehind*/ yaw = PI; // binary 0x40490fdb + pitch = m->lookBackAngle; break; + case 4: /*LookDown*/ pitch = m->lookFrontAngle; break; + default: break; // LookNone: identity + } + m->eyepointRotation = EulerAngles( + Radian(Radian::Normalize(pitch)), + Radian(Radian::Normalize(yaw)), + Radian(0.0f)); + + // weapon view-fire enables (binary: fwd = !rear, back = rear, else 0) + { + extern int BTWeaponIsRearFiring(Subsystem *sub); // -1 = not a weapon + extern void BTWeaponSetViewFireEnable(Subsystem *sub, int enable); + for (int id = 2; id < m->GetSubsystemCount(); ++id) + { + Subsystem *sub = m->GetSubsystem(id); + int rf = BTWeaponIsRearFiring(sub); + if (rf < 0) + continue; + BTWeaponSetViewFireEnable(sub, + (look_state == 0) ? (rf == 0) : + (look_state == 3) ? rf : 0); + } + } + if (getenv("BT_KEY_LOG")) + DEBUG_STREAM << "[look] state=" << look_state + << " rearFiring=" << m->rearFiring + << " yaw=" << yaw << " pitch=" << pitch << "\n" << std::flush; +} diff --git a/game/reconstructed/mechmppr.cpp b/game/reconstructed/mechmppr.cpp index 6b946a2..959958a 100644 --- a/game/reconstructed/mechmppr.cpp +++ b/game/reconstructed/mechmppr.cpp @@ -697,6 +697,12 @@ void } throttlePosition = (key_throttle >= 0.0f) ? key_throttle : -key_throttle; reverseThrust = (key_throttle < 0.0f) ? 1 : 0; + // (task #68) look-behind: hold 'V' = the pod's rear-view button. + // (The other look buttons stay unbound on the keyboard rig.) + { + extern int gBTLookBehind; + lookBehind = gBTLookBehind; + } // CONTROL-MODE AXIS ROUTING (2026-07-13, the pod mapping): in // BASIC the stick steers the legs (turn) and the torso auto- // centers; in STANDARD/VETERAN the stick is the TORSO (free aim / @@ -893,14 +899,16 @@ void if (lookState != previousLookState) { - // TODO(look-eyepoint): the commit block below writes the eyepoint pose at - // binary offsets whose Mech-member mapping is CONFLICTED (mech+0x360 is - // claimed by mechName, +0x410 by stateFlags, +0x7bc by weaponRoster -- - // and the old draft even routed them through the TORSO pointer). Arbitrate - // those labels against the raw before enabling; the port has no eyepoint - // consumer yet. Look-state SELECTION above stays live (this-only). - DEBUG_STREAM << "[mppr] look state -> " << lookState - << " (eyepoint commit deferred)\n" << std::flush; + // (task #68) the look commit is LIVE: the conflicted labels are + // arbitrated (mech+0x360 = EyepointRotation -- the eye renderable + // consumes it; +0x410 = RearFiring; the +0x7bc children = the WEAPON + // roster, not cameras). BTCommitLookState (mech4.cpp complete-type + // TU) re-aims the eyepoint and re-arms each weapon's viewFireEnable: + // forward view = the non-rear weapons, LOOK-BACK = the rear-mounted + // ones, side/down = none. + extern void BTCommitLookState(void *mech, int look_state); + BTCommitLookState((void *)mech, (int)lookState); + DEBUG_STREAM << "[mppr] look state -> " << lookState << "\n" << std::flush; } else if (0) // DEFERRED body (kept for the reconstruction record) { diff --git a/game/reconstructed/mechweap.cpp b/game/reconstructed/mechweap.cpp index d6d55c1..10db641 100644 --- a/game/reconstructed/mechweap.cpp +++ b/game/reconstructed/mechweap.cpp @@ -141,7 +141,7 @@ const MechWeapon::IndexEntry ATTRIBUTE_ENTRY(MechWeapon, PipColor, pipColor), // 0x18 @0x33C ATTRIBUTE_ENTRY(MechWeapon, PipExtendedRange, pipExtendedRange), // 0x19 @0x348 ATTRIBUTE_ENTRY(MechWeapon, EstimatedReadyTime, estimatedReadyTime),// 0x1A @0x330 - ATTRIBUTE_ENTRY(MechWeapon, RearFiring, usesExternalModel), // 0x1B @0x334 + ATTRIBUTE_ENTRY(MechWeapon, RearFiring, rearFiring), // 0x1B @0x334 (task #68: real semantics) ATTRIBUTE_ENTRY(MechWeapon, WeaponState, weaponAlarm), // 0x1C @0x350 ATTRIBUTE_ENTRY(MechWeapon, OutputVoltage, rechargeLevel) // 0x1D (port alias) }; @@ -315,9 +315,18 @@ MechWeapon::MechWeapon( // removed. The muzzle segment index is the inherited base slot this+0xdc (used by // GetMuzzlePoint); the pip mount is gated by useConfiguredPip in DrawWeaponPip. - // usesExternalModel: True if the model name contains the marker substring - // (DAT_00511aa2); selects whether the configured pip is drawn. - usesExternalModel = (strstr(GetName(), "EXT") != 0) ? True : False; + // (task #68) REAR-FIRING: the binary ctor (@004b99a8 tail, part_013.c:6913- + // 6930) resolves the weapon's MOUNT SEGMENT page name and tests it for the + // marker 'b' (DAT_00511aa2) -- the back gun ports (sitelbgunport / + // siterbgunport). Rear-mounted weapons fire only into the LOOK-BACK view. + { + extern int BTWeaponMountIsRear(void *ownerMech, int segIndex); + rearFiring = BTWeaponMountIsRear(owner, GetSegmentIndex()) ? True : False; + if (getenv("BT_PROJ_LOG")) { static int s_rf=0; if (s_rf++<40) + DEBUG_STREAM << "[rearfire] weapon '" << GetName() + << "' seg=" << GetSegmentIndex() + << " rear=" << (int)rearFiring << "\n" << std::flush; } + } fireImpulse = 0.0f; // 0x31C // task #6: the binary MechWeapon ctor defaults its controls-destination @@ -329,7 +338,7 @@ MechWeapon::MechWeapon( rechargeLevel = 1.0f; // 0x320 (fully charged) previousFireImpulse = 0.0f; // 0x3A4 recoil = 0.0f; // 0x3E8 - useConfiguredPip = (usesExternalModel == 0); // 0x3E0 + viewFireEnable = (rearFiring == 0); // 0x3E0 (spawn in the forward view) estimatedReadyTime = 0; // 0x330 (binary attr "EstimatedReadyTime") Check_Fpu(); @@ -887,3 +896,22 @@ int return True; } + +//############################################################################# +// Rear-fire bridges (task #68) -- complete-MechWeapon-type helpers for the +// Mech ctor's rear-arsenal OR and the mapper's look-state commit (both TUs +// hold only Subsystem*; the old SubProxy::IsDerivedFrom stub returned 0, so +// the roster loop's capabilityFlags read never ran). +//############################################################################# +int BTWeaponIsRearFiring(Subsystem *sub) // -1 = not a MechWeapon +{ + if (sub == 0 || !sub->IsDerivedFrom(MechWeapon::ClassDerivations)) + return -1; + return (((MechWeapon *)sub)->GetRearFiring() != 0) ? 1 : 0; +} + +void BTWeaponSetViewFireEnable(Subsystem *sub, int enable) +{ + if (sub != 0 && sub->IsDerivedFrom(MechWeapon::ClassDerivations)) + ((MechWeapon *)sub)->SetViewFireEnable(enable); +} diff --git a/game/reconstructed/mechweap.hpp b/game/reconstructed/mechweap.hpp index 5a9a9f3..452bc61 100644 --- a/game/reconstructed/mechweap.hpp +++ b/game/reconstructed/mechweap.hpp @@ -398,7 +398,17 @@ class CockpitHud; Scalar weaponRange; // @0x32C resource WeaponRange (configured max) int estimatedReadyTime; // @0x330 reset 0 (binary attr 0x1A "EstimatedReadyTime" // names this slot -- resolves the old pipState TODO) - Logical usesExternalModel; // @0x334 the "EXT" model-name marker; the binary attr + public: + // (task #68) rear-fire accessors for the complete-type bridges + int GetRearFiring() const { return (int)rearFiring; } + void SetViewFireEnable(int enable) { viewFireEnable = enable ? True : False; } + protected: + Logical rearFiring; // @0x334 REAR-MOUNTED weapon (task #68): the binary ctor + // sets it iff the MOUNT SEGMENT's page name contains 'b' + // (marker @0x511aa2; the back ports sitelbgunport/ + // siterbgunport -- blackhawk + owens author them). The + // old "EXT in the weapon name" was a double misread. + // The binary attr // 0x1B names this slot "RearFiring" (an EXT/rear-mount // weapon fires backward) int pipPosition; // @0x338 resource PipPosition @@ -415,7 +425,11 @@ class CockpitHud; Scalar heatCostToFire; // @0x3D8 resource HeatCostToFire Scalar rechargeRate; // @0x3DC resource RechargeRate - Logical useConfiguredPip; // @0x3E0 = (usesExternalModel == 0) (best-effort) + Logical viewFireEnable; // @0x3E0 "enabled in the CURRENT look view" (task #68): + // forward view = !rearFiring, rear view = rearFiring, + // side/down views = 0 (the mapper look-state commit + // toggles it). Gates BOTH firing (the emitter + // Loaded->Firing gate reads it) and the HUD pip. ResourceDescription::ResourceID explosionResourceID; // @0x3E4 resource ExplosionModelFile public: @@ -433,7 +447,7 @@ class CockpitHud; // collided with the Emitter subclass's own fields at 0x3F0+; removed: // * segmentReference -> the muzzle segment is the inherited base slot this+0xdc // (GetMuzzlePoint resolves it live via the mech4 BTResolveWeaponMuzzle bridge) - // * pipSegment -> DrawWeaponPip gates on useConfiguredPip + // * pipSegment -> DrawWeaponPip gates on viewFireEnable // * hasTarget -> dead (HasActiveTarget reads owner+0x388) // * targetPoint -> GetTargetPosition reads owner+0x37c // * muzzlePoint -> resolved live by GetMuzzlePoint diff --git a/game/reconstructed/mislanch.cpp b/game/reconstructed/mislanch.cpp index 157c02a..1c349eb 100644 --- a/game/reconstructed/mislanch.cpp +++ b/game/reconstructed/mislanch.cpp @@ -267,6 +267,12 @@ void MissileLauncher::FireWeapon() { Check(this); + // (task #68) the view gate: a weapon disabled in the CURRENT look view + // (rear rack in the forward view, anything in a side view) does not fire + // -- the same +0x3E0 gate the emitter's Loaded->Firing transition reads. + if (!viewFireEnable) + return; + // THE FIRING HEAT (task #9): heatCostToFire RAW into the launcher's own // pendingHeat (pre-scaled 1e7-unit resources: SRM6 = 5e7, LRM15 = 6.5e7) // -- the binary's add @part_013.c:8744-8747, gated on the heatable check. diff --git a/game/reconstructed/projweap.cpp b/game/reconstructed/projweap.cpp index 0717f13..593cd02 100644 --- a/game/reconstructed/projweap.cpp +++ b/game/reconstructed/projweap.cpp @@ -703,6 +703,11 @@ void { Check(this); + // (task #68) the view gate: a weapon disabled in the CURRENT look view + // does not fire (see mislanch.cpp / the emitter's Loaded->Firing gate). + if (!viewFireEnable) + return; + // THE FIRING HEAT (task #9; was "a separate pre-existing gap"): the // binary adds heatCostToFire RAW to the weapon's own pendingHeat -- // projectile resources author PRE-scaled 1e7-unit heat (AFC100 = 6.5e7)