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>
514 lines
13 KiB
C++
514 lines
13 KiB
C++
#include "munga.h"
|
|
#pragma hdrstop
|
|
|
|
#include "audrend.h"
|
|
#include "audent.h"
|
|
#include "jmover.h"
|
|
#include "app.h"
|
|
|
|
//#############################################################################
|
|
//########################### AudioRenderer #############################
|
|
//#############################################################################
|
|
|
|
//
|
|
//#############################################################################
|
|
// AudioRenderer
|
|
//#############################################################################
|
|
//
|
|
AudioRenderer::AudioRenderer(RendererRate render_rate):
|
|
Renderer(
|
|
render_rate,
|
|
MaxRendererComplexity,
|
|
DefaultRendererPriority,
|
|
AudioInterestType,
|
|
DefaultInterestDepth,
|
|
AudioRendererClassID
|
|
),
|
|
audioEventSocket(NULL, False)
|
|
{
|
|
audioHead = NULL;
|
|
|
|
#ifdef LAB_ONLY
|
|
sourceClippedCount = 0;
|
|
sourceStartCount = 0;
|
|
sourceSuspendCount = 0;
|
|
sourceResumeCount = 0;
|
|
doubleTriggerCount = 0;
|
|
resourceStealCount = 0;
|
|
resourceStealPriorityCount = 0;
|
|
resourceStealVolumeCount = 0;
|
|
stealShortDurationCount = 0;
|
|
#endif
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ~AudioRenderer
|
|
//#############################################################################
|
|
//
|
|
AudioRenderer::~AudioRenderer()
|
|
{
|
|
Unregister_Object(audioHead);
|
|
delete audioHead;
|
|
|
|
#ifdef LAB_ONLY
|
|
cout << "AudioRenderer::~AudioRenderer - sourceClippedCount="
|
|
<< sourceClippedCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - sourceStartCount="
|
|
<< sourceStartCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - sourceSuspendCount="
|
|
<< sourceSuspendCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - sourceResumeCount="
|
|
<< sourceResumeCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - doubleTriggerCount="
|
|
<< doubleTriggerCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - resourceStealCount="
|
|
<< resourceStealCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - resourceStealPriorityCount="
|
|
<< resourceStealPriorityCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - resourceStealVolumeCount="
|
|
<< resourceStealVolumeCount << "\n";
|
|
cout << "AudioRenderer::~AudioRenderer - stealShortDurationCount="
|
|
<< stealShortDurationCount << "\n";
|
|
#endif
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// Initialize
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioRenderer::Initialize()
|
|
{
|
|
audioHead = MakeAudioHead();
|
|
Register_Object(audioHead);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// TestInstance
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
AudioRenderer::TestInstance() const
|
|
{
|
|
Renderer::TestInstance();
|
|
if (audioHead != NULL)
|
|
{
|
|
Check(audioHead);
|
|
}
|
|
Check(&audioEventSocket);
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// LinkToEntity
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioRenderer::LinkToEntity(Entity *entity)
|
|
{
|
|
Check(this);
|
|
Check(entity);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Link the head to the entity
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Check(audioHead);
|
|
audioHead->LinkToEntity(entity);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Call inherited method
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Renderer::LinkToEntity(entity);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ExecuteImplementation
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioRenderer::ExecuteImplementation(
|
|
RendererComplexity,
|
|
RendererOrigin::InterestingEntityIterator*
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(audioHead);
|
|
audioHead->Execute();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// PostAudioRequestMessage
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioRenderer::PostAudioRequestMessage(
|
|
AudioSource *audio_source,
|
|
AudioSource::RequestMessage *message
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(audio_source);
|
|
Check(message);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// If audio source is clipped and the source is transient
|
|
// Then ignore
|
|
// Else set priority and volume
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
AudioSourcePriority
|
|
audio_source_priority;
|
|
AudioControlValue
|
|
audio_source_volume_scale;
|
|
|
|
if (audio_source->IsAudioSourceClipped(GetAudioHead()))
|
|
{
|
|
//
|
|
// If it is a transient source then ignore request
|
|
//
|
|
if (audio_source->GetAudioRenderType() == TransientAudioRenderType)
|
|
{
|
|
#ifdef LAB_ONLY
|
|
sourceClippedCount++;
|
|
#endif
|
|
return;
|
|
}
|
|
audio_source_priority = audio_source->GetAudioSourcePriority();
|
|
audio_source_volume_scale = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
audio_source_priority = audio_source->GetAudioSourcePriority();
|
|
audio_source_volume_scale = audio_source->CalculateSourceVolumeScale();
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// If this a transient source and volume is lower than threshold
|
|
// Then return
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (
|
|
message->controlID == StartAudioControlID &&
|
|
audio_source->GetAudioRenderType() == TransientAudioRenderType &&
|
|
audio_source_volume_scale < LowAudioVolumeThreshold
|
|
)
|
|
{
|
|
#ifdef LAB_ONLY
|
|
sourceClippedCount++;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Create audio weight based on priority and volume
|
|
// Add audio event
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
AudioWeighting
|
|
audio_weight(audio_source_priority, audio_source_volume_scale);
|
|
AudioEvent
|
|
*audio_event;
|
|
|
|
audio_event = new AudioEvent(audio_source, message);
|
|
Register_Object(audio_event);
|
|
audioEventSocket.AddValue(audio_event, audio_weight);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ProcessAudioRequestMessage
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
AudioRenderer::ProcessAudioRequestMessage()
|
|
{
|
|
//
|
|
// Process high priority, high volume event
|
|
//
|
|
AudioEventIterator
|
|
iterator(&audioEventSocket);
|
|
AudioEvent
|
|
*audio_event;
|
|
|
|
Logical stuff = False;
|
|
while ((audio_event = iterator.GetCurrent()) != NULL)
|
|
{
|
|
stuff = True;
|
|
Check(audio_event);
|
|
audio_event->Process();
|
|
//return True;
|
|
}
|
|
return stuff;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// FlushAudioMessages
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioRenderer::FlushAudioMessages(AudioSource *audio_source)
|
|
{
|
|
Check(this);
|
|
Check(audio_source);
|
|
|
|
//
|
|
// Process matching events
|
|
//
|
|
AudioEventIterator
|
|
iterator(&audioEventSocket);
|
|
AudioEvent
|
|
*audio_event;
|
|
|
|
while ((audio_event = iterator.ReadAndNext()) != NULL)
|
|
{
|
|
Check(audio_event);
|
|
if (audio_event->targetReceiver.GetCurrent() == audio_source)
|
|
{
|
|
if (audio_event->messageToSend->controlID == StopAudioControlID)
|
|
{
|
|
audio_event->Process();
|
|
}
|
|
else
|
|
{
|
|
Unregister_Object(audio_event);
|
|
delete audio_event;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeAudioHead
|
|
//#############################################################################
|
|
//
|
|
AudioHead*
|
|
AudioRenderer::MakeAudioHead()
|
|
{
|
|
return new AudioHead;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// StartEntityEffectImplementation
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioRenderer::StartEntityEffectImplementation(
|
|
Entity *parent_entity,
|
|
DamageZone *damage_zone,
|
|
ResourceDescription::ResourceID resource_ID
|
|
)
|
|
{
|
|
SET_AUDIO_RENDERER();
|
|
|
|
Check(this);
|
|
Check(parent_entity);
|
|
Check(damage_zone);
|
|
Verify(resource_ID != ResourceDescription::NullResourceID);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get the audio resource for this effect
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
ResourceDescription *audio_resource_description;
|
|
|
|
Check(application);
|
|
Check(application->GetResourceFile());
|
|
audio_resource_description =
|
|
application->GetResourceFile()->SearchList(
|
|
resource_ID,
|
|
ResourceDescription::AudioStreamListResourceType
|
|
);
|
|
if (audio_resource_description == NULL)
|
|
{
|
|
CLEAR_AUDIO_RENDERER();
|
|
return;
|
|
}
|
|
Check(audio_resource_description);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get the resource ID for the model resource for the audio effect entity
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
ResourceDescription *model_resource_description;
|
|
|
|
Check(application);
|
|
Check(application->GetResourceFile());
|
|
Check(audio_resource_description);
|
|
model_resource_description =
|
|
application->GetResourceFile()->SearchList(
|
|
audio_resource_description->resourceID,
|
|
ResourceDescription::ModelListResourceType
|
|
);
|
|
if (model_resource_description == NULL)
|
|
{
|
|
CLEAR_AUDIO_RENDERER();
|
|
return;
|
|
}
|
|
Check(model_resource_description);
|
|
model_resource_description->Lock();
|
|
audio_resource_description->Lock();
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get the entity segment
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Entity *linked_entity;
|
|
AudioRepresentation audio_representation;
|
|
EntitySegment *parent_entity_segment;
|
|
|
|
linked_entity = GetLinkedEntity();
|
|
Check(linked_entity);
|
|
audio_representation =
|
|
(AudioRepresentation)parent_entity->GetAudioRepresentation(
|
|
linked_entity
|
|
);
|
|
|
|
parent_entity_segment = damage_zone->GetCurrentEffectSite(
|
|
(audio_representation == InternalAudioRepresentation) ?
|
|
DamageZone::InternalAudioEffectSite :
|
|
DamageZone::ExternalAudioEffectSite
|
|
);
|
|
Check(parent_entity_segment);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Create the audio effect entity with this resource
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
JointedMover *jointed_mover;
|
|
|
|
Verify(parent_entity->IsDerivedFrom(JointedMover::GetClassDerivations()));
|
|
jointed_mover = Cast_Object(JointedMover*, parent_entity);
|
|
Check(jointed_mover);
|
|
|
|
AudioEntity::MakeMessage
|
|
make_message(
|
|
model_resource_description->resourceID,
|
|
jointed_mover,
|
|
parent_entity_segment
|
|
);
|
|
#if DEBUG_LEVEL>0
|
|
AudioEntity *audio_entity =
|
|
#endif
|
|
AudioEntity::Make(&make_message);
|
|
Register_Object(audio_entity);
|
|
|
|
model_resource_description->Unlock();
|
|
audio_resource_description->Unlock();
|
|
|
|
CLEAR_AUDIO_RENDERER();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~ AudioRenderer profile bits ~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER)
|
|
BitTrace Audio_Renderer("Audio Renderer");
|
|
#endif
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_CREATE_OBJECTS)
|
|
BitTrace Audio_Renderer_Create_Objects("Audio Renderer Create Objects");
|
|
#endif
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_DESTROY_OBJECTS)
|
|
BitTrace Audio_Renderer_Destroy_Objects("Audio Renderer Destroy Objects");
|
|
#endif
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_START_HANDLER)
|
|
BitTrace Audio_Renderer_Start_Handler("Audio Renderer Start Handler");
|
|
#endif
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_STOP_HANDLER)
|
|
BitTrace Audio_Renderer_Stop_Handler("Audio Renderer Stop Handler");
|
|
#endif
|
|
|
|
#if defined(TRACE_AUDIO_RENDERER_EXECUTE)
|
|
BitTrace Audio_Renderer_Execute("Audio Renderer Execute");
|
|
#endif
|
|
|
|
//#############################################################################
|
|
//############################## AudioEvent #############################
|
|
//#############################################################################
|
|
|
|
MemoryBlock *AudioEvent::GetAllocatedMemory()
|
|
{
|
|
static MemoryBlock allocatedMemory(sizeof(Event), AUDIOEVENT_MEMORYBLOCK_ALLOCATION, AUDIOEVENT_MEMORYBLOCK_ALLOCATION, "AudioEvents");
|
|
return &allocatedMemory;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
AudioEvent::AudioEvent(
|
|
AudioSource *target,
|
|
AudioSource::RequestMessage *message
|
|
):
|
|
targetReceiver(this)
|
|
{
|
|
Check(target);
|
|
Check(message);
|
|
|
|
//
|
|
// Store the message
|
|
//
|
|
size_t long_size = (message->messageLength+3)>>2;
|
|
messageToSend = (AudioSource::RequestMessage*)new long[long_size];
|
|
Check_Pointer(messageToSend);
|
|
Register_Pointer(messageToSend);
|
|
|
|
Mem_Copy(
|
|
messageToSend,
|
|
message,
|
|
message->messageLength,
|
|
long_size*sizeof(long)
|
|
);
|
|
|
|
//
|
|
// Store the target receiver
|
|
//
|
|
targetReceiver.Add(target);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
AudioEvent::~AudioEvent()
|
|
{
|
|
Unregister_Pointer(messageToSend);
|
|
delete messageToSend;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioEvent::ReleaseLinkHandler(
|
|
Socket*,
|
|
Plug*
|
|
)
|
|
{
|
|
Unregister_Object(this);
|
|
delete this;
|
|
}
|