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>
661 lines
15 KiB
C++
661 lines
15 KiB
C++
#include "munga.h"
|
|
#pragma hdrstop
|
|
|
|
#include "controls.h"
|
|
#include "app.h"
|
|
|
|
#if defined(DEBUG)
|
|
# define Test_Tell(n) DEBUG_STREAM << n
|
|
#else
|
|
# define Test_Tell(n)
|
|
#endif
|
|
|
|
//############################################################################
|
|
//####################### ControlsInstance #############################
|
|
//############################################################################
|
|
|
|
ControlsInstance::ControlsInstance(
|
|
ClassID class_ID,
|
|
ModeMask mode_mask,
|
|
Plug *dependant
|
|
):
|
|
Node(class_ID),
|
|
dependantPlug(this)
|
|
{
|
|
Check_Pointer(this);
|
|
modeMask = mode_mask;
|
|
dependantPlug.Add(dependant);
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
ControlsInstance::~ControlsInstance()
|
|
{
|
|
Check(this);
|
|
Check_Fpu();
|
|
}
|
|
|
|
void
|
|
ControlsInstance::Update(void *)
|
|
{
|
|
Fail("ControlsInstance::Update not overridden!");
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
ControlsInstance::ReleaseLinkHandler(
|
|
Socket*,
|
|
Plug*
|
|
)
|
|
{
|
|
Unregister_Object(this);
|
|
delete this;
|
|
Check_Fpu();
|
|
}
|
|
|
|
//############################################################################
|
|
//######################### ControlsMappingGroup #############################
|
|
//############################################################################
|
|
|
|
//
|
|
// The ControlsMappingGroup object maintains a group of ControlInstances.
|
|
//
|
|
// ControlsMappingGroup.Update() will call ControlsInstance.Update()
|
|
// for each instance in the group.
|
|
//
|
|
|
|
ControlsMappingGroup::ControlsMappingGroup():
|
|
Node(ControlsMappingGroup::ControlsMappingGroupClassID),
|
|
instanceList(this)
|
|
{
|
|
Check_Pointer(this);
|
|
Check_Fpu();
|
|
}
|
|
|
|
ControlsMappingGroup::~ControlsMappingGroup()
|
|
{
|
|
Check(this);
|
|
Remove(0); // remove all mappings
|
|
Check_Fpu();
|
|
}
|
|
|
|
void ControlsMappingGroup::Remove(ModeMask mode_mask)
|
|
{
|
|
Check(this);
|
|
|
|
ChainIteratorOf<ControlsInstance*>
|
|
i(instanceList);
|
|
|
|
ControlsInstance
|
|
*controls_instance;
|
|
|
|
while ((controls_instance=i.ReadAndNext()) != NULL)
|
|
{
|
|
Check(controls_instance);
|
|
//---------------------------------------------
|
|
// Remove mapping bit(s)
|
|
//---------------------------------------------
|
|
controls_instance->modeMask &= ~mode_mask;
|
|
//---------------------------------------------
|
|
// If no bits left, delete mapping
|
|
//---------------------------------------------
|
|
if (controls_instance->modeMask == (ModeMask)0)
|
|
{
|
|
Unregister_Object(controls_instance);
|
|
delete controls_instance;
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
void
|
|
ControlsMappingGroup::Update(void *data_source, ModeMask mode_mask)
|
|
{
|
|
Check(this);
|
|
Check_Pointer(data_source);
|
|
|
|
ChainIteratorOf<ControlsInstance*>
|
|
i(instanceList);
|
|
|
|
ControlsInstance
|
|
*controls_instance;
|
|
|
|
while ((controls_instance=i.ReadAndNext()) != NULL)
|
|
{
|
|
//------------------------------------------------
|
|
// Process only if at least one mode bit matches
|
|
//------------------------------------------------
|
|
Check(controls_instance);
|
|
if (controls_instance->modeMask & mode_mask)
|
|
{
|
|
controls_instance->Update(data_source);
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
Logical
|
|
ControlsMappingGroup::CurrentFilterStatus(ModeMask mode_mask)
|
|
{
|
|
Check(this);
|
|
|
|
ChainIteratorOf<ControlsInstance*>
|
|
i(instanceList);
|
|
|
|
ControlsInstance
|
|
*controls_instance;
|
|
|
|
while ((controls_instance=i.ReadAndNext()) != NULL)
|
|
{
|
|
Check(controls_instance);
|
|
|
|
if (controls_instance->modeMask & mode_mask)
|
|
{
|
|
Check_Fpu();
|
|
return True;
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
return False;
|
|
}
|
|
|
|
//############################################################################
|
|
//########################### ControlsManager ################################
|
|
//############################################################################
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Class derivation
|
|
//---------------------------------------------------------------------------
|
|
Derivation* ControlsManager::GetClassDerivations()
|
|
{
|
|
static Derivation classDerivations(Receiver::GetClassDerivations(), "ControlsManager");
|
|
return &classDerivations;
|
|
}
|
|
|
|
unsigned int
|
|
ControlsManager::nextOwnerID=0;
|
|
|
|
ControlsManager
|
|
*ControlsManager::headControlsManager = NULL;
|
|
|
|
//
|
|
//############################################################################
|
|
// ControlsManager
|
|
//
|
|
// Creator method
|
|
//----------------------------------------------------------------------------
|
|
// Enter: virtualDataPtr
|
|
//############################################################################
|
|
//
|
|
ControlsManager::ControlsManager(
|
|
ClassID class_ID,
|
|
SharedData &shared_data
|
|
):
|
|
Receiver(
|
|
class_ID,
|
|
shared_data
|
|
)
|
|
{
|
|
activeDestination = NULL;
|
|
activeReceiver = NULL;
|
|
activeMessageID = (Receiver::MessageID) 0;
|
|
activeDependant = NULL;
|
|
//----------------------------------------------------
|
|
// Add this object to the chain
|
|
//----------------------------------------------------
|
|
nextControlsManager = headControlsManager;
|
|
headControlsManager = this;
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//############################################################################
|
|
// ~ControlsManager
|
|
//
|
|
// Destructor method
|
|
//############################################################################
|
|
//
|
|
ControlsManager::~ControlsManager()
|
|
{
|
|
//----------------------------------------------------
|
|
// Unlink this object from the chain
|
|
//----------------------------------------------------
|
|
ControlsManager
|
|
*controls_manager,
|
|
*previous_controls_manager(NULL);
|
|
|
|
//
|
|
// Search for 'this' in the chain
|
|
//
|
|
for(
|
|
controls_manager = headControlsManager;
|
|
controls_manager != NULL;
|
|
controls_manager = controls_manager->nextControlsManager
|
|
)
|
|
{
|
|
if (controls_manager == this)
|
|
{
|
|
//
|
|
// Found! head of list?
|
|
//
|
|
if (previous_controls_manager == NULL)
|
|
{
|
|
headControlsManager = nextControlsManager;
|
|
}
|
|
//
|
|
// Not head, remove from chain
|
|
//
|
|
else
|
|
{
|
|
previous_controls_manager->nextControlsManager =
|
|
nextControlsManager;
|
|
}
|
|
break;
|
|
}
|
|
//
|
|
// Keep track of 'previous' object
|
|
//
|
|
previous_controls_manager = controls_manager;
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
void
|
|
ControlsManager::Remove(ModeMask /*mode_mask*/)
|
|
{
|
|
Fail("ControlsManager::Remove should not be called!\n");
|
|
}
|
|
|
|
void
|
|
ControlsManager::Execute()
|
|
{
|
|
Fail("ControlsManager::Execute should not be called!\n");
|
|
}
|
|
|
|
void
|
|
ControlsManager::StartMappableButtonsConfigure(
|
|
void *active_direct_target,
|
|
Receiver *target,
|
|
Plug *dependant,
|
|
Receiver::MessageID active_message_id,
|
|
ModeMask remove_mode_mask,
|
|
ModeMask add_mode_mask
|
|
)
|
|
{
|
|
Check(this);
|
|
|
|
activeReceiver = target;
|
|
activeMessageID = active_message_id;
|
|
activeDestination = active_direct_target;
|
|
activeDependant = dependant;
|
|
|
|
Check(application);
|
|
ModeManager
|
|
*mode_manager = application->GetModeManager();
|
|
Check(mode_manager);
|
|
|
|
mode_manager->RemoveModeMask(remove_mode_mask);
|
|
mode_manager->AddModeMask(add_mode_mask);
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
void
|
|
ControlsManager::StopMappableButtonsConfigure(
|
|
ModeMask remove_mode_mask,
|
|
ModeMask add_mode_mask
|
|
)
|
|
{
|
|
Check(this);
|
|
|
|
Check(application);
|
|
ModeManager
|
|
*mode_manager = application->GetModeManager();
|
|
Check(mode_manager);
|
|
|
|
mode_manager->RemoveModeMask(remove_mode_mask);
|
|
mode_manager->AddModeMask(add_mode_mask);
|
|
|
|
activeDestination = NULL;
|
|
activeReceiver = NULL;
|
|
activeMessageID = (Receiver::MessageID) 0;
|
|
activeDependant = NULL;
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
void
|
|
ControlsManager::AddOrEraseMappableButton(
|
|
int /*button_number*/,
|
|
ModeMask /*mode_mask*/
|
|
)
|
|
{
|
|
Check(this);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//############################################################################
|
|
// TestInstance
|
|
//
|
|
//############################################################################
|
|
//
|
|
Logical
|
|
ControlsManager::TestInstance() const
|
|
{
|
|
return IsDerivedFrom(*GetClassDerivations());
|
|
}
|
|
|
|
//
|
|
//############################################################################
|
|
// Shutdown
|
|
//
|
|
//############################################################################
|
|
//
|
|
void
|
|
ControlsManager::Shutdown()
|
|
{
|
|
ControlsManager
|
|
*controls_manager;
|
|
|
|
for(
|
|
controls_manager = headControlsManager;
|
|
controls_manager != NULL;
|
|
controls_manager = controls_manager->nextControlsManager
|
|
)
|
|
{
|
|
controls_manager->LocalShutdown();
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
void
|
|
ControlsManager::LocalShutdown()
|
|
{
|
|
Check(this);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//#########################################################################
|
|
//############################# ControlsString ############################
|
|
//#########################################################################
|
|
int
|
|
ControlsString::nextID = 1;
|
|
|
|
ControlsString::ControlsString(
|
|
const char *new_string,
|
|
int keypad_unit_number,
|
|
Receiver *receiver_pointer,
|
|
Receiver::MessageID message_id,
|
|
int user_code
|
|
):
|
|
Node(RegisteredClass::ControlsStringClassID)
|
|
{
|
|
Test_Tell(
|
|
"ControlsString creator('" new_string <<
|
|
"'," << keypad_unit_number <<
|
|
"," << receiver_pointer <<
|
|
"," << message_id <<
|
|
")\n"
|
|
);
|
|
Check_Pointer(this);
|
|
Check_Pointer(new_string);
|
|
Check(receiver_pointer);
|
|
|
|
//----------------------------------------------
|
|
// Assign unique ID
|
|
//----------------------------------------------
|
|
id = nextID++;
|
|
matchStringPointer = new_string;
|
|
keypadUnitNumber = keypad_unit_number;
|
|
//----------------------------------------------
|
|
// Save message data
|
|
//----------------------------------------------
|
|
receiverPointer = receiver_pointer;
|
|
messageID = message_id;
|
|
userCode = user_code;
|
|
//----------------------------------------------
|
|
// Clear the buffer
|
|
//----------------------------------------------
|
|
string[0] = '\0';
|
|
stringLength = 0;
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
ControlsString::~ControlsString()
|
|
{
|
|
Test_Tell("ControlsString destructor\n");
|
|
Check(this);
|
|
Check_Fpu();
|
|
}
|
|
|
|
Logical
|
|
ControlsString::TestInstance() const
|
|
{
|
|
return True;
|
|
}
|
|
|
|
void
|
|
ControlsString::Update(int keypad_unit_number, ControlsKey new_key)
|
|
{
|
|
Test_Tell(
|
|
"ControlsString::Update(" << keypad_unit_number <<
|
|
"," << new_key <<
|
|
"\n"
|
|
);
|
|
Check(this);
|
|
|
|
//----------------------------------------------
|
|
// Update only if proper keyboard unit
|
|
//----------------------------------------------
|
|
if (keypad_unit_number == keypadUnitNumber)
|
|
{
|
|
//--------------------------------------
|
|
// Get string length
|
|
//--------------------------------------
|
|
Check_Pointer(matchStringPointer);
|
|
int
|
|
length = strlen(matchStringPointer);
|
|
if (length > maxStringLength)
|
|
{
|
|
length = maxStringLength;
|
|
}
|
|
|
|
if (stringLength > length) // in case matchString changes
|
|
{
|
|
stringLength = length;
|
|
string[stringLength] = '\0';
|
|
}
|
|
//--------------------------------------
|
|
// Update string buffer
|
|
//--------------------------------------
|
|
if (stringLength < length)
|
|
{
|
|
string[stringLength++] = (char) new_key;
|
|
string[stringLength] = '\0';
|
|
}
|
|
else
|
|
{
|
|
if (length > 1)
|
|
{
|
|
memmove(&string[0],&string[1], length-1);
|
|
}
|
|
string[length-1] = (char) new_key;
|
|
}
|
|
Test_Tell("string=" << string << "\n");
|
|
//--------------------------------------
|
|
// Check for match
|
|
//--------------------------------------
|
|
if (strnicmp(string, matchStringPointer, length) == 0)
|
|
{
|
|
Test_Tell(
|
|
"ControlsString::Update, '" << matchStringPointer <<
|
|
"' matched!\n"
|
|
);
|
|
//--------------------------------------
|
|
// Match found, send message
|
|
//--------------------------------------
|
|
Check(receiverPointer);
|
|
Check(application);
|
|
|
|
ControlsStringMessage
|
|
message(messageID, this->userCode);
|
|
receiverPointer->Dispatch(&message);
|
|
//--------------------------------------
|
|
// Clear the string
|
|
//--------------------------------------
|
|
string[0] = '\0';
|
|
stringLength = 0;
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//#########################################################################
|
|
//######################## ControlsStringManager ##########################
|
|
//#########################################################################
|
|
ControlsStringManager::ControlsStringManager() :
|
|
Node(RegisteredClass::ControlsStringManagerClassID),
|
|
instanceList(this)
|
|
{
|
|
Test_Tell("ControlsStringManager creator\n");
|
|
Check_Pointer(this);
|
|
}
|
|
|
|
ControlsStringManager::~ControlsStringManager()
|
|
{
|
|
Test_Tell("ControlsStringManager destructor\n");
|
|
Check(this);
|
|
//----------------------------------------------
|
|
// Remove all ControlStrings
|
|
//----------------------------------------------
|
|
Remove(0);
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
Logical
|
|
ControlsStringManager::TestInstance() const
|
|
{
|
|
return True;
|
|
}
|
|
|
|
ControlsStringID
|
|
ControlsStringManager::Add(
|
|
const char *new_string,
|
|
int keypad_unit_number,
|
|
Receiver *receiver_pointer,
|
|
Receiver::MessageID message_id,
|
|
int user_code
|
|
)
|
|
{
|
|
Test_Tell("ControlsStringManager::Add(" << new_string <<
|
|
"," << keypad_unit_number <<
|
|
"," << receiver_pointer <<
|
|
"," << message_id <<
|
|
"\n"
|
|
);
|
|
Check(this);
|
|
Check_Pointer(new_string);
|
|
Check(receiver_pointer);
|
|
|
|
//----------------------------------------------
|
|
// Create the ControlsString
|
|
//----------------------------------------------
|
|
ControlsString
|
|
*string_item = new ControlsString(
|
|
new_string,
|
|
keypad_unit_number,
|
|
receiver_pointer,
|
|
message_id,
|
|
user_code
|
|
);
|
|
Check(string_item);
|
|
Register_Object(string_item);
|
|
//----------------------------------------------
|
|
// Add to the instance list
|
|
//----------------------------------------------
|
|
instanceList.Add(string_item);
|
|
//----------------------------------------------
|
|
// return the new string's (unique) ID
|
|
//----------------------------------------------
|
|
Check_Fpu();
|
|
return string_item->id;
|
|
}
|
|
|
|
void
|
|
ControlsStringManager::Remove(ControlsStringID string_id)
|
|
{
|
|
Test_Tell("ControlsStringManager::Remove(" << string_id << ")\n");
|
|
Check(this);
|
|
|
|
ChainIteratorOf<ControlsString*>
|
|
i(instanceList);
|
|
|
|
ControlsString
|
|
*controls_string;
|
|
|
|
//----------------------------------------------
|
|
// Search for the given ID (or universal match)
|
|
//----------------------------------------------
|
|
while ((controls_string=i.ReadAndNext()) != NULL)
|
|
{
|
|
Check(controls_string);
|
|
if (
|
|
(string_id == 0) ||
|
|
(controls_string->id == string_id)
|
|
)
|
|
{
|
|
Unregister_Object(controls_string);
|
|
delete controls_string;
|
|
//------------------------------------------
|
|
// If this is not a 'universal' remove,
|
|
// we're done (ID's are unique)
|
|
//------------------------------------------
|
|
if (string_id != 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
void
|
|
ControlsStringManager::Update(
|
|
int keypad_unit_number,
|
|
ControlsKey new_key
|
|
)
|
|
{
|
|
Test_Tell("ControlsStringManager::Update(" << keypad_unit_number <<
|
|
"," << new_key <<
|
|
")\n"
|
|
);
|
|
Check(this);
|
|
|
|
//--------------------------------------
|
|
// Update all matching string items
|
|
//--------------------------------------
|
|
ChainIteratorOf<ControlsString*>
|
|
i(instanceList);
|
|
|
|
ControlsString
|
|
*controls_string;
|
|
|
|
while ((controls_string=i.ReadAndNext()) != NULL)
|
|
{
|
|
Check(controls_string);
|
|
controls_string->Update(keypad_unit_number, new_key);
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
|
|
#if defined(TEST_CLASS)
|
|
#include "controls.tcp"
|
|
#endif
|