The per-contact responder (@004abb40), overriding the engine's protected
virtual per the MECH4.NOTES.md decode:
THE SEPARATING GATE: a contact whose relative velocity points away from
the surface ((normal . rel) < -1e-4) is skipped entirely -- no damage
while pulling apart.
MECH-VS-MECH: another mech takes the collision damage too, zone -1, its
own cylinder resolving where the ram landed. Both sides of a ram hurt,
and both route through the 5.3.100 economy, so a ram rattles rather than
executes.
THE CRUSHABLE SENTINEL: a CulturalIcon takes its crunch dispatch, and one
WITHOUT the StoppingCollisionVolume flag then sets the amount to exactly
0.00123f -- Simulate's response block reads that value as "the move
stands", restores the post-snap saves, and drives on through. Trees
fall; walls do not.
THE CONTACT ACCUMULATOR feeds the audio-bound collisionState indicator
(0->1 fires the impact sound once per contact, not per frame) and
captures the impact speed for the authored volume/brightness scales.
[T3 simplification, tagged in source: the authentic InitialHit/Slide
split keys StaticBounce's OUT coefficients against an unidentified
literal @0x4ac048.]
LIVE, one arena run, all three behaviours in the wild and zero deaths:
[crash] CRUNCH (crushable) at (371.551,-102.862) <- drove through it
[colldmg] rattle 1.72691 pts in 3 sub-hits
[crash] BLOCK dmg=47969.6 iv2=932.659 KNOCKDOWN <- wall still walls
[crash] BLOCK dmg=630 iv2=12.5 (x5, free taps) <- grind still free
STAGED remainder, named in MECH4.NOTES.md: the gyro crunch feed (torque 0.4
/ impulse 0.2 -- the gyro is a feel-wave stub), and the authentic
InitialHit/Slide split pending the @0x4ac048 literal.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
892 lines
32 KiB
C++
892 lines
32 KiB
C++
//===========================================================================//
|
|
// 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 <jmover.hpp>
|
|
# endif
|
|
|
|
# if !defined(RETICLE_HPP)
|
|
# include <reticle.hpp>
|
|
# endif
|
|
|
|
# if !defined(ALARM_HPP)
|
|
# include <alarm.hpp>
|
|
# endif
|
|
|
|
# if !defined(STATE_HPP)
|
|
# include <state.hpp>
|
|
# endif
|
|
|
|
# if !defined(CHAIN_HPP)
|
|
# include <chain.hpp>
|
|
# endif
|
|
|
|
# if !defined(AVERAGE_HPP)
|
|
# include <average.hpp>
|
|
# endif
|
|
|
|
# if !defined(CSTR_HPP)
|
|
# include <cstr.hpp>
|
|
# endif
|
|
|
|
# if !defined(SEQCTL_HPP)
|
|
# include <seqctl.hpp>
|
|
# endif
|
|
|
|
# if !defined(NAMEFILT_HPP)
|
|
# include <namefilt.hpp>
|
|
# endif
|
|
|
|
# if !defined(ROTATION_HPP)
|
|
# include <rotation.hpp>
|
|
# 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);
|
|
|
|
//
|
|
// The per-contact collision responder (binary @004abb40) -- the
|
|
// override of the engine's protected virtual. Base resolution plus:
|
|
// the separating-contact gate, the mech-vs-mech ram dispatch, the
|
|
// CulturalIcon crunch, and the 0.00123f walk-through sentinel for
|
|
// crushable props. See MECH4.NOTES.md for the decode.
|
|
//
|
|
void
|
|
ProcessCollision(
|
|
Scalar time_slice,
|
|
BoxedSolidCollision &collision,
|
|
const Point3D &old_position,
|
|
Damage *damage);
|
|
|
|
int collisionTemporaryState; // binary mech+0x44c -- the frame's
|
|
// contact accumulator; pushed into
|
|
// collisionState once per frame
|
|
|
|
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<Subsystem*> controllableSubsystems;
|
|
ChainOf<Subsystem*> watchedSubsystems;
|
|
ChainOf<Subsystem*> heatableSubsystems;
|
|
ChainOf<Subsystem*> weaponRoster;
|
|
ChainOf<Subsystem*> 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<Scalar> 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<T> 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
|