Live trace (BT_MECH_LOG) on the pod TEST.EGG mech: '[mech] segment walk done: subsystemCount=33 weaponCount=7' -- the segment walk instantiates the ENTIRE real subsystem roster (31 subsystems + 2 sentinels, 7 weapons) from the actual streamed model, no crash. The structural reconstruction is proven end-to-end on real data. The crash is AFTER full construction, in MakeAndLinkViewpointEntity's attribute- watcher setup (an AttributeWatcher resolving a subsystem attribute via GetAttributePointer). Phase-5 frontier confirmed = per-subsystem AttributeIndex tables + watcher wiring. Trace gated behind BT_MECH_LOG. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
338 lines
10 KiB
C++
338 lines
10 KiB
C++
//===========================================================================//
|
|
// File: mech.cpp //
|
|
// Project: BattleTech Brick: Entity Manager //
|
|
// Contents: Implementation details for the Mech entity //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// //
|
|
//---------------------------------------------------------------------------//
|
|
// 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(MECH_HPP)
|
|
# include <mech.hpp>
|
|
#endif
|
|
|
|
#if !defined(APP_HPP)
|
|
# include <app.hpp>
|
|
#endif
|
|
#if !defined(MEMSTRM_HPP)
|
|
# include <memstrm.hpp>
|
|
#endif
|
|
#if !defined(RESOURCE_HPP)
|
|
# include <resource.hpp>
|
|
#endif
|
|
|
|
// The subsystem roster the segment walk instantiates.
|
|
#include <heat.hpp> // HeatableSubsystem, HeatSink, HeatWatcher, Condenser
|
|
#include <powersub.hpp> // PoweredSubsystem, PowerWatcher, Generator
|
|
#include <reservr.hpp> // Reservoir
|
|
#include <sensor.hpp> // Sensor
|
|
#include <gyro.hpp> // Gyroscope
|
|
#include <torso.hpp> // Torso
|
|
#include <myomers.hpp> // Myomers
|
|
#include <hud.hpp> // HUD
|
|
#include <searchlt.hpp> // Searchlight
|
|
#include <thermsgt.hpp> // ThermalSight
|
|
#include <mechtech.hpp> // MechTech
|
|
#include <messmgr.hpp> // SubsystemMessageManager
|
|
#include <mechweap.hpp> // MechWeapon
|
|
#include <emitter.hpp> // Emitter
|
|
#include <ppc.hpp> // PPC
|
|
#include <gauss.hpp> // GaussRifle
|
|
#include <projweap.hpp> // ProjectileWeapon
|
|
#include <mislanch.hpp> // MissileLauncher
|
|
#include <ammobin.hpp> // AmmoBin
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Derivation
|
|
Mech::ClassDerivations(
|
|
JointedMover::ClassDerivations,
|
|
"Mech"
|
|
);
|
|
|
|
Mech::SharedData
|
|
Mech::DefaultData(
|
|
Mech::ClassDerivations,
|
|
JointedMover::MessageHandlers,
|
|
JointedMover::AttributeIndex,
|
|
33,
|
|
(Entity::MakeHandler)Mech::Make
|
|
);
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Mech*
|
|
Mech::Make(MakeMessage *creation_message)
|
|
{
|
|
return new Mech(creation_message);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// Mech ctor -- the heart of the entity (walks the model segment table and
|
|
// instantiates the full subsystem roster: power, heat, weapons, actuators,
|
|
// controls, tech, damage zones). This is the largest single function in the
|
|
// game and the current reconstruction frontier; see MECH.NOTES.md. Chains to
|
|
// the JointedMover base so the skeleton/segments stream before it Fails.
|
|
//#############################################################################
|
|
//
|
|
Mech::Mech(
|
|
MakeMessage *creation_message,
|
|
SharedData &shared_data
|
|
):
|
|
JointedMover(creation_message, shared_data),
|
|
controllableSubsystems(this),
|
|
watchedSubsystems(this),
|
|
heatableSubsystems(this),
|
|
weaponRoster(this),
|
|
damageableSubsystems(this)
|
|
{
|
|
Check_Pointer(creation_message);
|
|
|
|
//
|
|
// Cached subsystem back-pointers -- filled by the segment walk below.
|
|
//
|
|
sensorSubsystem = NULL;
|
|
gyroSubsystem = NULL;
|
|
sinkSourceSubsystem = NULL;
|
|
hudSubsystem = NULL;
|
|
messageManager = NULL;
|
|
weaponCount = 0;
|
|
|
|
//
|
|
// Embedded status / animation / naming state.
|
|
//
|
|
mechNameFilter.Initialize();
|
|
masterAlarm.Initialize(0x21);
|
|
heatAlarm.Initialize(3);
|
|
stabilityAlarm.Initialize(2);
|
|
statusAlarm.Initialize(0x21);
|
|
|
|
targetReticle.reticleState = Reticle::ReticleOn;
|
|
targetReticle.pickPointingOn = True;
|
|
targetReticle.reticleElementMask = Reticle::AllEnabledGroup;
|
|
|
|
animationState = StateIndicator(0x21);
|
|
animationState.SetState(0);
|
|
replicantAnimationState = StateIndicator(0x21);
|
|
replicantAnimationState.SetState(0);
|
|
collisionState = StateIndicator(4);
|
|
collisionState.SetState(0);
|
|
|
|
{
|
|
for (int i = 0; i < 5; ++i)
|
|
{
|
|
telemetryFilter[i].SetSize(15, 0.0f);
|
|
}
|
|
}
|
|
|
|
legAnimation.Init(this);
|
|
bodyAnimation.Init(this);
|
|
|
|
{
|
|
for (int i = 0; i < 220; ++i)
|
|
{
|
|
reservedState[i] = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// Segment-table walk: instantiate one Subsystem per streamed segment,
|
|
// dispatching on its classID. The subsystem roster (subsystemArray /
|
|
// subsystemCount) lives in the base Entity.
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
ResourceDescription::ResourceID modelResourceID = creation_message->resourceID;
|
|
|
|
ResourceDescription *subsystemDesc =
|
|
application->GetResourceFile()->SearchList(
|
|
modelResourceID,
|
|
ResourceDescription::SubsystemModelStreamResourceType
|
|
);
|
|
Check(subsystemDesc);
|
|
subsystemDesc->Lock();
|
|
|
|
//
|
|
// Copy the raw stream into a padded buffer: reading a SubsystemResource
|
|
// struct off the tail segment can over-read the raw resource, so pad it.
|
|
//
|
|
size_t rawSize = (size_t)subsystemDesc->resourceSize;
|
|
size_t padSize = rawSize + 0x400;
|
|
void *padBuffer = (void *)new char[padSize];
|
|
memcpy(padBuffer, subsystemDesc->resourceAddress, rawSize);
|
|
|
|
MemoryStream subsystemStream(padBuffer, padSize);
|
|
|
|
int streamedSubsystemCount = *(int *)subsystemStream.GetPointer();
|
|
subsystemStream.AdvancePointer(sizeof(int));
|
|
|
|
//
|
|
// Slot 0 = the (later-installed) control mapper, slot 1 = the voltage bus
|
|
// sentinel; the streamed subsystems fill from slot 2.
|
|
//
|
|
subsystemCount = streamedSubsystemCount + 2;
|
|
subsystemArray = new Subsystem *[subsystemCount];
|
|
{
|
|
for (int z = 0; z < subsystemCount; ++z)
|
|
{
|
|
subsystemArray[z] = NULL;
|
|
}
|
|
}
|
|
|
|
for (int id = 2; id < subsystemCount; ++id)
|
|
{
|
|
Subsystem::SubsystemResource *seg =
|
|
(Subsystem::SubsystemResource *)subsystemStream.GetPointer();
|
|
|
|
Subsystem *made = NULL;
|
|
|
|
switch (seg->classID)
|
|
{
|
|
case CondenserClassID:
|
|
made = new Condenser(this, id, (Condenser::SubsystemResource *)seg);
|
|
break;
|
|
case HeatSinkClassID:
|
|
made = new HeatSink(this, id, (HeatSink::SubsystemResource *)seg);
|
|
break;
|
|
case HeatWatcherClassID:
|
|
made = new HeatWatcher(this, id, (HeatWatcher::SubsystemResource *)seg);
|
|
break;
|
|
case ReservoirClassID:
|
|
made = new Reservoir(this, id, (Reservoir::SubsystemResource *)seg);
|
|
break;
|
|
case GeneratorClassID:
|
|
made = new Generator(this, id, (Generator::SubsystemResource *)seg);
|
|
break;
|
|
case PoweredSubsystemClassID:
|
|
made = new PoweredSubsystem(this, id, (PoweredSubsystem::SubsystemResource *)seg);
|
|
break;
|
|
case SensorClassID:
|
|
made = new Sensor(this, id, (Sensor::SubsystemResource *)seg);
|
|
sensorSubsystem = made;
|
|
break;
|
|
case GyroscopeClassID:
|
|
made = new Gyroscope(this, id, (Gyroscope::SubsystemResource *)seg);
|
|
gyroSubsystem = made;
|
|
break;
|
|
case TorsoClassID:
|
|
made = new Torso(this, id, (Torso::SubsystemResource *)seg);
|
|
sinkSourceSubsystem = made;
|
|
break;
|
|
case MyomersClassID:
|
|
made = new Myomers(this, id, (Myomers::SubsystemResource *)seg);
|
|
break;
|
|
case EmitterClassID:
|
|
made = new Emitter(this, id, (Emitter::SubsystemResource *)seg);
|
|
++weaponCount;
|
|
break;
|
|
case PPCClassID:
|
|
made = new PPC(this, id, (PPC::SubsystemResource *)seg, PPC::DefaultData);
|
|
++weaponCount;
|
|
break;
|
|
case AmmoBinClassID:
|
|
made = new AmmoBin(this, id, (AmmoBin::SubsystemResource *)seg);
|
|
break;
|
|
case ProjectileWeaponClassID:
|
|
made = new ProjectileWeapon(this, id, (ProjectileWeapon::SubsystemResource *)seg);
|
|
++weaponCount;
|
|
break;
|
|
case GaussRifleClassID:
|
|
made = new GaussRifle(this, id, (GaussRifle::SubsystemResource *)seg);
|
|
++weaponCount;
|
|
break;
|
|
case MissileLauncherClassID:
|
|
made = new MissileLauncher(this, id, (MissileLauncher::SubsystemResource *)seg);
|
|
++weaponCount;
|
|
break;
|
|
case SubsystemMessageManagerClassID:
|
|
made = new SubsystemMessageManager(this, id, (SubsystemMessageManager::SubsystemResource *)seg);
|
|
messageManager = (SubsystemMessageManager *)made;
|
|
break;
|
|
case HUDClassID:
|
|
made = new HUD(this, id, (HUD::SubsystemResource *)seg);
|
|
hudSubsystem = made;
|
|
break;
|
|
case SearchlightClassID:
|
|
made = new Searchlight(this, id, (Searchlight::SubsystemResource *)seg);
|
|
break;
|
|
case ThermalSightClassID:
|
|
made = new ThermalSight(this, id, (ThermalSight::SubsystemResource *)seg);
|
|
break;
|
|
case MechTechClassID:
|
|
made = new MechTech(this, id, (MechTech::SubsystemResource *)seg);
|
|
break;
|
|
case EmitterClassID + 1: // LaserClassID -- an Emitter energy weapon
|
|
case EmitterClassID + 2: // ParticleCannonClassID -- an Emitter energy weapon
|
|
made = new Emitter(this, id, (Emitter::SubsystemResource *)seg);
|
|
++weaponCount;
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// Unrecognised / not-yet-reconstructed subsystem class (Capacitor,
|
|
// AmmoFeeder, Radar, Turret, ...): give the slot a base
|
|
// MechSubsystem so control/damage bindings that resolve this
|
|
// subsystemID find a real (if generic) subsystem rather than a NULL
|
|
// plug. The roster stays aligned.
|
|
//
|
|
made = new MechSubsystem(
|
|
this, id,
|
|
(MechSubsystem::SubsystemResource *)seg,
|
|
MechSubsystem::DefaultData
|
|
);
|
|
break;
|
|
}
|
|
|
|
subsystemArray[id] = made;
|
|
|
|
subsystemStream.AdvancePointer(seg->subsystemModelSize);
|
|
}
|
|
|
|
subsystemDesc->Unlock();
|
|
delete [] (char *)padBuffer;
|
|
|
|
if (getenv("BT_MECH_LOG"))
|
|
{
|
|
DEBUG_STREAM << "[mech] segment walk done: subsystemCount=" << subsystemCount
|
|
<< " weaponCount=" << weaponCount << endl << flush;
|
|
}
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
Mech::~Mech()
|
|
{
|
|
}
|
|
|
|
void
|
|
Mech::SetMappingSubsystem(Subsystem *subsystem)
|
|
{
|
|
Check(this);
|
|
Check_Pointer(subsystemArray);
|
|
|
|
//
|
|
// The control mapper lives in roster slot 0 (the streamed control-mapping
|
|
// resource binds its DirectMappings to subsystemID 0, so it must resolve
|
|
// there via Entity::GetSimulation(0)). On a re-spawn, drop the old one.
|
|
//
|
|
if (subsystemArray[0] != NULL)
|
|
{
|
|
Unregister_Object(subsystemArray[0]);
|
|
delete subsystemArray[0];
|
|
}
|
|
subsystemArray[0] = subsystem;
|
|
}
|