Files
BT411/game/reconstructed/btl4galm.cpp
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

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;
}