The energy weapons now run a live fire cycle, and the REAL trigger wiring is in place: the 1995 MechWeapon attribute table (binary @0x511890) is published with PINNED IDs, so the streamed per-mech fire-button mappings bind TriggerState (0x13) -> fireImpulse and the controls push writes the trigger into the weapon. - MECHWEAP: attribute enum + table (PercentDone 0x12, TriggerState 0x13, DistanceToTarget 0x14, TargetWithinRange 0x15, WeaponRange 0x16, EstimatedReadyTime 0x1A, RearFiring 0x1B, WeaponState 0x1C). Pads bridge the chain gap 2..0x11 -- LOAD-BEARING: AttributeIndexSet::Build leaves uncovered gap slots as uninitialized garbage, so every ID up to the max must be covered (they shrink to the authentic 0x0F..0x11 when the parent attribute waves land; SENSOR.HPP pins PoweredSubsystem::NextAttributeID at 0x0F). - MECHWEAP: fire-machine members (fireImpulse/previousFireImpulse, rechargeLevel, rangeToTarget, effectiveRange, estimatedReadyTime, recoil, weaponAlarm 0/2/3/4); CheckFireEdge (@004b9608, BIT-PATTERN int compare -- TriggerState carries raw ControlsButton ints whose release value is a float NaN; IEEE compare would latch the detector shut); ComputeOutputVoltage (@004b9c9c recharge dial). - EMITTER: EmitterSimulation (PARTIAL @004baa88) -- Firing/Loaded/Loading FSM on the weapon alarm, viewFireEnable gate at Loaded->Firing, recoil-decay recharge over the authored RechargeRate seconds; FireWeapon (PARTIAL @004bace8) discharge bookkeeping; ChargeLevel re-pinned to the authentic 0x1D; index re-chained to MechWeapon's. Deferred: electrical charge model (TrackSeekVoltage), heat dump, HasActiveTarget gate, beam/damage submission. - BT_FORCE_FIRE dev hook: trigger pulse whenever Loaded (auto-fire cadence). VERIFIED headlessly: forward view -- PPC_1/2 + ERMLaser_1 cycle FIRED->LOADED at the authored 0.6s/1s cadence, rear lasers silent; BT_FORCE_LOOK=3 -- ONLY the rear lasers (ERMLaser_2/3) fire. The view-fire x fire-FSM interlock holds both directions. Zero Fail. (SRM6 ballistic FSM = a later increment.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
294 lines
9.8 KiB
C++
294 lines
9.8 KiB
C++
//===========================================================================//
|
|
// 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 <bt.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(MECHWEAP_HPP)
|
|
# include <mechweap.hpp>
|
|
#endif
|
|
|
|
#if !defined(MECH_HPP)
|
|
# include <mech.hpp>
|
|
#endif
|
|
|
|
#if !defined(SEGMENT_HPP)
|
|
# include <segment.hpp>
|
|
#endif
|
|
|
|
#include <string.h>
|
|
|
|
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;
|
|
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 << "<none>";
|
|
}
|
|
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)");
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// SendDamage Deliver this weapon's damage to a struck entity. The damage/
|
|
// networking path is not yet reconstructed.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
MechWeapon::SendDamage(Entity *, Damage &)
|
|
{
|
|
Fail("MechWeapon::SendDamage -- mechweap.cpp not yet reconstructed");
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// 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;
|
|
}
|