Files
BT411/game/reconstructed/dmgtable.hpp
T
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.

Layout:
  engine/   MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
            work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
            models) + image codec; the minimal rp/ headers the audio HAL needs
  game/     reconstructed BT logic + surviving-original BT source + fwd shims
            + WinMain launcher
  content/  full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
  docs/     format specs + reconstruction ledgers
  reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
  tools/    MP console emulator + map/resource scanners

One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:03:40 -05:00

263 lines
10 KiB
C++

//===========================================================================//
// File: dmgtable.hpp //
// Project: BattleTech Brick: Entity Manager //
// Contents: Damage lookup table -- layered / pie-slice hit-location selector //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// --/--/95 ?? Initial coding. //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
// PROPRIETARY AND CONFIDENTIAL //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary. No header survived for this module
// (dmgtable.hpp/.cpp). The class cluster lives at @0x49de14..@0x49f5fb, was
// found ADJACENT to (but separate from) the DamageZone code in mechdmg.cpp,
// and is streamed from the ".tbl" notation file by the mech's
// CreateDamageZoneStream (@0x4a474c, mech.cpp) -- caller sites @0x49ed28 is
// invoked from the per-zone parser @line~11580 and the streamed form @0x49ea48
// from the object-stream reader @line~10423.
//
// Identifying strings (data @0x50bc00..0x50bd00):
// "DamageZone" "video" "skeleton" "%s %f" "RotateWithTorso" "PieSlice"
// "Layer" "TableEntryOf" "DamageZone Table Format!" "Not Found!"
// "percentages do not sum to 100%(i.e., 1.0)!" "missing RotateWithTorso!"
// "error in damage table format" " has bad format!"
// vtables: 0x50bd84 (DamageLookupTable), 0x50bd90 (PieSlice),
// 0x50bd9c (DamageZonePercentTable / leaf).
//
// The table is a three-level hit-location resolver:
//
// DamageLookupTable -- stack of armour LAYERS (penetration depth)
// -> PieSlice -- an angular sector wheel (optionally torso-relative)
// -> DamageZonePercentTable
// -- a weighted list of {zone segment index, percentage}
// whose percentages must sum to 1.0; a uniform random
// roll then selects the final damage-zone index.
//
// Class names are reconstructed from the .tbl section keywords; member names
// are inferred from usage and flagged best-effort. See dmgtable.cpp for the
// per-method @ADDR evidence.
//
#if !defined(DMGTABLE_HPP)
# define DMGTABLE_HPP
#if !defined(PLUG_HPP)
# include <plug.hpp>
#endif
#if !defined(TABLE_HPP)
# include <table.hpp>
#endif
#include "mechrecon.hpp" // reconstruction shim
//##################### Forward Class Declarations #######################
class Mech;
class NotationFile;
class GenericObjectStream;
class Point3D;
//###########################################################################
//##################### DamageZonePercentTable ##########################
//###########################################################################
//
// Leaf node. Holds a sorted, cumulative-weighted list of damage-zone
// segment indices and the uniform random "roll" that turns a [0,1) draw into
// one of those zones. Built from a ".tbl" "DamageZone" page whose lines are
// "<zoneName> <percentage>" ("%s %f") and whose percentages must total 1.0.
// (vtable @0x50bd9c, ctor @0x49deb0, dtor @0x49df80, object size 0x2c.)
//
class DamageZonePercentTable:
public Plug
{
public:
//
// Stream constructor -- rebuilds the entry list from an object stream.
// @0x49deb0 (FUN_004178cc base Plug ctor, then ReadEntries @0x49e5e4)
//
DamageZonePercentTable() {} // reconstruction: stack-temp default ctor
DamageZonePercentTable(
Mech *owner,
GenericObjectStream *stream
);
~DamageZonePercentTable(); // @0x49df80
Logical
TestInstance() const; // @0x49e000 (returns True)
//
// @0x49de14 -- uniform random weighted roll. Walks the entries in
// ascending cumulative-percentage order, draws a [0,1) sample
// (FUN_00408050) and returns the segment index (entry+0xc) of the first
// entry whose cumulative threshold (entry+0x14) exceeds the draw.
// Returns -1 if the list is empty.
//
int
SelectZone() const;
//
// @0x49e00c -- parse a "DamageZone" page from the model .tbl: read the
// "video"/"skeleton" model, then each "<zone> <pct>" line; resolve the
// zone name to a skeleton segment index (Get_Segment_Index @0x4274f8),
// accumulate the percentages and verify the total == 1.0.
// Returns False (and logs) on a bad line, an unknown zone, or a total
// that does not sum to 1.0.
//
static int
BuildFromNotation(
NotationFile *model_file,
const char *model_name,
const char *page_name,
NotationFile *table_file,
Mech *owner,
GenericObjectStream *stream
);
//
// @0x49e524 WriteEntries (serialise count + {threshold,zone} pairs),
// @0x49e5e4 ReadEntries (deserialise into the entry table).
//
void WriteEntries(GenericObjectStream *stream) const; // @0x49e524
void ReadEntries (GenericObjectStream *stream); // @0x49e5e4
protected:
Mech *owner; // @0x0c param_1[3]
void *refCount; // @0x10 param_1[4] (MemoryBlock/RefCount)
ReconTable<int> entries; // @0x14 param_1+5 sorted by cumulative %
// each entry: zoneIndex@+0xc, cumulativePercent@+0x14
};
//###########################################################################
//############################# PieSlice ################################
//###########################################################################
//
// An angular sector "wheel". N equal-angle slices (each 2*PI/N radians) wrap
// a circle; the slice that contains the incoming hit direction is chosen and
// its leaf DamageZonePercentTable is rolled. When "RotateWithTorso" is set
// the incoming angle is measured relative to the live torso orientation
// (orientationSource+0x1d8), so the slice layout turns with the mech's torso.
// (vtable @0x50bd90, ctor @0x49e740, object size 0x30.)
//
class PieSlice:
public Plug
{
public:
//
// @0x49e740 -- stream constructor. Reads owner@+0xc and sliceCount@+0x2c,
// builds 'sliceCount' DamageZonePercentTable children (@0x49deb0) each
// keyed to a cumulative angle i*(2*PI/sliceCount), and caches the torso
// orientation source = owner+0x438.
//
PieSlice(
Mech *owner,
GenericObjectStream *stream
);
~PieSlice();
//
// @0x49e678 -- pick a slice for incoming angle 'theta' (radians). When
// rotateWithTorso is set, theta is shifted by the torso orientation
// (orientationSource+0x1d8) and wrapped into [0, 2*PI). The slice index
// = floor(theta * sliceCount / (2*PI)) feeds the child table lookup.
//
DamageZonePercentTable *
SelectSlice(Scalar theta) const;
//
// @0x49e88c -- parse a "PieSlice" page: read the mandatory bool
// "RotateWithTorso" (logged "missing RotateWithTorso!" if absent), then
// each named sub-page as a DamageZonePercentTable (@0x49e00c).
// Logs "error in damage table format" on a malformed entry.
//
static int
BuildFromNotation(
NotationFile *model_file,
const char *model_name,
const char *page_name,
NotationFile *table_file,
Mech *owner,
GenericObjectStream *stream
);
protected:
Mech *owner; // @0x0c param_1[3]
void *orientationSource; // @0x10 param_1[4] = owner+0x438 (torso)
ReconTable<DamageZonePercentTable*>
slices; // @0x14 param_1+5
int rotateWithTorso; // @0x28 (read first in BuildFromNotation)
int sliceCount; // @0x2c param_1[0xb]
};
//###########################################################################
//######################### DamageLookupTable ###########################
//###########################################################################
//
// Top-level table streamed from the ".tbl" key for a mech. A stack of armour
// LAYERS: the deeper a shot penetrates, the higher the layer index selected.
// Each layer is a PieSlice wheel. (vtable @0x50bd84, ctor @0x49ea48,
// dtor @0x49eadc, object size 0x30.)
//
class DamageLookupTable:
public Plug
{
public:
//
// @0x49ea48 -- stream constructor. Reads layerCount@+0x28 and builds
// that many PieSlice layers (@0x49e740).
//
DamageLookupTable(
Mech *owner,
GenericObjectStream *stream
);
~DamageLookupTable(); // @0x49eadc
Logical
TestInstance() const; // @0x49eb48 / @0x49e880 (returns True)
//
// @0x49eb54 -- resolve a hit to a damage zone. Transforms the impact
// point into the mech frame (owner+0xd0), derives a penetration-depth
// fraction (clamped against the armour reference at owner+0x2ec[+0xc]),
// maps it to a layer index, fetches that layer's PieSlice and forwards
// an incident angle (computed from the hit direction, or a random draw
// when the direction is degenerate) to PieSlice::SelectSlice (@0x49e678).
//
void
ResolveHit(const Point3D &impact) const;
//
// @0x49ed28 -- parse the layered table. Reads consecutive "Layer1",
// "Layer2", ... pages (number suffix via FUN_004dd3ec), parsing each as
// a PieSlice page (@0x49e88c). Logs "<name> has bad format!" when no
// layers are found.
//
static int
BuildFromNotation(
NotationFile *model_file,
const char *model_name,
NotationFile *table_file,
Mech *owner,
GenericObjectStream *stream
);
protected:
Mech *owner; // @0x0c param_1[3]
ReconTable<PieSlice*>
layers; // @0x10 param_1+4
int layerCount; // @0x28 param_1[10]
};
//###########################################################################
// Tool support -- entry comparator
//
// @0x49f301 -- orders table entries by their cumulative threshold (+0x14)
// within an epsilon (_DAT_0049f369 ~= 1.0e-4). Used to keep the entry tables
// sorted so the SelectZone / SelectSlice scans are monotone.
//
int
CompareTableEntryThreshold(const void *a, const void *b);
#endif