BT410 Phase 5.3.23: the cylinder hit-location table -- unaimed hits land WHERE THEY STRIKE

DMGTABLE born (type-29 streams, BT411 byte-verified format): the height x
angle grid -- DamageLookupTable rows by height, PieSlice cells by angle
(rotate-with-torso rows add the live twist), DamageZonePercentTable = the
cumulative hit-distribution roll (first threshold above the roll -- the
BattleTech dice scatter).  Loaded in the Mech ctor by the DamageZoneStream
member's NAME (mech+0x444, resident); Mech::TakeDamageMessageHandler
resolves invalidDamageZone hits through it (binary @0x4a0264 tail).

The missile fuse becomes the authentic unaimed producer: zone -1 + the
round's world position as the impact point (ENTITY3.HPP: "damage zones are
only valid via reticle based weapons").

Fight-verified on the authentic 7-row bhk1 table: flat-flying SRMs strike
low -> row 0 -> FEET AND LEG zones, side-correct by impact angle (+x right,
-x left), identical geometry spread across the cell distribution by the
roll.  17/17 missile lifecycle, zero faults; smoke + novice green.  Also
fixed: the ctor's reservedState zero-loop counted past the shrunken array.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-24 14:46:39 -05:00
co-authored by Claude Fable 5
parent bfff7fe874
commit 2f0d3b6239
6 changed files with 2111 additions and 1533 deletions
+333
View File
@@ -0,0 +1,333 @@
//===========================================================================//
// File: dmgtable.cpp //
// Project: BattleTech Brick: Mech damage //
// Contents: The cylinder hit-location table (unaimed damage -> zone) //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
//
// RECONSTRUCTED from the binary via the BT411 reversal (byte-verified
// against the real type-29 streams, 18 tables). STREAM FORMAT:
//
// Table { i32 rowCount; Row[rowCount] }
// Row { i32 rotateWithTorso; i32 cellCount; Cell[cellCount] }
// Cell { i32 nameLen; char name[nameLen]; u8 0; i32 entryCount;
// Entry[entryCount] }
// Entry { f32 cumulativeThreshold; i32 zoneIndex }
//
// Real sample (ava1, 7 rows x 8 cells): rows 0-2 fixed to the chassis
// (feet/legs), rows 3-6 rotate with the torso; a foot cell =
// {0.5->16, 0.8->10, 1.0->5}.
//
#include <bt.hpp>
#pragma hdrstop
#if !defined(DMGTABLE_HPP)
# include <dmgtable.hpp>
#endif
#if !defined(MECH_HPP)
# include <mech.hpp>
#endif
#if !defined(RANDOM_HPP)
# include <random.hpp>
#endif
#include <math.h>
static const Scalar kTwoPi = 6.2831855f; // _DAT_0049e810
static const Scalar kDegenerateEps = 1.0e-4f; // _DAT_0049ed04
//
// STAGED: the binary reads the mech's collision-cylinder height
// (mech+0x2ec -> +0xc); the collision volume isn't reconstructed yet, so a
// fixed standing height maps impacts onto the rows. (bhk1 torso mounts sit
// at y~4; 10u spans feet->cockpit plausibly.)
//
static const Scalar kStagedMechHeight = 10.0f;
//
//#############################################################################
// DamageZonePercentTable -- one angular cell (binary ctor @0049deb0; the
// entry fill @0049e5e4). The cell NAME string ([i32 len][chars][NUL]) is
// consumed and discarded, exactly as the binary does.
//#############################################################################
//
DamageZonePercentTable::DamageZonePercentTable(MemoryStream *stream)
{
Check(stream);
int name_length = 0;
*stream >> name_length;
if (name_length > 0)
{
stream->AdvancePointer(name_length);
}
stream->AdvancePointer(1); // the NUL
*stream >> entryCount;
thresholds = NULL;
zoneIndices = NULL;
if (entryCount > 0)
{
thresholds = new Scalar[entryCount];
Register_Pointer(thresholds);
zoneIndices = new int[entryCount];
Register_Pointer(zoneIndices);
for (int ee = 0; ee < entryCount; ++ee)
{
*stream >> thresholds[ee];
*stream >> zoneIndices[ee];
}
}
}
DamageZonePercentTable::~DamageZonePercentTable()
{
if (thresholds != NULL)
{
Unregister_Pointer(thresholds);
delete [] thresholds;
}
if (zoneIndices != NULL)
{
Unregister_Pointer(zoneIndices);
delete [] zoneIndices;
}
}
//
//#############################################################################
// SelectZone (binary @0049de14): uniform roll, first cumulative threshold
// ABOVE the roll wins (entries ascend); the last entry backstops.
//#############################################################################
//
int
DamageZonePercentTable::SelectZone()
{
Check(this);
if (entryCount <= 0)
{
return -1;
}
Scalar roll = (Scalar)Random;
for (int ee = 0; ee < entryCount; ++ee)
{
if (thresholds[ee] > roll)
{
return zoneIndices[ee];
}
}
return zoneIndices[entryCount - 1];
}
//
//#############################################################################
// PieSlice -- one height row (binary ctor @0049e740).
//#############################################################################
//
PieSlice::PieSlice(Mech *mech, MemoryStream *stream)
{
Check(mech);
Check(stream);
owner = mech;
*stream >> rotateWithTorso;
*stream >> cellCount;
cells = NULL;
if (cellCount > 0)
{
cells = new DamageZonePercentTable *[cellCount];
Register_Pointer(cells);
for (int cc = 0; cc < cellCount; ++cc)
{
cells[cc] = new DamageZonePercentTable(stream);
Register_Object(cells[cc]);
}
}
}
PieSlice::~PieSlice()
{
if (cells != NULL)
{
for (int cc = 0; cc < cellCount; ++cc)
{
Unregister_Object(cells[cc]);
delete cells[cc];
}
Unregister_Pointer(cells);
delete [] cells;
}
}
//
//#############################################################################
// SelectSlice (binary @0049e678): rotate-with-torso rows add the LIVE torso
// twist to the impact angle (an upper-body ring follows the twist; legs
// stay chassis-fixed), wrap to [0,2pi), pick the cell by angular fraction.
//#############################################################################
//
DamageZonePercentTable*
PieSlice::SelectSlice(Scalar angle)
{
Check(this);
if (cellCount <= 0)
{
return NULL;
}
Scalar a = angle;
if (rotateWithTorso)
{
a += owner->CurrentTorsoTwist();
while (a >= kTwoPi)
{
a -= kTwoPi;
}
while (a < 0.0f)
{
a += kTwoPi;
}
}
int index = (int)((Scalar)cellCount * a / kTwoPi);
if (index < 0)
{
index = 0;
}
if (index >= cellCount)
{
index = cellCount - 1;
}
return cells[index];
}
//
//#############################################################################
// DamageLookupTable (binary ctor @0049ea48).
//#############################################################################
//
DamageLookupTable::DamageLookupTable(Mech *mech, MemoryStream *stream)
{
Check(mech);
Check(stream);
owner = mech;
*stream >> rowCount;
rows = NULL;
if (rowCount > 0)
{
rows = new PieSlice *[rowCount];
Register_Pointer(rows);
for (int rr = 0; rr < rowCount; ++rr)
{
rows[rr] = new PieSlice(mech, stream);
Register_Object(rows[rr]);
}
}
}
DamageLookupTable::~DamageLookupTable()
{
if (rows != NULL)
{
for (int rr = 0; rr < rowCount; ++rr)
{
Unregister_Object(rows[rr]);
delete rows[rr];
}
Unregister_Pointer(rows);
delete [] rows;
}
}
//
//#############################################################################
// ResolveHit (binary @0049eb54 + the glue @0049ed0c): world impact ->
// mech-local; clamp local height onto the cylinder -> row; impact angle =
// atan2(z, x) wrapped [0,2pi) (a degenerate on-axis hit rolls a random
// angle); row/angle -> cell -> the distribution roll -> a hull zone.
//#############################################################################
//
int
DamageLookupTable::ResolveHit(const Point3D &world_impact)
{
Check(this);
if (rowCount <= 0)
{
return -1;
}
//
// World -> mech-local.
//
LinearMatrix to_world;
to_world = owner->localToWorld;
LinearMatrix to_local;
to_local.Invert(to_world);
Point3D local;
local.Multiply(world_impact, to_local);
//
// Height -> row.
//
Scalar height = kStagedMechHeight;
Scalar y = local.y;
if (y < 0.0f)
{
y = 0.0f;
}
if (y > height)
{
y = height;
}
int row = (int)((Scalar)rowCount * y / height);
if (row < 0)
{
row = 0;
}
if (row >= rowCount)
{
row = rowCount - 1;
}
//
// Impact angle.
//
Scalar angle;
Scalar ax = (local.x < 0.0f) ? -local.x : local.x;
Scalar az = (local.z < 0.0f) ? -local.z : local.z;
if (ax > kDegenerateEps || az > kDegenerateEps)
{
angle = (Scalar)atan2((double)local.z, (double)local.x);
if (angle < 0.0f)
{
angle += kTwoPi;
}
}
else
{
angle = (Scalar)Random * kTwoPi;
}
DamageZonePercentTable *cell = rows[row]->SelectSlice(angle);
int zone = (cell != NULL) ? cell->SelectZone() : -1;
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[cyl] impact local=(" << local.x
<< "," << local.y << "," << local.z
<< ") row=" << row << "/" << rowCount
<< " angle=" << angle
<< " -> zone " << zone << endl << flush;
}
return zone;
}
+105
View File
@@ -0,0 +1,105 @@
//===========================================================================//
// File: dmgtable.hpp //
// Project: BattleTech //
// Contents: The cylinder hit-location table (unaimed damage -> zone) //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
//
// STAGED header -- no dmgtable.hpp survives; the interface is reconstructed
// from the binary (BT411, byte-verified vs the real type-29 .RES streams):
// a passive nested height x angle grid of three container classes,
//
// DamageLookupTable (binary ctor @0049ea48, vtable @0050bd84, 0x2c)
// -> PieSlice ROWS by height (@0049e740, @0050bd90, 0x30)
// -> DamageZonePercentTable CELLS by angle (@0049deb0, @0050bd9c, 0x2c)
// -> the cumulative hit-distribution (roll -> zone index).
//
// Class names follow the BT411 reconstruction (no 1995 names survive).
// The binary keys rows/cells into refcounted keyed lists; direct-indexed
// arrays are equivalent (the float key (i+1)*2pi/count === cells[i]).
//
#if !defined(DMGTABLE_HPP)
# define DMGTABLE_HPP
//##################### Forward Class Declarations #######################
class Mech;
//###########################################################################
//##################### DamageZonePercentTable ##########################
//###########################################################################
//
// One angular CELL: the cumulative hit-distribution. Entries ascend;
// SelectZone (binary @0049de14) rolls uniform [0,1) and returns the
// first entry whose threshold exceeds the roll -- the classic
// BattleTech dice hit-scatter.
//
class DamageZonePercentTable
{
public:
DamageZonePercentTable(MemoryStream *stream);
~DamageZonePercentTable();
int
SelectZone();
int entryCount;
Scalar *thresholds; // cumulative, ascending
int *zoneIndices; // -> Entity::damageZones[]
};
//###########################################################################
//############################ PieSlice #################################
//###########################################################################
//
// One height ROW: the ring of angular cells. rotateWithTorso rows
// (upper body) add the live torso twist to the impact angle before the
// cell pick (binary @0049e678).
//
class PieSlice
{
public:
PieSlice(Mech *mech, MemoryStream *stream);
~PieSlice();
DamageZonePercentTable*
SelectSlice(Scalar angle);
Mech *owner;
int rotateWithTorso;
int cellCount;
DamageZonePercentTable
**cells;
};
//###########################################################################
//######################## DamageLookupTable ############################
//###########################################################################
//
// The table: rows stacked by height over the mech's cylinder.
// ResolveHit (binary @0049eb54 + glue @0049ed0c) maps a WORLD impact
// point -> mech-local -> (height row, impact angle) -> cell -> the
// distribution roll -> a hull zone index.
//
class DamageLookupTable
{
public:
DamageLookupTable(Mech *mech, MemoryStream *stream);
~DamageLookupTable();
int
ResolveHit(const Point3D &world_impact);
Mech *owner;
int rowCount;
PieSlice
**rows;
};
#endif
@@ -0,0 +1,32 @@
# DMGTABLE.CPP — the cylinder hit-location table
**Status: RECONSTRUCTED (5.3.23) — loads the authentic type-29 streams and
resolves unaimed hits live (fight-verified).**
No 1995 header survives; class names (DamageLookupTable / PieSlice /
DamageZonePercentTable) follow the BT411 reversal, which byte-verified the
stream against the real .RES (18 tables). Binary anatomy: TABLE ctor
@0049ea48 (vtable @0050bd84) → ROW @0049e740 (@0050bd90) → CELL @0049deb0
(@0050bd9c); lookup @0049eb54 (height→row, atan2 angle) + @0049e678
(rotate-with-torso, angle→cell) + @0049de14 (the cumulative-distribution
roll); glue @0049ed0c. The binary's refcounted keyed lists are replaced by
direct-indexed arrays (the float key (i+1)·2π/count ≡ cells[i]).
Stream (type 29, found by the mech's DamageZoneStream member NAME):
`Table{i32 rows; Row[]} Row{i32 rotateWithTorso; i32 cells; Cell[]}
Cell{i32 nameLen; chars; NUL; i32 entries; {f32 cumThreshold; i32 zone}[]}`.
Wiring: the Mech ctor Pass-3 tail loads it (mech+0x444, kept locked);
Mech::TakeDamageMessageHandler resolves `invalidDamageZone` hits through it
before chaining the base. The MISSILE fuse is the authentic unaimed
producer: zone 1 + the round's world position as impactPoint (the
ENTITY3.HPP warning: "damage zones are only valid via reticle based
weapons"); victims without a table keep the interim random pick.
Verified (bhk1, 7 rows): flat-flying SRMs strike low → row 0 → feet/leg
zones, side-correct by impact angle (+x hits right, x left), identical
geometry spread across the cell's distribution by the roll. Zero faults.
STAGED: mech cylinder height = 10.0 constant (binary reads the collision
cylinder mech+0x2ec→+0xc — not reconstructed); torso twist feeds
rotate-with-torso rows via Mech::CurrentTorsoTwist (Torso::CurrentTwist).
File diff suppressed because it is too large Load Diff
+468 -455
View File
@@ -1,455 +1,468 @@
//===========================================================================//
// 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;
//###########################################################################
//######################### 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));
}
};
//###########################################################################
//############################## 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;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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; }
//
// 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;
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)
//
// 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.
//
int reservedState[199];
};
#endif
//===========================================================================//
// 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));
}
};
//###########################################################################
//############################## 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;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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;
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)
DamageLookupTable *damageLookupTable; // binary mech+0x444 -- the
// cylinder hit-location table
//
// 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.
//
int reservedState[198];
};
#endif
+20 -1
View File
@@ -313,8 +313,27 @@ void
if (range <= fuseRadius && seeker->targetEntity != NULL)
{
Entity *victim = seeker->targetEntity;
//
// The AUTHENTIC unaimed delivery: zone -1 (invalidDamageZone) with
// the round's world position as the impact point -- the victim's
// cylinder hit-location table resolves WHERE the round physically
// struck ("For BattleTech, damage zones are only valid via reticle
// based weapons" -- the surviving ENTITY3.HPP warning). Without a
// table the base handler drops the hit, so victims with no
// type-29 stream fall back to the interim random-zone pick.
//
int zone = -1;
if (victim->damageZoneCount > 0)
damageData.impactPoint = localOrigin.linearPosition;
if (victim->IsDerivedFrom(Mech::ClassDerivations))
{
Mech *mech_victim = (Mech *)victim;
if (mech_victim->GetDamageLookupTable() == NULL
&& victim->damageZoneCount > 0)
{
zone = Random(victim->damageZoneCount);
}
}
else if (victim->damageZoneCount > 0)
{
zone = Random(victim->damageZoneCount);
}