Files
BT411/engine/MUNGA/CAMMGR.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

749 lines
17 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "cammgr.h"
#include "mission.h"
#include "app.h"
#include "notation.h"
#include "namelist.h"
//##########################################################################
//##################### CameraInstanceManager #######################
//##########################################################################
//##########################################################################
// Construction and Destruction
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstanceManager::CameraInstanceManager() :
cameraList(NULL)
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Initialize local member data
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
cameraIterator = new SChainIteratorOf<CameraInstance*>(cameraList);
Register_Object(cameraIterator);
InitializeCameraInstances();
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstanceManager::~CameraInstanceManager()
{
DeleteCameraList();
Unregister_Object(cameraIterator);
delete cameraIterator;
cameraIterator = NULL;
Check_Fpu();
}
//##########################################################################
// CameraInstance Creation and Deletion Support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
CameraInstanceManager::FindUniqueCameraID()
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Finds a cameraID that has been freed up or the next sequential number
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
SChainIteratorOf<CameraInstance*> iterator(cameraList);
CameraInstance *camera_instance;
int camera_ID = 0;
Logical ID_exists = False;
while(1)
{
//
//~~~~~~~~~~~~~~~~~~~~
// Reset the iterator
//~~~~~~~~~~~~~~~~~~~~
//
iterator.First();
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Iterate through the list of all cameraInstances for every ID Chosen
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
while ( (camera_instance = iterator.ReadAndNext()) != NULL)
{
if(camera_ID == camera_instance->GetCameraID())
{
ID_exists = True;
break;
}
}
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// If this cameraID not already used return the ID
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
if(!ID_exists)
{
break;
}
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Otherwise increment the ID and reset the boolean
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
++camera_ID;
ID_exists = False;
}
Check_Fpu();
return camera_ID;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraInstanceManager::SetCamera(
const Origin &new_origin,
CameraInstance *camera_instance
)
{
if(!camera_instance)
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// No camera passed as an argument use cameraIterator
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
camera_instance = cameraIterator->GetCurrent();
if(!camera_instance)
{
CreateCameraInstance(new_origin);
}
else
{
camera_instance->SetLocalOrigin(new_origin);
}
}
else
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Camera passed in set its origin
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
camera_instance->SetLocalOrigin(new_origin);
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance*
CameraInstanceManager::CreateCameraInstance(const Origin &new_origin)
{
Check(this);
//
//~~~~~~~~~~~~~~~~~~~~~~~~
// Find a Unique CameraID
//~~~~~~~~~~~~~~~~~~~~~~~~
//
int camera_ID;
camera_ID = FindUniqueCameraID();
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Create the new cameraInstance
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance *camera_instance =
new CameraInstance(
camera_ID,
new_origin
);
Register_Object(camera_instance);
AddCameraInstance(camera_instance);
Check_Fpu();
return camera_instance;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance*
CameraInstanceManager::AddCameraInstance(CameraInstance *camera_instance)
{
Check(this);
Check(cameraIterator);
Check(&cameraList);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Insert the new Camera Instance into the cameraList
// & Point the cameraIterator to it
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
if (!cameraIterator->GetCurrent())
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// If nothing in the cameraList create the first element
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
cameraList.Add(camera_instance);
cameraIterator->First();
Tell("Created the First Camera Instance \n");
}
else
{
cameraList.Add(camera_instance);
cameraIterator->Next();
Tell("Created a Camera Instance \n");
}
Check_Fpu();
return camera_instance;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance*
CameraInstanceManager::DeleteCameraInstance()
{
CameraInstance *camera_instance = cameraIterator->GetCurrent();
if(!camera_instance)
{
return NULL;
}
cameraIterator->Remove();
if(!cameraIterator->GetCurrent())
{
cameraIterator->First();
}
Unregister_Object(camera_instance);
delete camera_instance;
return GetCurrent();
}
//##########################################################################
// CameraList Manipulator Functions
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraInstanceManager::DeleteCameraList()
{
while (cameraIterator->GetCurrent() != NULL)
{
DeleteCameraInstance();
}
}
//##########################################################################
// CameraList Iterator Manipulator Functions
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance*
CameraInstanceManager::IncrementIterator()
{
Check(this);
if (cameraIterator->GetCurrent())
{
Check(cameraIterator);
cameraIterator->ReadAndNext();
if (!cameraIterator->GetCurrent())
{
cameraIterator->First();
}
}
Check_Fpu();
return GetCurrent();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance*
CameraInstanceManager::DecrementIterator()
{
Check(this);
if (cameraIterator->GetCurrent())
{
Check(cameraIterator);
cameraIterator->ReadAndPrevious();
if (!cameraIterator->GetCurrent())
{
cameraIterator->Last();
}
}
Check_Fpu();
return GetCurrent();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance*
CameraInstanceManager::AdvanceIterator(const CameraInstance &camera_instance)
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Search for the camera_instance in the cameraList
// Iterator points to camera_instance upon return
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Check(&camera_instance);
Check(&cameraList);
Check(cameraIterator);
CameraInstance *current_instance;
cameraIterator->First();
while((current_instance = cameraIterator->GetCurrent()) != NULL)
{
if(current_instance == &camera_instance)
{
Check_Fpu();
return current_instance;
}
cameraIterator->Next();
}
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// camera_instance not found return NULL
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Check_Fpu();
return NULL;
}
//##########################################################################
// CameraList Searching and Query Functions
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance*
CameraInstanceManager::FindClosestCamera(const Point3D &point_3D)
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Use a Simple distance formula to find closest camera
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Check(this);
CameraInstance *closest_camera = NULL;
Scalar dist_to_goal, min_dist=1e8f;
SChainIteratorOf<CameraInstance*> iterator(cameraList);
CameraInstance *camera_instance;
while ( (camera_instance = iterator.ReadAndNext()) != NULL)
{
Check(camera_instance);
if (camera_instance->CanCameraSee(point_3D))
{
Vector3D v;
v.Subtract(point_3D, camera_instance->GetPosition());
dist_to_goal = v.LengthSquared();
Check_Fpu();
if (dist_to_goal < min_dist)
{
min_dist = dist_to_goal;
closest_camera = camera_instance;
}
}
}
Check_Fpu();
return closest_camera;
}
//##########################################################################
// Test Support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
CameraInstanceManager::TestInstance() const
{
return True;
}
//##########################################################################
// Tool Support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraInstanceManager::InitializeCameraInstances()
{
Check(application);
ResourceDescription::ResourceID map_ID;
map_ID = application->GetCurrentMission()->GetMapID();
ResourceFile *res_file;
res_file = application->GetResourceFile();
Check(res_file);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Create the camera filename from the map name
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription *res = res_file->FindResourceDescription(map_ID);
Check(res);
Str_Copy(
cameraFilename,
"cameras\\",
sizeof(cameraFilename)
);
//
// cam name matches map name
//
Str_Cat(
cameraFilename,
res->resourceName,
sizeof(cameraFilename)
);
//
// trunc cam name to DOS filename size
//
int name_length = strlen(cameraFilename);
if(name_length > 17)
{
cameraFilename[17] = '\0';
}
//
// Add the extension
//
Str_Cat(
cameraFilename,
".cam",
sizeof(cameraFilename)
);
//
//~~~~~~~~~~~~~~~~~~~~~~~~
// See if the file exists
//~~~~~~~~~~~~~~~~~~~~~~~~
//
std::fstream cam_file;
//cam_file.open(cameraFilename, std::ios::nocreate | std::ios::in | std::ios::out);
// following alternate line created by Ryan Bunker on 1/6/07
cam_file.open(cameraFilename, std::ios::in);
if(cam_file) //NOTE: this appears broken (RB 1/6/07)
{
cam_file.close();
ReadNotationFile();
}
else
{
cam_file.close();
CreateStreamedCameraInstances(res_file, res->resourceName);
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraInstanceManager::ReadNotationFile()
{
Check(this);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Create a Notation file out of the camera file
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
NotationFile *cam_file = new NotationFile(cameraFilename);
Register_Object(cam_file);
NameList* camera_namelist = cam_file->MakePageList();
Register_Object(camera_namelist);
NameList::Entry *camera_entry = camera_namelist->GetFirstEntry();
char
camera_page_name[128];
while (camera_entry)
{
Str_Copy(
camera_page_name,
camera_entry->GetName(),
sizeof(camera_page_name)
);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Create a new camera Instance for this page
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance *camera_instance;
const char *camera_data_entry;
int cam_type;
if(
!cam_file->GetEntry(
camera_page_name,
"cameraType",
&camera_data_entry
)
)
{
cam_type = CameraInstance::DefaultCameraType;
}
else
{
Check_Pointer(camera_data_entry);
cam_type = CameraInstance::FindCameraType(camera_data_entry);
}
switch (cam_type)
{
case CameraInstance::DefaultCameraType:
camera_instance = new CameraInstance(cam_file, camera_page_name);
break;
case CameraInstance::AlwaysSeesCameraType:
camera_instance =
new AlwaysSeesCameraInstance(cam_file, camera_page_name);
break;
}
Register_Object(camera_instance);
AddCameraInstance(camera_instance);
camera_entry = camera_entry->GetNextEntry();
}
Unregister_Object(camera_namelist);
delete camera_namelist;
Unregister_Object(cam_file);
delete cam_file;
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#define PAGE_NAME_SIZE 128
void
CameraInstanceManager::CreateStreamedCameraInstances(
ResourceFile *res_file,
const char *map_name
)
{
Check(this);
Check_Pointer(res_file);
Check_Pointer(map_name);
ResourceDescription *res =
res_file->FindResourceDescription(
map_name,
ResourceDescription::CameraStreamResourceType
);
if (!res)
{
Check_Fpu();
return;
}
res->Lock();
MemoryStream camera_stream(res->resourceAddress, res->resourceSize);
int camera_count = *(int*)camera_stream.GetPointer();
camera_stream.AdvancePointer(sizeof(camera_count));
//
//--------------------
// Read in each camera
//--------------------
//
while (camera_count--)
{
Check(&camera_stream);
CameraInstance::StreamedInstance *model =
(CameraInstance::StreamedInstance*)camera_stream.GetPointer();
Check_Pointer(model);
CameraInstance *camera_instance;
switch (model->cameraType)
{
case CameraInstance::DefaultCameraType:
camera_instance = new CameraInstance(model);
break;
case CameraInstance::AlwaysSeesCameraType:
camera_instance = new AlwaysSeesCameraInstance(model);
break;
}
Register_Object(camera_instance);
AddCameraInstance(camera_instance);
camera_stream.AdvancePointer(model->instanceSize);
}
res->Unlock();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraInstanceManager::WriteNotationFile()
{
Check(this);
Check(&cameraList);
NotationFile *camera_stream = new NotationFile();
Register_Object(camera_stream);
if(!camera_stream)
{
Fail("Error Opening Camera File\n");
}
SChainIteratorOf<CameraInstance*> iterator(cameraList);
CameraInstance *camera_instance;
iterator.First();
while ( (camera_instance = iterator.ReadAndNext()) != NULL)
{
camera_instance->WriteNotationPage(camera_stream);
}
camera_stream->WriteFile(cameraFilename);
Unregister_Object(camera_stream);
delete camera_stream;
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#define PAGE_NAME_SIZE 128
ResourceDescription::ResourceID
CameraInstanceManager::CreateCameraInstancesStream(
ResourceFile *resource_file,
const char *camera_name,
const ResourceDirectories *directories
)
{
Check_Pointer(camera_name);
Check_Pointer(directories);
//
//-------------------
// Open the .cam file
//-------------------
//
int length = strlen(camera_name) + strlen(directories->mapDirectory)+ 1;
char *filename = new char[length];
Register_Pointer(filename);
Str_Copy(filename, directories->mapDirectory, length);
strncat(filename, camera_name, strlen(camera_name)-3);
Str_Cat(filename, "cam", length);
NotationFile *camera_notation = new NotationFile(filename);
Register_Object(camera_notation);
if (camera_notation->PageCount() == 0)
{
DEBUG_STREAM << "Missing .cam file" << std::endl << std::flush;
Dump_And_Die:
Unregister_Object(camera_notation);
delete camera_notation;
Unregister_Pointer(filename);
delete[] filename;
return ResourceDescription::NullResourceID;
}
int camera_count = 0;
NameList *pagelist = camera_notation->MakePageList();
Register_Object(pagelist);
NameList::Entry *entry = pagelist->GetFirstEntry();
char page_name[PAGE_NAME_SIZE];
while (entry)
{
++camera_count;
entry = entry->GetNextEntry();
}
size_t array_size =
sizeof(CameraInstance::StreamedInstance) * camera_count * 2;
long *camera_array = new long[(array_size+3) >> 2];
Register_Pointer(camera_array);
MemoryStream camera_stream(camera_array, array_size);
*(int*)camera_stream.GetPointer() = camera_count;
camera_stream.AdvancePointer(sizeof(camera_count));
entry = pagelist->GetFirstEntry();
while (entry)
{
Str_Copy(
page_name,
entry->GetName(),
sizeof(page_name)
);
if(strcmp(page_name, "CameraInfo") == 0)
{
entry = entry->GetNextEntry();
continue;
}
Check(&camera_stream);
CameraInstance::StreamedInstance *model =
(CameraInstance::StreamedInstance*)camera_stream.GetPointer();
Check_Pointer(model);
if (
!CameraInstance::CreateStreamedInstance(
model,
camera_notation,
page_name
)
)
{
Unregister_Pointer(camera_array);
delete[] camera_array;
Unregister_Object(pagelist);
delete pagelist;
goto Dump_And_Die;
}
camera_stream.AdvancePointer(model->instanceSize);
entry = entry->GetNextEntry();
}
//
//-------------------------------
// Write the stream out to disk.
//
Str_Copy(filename, camera_name, length);
filename[strlen(camera_name)-4] = '\0';
ResourceDescription *new_res =
resource_file->AddResource(
filename,
ResourceDescription::CameraStreamResourceType,
1,
ResourceDescription::LoadOnDemandFlag,
camera_array,
camera_stream.GetBytesUsed()
);
Check(new_res);
//-----------
// Free Mem
//
Unregister_Pointer(camera_array);
delete[] camera_array;
Unregister_Pointer(filename);
delete[] filename;
Unregister_Object(camera_notation);
delete camera_notation;
Unregister_Object(pagelist);
delete pagelist;
return new_res->resourceID;
}