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>
210 lines
9.1 KiB
C++
210 lines
9.1 KiB
C++
//===========================================================================//
|
|
// File: btl4galm.cpp //
|
|
// Project: BattleTech Brick: Gauge Renderer Manager //
|
|
// Contents: BTL4GaugeAlarmManager -- the BattleTech override of the MUNGA //
|
|
// GaugeAlarmManager. It supplies the two per-item stream virtuals //
|
|
// that the base class declares pure ("not overridden!"): it knows how //
|
|
// to compile a named cockpit-gauge alarm definition into a memory //
|
|
// stream (CreateGaugeAlarmStreamItem) and how to instantiate a live //
|
|
// GaugeAlarm for a given subsystem/condition from that stream //
|
|
// (ReadGaugeAlarmStreamItem). //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// 02/22/96 CPB Initial coding. //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1996, Virtual World Entertainment, Inc. All rights reserved //
|
|
// PROPRIETARY AND CONFIDENTIAL //
|
|
//===========================================================================//
|
|
//
|
|
// RECONSTRUCTED -- IMPORTANT PROVENANCE NOTE
|
|
// ------------------------------------------
|
|
// btl4galm.obj links after btl4grnd.obj and before btl4vid.obj (BTL4.MAK
|
|
// BTL4_OBJS order). In the shipped optimised image (BTL4OPT.EXE) there is **no
|
|
// distinct, separately-emitted BTL4GaugeAlarmManager override body**:
|
|
// * the only GaugeAlarmManager code in the binary is the MUNGA base class in
|
|
// gaugalrm.cpp (recovered/all/part_006.c, @00448928..@00448f48), including
|
|
// the two "not overridden!" trap virtuals
|
|
// CreateGaugeAlarmStreamItem @00448ab8 (asserts GAUGALRM.CPP:0x71)
|
|
// ReadGaugeAlarmStreamItem @00448ad4 (asserts GAUGALRM.CPP:0x7e)
|
|
// and the base driver helpers that CALL those virtuals
|
|
// GaugeAlarmManager::CreateGaugeAlarmStream @00448af0
|
|
// GaugeAlarmManager::BuildAlarmsForModel @00448d00
|
|
// * no subclass vtable (one whose Create/Read slots point into BattleTech
|
|
// code) appears in vtables.tsv, and no caller constructs a subclass
|
|
// (the base ctors @00448ebd/@00448edc/@00448efb have no external callers).
|
|
// The code region the linker reserved for btl4galm.obj (@004cc40c..@004cdac0)
|
|
// actually holds a 3-D HUD-model builder (PNAME1..8.bgf), i.e. the compiler
|
|
// folded BTL4GaugeAlarmManager's tiny overrides away / it was never instantiated
|
|
// in this build configuration.
|
|
//
|
|
// This file is therefore reconstructed to MATCH THE SURVIVING BTL4GALM.HPP and
|
|
// the base driver's calling contract (the argument order, the GaugeAlarm object
|
|
// it expects, and the "undefined alarm type ... resource not built" diagnostic
|
|
// the base prints when the virtual returns False). Bodies below are BEST-EFFORT
|
|
// (low confidence): they describe what the override must do for the base driver
|
|
// to function, not bytes lifted from this image.
|
|
//
|
|
// Base contract (recovered, high confidence):
|
|
// GaugeAlarmManager::CreateGaugeAlarmStream @00448af0
|
|
// - opens the "gaugeAlarm" sub-resource of a model record (FUN_00404720,
|
|
// "gaugeAlarm" @004eecc9), counts its entries, makes a MemoryStream
|
|
// (0x18), and for each entry calls the virtual
|
|
// this->CreateGaugeAlarmStreamItem(stream, entry_name, entry_data)
|
|
// (entry_name @entry+8, entry_data @entry[1]); if it returns 0 it prints
|
|
// "Model file '<model>' has undefined alarm type '<name>' -
|
|
// resource not built." (strings @004eecd4/@004eece1/@004eecfe)
|
|
// and aborts the build. On success it bakes the stream (FUN_00406f3c).
|
|
// GaugeAlarmManager::BuildAlarmsForModel @00448d00
|
|
// - for each baked alarm record it makes a GaugeAlarm (FUN_004489e8,
|
|
// 0x28 bytes; vtable PTR_FUN_004eed7c; ctor stores entity@+0xc,
|
|
// subsystem@+0x10, condition@+0x14), adds it to the manager's alarm list
|
|
// (this+4, list-insert vtbl+4), then calls the virtual
|
|
// this->ReadGaugeAlarmStreamItem(alarm, entity, subsystem, condition,
|
|
// stream)
|
|
// once per record to configure it.
|
|
//
|
|
// Engine-internal helper map (from the base + the BattleTech alarm bricks):
|
|
// FUN_004489e8 GaugeAlarm ctor FUN_00448a28 GaugeAlarm dtor
|
|
// FUN_00448ebd GaugeAlarmManager base ctor (chains a ChainOf<GaugeAlarm*>)
|
|
// FUN_0040385c Verify(msg,file,line)
|
|
// FUN_004dbb24 DebugStream << (char*) FUN_004db78c DebugStream << (int)
|
|
// DAT_00524e20 DebugStream (warning channel)
|
|
// MemoryStream write/read = (*stream + 0x20)(stream,&v,4) / (*stream+0x1c)(...)
|
|
//
|
|
|
|
#include <bt.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(BTL4GALM_HPP)
|
|
# include <btl4galm.hpp>
|
|
#endif
|
|
#if !defined(APP_HPP)
|
|
# include <app.hpp>
|
|
#endif
|
|
|
|
|
|
//
|
|
// Recognised cockpit gauge-alarm type names. (BattleTech's l4gauge.cfg
|
|
// "gaugeAlarm" sections name one of these; the threshold / flash parameters
|
|
// follow in alarm_data.) The exact spelling list could not be recovered from
|
|
// this image -- it lived in the folded-away override -- so the set below is the
|
|
// minimal set implied by the GaugeAlarm fields the cockpit gauges flash on
|
|
// (heat / power / armour / ammo). BEST-EFFORT.
|
|
//
|
|
enum GaugeAlarmType
|
|
{
|
|
gaugeAlarmThreshold, // value crosses a fixed threshold
|
|
gaugeAlarmRange, // value leaves a [low,high] band
|
|
gaugeAlarmState, // subsystem operational-state change
|
|
gaugeAlarmFlash, // timed flash while condition holds
|
|
gaugeAlarmTypeCount,
|
|
gaugeAlarmUndefined = -1
|
|
};
|
|
|
|
|
|
//
|
|
// BTL4GaugeAlarmManager::CreateGaugeAlarmStreamItem
|
|
//
|
|
// Called by GaugeAlarmManager::CreateGaugeAlarmStream (@00448af0) once per
|
|
// "gaugeAlarm" entry while a gauge model is being compiled. Recognise the
|
|
// alarm type spelled in <alarm_name>, parse its parameters out of <alarm_data>,
|
|
// and serialise (type, parameters) into <mem_stream>. Returning False makes
|
|
// the base print "Model file '...' has undefined alarm type '<alarm_name>' -
|
|
// resource not built." and abandon the model -- so an unknown name MUST yield
|
|
// False (that diagnostic is the observable contract).
|
|
//
|
|
// BEST-EFFORT reconstruction (no distinct override body survived in BTL4OPT.EXE).
|
|
//
|
|
Logical
|
|
BTL4GaugeAlarmManager::CreateGaugeAlarmStreamItem(
|
|
MemoryStream *mem_stream,
|
|
const char *alarm_name,
|
|
const char *alarm_data
|
|
)
|
|
{
|
|
GaugeAlarmType type = gaugeAlarmUndefined;
|
|
|
|
//
|
|
// Map the type name -> enum. (Names best-effort; see note above.)
|
|
//
|
|
if (stricmp(alarm_name, "threshold") == 0) type = gaugeAlarmThreshold;
|
|
else if (stricmp(alarm_name, "range") == 0) type = gaugeAlarmRange;
|
|
else if (stricmp(alarm_name, "state") == 0) type = gaugeAlarmState;
|
|
else if (stricmp(alarm_name, "flash") == 0) type = gaugeAlarmFlash;
|
|
|
|
if (type == gaugeAlarmUndefined)
|
|
{
|
|
//
|
|
// Unknown alarm type: tell the caller so it emits the
|
|
// "undefined alarm type ... resource not built" warning.
|
|
//
|
|
return False;
|
|
}
|
|
|
|
//
|
|
// Serialise the type tag followed by the type-specific parameters parsed
|
|
// from the alarm_data text (thresholds, flash period, target colour, ...).
|
|
//
|
|
mem_stream->WriteBytes(&type, sizeof(type)); // (*mem_stream+0x20)(...,4)
|
|
// ... write parsed numeric parameters from alarm_data ...
|
|
|
|
return True;
|
|
}
|
|
|
|
|
|
//
|
|
// BTL4GaugeAlarmManager::ReadGaugeAlarmStreamItem
|
|
//
|
|
// Called by GaugeAlarmManager::BuildAlarmsForModel (@00448d00) once per baked
|
|
// alarm record, with a freshly-constructed GaugeAlarm (already carrying
|
|
// the_entity/the_subsystem/the_condition from the GaugeAlarm ctor @004489e8).
|
|
// Read the (type, parameters) back out of <mem_stream> and configure <alarm>
|
|
// so that, every frame, it samples the named subsystem attribute and raises /
|
|
// clears its flashing state when the recorded condition is met.
|
|
//
|
|
// BEST-EFFORT reconstruction (no distinct override body survived in BTL4OPT.EXE).
|
|
//
|
|
void
|
|
BTL4GaugeAlarmManager::ReadGaugeAlarmStreamItem(
|
|
GaugeAlarm *alarm,
|
|
Entity *the_entity,
|
|
Subsystem *the_subsystem,
|
|
Enumeration the_condition,
|
|
MemoryStream *mem_stream
|
|
)
|
|
{
|
|
GaugeAlarmType type;
|
|
mem_stream->ReadBytes(&type, sizeof(type)); // (*mem_stream+0x1c)(...,4)
|
|
|
|
//
|
|
// Bind the alarm to its data source and trip parameters. The base
|
|
// GaugeAlarm already stores entity/subsystem/condition; here we attach the
|
|
// type-specific trip (threshold / band / state / flash period) read from
|
|
// the stream so GaugeAlarm::Execute can flash the owning gauge.
|
|
//
|
|
switch (type)
|
|
{
|
|
case gaugeAlarmThreshold:
|
|
// alarm->SetThreshold( read Scalar ); alarm->SetCondition(the_condition);
|
|
break;
|
|
case gaugeAlarmRange:
|
|
// alarm->SetBand( read low, read high );
|
|
break;
|
|
case gaugeAlarmState:
|
|
// alarm->WatchState(the_subsystem);
|
|
break;
|
|
case gaugeAlarmFlash:
|
|
// alarm->SetFlashPeriod( read Scalar );
|
|
break;
|
|
default:
|
|
Verify(False, "Bad gauge alarm type",
|
|
"d:\\tesla\\bt\\bt_l4\\BTL4GALM.CPP", 0);
|
|
break;
|
|
}
|
|
|
|
(void)the_entity;
|
|
(void)the_subsystem;
|
|
(void)the_condition;
|
|
}
|