//===========================================================================// // 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 #pragma hdrstop #if !defined(MECH_HPP) # include #endif #if !defined(APP_HPP) # include #endif #if !defined(MEMSTRM_HPP) # include #endif #if !defined(RESOURCE_HPP) # include #endif // The subsystem roster the segment walk instantiates. #include // HeatableSubsystem, HeatSink, HeatWatcher, Condenser #include // PoweredSubsystem, PowerWatcher, Generator #include // Reservoir #include // Sensor #include // Gyroscope #include // Torso #include // Myomers #include // HUD #include // Searchlight #include // ThermalSight #include // MechTech #include // SubsystemMessageManager #include // MechWeapon #include // Emitter #include // PPC #include // GaussRifle #include // ProjectileWeapon #include // MissileLauncher #include // 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; } // // Install the per-frame body Performance. Until now the mech ran the base // DoNothingOnce; from here the engine dispatches Mech::Simulate every frame // (Mover -> Entity -> Simulation::PerformAndWatch) once the mission is // RunningMission. // SetPerformance(&Mech::Simulate); 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; } // //############################################################################# // Simulate -- the mech's per-frame body Performance (the Mover locomotion tick). // // FUNCTIONAL MOTION CORE (Phase 5.3, increment 1). A Mech is a Mover; its // per-frame job is to advance its Origin and commit it to the world transform. // This reconstructs the load-bearing spine of the 1995 Mech::Simulate // (mech4.cpp @004ab430): integrate the body velocity into localOrigin, then // rebuild localToWorld with the engine's own idiom (ENTITY.CPP:988 / // MOVER.CPP:850, `localToWorld = localOrigin`). // // Still layered on top of this (next increments): // * the gait-cycle self-propulsion that FEEDS worldLinearVelocity -- the // authentic model drives forward speed from the walk/run animation cycle // (IntegrateMotion -> AdvanceBodyAnimation -> cycleDistance) steered by the // control-mapper demands (throttle/turn), not a raw velocity; // * heading integration (rotate localOrigin.angularPosition by the turn rate); // * the terrain-height drop (BoundingBoxTreeNode::FindBoundingBoxUnder) that // rests the feet on the ground; // * the cockpit telemetry FilteredScalars (head/aim/leg/torso angular rates). // // With no locomotion layer yet, worldLinearVelocity is zero for a freshly // spawned mech, so it holds its pose -- identical to the prior DoNothing, but // now on the real Simulate path. DEV hook BT_DRIVE="vx,vy,vz" injects a // constant world velocity so the integrate + transform path is verifiable // headlessly (no RIO/controls needed). See MECH.NOTES.md. //############################################################################# // void Mech::Simulate(Scalar time_slice) { Check(this); Vector3D velocity = worldLinearVelocity; { const char *drive = getenv("BT_DRIVE"); if (drive != NULL) { float dx = 0.0f, dy = 0.0f, dz = 0.0f; if (sscanf(drive, "%f,%f,%f", &dx, &dy, &dz) == 3) { velocity.x = dx; velocity.y = dy; velocity.z = dz; } } } // // Integrate position (pos = pos + velocity*dt) and commit the Origin to the // world transform. // localOrigin.linearPosition.AddScaled( localOrigin.linearPosition, velocity, time_slice ); localToWorld = localOrigin; if (getenv("BT_MECH_LOG")) { static Scalar reportAccum = 0.0f; reportAccum += time_slice; if (reportAccum >= 1.0f) { reportAccum = 0.0f; DEBUG_STREAM << "[sim] mech pos=(" << localOrigin.linearPosition.x << "," << localOrigin.linearPosition.y << "," << localOrigin.linearPosition.z << ")" << endl << flush; } } Check_Fpu(); }