//===========================================================================// // 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 #pragma hdrstop #if !defined(DMGTABLE_HPP) # include #endif #if !defined(MECH_HPP) # include #endif #if !defined(RANDOM_HPP) # include #endif #include 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; }