diff --git a/restoration/source410/BT/MECHWEAP.CPP b/restoration/source410/BT/MECHWEAP.CPP index 1e0acd2c..58da218a 100644 --- a/restoration/source410/BT/MECHWEAP.CPP +++ b/restoration/source410/BT/MECHWEAP.CPP @@ -1,394 +1,421 @@ -//===========================================================================// -// File: mechweap.cpp // -// Project: BattleTech Brick: Mech weapons // -// Contents: MechWeapon -- the base weapon subsystem // -//---------------------------------------------------------------------------// -// Copyright (C) 1995, Virtual World Entertainment, Inc. // -// All Rights reserved worldwide // -// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // -//===========================================================================// - -#include -#pragma hdrstop - -#if !defined(MECHWEAP_HPP) -# include -#endif - -#if !defined(MECH_HPP) -# include -#endif - -#if !defined(SEGMENT_HPP) -# include -#endif - -#include -#include - -#if !defined(RANDOM_HPP) -# include -#endif - -Derivation - MechWeapon::ClassDerivations( - PoweredSubsystem::ClassDerivations, - "MechWeapon" - ); - -// -//############################################################################# -// Message handlers. MechWeapon publishes no handlers of its own yet (the -// weapon fire/damage handler wave), but the set MUST be a real defined object: -// the surviving CODE PPC.CPP binds the inherited name (`PPC::MessageHandlers`) -// into PPC::DefaultData, and a declared-but-undefined static links as a -// zero-filled common block -- the same silent-NULL trap as the -// Emitter::AttributeIndex crash (see MECHWEAP.NOTES.md). -//############################################################################# -// -MechWeapon::MessageHandlerSet - MechWeapon::MessageHandlers( - 0, - NULL, - Subsystem::MessageHandlers - ); - -// -//############################################################################# -// Attribute Support -- the 1995 binary table (@0x511890), IDs pinned so the -// streamed per-mech control mappings resolve: TriggerState (0x13) is what the -// authored fire buttons bind (the controls push then writes the trigger state -// straight into fireImpulse each frame). The pads bridge our chain gap -// 2..0x11 (parents publish nothing yet) -- REQUIRED, because Build leaves -// uncovered gap slots uninitialized; they target rechargeLevel, harmless if a -// stray mapping ever binds one (BT411 shipped the same). Pip*/WeaponRange -// pads-to-rechargeLevel where the member isn't reconstructed yet are noted. -//############################################################################# -// -#define MECHWEAP_PAD(n, name) \ - { (int)MechWeapon::MechWeaponPadFirstAttributeID + n, name, \ - (Simulation::AttributePointer)&MechWeapon::rechargeLevel } - -const MechWeapon::IndexEntry - MechWeapon::AttributePointers[]= -{ - MECHWEAP_PAD( 0, "MechWeaponPad02"), - MECHWEAP_PAD( 1, "MechWeaponPad03"), - MECHWEAP_PAD( 2, "MechWeaponPad04"), - MECHWEAP_PAD( 3, "MechWeaponPad05"), - MECHWEAP_PAD( 4, "MechWeaponPad06"), - MECHWEAP_PAD( 5, "MechWeaponPad07"), - MECHWEAP_PAD( 6, "MechWeaponPad08"), - MECHWEAP_PAD( 7, "MechWeaponPad09"), - MECHWEAP_PAD( 8, "MechWeaponPad0A"), - MECHWEAP_PAD( 9, "MechWeaponPad0B"), - MECHWEAP_PAD(10, "MechWeaponPad0C"), - MECHWEAP_PAD(11, "MechWeaponPad0D"), - MECHWEAP_PAD(12, "MechWeaponPad0E"), - MECHWEAP_PAD(13, "MechWeaponPad0F"), - MECHWEAP_PAD(14, "MechWeaponPad10"), - MECHWEAP_PAD(15, "MechWeaponPad11"), - ATTRIBUTE_ENTRY(MechWeapon, PercentDone, rechargeLevel), // 0x12 - ATTRIBUTE_ENTRY(MechWeapon, TriggerState, fireImpulse), // 0x13 - ATTRIBUTE_ENTRY(MechWeapon, DistanceToTarget, rangeToTarget), // 0x14 - ATTRIBUTE_ENTRY(MechWeapon, TargetWithinRange, targetWithinRange), // 0x15 - ATTRIBUTE_ENTRY(MechWeapon, WeaponRange, effectiveRange), // 0x16 - { (int)MechWeapon::PipPositionAttributeID, "PipPosition", // 0x17 (pip - (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, // members - { (int)MechWeapon::PipColorAttributeID, "PipColor", // 0x18 not yet - (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, // recon- - { (int)MechWeapon::PipExtendedRangeAttributeID, "PipExtendedRange", // 0x19 structed) - (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, - ATTRIBUTE_ENTRY(MechWeapon, EstimatedReadyTime, estimatedReadyTime),// 0x1A - ATTRIBUTE_ENTRY(MechWeapon, RearFiring, rearFiring), // 0x1B - ATTRIBUTE_ENTRY(MechWeapon, WeaponState, weaponAlarm) // 0x1C -}; - -MechWeapon::AttributeIndexSet - MechWeapon::AttributeIndex( - ELEMENTS(MechWeapon::AttributePointers), - MechWeapon::AttributePointers, - Subsystem::AttributeIndex - ); - -MechWeapon::SharedData - MechWeapon::DefaultData( - MechWeapon::ClassDerivations, - MechWeapon::MessageHandlers, - MechWeapon::AttributeIndex, - Subsystem::StateCount - ); - -MechWeapon::MechWeapon( - Mech *owner, - int subsystem_ID, - SubsystemResource *subsystem_resource, - SharedData &shared_data -): - PoweredSubsystem(owner, subsystem_ID, subsystem_resource, shared_data) -{ - Check(owner); - Check_Pointer(subsystem_resource); - - rechargeRate = subsystem_resource->rechargeRate; - weaponRange = subsystem_resource->weaponRange; - damageAmount = subsystem_resource->damageAmount; - damageType = subsystem_resource->damageType; - heatCostToFire = subsystem_resource->heatCostToFire; - explosionResourceID = subsystem_resource->explosionModelFile; - targetWithinRange = False; - - damageData.damageType = damageType; - damageData.damageAmount = damageAmount; - - // - // Fire-machine state: spawn fully charged and Loaded (ready to fire). - // - fireImpulse = 0.0f; - previousFireImpulse = 0.0f; - rechargeLevel = 1.0f; - rangeToTarget = 0.0f; - effectiveRange = weaponRange; - estimatedReadyTime = 0; - recoil = 0.0f; - weaponAlarm.Initialize(WeaponStateCount); - weaponAlarm.SetLevel(LoadedState); - - // - // REAR-FIRING resolve (the binary ctor tail @004b99a8): a weapon is - // rear-mounted when its MOUNT SEGMENT's site name carries the 'b' (back) - // marker -- the back gun ports (sitelbgunport / siterbgunport) are the only - // port names containing it. Rear-mounted weapons fire only into the - // LOOK-BACK view; everything else fires forward. The look-state commit - // (Mech::CommitLookState) re-arms viewFireEnable on every view change; a - // weapon spawns armed for the forward view. - // - rearFiring = False; - { - EntitySegment *mount = owner->GetSegment(GetSegmentIndex()); - if (mount != NULL) - { - const char *mount_name = mount->GetName(); - if (mount_name != NULL && strstr(mount_name, "b") != NULL) - { - rearFiring = True; - } - } - } - viewFireEnable = (rearFiring == False); - - if (getenv("BT_MECH_LOG")) - { - EntitySegment *mount = owner->GetSegment(GetSegmentIndex()); - DEBUG_STREAM << "[weap] '" << GetName() - << "' seg=" << GetSegmentIndex() << " mount="; - if (mount != NULL) - { - DEBUG_STREAM << mount->GetName(); - } - else - { - DEBUG_STREAM << ""; - } - DEBUG_STREAM << " rear=" << (int)rearFiring << endl << flush; - } - - Check_Fpu(); -} - -MechWeapon::~MechWeapon() -{ -} - -Logical - MechWeapon::TestClass(Mech &) -{ - return True; -} - -// -//############################################################################# -// CheckFireEdge -- the rising-edge detector on fireImpulse (binary @004b9608). -// TriggerState (attr 0x13) carries the raw ControlsButton INT bit-copied by the -// streamed direct mapping (+(button+1) press / -(button+1) release), so the -// release value reads as a negative NaN in float terms; the binary's x87 -// compare effectively treated it as "released". Comparing the BIT PATTERNS as -// signed ints is sign-correct for the button ints AND for genuine float values -// (0.0f = 0x0, positives > 0, negatives/NaNs < 0), reproducing the binary's -// behavior exactly. -//############################################################################# -// -Logical - MechWeapon::CheckFireEdge() -{ - Check(this); - - int current = *(int *)&fireImpulse; - int previous = *(int *)&previousFireImpulse; - Logical edge = (current > 0 && previous <= 0) ? True : False; - previousFireImpulse = fireImpulse; - return edge; -} - -// -//############################################################################# -// ComputeOutputVoltage -- the recharge-dial writer (binary @004b9c9c, vtable -// slot the Emitter overrides with its own voltage form): -// rechargeLevel = (rechargeRate - recoil) / rechargeRate -// The Loading tick decays recoil from rechargeRate toward 0, so the dial rises -// 0 -> 1 over the authored RechargeRate seconds. -//############################################################################# -// -void - MechWeapon::ComputeOutputVoltage() -{ - Check(this); - - rechargeLevel = (rechargeRate - recoil) / rechargeRate; -} - -Logical - MechWeapon::TestInstance() const -{ - return IsDerivedFrom(ClassDerivations); -} - -// -//############################################################################# -// FireWeapon Abstract -- each concrete weapon (Emitter/PPC, ProjectileWeapon/ -// Gauss/MissileLauncher) overrides this. Reaching the base is a bug. -//############################################################################# -// -void - MechWeapon::FireWeapon() -{ - Fail("MechWeapon::FireWeapon -- should not be here (abstract)"); -} - -// -//############################################################################# -// HasActiveTarget -- the owner mech's target slot (binary owner+0x388). -//############################################################################# -// -Logical - MechWeapon::HasActiveTarget() -{ - Check(this); - return (owner != NULL && owner->GetTargetEntity() != NULL) - ? True : False; -} - -// -//############################################################################# -// UpdateTargeting -- refresh the range to the current target and the -// within-range flag (binary @004b9bdc). effectiveRange is the (heat-degraded, -// later) weapon range. -//############################################################################# -// -Logical - MechWeapon::UpdateTargeting() -{ - Check(this); - - Entity *target = (owner != NULL) ? owner->GetTargetEntity() : NULL; - if (target == NULL) - { - targetWithinRange = False; - return False; - } - - Vector3D span; - span.Subtract( - target->localOrigin.linearPosition, - owner->localOrigin.linearPosition - ); - rangeToTarget = span.Length(); - targetWithinRange = (rangeToTarget <= effectiveRange) ? True : False; - return targetWithinRange; -} - -// -//############################################################################# -// SendDamage Deliver this weapon's damage to the struck entity: build the -// engine TakeDamageMessage (inflictor = our mech) and dispatch it AT the -// target, whose handler routes damageZones[zone]->TakeDamage with the -// authored per-type scales. INTERIM zone pick: a uniform roll over the -// target's hull zones -- the authentic resolver is the reticle hit (aimed) or -// the cylinder hit-location table (unaimed), both in the render/mech3 wave. -//############################################################################# -// -void - MechWeapon::SendDamage(Entity *target, Damage &damage) -{ - Check(this); - Check(target); - - int zone = -1; - if (target->damageZoneCount > 0) - { - zone = Random(target->damageZoneCount); - - // - // DEV hook BT_FORCE_ZONE=n: pin every hit to zone n (concentrated - // fire -- exercises the zone-death cascade headlessly). - // - { - const char *force_zone = getenv("BT_FORCE_ZONE"); - if (force_zone != NULL) - { - int forced = atoi(force_zone); - if (forced >= 0 && forced < target->damageZoneCount) - { - zone = forced; - } - } - } - } - - Entity::TakeDamageMessage - hit( - Entity::TakeDamageMessageID, - sizeof(Entity::TakeDamageMessage), - (owner != NULL) ? owner->GetEntityID() : EntityID::Null, - zone, - damage - ); - target->Dispatch(&hit); - - if (getenv("BT_MECH_LOG")) - { - DEBUG_STREAM << "[dmg] '" << GetName() - << "' hit zone " << zone - << " amount=" << damage.damageAmount - << " type=" << damage.damageType; - if (zone >= 0 && zone < target->damageZoneCount - && target->damageZones != NULL - && target->damageZones[zone] != NULL) - { - DEBUG_STREAM << " -> zoneLevel=" - << target->damageZones[zone]->damageLevel; - } - DEBUG_STREAM << endl << flush; - } -} - -// -//############################################################################# -// CreateStreamedSubsystem Model-load-time construction. Not yet reconstructed. -//############################################################################# -// -int - MechWeapon::CreateStreamedSubsystem( - ResourceFile *, - NotationFile *, - const char *, - const char *, - SubsystemResource *, - NotationFile *, - const ResourceDirectories *, - int - ) -{ - Fail("MechWeapon::CreateStreamedSubsystem -- mechweap.cpp not yet reconstructed"); - return 0; -} +//===========================================================================// +// File: mechweap.cpp // +// Project: BattleTech Brick: Mech weapons // +// Contents: MechWeapon -- the base weapon subsystem // +//---------------------------------------------------------------------------// +// Copyright (C) 1995, Virtual World Entertainment, Inc. // +// All Rights reserved worldwide // +// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // +//===========================================================================// + +#include +#pragma hdrstop + +#if !defined(MECHWEAP_HPP) +# include +#endif + +#if !defined(MECH_HPP) +# include +#endif + +#if !defined(SEGMENT_HPP) +# include +#endif + +#include +#include + +#if !defined(RANDOM_HPP) +# include +#endif + +Derivation + MechWeapon::ClassDerivations( + PoweredSubsystem::ClassDerivations, + "MechWeapon" + ); + +// +//############################################################################# +// Message handlers. MechWeapon publishes no handlers of its own yet (the +// weapon fire/damage handler wave), but the set MUST be a real defined object: +// the surviving CODE PPC.CPP binds the inherited name (`PPC::MessageHandlers`) +// into PPC::DefaultData, and a declared-but-undefined static links as a +// zero-filled common block -- the same silent-NULL trap as the +// Emitter::AttributeIndex crash (see MECHWEAP.NOTES.md). +//############################################################################# +// +MechWeapon::MessageHandlerSet + MechWeapon::MessageHandlers( + 0, + NULL, + Subsystem::MessageHandlers + ); + +// +//############################################################################# +// Attribute Support -- the 1995 binary table (@0x511890), IDs pinned so the +// streamed per-mech control mappings resolve: TriggerState (0x13) is what the +// authored fire buttons bind (the controls push then writes the trigger state +// straight into fireImpulse each frame). The pads bridge our chain gap +// 2..0x11 (parents publish nothing yet) -- REQUIRED, because Build leaves +// uncovered gap slots uninitialized; they target rechargeLevel, harmless if a +// stray mapping ever binds one (BT411 shipped the same). Pip*/WeaponRange +// pads-to-rechargeLevel where the member isn't reconstructed yet are noted. +//############################################################################# +// +#define MECHWEAP_PAD(n, name) \ + { (int)MechWeapon::MechWeaponPadFirstAttributeID + n, name, \ + (Simulation::AttributePointer)&MechWeapon::rechargeLevel } + +const MechWeapon::IndexEntry + MechWeapon::AttributePointers[]= +{ + MECHWEAP_PAD( 0, "MechWeaponPad02"), + MECHWEAP_PAD( 1, "MechWeaponPad03"), + MECHWEAP_PAD( 2, "MechWeaponPad04"), + MECHWEAP_PAD( 3, "MechWeaponPad05"), + MECHWEAP_PAD( 4, "MechWeaponPad06"), + MECHWEAP_PAD( 5, "MechWeaponPad07"), + MECHWEAP_PAD( 6, "MechWeaponPad08"), + MECHWEAP_PAD( 7, "MechWeaponPad09"), + MECHWEAP_PAD( 8, "MechWeaponPad0A"), + MECHWEAP_PAD( 9, "MechWeaponPad0B"), + MECHWEAP_PAD(10, "MechWeaponPad0C"), + MECHWEAP_PAD(11, "MechWeaponPad0D"), + MECHWEAP_PAD(12, "MechWeaponPad0E"), + MECHWEAP_PAD(13, "MechWeaponPad0F"), + MECHWEAP_PAD(14, "MechWeaponPad10"), + MECHWEAP_PAD(15, "MechWeaponPad11"), + ATTRIBUTE_ENTRY(MechWeapon, PercentDone, rechargeLevel), // 0x12 + ATTRIBUTE_ENTRY(MechWeapon, TriggerState, fireImpulse), // 0x13 + ATTRIBUTE_ENTRY(MechWeapon, DistanceToTarget, rangeToTarget), // 0x14 + ATTRIBUTE_ENTRY(MechWeapon, TargetWithinRange, targetWithinRange), // 0x15 + ATTRIBUTE_ENTRY(MechWeapon, WeaponRange, effectiveRange), // 0x16 + { (int)MechWeapon::PipPositionAttributeID, "PipPosition", // 0x17 (pip + (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, // members + { (int)MechWeapon::PipColorAttributeID, "PipColor", // 0x18 not yet + (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, // recon- + { (int)MechWeapon::PipExtendedRangeAttributeID, "PipExtendedRange", // 0x19 structed) + (Simulation::AttributePointer)&MechWeapon::rechargeLevel }, + ATTRIBUTE_ENTRY(MechWeapon, EstimatedReadyTime, estimatedReadyTime),// 0x1A + ATTRIBUTE_ENTRY(MechWeapon, RearFiring, rearFiring), // 0x1B + ATTRIBUTE_ENTRY(MechWeapon, WeaponState, weaponAlarm) // 0x1C +}; + +MechWeapon::AttributeIndexSet + MechWeapon::AttributeIndex( + ELEMENTS(MechWeapon::AttributePointers), + MechWeapon::AttributePointers, + Subsystem::AttributeIndex + ); + +MechWeapon::SharedData + MechWeapon::DefaultData( + MechWeapon::ClassDerivations, + MechWeapon::MessageHandlers, + MechWeapon::AttributeIndex, + Subsystem::StateCount + ); + +MechWeapon::MechWeapon( + Mech *owner, + int subsystem_ID, + SubsystemResource *subsystem_resource, + SharedData &shared_data +): + PoweredSubsystem(owner, subsystem_ID, subsystem_resource, shared_data) +{ + Check(owner); + Check_Pointer(subsystem_resource); + + rechargeRate = subsystem_resource->rechargeRate; + weaponRange = subsystem_resource->weaponRange; + damageAmount = subsystem_resource->damageAmount; + damageType = subsystem_resource->damageType; + heatCostToFire = subsystem_resource->heatCostToFire; + explosionResourceID = subsystem_resource->explosionModelFile; + targetWithinRange = False; + + damageData.damageType = damageType; + damageData.damageAmount = damageAmount; + + // + // Fire-machine state: spawn fully charged and Loaded (ready to fire). + // + fireImpulse = 0.0f; + previousFireImpulse = 0.0f; + rechargeLevel = 1.0f; + rangeToTarget = 0.0f; + effectiveRange = weaponRange; + estimatedReadyTime = 0; + recoil = 0.0f; + weaponAlarm.Initialize(WeaponStateCount); + weaponAlarm.SetLevel(LoadedState); + + // + // REAR-FIRING resolve (the binary ctor tail @004b99a8): a weapon is + // rear-mounted when its MOUNT SEGMENT's site name carries the 'b' (back) + // marker -- the back gun ports (sitelbgunport / siterbgunport) are the only + // port names containing it. Rear-mounted weapons fire only into the + // LOOK-BACK view; everything else fires forward. The look-state commit + // (Mech::CommitLookState) re-arms viewFireEnable on every view change; a + // weapon spawns armed for the forward view. + // + rearFiring = False; + { + EntitySegment *mount = owner->GetSegment(GetSegmentIndex()); + if (mount != NULL) + { + const char *mount_name = mount->GetName(); + if (mount_name != NULL && strstr(mount_name, "b") != NULL) + { + rearFiring = True; + } + } + } + viewFireEnable = (rearFiring == False); + + if (getenv("BT_MECH_LOG")) + { + EntitySegment *mount = owner->GetSegment(GetSegmentIndex()); + DEBUG_STREAM << "[weap] '" << GetName() + << "' seg=" << GetSegmentIndex() << " mount="; + if (mount != NULL) + { + DEBUG_STREAM << mount->GetName(); + } + else + { + DEBUG_STREAM << ""; + } + DEBUG_STREAM << " rear=" << (int)rearFiring << endl << flush; + } + + Check_Fpu(); +} + +MechWeapon::~MechWeapon() +{ +} + +Logical + MechWeapon::TestClass(Mech &) +{ + return True; +} + +// +//############################################################################# +// CheckFireEdge -- the rising-edge detector on fireImpulse (binary @004b9608). +// TriggerState (attr 0x13) carries the raw ControlsButton INT bit-copied by the +// streamed direct mapping (+(button+1) press / -(button+1) release), so the +// release value reads as a negative NaN in float terms; the binary's x87 +// compare effectively treated it as "released". Comparing the BIT PATTERNS as +// signed ints is sign-correct for the button ints AND for genuine float values +// (0.0f = 0x0, positives > 0, negatives/NaNs < 0), reproducing the binary's +// behavior exactly. +//############################################################################# +// +Logical + MechWeapon::CheckFireEdge() +{ + Check(this); + + int current = *(int *)&fireImpulse; + int previous = *(int *)&previousFireImpulse; + Logical edge = (current > 0 && previous <= 0) ? True : False; + previousFireImpulse = fireImpulse; + return edge; +} + +// +//############################################################################# +// ComputeOutputVoltage -- the recharge-dial writer (binary @004b9c9c, vtable +// slot the Emitter overrides with its own voltage form): +// rechargeLevel = (rechargeRate - recoil) / rechargeRate +// The Loading tick decays recoil from rechargeRate toward 0, so the dial rises +// 0 -> 1 over the authored RechargeRate seconds. +//############################################################################# +// +void + MechWeapon::ComputeOutputVoltage() +{ + Check(this); + + rechargeLevel = (rechargeRate - recoil) / rechargeRate; +} + +Logical + MechWeapon::TestInstance() const +{ + return IsDerivedFrom(ClassDerivations); +} + +// +//############################################################################# +// FireWeapon Abstract -- each concrete weapon (Emitter/PPC, ProjectileWeapon/ +// Gauss/MissileLauncher) overrides this. Reaching the base is a bug. +//############################################################################# +// +void + MechWeapon::FireWeapon() +{ + Fail("MechWeapon::FireWeapon -- should not be here (abstract)"); +} + +// +//############################################################################# +// GetMuzzlePoint (binary @004b9948) -- the mount segment's world-frame +// position: segmentToEntity x localToWorld (GetSegmentToWorld), translation +// extracted by transforming the segment-space origin. This is what makes +// fire leave FROM THE GUN and follow the torso twist. +//############################################################################# +// +void + MechWeapon::GetMuzzlePoint(Point3D &point) +{ + Check(this); + + point = owner->localOrigin.linearPosition; + + if (GetSegmentIndex() >= 0) + { + EntitySegment *segment = owner->GetSegment(GetSegmentIndex()); + if (segment != NULL) + { + LinearMatrix to_world; + owner->GetSegmentToWorld(*segment, &to_world); + point.Multiply(Point3D(0.0f, 0.0f, 0.0f), to_world); + } + } +} + +// +//############################################################################# +// HasActiveTarget -- the owner mech's target slot (binary owner+0x388). +//############################################################################# +// +Logical + MechWeapon::HasActiveTarget() +{ + Check(this); + return (owner != NULL && owner->GetTargetEntity() != NULL) + ? True : False; +} + +// +//############################################################################# +// UpdateTargeting -- refresh the range to the current target and the +// within-range flag (binary @004b9bdc). effectiveRange is the (heat-degraded, +// later) weapon range. +//############################################################################# +// +Logical + MechWeapon::UpdateTargeting() +{ + Check(this); + + Entity *target = (owner != NULL) ? owner->GetTargetEntity() : NULL; + if (target == NULL) + { + targetWithinRange = False; + return False; + } + + Vector3D span; + span.Subtract( + target->localOrigin.linearPosition, + owner->localOrigin.linearPosition + ); + rangeToTarget = span.Length(); + targetWithinRange = (rangeToTarget <= effectiveRange) ? True : False; + return targetWithinRange; +} + +// +//############################################################################# +// SendDamage Deliver this weapon's damage to the struck entity: build the +// engine TakeDamageMessage (inflictor = our mech) and dispatch it AT the +// target, whose handler routes damageZones[zone]->TakeDamage with the +// authored per-type scales. INTERIM zone pick: a uniform roll over the +// target's hull zones -- the authentic resolver is the reticle hit (aimed) or +// the cylinder hit-location table (unaimed), both in the render/mech3 wave. +//############################################################################# +// +void + MechWeapon::SendDamage(Entity *target, Damage &damage) +{ + Check(this); + Check(target); + + int zone = -1; + if (target->damageZoneCount > 0) + { + zone = Random(target->damageZoneCount); + + // + // DEV hook BT_FORCE_ZONE=n: pin every hit to zone n (concentrated + // fire -- exercises the zone-death cascade headlessly). + // + { + const char *force_zone = getenv("BT_FORCE_ZONE"); + if (force_zone != NULL) + { + int forced = atoi(force_zone); + if (forced >= 0 && forced < target->damageZoneCount) + { + zone = forced; + } + } + } + } + + Entity::TakeDamageMessage + hit( + Entity::TakeDamageMessageID, + sizeof(Entity::TakeDamageMessage), + (owner != NULL) ? owner->GetEntityID() : EntityID::Null, + zone, + damage + ); + target->Dispatch(&hit); + + if (getenv("BT_MECH_LOG")) + { + DEBUG_STREAM << "[dmg] '" << GetName() + << "' hit zone " << zone + << " amount=" << damage.damageAmount + << " type=" << damage.damageType; + if (zone >= 0 && zone < target->damageZoneCount + && target->damageZones != NULL + && target->damageZones[zone] != NULL) + { + DEBUG_STREAM << " -> zoneLevel=" + << target->damageZones[zone]->damageLevel; + } + DEBUG_STREAM << endl << flush; + } +} + +// +//############################################################################# +// CreateStreamedSubsystem Model-load-time construction. Not yet reconstructed. +//############################################################################# +// +int + MechWeapon::CreateStreamedSubsystem( + ResourceFile *, + NotationFile *, + const char *, + const char *, + SubsystemResource *, + NotationFile *, + const ResourceDirectories *, + int + ) +{ + Fail("MechWeapon::CreateStreamedSubsystem -- mechweap.cpp not yet reconstructed"); + return 0; +} diff --git a/restoration/source410/BT/MECHWEAP.HPP b/restoration/source410/BT/MECHWEAP.HPP index e18b1adb..cb3fc2a8 100644 --- a/restoration/source410/BT/MECHWEAP.HPP +++ b/restoration/source410/BT/MECHWEAP.HPP @@ -204,6 +204,15 @@ Logical UpdateTargeting(); + // + // The muzzle world position (binary @004b9948): the MOUNT segment's + // world frame -- rounds and beams leave FROM THE GUN, following the + // torso twist. Falls back to the mech origin when the mount + // segment doesn't resolve. + // + void + GetMuzzlePoint(Point3D &point); + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Local Data // diff --git a/restoration/source410/BT/MISLANCH.CPP b/restoration/source410/BT/MISLANCH.CPP index fba76594..238da3a3 100644 --- a/restoration/source410/BT/MISLANCH.CPP +++ b/restoration/source410/BT/MISLANCH.CPP @@ -382,7 +382,13 @@ void HostManager *host_manager = application->GetHostManager(); Check(host_manager); + // + // The round leaves FROM THE GUN: the mount segment's world + // frame (GetMuzzlePoint @004b9948 -- follows the torso twist). + // The rotation stays the mech's (the missile aims by velocity). + // Origin muzzle = owner->localOrigin; + GetMuzzlePoint(muzzle.linearPosition); // // NoCollisionVolumeFlag is LOAD-BEARING: collision volumes are // the Mover DEFAULT (IsCollisionVolume = the flag NOT set), and