//===========================================================================// // File: mech.hpp // // 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 // //===========================================================================// #if !defined(MECH_HPP) # define MECH_HPP # if !defined(JMOVER_HPP) # include # endif # if !defined(RETICLE_HPP) # include # endif # if !defined(ALARM_HPP) # include # endif # if !defined(STATE_HPP) # include # endif # if !defined(CHAIN_HPP) # include # endif # if !defined(AVERAGE_HPP) # include # endif # if !defined(CSTR_HPP) # include # endif # if !defined(SEQCTL_HPP) # include # endif # if !defined(NAMEFILT_HPP) # include # endif # if !defined(ROTATION_HPP) # include # endif //##################### Forward Class Declarations ####################### class Mech; class Subsystem; class SubsystemMessageManager; class ControlsMapping; class PlatformTool; class MechControlsMapper; class Mech__DamageZone; class Joint; class DamageLookupTable; //########################################################################### //######################### Mech Model Resource ######################### //########################################################################### struct Mech__ModelResource: public JointedMover::ModelResource { char animationPrefix[4]; Scalar maxAcceleration; Scalar superStopAcceleration; Scalar throttleAdjustment; Scalar lookLeftAngle; Scalar lookRightAngle; Scalar lookFrontAngle; Scalar lookBackAngle; Scalar walkingTurnRate; Scalar runningTurnRate; Scalar reticleX; Scalar reticleY; Scalar relativeMechValue; int deathEffectResourceID; Scalar deathSplashDamage; Scalar deathSplashRadius; Scalar maxUnstableAcceleration; Scalar unstableAccelerationEffect; Scalar unstableGunTheEngineEffect; Scalar unstableSuperStopEffect; Scalar unstableHighVelocityEffect; Scalar unstableStopedTurnEffect; Scalar updatePositionDiffrence; Scalar updateTurnVelocityDiffrence; Scalar updateTurnDegreeDiffrence; Scalar timeDelay; Vector3D cameraOffset; char shadowJointName[20]; }; //########################################################################### //########################## Mech Make Message ########################## //########################################################################### class Mech__MakeMessage: public JointedMover::MakeMessage { public: char resourceNameA[20]; char resourceNameB[20]; char resourceNameC[20]; Mech__MakeMessage( Receiver::MessageID message_ID, size_t length, const EntityID &entity_ID, Entity::ClassID class_ID, const EntityID &owner_ID, ResourceDescription::ResourceID resource_ID, LWord instance_flags, const Origin &origin, const Motion &velocity, const Motion &acceleration, const char *badge, const char *color, const char *patch ): JointedMover::MakeMessage( message_ID, length, entity_ID, class_ID, owner_ID, resource_ID, instance_flags, origin, velocity, acceleration ) { Str_Copy(resourceNameA, badge, sizeof(resourceNameA)); Str_Copy(resourceNameB, color, sizeof(resourceNameB)); Str_Copy(resourceNameC, patch, sizeof(resourceNameC)); } }; //########################################################################### //####################### MechAnimationState ############################ //########################################################################### // // The gait clip a mech is currently playing. Recovered VERBATIM from the // 0x3c-byte-stride name table at .data:0050cfe8 -- the table the binary's // "Unsupported mech animation" assert indexes -- so the names and their order // are the original's, not a reading of behaviour. // // The cycle alternates LEFT and RIGHT because each clip is one stride: a // walk is Right -> Left -> Right forever, and the transitions in and out of // stand / run / reverse each have their own handed pair so the mech always // leaves a cycle on the correct foot. // enum MechAnimationState { StandingAnimation = 0x00, RightStandToWalkAnimation = 0x01, RightWalkForwardAnimation = 0x02, LeftWalkForwardAnimation = 0x03, RightWalkToStandAnimation = 0x04, LeftWalkToStandAnimation = 0x05, RightWalkToRunAnimation = 0x06, LeftWalkToRunAnimation = 0x07, RightRunAnimation = 0x08, LeftRunAnimation = 0x09, RightRunToWalkAnimation = 0x0a, LeftRunToWalkAnimation = 0x0b, RightStandToReverseAnimation = 0x0c, LeftStandToReverseAnimation = 0x0d, RightReverseAnimation = 0x0e, LeftReverseAnimation = 0x0f, RightReverseToStandAnimation = 0x10, LeftReverseToStandAnimation = 0x11, LeftWalkToGimpAnimation = 0x12, RightWalkToGimpAnimation = 0x13, LeftGimpAnimation = 0x14, RightGimpAnimation = 0x15, LeftGimpToStandAnimation = 0x16, RightGimpToStandAnimation = 0x17, FallForwardAnimation = 0x18, FallBackwardAnimation = 0x19, FallLeftAnimation = 0x1a, FallRightAnimation = 0x1b, CrashAnimation = 0x1c, AnimationCount = 0x1d, // // The clip ARRAY is larger than the name table. Slot 0x20 holds the // bump/crash clip (mech+0x64c) bound on a hard wall impact, so the // array runs to 0x21 entries even though only 0x1d are named. // AnimationSlotCount = 0x21 }; // // A WARNING ABOUT THE NAMES ABOVE, which are verbatim from the binary's table // and therefore authoritative as NAMES -- but are NOT a reliable guide to what // each slot actually plays. // // The clip loader's slot assignments (LoadLocomotionClips) disagree with them. // Slot 0x0e takes the "rwr" run-to-walk clip while the table calls 0x0e // RightReverseAnimation; the reverse gait actually lives at 0x10-0x15 // (sbr/sbl entry, bbr/bbl cycle, bsr/bsl exit); and the states the gait // machine alternates between while walking forward are 6 and 7, not the // pair the "WalkForward" names suggest. // // Read the slot map in MECH2.NOTES.md when the question is "what does this // state play"; read the enum when the question is "what did the original call // this index". Conflating the two is how BT411 shipped a reverse gait that // played its coming-to-a-halt frames mid-cycle. // //########################################################################### //############################## Mech ############################### //########################################################################### class Mech: public JointedMover { // // The per-zone damage code walks the roster / segment table / alarms // directly (the 1995 arrangement, mirrored by the reconstruction). // friend class Mech__DamageZone; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Shared Data Support // public: static Derivation ClassDerivations; static SharedData DefaultData; static const HandlerEntry MessageHandlerEntries[]; static MessageHandlerSet MessageHandlers; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Attribute Support -- the ENTITY-level rows the cockpit binds without a // subsystem prefix (the radar trio the map() primitive reads, the speed // pair the speedometer arc reads, and the duck/crouch state selector). // Chained onto JointedMover's index. // public: enum { RadarRangeAttributeID = JointedMover::NextAttributeID, RadarLinearPositionAttributeID, RadarAngularPositionAttributeID, LinearSpeedAttributeID, MaxRunSpeedAttributeID, DuckStateAttributeID, // // The 3-D renderer reads this BY NAME: DPLRenderer::SetupCull // (CODE/RP/MUNGA_L4/L4VIDEO.CPP:4990) fetches // "EyepointRotation" off the viewpoint entity and, for a // JointedMover, composes it with the siteeyepoint segment to // build worldToEyeMatrix. Without it the engine Fails every // frame. The member already existed -- only the publication // was missing. // EyepointRotationAttributeID, // // Bound BY NAME by an authored AttributeWatcher (BTL4.RES); the // shipped binary carries the same string in its pool. // UnstablePercentageAttributeID, CollisionSpeedAttributeID, DistanceToMissileAttributeID, FootStepAttributeID, IncomingLockAttributeID, // // The remaining names BTL4.RES binds watchers to. The two // state indicators already existed as members -- only the // publication was missing, same as EyepointRotation. // AnimationState is bound 307 times in the resource: it is how // the audio system follows the gait. // CollisionStateAttributeID, CollisionNormalAttributeID, AnimationStateAttributeID, ReduceButtonAttributeID, NextAttributeID }; static const IndexEntry AttributePointers[]; static AttributeIndexSet AttributeIndex; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Test Class Support // public: static Logical TestClass(Mech &); Logical TestInstance() const; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Construction and Destruction // public: typedef Mech__ModelResource ModelResource; typedef Mech__MakeMessage MakeMessage; typedef Mech__DamageZone DamageZone; static Mech* Make(MakeMessage *creation_message); Mech( MakeMessage *creation_message, SharedData &shared_data = DefaultData ); ~Mech(); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // App interface // public: void SetMappingSubsystem(Subsystem *mapper); // // Resolve a skeleton joint by name (the shared resolver the subsystems // use to bind their animated joints -- Torso twist, etc.): // GetSegment(name) -> segment jointIndex -> JointSubsystem::GetJoint. // Joint* ResolveJoint(const char *joint_name); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Per-frame simulation (the mech's body Performance; installed by the ctor // and dispatched each frame from Simulation::PerformAndWatch). // public: typedef void (Mech::*Performance)(Scalar time_slice); void SetPerformance(Performance performance) { Check(this); activePerformance = (Simulation::Performance)performance; } void Simulate(Scalar time_slice); // //-------------------------------------------------------------------- // The gait transition machine (mech2.cpp). // // Set*Animation binds a channel's clip player to the clip for a state // and records the state; *Transition is the shared tail every handler // ends in (bind the next state, then spend the leftover time in it). // // *ClipFinished are the callbacks the clip players invoke at end of // clip -- hence static, taking the mech explicitly. They are // RE-ENTRANT BY CONTRACT: each re-arms its channel and advances the // carryover itself, returning the distance that carryover covered, so // SequenceController::Advance folds the result into its own return. //-------------------------------------------------------------------- // void SetLegAnimation(int state); void SetBodyAnimation(int state); Scalar LegTransition(int next_state, Scalar advance_time, int move_joints); Scalar BodyTransition(int next_state, Scalar advance_time, int move_joints); static Scalar LegClipFinished( Mech *mech, unsigned callback_arg, Scalar carryover, int move_joints); // // The collision-damage economy (binary @0049ffcc): type-0 damage is // diverted here by the damage hub and rattles internal subsystems -- // armor is never touched by a collision. // void DistributeCollisionDamage(Damage *damage); static Scalar BodyClipFinished( Mech *mech, unsigned callback_arg, Scalar carryover, int move_joints); // //-------------------------------------------------------------------- // The gait clip loader (mech2.cpp). ResolveAnimationClip maps the // model's animation prefix + a 3-char suffix to a clip resource; // MeasureClipStride binds a loaded slot and integrates its keyframe // strides; LoadLocomotionClips fills animationClips[] and measures // every gait constant from the clips themselves. //-------------------------------------------------------------------- // ResourceDescription::ResourceID * ResolveAnimationClip(const char *prefix, const char *suffix); void MeasureClipStride(int slot, Scalar *total, Scalar *last_key); void LoadLocomotionClips(ModelResource *model); int LoadClipSlot(int slot, const char *prefix, const char *suffix); // //-------------------------------------------------------------------- // The per-frame gait entry points (mech2.cpp). Each advances its // channel's clip by the frame's time slice -- slewing the channel's // cycle speed toward its demand, driving the state machine at clip // boundaries through the *ClipFinished callbacks -- and returns the // cycle DISTANCE covered, which is what locomotion integrates. // // The leg version always moves the joints; the body version takes // move_joints explicitly so the caller can run it as a pure stride // measurement. (The airborne flavours @004a5bf8/@004a71f4 are a // later increment.) //-------------------------------------------------------------------- // Scalar AdvanceLegAnimation(Scalar time_slice); Scalar AdvanceBodyAnimation(Scalar time_slice, int move_joints); // //-------------------------------------------------------------------- // The LIMP flavours (mech2.cpp). Selected instead of the pair above // while the movement mode is 3 (left-leg limp) or 4 (right) and the // model carries the limp clip set. SETTLED from the binary after two // conflicting donor readings: the mode test is mech+0x40 -- the same // field the damage model documents as "limp gait graphic (left 3 / // right 4)" -- and NOT a jump-jet system. // // The normal advancers treat states 0x16-0x1b as the RESET group, so // the limp flavours must be selected while limping or the limp figure // gets its skeleton neutralized mid-cycle. //-------------------------------------------------------------------- // Scalar AdvanceLegAnimationGimp(Scalar time_slice); Scalar AdvanceBodyAnimationGimp(Scalar time_slice, int move_joints); Scalar GimpLegClipFinished(Scalar carryover); Scalar GimpBodyClipFinished(Scalar carryover, int move_joints); // // The respawn heal-and-move (binary @0049fb74): reposition the SAME // entity at the drop-zone origin, kill all motion, clear the death // latch, heal every hull zone and sweep the roster through // DeathReset. Shared by the initial drop-in and the respawn. // void Reset(const Origin &origin, Logical full_reset); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Resource creation // public: static void CreateMakeMessage( MakeMessage *creation_message, NotationFile *model_file, const ResourceDirectories *directories ); static ResourceDescription::ResourceID CreateModelResource( ResourceFile *resource_file, const char *model_name, NotationFile *model_file, const ResourceDirectories *directories, ModelResource *model = 0 ); static ResourceDescription::ResourceID CreateSubsystemStream( ResourceFile *resource_file, const char *model_name, NotationFile *model_file, const ResourceDirectories *directories ); static ResourceDescription::ResourceID CreateDamageZoneStream( ResourceFile *resource_file, const char *model_name, NotationFile *model_file, const ResourceDirectories *directories ); static ResourceDescription::ResourceID CreateSkeletonStream( ResourceFile *resource_file, const char *model_name, NotationFile *model_file, const ResourceDirectories *directories ); static ResourceDescription::ResourceID CreateExplosionTableStream( ResourceFile *resource_file, const char *model_name, NotationFile *model_file, const ResourceDirectories *directories ); typedef Logical (*FindNameFunction)( const char *control_name, ControlsMapping *mapping); static ResourceDescription::ResourceID CreateControlMappingStream( const char *mapping_name, NotationFile *mapping_file, FindNameFunction find_name, ResourceFile *resource_file, const char *model_name, NotationFile *model_file, const ResourceDirectories *directories, PlatformTool *current_tool ); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Subsystem roster accessors (the roster itself -- subsystemArray / // subsystemCount -- lives in the base Entity) // public: Subsystem* GetGyroSubsystem() { Check(this); return gyroSubsystem; } Subsystem* GetTorsoSubsystem() { Check(this); return sinkSourceSubsystem; } Subsystem* GetHudSubsystem() { Check(this); return hudSubsystem; } Subsystem* GetSensorSubsystem() { Check(this); return sensorSubsystem; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Locomotion parameters (read by MechControlsMapper::InterpretControls to // shape the demands, and by the drive in Simulate). reverseStrideLength is // the top/run cycle speed the throttle scales (the naming is the 1995 // field's; LoadLocomotionClips measures it from the run clips), walkStride // Length the walk speed. Turn rates are radians/sec. // public: Scalar GetReverseStrideLength() const { Check(this); return reverseStrideLength; } Scalar GetWalkStrideLength() const { Check(this); return walkStrideLength; } Scalar GetForwardThrottleScale() const { Check(this); return forwardThrottleScale; } void SetForwardThrottleScale(Scalar s){ Check(this); forwardThrottleScale = s; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Eyepoint / aim-ray composition. The pilot's torso-elevation aim (stick // pitch) does NOT tilt any skeleton joint on this mech family -- it pitches // the COCKPIT EYE and the weapon boresight (pixel-calibrated in the BT411 // reverse-engineering: "pitch does not work" turned out to mean nothing // consumed Torso's currentElevation, not that a joint was un-animated). // Composed each frame in Simulate; DPLEyeRenderable / the aim ray read it. // public: const EulerAngles& GetEyepointRotation() const { Check(this); return eyepointRotation; } // // The current target (the binary's mech target slot @0x388): read by // the weapons' HasActiveTarget / UpdateTargeting and the fire path. // The authentic writer is the reticle targeting step (mech4, the // render wave); the dev harness sets it directly. // Entity* GetTargetEntity() const { Check(this); return targetEntity; } void SetTargetEntity(Entity *target) { Check(this); targetEntity = target; } // // Damage entry (overrides the Entity handler): latches the attacker // (mech+0x43c, read by the zone LOD router) and feeds the gyro // cockpit bounce before chaining to the base zone routing. The // cylinder unaimed-hit resolver (type-0x1d table) is a later wave. // void TakeDamageMessageHandler(TakeDamageMessage *message); // // Damage-side death flag: the body graphic alarm (statusAlarm, // binary @0x714) at level >= 9 = the death/fall state. // Logical IsMechDestroyed() { Check(this); return statusAlarm.GetLevel() >= 9; } // // The live torso twist (the cylinder table's rotate-with-torso rows // follow it). NULL-safe: 0 with no torso subsystem. // Scalar CurrentTorsoTwist(); DamageLookupTable* GetDamageLookupTable() const { Check(this); return damageLookupTable; } // // Look-state commit (called by the controls mapper on a look-button // state change): re-aim the eyepoint from the model's authored look // angles. look_state = MechControlsMapper::LookState. // void CommitLookState(int look_state); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Local Data // protected: // // Cached subsystem back-pointers (also held in the base roster). // Subsystem *sensorSubsystem; Subsystem *gyroSubsystem; Subsystem *sinkSourceSubsystem; // the real Torso Subsystem *hudSubsystem; SubsystemMessageManager *messageManager; int weaponCount; // // Capability sub-rosters (Socket views onto the subsystem roster; // populated per-frame -- phase 5). // ChainOf controllableSubsystems; ChainOf watchedSubsystems; ChainOf heatableSubsystems; ChainOf weaponRoster; ChainOf damageableSubsystems; // // Embedded status / animation / naming state. // NameFilter mechNameFilter; AlarmIndicator masterAlarm; AlarmIndicator heatAlarm; AlarmIndicator stabilityAlarm; AlarmIndicator statusAlarm; Reticle targetReticle; StateIndicator animationState; StateIndicator replicantAnimationState; StateIndicator collisionState; SequenceController legAnimation; SequenceController bodyAnimation; // // GAIT ANIMATION (mech2.cpp). Two parallel channels, each a clip // player plus a state alarm holding the current MechAnimationState: // // LEG channel -- the locally-simulated gait. Its transitions read // the LIVE commanded speed from the controls mapper. // BODY channel -- the displayed-motion gait, whose advanced distance // is what feeds the mech's forward motion. Its // transitions read bodyTargetSpeed instead, so a // dead-reckoned or networked mech walks correctly // without a local controls mapper. // // The alarms ARE the state (binary mech+0x3b0 / +0x728 mirror the // alarm level); read them with GetLevel rather than keeping a copy. // AlarmIndicator legStateAlarm; AlarmIndicator bodyStateAlarm; Scalar legCycleSpeed; // channel-A cycle speed Scalar bodyCycleSpeed; // channel-B cycle speed Scalar forwardCycleRate; // cycle-speed slew rate Scalar gimpCycleRate; // slew rate while limping Scalar standSpeed; // "moving at all" threshold Scalar gimpSpeedMax; // limp cycle speed cap Scalar gimpStrideLength; // limp clip length (NEGATIVE // as authored -- the machines // fold the sign) Scalar globalTimeScale; // multiplies every increment // // Clip handle per gait SLOT (binary mech+0x5cc, indexed by state). // // SIZED 0x21, NOT AnimationCount. The name table stops at 0x1d but // the array does not: slot 0x20 (mech+0x64c) is the bump/crash clip // the mech binds on a hard wall impact. Sizing this by the enum // leaves that slot off the end. // int animationClips[AnimationSlotCount]; // // The OPTIONAL limp clip set (slots 22-27). LoadLocomotionClips // probes for the "wgl" clip; a model without it leaves hasGimpClips 0 // and those slots unfilled, so the limp machine must never be entered // for such a mech. (These four measured figures ARE the limp's -- // unlike gimpSpeedMax/gimpStrideLength above, which despite the names // are measured from the REVERSE clips. See MECH2.NOTES.md.) // int hasGimpClips; Scalar gimpLeftSpeedMax; Scalar gimpRightSpeedMax; Scalar gimpLeftStrideLength; Scalar gimpRightStrideLength; Scalar gyroRumbleTimer; // binary mech+0x5c4; the // clip loader zeroes it // // Per-frame gait state (the Advance* entry points). // // idleStrideScale scales ONLY the idle/transition clip group's // advance rate (binary +0x5ac); the cycles use their own // cycle/stride ratio instead. UNSOURCED -- nothing observed sets // it yet; defaulted 1. // // runSpeedMax (+0x7a0) caps the run cycle's upward slew the way // walkStrideLength caps the walk's. UNSOURCED -- not set by // LoadLocomotionClips; defaulted huge so it never binds until its // real source is found. // // The two reset latches and the death latch are one-shots the // fall/reset clip group clears; motionEventName/Armed are cleared // there too (their consumer is a later TU). // Scalar idleStrideScale; Scalar runSpeedMax; CString motionEventName; int motionEventArmed; int deathAnimationLatched; int legResetLatch; int bodyResetLatch; // // STAGED (binary @004a4c54, inline in the original mech.hpp): the // action-request bits are the update-record request mask -- setting // bit 3 (mask 8) asks the replication layer to emit the leg-state / // stability record. Our replication emitter is not reconstructed, // so the request has nowhere to go yet; the call sites keep the // binary's shape. // void ForceUpdate(int /*record_mask*/) { Check(this); } // // The movement mode (binary mech+0x40 == the simulation state): // 3/4 = left/right leg limp, 5-8 = the falls/deaths, 2||9 = the // death transition. Every gait read goes through here so the // BT_FORCE_LIMP bring-up hook (set once in the ctor) can exercise // the limp machinery before the damage model's limp hook exists. // int MovementMode() const { Check(this); return (limpModeOverride != 0) ? limpModeOverride : (int)((Mech *)this)->GetSimulationState(); } int limpModeOverride; // DEV: 3/4 from BT_FORCE_LIMP; 0 = off AverageOf telemetryFilter[5]; CString resourceNameA; CString resourceNameB; CString resourceNameC; // // Locomotion state (Phase 5.3). Turn rates in rad/s, speeds/strides in // world-units/s. reverseStrideLength = top (run) speed; walkStrideLength // = walk speed; reverseSpeedMax = the low-speed turn-rate gate; forward // ThrottleScale multiplies the forward demand; bodyTargetSpeed = the // demanded speed; currentBodySpeed = the accel-tracked actual speed. // Scalar walkingTurnRate; Scalar runningTurnRate; Scalar reverseStrideLength; Scalar walkStrideLength; Scalar reverseSpeedMax; Scalar forwardThrottleScale; Scalar maxBodyAcceleration; Scalar bodyTargetSpeed; Scalar currentBodySpeed; // // Composed each frame in Simulate: the look-state eye component // (lookPitch/lookYaw, set by CommitLookState from the authored look // angles) plus the Torso elevation. // EulerAngles eyepointRotation; Scalar lookPitch; Scalar lookYaw; Entity *targetEntity; EntityID lastInflictingID; // binary mech+0x43c -- last // attacker (zone LOD router) Scalar lastInflictingDamage; // the killing-blow // magnitude (kill score) int deathTransitionDone; // the once-per-death latch // (binary movementMode 2||9) DamageLookupTable *damageLookupTable; // binary mech+0x444 -- the // cylinder hit-location table // // Cockpit-published state (bound by name from L4GAUGE.CFG): the radar // scale + the pointers the map gauge follows, and the duck selector. // The position/angle pointers are the 1995 shape -- the gauge holds a // pointer TO the pointer and re-reads the live transform every frame. // Scalar radarRange; Point3D *radarLinearPosition; Quaternion *radarAngularPosition; int duckState; // // STAGED VALUE (the instability model is not reconstructed). // The mech streams the authored unstable* effect constants in its // resource (maxUnstableAcceleration and friends) but nothing yet // computes how close the mech is to going over. It is PUBLISHED // because an authored AttributeWatcher in BTL4.RES binds // "UnstablePercentage" by name and the engine Fails outright when // the name does not resolve (WATCHER.CPP:141). Holds 0 = stable // until the model lands. // Scalar unstablePercentage; // // The AUDIO watcher set, staged alongside unstablePercentage. // BTL4.RES binds exactly six attribute watchers by name -- // UnstablePercentage, CollisionSpeed, FootStep, IncomingLock, // DistanceToMissile (Mech) and SpeedEffect (Myomers) -- and the // engine Fails outright on any that does not resolve. // These are the sound triggers: impact volume, footfalls, the // missile-lock warning, the myomer whine. // // TYPES ARE PROVISIONAL. AttributeWatcherOf reads // *(T*)attributePointer (WATCHER.HPP:288) and the instantiation // comes from the resource, which we cannot read off the string // pool. Nothing drives these yet, so nothing ever changes and no // watcher can fire -- settle the types WITH the models that write // them (collision response, the gait FSM, the missile threat // track), not before. // Scalar collisionSpeed; Scalar distanceToMissile; int footStep; int incomingLock; UnitVector collisionNormal; // staged with collisionSpeed int reduceButton; // staged: cockpit button feed // // The model's authored look-view angles (rad; deg in the resource). // Scalar lookLeftAngle; Scalar lookRightAngle; Scalar lookFrontAngle; Scalar lookBackAngle; // // Remaining per-frame targeting / animation state, advanced by the // mech2/mech3/mech4 simulation. Reserved until that path is // reconstructed with named fields. // // // 60 ints carved out for the gait channel above (2 AlarmIndicators = 4, // 8 Scalars, animationClips[0x21] = 33, the optional limp set = 6, the // per-frame gait state = 9 counting CString loosely); 191 -> 131. The // block is a layout BUDGET, not offset-exact -- MECH-LAYOUT.md holds // the real offsets. // int reservedState[131]; }; #endif