Two real bugs found + fixed chasing the footstep volume: 1. BTL4Application::MakeAudioRenderer read sample_rate from a raw 1995-layout offset (divisionParameters+0x10) -- the databinding trap: it yielded -1.6e14 (measured), poisoning Renderer::calibrationRate for the ENTIRE audio system. Every AudioTime conversion (sequence event scheduling, compression curve durations, Seconds_To_Frames) was garbage. Sanity-clamp to the authored 1000 frames/sec default (BT_AUDIO_LOG logs the value). 2. AudioHead::Execute fed audioFrameCount raw OS ticks; now converts ticks -> calibrated frames via SystemClock::GetTicksPerSecond (1000ms fallback when the static isn't measured yet), so AudioTime consumers see the rate they were calibrated for. With rate=1000 + ms ticks the units now align end-to-end. Footstep status: chain verified through pulse -> matcher -> Start -> renderer; the volume mixer's two inputs receive only value-0 AudioControlSequence events even with correct timing -- the authored event VALUES need decoding next (sequence tempo/divisions ctor dump; possibly Verify()-stripped tempo garbage or the events are resets and the true volume rides another control id). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
737 lines
27 KiB
C++
737 lines
27 KiB
C++
//===========================================================================//
|
|
// File: btl4app.cpp //
|
|
// Project: BattleTech Brick: BattleTech LBE Application //
|
|
// Contents: BTL4Application -- the top-level LBE-mode application object. //
|
|
// Builds the registry, renderers, mode/controls managers, the //
|
|
// mission, and the player's viewpoint entity; drives the run / //
|
|
// stop mission state. //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
|
|
// PROPRIETARY AND CONFIDENTIAL //
|
|
//===========================================================================//
|
|
//
|
|
// RECONSTRUCTED from the shipped binary (BTL4OPT.EXE). The class declaration
|
|
// is taken VERBATIM from the surviving header BTL4APP.HPP; the method bodies
|
|
// are recovered from the Ghidra pseudo-C for the 0x4d34xx-0x4d3axx cluster
|
|
// (see all/part_014.c) and cross-checked against Red Planet's direct analog
|
|
// RP_L4\RPL4APP.cpp and the MUNGA base MUNGA\APP.cpp.
|
|
//
|
|
// The 12 functions of this object file map 1:1 onto the 12 methods declared
|
|
// in BTL4APP.HPP, in source (compiler-emission) order:
|
|
//
|
|
// @004d34c4 BTL4Application::BTL4Application (constructor)
|
|
// @004d350c BTL4Application::~BTL4Application (scalar-deleting dtor)
|
|
// @004d3538 BTL4Application::MakeRegistry
|
|
// @004d3564 BTL4Application::MakeVideoRenderer
|
|
// @004d35b0 BTL4Application::MakeAudioRenderer
|
|
// @004d3614 BTL4Application::MakeModeManager
|
|
// @004d3644 BTL4Application::MakeControlsManager
|
|
// @004d368c BTL4Application::MakeGaugeRenderer
|
|
// @004d36c4 BTL4Application::MakeMission
|
|
// @004d36f4 BTL4Application::MakeViewpointEntity (anchor; 822 bytes)
|
|
// @004d3a2c BTL4Application::RunMissionMessageHandler
|
|
// @004d3a94 BTL4Application::StopMissionMessageHandler
|
|
//
|
|
// Helper / engine-internal name mapping used below (FUN_ -> intent):
|
|
// FUN_0047b410 L4Application::L4Application (base ctor)
|
|
// FUN_0047b624 L4Application::~L4Application (base dtor)
|
|
// FUN_0047b788 L4Application::RunMissionMessageHandler (base)
|
|
// FUN_0047b864 L4Application::StopMissionMessageHandler (base)
|
|
// FUN_00402298 operator new / Mem_Alloc(size)
|
|
// FUN_004022d0 operator delete / Mem_Free(p)
|
|
// FUN_004c1548 BTRegistry::BTRegistry (0x10 bytes)
|
|
// FUN_004ceecc BTL4VideoRenderer::BTL4VideoRenderer (0x3e0 bytes)
|
|
// FUN_004d2b6c BTL4AudioRenderer::BTL4AudioRenderer (0x1b0 bytes)
|
|
// FUN_0044c2d0 ModeManager init helper (BTL4ModeManager, 0xc bytes)
|
|
// FUN_00475c18 L4ControlsManager::L4ControlsManager (0xd10 bytes)
|
|
// FUN_004cbea0 BTL4GaugeRenderer::BTL4GaugeRenderer (0x1c53c bytes)
|
|
// FUN_004d2be0 BTL4Mission::BTL4Mission (0xfc bytes)
|
|
// FUN_004dee74 getenv()
|
|
// FUN_0044e19c GetSampleRate() (reads division-params float @+0x10)
|
|
// FUN_0042d16c CameraShip::Make
|
|
// FUN_004a2d48 Mech::Make
|
|
// FUN_00476eac ControlsManager::SetJoystickDeadBand
|
|
// FUN_00476eec ControlsManager::SetThrottleDeadBand
|
|
// FUN_00476f10 ControlsManager::SetPedalsDeadBand
|
|
// FUN_0047ac5c CameraThrustmasterMapper::CameraThrustmasterMapper
|
|
// FUN_0047ad3c CameraRIOMapper::CameraRIOMapper
|
|
// FUN_0047aa78 CameraL4Mapper::CameraL4Mapper
|
|
// FUN_0042c630 CameraShip::SetMappingSubsystem
|
|
// FUN_004d21d0 MechThrustmasterMapper::MechThrustmasterMapper
|
|
// FUN_004d266c MechRIOMapper::MechRIOMapper
|
|
// FUN_00402460 (controls-mapper resource ctor / Str_Copy "ControlsMapper")
|
|
// FUN_0049fe40 Mech::SetMappingSubsystem
|
|
// FUN_00407064 ResourceFile::SearchList(id, type)
|
|
// FUN_004070dc ResourceFile::SearchList(id, name)
|
|
// FUN_00406cd0 ResourceDescription::Lock
|
|
// FUN_0047703c ControlsManager::CreateStreamedMappings
|
|
// FUN_0046ff64 GaugeRenderer::ConfigureForModel(name, entity)
|
|
// FUN_004dbb24 DEBUG_STREAM << (msg) FUN_004d9c38 << flush
|
|
// (the two together == the Tell()/Msg() macro)
|
|
// FUN_0040385c Fail(msg, file, line)
|
|
// DAT_004efc94 the global application pointer
|
|
// DAT_00524e20 the debug message stream
|
|
//
|
|
// L4Application base member offsets observed in the decomp:
|
|
// this+0x3c controls manager (GetControlsManager)
|
|
// this+0x4c gauge renderer (GetGaugeRenderer)
|
|
// this+0x50 mode manager (GetModeManager)
|
|
// this+0x68 resource file (GetResourceFile)
|
|
// this+0xd0 division-params present flag (cf. RP "divisionParameters")
|
|
// controlsManager+0xc98 primaryControlType (2==ThrustMaster, 4==RIO)
|
|
// modeManager+0x4 current mode mask modeManager+0x8 saved mode mask
|
|
//
|
|
|
|
#include <bt.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(BTL4APP_HPP)
|
|
# include <btl4app.hpp>
|
|
#endif
|
|
#if !defined(BTL4VID_HPP)
|
|
# include <btl4vid.hpp>
|
|
#endif
|
|
#if !defined(BTL4ARND_HPP)
|
|
# include <btl4arnd.hpp>
|
|
#endif
|
|
#if !defined(BTL4GRND_HPP)
|
|
# include <btl4grnd.hpp>
|
|
#endif
|
|
#if !defined(BTL4MSSN_HPP)
|
|
# include <btl4mssn.hpp>
|
|
#endif
|
|
#if !defined(BTL4MODE_HPP)
|
|
# include <btl4mode.hpp>
|
|
#endif
|
|
#if !defined(BTL4MPPR_HPP)
|
|
# include <btl4mppr.hpp>
|
|
#endif
|
|
#if !defined(BTREG_HPP)
|
|
# include <btreg.hpp>
|
|
#endif
|
|
#if !defined(L4CTRL_HPP)
|
|
# include <l4ctrl.hpp> // LBE4ControlsManager
|
|
#endif
|
|
#if !defined(CAMSHIP_HPP)
|
|
# include <camship.hpp> // CameraShip
|
|
#endif
|
|
#if !defined(CAMMPPR_HPP)
|
|
# include <cammppr.hpp> // CameraControlsMapper
|
|
#endif
|
|
#if !defined(L4MPPR_HPP)
|
|
# include <l4mppr.hpp> // Camera{Thrustmaster,RIO,L4}Mapper
|
|
#endif
|
|
|
|
//
|
|
// Audio sound-card environment variables (both must be present for the
|
|
// audio renderer to be built). Observed at @004d35b0 as the live form of
|
|
// the front/rear-card check that RPL4Application left commented out.
|
|
//
|
|
#define AWE_FRONT_ENV_VAR "AWE_FRONT" // s_AWE_FRONT_0051ec1e
|
|
#define AWE_REAR_ENV_VAR "AWE_REAR" // s_AWE_REAR_0051ec28
|
|
|
|
//#############################################################################
|
|
// Message Support
|
|
//
|
|
const Receiver::HandlerEntry
|
|
BTL4Application::MessageHandlerEntries[]=
|
|
{
|
|
MESSAGE_ENTRY(BTL4Application, RunMission),
|
|
MESSAGE_ENTRY(BTL4Application, StopMission)
|
|
};
|
|
|
|
BTL4Application::MessageHandlerSet
|
|
BTL4Application::MessageHandlers(
|
|
ELEMENTS(BTL4Application::MessageHandlerEntries),
|
|
BTL4Application::MessageHandlerEntries,
|
|
L4Application::GetMessageHandlers() // WinTesla: GetMessageHandlers() replaces the static MessageHandlers
|
|
);
|
|
|
|
//#############################################################################
|
|
// Shared Data Support
|
|
//
|
|
Derivation
|
|
BTL4Application::ClassDerivations(
|
|
L4Application::GetClassDerivations(), // WinTesla: GetClassDerivations() (returns Derivation*)
|
|
"BTL4Application"
|
|
);
|
|
|
|
BTL4Application::SharedData
|
|
BTL4Application::DefaultData(
|
|
&BTL4Application::ClassDerivations, // SharedData(Derivation*, MessageHandlerSet&)
|
|
BTL4Application::MessageHandlers
|
|
);
|
|
|
|
|
|
//
|
|
//#############################################################################
|
|
// BTL4Application @004d34c4
|
|
//#############################################################################
|
|
//
|
|
BTL4Application::BTL4Application(
|
|
HINSTANCE hInstance,
|
|
HWND hWnd,
|
|
ResourceFile *resource_file,
|
|
ClassID class_ID,
|
|
SharedData &shared_data
|
|
):
|
|
//
|
|
// Base call FUN_0047b410(this, resource_file, 1, class_ID, shared_data).
|
|
// The literal "1" is the L4 game identifier (BTL4) -- the analog of
|
|
// RPL4Application passing the RPL4 enum to L4Application. The WinTesla
|
|
// L4Application ctor additionally takes (hInstance, hWnd) up front
|
|
// (cf. RPL4Application::RPL4Application).
|
|
//
|
|
L4Application(hInstance, hWnd, resource_file, BTL4, class_ID, shared_data)
|
|
{
|
|
// vtable installed by the compiler == &PTR_FUN_0051ed9c
|
|
Tell("BTL4Application::BTL4Application\n"); // FUN_004dbb24/004d9c38
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ~BTL4Application @004d350c
|
|
//#############################################################################
|
|
//
|
|
BTL4Application::~BTL4Application()
|
|
{
|
|
// FUN_004d350c is the compiler-generated scalar-deleting destructor;
|
|
// the body is empty -- it merely reinstalls the vtable and chains to
|
|
// ~L4Application (FUN_0047b624).
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeRegistry @004d3538
|
|
//#############################################################################
|
|
//
|
|
Registry*
|
|
BTL4Application::MakeRegistry()
|
|
{
|
|
Check(this);
|
|
return new BTRegistry(GetResourceFile()); // FUN_00402298(0x10) -> FUN_004c1548
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeVideoRenderer @004d3564
|
|
//#############################################################################
|
|
//
|
|
VideoRenderer*
|
|
BTL4Application::MakeVideoRenderer()
|
|
{
|
|
//
|
|
// Only build the renderer when the division parameters are present
|
|
// (this+0xd0 != 0), exactly as RPL4Application gates on
|
|
// "divisionParameters".
|
|
//
|
|
// The DOS-era "division parameters present" flag (this+0xd0) is the
|
|
// WinTesla public char* L4Application::divisionParameters. The
|
|
// reconstructed BTL4VideoRenderer ctor (btl4vid.hpp) takes the standard
|
|
// Renderer calibration tuple (rate, complexity, priority) followed by
|
|
// the interest type/depth; it derives the window + screen geometry from
|
|
// the L4Application statics internally.
|
|
//
|
|
if (divisionParameters)
|
|
{
|
|
return
|
|
new BTL4VideoRenderer(
|
|
DefaultRendererRate, // calibration rate
|
|
MaxRendererComplexity, // calibration complexity
|
|
DefaultRendererPriority, // calibration priority
|
|
VisualInterestType, // DAT_004e8238
|
|
DefaultInterestDepth // DAT_004e823c
|
|
);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeAudioRenderer @004d35b0
|
|
//#############################################################################
|
|
//
|
|
AudioRenderer*
|
|
BTL4Application::MakeAudioRenderer()
|
|
{
|
|
//
|
|
// Both AWE sound-card environment variables must be present. This is
|
|
// the live form of the front/rear-card test RPL4Application left
|
|
// commented out (blaster1/blaster2).
|
|
//
|
|
char *front_card = getenv(AWE_FRONT_ENV_VAR); // FUN_004dee74
|
|
char *rear_card = getenv(AWE_REAR_ENV_VAR);
|
|
|
|
// AUTHENTIC 1995 gate: the pod carried TWO AWE32 cards (front/rear) and
|
|
// audio was OFF unless both AWE_FRONT/AWE_REAR were set. Those cards don't
|
|
// exist on modern hardware, so this check silenced audio on every machine
|
|
// (root cause of "no sound" -- the renderer was never created).
|
|
// MODERNIZED (task #50, 2026-07-15): default audio ON through the WinTesla
|
|
// OpenAL renderer (the OS default output device). BT_NO_AUDIO=1 restores
|
|
// the silent/no-renderer behavior; the AWE vars still force it on too.
|
|
(void)front_card; (void)rear_card;
|
|
if (getenv("BT_NO_AUDIO"))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// FUN_0044e19c read the sample rate as a float out of the division
|
|
// parameter block (@+0x10). In WinTesla that block is the public
|
|
// L4Application::divisionParameters char*; fall back to 1000.0f (the
|
|
// RP default) when it is absent. Second arg is the mission-review
|
|
// flag (the 1995 binary hard-coded it off; DAT_004fd550 == 0).
|
|
//
|
|
RendererRate sample_rate =
|
|
divisionParameters
|
|
? (RendererRate)*reinterpret_cast<float*>(divisionParameters + 0x10)
|
|
: (RendererRate)1000.0f;
|
|
// DATABINDING GUARD (task #50): divisionParameters is a raw engine block
|
|
// pointer and +0x10 is a 1995-layout offset; on the port it read GARBAGE
|
|
// (-1.6e14 measured), poisoning calibrationRate -> every AudioTime
|
|
// conversion (sequence event scheduling, compression durations) broke,
|
|
// which is why AudioControlSequence timeline events (footstep volumes!)
|
|
// never landed. Sanity-clamp to the authored 1000 frames/sec default.
|
|
if (!(sample_rate > 0.0f && sample_rate <= 100000.0f))
|
|
sample_rate = (RendererRate)1000.0f;
|
|
if (getenv("BT_AUDIO_LOG"))
|
|
DEBUG_STREAM << "[audio] renderer sample_rate=" << sample_rate
|
|
<< " (divisionParameters=" << (void*)divisionParameters << ")" << "\n" << std::flush;
|
|
|
|
return
|
|
new BTL4AudioRenderer(
|
|
sample_rate,
|
|
False
|
|
);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeModeManager @004d3614
|
|
//#############################################################################
|
|
//
|
|
ModeManager*
|
|
BTL4Application::MakeModeManager()
|
|
{
|
|
// FUN_00402298(0xc) -> FUN_0044c2d0(p, 0x50421)
|
|
BTL4ModeManager
|
|
*manager = new BTL4ModeManager(BTL4ModeManager::ModeInitial);
|
|
Check(manager);
|
|
return manager;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeControlsManager @004d3644
|
|
//#############################################################################
|
|
//
|
|
ControlsManager*
|
|
BTL4Application::MakeControlsManager()
|
|
{
|
|
// FUN_00402298(0xd10) -> FUN_00475c18(p)
|
|
// WinTesla renamed the DOS L4ControlsManager to LBE4ControlsManager.
|
|
LBE4ControlsManager
|
|
*manager = new LBE4ControlsManager;
|
|
Check(manager);
|
|
|
|
//
|
|
// Register this application as the controls owner / key-command
|
|
// handler. The DOS decomp showed a virtual call
|
|
// (manager+0x68)->vtbl[+0x14](-1, this, 7, this) -- "mode mask = all,
|
|
// receiver = this, message = KeyCommand (7), handler = this". In
|
|
// WinTesla that is the keyboard-group Add() the base
|
|
// L4Application::MakeControlsManager performs.
|
|
//
|
|
manager->keyboardGroup[LBE4ControlsManager::KeyboardPC].Add(
|
|
ModeManager::ModeAlwaysActive,
|
|
this,
|
|
KeyCommandMessageID,
|
|
this
|
|
);
|
|
|
|
return manager;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeGaugeRenderer @004d368c
|
|
//#############################################################################
|
|
//
|
|
GaugeRenderer*
|
|
BTL4Application::MakeGaugeRenderer(int *secondaryIndex, int *aux1Index, int *aux2Index)
|
|
{
|
|
//
|
|
// Only build the gauge renderer when the L4GAUGE environment variable
|
|
// is set -- matches RPL4Application's getenv("L4GAUGE") gate.
|
|
//
|
|
// SIGNATURE NOTE: the shipped BT override (@004d368c) took no arguments,
|
|
// but the 2007 WinTesla engine WIDENED the base virtual to
|
|
// Application::MakeGaugeRenderer(int* secondaryIndex, int* aux1, int* aux2)
|
|
// (the secondary/aux surface indices for windowed mode) and calls THAT
|
|
// 3-arg virtual from Application::Initialize (APP.cpp:382). A no-arg
|
|
// method here only HIDES the virtual -- it never overrides it -- so the
|
|
// engine's L4Application::MakeGaugeRenderer ran instead and built a BASE
|
|
// L4GaugeRenderer (which never parses gauge\l4gauge.cfg -> empty symbol
|
|
// table -> "undefined label 'bhk1Init'" -> no ports). Matching the 3-arg
|
|
// signature makes this the real override. BT is fullscreen with no
|
|
// secondary/aux gauge indices, so the args are ignored (the
|
|
// BTL4GaugeRenderer ctor hardcodes windowed=false / NULL indices).
|
|
//
|
|
(void)secondaryIndex; (void)aux1Index; (void)aux2Index;
|
|
|
|
if (getenv("L4GAUGE") == NULL) // FUN_004dee74
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return new BTL4GaugeRenderer; // FUN_00402298(0x1c53c) -> FUN_004cbea0
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeMission @004d36c4
|
|
//#############################################################################
|
|
//
|
|
Mission*
|
|
BTL4Application::MakeMission(
|
|
NotationFile *notation_file,
|
|
ResourceFile *resources
|
|
)
|
|
{
|
|
// FUN_00402298(0xfc) -> FUN_004d2be0(p, notation_file, resources)
|
|
return new BTL4Mission(notation_file, resources);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeViewpointEntity @004d36f4
|
|
//#############################################################################
|
|
//
|
|
// Anchor function (822 bytes). Creates the viewpoint entity for the local
|
|
// player and wires up its controls mapper + gauges. Structurally identical
|
|
// to RPL4Application::MakeViewpointEntity, except the second case creates a
|
|
// Mech (class 0xbb9) instead of a VTV.
|
|
//
|
|
Entity*
|
|
BTL4Application::MakeViewpointEntity(Entity::MakeMessage *message)
|
|
{
|
|
Check(this);
|
|
|
|
// WinTesla: GetControlsManager() yields the LBE4ControlsManager
|
|
// (the renamed DOS L4ControlsManager); the deadband / primary-control
|
|
// accessors live on it, not on the ControlsManager base.
|
|
LBE4ControlsManager
|
|
*controls = (LBE4ControlsManager*)GetControlsManager(); // *(this+0x3c)
|
|
|
|
Entity
|
|
*viewing_entity = NULL;
|
|
char
|
|
*primary_mapping_name = NULL;
|
|
|
|
switch (message->classToCreate) // *(message+0x1c)
|
|
{
|
|
case CameraShipClassID: // 0x45
|
|
//===============================================================
|
|
// Create Camera ship
|
|
//===============================================================
|
|
{
|
|
CameraShip
|
|
*viewing_camera =
|
|
CameraShip::Make((CameraShip::MakeMessage*)message);
|
|
Check(viewing_camera);
|
|
|
|
viewing_entity = viewing_camera;
|
|
|
|
//------------------------------------------------------------
|
|
// Create the controls-mapping subsystem resource
|
|
//------------------------------------------------------------
|
|
CameraControlsMapper::SubsystemResource
|
|
control_subsystem_resource;
|
|
|
|
Str_Copy(
|
|
control_subsystem_resource.subsystemName,
|
|
"ControlsMapper",
|
|
sizeof(control_subsystem_resource.subsystemName)
|
|
);
|
|
control_subsystem_resource.classID = TrivialSubsystemClassID; // 0xf
|
|
control_subsystem_resource.subsystemModelSize = // 0x30
|
|
sizeof(control_subsystem_resource);
|
|
|
|
CameraControlsMapper
|
|
*camera_mapper;
|
|
|
|
switch (controls->primaryControlType) // *(controls+0xc98)
|
|
{
|
|
case LBE4ControlsManager::PrimaryThrustMaster: // 2
|
|
camera_mapper = new CameraThrustmasterMapper( // FUN_0047ac5c
|
|
viewing_camera,
|
|
CameraShip::ControlsMapperSubsystem,
|
|
&control_subsystem_resource
|
|
);
|
|
primary_mapping_name = "Thrustmaster";
|
|
break;
|
|
|
|
case LBE4ControlsManager::PrimaryRIO: // 4
|
|
camera_mapper = new CameraRIOMapper( // FUN_0047ad3c
|
|
viewing_camera,
|
|
CameraShip::ControlsMapperSubsystem,
|
|
&control_subsystem_resource
|
|
);
|
|
primary_mapping_name = "L4";
|
|
break;
|
|
|
|
default:
|
|
camera_mapper = new CameraL4Mapper( // FUN_0047aa78
|
|
viewing_camera,
|
|
CameraShip::ControlsMapperSubsystem,
|
|
&control_subsystem_resource
|
|
);
|
|
// primary mapping name is NULL
|
|
break;
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// Attach the mapping subsystem to the entity
|
|
//------------------------------------------------------------
|
|
viewing_camera->SetMappingSubsystem(camera_mapper); // FUN_0042c630
|
|
}
|
|
break;
|
|
|
|
case MechClassID: // 0xbb9
|
|
//===============================================================
|
|
// Create Mech (the BattleTech analog of RP's VTV case)
|
|
//===============================================================
|
|
{
|
|
Mech
|
|
*viewing_mech = Mech::Make((Mech::MakeMessage*)message); // FUN_004a2d48
|
|
Check(viewing_mech);
|
|
|
|
viewing_entity = viewing_mech;
|
|
|
|
//------------------------------------------------------------
|
|
// Control deadbanding
|
|
//------------------------------------------------------------
|
|
controls->SetJoystickDeadBand(0.05f); // FUN_00476eac
|
|
controls->SetThrottleDeadBand(0.05f); // FUN_00476eec
|
|
controls->SetPedalsDeadBand(0.05f); // FUN_00476f10
|
|
|
|
MechControlsMapper
|
|
*mech_mapper;
|
|
|
|
switch (controls->primaryControlType) // *(controls+0xc98)
|
|
{
|
|
case LBE4ControlsManager::PrimaryThrustMaster: // 2
|
|
{
|
|
//
|
|
// The DOS binary heap-allocated the subsystem resource
|
|
// and ref-counted it (FUN_00402460 + puVar2[3]++). The
|
|
// WinTesla Subsystem__SubsystemResource is a plain struct
|
|
// that the Subsystem ctor copies out of, so we build it
|
|
// on the stack and name it via Str_Copy -- the exact
|
|
// idiom RP uses for its VTV/Camera mapper resources.
|
|
//
|
|
MechControlsMapper::SubsystemResource
|
|
control_subsystem_resource;
|
|
|
|
Str_Copy(
|
|
control_subsystem_resource.subsystemName,
|
|
"ControlsMapper",
|
|
sizeof(control_subsystem_resource.subsystemName)
|
|
);
|
|
control_subsystem_resource.classID = TrivialSubsystemClassID;
|
|
control_subsystem_resource.subsystemModelSize =
|
|
sizeof(control_subsystem_resource);
|
|
|
|
mech_mapper = new MechThrustmasterMapper( // FUN_004d21d0
|
|
viewing_mech,
|
|
0,
|
|
&control_subsystem_resource,
|
|
(ClassID)MechControlsMapperClassID, // 0x7dc
|
|
MechThrustmasterMapper::DefaultData // &DAT_0051dc68
|
|
);
|
|
}
|
|
primary_mapping_name = "Thrustmaster";
|
|
break;
|
|
|
|
case LBE4ControlsManager::PrimaryRIO: // 4
|
|
{
|
|
MechControlsMapper::SubsystemResource
|
|
control_subsystem_resource;
|
|
|
|
Str_Copy(
|
|
control_subsystem_resource.subsystemName,
|
|
"ControlsMapper",
|
|
sizeof(control_subsystem_resource.subsystemName)
|
|
);
|
|
control_subsystem_resource.classID = TrivialSubsystemClassID;
|
|
control_subsystem_resource.subsystemModelSize =
|
|
sizeof(control_subsystem_resource);
|
|
|
|
mech_mapper = new MechRIOMapper( // FUN_004d266c
|
|
viewing_mech,
|
|
0,
|
|
&control_subsystem_resource,
|
|
(ClassID)MechControlsMapperClassID, // 0x7dc
|
|
MechRIOMapper::DefaultData // &DAT_0051dcf0
|
|
);
|
|
}
|
|
primary_mapping_name = "L4";
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// TODO(bring-up): the shipped pod binary Fail()s here when no
|
|
// RIO/Thrustmaster is detected (primaryControlType==None on a
|
|
// dev box with no joystick). To let the spawn pipeline finish
|
|
// and the mech reach the render/frame loop, fall back to the
|
|
// RIO mapper (the pod's real L4 device). Keyboard/gamepad
|
|
// remap is a separate pod-bring-up task (CLAUDE.md Phase 8).
|
|
//
|
|
DEBUG_STREAM << "*****Mech has no controls mapping -- bring-up RIO fallback*****";
|
|
{
|
|
MechControlsMapper::SubsystemResource
|
|
control_subsystem_resource;
|
|
|
|
Str_Copy(
|
|
control_subsystem_resource.subsystemName,
|
|
"ControlsMapper",
|
|
sizeof(control_subsystem_resource.subsystemName)
|
|
);
|
|
control_subsystem_resource.classID = TrivialSubsystemClassID;
|
|
control_subsystem_resource.subsystemModelSize =
|
|
sizeof(control_subsystem_resource);
|
|
|
|
mech_mapper = new MechRIOMapper(
|
|
viewing_mech,
|
|
0,
|
|
&control_subsystem_resource,
|
|
(ClassID)MechControlsMapperClassID,
|
|
MechRIOMapper::DefaultData
|
|
);
|
|
}
|
|
primary_mapping_name = "L4";
|
|
break;
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// Attach the mapping subsystem and enable collision
|
|
//------------------------------------------------------------
|
|
viewing_mech->SetMappingSubsystem(mech_mapper); // FUN_0049fe40
|
|
viewing_mech->StartCollisionAssistant(); // vtbl[+0x38]
|
|
}
|
|
break;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Load the control-mapping resource stream for the entity
|
|
//-------------------------------------------------------------------
|
|
if (primary_mapping_name != NULL)
|
|
{
|
|
ResourceDescription
|
|
*resource_description =
|
|
application->GetResourceFile()->SearchList( // *(DAT_004efc94+0x68)
|
|
viewing_entity->GetResourceID(), // piVar5[0x6f]
|
|
ResourceDescription::ControlMappingsListResourceType // 6
|
|
);
|
|
|
|
if (resource_description != NULL)
|
|
{
|
|
resource_description =
|
|
GetResourceFile()->SearchList( // *(this+0x68)
|
|
resource_description->resourceID,
|
|
primary_mapping_name
|
|
);
|
|
|
|
if (resource_description != NULL)
|
|
{
|
|
resource_description->Lock(); // FUN_00406cd0
|
|
DEBUG_STREAM << "[ctrlmap] installing '" << primary_mapping_name
|
|
<< "' table: " << *(int *)resource_description->resourceAddress
|
|
<< " mappings on entity " << (void *)viewing_entity << std::endl;
|
|
controls->CreateStreamedMappings( // FUN_0047703c
|
|
viewing_entity,
|
|
(int*)resource_description->resourceAddress
|
|
);
|
|
resource_description->Unlock();
|
|
}
|
|
else
|
|
{
|
|
DEBUG_STREAM << "[ctrlmap] NO '" << primary_mapping_name
|
|
<< "' sub-table for this mech" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Configure the gauges for this entity
|
|
//-------------------------------------------------------------------
|
|
BTL4GaugeRenderer
|
|
*gauge_renderer = (BTL4GaugeRenderer*)GetGaugeRenderer(); // *(this+0x4c)
|
|
|
|
if (getenv("BT_VSS_LOG"))
|
|
DEBUG_STREAM << "[vss] MakeViewpointEntity: gaugeRenderer="
|
|
<< (int)(gauge_renderer != NULL) << " -> ConfigureForModel(Init)\n" << std::flush;
|
|
if (gauge_renderer != NULL)
|
|
{
|
|
gauge_renderer->ConfigureForModel("Init", viewing_entity); // FUN_0046ff64
|
|
}
|
|
|
|
return viewing_entity;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// RunMissionMessageHandler @004d3a2c
|
|
//#############################################################################
|
|
//
|
|
void
|
|
BTL4Application::RunMissionMessageHandler(RunMissionMessage *message)
|
|
{
|
|
Tell("BTL4Application::RunMissionMessageHandler\n"); // FUN_004dbb24/004d9c38
|
|
|
|
//
|
|
// Turn on the plasma score display: save the current mode mask and
|
|
// set the ModePlasmaDisplay bit (1<<22 == 0x400000) on the mode
|
|
// manager (this+0x50).
|
|
//
|
|
BTL4ModeManager
|
|
*mode_manager = (BTL4ModeManager*)GetModeManager(); // *(this+0x50)
|
|
|
|
// WinTesla ModeManager exposes the mask through AddModeMask(), which
|
|
// stashes the old mask into previousModeMask (== the DOS "savedMode"
|
|
// = "currentMode" step) and ORs in the new bits (== "currentMode |=").
|
|
mode_manager->AddModeMask(BTL4ModeManager::ModePlasmaDisplay); // |= 0x400000
|
|
|
|
Tell("Turning Plasma Score Display On\n");
|
|
|
|
L4Application::RunMissionMessageHandler(message); // FUN_0047b788
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// StopMissionMessageHandler @004d3a94
|
|
//#############################################################################
|
|
//
|
|
void
|
|
BTL4Application::StopMissionMessageHandler(StopMissionMessage *message)
|
|
{
|
|
//
|
|
// Turn off the plasma score display: save the current mode mask and
|
|
// clear the ModePlasmaDisplay bit (~0x400000 == 0xffbfffff).
|
|
//
|
|
BTL4ModeManager
|
|
*mode_manager = (BTL4ModeManager*)GetModeManager(); // *(this+0x50)
|
|
|
|
// RemoveModeMask() = stash old mask into previousModeMask (the DOS
|
|
// "savedMode = currentMode") then clear the bits ("currentMode &= ~").
|
|
mode_manager->RemoveModeMask(BTL4ModeManager::ModePlasmaDisplay); // &= 0xffbfffff
|
|
|
|
L4Application::StopMissionMessageHandler(message); // FUN_0047b864
|
|
|
|
Tell("Turning Plasma Score Display Off\n");
|
|
}
|