Files
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

451 lines
10 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "audlvl.h"
#include "objstrm.h"
#include "namelist.h"
#include "app.h"
#include "audrend.h"
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioLevelOfDetail ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
//#############################################################################
//
AudioLevelOfDetail::AudioLevelOfDetail(PlugStream *stream):
Plug(stream)
{
#if 0
amplitudeRadiationProfile = 0.0f;
filterRadiationProfile = 0.0f;
#endif
//
// Read fields
//
MemoryStream_Read(stream, &voiceCount);
MemoryStream_Read(stream, &audioRenderType);
//
// Read suspend seconds and calculate suspendDelay
//
Scalar
suspend_delay_seconds;
MemoryStream_Read(stream, &suspend_delay_seconds);
Check(application);
Check(application->GetAudioRenderer());
suspendDelay = suspend_delay_seconds *
application->GetAudioRenderer()->GetCalibrationRate() + 0.5f;
}
//
//#############################################################################
//#############################################################################
//
void
AudioLevelOfDetail::BuildFromPage(
PlugStream *stream,
NameList *name_list,
ClassID class_ID,
ObjectID object_ID
)
{
Plug::BuildFromPage(stream, name_list, class_ID, object_ID);
//
// Store fields
//
MEM_STRM_WRITE_ENTRY(*stream, name_list, AudioVoiceCount, voice_count);
MEM_STRM_WRITE_ENTRY(*stream, name_list, Enumeration, render_type);
//
// Store suspend delay
//
CString suspend_delay_string("suspend_delay");
Scalar suspend_delay_seconds = 0.33f; // HACK - should come from somewhere else
if (name_list->FindData(suspend_delay_string) != NULL)
{
Check_Pointer(name_list->FindData(suspend_delay_string));
Convert_From_Ascii(
(const char *)name_list->FindData(suspend_delay_string),
&suspend_delay_seconds
);
}
MemoryStream_Write(stream, &suspend_delay_seconds);
}
//
//#############################################################################
//#############################################################################
//
AudioLevelOfDetail::~AudioLevelOfDetail()
{
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioLevelOfDetail::TestInstance() const
{
Plug::TestInstance();
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioResource ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
//#############################################################################
//
AudioResource::AudioResource(PlugStream *stream):
Node(stream),
audioLevelOfDetailSocket(NULL, True)
{
//
// Read the table
//
CollectionSize i, number_of_entries;
MemoryStream_Read(stream, &number_of_entries);
for (i = 0; i < number_of_entries; i++)
{
Scalar distance;
AudioLevelOfDetail *audio_level_of_detail;
MemoryStream_Read(stream, &distance);
PlugStream_ReadObjectIDAndFindPlug(stream, &audio_level_of_detail);
Check(audio_level_of_detail);
audioLevelOfDetailSocket.AddValue(audio_level_of_detail, distance);
}
//
// Verify that there is at least one entry and the first one is at 0.0f
//
#if DEBUG_LEVEL>0
{
TableIteratorOf<AudioLevelOfDetail*, Scalar> iterator(&audioLevelOfDetailSocket);
Check(&iterator);
Verify(iterator.GetSize() > 0);
Verify(iterator.GetCurrent() != NULL);
Verify(iterator.GetValue() == 0.0f);
}
#endif
//
// Select the first entry
//
TableIteratorOf<AudioLevelOfDetail*, Scalar> iterator(&audioLevelOfDetailSocket);
audioLevelOfDetail = iterator.GetCurrent();
Check(audioLevelOfDetail);
}
//
//#############################################################################
//#############################################################################
//
void
AudioResource::BuildFromPage(
PlugStream *stream,
NameList *name_list,
ClassID class_ID,
ObjectID object_ID
)
{
Node::BuildFromPage(stream, name_list, class_ID, object_ID);
//
// Count number of entries
//
int number_of_entries = 0;
NameList::Entry *entry;
Check(name_list);
entry = name_list->GetFirstEntry();
while (entry != NULL)
{
Check(entry);
if (entry->IsName("audio_level_of_detail"))
number_of_entries++;
entry = entry->GetNextEntry();
}
//
// Store entries
//
MemoryStream_Write(stream, &number_of_entries);
Check(name_list);
entry = name_list->GetFirstEntry();
while (entry != NULL)
{
Check(entry);
if (entry->IsName("audio_level_of_detail"))
{
CString object_string;
Scalar distance;
ObjectID object_ID;
Check_Pointer(entry->GetChar());
object_string = entry->GetChar();
Check_Pointer(object_string.GetNthToken(0));
Convert_From_Ascii(object_string.GetNthToken(0), &distance);
MemoryStream_Write(stream, &distance);
Check(stream);
Check_Pointer(object_string.GetNthToken(1));
object_ID = stream->FindObjectID(object_string.GetNthToken(1));
Verify(object_ID != NullObjectID);
MemoryStream_Write(stream, &object_ID);
}
entry = entry->GetNextEntry();
}
}
//
//#############################################################################
//#############################################################################
//
AudioResource::~AudioResource()
{
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioResource::TestInstance() const
{
Node::TestInstance();
Check(&audioLevelOfDetailSocket);
return True;
}
//
//#############################################################################
//#############################################################################
//
void
AudioResource::SetDistance(Scalar distance)
{
Check(this);
Verify(distance >= 0.0f);
//
// Goto the last entry
//
TableIteratorOf<AudioLevelOfDetail*, Scalar>
iterator(&audioLevelOfDetailSocket);
Check(&iterator);
iterator.Last();
//
// While the distance is less than the distance of the entry
//
while (distance < iterator.GetValue())
{
iterator.Previous();
}
audioLevelOfDetail = iterator.GetCurrent();
Check(audioLevelOfDetail);
//
// Verify that the distance is greater than this entry, and less than
// the next
//
#if DEBUG_LEVEL>0
Verify(distance >= iterator.GetValue());
iterator.Next();
if (iterator.GetCurrent() != NULL)
{
Verify(distance < iterator.GetValue());
}
#endif
//
// Dump the LOD of the resource
//
#if 0
CollectionSize i = 0;
iterator.First();
while (iterator.GetCurrent() != NULL)
{
Check(iterator.GetCurrent());
if (audioLevelOfDetail == iterator.GetCurrent() /* && i > 0 */ )
{
Tell(
"AudioLOD:" << i <<
" d:" << distance <<
" t:" << iterator.GetValue() <<
"\n"
);
break;
}
iterator.Next();
i++;
}
#endif
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioResourceIndex ~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
//#############################################################################
//
AudioResourceIndex::AudioResourceIndex(PlugStream *stream):
Node(stream),
audioResourceSocket(NULL, True)
{
CollectionSize i, number_of_entries;
MemoryStream_Read(stream, &number_of_entries);
for (i = 0; i < number_of_entries; i++)
{
AudioResource *audio_resource;
PlugStream_ReadObjectIDAndFindPlug(stream, &audio_resource);
Check(audio_resource);
AddAudioResource(audio_resource, i);
}
}
//
//#############################################################################
//#############################################################################
//
void
AudioResourceIndex::BuildFromPage(
PlugStream *stream,
NameList *name_list,
ClassID class_ID,
ObjectID object_ID
)
{
Node::BuildFromPage(stream, name_list, class_ID, object_ID);
//
// Count number of entries
//
int number_of_entries = 0;
NameList::Entry *entry;
Check(name_list);
entry = name_list->GetFirstEntry();
while (entry != NULL)
{
Check(entry);
if (entry->IsName("audio_resource"))
number_of_entries++;
entry = entry->GetNextEntry();
}
//
// Store entries
//
MemoryStream_Write(stream, &number_of_entries);
Check(name_list);
entry = name_list->GetFirstEntry();
while (entry != NULL)
{
Check(entry);
if (entry->IsName("audio_resource"))
{
CString object_name;
ObjectID object_ID;
Check_Pointer(entry->GetChar());
object_name = entry->GetChar();
Check(stream);
object_ID = stream->FindObjectID(object_name);
Verify(object_ID != NullObjectID);
MemoryStream_Write(stream, &object_ID);
}
entry = entry->GetNextEntry();
}
}
//
//#############################################################################
//#############################################################################
//
AudioResourceIndex::~AudioResourceIndex()
{
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioResourceIndex::TestInstance() const
{
Node::TestInstance();
Check(&audioResourceSocket);
return True;
}
//
//#############################################################################
//#############################################################################
//
void
AudioResourceIndex::AddAudioResource(
AudioResource *audio_resource,
Enumeration index
)
{
Check(this);
audioResourceSocket.AddValue(audio_resource, index);
#if DEBUG_LEVEL>0
TableIteratorOf<AudioResource*, Enumeration> iterator(&audioResourceSocket);
Check(&iterator);
for (int i = 0; i < iterator.GetSize(); i++)
{
Verify(iterator.GetNth(i) == audioResourceSocket.Find(i));
}
#endif
}
//
//#############################################################################
//#############################################################################
//
AudioResource*
AudioResourceIndex::GetAudioResource(Enumeration index)
{
Check(this);
TableIteratorOf<AudioResource*, Enumeration> iterator(&audioResourceSocket);
Check(&iterator);
Verify(iterator.GetNth(index) == audioResourceSocket.Find(index));
return iterator.GetNth(index);
}
//
//#############################################################################
//#############################################################################
//
CollectionSize
AudioResourceIndex::GetSize()
{
Check(this);
TableIteratorOf<AudioResource*, Enumeration> iterator(&audioResourceSocket);
Check(&iterator);
return iterator.GetSize();
}