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>
407 lines
9.7 KiB
C++
407 lines
9.7 KiB
C++
#include "munga.h"
|
|
#pragma hdrstop
|
|
|
|
#include "gaugalrm.h"
|
|
#include "app.h"
|
|
#include "notation.h"
|
|
#include "namelist.h"
|
|
#include "entity.h"
|
|
#include "subsystm.h"
|
|
#include <iostream>
|
|
|
|
// #define LOCAL_TEST
|
|
|
|
#if defined(LOCAL_TEST)
|
|
# define Test_Tell(n) std::cout << n << std::flush
|
|
#else
|
|
# define Test_Tell(n)
|
|
#endif
|
|
|
|
//#######################################################################
|
|
// GaugeAlarm
|
|
//#######################################################################
|
|
GaugeAlarm::GaugeAlarm(
|
|
Entity *the_entity,
|
|
Subsystem *the_subsystem,
|
|
Enumeration the_condition
|
|
) :
|
|
Node(RegisteredClass::GaugeAlarmClassID),
|
|
lampList(NULL)
|
|
{
|
|
Check(the_entity);
|
|
Check(the_subsystem);
|
|
|
|
Test_Tell(
|
|
"GaugeAlarm(" << the_entity <<
|
|
", " << the_subsystem <<
|
|
", " << the_condition <<
|
|
")\n"
|
|
);
|
|
|
|
entity = the_entity;
|
|
subsystem = the_subsystem;
|
|
conditionIndex = the_condition;
|
|
Check_Fpu();
|
|
}
|
|
|
|
GaugeAlarm::~GaugeAlarm()
|
|
{
|
|
Test_Tell("~GaugeAlarm()");
|
|
Check(this);
|
|
|
|
ChainIteratorOf<Lamp*>
|
|
i(lampList);
|
|
Lamp
|
|
*lamp_pointer;
|
|
|
|
while ((lamp_pointer=i.ReadAndNext()) != NULL)
|
|
{
|
|
Check(lamp_pointer);
|
|
Test_Tell("lamp ");
|
|
lamp_pointer->SetAlertState(False);
|
|
}
|
|
Test_Tell("\n");
|
|
Check_Fpu();
|
|
}
|
|
|
|
Logical
|
|
GaugeAlarm::TestInstance() const
|
|
{
|
|
return Node::TestInstance();
|
|
}
|
|
|
|
|
|
//#######################################################################
|
|
// GaugeAlarmManager
|
|
//#######################################################################
|
|
Logical
|
|
GaugeAlarmManager::CreateGaugeAlarmStreamItem(
|
|
MemoryStream */*mem_stream*/,
|
|
const char */*alarm_name*/,
|
|
const char */*alarm_data*/
|
|
)
|
|
{
|
|
Fail("GaugeAlarmManager::CreateGaugeAlarmStreamItem not overridden!");
|
|
return False;
|
|
}
|
|
|
|
void
|
|
GaugeAlarmManager::ReadGaugeAlarmStreamItem(
|
|
GaugeAlarm */*alarm*/,
|
|
Entity */*the_entity*/,
|
|
Subsystem */*the_subsystem*/,
|
|
Enumeration /*the_condition*/,
|
|
MemoryStream */*mem_stream*/
|
|
)
|
|
{
|
|
Fail("GaugeAlarmManager::ReadGaugeAlarmStreamItem not overridden!");
|
|
}
|
|
|
|
ResourceDescription::ResourceID
|
|
GaugeAlarmManager::CreateModelGaugeAlarmStreamResource(
|
|
ResourceFile *resource_file,
|
|
const char *model_name,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories */*directories*/
|
|
)
|
|
{
|
|
Check(resource_file);
|
|
Check_Pointer(model_name);
|
|
Check(model_file);
|
|
|
|
int
|
|
i;
|
|
ResourceDescription::ResourceID
|
|
res_id = ResourceDescription::NullResourceID;
|
|
|
|
//-------------------------------------------------------------
|
|
// Make sure the notation file is in relaxed operation mode
|
|
//-------------------------------------------------------------
|
|
Enumeration
|
|
old_notation_file_mode = model_file->GetModes();
|
|
|
|
model_file->SetMode(
|
|
NotationFile::RelaxedOperationModes | NotationFile::SkipBlanksListMode
|
|
);
|
|
//-------------------------------------------------------------
|
|
// Build a list of all the alarm entries for the gauge renderer
|
|
//-------------------------------------------------------------
|
|
NameList
|
|
*alarm_list = model_file->MakeEntryList("gaugeAlarm");
|
|
|
|
if (alarm_list != NULL)
|
|
{
|
|
Test_Tell(
|
|
"Processing model gauge alarm data for '" << model_name << "':"
|
|
);
|
|
Register_Object(alarm_list);
|
|
//-------------------------------------------------------------
|
|
// Get the number of alarms
|
|
//-------------------------------------------------------------
|
|
Logical
|
|
built_ok = True;
|
|
int
|
|
alarm_count = alarm_list->EntryCount();
|
|
|
|
Test_Tell(alarm_count << " entries.\n");
|
|
Verify(alarm_count > 0);
|
|
//-------------------------------------------------------------
|
|
// Create a dynamic memory stream
|
|
//-------------------------------------------------------------
|
|
DynamicMemoryStream
|
|
*alarm_stream = new DynamicMemoryStream();
|
|
Register_Object(alarm_stream);
|
|
//-------------------------------------------------------------
|
|
// Write the number of alarms to the stream
|
|
//-------------------------------------------------------------
|
|
MemoryStream_Write(alarm_stream, &alarm_count);
|
|
//-------------------------------------------------------------
|
|
// Write the alarms to the stream
|
|
//-------------------------------------------------------------
|
|
NameList::Entry
|
|
*alarm_entry = alarm_list->GetFirstEntry();
|
|
|
|
for(i=0; i<alarm_count; ++i)
|
|
{
|
|
Verify(alarm_entry != NULL);
|
|
|
|
if (
|
|
CreateGaugeAlarmStreamItem(
|
|
alarm_stream,
|
|
alarm_entry->GetName(),
|
|
alarm_entry->GetChar()
|
|
)
|
|
== False
|
|
)
|
|
{
|
|
DEBUG_STREAM <<
|
|
"Model file '" << model_name <<
|
|
"' has undefined alarm type '" << alarm_entry->GetName() <<
|
|
"' - resource not built.\n";
|
|
built_ok = False;
|
|
break;
|
|
}
|
|
alarm_entry = alarm_entry->GetNextEntry();
|
|
}
|
|
//-------------------------------------------------------------
|
|
// Create the resource description, assign an ID
|
|
//-------------------------------------------------------------
|
|
if (built_ok)
|
|
{
|
|
Test_Tell("Creating resource description for '"<<model_name<<"'\n");
|
|
|
|
ResourceDescription
|
|
*res_desc_stream =
|
|
resource_file->AddResourceMemoryStream(
|
|
model_name,
|
|
ResourceDescription::GaugeAlarmStreamResourceType,
|
|
1, // priority
|
|
ResourceDescription::Preload,
|
|
alarm_stream
|
|
);
|
|
|
|
Check(res_desc_stream);
|
|
res_id = res_desc_stream->resourceID;
|
|
}
|
|
|
|
Unregister_Object(alarm_stream);
|
|
delete alarm_stream;
|
|
Test_Tell("Closed gauge alarm stream\n" << std::flush);
|
|
Test_Tell("res_id = " << res_id << "\n");
|
|
}
|
|
//-------------------------------------------------------------
|
|
// Restore the notation file's previous operation mode
|
|
//-------------------------------------------------------------
|
|
model_file->PutModes(old_notation_file_mode);
|
|
//-------------------------------------------------------------
|
|
// Return the new ResourceID
|
|
//-------------------------------------------------------------
|
|
Check_Fpu();
|
|
return res_id;
|
|
}
|
|
|
|
GaugeAlarmManager::GaugeAlarmManager() :
|
|
gaugeAlarmList(NULL)
|
|
{
|
|
Check_Fpu();
|
|
}
|
|
|
|
GaugeAlarmManager::~GaugeAlarmManager()
|
|
{
|
|
Check(this);
|
|
Check_Fpu();
|
|
}
|
|
|
|
Logical
|
|
GaugeAlarmManager::TestInstance() const
|
|
{
|
|
return True;
|
|
}
|
|
|
|
void
|
|
GaugeAlarmManager::RemoveAllAlarms()
|
|
{
|
|
Check(this);
|
|
|
|
//-------------------------------------------
|
|
// Delete all GaugeAlarms
|
|
//-------------------------------------------
|
|
ChainIteratorOf<GaugeAlarm*>
|
|
i(gaugeAlarmList);
|
|
GaugeAlarm
|
|
*alarm_pointer;
|
|
|
|
while ((alarm_pointer=i.ReadAndNext()) != NULL)
|
|
{
|
|
Check(alarm_pointer);
|
|
Unregister_Object(alarm_pointer);
|
|
delete alarm_pointer;
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
void
|
|
GaugeAlarmManager::Activate(
|
|
Entity *entity,
|
|
Subsystem *subsystem,
|
|
Enumeration condition,
|
|
ResourceDescription::ResourceID new_resource_id
|
|
)
|
|
{
|
|
//Test_Tell(
|
|
// "GaugeAlarmManager::Activate(" << entity <<
|
|
// ", " << subsystem <<
|
|
// ", " << condition <<
|
|
// ", " << new_resource_id <<
|
|
// ")\n";
|
|
//);
|
|
Check(this);
|
|
|
|
Check(application);
|
|
Check(application->GetResourceFile());
|
|
//-------------------------------------------
|
|
// Attempt to find the specified resource
|
|
//-------------------------------------------
|
|
ResourceDescription
|
|
*stream_resource_description =
|
|
application->GetResourceFile()->SearchList(
|
|
new_resource_id,
|
|
ResourceDescription::GaugeAlarmStreamResourceType
|
|
);
|
|
|
|
if (stream_resource_description == NULL)
|
|
{
|
|
Test_Tell(
|
|
"GaugeAlarmManager::Activate: resource ID '" << new_resource_id <<
|
|
"' does not exist\n"
|
|
);
|
|
}
|
|
else
|
|
{
|
|
//-------------------------------------------
|
|
// Lock the stream
|
|
//-------------------------------------------
|
|
stream_resource_description->Lock();
|
|
//-------------------------------------------
|
|
// Found it, open a DynamicMemoryStream
|
|
//-------------------------------------------
|
|
DynamicMemoryStream
|
|
*dynamic_memory_stream =
|
|
new DynamicMemoryStream(
|
|
stream_resource_description->resourceAddress,
|
|
stream_resource_description->resourceSize
|
|
);
|
|
if (dynamic_memory_stream == NULL)
|
|
{
|
|
Test_Tell("GaugeAlarmManager::Activate: couldn't open stream\n");
|
|
}
|
|
else
|
|
{
|
|
Register_Object(dynamic_memory_stream);
|
|
//-------------------------------------------
|
|
// Create the object, add to alarm list
|
|
//-------------------------------------------
|
|
GaugeAlarm
|
|
*alarm = new GaugeAlarm(
|
|
entity,
|
|
subsystem,
|
|
condition
|
|
);
|
|
Register_Object(alarm);
|
|
gaugeAlarmList.Add(alarm);
|
|
//-------------------------------------------
|
|
// Read the alarm items from the stream
|
|
//-------------------------------------------
|
|
int
|
|
alarm_count;
|
|
MemoryStream_Read(dynamic_memory_stream, &alarm_count);
|
|
Verify(alarm_count > 0);
|
|
|
|
for(; alarm_count>0; --alarm_count)
|
|
{
|
|
ReadGaugeAlarmStreamItem(
|
|
alarm,
|
|
entity,
|
|
subsystem,
|
|
condition,
|
|
dynamic_memory_stream
|
|
);
|
|
}
|
|
//-------------------------------------------
|
|
// Destroy the stream
|
|
//-------------------------------------------
|
|
Unregister_Object(dynamic_memory_stream);
|
|
delete dynamic_memory_stream;
|
|
}
|
|
//-------------------------------------------
|
|
// Unlock the stream
|
|
//-------------------------------------------
|
|
stream_resource_description->Unlock();
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
void
|
|
GaugeAlarmManager::Deactivate(
|
|
Entity *entity,
|
|
Subsystem *subsystem,
|
|
Enumeration condition
|
|
)
|
|
{
|
|
//Test_Tell(
|
|
// "GaugeAlarmManager::Deactivate(" << entity <<
|
|
// ", " << subsystem <<
|
|
// ", " << condition <<
|
|
// ")\n";
|
|
//);
|
|
Check(this);
|
|
//-------------------------------------------
|
|
// Find matching GaugeAlarm
|
|
//-------------------------------------------
|
|
ChainIteratorOf<GaugeAlarm*>
|
|
i(gaugeAlarmList);
|
|
GaugeAlarm
|
|
*alarm_pointer;
|
|
|
|
while ((alarm_pointer=i.ReadAndNext()) != NULL)
|
|
{
|
|
Check(alarm_pointer);
|
|
Test_Tell("pointer=" << alarm_pointer);
|
|
if (alarm_pointer->entity == entity)
|
|
{
|
|
if (alarm_pointer->subsystem == subsystem)
|
|
{
|
|
if (alarm_pointer->conditionIndex == condition)
|
|
{
|
|
Test_Tell("Found:");
|
|
Unregister_Object(alarm_pointer);
|
|
delete alarm_pointer;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Test_Tell("\n");
|
|
}
|
|
Check_Fpu();
|
|
}
|